dfhack icon indicating copy to clipboard operation
dfhack copied to clipboard

Channel-Safely should detect inaccessible designations

Open cppcooper opened this issue 2 years ago • 11 comments
trafficstars

This is something I'm currently working on, however I'm struggling to grasp a sufficient algorithm. The images below depict designations (circled) which will never be accessible. image image image

The first pair had channel designations between where that gap is, so it they could be reached if those gap designations had of been swapped for dig designations and channeled after the circled were completed. Doing so doesn't strike me as urgent though and I think it's more important to figure out how to determine they are inaccessible.

I currently have the following algorithm (not merged, or even pushed). As mentioned this isn't sufficient, and results in undesirable behaviour (such as insta-digging that gap)

uint8_t count_accessibility(const df::coord &map_pos) {
    df::coord neighbours[8];
    get_neighbours(map_pos, neighbours);
    uint8_t accessibility = 0;
    for (auto n: neighbours) {
        if (Maps::canStepBetween(map_pos, n)) {
            accessibility++;
        }
    }
    return accessibility;
}
void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool marker_mode) {
...
    // cavein prevention
    bool cavein_possible = false;
    uint8_t least_access = 10;
    std::unordered_map<df::coord, uint8_t> cavein_candidates;
    if (!marker_mode) {
        for (const auto &pos: group) {
            df::coord below(pos);
            below.z--;
            auto ttype = *Maps::getTileType(below);
            if (DFHack::isOpenTerrain(ttype) || DFHack::isFloorTerrain(ttype)) {
                cavein_possible = true;
                auto access = count_accessibility(pos);
                if (access == 0) {
                    if (config.insta_dig){
                        CSP::dignow_queue.emplace(pos);

                    }
                } else {
                    cavein_candidates.emplace(pos, access);
                    least_access = min(least_access, access);
                }
            }
        }
    }
    // managing designations
    for (auto &pos: group) {
        if (cavein_possible) {
            // cavein is only possible if marker_mode is false
            // todo: dig least accessible designations, first
            if ((cavein_candidates.count(pos) && cavein_candidates[pos] == least_access) || CSP::dignow_queue.count(pos)) {
                // we want to dig the cavein candidates first
                manage_one(pos, true, false);
            } else {
                manage_one(pos, true, true);
            }
        } else {
            manage_one(pos, true, marker_mode);
        }
    }
...

cppcooper avatar Dec 03 '22 02:12 cppcooper

I've been thinking about this a bit. How about if, for the top z-level of a project, change enough channel designations into dig designations such that all remaining channel designations are reachable from a dig designation. The dig designations can start from an edge of the project that is reachable from a dwarf with mining labor enabled, then flood from there.

Then once the designations are completed, go back and channel the pathways, starting from the inaccessible edges

myk002 avatar Dec 03 '22 19:12 myk002

The problem I take with changing the designations is dealing with disjointed play. -> The plugin changes designations, the player saves and exits, returns, the plugin now only knows about the dig designations. If that problem can be solved then it makes sense to do this I think.

I think ultimately it is the count_accessibility that most needs to change. It occurred to me last night that two impassable rock tiles may pass that step between check, but then fail when compared with passable terrain. Which may be why all the "inaccessible" designations weren't instantly dug at the same time. So checking from a dwarf with mining enabled I think makes sense as an improvement, might need to be a random dwarf with it enabled though, to avoid picking one that somehow ended up on a different section of the map with no path back.

This is certainly no easy feat. Even if correctly counting accessibility, this algorithm may incorrectly predict when insta-dig is necessary (or so I'm thinking)

cppcooper avatar Dec 03 '22 22:12 cppcooper

Just need to test this, but I think the detection should definitely work better.

bool isEntombed(const df::coord &map_pos) {
    df::coord neighbours[8];
    get_neighbours(map_pos, neighbours);
    for (auto n: neighbours) {
        auto digdes = index_tile(Maps::getBlock(n)->designation, n).bits.dig;
        bool has_plan = digdes != df::tile_dig_designation::No && digdes != df::enums::tile_dig_designation::Channel;
        if (isWalkable(*Maps::getTileType(n)) || has_plan) {
            return false;
        }
    }
    return true;
}

uint8_t count_accessibility(const df::coord &unit_pos, const df::coord &map_pos) {
    df::coord neighbours[8];
    get_neighbours(map_pos, neighbours);
    uint8_t accessibility = Maps::canWalkBetween(unit_pos, map_pos) ? 1 : 0;
    for (auto n: neighbours) {
        if (Maps::canWalkBetween(unit_pos, n)) {
            accessibility++;
        }
    }
    return accessibility;
}

// excerpt from management code
        // Analyze designations
        for (const auto &pos: group) {
            df::coord below(pos);
            below.z--;
            auto ttype = *Maps::getTileType(below);
            // we can skip designations already queued for insta-digging
            if (CSP::dignow_queue.count(pos)) continue;
            if (DFHack::isOpenTerrain(ttype) || DFHack::isFloorTerrain(ttype)) {
                cavein_possible = true;
                auto access = count_accessibility(miner->pos, pos);
                if (access == 0) {
                    if (config.insta_dig && isEntombed(pos)){
                        CSP::dignow_queue.emplace(pos);
                    } else {
                        // todo: engage dig management, swap channel designations for dig
                    }
                } else {
                    cavein_candidates.emplace(pos, access);
                    least_access = min(least_access, access);
                }
            }
        }

As for ensuring accessibility, maybe that should be a new issue.. but maybe it's just a matter of some persistent json utilities.

cppcooper avatar Dec 05 '22 19:12 cppcooper

change enough channel designations into dig designations such that all remaining channel designations are reachable from a dig designation.

The problem I take with changing the designations is dealing with disjointed play. -> The plugin changes designations, the player saves and exits, returns, the plugin now only knows about the dig designations. If that problem can be solved then it makes sense to do this I think.

My mind has been leaning towards this as the only easy solution. To solve the problem I pointed out, I think a json will be used to save the information needed to initialize the plugin. I've been thinking about possibly making a [second] wrapper for the persistence module or the underlying json api. It is not yet clear to me whether I'll be able to serialize N data with the tools we have without making the wrapper.

cppcooper avatar Jan 19 '23 02:01 cppcooper

Check out how autochop, autobutcher, and buildingplan do it. They all handle N configs, and they all do it differently.

myk002 avatar Jan 19 '23 02:01 myk002

I'm not talking about N configs, it is N data points

cppcooper avatar Jan 19 '23 03:01 cppcooper

What's the distinction here?

myk002 avatar Jan 19 '23 03:01 myk002

A config suggests to me we are talking about a fortress' plugin settings.

cppcooper avatar Jan 19 '23 03:01 cppcooper

Perhaps I should have said PersistentDataItem. You can easily store N of those with the same identifier or a common prefix

myk002 avatar Jan 19 '23 03:01 myk002

that sounds like extra steps if I need to save (let's say) 300 tiles

cppcooper avatar Jan 19 '23 03:01 cppcooper

Here's buildingplan's implementation. https://github.com/DFHack/dfhack/blob/653e09c322c895f0e3dc7c6d324ac2d5e046ad88/plugins/buildingplan.cpp#L75

myk002 avatar Jan 19 '23 03:01 myk002

This issue is basically done already, in terms of the title. The next update if/when I make that happen was always going to involve the rest of this discussion.

cppcooper avatar Oct 17 '23 22:10 cppcooper