#include "assignJob.h" #include "edgeCost.h" #include "Core.h" #include "Console.h" #include "DataDefs.h" #include "Export.h" #include "PluginManager.h" #include "Types.h" #include "modules/Buildings.h" #include "modules/EventManager.h" #include "modules/Gui.h" #include "modules/Job.h" #include "modules/Maps.h" #include "modules/MapCache.h" #include "modules/Units.h" #include "modules/World.h" #include "df/body_part_raw_flags.h" #include "df/building.h" #include "df/building_type.h" #include "df/caste_body_info.h" #include "df/coord.h" #include "df/creature_raw.h" #include "df/general_ref.h" #include "df/general_ref_building_holderst.h" #include "df/general_ref_unit.h" #include "df/general_ref_unit_holderst.h" #include "df/general_ref_unit_workerst.h" #include "df/global_objects.h" #include "df/invasion_info.h" #include "df/item.h" #include "df/itemdef_weaponst.h" #include "df/item_quality.h" #include "df/item_weaponst.h" #include "df/inorganic_raw.h" #include "df/job.h" #include "df/job_skill.h" #include "df/job_type.h" #include "df/map_block.h" #include "df/strain_type.h" #include "df/tile_building_occ.h" #include "df/tile_occupancy.h" #include "df/tiletype.h" #include "df/tiletype_material.h" #include "df/tiletype_shape.h" #include "df/tiletype_shape_basic.h" #include "df/ui.h" #include "df/unit.h" #include "df/unit_inventory_item.h" #include "df/world.h" #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace DFHack; using namespace df::enums; command_result diggingInvadersFunc(color_ostream &out, std::vector & parameters); void watchForJobComplete(color_ostream& out, void* ptr); void initiateDigging(color_ostream& out, void* ptr); int32_t manageInvasion(color_ostream& out); DFHACK_PLUGIN("diggingInvaders"); //TODO: when world unloads static int32_t lastInvasionJob=-1; static int32_t lastInvasionDigger = -1; static EventManager::EventHandler jobCompleteHandler(watchForJobComplete, 5); static bool enabled=false; static unordered_set diggingRaces; DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { EventManager::EventHandler invasionHandler(initiateDigging, 1000); EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, plugin_self); commands.push_back(PluginCommand( "diggingInvaders", "Makes invaders dig to your dwarves.", diggingInvadersFunc, false, /* true means that the command can't be used from non-interactive user interface */ " diggingInvaders enable\n enables the plugin\n" " diggingInvaders disable\n disables the plugin\n" " diggingInvaders add GOBLIN\n registers the race GOBLIN as a digging invader\n" " diggingInvaders remove GOBLIN\n unregisters the race GOBLIN as a digging invader\n" " diggingInvaders\n Makes invaders try to dig now.\n" )); return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { EventManager::EventHandler invasionHandler(initiateDigging, 1000); switch (event) { case DFHack::SC_WORLD_LOADED: //TODO: check game mode lastInvasionJob = -1; //in case there are invaders when the game is loaded, we should check EventManager::registerTick(invasionHandler, 10, plugin_self); break; case DFHack::SC_WORLD_UNLOADED: // cleanup break; default: break; } return CR_OK; } df::coord getRoot(df::coord point, unordered_map& rootMap); class PointComp { public: unordered_map *pointCost; PointComp(unordered_map *p): pointCost(p) { } int32_t operator()(df::coord p1, df::coord p2) { if ( p1 == p2 ) return 0; auto i1 = pointCost->find(p1); auto i2 = pointCost->find(p2); if ( i1 == pointCost->end() && i2 == pointCost->end() ) return p1 < p2; if ( i1 == pointCost->end() ) return true; if ( i2 == pointCost->end() ) return false; int64_t c1 = (*i1).second; int64_t c2 = (*i2).second; if ( c1 != c2 ) return c1 < c2; return p1 < p2; } }; //bool important(df::coord pos, map >& edges, df::coord prev, set& importantPoints, set& importantEdges); int32_t findAndAssignInvasionJob(color_ostream& out); void initiateDigging(color_ostream& out, void* ptr) { //called when there's a new invasion //TODO: check if invaders can dig if ( manageInvasion(out) == -2 ) return; //schedule the next thing uint32_t tick = World::ReadCurrentTick(); tick = tick % 1000; tick = 1000 - tick; EventManager::EventHandler handle(initiateDigging, 1000); EventManager::registerTick(handle, tick, plugin_self); } void watchForJobComplete(color_ostream& out, void* ptr) { /* df::job* job = (df::job*)ptr; if ( job->id != lastInvasionJob ) return; EventManager::unregister(EventManager::EventType::JOB_COMPLETED, jobCompleteHandler, plugin_self); */ manageInvasion(out); } int32_t manageInvasion(color_ostream& out) { //EventManager::unregisterAll(plugin_self); if ( !enabled ) { return -1; } int32_t lastInvasion = df::global::ui->invasions.next_id-1; if ( lastInvasion < 0 || df::global::ui->invasions.list[lastInvasion]->flags.bits.active == 0 ) { //if the invasion is over, we're done //out.print("Invasion is over. Stopping diggingInvaders.\n"); return -2; } EventManager::registerTick(jobCompleteHandler, 1, plugin_self); if ( lastInvasionJob != -1 ) { //check if he's still doing it df::unit* worker = df::unit::find(lastInvasionDigger); //int32_t index = df::unit::binsearch_index(df::global::world->units.all, lastInvasionDigger); if ( !worker ) { out.print("Error %s line %d.\n", __FILE__, __LINE__); return -1; } df::job* job = worker->job.current_job; //out.print("job id: old = %d, new = %d\n", lastInvasionJob, job == NULL ? -1 : job->id); if ( job != NULL && lastInvasionJob == job->id ) { //out.print("Still working on the previous job.\n"); return -1; } //return 1; //still invading, but nothing new done } int32_t unitId = findAndAssignInvasionJob(out); if ( unitId == -1 ) { //might need to do more digging later, after we've killed off a few locals //out.print("DiggingInvaders is waiting.\n"); return -1; } *df::global::pause_state = true; lastInvasionDigger = unitId; { int32_t index = df::unit::binsearch_index(df::global::world->units.all, unitId); if ( index == -1 ) { //out.print("Error %s line %d: unitId = %d, index = %d.\n", __FILE__, __LINE__, unitId, index); return -1; } lastInvasionJob = df::global::world->units.all[index]->job.current_job->id; } //EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, jobCompleteHandler, plugin_self); //out.print("DiggingInvaders: job assigned.\n"); return 0; //did something } static df::coord lastDebugEdgeCostPoint; command_result diggingInvadersFunc(color_ostream& out, std::vector& parameters) { for ( size_t a = 0; a < parameters.size(); a++ ) { if ( parameters[a] == "enable" ) { enabled = true; } else if ( parameters[a] == "disable" ) { enabled = false; } else if ( parameters[a] == "add" || parameters[a] == "remove" ) { if ( a+1 >= parameters.size() ) return CR_WRONG_USAGE; string race = parameters[a+1]; bool foundIt = false; for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) { df::creature_raw* raw = df::global::world->raws.creatures.all[b]; if ( race == raw->creature_id ) { //out.print("%s = %s\n", race.c_str(), raw->creature_id.c_str()); if ( parameters[a] == "add" ) { diggingRaces.insert(b); } else { diggingRaces.erase(b); } foundIt = true; break; } } if ( !foundIt ) { out.print("Couldn't find \"%s\"\n", race.c_str()); return CR_WRONG_USAGE; } a++; } else if ( parameters[a] == "setCost" ) { if ( a+2 >= parameters.size() ) return CR_WRONG_USAGE; string costStr = parameters[a+1]; int32_t costDim = -1; if ( costStr == "walk" ) { costDim = CostDimension::Walk; } else if ( costStr == "destroyBuilding" ) { costDim = CostDimension::DestroyBuilding; } else if ( costStr == "dig" ) { costDim = CostDimension::Dig; } else if ( costStr == "destroyConstruction" ) { costDim = CostDimension::DestroyConstruction; } else { return CR_WRONG_USAGE; } int64_t value; stringstream asdf(parameters[a+2]); asdf >> value; if ( value <= 0 ) return CR_WRONG_USAGE; costWeight[costDim] = value; a += 2; } else if ( parameters[a] == "edgeCost" ) { df::coord bob = Gui::getCursorPos(); out.print("(%d,%d,%d), (%d,%d,%d): cost = %lld\n", lastDebugEdgeCostPoint.x, lastDebugEdgeCostPoint.y, lastDebugEdgeCostPoint.z, bob.x, bob.y, bob.z, getEdgeCost(out, lastDebugEdgeCostPoint, bob)); lastDebugEdgeCostPoint = bob; } else { return CR_WRONG_USAGE; } } if ( parameters.size() == 0 ) manageInvasion(out); return CR_OK; } int32_t findAndAssignInvasionJob(color_ostream& out) { //returns the worker id of the job created //CoreSuspender suspend; unordered_set invaderPts; unordered_set localPts; unordered_map parentMap; unordered_map costMap; PointComp comp(&costMap); set fringe(comp); uint32_t xMax, yMax, zMax; Maps::getSize(xMax,yMax,zMax); xMax *= 16; yMax *= 16; MapExtras::MapCache cache; vector invaders; unordered_set invaderConnectivity; unordered_set localConnectivity; //find all locals and invaders for ( size_t a = 0; a < df::global::world->units.all.size(); a++ ) { df::unit* unit = df::global::world->units.all[a]; if ( unit->flags1.bits.dead ) continue; if ( Units::isCitizen(unit) ) { if ( localPts.find(unit->pos) != localPts.end() ) continue; localPts.insert(unit->pos); df::map_block* block = Maps::getTileBlock(unit->pos); localConnectivity.insert(block->walkable[unit->pos.x&0xF][unit->pos.y&0xF]); } else if ( unit->flags1.bits.active_invader ) { if ( diggingRaces.find(unit->race) == diggingRaces.end() ) continue; if ( invaderPts.find(unit->pos) != invaderPts.end() ) continue; invaderPts.insert(unit->pos); costMap[unit->pos] = 0; fringe.insert(unit->pos); invaders.push_back(unit); df::map_block* block = Maps::getTileBlock(unit->pos); invaderConnectivity.insert(block->walkable[unit->pos.x&0xF][unit->pos.y&0xF]); } else { continue; } } if ( invaders.empty() ) { return -1; } //if local connectivity is not disjoint from invader connectivity, no digging required bool overlap = false; for ( auto a = localConnectivity.begin(); a != localConnectivity.end(); a++ ) { uint16_t conn = *a; if ( invaderConnectivity.find(conn) == invaderConnectivity.end() ) continue; overlap = true; break; } if ( overlap ) { return -1; } df::unit* firstInvader = invaders[0]; //out << firstInvader->id << endl; //out << firstInvader->pos.x << ", " << firstInvader->pos.y << ", " << firstInvader->pos.z << endl; //out << __LINE__ << endl; int32_t localPtsFound = 0; unordered_set closedSet; unordered_map workNeeded; //non-walking work needed to get there bool foundTarget = false; int32_t edgeCount = 0; clock_t t0 = clock(); clock_t totalEdgeTime = 0; while(!fringe.empty()) { df::coord pt = *(fringe.begin()); fringe.erase(fringe.begin()); //out.print("line %d: fringe size = %d, localPtsFound = %d / %d, closedSetSize = %d, pt = %d,%d,%d\n", __LINE__, fringe.size(), localPtsFound, localPts.size(), closedSet.size(), pt.x,pt.y,pt.z); if ( closedSet.find(pt) != closedSet.end() ) { out.print("%s, line %d: Double closure! Bad!\n", __FILE__, __LINE__); break; } closedSet.insert(pt); if ( localPts.find(pt) != localPts.end() ) { localPtsFound++; if ( true || localPtsFound >= localPts.size() ) { foundTarget = true; break; } if ( workNeeded.find(pt) == workNeeded.end() || workNeeded[pt] == 0 ) { //there are still dwarves to kill that don't require digging to get to return -1; } } int64_t myCost = costMap[pt]; clock_t edgeTime = clock(); vector* myEdges = getEdgeSet(out, pt, cache, xMax, yMax, zMax); totalEdgeTime += (clock() - edgeTime); for ( auto a = myEdges->begin(); a != myEdges->end(); a++ ) { Edge &e = *a; if ( e.p1 == df::coord() ) break; edgeCount++; df::coord& other = e.p1; if ( other == pt ) other = e.p2; //if ( closedSet.find(other) != closedSet.end() ) // continue; auto i = costMap.find(other); if ( i != costMap.end() ) { int64_t cost = (*i).second; if ( cost <= myCost + e.cost ) { continue; } fringe.erase((*i).first); } costMap[other] = myCost + e.cost; fringe.insert(other); parentMap[other] = pt; workNeeded[other] = (e.cost > 1 ? 1 : 0) + workNeeded[pt]; } delete myEdges; } clock_t time = clock() - t0; //out.print("time = %d, totalEdgeTime = %d, total points = %d, total edges = %d, time per point = %.3f, time per edge = %.3f, clocks/sec = %d\n", time, totalEdgeTime, closedSet.size(), edgeCount, (float)time / closedSet.size(), (float)time / edgeCount, CLOCKS_PER_SEC); if ( !foundTarget ) return -1; unordered_set requiresZNeg; unordered_set requiresZPos; //find important edges Edge firstImportantEdge(df::coord(), df::coord(), -1); for ( auto i = localPts.begin(); i != localPts.end(); i++ ) { df::coord pt = *i; if ( costMap.find(pt) == costMap.end() ) continue; if ( parentMap.find(pt) == parentMap.end() ) continue; //if ( workNeeded[pt] == 0 ) // continue; while ( parentMap.find(pt) != parentMap.end() ) { //out.print("(%d,%d,%d)\n", pt.x, pt.y, pt.z); df::coord parent = parentMap[pt]; if ( Maps::canStepBetween(parent, pt) ) { } else { if ( pt.x == parent.x && pt.y == parent.y ) { if ( pt.z < parent.z ) { requiresZNeg.insert(parent); requiresZPos.insert(pt); } else if ( pt.z > parent.z ) { requiresZNeg.insert(pt); requiresZPos.insert(parent); } } //if ( workNeeded[pt] > workNeeded[parent] ) { //importantEdges.push_front(Edge(pt,parent,0)); //} firstImportantEdge = Edge(pt,parent,0); //out.print("(%d,%d,%d) -> (%d,%d,%d)\n", parent.x,parent.y,parent.z, pt.x,pt.y,pt.z); } pt = parent; } break; } #if 0 unordered_set toDelete; for ( auto a = requiresZNeg.begin(); a != requiresZNeg.end(); a++ ) { df::coord pos = *a; df::tiletype* type = Maps::getTileType(pos); df::tiletype_shape shape = ENUM_ATTR(tiletype, shape, *type); if ( ENUM_ATTR(tiletype_shape, passable_low, shape) ) { toDelete.insert(pos); } } requiresZNeg.erase(toDelete.begin(), toDelete.end()); toDelete.clear(); for ( auto a = requiresZPos.begin(); a != requiresZPos.end(); a++ ) { df::coord pos = *a; df::tiletype* type = Maps::getTileType(pos); df::tiletype_shape shape = ENUM_ATTR(tiletype, shape, *type); if ( ENUM_ATTR(tiletype_shape, passable_high, shape) ) { toDelete.insert(pos); } } requiresZPos.erase(toDelete.begin(), toDelete.end()); toDelete.clear(); #endif return assignJob(out, firstImportantEdge, parentMap, costMap, invaders, requiresZNeg, requiresZPos, cache, diggingRaces); } df::coord getRoot(df::coord point, map& rootMap) { map::iterator i = rootMap.find(point); if ( i == rootMap.end() ) { rootMap[point] = point; return point; } df::coord parent = (*i).second; if ( parent == point ) return parent; df::coord root = getRoot(parent, rootMap); rootMap[point] = root; return root; }