#ifndef BUILDINGPLAN_H #define BUILDINGPLAN_H #include "uicommon.h" #include // DF data structure definition headers #include "DataDefs.h" #include "Types.h" #include "df/build_req_choice_genst.h" #include "df/build_req_choice_specst.h" #include "df/item.h" #include "df/ui.h" #include "df/ui_build_selector.h" #include "df/viewscreen_dwarfmodest.h" #include "df/items_other_id.h" #include "df/job.h" #include "df/world.h" #include "df/building_constructionst.h" #include "df/building_design.h" #include "df/entity_position.h" #include "modules/Buildings.h" #include "modules/Maps.h" #include "modules/Items.h" #include "modules/Units.h" #include "modules/Gui.h" #include "TileTypes.h" #include "df/job_item.h" #include "df/dfhack_material_category.h" #include "df/general_ref_building_holderst.h" #include "modules/Job.h" #include "df/building_design.h" #include "df/buildings_other_id.h" #include "modules/World.h" #include "df/building.h" #include "df/building_doorst.h" using df::global::ui; using df::global::ui_build_selector; using df::global::world; struct MaterialDescriptor { df::item_type item_type; int16_t item_subtype; int16_t type; int32_t index; bool valid; bool matches(const MaterialDescriptor &a) const { return a.valid && valid && a.type == type && a.index == index && a.item_type == item_type && a.item_subtype == item_subtype; } }; #define MAX_MASK 10 #define MAX_MATERIAL 21 #define SIDEBAR_WIDTH 30 static bool canReserveRoom(df::building *building) { if (!building) return false; if (building->jobs.size() > 0 && building->jobs[0]->job_type == job_type::DestroyBuilding) return false; return building->is_room; } static std::vector getUniqueNoblePositions(df::unit *unit) { std::vector np; Units::getNoblePositions(&np, unit); for (auto iter = np.begin(); iter != np.end(); iter++) { if (iter->position->code == "MILITIA_CAPTAIN") { np.erase(iter); break; } } return np; } static void delete_item_fn(df::job_item *x); static MaterialInfo &material_info_identity_fn(MaterialInfo &m); static map planmode_enabled, saved_planmodes; static void enable_quickfort_fn(pair& pair); static void debug(const std::string &msg); static std::string material_to_string_fn(MaterialInfo m); static bool show_debugging = true; static bool show_help = false; struct ItemFilter { df::dfhack_material_category mat_mask; std::vector materials; df::item_quality min_quality; bool decorated_only; ItemFilter() : min_quality(df::item_quality::Ordinary), decorated_only(false), valid(true) { } bool matchesMask(DFHack::MaterialInfo &mat); bool matches(const df::dfhack_material_category mask) const; bool matches(DFHack::MaterialInfo &material) const; bool matches(df::item *item); std::vector getMaterialFilterAsVector(); std::string getMaterialFilterAsSerial(); bool parseSerializedMaterialTokens(std::string str); std::string getMinQuality(); bool isValid(); void clear(); private: bool valid; }; class ViewscreenChooseMaterial : public dfhack_viewscreen { public: ViewscreenChooseMaterial(ItemFilter *filter); void feed(set *input); void render(); std::string getFocusString() { return "buildingplan_choosemat"; } private: ListColumn masks_column; ListColumn materials_column; int selected_column; ItemFilter *filter; df::building_type btype; void addMaskEntry(df::dfhack_material_category &mask, const std::string &text) { auto entry = ListEntry(pad_string(text, MAX_MASK, false), mask); if (filter->matches(mask)) entry.selected = true; masks_column.add(entry); } void populateMasks() { masks_column.clear(); df::dfhack_material_category mask; mask.whole = 0; mask.bits.stone = true; addMaskEntry(mask, "Stone"); mask.whole = 0; mask.bits.wood = true; addMaskEntry(mask, "Wood"); mask.whole = 0; mask.bits.metal = true; addMaskEntry(mask, "Metal"); mask.whole = 0; mask.bits.soap = true; addMaskEntry(mask, "Soap"); masks_column.filterDisplay(); } void populateMaterials() { materials_column.clear(); df::dfhack_material_category selected_category; std::vector selected_masks = masks_column.getSelectedElems(); if (selected_masks.size() == 1) selected_category = selected_masks[0]; else if (selected_masks.size() > 1) return; df::world_raws &raws = world->raws; for (int i = 1; i < DFHack::MaterialInfo::NUM_BUILTIN; i++) { auto obj = raws.mat_table.builtin[i]; if (obj) { MaterialInfo material; material.decode(i, -1); addMaterialEntry(selected_category, material, material.toString()); } } for (size_t i = 0; i < raws.inorganics.size(); i++) { df::inorganic_raw *p = raws.inorganics[i]; MaterialInfo material; material.decode(0, i); addMaterialEntry(selected_category, material, material.toString()); } decltype(selected_category) wood_flag; wood_flag.bits.wood = true; if (!selected_category.whole || selected_category.bits.wood) { for (size_t i = 0; i < raws.plants.all.size(); i++) { df::plant_raw *p = raws.plants.all[i]; for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++) { auto t = p->material[j]; if (p->material[j]->id != "WOOD") continue; MaterialInfo material; material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i); auto name = material.toString(); ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); if (filter->matches(material)) entry.selected = true; materials_column.add(entry); } } } materials_column.sort(); } void addMaterialEntry(df::dfhack_material_category &selected_category, MaterialInfo &material, std::string name) { if (!selected_category.whole || material.matches(selected_category)) { ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); if (filter->matches(material)) entry.selected = true; materials_column.add(entry); } } void validateColumn() { set_to_limit(selected_column, 1); } void resize(int32_t x, int32_t y) { dfhack_viewscreen::resize(x, y); masks_column.resize(); materials_column.resize(); } }; class ReservedRoom { public: ReservedRoom(df::building *building, std::string noble_code); ReservedRoom(PersistentDataItem &config, color_ostream &out); bool checkRoomAssignment(); void remove() { DFHack::World::DeletePersistentData(config); } bool isValid() { if (!building) return false; if (Buildings::findAtTile(pos) != building) return false; return canReserveRoom(building); } int32_t getId() { if (!isValid()) return 0; return building->id; } std::string getCode() { return config.val(); } void setCode(const std::string &noble_code) { config.val() = noble_code; } private: df::building *building; PersistentDataItem config; df::coord pos; std::vector getOwnersNobleCode() { if (!building->owner) return std::vector (); return getUniqueNoblePositions(building->owner); } }; class RoomMonitor { public: RoomMonitor() { } std::string getReservedNobleCode(int32_t buildingId); void toggleRoomForPosition(int32_t buildingId, std::string noble_code); void doCycle(); void reset(color_ostream &out); private: std::vector reservedRooms; void addRoom(ReservedRoom &rr) { for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) { if (iter->getId() == rr.getId()) return; } reservedRooms.push_back(rr); } }; // START Planning class PlannedBuilding { public: PlannedBuilding(df::building *building, ItemFilter *filter); PlannedBuilding(PersistentDataItem &config, color_ostream &out); bool assignClosestItem(std::vector *items_vector); bool assignItem(df::item *item); bool isValid(); df::building_type getType() { return building->getType(); } bool isCurrentlySelectedBuilding() { return isValid() && (building == world->selected_building); } ItemFilter *getFilter() { return &filter; } void remove() { DFHack::World::DeletePersistentData(config); } private: df::building *building; PersistentDataItem config; df::coord pos; ItemFilter filter; }; class Planner { public: bool in_dummmy_screen; Planner() : quickfort_mode(false), in_dummmy_screen(false) { } bool isPlanableBuilding(const df::building_type type) const { return item_for_building_type.find(type) != item_for_building_type.end(); } void reset(color_ostream &out); void initialize(); void addPlannedBuilding(df::building *bld) { PlannedBuilding pb(bld, &default_item_filters[bld->getType()]); planned_buildings.push_back(pb); } void doCycle(); bool allocatePlannedBuilding(df::building_type type); PlannedBuilding *getSelectedPlannedBuilding(); void removeSelectedPlannedBuilding() { getSelectedPlannedBuilding()->remove(); } ItemFilter *getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; } void cycleDefaultQuality(df::building_type type); void enableQuickfortMode() { saved_planmodes = planmode_enabled; for_each_(planmode_enabled, enable_quickfort_fn); quickfort_mode = true; } void disableQuickfortMode() { planmode_enabled = saved_planmodes; quickfort_mode = false; } bool inQuickFortMode() { return quickfort_mode; } private: map item_for_building_type; map default_item_filters; map> available_item_vectors; map is_relevant_item_type; //Needed for fast check when looping over all items bool quickfort_mode; std::vector planned_buildings; void gather_available_items() { debug("Gather available items"); for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++) { iter->second.clear(); } // Precompute a bitmask with the bad flags df::item_flags bad_flags; bad_flags.whole = 0; #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); F(in_building); F(construction); F(artifact); #undef F std::vector &items = world->items.other[items_other_id::IN_PLAY]; for (size_t i = 0; i < items.size(); i++) { df::item *item = items[i]; if (item->flags.whole & bad_flags.whole) continue; df::item_type itype = item->getType(); if (!is_relevant_item_type[itype]) continue; if (itype == item_type::BOX && item->isBag()) continue; //Skip bags if (item->flags.bits.artifact) continue; if (item->flags.bits.in_job || item->isAssignedToStockpile() || item->flags.bits.owned || item->flags.bits.in_chest) { continue; } available_item_vectors[itype].push_back(item); } } }; static Planner planner; static RoomMonitor roomMonitor; #endif