From f3da131db7a8ec41389342eaf44bfe85a4f281d2 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 8 Sep 2020 00:17:56 -0700 Subject: [PATCH 1/5] reorganize buildingplan code - no logic changes - no API or logic changes, just moving code around - split buildingplan-lib into planner and rooms files - move business logic from .h files to .cpp files --- plugins/CMakeLists.txt | 2 +- plugins/buildingplan-lib.cpp | 688 +------------------------------ plugins/buildingplan-lib.h | 507 +---------------------- plugins/buildingplan-planner.cpp | 552 +++++++++++++++++++++++++ plugins/buildingplan-planner.h | 108 +++++ plugins/buildingplan-rooms.cpp | 227 ++++++++++ plugins/buildingplan-rooms.h | 51 +++ plugins/buildingplan.cpp | 311 +++++++++++++- plugins/fortplan.cpp | 20 +- 9 files changed, 1257 insertions(+), 1209 deletions(-) create mode 100644 plugins/buildingplan-planner.cpp create mode 100644 plugins/buildingplan-planner.h create mode 100644 plugins/buildingplan-rooms.cpp create mode 100644 plugins/buildingplan-rooms.h diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 07e14fa11..8301d05db 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -74,7 +74,7 @@ add_custom_target(generate_proto DEPENDS ${PROJECT_PROTO_TMP_FILES}) set_source_files_properties( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE ) -add_library(buildingplan-lib STATIC buildingplan-lib.cpp) +add_library(buildingplan-lib STATIC buildingplan-lib.cpp buildingplan-planner.cpp buildingplan-rooms.cpp) target_link_libraries(buildingplan-lib dfhack) # xlsxreader deps diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp index e21c843aa..215dbb900 100644 --- a/plugins/buildingplan-lib.cpp +++ b/plugins/buildingplan-lib.cpp @@ -1,690 +1,16 @@ #include "buildingplan-lib.h" -#define PLUGIN_VERSION 0.00 -void debug(const string &msg) -{ - if (!show_debugging) - return; - - color_ostream_proxy out(Core::getInstance().getConsole()); - out << "DEBUG (" << PLUGIN_VERSION << "): " << msg << endl; -} - -void enable_quickfort_fn(pair& pair) { pair.second = true; } - -/* - * Material Choice Screen - */ - -std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } - -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::vector ItemFilter::getMaterialFilterAsVector() -{ - std::vector 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"); +#include "Core.h" - 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 tokens; - split_string(&tokens, str, "/"); +using namespace DFHack; - if (tokens.size() > 0 && !tokens[0].empty()) - { - if (!parseJobMaterialCategory(&mat_mask, tokens[0])) - return false; - } - - if (tokens.size() > 1 && !tokens[1].empty()) - { - std::vector 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(); -} - -DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } - -ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) -{ - selected_column = 0; - masks_column.setTitle("Type"); - masks_column.multiselect = true; - masks_column.allow_search = false; - masks_column.left_margin = 2; - materials_column.left_margin = MAX_MASK + 3; - materials_column.setTitle("Material"); - materials_column.multiselect = true; - this->filter = filter; - - masks_column.changeHighlight(0); - - populateMasks(); - populateMaterials(); - - masks_column.selectDefaultEntry(); - materials_column.selectDefaultEntry(); - materials_column.changeHighlight(0); -} - -void ViewscreenChooseMaterial::feed(set *input) -{ - bool key_processed = false; - switch (selected_column) - { - case 0: - key_processed = masks_column.feed(input); - if (input->count(interface_key::SELECT)) - populateMaterials(); // Redo materials lists based on category selection - break; - case 1: - key_processed = materials_column.feed(input); - break; - } - - if (key_processed) - return; - - if (input->count(interface_key::LEAVESCREEN)) - { - input->clear(); - Screen::dismiss(this); - return; - } - if (input->count(interface_key::CUSTOM_SHIFT_C)) - { - filter->clear(); - masks_column.clearSelection(); - materials_column.clearSelection(); - populateMaterials(); - } - else if (input->count(interface_key::SEC_SELECT)) - { - // Convert list selections to material filters - - - filter->mat_mask.whole = 0; - filter->materials.clear(); - - // Category masks - auto masks = masks_column.getSelectedElems(); - for (auto it = masks.begin(); it != masks.end(); ++it) - filter->mat_mask.whole |= it->whole; - - // Specific materials - auto materials = materials_column.getSelectedElems(); - transform_(materials, filter->materials, material_info_identity_fn); - - Screen::dismiss(this); - } - else if (input->count(interface_key::CURSOR_LEFT)) - { - --selected_column; - validateColumn(); - } - else if (input->count(interface_key::CURSOR_RIGHT)) - { - selected_column++; - validateColumn(); - } - else if (enabler->tracking_on && enabler->mouse_lbut) - { - if (masks_column.setHighlightByMouse()) - selected_column = 0; - else if (materials_column.setHighlightByMouse()) - selected_column = 1; - - enabler->mouse_lbut = enabler->mouse_rbut = 0; - } -} - -void ViewscreenChooseMaterial::render() -{ - if (Screen::isDismissed(this)) - return; - - dfhack_viewscreen::render(); - - Screen::clear(); - Screen::drawBorder(" Building Material "); - - masks_column.display(selected_column == 0); - materials_column.display(selected_column == 1); - - int32_t y = gps->dimy - 3; - int32_t x = 2; - OutputHotkeyString(x, y, "Toggle", interface_key::SELECT); - x += 3; - OutputHotkeyString(x, y, "Save", interface_key::SEC_SELECT); - x += 3; - OutputHotkeyString(x, y, "Clear", interface_key::CUSTOM_SHIFT_C); - x += 3; - OutputHotkeyString(x, y, "Cancel", interface_key::LEAVESCREEN); -} - -// START Room Reservation -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 &out) -{ - 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 = world->units.active.begin(); iter != 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; -} - -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 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 delete_item_fn(df::job_item *x) { delete x; } - -// START Planning - -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(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(config.ival(2) - 1); - filter.max_quality = static_cast(config.ival(4) - 1); - filter.decorated_only = config.ival(3) - 1; -} - -bool PlannedBuilding::assignClosestItem(std::vector *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; -} - -bool PlannedBuilding::assignItem(df::item *item) -{ - auto ref = df::allocate(); - 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; -} - -void Planner::reset(color_ostream &out) -{ - planned_buildings.clear(); - std::vector 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(); \ - 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 -} +bool show_debugging = false; -void Planner::doCycle() +void debug(const std::string &msg) { - debug("Running Cycle"); - if (planned_buildings.size() == 0) + if (!show_debugging) 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 filters; - filters.push_back(filter); - - if (!Buildings::constructWithFilters(newinst, filters)) - { - delete newinst; - return false; - } - - if (type == building_type::Door) - { - auto door = virtual_cast(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::adjustMinQuality(df::building_type type, int amount) -{ - auto min_quality = &getDefaultItemFilterForType(type)->min_quality; - *min_quality = static_cast(*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(*max_quality + amount); - - boundsCheckItemQuality(max_quality); - auto min_quality = &getDefaultItemFilterForType(type)->min_quality; - if (*max_quality < *min_quality) - (*min_quality) = *max_quality; -} - -void Planner::boundsCheckItemQuality(item_quality::item_quality *quality) -{ - *quality = static_cast(*quality); - if (*quality > item_quality::Artifact) - (*quality) = item_quality::Artifact; - if (*quality < item_quality::Ordinary) - (*quality) = item_quality::Ordinary; + color_ostream_proxy out(Core::getInstance().getConsole()); + out << "DEBUG: " << msg << endl; } - -map planmode_enabled, saved_planmodes; - -bool show_debugging = false; -bool show_help = false; - -Planner planner; - -RoomMonitor roomMonitor; diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h index d8d276a55..61a00e796 100644 --- a/plugins/buildingplan-lib.h +++ b/plugins/buildingplan-lib.h @@ -1,509 +1,8 @@ -#ifndef BUILDINGPLAN_H -#define BUILDINGPLAN_H +#pragma once -#include "uicommon.h" -#include "listcolumn.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 inline 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 inline 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; -} - -void delete_item_fn(df::job_item *x); - -MaterialInfo &material_info_identity_fn(MaterialInfo &m); - -extern map planmode_enabled, saved_planmodes; - -void enable_quickfort_fn(pair& pair); +#include "buildingplan-planner.h" +#include "buildingplan-rooms.h" void debug(const std::string &msg); -std::string material_to_string_fn(MaterialInfo m); extern bool show_debugging; -extern bool show_help; - -struct ItemFilter -{ - df::dfhack_material_category mat_mask; - std::vector materials; - df::item_quality min_quality; - df::item_quality max_quality; - - bool decorated_only; - - 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 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(); - std::string getMaxQuality(); - - 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() : in_dummmy_screen(false), quickfort_mode(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) - { - 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 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 adjustMinQuality(df::building_type type, int amount); - void adjustMaxQuality(df::building_type type, int amount); - - 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 boundsCheckItemQuality(item_quality::item_quality *quality); - - 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); - } - } -}; - -extern Planner planner; - -extern RoomMonitor roomMonitor; - -#endif diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp new file mode 100644 index 000000000..31caf995e --- /dev/null +++ b/plugins/buildingplan-planner.cpp @@ -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 ItemFilter::getMaterialFilterAsVector() +{ + std::vector 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 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 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(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(config.ival(2) - 1); + filter.max_quality = static_cast(config.ival(4) - 1); + filter.decorated_only = config.ival(3) - 1; +} + +bool PlannedBuilding::assignClosestItem(std::vector *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(); + 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& 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 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(); \ + 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 filters; + filters.push_back(filter); + + if (!Buildings::constructWithFilters(newinst, filters)) + { + delete newinst; + return false; + } + + if (type == building_type::Door) + { + auto door = virtual_cast(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(*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(*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(*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 &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 planmode_enabled, saved_planmodes; +Planner planner; diff --git a/plugins/buildingplan-planner.h b/plugins/buildingplan-planner.h new file mode 100644 index 000000000..b073e96b8 --- /dev/null +++ b/plugins/buildingplan-planner.h @@ -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 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 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 *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 item_for_building_type; + std::map default_item_filters; + std::map> available_item_vectors; + std::map is_relevant_item_type; //Needed for fast check when looping over all items + bool quickfort_mode; + + std::vector planned_buildings; + + void boundsCheckItemQuality(df::enums::item_quality::item_quality *quality); + + void gather_available_items(); +}; + +extern std::map planmode_enabled, saved_planmodes; +extern Planner planner; diff --git a/plugins/buildingplan-rooms.cpp b/plugins/buildingplan-rooms.cpp new file mode 100644 index 000000000..87cd413d8 --- /dev/null +++ b/plugins/buildingplan-rooms.cpp @@ -0,0 +1,227 @@ +#include "buildingplan-rooms.h" +#include "buildingplan-lib.h" + +#include +#include +#include + +#include +#include +#include + +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 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; +} + +/* + * 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 ReservedRoom::getOwnersNobleCode() +{ + if (!building->owner) + return std::vector (); + + 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 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; diff --git a/plugins/buildingplan-rooms.h b/plugins/buildingplan-rooms.h new file mode 100644 index 000000000..3880dbe06 --- /dev/null +++ b/plugins/buildingplan-rooms.h @@ -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 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 reservedRooms; + + void addRoom(ReservedRoom &rr); +}; + +bool canReserveRoom(df::building *building); +std::vector getUniqueNoblePositions(df::unit *unit); + +extern RoomMonitor roomMonitor; diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index c42aeac53..ae8ce8c8d 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,41 +1,297 @@ -#include "LuaTools.h" #include "buildingplan-lib.h" + +#include "df/entity_position.h" +#include "df/interface_key.h" +#include "df/ui_build_selector.h" +#include "df/viewscreen_dwarfmodest.h" + +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/World.h" + +#include "LuaTools.h" +#include "PluginManager.h" + +#include "uicommon.h" +#include "listcolumn.h" DFHACK_PLUGIN("buildingplan"); -#define PLUGIN_VERSION 0.14 +#define PLUGIN_VERSION 0.15 REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_build_selector); REQUIRE_GLOBAL(world); -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} +#define MAX_MASK 10 +#define MAX_MATERIAL 21 +using namespace DFHack; +using namespace df::enums; -static bool is_planmode_enabled(df::building_type type) +bool show_help = false; + +class ViewscreenChooseMaterial : public dfhack_viewscreen { - if (planmode_enabled.find(type) == planmode_enabled.end()) +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) { - return false; + auto entry = ListEntry(pad_string(text, MAX_MASK, false), mask); + if (filter->matches(mask)) + entry.selected = true; + + masks_column.add(entry); } - return planmode_enabled[type]; + 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(); + } +}; + +DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } + +ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) +{ + selected_column = 0; + masks_column.setTitle("Type"); + masks_column.multiselect = true; + masks_column.allow_search = false; + masks_column.left_margin = 2; + materials_column.left_margin = MAX_MASK + 3; + materials_column.setTitle("Material"); + materials_column.multiselect = true; + this->filter = filter; + + masks_column.changeHighlight(0); + + populateMasks(); + populateMaterials(); + + masks_column.selectDefaultEntry(); + materials_column.selectDefaultEntry(); + materials_column.changeHighlight(0); } -#define DAY_TICKS 1200 -DFhackCExport command_result plugin_onupdate(color_ostream &out) +void ViewscreenChooseMaterial::feed(set *input) { - if (Maps::IsValid() && !World::ReadPauseState() && world->frame_counter % (DAY_TICKS/2) == 0) + bool key_processed = false; + switch (selected_column) { - planner.doCycle(); - roomMonitor.doCycle(); + case 0: + key_processed = masks_column.feed(input); + if (input->count(interface_key::SELECT)) + populateMaterials(); // Redo materials lists based on category selection + break; + case 1: + key_processed = materials_column.feed(input); + break; } - return CR_OK; + if (key_processed) + return; + + if (input->count(interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + } + if (input->count(interface_key::CUSTOM_SHIFT_C)) + { + filter->clear(); + masks_column.clearSelection(); + materials_column.clearSelection(); + populateMaterials(); + } + else if (input->count(interface_key::SEC_SELECT)) + { + // Convert list selections to material filters + filter->mat_mask.whole = 0; + filter->materials.clear(); + + // Category masks + auto masks = masks_column.getSelectedElems(); + for (auto it = masks.begin(); it != masks.end(); ++it) + filter->mat_mask.whole |= it->whole; + + // Specific materials + auto materials = materials_column.getSelectedElems(); + transform_(materials, filter->materials, material_info_identity_fn); + + Screen::dismiss(this); + } + else if (input->count(interface_key::CURSOR_LEFT)) + { + --selected_column; + validateColumn(); + } + else if (input->count(interface_key::CURSOR_RIGHT)) + { + selected_column++; + validateColumn(); + } + else if (enabler->tracking_on && enabler->mouse_lbut) + { + if (masks_column.setHighlightByMouse()) + selected_column = 0; + else if (materials_column.setHighlightByMouse()) + selected_column = 1; + + enabler->mouse_lbut = enabler->mouse_rbut = 0; + } +} + +void ViewscreenChooseMaterial::render() +{ + if (Screen::isDismissed(this)) + return; + + dfhack_viewscreen::render(); + + Screen::clear(); + Screen::drawBorder(" Building Material "); + + masks_column.display(selected_column == 0); + materials_column.display(selected_column == 1); + + int32_t y = gps->dimy - 3; + int32_t x = 2; + OutputHotkeyString(x, y, "Toggle", interface_key::SELECT); + x += 3; + OutputHotkeyString(x, y, "Save", interface_key::SEC_SELECT); + x += 3; + OutputHotkeyString(x, y, "Clear", interface_key::CUSTOM_SHIFT_C); + x += 3; + OutputHotkeyString(x, y, "Cancel", interface_key::LEAVESCREEN); } //START Viewscreen Hook +static bool is_planmode_enabled(df::building_type type) +{ + if (planmode_enabled.find(type) == planmode_enabled.end()) + { + return false; + } + + return planmode_enabled[type]; +} + struct buildingplan_hook : public df::viewscreen_dwarfmodest { //START UI Methods @@ -58,8 +314,8 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest bool isInPlannedBuildingPlacementMode() { return ui->main.mode == ui_sidebar_mode::Build && - ui_build_selector && - ui_build_selector->stage < 2 && + df::global::ui_build_selector && + df::global::ui_build_selector->stage < 2 && planner.isPlanableBuilding(ui_build_selector->building_type); } @@ -350,7 +606,6 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render); - static command_result buildingplan_cmd(color_ostream &out, vector & parameters) { if (!parameters.empty()) @@ -401,7 +656,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector frame_counter % (DAY_TICKS/2) == 0) + { + planner.doCycle(); + roomMonitor.doCycle(); + } + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &) +{ + return CR_OK; +} + // Lua API section static bool isPlannableBuilding(df::building_type type) { diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index 5dceb35c9..ccaab18c3 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -1,11 +1,25 @@ -#include "buildingplan-lib.h" #include #include + +#include "df/world.h" +#include "df/trap_type.h" + #include "modules/Filesystem.h" +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/World.h" + +#include "PluginManager.h" + +#include "buildingplan-lib.h" +#include "uicommon.h" DFHACK_PLUGIN("fortplan"); #define PLUGIN_VERSION 0.15 +using namespace std; +using namespace DFHack; + command_result fortplan(color_ostream &out, vector & params); struct BuildingInfo { @@ -88,7 +102,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector frame_counter % (DAY_TICKS/2) == 0) + if (Maps::IsValid() && !World::ReadPauseState() && df::global::world->frame_counter % (DAY_TICKS/2) == 0) { planner.doCycle(); } @@ -100,7 +114,7 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!gps) + if (!df::global::gps ) return CR_FAILURE; if (enable != is_enabled) From 8660355838ea954a38dca49ca1c1a18bc5e231ac Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 8 Sep 2020 21:30:12 -0700 Subject: [PATCH 2/5] formally declare used globals in fortplan --- plugins/fortplan.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index ccaab18c3..72d11b78a 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -15,6 +15,9 @@ #include "uicommon.h" DFHACK_PLUGIN("fortplan"); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(world); + #define PLUGIN_VERSION 0.15 using namespace std; From d43bf0ce4474a8e994d59d552469d16dbd79f4a6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 21 Sep 2020 12:27:27 -0400 Subject: [PATCH 3/5] Update dependencies --- depends/libexpat | 2 +- depends/libzip | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/libexpat b/depends/libexpat index 26839cce4..3c0f2e86c 160000 --- a/depends/libexpat +++ b/depends/libexpat @@ -1 +1 @@ -Subproject commit 26839cce4534ea4ee70ba992a6d7a774c624d584 +Subproject commit 3c0f2e86ce4e7a3a3b30e765087d02a68bba7e6f diff --git a/depends/libzip b/depends/libzip index 33c059d22..be76fa508 160000 --- a/depends/libzip +++ b/depends/libzip @@ -1 +1 @@ -Subproject commit 33c059d2217de6a5271e9ecbf19908e6efbf0e79 +Subproject commit be76fa5086bfe6b1a5e83c9855e39f98edc1f066 From b1f80b6d26df0fc9622524396055c93e5c0af535 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 23 Sep 2020 20:05:45 -0400 Subject: [PATCH 4/5] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index d069acb3a..436b98f6b 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit d069acb3ab7660c18cbb789070186f0757775462 +Subproject commit 436b98f6b2dd8a8a845e5eccaeaecd5024dc1b9f From dfac5bc143a0fa77e29b1ebd645a0cb08e3e4926 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 23 Sep 2020 22:00:19 -0400 Subject: [PATCH 5/5] Update gui.Painter docs, add sections to dfhack.screen, add more links, etc --- docs/Lua API.rst | 103 ++++++++++++++++++++++++++++++++++++-------- library/lua/gui.lua | 8 ++-- 2 files changed, 88 insertions(+), 23 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 633ae2278..f31bb5469 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1831,9 +1831,27 @@ Screen API The screen module implements support for drawing to the tiled screen of the game. Note that drawing only has any effect when done from callbacks, so it can only -be feasibly used in the core context. +be feasibly used in the `core context `. -Basic painting functions: +.. contents:: + :local: + +Basic painting functions +~~~~~~~~~~~~~~~~~~~~~~~~ + +Common parameters to these functions include: + +* ``x``, ``y``: screen coordinates in tiles; the upper left corner of the screen + is ``x = 0, y = 0`` +* ``pen``: a `pen object ` +* ``map``: a boolean indicating whether to draw to a separate map buffer + (defaults to false, which is suitable for off-map text or a screen that hides + the map entirely). Note that only third-party plugins like TWBT currently + implement a separate map buffer. If no such plugins are enabled, passing + ``true`` has no effect. However, this parameter should still be used to ensure + that scripts work properly with such plugins. + +Functions: * ``dfhack.screen.getWindowSize()`` @@ -1849,25 +1867,25 @@ Basic painting functions: * ``dfhack.screen.paintTile(pen,x,y[,char,tile,map])`` - Paints a tile using given parameters. See below for a description of pen. + Paints a tile using given parameters. `See below ` for a description of ``pen``. - Returns *false* if coordinates out of bounds, or other error. + Returns *false* on error, e.g. if coordinates are out of bounds * ``dfhack.screen.readTile(x,y[,map])`` Retrieves the contents of the specified tile from the screen buffers. - Returns a pen object, or *nil* if invalid or TrueType. + Returns a `pen object `, or *nil* if invalid or TrueType. * ``dfhack.screen.paintString(pen,x,y,text[,map])`` Paints the string starting at *x,y*. Uses the string characters - in sequence to override the ``ch`` field of pen. + in sequence to override the ``ch`` field of `pen `. Returns *true* if painting at least one character succeeded. * ``dfhack.screen.fillRect(pen,x1,y1,x2,y2[,map])`` - Fills the rectangle specified by the coordinates with the given pen. + Fills the rectangle specified by the coordinates with the given `pen `. Returns *true* if painting at least one character succeeded. * ``dfhack.screen.findGraphicsTile(pagename,x,y)`` @@ -1903,7 +1921,12 @@ Basic painting functions: Returns the keybinding representing the given string input character, or *nil* if impossible. -The "pen" argument used by functions above may be represented by +.. _lua-screen-pen: + +Pen API +~~~~~~~ + +The ``pen`` argument used by ``dfhack.screen`` functions may be represented by a table with the following possible fields: ``ch`` @@ -1958,6 +1981,9 @@ Alternatively, it may be a pre-parsed native object with the following API: assigning to ``pen.tile_color`` also resets ``pen.tile_fg`` and ``pen.tile_bg`` to *nil*. +Screen management +~~~~~~~~~~~~~~~~~ + In order to actually be able to paint to the screen, it is necessary to create and register a viewscreen (basically a modal dialog) with the game. @@ -1986,7 +2012,11 @@ Apart from a native viewscreen object, these functions accept a table as a screen. In this case, ``show`` creates a new native viewscreen that delegates all processing to methods stored in that table. -.. note:: Lua-implemented screens are only supported in the core context. +.. note:: + + * The `gui.Screen class ` provides stubs for all of the + functions listed below, and its use is recommended + * Lua-implemented screens are only supported in the `core context `. Supported callbacks and fields are: @@ -2314,11 +2344,13 @@ and are only documented here for completeness: Returns a numeric identifier of the current thread. +.. _lua-core-context: + Core interpreter context ======================== While plugins can create any number of interpreter instances, -there is one special context managed by dfhack core. It is the +there is one special context managed by the DFHack core. It is the only context that can receive events from DF and plugins. Core context specific functions: @@ -2348,7 +2380,8 @@ Core context specific functions: * ``dfhack.onStateChange.foo = function(code)`` - Event. Receives the same codes as plugin_onstatechange in C++. + Creates a handler for state change events. Receives the same + `SC_ codes ` as ``plugin_onstatechange()`` in C++. Event type @@ -2360,7 +2393,7 @@ through the table with next and calls all contained values. This is intended as an extensible way to add listeners. This type itself is available in any context, but only the -core context has the actual events defined by C++ code. +`core context ` has the actual events defined by C++ code. Features: @@ -2427,6 +2460,8 @@ The following module management functions are provided: should be kept limited to the standard Lua library and API described in this document. +.. _lua-globals: + Global environment ================== @@ -2443,9 +2478,9 @@ environment by the mandatory init file dfhack.lua: COLOR_LIGHTBLUE, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN, COLOR_LIGHTRED, COLOR_LIGHTMAGENTA, COLOR_YELLOW, COLOR_WHITE -* ``dfhack.onStateChange`` event codes +* State change event codes, used by ``dfhack.onStateChange`` - Available only in the core context, as is the event itself: + Available only in the `core context `, as is the event itself: SC_WORLD_LOADED, SC_WORLD_UNLOADED, SC_MAP_LOADED, SC_MAP_UNLOADED, SC_VIEWSCREEN_CHANGED, SC_CORE_INITIALIZED @@ -2964,7 +2999,9 @@ The painting natives in ``dfhack.screen`` apply to the whole screen, are completely stateless and don't implement clipping. The Painter class inherits from ViewRect to provide clipping and local -coordinates, and tracks current cursor position and current pen. +coordinates, and tracks current cursor position and current pen. It also +supports drawing to a separate map buffer if applicable (see ``map()`` below +for details). * ``Painter{ ..., pen = ..., key_pen = ... }`` @@ -2989,7 +3026,15 @@ coordinates, and tracks current cursor position and current pen. * ``painter:cursor()`` - Returns the current cursor *x,y* in local coordinates. + Returns the current cursor *x,y* in screen coordinates. + +* ``painter:cursorX()`` + + Returns just the current *x* cursor coordinate + +* ``painter:cursorY()`` + + Returns just the current *y* cursor coordinate * ``painter:seek(x,y)`` @@ -3009,10 +3054,22 @@ coordinates, and tracks current cursor position and current pen. Sets the current pen to ``dfhack.pen.parse(old_pen,...)``, and returns *self*. +* ``painter:color(fg[,bold[,bg]])`` + + Sets the specified colors of the current pen and returns *self*. + * ``painter:key_pen(...)`` Sets the current keybinding pen to ``dfhack.pen.parse(old_pen,...)``, and returns *self*. +* ``painter:map(to_map)`` + + Enables or disables drawing to a separate map buffer. ``to_map`` is a boolean + that will be passed as the ``map`` parameter to any ``dfhack.screen`` functions + that accept it. Note that only third-party plugins like TWBT currently implement + a separate map buffer; if none are enabled, this function has no effect (but + should still be used to ensure proper support for such plugins). Returns *self*. + * ``painter:clear()`` Fills the whole clip rectangle with ``CLEAR_PEN``, and returns *self*. @@ -3029,7 +3086,7 @@ coordinates, and tracks current cursor position and current pen. * ``painter:tile([char, tile[, ...]])`` - Like above, but also allows overriding the ``tile`` property on ad-hoc basis. + Like ``char()`` above, but also allows overriding the ``tile`` property on ad-hoc basis. * ``painter:string(text[, ...])`` @@ -3039,7 +3096,13 @@ coordinates, and tracks current cursor position and current pen. Paints the description of the keycode using ``dfhack.pen.parse(cur_key_pen,...)``; returns *self*. -As noted above, all painting methods return *self*, in order to allow chaining them like this:: +* ``painter:key_string(keycode, text, ...)`` + + A convenience wrapper around both ``key()`` and ``string()`` that prints both + the specified keycode description and text, separated by ``:``. Any extra + arguments are passed directly to ``string()``. Returns *self*. + +Unless specified otherwise above, all Painter methods return *self*, in order to allow chaining them like this:: painter:pen(foo):seek(x,y):char(1):advance(1):string('bar')... @@ -3160,6 +3223,8 @@ The class has the following methods: Returns *true* if any of the subviews handled the event. +.. _lua-gui-screen: + Screen class ------------ @@ -4193,7 +4258,7 @@ Arguments are passed in to the scripts via the **...** built-in quasi-variable; when the script is called by the DFHack core, they are all guaranteed to be non-nil strings. -DFHack core invokes the scripts in the *core context* (see above); +DFHack core invokes the scripts in the `core context `; however it is possible to call them from any lua code (including from other scripts) in any context, via the same function the core uses: diff --git a/library/lua/gui.lua b/library/lua/gui.lua index e92ef4d26..b7f2e8ce9 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -355,15 +355,15 @@ function Painter:string(text,pen,...) return self:advance(#text, nil) end -function Painter:key(code,pen,...) +function Painter:key(keycode,pen,...) return self:string( - getKeyDisplay(code), + getKeyDisplay(keycode), to_pen(self.cur_key_pen, pen, ...) ) end -function Painter:key_string(code, text, ...) - return self:key(code):string(': '):string(text, ...) +function Painter:key_string(keycode, text, ...) + return self:key(keycode):string(': '):string(text, ...) end --------------------------