widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #17649
Re: [Merge] lp:~widelands-dev/widelands/constructionsite_options into lp:widelands
Review: Needs Fixing
I finally got around to doing the code review. This branch still has a lot of room for streamlining the code and the performance.
Diff comments:
>
> === 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-28 15:09:01 +0000
> @@ -0,0 +1,346 @@
> +/*
> + * 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) {
Why not simply use
for (const auto& WareAmount amount : descr.input_wares()) {
}
And get rid of the WareRange struct?
> + 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);
Use WareDescr::has_demand_check to not add wares like log, wheat.
> + }
> + for (const DescriptionIndex di : tribe.workers()) {
> + worker_preferences.emplace(di, StockPolicy::kNormal);
If you use WorkerDescr::has_demand_check() here, you can get rid of the loop below where you erase them.
> + }
> + 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) {
These 2 data structures should have the same keys always - so you should be able to get the element from the second map directly and get rid of the inner loop. Same below for the workers.
> + 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;
> + }
> + }
> + if (!result) {
> + 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
>
> === modified file 'src/logic/map_objects/tribes/constructionsite.cc'
> --- src/logic/map_objects/tribes/constructionsite.cc 2019-05-26 17:21:15 +0000
> +++ src/logic/map_objects/tribes/constructionsite.cc 2019-05-28 15:09:01 +0000
> @@ -46,32 +51,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", nullptr) :
> - 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", nullptr) :
> + 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");
Basically the same code as for the intermediates. We could have a local lambda function here to get rid of the code duplication.
> + const uint32_t anim_idx = known ?
> + becomes->get_animation("build", nullptr) :
> + 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 - 1) : 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);
> }
>
> /**
> @@ -207,6 +268,52 @@
> 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());
> + if (ts->desired_capacity != b.mutable_soldier_control()->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());
> + if (ms->desired_capacity != b.mutable_soldier_control()->soldier_capacity()) {
You don't need the mutable in this line, only in the line below.
> + 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));
> }
> @@ -214,6 +321,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) {
I don't see any iterator manipulation here, so you can use a range-based for loop, which is easier to read.
> + 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();
I don't see any iterator manipulation here, so you can use a range-based for loop, which is easier to read.
> +
> + 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) {
Investigate whether you can get rid of the 4 double-loops in this function too (see above). No is a perfectly good answer ;)
> + 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)) {
Is there a case where the old settings would not be ProductionSite settings? If not, a dynamic_cast followed by an assert would ensure the correct behaviour.
> + 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.
> ===============
> */
>
> === modified file 'src/logic/map_objects/tribes/militarysite.cc'
> --- src/logic/map_objects/tribes/militarysite.cc 2019-05-26 17:21:15 +0000
> +++ src/logic/map_objects/tribes/militarysite.cc 2019-05-28 15:09:01 +0000
> @@ -982,6 +982,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;
In trunk, we prefer rookies for small buildings like the Barbarian Sentry.
> + return settings;
> +}
> +
> // setters
>
> void MilitarySite::set_soldier_preference(SoldierPreference p) {
>
> === 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-28 15:09:01 +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_;
Everything from here below is identical to ProdutionSite. Find a common spot where to parse this so that we can get rid of the code duplication.
> + 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/playercommand.cc'
> --- src/logic/playercommand.cc 2019-05-25 10:54:30 +0000
> +++ src/logic/playercommand.cc 2019-05-28 15:09:01 +0000
> @@ -1906,4 +1930,599 @@
> // 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_))) {
Can these not all be handled by the existing player commands like CmdChangeSoldierCapacity, by detecting there whether it's a construction site or not?
> + 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();
> + }
> + 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();
> + }
> + 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/wui/constructionsitewindow.cc'
> --- src/wui/constructionsitewindow.cc 2019-02-23 11:00:49 +0000
> +++ src/wui/constructionsitewindow.cc 2019-05-28 15:09:01 +0000
> @@ -22,9 +22,270 @@
> #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_settings[] = "images/wui/menus/menu_stock.png";
> +static const char pic_tab_settings_wares[] = "images/wui/stats/menu_tab_wares_warehouse.png";
> +static const char pic_tab_settings_workers[] = "images/wui/stats/menu_tab_workers_warehouse.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";
> +static const char pic_decrease_capacity[] = "images/wui/buildings/menu_down_train.png";
> +static const char pic_increase_capacity[] = "images/wui/buildings/menu_up_train.png";
> +
> +constexpr uint32_t kPriorityButtonSize = 10;
> +constexpr uint32_t kFakeInputQueueWareWidth = WARE_MENU_PIC_WIDTH + 2;
> +constexpr uint32_t kFakeInputQueueWareHeight = 3 * kPriorityButtonSize;
> +constexpr uint32_t kFakeInputQueueButtonSize = 24;
> +
> +ConstructionSiteWindow::FakeInputQueue::FakeInputQueue(Panel* parent,
Create a common superclass for this and the real InputQueue to get rid of code duplication
> + int32_t x,
> + int32_t y,
> + bool can_act,
> + Widelands::ConstructionSite& cs,
> + Widelands::WareWorker ww,
> + Widelands::DescriptionIndex di)
> + : UI::Panel(parent, x, y, 0, 0),
> + 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)),
> + priority_group_(nullptr) {
> + 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, (kFakeInputQueueWareHeight - kFakeInputQueueButtonSize) / 2,
> + kFakeInputQueueButtonSize, kFakeInputQueueButtonSize,
> + 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", kFakeInputQueueButtonSize + kFakeInputQueueWareWidth * max_fill_ + 8,
> + (kFakeInputQueueWareHeight - kFakeInputQueueButtonSize) / 2,
> + kFakeInputQueueButtonSize, kFakeInputQueueButtonSize, 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);
> + if (type_ == Widelands::wwWARE) {
> + priority_group_.reset(new UI::Radiogroup());
> + Vector2i pos(kFakeInputQueueWareWidth * max_fill_ + 2 * kFakeInputQueueButtonSize + 10, 0);
> + priority_group_->add_button(
> + this, pos, g_gr->images().get(pic_priority_high), _("Highest priority"));
> + pos.y += kPriorityButtonSize;
> + priority_group_->add_button(
> + this, pos, g_gr->images().get(pic_priority_normal), _("Normal priority"));
> + pos.y += kPriorityButtonSize;
> + priority_group_->add_button(
> + this, pos, g_gr->images().get(pic_priority_low), _("Lowest priority"));
> + if (can_act) {
> + priority_group_->changedto.connect([this](uint32_t state) {
> + Widelands::Game& game = dynamic_cast<Widelands::Game&>(constructionsite_.get_owner()->egbase());
> + int32_t priority;
> + switch (state) {
> + case 0:
> + priority = Widelands::kPriorityHigh;
> + break;
> + case 1:
> + priority = Widelands::kPriorityNormal;
> + break;
> + case 2:
> + priority = Widelands::kPriorityLow;
> + break;
> + default:
> + return;
> + }
> + if (SDL_GetModState() & KMOD_CTRL) {
> + for (const auto& pair : settings_.ware_queues) {
> + game.send_player_constructionsite_input_queue_priority(constructionsite_,
> + Widelands::wwWARE, pair.first, priority);
> + }
> + for (const auto& pair : settings_.worker_queues) {
> + game.send_player_constructionsite_input_queue_priority(constructionsite_,
> + Widelands::wwWORKER, pair.first, priority);
> + }
> + } else {
> + game.send_player_constructionsite_input_queue_priority(constructionsite_,
> + type_, index_, priority);
> + }
> + });
> + }
> + }
> + decrease.set_enabled(can_act);
> + increase.set_enabled(can_act);
> + update_desired_size();
> +}
> +
> +void ConstructionSiteWindow::FakeInputQueue::update_desired_size() {
> + set_desired_size(kFakeInputQueueWareWidth * max_fill_ + 2 * kFakeInputQueueButtonSize + kPriorityButtonSize + 12,
> + 3 * kPriorityButtonSize);
> +}
> +
> +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();
> + }
> + NEVER_HERE();
> +}
> +
> +void ConstructionSiteWindow::FakeInputQueue::think() {
> + UI::Panel::think();
> + if (priority_group_) {
> + switch (get_settings().priority) {
> + case Widelands::kPriorityHigh:
> + priority_group_->set_state(0);
> + break;
> + case Widelands::kPriorityNormal:
> + priority_group_->set_state(1);
> + break;
> + case Widelands::kPriorityLow:
> + priority_group_->set_state(2);
> + break;
> + default:
> + NEVER_HERE();
> + }
> + }
> +}
> +
> +void ConstructionSiteWindow::FakeInputQueue::draw(RenderTarget& dst) {
> + UI::Panel::draw(dst);
> +
> + Vector2i point = Vector2i::zero();
> + point.x = kFakeInputQueueButtonSize + 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,
--
https://code.launchpad.net/~widelands-dev/widelands/constructionsite_options/+merge/367428
Your team Widelands Developers is subscribed to branch lp:~widelands-dev/widelands/constructionsite_options.
References