Skip to content

Bring back zones in gui/blueprint (WIP) #5222

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions docs/plugins/blueprint.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ selected interactively with the `gui/blueprint` command or, if the GUI is not
used, starts at the active cursor location and extends right and down for the
requested width and height.

.. admonition:: Note

blueprint is still in the process of being updated for the new version of
DF. Stockpiles (the "place" phase), zones (the "zone" phase), building
configuration (the "query" phase), and game configuration (the "config"
phase) are not yet supported.

Usage
-----

Expand Down Expand Up @@ -83,11 +76,6 @@ phases; just separate them with a space.
Generate quickfort ``#place`` blueprints for placing stockpiles.
``zone``
Generate quickfort ``#zone`` blueprints for designating zones.
``query``
Generate quickfort ``#query`` blueprints for configuring stockpiles and
naming buildings.
``rooms``
Generate quickfort ``#query`` blueprints for defining rooms.

If no phases are specified, phases are autodetected. For example, a ``#place``
blueprint will be created only if there are stockpiles in the blueprint area.
Expand Down
219 changes: 64 additions & 155 deletions plugins/blueprint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,8 @@ struct blueprint_options {
bool construct = false;
bool build = false;
bool place = false;
// bool zone = false;
// bool query = false;
// bool rooms = false;
bool zone = false;


static struct_identity _identity;
};
Expand All @@ -132,9 +131,7 @@ static const struct_field_info blueprint_options_fields[] = {
{ struct_field_info::PRIMITIVE, "construct", offsetof(blueprint_options, construct), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "build", offsetof(blueprint_options, build), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "place", offsetof(blueprint_options, place), &df::identity_traits<bool>::identity, 0, 0 },
// { struct_field_info::PRIMITIVE, "zone", offsetof(blueprint_options, zone), &df::identity_traits<bool>::identity, 0, 0 },
// { struct_field_info::PRIMITIVE, "query", offsetof(blueprint_options, query), &df::identity_traits<bool>::identity, 0, 0 },
// { struct_field_info::PRIMITIVE, "rooms", offsetof(blueprint_options, rooms), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "zone", offsetof(blueprint_options, zone), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::END }
};
struct_identity blueprint_options::_identity(sizeof(blueprint_options), &df::allocator_fn<blueprint_options>, NULL, "blueprint_options", NULL, blueprint_options_fields);
Expand Down Expand Up @@ -1107,78 +1104,70 @@ static const char * get_tile_place(const df::coord &pos,
return add_expansion_syntax(ctx, get_place_keys(ctx));
}

