Merge remote-tracking branch 'myk002/buildingplan_format' into develop
						commit
						577bb0a258
					
				| @ -0,0 +1,552 @@ | |||||||
|  | #include "df/general_ref_building_holderst.h" | ||||||
|  | #include "df/job_item.h" | ||||||
|  | #include "df/building_doorst.h" | ||||||
|  | #include "df/building_design.h" | ||||||
|  | 
 | ||||||
|  | #include "modules/Job.h" | ||||||
|  | #include "modules/Buildings.h" | ||||||
|  | #include "modules/Gui.h" | ||||||
|  | 
 | ||||||
|  | #include "buildingplan-planner.h" | ||||||
|  | #include "buildingplan-lib.h" | ||||||
|  | #include "uicommon.h" | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | * ItemFilter | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | ItemFilter::ItemFilter() : | ||||||
|  |         min_quality(df::item_quality::Ordinary), | ||||||
|  |         max_quality(df::item_quality::Artifact), | ||||||
|  |         decorated_only(false), | ||||||
|  |         valid(true) | ||||||
|  | { | ||||||
|  |     clear(); // mat_mask is not cleared by default (see issue #1047)
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) | ||||||
|  | { | ||||||
|  |     return (mat_mask.whole) ? mat.matches(mat_mask) : true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ItemFilter::matches(const df::dfhack_material_category mask) const | ||||||
|  | { | ||||||
|  |     return mask.whole & mat_mask.whole; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ItemFilter::matches(DFHack::MaterialInfo &material) const | ||||||
|  | { | ||||||
|  |     for (auto it = materials.begin(); it != materials.end(); ++it) | ||||||
|  |         if (material.matches(*it)) | ||||||
|  |             return true; | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ItemFilter::matches(df::item *item) | ||||||
|  | { | ||||||
|  |     if (item->getQuality() < min_quality || item->getQuality() > max_quality) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     if (decorated_only && !item->hasImprovements()) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     auto imattype = item->getActualMaterial(); | ||||||
|  |     auto imatindex = item->getActualMaterialIndex(); | ||||||
|  |     auto item_mat = DFHack::MaterialInfo(imattype, imatindex); | ||||||
|  | 
 | ||||||
|  |     return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } | ||||||
|  | 
 | ||||||
|  | std::vector<std::string> ItemFilter::getMaterialFilterAsVector() | ||||||
|  | { | ||||||
|  |     std::vector<std::string> descriptions; | ||||||
|  | 
 | ||||||
|  |     transform_(materials, descriptions, material_to_string_fn); | ||||||
|  | 
 | ||||||
|  |     if (descriptions.size() == 0) | ||||||
|  |         bitfield_to_string(&descriptions, mat_mask); | ||||||
|  | 
 | ||||||
|  |     if (descriptions.size() == 0) | ||||||
|  |         descriptions.push_back("any"); | ||||||
|  | 
 | ||||||
|  |     return descriptions; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string ItemFilter::getMaterialFilterAsSerial() | ||||||
|  | { | ||||||
|  |     std::string str; | ||||||
|  | 
 | ||||||
|  |     str.append(bitfield_to_string(mat_mask, ",")); | ||||||
|  |     str.append("/"); | ||||||
|  |     if (materials.size() > 0) | ||||||
|  |     { | ||||||
|  |         for (size_t i = 0; i < materials.size(); i++) | ||||||
|  |             str.append(materials[i].getToken() + ","); | ||||||
|  | 
 | ||||||
|  |         if (str[str.size()-1] == ',') | ||||||
|  |             str.resize(str.size () - 1); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ItemFilter::parseSerializedMaterialTokens(std::string str) | ||||||
|  | { | ||||||
|  |     valid = false; | ||||||
|  |     std::vector<std::string> tokens; | ||||||
|  |     split_string(&tokens, str, "/"); | ||||||
|  | 
 | ||||||
|  |     if (tokens.size() > 0 && !tokens[0].empty()) | ||||||
|  |     { | ||||||
|  |         if (!parseJobMaterialCategory(&mat_mask, tokens[0])) | ||||||
|  |             return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (tokens.size() > 1 && !tokens[1].empty()) | ||||||
|  |     { | ||||||
|  |         std::vector<std::string> mat_names; | ||||||
|  |         split_string(&mat_names, tokens[1], ","); | ||||||
|  |         for (auto m = mat_names.begin(); m != mat_names.end(); m++) | ||||||
|  |         { | ||||||
|  |             DFHack::MaterialInfo material; | ||||||
|  |             if (!material.find(*m) || !material.isValid()) | ||||||
|  |                 return false; | ||||||
|  | 
 | ||||||
|  |             materials.push_back(material); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     valid = true; | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string ItemFilter::getMinQuality() | ||||||
|  | { | ||||||
|  |     return ENUM_KEY_STR(item_quality, min_quality); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string ItemFilter::getMaxQuality() | ||||||
|  | { | ||||||
|  |     return ENUM_KEY_STR(item_quality, max_quality); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ItemFilter::isValid() | ||||||
|  | { | ||||||
|  |     return valid; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ItemFilter::clear() | ||||||
|  | { | ||||||
|  |     mat_mask.whole = 0; | ||||||
|  |     materials.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | * PlannedBuilding | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter) | ||||||
|  | { | ||||||
|  |     this->building = building; | ||||||
|  |     this->filter = *filter; | ||||||
|  |     pos = df::coord(building->centerx, building->centery, building->z); | ||||||
|  |     config = DFHack::World::AddPersistentData("buildingplan/constraints"); | ||||||
|  |     config.val() = filter->getMaterialFilterAsSerial(); | ||||||
|  |     config.ival(1) = building->id; | ||||||
|  |     config.ival(2) = filter->min_quality + 1; | ||||||
|  |     config.ival(3) = static_cast<int>(filter->decorated_only) + 1; | ||||||
|  |     config.ival(4) = filter->max_quality + 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out) | ||||||
|  | { | ||||||
|  |     this->config = config; | ||||||
|  | 
 | ||||||
|  |     if (!filter.parseSerializedMaterialTokens(config.val())) | ||||||
|  |     { | ||||||
|  |         out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str()); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     building = df::building::find(config.ival(1)); | ||||||
|  |     if (!building) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     pos = df::coord(building->centerx, building->centery, building->z); | ||||||
|  |     filter.min_quality = static_cast<df::item_quality>(config.ival(2) - 1); | ||||||
|  |     filter.max_quality = static_cast<df::item_quality>(config.ival(4) - 1); | ||||||
|  |     filter.decorated_only = config.ival(3) - 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool PlannedBuilding::assignClosestItem(std::vector<df::item *> *items_vector) | ||||||
|  | { | ||||||
|  |     decltype(items_vector->begin()) closest_item; | ||||||
|  |     int32_t closest_distance = -1; | ||||||
|  |     for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++) | ||||||
|  |     { | ||||||
|  |         auto item = *item_iter; | ||||||
|  |         if (!filter.matches(item)) | ||||||
|  |             continue; | ||||||
|  | 
 | ||||||
|  |         auto pos = item->pos; | ||||||
|  |         auto distance = abs(pos.x - building->centerx) + | ||||||
|  |             abs(pos.y - building->centery) + | ||||||
|  |             abs(pos.z - building->z) * 50; | ||||||
|  | 
 | ||||||
|  |         if (closest_distance > -1 && distance >= closest_distance) | ||||||
|  |             continue; | ||||||
|  | 
 | ||||||
|  |         closest_distance = distance; | ||||||
|  |         closest_item = item_iter; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (closest_distance > -1 && assignItem(*closest_item)) | ||||||
|  |     { | ||||||
|  |         debug("Item assigned"); | ||||||
|  |         items_vector->erase(closest_item); | ||||||
|  |         remove(); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void delete_item_fn(df::job_item *x) { delete x; } | ||||||
|  | 
 | ||||||
|  | bool PlannedBuilding::assignItem(df::item *item) | ||||||
|  | { | ||||||
|  |     auto ref = df::allocate<df::general_ref_building_holderst>(); | ||||||
|  |     if (!ref) | ||||||
|  |     { | ||||||
|  |         Core::printerr("Could not allocate general_ref_building_holderst\n"); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ref->building_id = building->id; | ||||||
|  | 
 | ||||||
|  |     if (building->jobs.size() != 1) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     auto job = building->jobs[0]; | ||||||
|  | 
 | ||||||
|  |     for_each_(job->job_items, delete_item_fn); | ||||||
|  |     job->job_items.clear(); | ||||||
|  |     job->flags.bits.suspend = false; | ||||||
|  | 
 | ||||||
|  |     bool rough = false; | ||||||
|  |     Job::attachJobItem(job, item, df::job_item_ref::Hauled); | ||||||
|  |     if (item->getType() == item_type::BOULDER) | ||||||
|  |         rough = true; | ||||||
|  |     building->mat_type = item->getMaterial(); | ||||||
|  |     building->mat_index = item->getMaterialIndex(); | ||||||
|  | 
 | ||||||
|  |     job->mat_type = building->mat_type; | ||||||
|  |     job->mat_index = building->mat_index; | ||||||
|  | 
 | ||||||
|  |     if (building->needsDesign()) | ||||||
|  |     { | ||||||
|  |         auto act = (df::building_actual *) building; | ||||||
|  |         act->design = new df::building_design(); | ||||||
|  |         act->design->flags.bits.rough = rough; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool PlannedBuilding::isValid() | ||||||
|  | { | ||||||
|  |     bool valid = filter.isValid() && | ||||||
|  |         building && Buildings::findAtTile(pos) == building && | ||||||
|  |         building->getBuildStage() == 0; | ||||||
|  | 
 | ||||||
|  |     if (!valid) | ||||||
|  |         remove(); | ||||||
|  | 
 | ||||||
|  |     return valid; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | df::building_type PlannedBuilding::getType() | ||||||
|  | { | ||||||
|  |     return building->getType(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool PlannedBuilding::isCurrentlySelectedBuilding() | ||||||
|  | { | ||||||
|  |     return isValid() && (building == df::global::world->selected_building); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ItemFilter *PlannedBuilding::getFilter() | ||||||
|  | { | ||||||
|  |     return &filter; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlannedBuilding::remove() | ||||||
|  | { | ||||||
|  |     DFHack::World::DeletePersistentData(config); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | * Planner | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | Planner::Planner() : in_dummmy_screen(false), quickfort_mode(false) { } | ||||||
|  | 
 | ||||||
|  | void enable_quickfort_fn(pair<const df::building_type, bool>& pair) { pair.second = true; } | ||||||
|  | 
 | ||||||
|  | bool Planner::isPlanableBuilding(const df::building_type type) const | ||||||
|  | { | ||||||
|  |     return item_for_building_type.find(type) != item_for_building_type.end(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Planner::reset(color_ostream &out) | ||||||
|  | { | ||||||
|  |     planned_buildings.clear(); | ||||||
|  |     std::vector<PersistentDataItem> items; | ||||||
|  |     DFHack::World::GetPersistentData(&items, "buildingplan/constraints"); | ||||||
|  | 
 | ||||||
|  |     for (auto i = items.begin(); i != items.end(); i++) | ||||||
|  |     { | ||||||
|  |         PlannedBuilding pb(*i, out); | ||||||
|  |         if (pb.isValid()) | ||||||
|  |             planned_buildings.push_back(pb); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Planner::initialize() | ||||||
|  | { | ||||||
|  | #define add_building_type(btype, itype) \ | ||||||
|  |     item_for_building_type[df::building_type::btype] = df::item_type::itype; \ | ||||||
|  |     default_item_filters[df::building_type::btype] =  ItemFilter(); \ | ||||||
|  |     available_item_vectors[df::item_type::itype] = std::vector<df::item *>(); \ | ||||||
|  |     is_relevant_item_type[df::item_type::itype] = true; \ | ||||||
|  |     if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \ | ||||||
|  |         planmode_enabled[df::building_type::btype] = false | ||||||
|  | 
 | ||||||
|  |     FOR_ENUM_ITEMS(item_type, it) | ||||||
|  |         is_relevant_item_type[it] = false; | ||||||
|  | 
 | ||||||
|  |     add_building_type(Armorstand, ARMORSTAND); | ||||||
|  |     add_building_type(Bed, BED); | ||||||
|  |     add_building_type(Chair, CHAIR); | ||||||
|  |     add_building_type(Coffin, COFFIN); | ||||||
|  |     add_building_type(Door, DOOR); | ||||||
|  |     add_building_type(Floodgate, FLOODGATE); | ||||||
|  |     add_building_type(Hatch, HATCH_COVER); | ||||||
|  |     add_building_type(GrateWall, GRATE); | ||||||
|  |     add_building_type(GrateFloor, GRATE); | ||||||
|  |     add_building_type(BarsVertical, BAR); | ||||||
|  |     add_building_type(BarsFloor, BAR); | ||||||
|  |     add_building_type(Cabinet, CABINET); | ||||||
|  |     add_building_type(Box, BOX); | ||||||
|  |     // skip kennels, farm plot
 | ||||||
|  |     add_building_type(Weaponrack, WEAPONRACK); | ||||||
|  |     add_building_type(Statue, STATUE); | ||||||
|  |     add_building_type(Slab, SLAB); | ||||||
|  |     add_building_type(Table, TABLE); | ||||||
|  |     // skip roads ... furnaces
 | ||||||
|  |     add_building_type(WindowGlass, WINDOW); | ||||||
|  |     // skip gem window ... support
 | ||||||
|  |     add_building_type(AnimalTrap, ANIMALTRAP); | ||||||
|  |     add_building_type(Chain, CHAIN); | ||||||
|  |     add_building_type(Cage, CAGE); | ||||||
|  |     // skip archery target
 | ||||||
|  |     add_building_type(TractionBench, TRACTION_BENCH); | ||||||
|  |     // skip nest box, hive (tools)
 | ||||||
|  | 
 | ||||||
|  | #undef add_building_type | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Planner::addPlannedBuilding(df::building *bld) | ||||||
|  | { | ||||||
|  |     for (auto iter = bld->jobs.begin(); iter != bld->jobs.end(); iter++) | ||||||
|  |     { | ||||||
|  |         (*iter)->flags.bits.suspend = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     PlannedBuilding pb(bld, &default_item_filters[bld->getType()]); | ||||||
|  |     planned_buildings.push_back(pb); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Planner::doCycle() | ||||||
|  | { | ||||||
|  |     debug("Running Cycle"); | ||||||
|  |     if (planned_buildings.size() == 0) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     debug("Planned count: " + int_to_string(planned_buildings.size())); | ||||||
|  | 
 | ||||||
|  |     gather_available_items(); | ||||||
|  |     for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) | ||||||
|  |     { | ||||||
|  |         if (building_iter->isValid()) | ||||||
|  |         { | ||||||
|  |             if (show_debugging) | ||||||
|  |                 debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType())); | ||||||
|  | 
 | ||||||
|  |             auto required_item_type = item_for_building_type[building_iter->getType()]; | ||||||
|  |             auto items_vector = &available_item_vectors[required_item_type]; | ||||||
|  |             if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector)) | ||||||
|  |             { | ||||||
|  |                 debug("Unable to allocate an item"); | ||||||
|  |                 ++building_iter; | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         debug("Removing building plan"); | ||||||
|  |         building_iter = planned_buildings.erase(building_iter); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool Planner::allocatePlannedBuilding(df::building_type type) | ||||||
|  | { | ||||||
|  |     coord32_t cursor; | ||||||
|  |     if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     auto newinst = Buildings::allocInstance(cursor.get_coord16(), type); | ||||||
|  |     if (!newinst) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     df::job_item *filter = new df::job_item(); | ||||||
|  |     filter->item_type = item_type::NONE; | ||||||
|  |     filter->mat_index = 0; | ||||||
|  |     filter->flags2.bits.building_material = true; | ||||||
|  |     std::vector<df::job_item*> filters; | ||||||
|  |     filters.push_back(filter); | ||||||
|  | 
 | ||||||
|  |     if (!Buildings::constructWithFilters(newinst, filters)) | ||||||
|  |     { | ||||||
|  |         delete newinst; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (type == building_type::Door) | ||||||
|  |     { | ||||||
|  |         auto door = virtual_cast<df::building_doorst>(newinst); | ||||||
|  |         if (door) | ||||||
|  |             door->door_flags.bits.pet_passable = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     addPlannedBuilding(newinst); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | PlannedBuilding *Planner::getSelectedPlannedBuilding() | ||||||
|  | { | ||||||
|  |     for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++) | ||||||
|  |     { | ||||||
|  |         if (building_iter->isCurrentlySelectedBuilding()) | ||||||
|  |         { | ||||||
|  |             return &(*building_iter); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Planner::removeSelectedPlannedBuilding() { getSelectedPlannedBuilding()->remove(); } | ||||||
|  | 
 | ||||||
|  | ItemFilter *Planner::getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; } | ||||||
|  | 
 | ||||||
|  | void Planner::adjustMinQuality(df::building_type type, int amount) | ||||||
|  | { | ||||||
|  |     auto min_quality = &getDefaultItemFilterForType(type)->min_quality; | ||||||
|  |     *min_quality = static_cast<df::item_quality>(*min_quality + amount); | ||||||
|  | 
 | ||||||
|  |     boundsCheckItemQuality(min_quality); | ||||||
|  |     auto max_quality = &getDefaultItemFilterForType(type)->max_quality; | ||||||
|  |     if (*min_quality > *max_quality) | ||||||
|  |         (*max_quality) = *min_quality; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Planner::adjustMaxQuality(df::building_type type, int amount) | ||||||
|  | { | ||||||
|  |     auto max_quality = &getDefaultItemFilterForType(type)->max_quality; | ||||||
|  |     *max_quality = static_cast<df::item_quality>(*max_quality + amount); | ||||||
|  | 
 | ||||||
|  |     boundsCheckItemQuality(max_quality); | ||||||
|  |     auto min_quality = &getDefaultItemFilterForType(type)->min_quality; | ||||||
|  |     if (*max_quality < *min_quality) | ||||||
|  |         (*min_quality) = *max_quality; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Planner::enableQuickfortMode() | ||||||
|  | { | ||||||
|  |     saved_planmodes = planmode_enabled; | ||||||
|  |     for_each_(planmode_enabled, enable_quickfort_fn); | ||||||
|  | 
 | ||||||
|  |     quickfort_mode = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Planner::disableQuickfortMode() | ||||||
|  | { | ||||||
|  |     planmode_enabled = saved_planmodes; | ||||||
|  |     quickfort_mode = false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool Planner::inQuickFortMode() { return quickfort_mode; } | ||||||
|  | 
 | ||||||
|  | void Planner::boundsCheckItemQuality(item_quality::item_quality *quality) | ||||||
|  | { | ||||||
|  |     *quality = static_cast<df::item_quality>(*quality); | ||||||
|  |     if (*quality > item_quality::Artifact) | ||||||
|  |         (*quality) = item_quality::Artifact; | ||||||
|  |     if (*quality < item_quality::Ordinary) | ||||||
|  |         (*quality) = item_quality::Ordinary; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Planner::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<df::item*> &items = df::global::world->items.other[df::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 == df::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); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::map<df::building_type, bool> planmode_enabled, saved_planmodes; | ||||||
|  | Planner planner; | ||||||
| @ -0,0 +1,108 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "df/item_quality.h" | ||||||
|  | #include "df/dfhack_material_category.h" | ||||||
|  | #include "modules/Materials.h" | ||||||
|  | #include "modules/Persistence.h" | ||||||
|  | 
 | ||||||
|  | struct ItemFilter | ||||||
|  | { | ||||||
|  |     df::dfhack_material_category mat_mask; | ||||||
|  |     std::vector<DFHack::MaterialInfo> materials; | ||||||
|  |     df::item_quality min_quality; | ||||||
|  |     df::item_quality max_quality; | ||||||
|  |     bool decorated_only; | ||||||
|  | 
 | ||||||
|  |     ItemFilter(); | ||||||
|  | 
 | ||||||
|  |     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<std::string> getMaterialFilterAsVector(); | ||||||
|  |     std::string getMaterialFilterAsSerial(); | ||||||
|  |     bool parseSerializedMaterialTokens(std::string str); | ||||||
|  | 
 | ||||||
|  |     std::string getMinQuality(); | ||||||
|  |     std::string getMaxQuality(); | ||||||
|  | 
 | ||||||
|  |     bool isValid(); | ||||||
|  |     void clear(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     bool valid; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class PlannedBuilding | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |     PlannedBuilding(df::building *building, ItemFilter *filter); | ||||||
|  |     PlannedBuilding(DFHack::PersistentDataItem &config, DFHack::color_ostream &out); | ||||||
|  | 
 | ||||||
|  |     bool assignClosestItem(std::vector<df::item *> *items_vector); | ||||||
|  |     bool assignItem(df::item *item); | ||||||
|  | 
 | ||||||
|  |     bool isValid(); | ||||||
|  |     void remove(); | ||||||
|  | 
 | ||||||
|  |     df::building_type getType(); | ||||||
|  |     bool isCurrentlySelectedBuilding(); | ||||||
|  | 
 | ||||||
|  |     ItemFilter *getFilter(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     df::building *building; | ||||||
|  |     DFHack::PersistentDataItem config; | ||||||
|  |     df::coord pos; | ||||||
|  |     ItemFilter filter; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class Planner | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |     bool in_dummmy_screen; | ||||||
|  | 
 | ||||||
|  |     Planner(); | ||||||
|  | 
 | ||||||
|  |     bool isPlanableBuilding(const df::building_type type) const; | ||||||
|  | 
 | ||||||
|  |     void reset(DFHack::color_ostream &out); | ||||||
|  | 
 | ||||||
|  |     void initialize(); | ||||||
|  | 
 | ||||||
|  |     void addPlannedBuilding(df::building *bld); | ||||||
|  | 
 | ||||||
|  |     void doCycle(); | ||||||
|  | 
 | ||||||
|  |     bool allocatePlannedBuilding(df::building_type type); | ||||||
|  | 
 | ||||||
|  |     PlannedBuilding *getSelectedPlannedBuilding(); | ||||||
|  | 
 | ||||||
|  |     void removeSelectedPlannedBuilding(); | ||||||
|  | 
 | ||||||
|  |     ItemFilter *getDefaultItemFilterForType(df::building_type type); | ||||||
|  | 
 | ||||||
|  |     void adjustMinQuality(df::building_type type, int amount); | ||||||
|  |     void adjustMaxQuality(df::building_type type, int amount); | ||||||
|  | 
 | ||||||
|  |     void enableQuickfortMode(); | ||||||
|  |     void disableQuickfortMode(); | ||||||
|  |     bool inQuickFortMode(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::map<df::building_type, df::item_type> item_for_building_type; | ||||||
|  |     std::map<df::building_type, ItemFilter> default_item_filters; | ||||||
|  |     std::map<df::item_type, std::vector<df::item *>> available_item_vectors; | ||||||
|  |     std::map<df::item_type, bool> is_relevant_item_type; //Needed for fast check when looping over all items
 | ||||||
|  |     bool quickfort_mode; | ||||||
|  | 
 | ||||||
|  |     std::vector<PlannedBuilding> planned_buildings; | ||||||
|  | 
 | ||||||
|  |     void boundsCheckItemQuality(df::enums::item_quality::item_quality *quality); | ||||||
|  | 
 | ||||||
|  |     void gather_available_items(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | extern std::map<df::building_type, bool> planmode_enabled, saved_planmodes; | ||||||
|  | extern Planner planner; | ||||||
| @ -0,0 +1,227 @@ | |||||||
|  | #include "buildingplan-rooms.h" | ||||||
|  | #include "buildingplan-lib.h" | ||||||
|  | 
 | ||||||
|  | #include <df/entity_position.h> | ||||||
|  | #include <df/job_type.h> | ||||||
|  | #include <df/world.h> | ||||||
|  | 
 | ||||||
|  | #include <modules/World.h> | ||||||
|  | #include <modules/Units.h> | ||||||
|  | #include <modules/Buildings.h> | ||||||
|  | 
 | ||||||
|  | using namespace DFHack; | ||||||
|  | 
 | ||||||
|  | bool canReserveRoom(df::building *building) | ||||||
|  | { | ||||||
|  |     if (!building) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     if (building->jobs.size() > 0 && building->jobs[0]->job_type == df::job_type::DestroyBuilding) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     return building->is_room; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<Units::NoblePosition> getUniqueNoblePositions(df::unit *unit) | ||||||
|  | { | ||||||
|  |     std::vector<Units::NoblePosition> 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; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * ReservedRoom | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | ReservedRoom::ReservedRoom(df::building *building, std::string noble_code) | ||||||
|  | { | ||||||
|  |     this->building = building; | ||||||
|  |     config = DFHack::World::AddPersistentData("buildingplan/reservedroom"); | ||||||
|  |     config.val() = noble_code; | ||||||
|  |     config.ival(1) = building->id; | ||||||
|  |     pos = df::coord(building->centerx, building->centery, building->z); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ReservedRoom::ReservedRoom(PersistentDataItem &config, color_ostream &) | ||||||
|  | { | ||||||
|  |     this->config = config; | ||||||
|  | 
 | ||||||
|  |     building = df::building::find(config.ival(1)); | ||||||
|  |     if (!building) | ||||||
|  |         return; | ||||||
|  |     pos = df::coord(building->centerx, building->centery, building->z); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ReservedRoom::checkRoomAssignment() | ||||||
|  | { | ||||||
|  |     if (!isValid()) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     auto np = getOwnersNobleCode(); | ||||||
|  |     bool correctOwner = false; | ||||||
|  |     for (auto iter = np.begin(); iter != np.end(); iter++) | ||||||
|  |     { | ||||||
|  |         if (iter->position->code == getCode()) | ||||||
|  |         { | ||||||
|  |             correctOwner = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (correctOwner) | ||||||
|  |         return true; | ||||||
|  | 
 | ||||||
|  |     for (auto iter = df::global::world->units.active.begin(); iter != df::global::world->units.active.end(); iter++) | ||||||
|  |     { | ||||||
|  |         df::unit* unit = *iter; | ||||||
|  |         if (!Units::isCitizen(unit)) | ||||||
|  |             continue; | ||||||
|  | 
 | ||||||
|  |         if (!Units::isActive(unit)) | ||||||
|  |             continue; | ||||||
|  | 
 | ||||||
|  |         np = getUniqueNoblePositions(unit); | ||||||
|  |         for (auto iter = np.begin(); iter != np.end(); iter++) | ||||||
|  |         { | ||||||
|  |             if (iter->position->code == getCode()) | ||||||
|  |             { | ||||||
|  |                 Buildings::setOwner(building, unit); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ReservedRoom::remove() { DFHack::World::DeletePersistentData(config); } | ||||||
|  | 
 | ||||||
|  | bool ReservedRoom::isValid() | ||||||
|  | { | ||||||
|  |     if (!building) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     if (Buildings::findAtTile(pos) != building) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     return canReserveRoom(building); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t ReservedRoom::getId() | ||||||
|  | { | ||||||
|  |     if (!isValid()) | ||||||
|  |         return 0; | ||||||
|  | 
 | ||||||
|  |     return building->id; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string ReservedRoom::getCode() { return config.val(); } | ||||||
|  | 
 | ||||||
|  | void ReservedRoom::setCode(const std::string &noble_code) { config.val() = noble_code; } | ||||||
|  | 
 | ||||||
|  | std::vector<Units::NoblePosition> ReservedRoom::getOwnersNobleCode() | ||||||
|  | { | ||||||
|  |     if (!building->owner) | ||||||
|  |         return std::vector<Units::NoblePosition> (); | ||||||
|  | 
 | ||||||
|  |     return getUniqueNoblePositions(building->owner); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * RoomMonitor | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | std::string RoomMonitor::getReservedNobleCode(int32_t buildingId) | ||||||
|  | { | ||||||
|  |     for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) | ||||||
|  |     { | ||||||
|  |         if (buildingId == iter->getId()) | ||||||
|  |             return iter->getCode(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ""; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RoomMonitor::toggleRoomForPosition(int32_t buildingId, std::string noble_code) | ||||||
|  | { | ||||||
|  |     bool found = false; | ||||||
|  |     for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) | ||||||
|  |     { | ||||||
|  |         if (buildingId != iter->getId()) | ||||||
|  |         { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             if (noble_code == iter->getCode()) | ||||||
|  |             { | ||||||
|  |                 iter->remove(); | ||||||
|  |                 reservedRooms.erase(iter); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 iter->setCode(noble_code); | ||||||
|  |             } | ||||||
|  |             found = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!found) | ||||||
|  |     { | ||||||
|  |         ReservedRoom room(df::building::find(buildingId), noble_code); | ||||||
|  |         reservedRooms.push_back(room); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RoomMonitor::doCycle() | ||||||
|  | { | ||||||
|  |     for (auto iter = reservedRooms.begin(); iter != reservedRooms.end();) | ||||||
|  |     { | ||||||
|  |         if (iter->checkRoomAssignment()) | ||||||
|  |         { | ||||||
|  |             ++iter; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             iter->remove(); | ||||||
|  |             iter = reservedRooms.erase(iter); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RoomMonitor::reset(color_ostream &out) | ||||||
|  | { | ||||||
|  |     reservedRooms.clear(); | ||||||
|  |     std::vector<PersistentDataItem> items; | ||||||
|  |     DFHack::World::GetPersistentData(&items, "buildingplan/reservedroom"); | ||||||
|  | 
 | ||||||
|  |     for (auto i = items.begin(); i != items.end(); i++) | ||||||
|  |     { | ||||||
|  |         ReservedRoom rr(*i, out); | ||||||
|  |         if (rr.isValid()) | ||||||
|  |             addRoom(rr); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RoomMonitor::addRoom(ReservedRoom &rr) | ||||||
|  | { | ||||||
|  |     for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) | ||||||
|  |     { | ||||||
|  |         if (iter->getId() == rr.getId()) | ||||||
|  |             return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     reservedRooms.push_back(rr); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | RoomMonitor roomMonitor; | ||||||
| @ -0,0 +1,51 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "modules/Persistence.h" | ||||||
|  | #include "modules/Units.h" | ||||||
|  | 
 | ||||||
|  | class ReservedRoom | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |     ReservedRoom(df::building *building, std::string noble_code); | ||||||
|  | 
 | ||||||
|  |     ReservedRoom(DFHack::PersistentDataItem &config, DFHack::color_ostream &out); | ||||||
|  | 
 | ||||||
|  |     bool checkRoomAssignment(); | ||||||
|  |     void remove(); | ||||||
|  |     bool isValid(); | ||||||
|  | 
 | ||||||
|  |     int32_t getId(); | ||||||
|  |     std::string getCode(); | ||||||
|  |     void setCode(const std::string &noble_code); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     df::building *building; | ||||||
|  |     DFHack::PersistentDataItem config; | ||||||
|  |     df::coord pos; | ||||||
|  | 
 | ||||||
|  |     std::vector<DFHack::Units::NoblePosition> getOwnersNobleCode(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class RoomMonitor | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |     RoomMonitor() { } | ||||||
|  | 
 | ||||||
|  |     std::string getReservedNobleCode(int32_t buildingId); | ||||||
|  | 
 | ||||||
|  |     void toggleRoomForPosition(int32_t buildingId, std::string noble_code); | ||||||
|  | 
 | ||||||
|  |     void doCycle(); | ||||||
|  | 
 | ||||||
|  |     void reset(DFHack::color_ostream &out); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::vector<ReservedRoom> reservedRooms; | ||||||
|  | 
 | ||||||
|  |     void addRoom(ReservedRoom &rr); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | bool canReserveRoom(df::building *building); | ||||||
|  | std::vector<DFHack::Units::NoblePosition> getUniqueNoblePositions(df::unit *unit); | ||||||
|  | 
 | ||||||
|  | extern RoomMonitor roomMonitor; | ||||||
		Loading…
	
		Reference in New Issue