/* TODO: understand how this changes for v50
static bool hospital_maximums_eq(const df::hospital_supplies &a,
const df::hospital_supplies &b) {
return a.max_thread == b.max_thread &&
a.max_cloth == b.max_cloth &&
a.max_splints == b.max_splints &&
a.max_crutches == b.max_crutches &&
a.max_plaster == b.max_plaster &&
a.max_buckets == b.max_buckets &&
a.max_soap == b.max_soap;
}

static const char * get_zone_keys(const df::building_civzonest *zone) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function needs to be rewritten to support the blueprint format for zones and locations. The docs are here: https://docs.dfhack.org/en/latest/docs/guides/quickfort-user-guide.html#zone-mode and here: https://docs.dfhack.org/en/latest/docs/guides/quickfort-user-guide.html#zone-mode-reference

static const uint32_t DEFAULT_GATHER_FLAGS =
df::building_civzonest::T_gather_flags::mask_pick_trees |
df::building_civzonest::T_gather_flags::mask_pick_shrubs |
df::building_civzonest::T_gather_flags::mask_gather_fallen;
static const df::hospital_supplies DEFAULT_HOSPITAL;
df::civzone_gather_flag::mask_pick_trees |
df::civzone_gather_flag::mask_pick_shrubs |
df::civzone_gather_flag::mask_gather_fallen;

ostringstream keys;
const df::building_civzonest::T_zone_flags &flags = zone->zone_flags;

// inverted logic for Active since it's on by default
if (!flags.bits.active) keys << 'a';

// in UI order
if (flags.bits.water_source) keys << 'w';
if (flags.bits.fishing) keys << 'f';
if (flags.bits.gather) {
keys << 'g';
if (zone->gather_flags.whole != DEFAULT_GATHER_FLAGS) {
keys << 'G';
// logic is inverted since they're all on by default
if (!zone->gather_flags.bits.pick_trees) keys << 't';
if (!zone->gather_flags.bits.pick_shrubs) keys << 's';
if (!zone->gather_flags.bits.gather_fallen) keys << 'f';
keys << '^';
}
const df::civzone_type type = zone->type;

// in DFHack docs order
if (type == df::civzone_type::MeetingHall) keys << 'm';
if (type == df::civzone_type::Bedroom) keys << 'b';
if (type == df::civzone_type::DiningHall) keys << 'h';
if (type == df::civzone_type::Pen) keys << 'n';
if (type == df::civzone_type::Pond) keys << 'p';
if (type == df::civzone_type::WaterSource) keys << 'w';
if (type == df::civzone_type::Dungeon) keys << 'j';
if (type == df::civzone_type::FishingArea) keys << 'f';
if (type == df::civzone_type::SandCollection) keys << 's';
if (type == df::civzone_type::Office) keys << 'o';
if (type == df::civzone_type::Dormitory) keys << 'D';
if (type == df::civzone_type::Barracks) keys << 'B';
if (type == df::civzone_type::ArcheryRange) keys << 'a';
if (type == df::civzone_type::Dump) keys << 'd';
if (type == df::civzone_type::AnimalTraining) keys << 't';
if (type == df::civzone_type::Tomb) keys << 'T';
if (type == df::civzone_type::PlantGathering) keys << 'g';
if (type == df::civzone_type::ClayCollection) keys << 'c';

keys << '{';
if (!zone->name.empty()) {
keys << "name=" << zone->name << ' ';
}
if (flags.bits.garbage_dump) keys << 'd';
if (flags.bits.pen_pasture) keys << 'n';
if (flags.bits.pit_pond) {
keys << 'p';
if (zone->pit_flags.bits.is_pond)
keys << "Pf^";
if (!zone->spec_sub_flag.bits.active) {
keys << "active=false ";
}
if (flags.bits.sand) keys << 's';
if (flags.bits.clay) keys << 'c';
if (flags.bits.meeting_area) keys << 'm';
if (flags.bits.hospital) {
keys << 'h';
const df::hospital_supplies &hospital = zone->hospital;
if (!hospital_maximums_eq(hospital, DEFAULT_HOSPITAL)) {
keys << "H{hospital";
if (hospital.max_thread != DEFAULT_HOSPITAL.max_thread)
keys << " thread=" << hospital.max_thread;
if (hospital.max_cloth != DEFAULT_HOSPITAL.max_cloth)
keys << " cloth=" << hospital.max_cloth;
if (hospital.max_splints != DEFAULT_HOSPITAL.max_splints)
keys << " splints=" << hospital.max_splints;
if (hospital.max_crutches != DEFAULT_HOSPITAL.max_crutches)
keys << " crutches=" << hospital.max_crutches;
if (hospital.max_plaster != DEFAULT_HOSPITAL.max_plaster)
keys << " plaster=" << hospital.max_plaster;
if (hospital.max_buckets != DEFAULT_HOSPITAL.max_buckets)
keys << " buckets=" << hospital.max_buckets;
if (hospital.max_soap != DEFAULT_HOSPITAL.max_soap)
keys << " soap=" << hospital.max_soap;
keys << "}^";
}
if (zone->assigned_unit) {
keys << "assigned_unit=" << zone->assigned_unit << ' ';
}
if (!zone->zone_settings.pond.flag.bits.keep_filled) {
keys << "pond=false ";
}
auto archery_settings = zone->zone_settings.archery; // need this to get the `shoot_from` direction
if (archery_settings.dir_y == 0) {
if (archery_settings.dir_x == 1) keys << "shoot_from=west ";
if (archery_settings.dir_x == -1) keys << "shoot_from=east ";
}
if (archery_settings.dir_x == 0) {
if (archery_settings.dir_y == 1) keys << "shoot_from=north ";
if (archery_settings.dir_y == -1) keys << "shoot_from=south ";
}
//FIXEME (Squid): Need to know how to get the location data here
if (!zone->zone_settings.tomb.flags.bits.no_pets) {
keys << "pets=true ";
}
if (zone->zone_settings.tomb.flags.bits.no_citizens) {
keys << "citizens=false ";
}
if (zone->zone_settings.gather.flags.whole != DEFAULT_GATHER_FLAGS) {
// logic is inverted since they're all on by default
if (!zone->zone_settings.gather.flags.bits.pick_trees) keys << "pick_trees=false ";
if (!zone->zone_settings.gather.flags.bits.pick_shrubs) keys << "pick_shrubs=false ";
if (!zone->zone_settings.gather.flags.bits.gather_fallen) keys << "gather_fallen=false ";
}
if (flags.bits.animal_training) keys << 't';

string keys_str = keys.str();

Expand All @@ -1187,8 +1176,11 @@ static const char * get_zone_keys(const df::building_civzonest *zone) {
return NULL;

// remove final '^' character if there is one
if (keys_str.back() == '^')
if (keys_str.back() == '{') {
keys_str.pop_back();
} else {
keys << '}';
}

return cache(keys_str);
}
Expand All @@ -1215,84 +1207,7 @@ static const char * get_tile_zone(const df::coord &pos,
return add_expansion_syntax(zone, get_zone_keys(zone));
}

// surrounds the given string in quotes and replaces internal double quotes (")
// with double double quotes ("") (as per the csv spec)
static string csv_quote(const string &str) {
ostringstream outstr;
outstr << "\"";

size_t start = 0;
auto end = str.find('"');
while (end != string::npos) {
outstr << str.substr(start, end - start);
outstr << "\"\"";
start = end + 1;
end = str.find('"', start);
}
outstr << str.substr(start, end) << "\"";

return outstr.str();
}

static const char * get_tile_query(const df::coord &pos,
const tile_context &ctx) {
string bld_name, zone_name;
auto & seen = ctx.processor->seen;

if (ctx.b && !seen.count(ctx.b)) {
bld_name = ctx.b->name;
seen.emplace(ctx.b);
}

vector<df::building_civzonest*> civzones;
if (Buildings::findCivzonesAt(&civzones, pos)) {
auto civzone = civzones.back();
if (!seen.count(civzone)) {
zone_name = civzone->name;
seen.emplace(civzone);
}
}

if (!bld_name.size() && !zone_name.size())
return NULL;

ostringstream str;
if (bld_name.size())
str << "{givename name=" + csv_quote(bld_name) + "}";
if (zone_name.size())
str << "{namezone name=" + csv_quote(zone_name) + "}";

return cache(csv_quote(str.str()));
}

static const char * get_tile_rooms(const df::coord &, const tile_context &ctx) {
if (!ctx.b || !ctx.b->is_room)
return NULL;

// get the maximum distance from the center of the building
df::building_extents &room = ctx.b->room;
int32_t x1 = room.x;
int32_t x2 = room.x + room.width - 1;
int32_t y1 = room.y;
int32_t y2 = room.y + room.height - 1;

int32_t dimx = std::max(ctx.b->centerx - x1, x2 - ctx.b->centerx);
int32_t dimy = std::max(ctx.b->centery - y1, y2 - ctx.b->centery);
int32_t max_dim = std::max(dimx, dimy);

switch (max_dim) {
case 0: return "r---&";
case 1: return "r--&";
case 2: return "r-&";
case 3: return "r&";
case 4: return "r+&";
}

ostringstream str;
str << "r{+ " << (max_dim - 3) << "}&";
return cache(str);
}
*/

static bool create_output_dir(color_ostream &out,
const blueprint_options &opts) {
Expand Down Expand Up @@ -1503,13 +1418,7 @@ static bool do_transform(color_ostream &out,
get_tile_build, ensure_building);
add_processor(processors, opts, "place", "place", opts.place,
get_tile_place, ensure_building);
/* TODO: understand how this changes for v50
add_processor(processors, opts, "zone", "zone", opts.zone, get_tile_zone);
add_processor(processors, opts, "query", "query", opts.query,
get_tile_query, ensure_building);
add_processor(processors, opts, "query", "rooms", opts.rooms,
get_tile_rooms, ensure_building);
*/
if (processors.empty()) {
out.printerr("no phases requested! nothing to do!\n");
return false;
Expand Down
7 changes: 2 additions & 5 deletions plugins/lua/blueprint.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,14 @@ local valid_phase_list = {
'construct',
'build',
'place',
-- 'zone',
-- 'query',
-- 'rooms',
'zone',
}
valid_phases = utils.invert(valid_phase_list)

local meta_phase_list = {
'build',
'place',
-- 'zone',
-- 'query',
'zone',
}
meta_phases = utils.invert(meta_phase_list)

Expand Down