diff --git a/plugins/diggingInvaders/assignJob.cpp b/plugins/diggingInvaders/assignJob.cpp index 6d27aad9b..2b49fa02c 100644 --- a/plugins/diggingInvaders/assignJob.cpp +++ b/plugins/diggingInvaders/assignJob.cpp @@ -47,8 +47,11 @@ void getRidOfOldJob(df::unit* unit) { //delete job; } -int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map parentMap, unordered_map& costMap, vector& invaders, unordered_set& requiresZNeg, unordered_set& requiresZPos, MapExtras::MapCache& cache, unordered_set& diggingRaces) { - df::unit* firstInvader = invaders[0]; +int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map parentMap, unordered_map& costMap, vector& invaders, unordered_set& requiresZNeg, unordered_set& requiresZPos, MapExtras::MapCache& cache) { + df::unit* firstInvader = df::unit::find(invaders[0]); + if ( !firstInvader ) { + return -1; + } //do whatever you need to do at the first important edge df::coord pt1 = firstImportantEdge.p1; @@ -58,7 +61,7 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map (%d,%d,%d)\n", pt1.x,pt1.y,pt1.z, pt2.x,pt2.y,pt2.z); + out.print("first important edge: (%d,%d,%d) -> (%d,%d,%d)\n", pt1.x,pt1.y,pt1.z, pt2.x,pt2.y,pt2.z); int32_t jobId = -1; @@ -163,18 +166,18 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mapjob_type = df::enums::job_type::CarveUpDownStaircase; - //out.print("%s, line %d: type = up/down\n", __FILE__, __LINE__); + out.print("%s, line %d: type = up/down\n", __FILE__, __LINE__); } else if ( up && !down ) { job->job_type = df::enums::job_type::CarveUpwardStaircase; - //out.print("%s, line %d: type = up\n", __FILE__, __LINE__); + out.print("%s, line %d: type = up\n", __FILE__, __LINE__); } else if ( !up && down ) { job->job_type = df::enums::job_type::CarveDownwardStaircase; - //out.print("%s, line %d: type = down\n", __FILE__, __LINE__); + out.print("%s, line %d: type = down\n", __FILE__, __LINE__); } else { job->job_type = df::enums::job_type::Dig; - //out.print("%s, line %d: type = dig\n", __FILE__, __LINE__); + out.print("%s, line %d: type = dig\n", __FILE__, __LINE__); } - //out.print("%s, line %d: up=%d,up1=%d,up2=%d, down=%d,down1=%d,down2=%d\n", __FILE__, __LINE__, up,up1,up2, down,down1,down2); + out.print("%s, line %d: up=%d,up1=%d,up2=%d, down=%d,down1=%d,down2=%d\n", __FILE__, __LINE__, up,up1,up2, down,down1,down2); job->pos = workHere; firstInvader->path.dest = goHere; location = goHere; @@ -230,7 +233,7 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map parentMap, unordered_map& costMap, vector& invaders, unordered_set& requiresZNeg, unordered_set& requiresZPos, MapExtras::MapCache& cache, unordered_set& diggingRaces); +int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map parentMap, unordered_map& costMap, vector& invaders, unordered_set& requiresZNeg, unordered_set& requiresZPos, MapExtras::MapCache& cache); diff --git a/plugins/diggingInvaders/diggingInvaders.cpp b/plugins/diggingInvaders/diggingInvaders.cpp index f39938752..99607a531 100644 --- a/plugins/diggingInvaders/diggingInvaders.cpp +++ b/plugins/diggingInvaders/diggingInvaders.cpp @@ -36,6 +36,7 @@ #include "df/item_weaponst.h" #include "df/inorganic_raw.h" #include "df/job.h" +#include "df/job_list_link.h" #include "df/job_skill.h" #include "df/job_type.h" #include "df/map_block.h" @@ -67,28 +68,31 @@ using namespace std; using namespace DFHack; using namespace df::enums; -command_result diggingInvadersFunc(color_ostream &out, std::vector & parameters); +command_result diggingInvadersCommand(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); +void newInvasionHandler(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 EventManager::EventHandler jobCompleteHandler(watchForJobComplete, 5); static bool enabled=false; -static unordered_set diggingRaces; +static bool activeDigging=false; +static unordered_set diggingRaces; +static unordered_set invaderJobs; +static df::coord lastDebugEdgeCostPoint; DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - EventManager::EventHandler invasionHandler(initiateDigging, 1000); - EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, plugin_self); + EventManager::EventHandler handler(newInvasionHandler, 1000); + EventManager::registerListener(EventManager::EventType::INVASION, handler, 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 */ + diggingInvadersCommand, 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" @@ -96,6 +100,7 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector >& edges, df::coord prev, set& importantPoints, set& importantEdges); -int32_t findAndAssignInvasionJob(color_ostream& out); +void findAndAssignInvasionJob(color_ostream& out, void*); -void initiateDigging(color_ostream& out, void* ptr) { +void newInvasionHandler(color_ostream& out, void* ptr) { + if ( activeDigging ) + return; + activeDigging = true; + EventManager::EventHandler handler(findAndAssignInvasionJob, 1); + EventManager::registerTick(handler, 1, plugin_self); +#if 0 //called when there's a new invasion //TODO: check if invaders can dig if ( manageInvasion(out) == -2 ) @@ -164,10 +175,12 @@ void initiateDigging(color_ostream& out, void* ptr) { tick = tick % 1000; tick = 1000 - tick; - EventManager::EventHandler handle(initiateDigging, 1000); + EventManager::EventHandler handle(newInvasionHandler, 1000); EventManager::registerTick(handle, tick, plugin_self); +#endif } +#if 0 void watchForJobComplete(color_ostream& out, void* ptr) { /* df::job* job = (df::job*)ptr; @@ -180,7 +193,9 @@ void watchForJobComplete(color_ostream& out, void* ptr) { manageInvasion(out); } +#endif +#if 0 int32_t manageInvasion(color_ostream& out) { //EventManager::unregisterAll(plugin_self); if ( !enabled ) { @@ -233,9 +248,9 @@ int32_t manageInvasion(color_ostream& out) { *df::global::pause_state = true; return 0; //did something } +#endif -static df::coord lastDebugEdgeCostPoint; -command_result diggingInvadersFunc(color_ostream& out, std::vector& parameters) { +command_result diggingInvadersCommand(color_ostream& out, std::vector& parameters) { for ( size_t a = 0; a < parameters.size(); a++ ) { if ( parameters[a] == "enable" ) { enabled = true; @@ -245,7 +260,12 @@ command_result diggingInvadersFunc(color_ostream& out, std::vector& if ( a+1 >= parameters.size() ) return CR_WRONG_USAGE; string race = parameters[a+1]; - bool foundIt = false; + if ( parameters[a] == "add" ) { + diggingRaces.insert(race); + } else { + diggingRaces.erase(race); + } + /*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 ) { @@ -262,7 +282,7 @@ command_result diggingInvadersFunc(color_ostream& out, std::vector& 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() ) @@ -297,92 +317,151 @@ command_result diggingInvadersFunc(color_ostream& out, std::vector& } } - if ( parameters.size() == 0 ) - manageInvasion(out); + if ( parameters.size() == 0 ) { + //manageInvasion(out); + newInvasionHandler(out, (void*)0); + } return CR_OK; } -int32_t findAndAssignInvasionJob(color_ostream& out) { - //returns the worker id of the job created - - //CoreSuspender suspend; +///////////////////////////////////////////////////////////////////////////////////////// +//dijkstra globals +vector invaders; +unordered_set invaderPts; +unordered_set localPts; +unordered_map parentMap; +unordered_map costMap; + +PointComp comp(&costMap); +set fringe(comp); +EventManager::EventHandler findJobTickHandler(findAndAssignInvasionJob, 1); +const int32_t edgesPerFrame = 1000; + +int32_t localPtsFound = 0; +unordered_set closedSet; +unordered_map workNeeded; //non-walking work needed to get there +bool foundTarget = false; +int32_t edgeCount = 0; + +void clearDijkstra() { + invaders.clear(); + invaderPts.clear(); + localPts.clear(); + parentMap.clear(); + costMap.clear(); + comp = PointComp(&costMap); + fringe = set(comp); + localPtsFound = edgeCount = 0; + foundTarget = false; + closedSet.clear(); + workNeeded.clear(); +} +///////////////////////////////////////////////////////////////////////////////////////// + +void findAndAssignInvasionJob(color_ostream& out, void* tickTime) { + CoreSuspender suspend; + //returns the worker id of the job created //used to + //out.print("%s, %d: %d\n", __FILE__, __LINE__, (int32_t)tickTime); - unordered_set invaderPts; - unordered_set localPts; - unordered_map parentMap; - unordered_map costMap; + if ( !activeDigging ) { + clearDijkstra(); + return; + } + EventManager::unregister(EventManager::EventType::TICK, findJobTickHandler, plugin_self); + EventManager::registerTick(findJobTickHandler, 1, plugin_self); - 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() ) + if ( fringe.empty() ) { + df::unit* lastDigger = df::unit::find(lastInvasionDigger); + if ( lastDigger && lastDigger->job.current_job && lastDigger->job.current_job->id == lastInvasionJob ) { + return; + } + //out.print("%s,%d: lastDigger = %d, last job = %d, last digger's job = %d\n", __FILE__, __LINE__, lastInvasionDigger, lastInvasionJob, !lastDigger ? -1 : (!lastDigger->job.current_job ? -1 : lastDigger->job.current_job->id)); + lastInvasionDigger = lastInvasionJob = -1; + + clearDijkstra(); + 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; - df::map_block* block = Maps::getTileBlock(unit->pos); - invaderConnectivity.insert(block->walkable[unit->pos.x&0xF][unit->pos.y&0xF]); - if ( invaderPts.size() > 0 ) + 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 ) { + df::creature_raw* raw = df::creature_raw::find(unit->race); + if ( raw == NULL ) { + out.print("%s,%d: WTF? Couldn't find creature raw.\n", __FILE__, __LINE__); + continue; + } + if ( diggingRaces.find(raw->creature_id) == diggingRaces.end() ) + continue; + if ( invaderPts.find(unit->pos) != invaderPts.end() ) + continue; + //must be able to wield a pick: this is overly pessimistic + if ( unit->status2.limbs_grasp_count < unit->status2.limbs_grasp_max ) + continue; + df::map_block* block = Maps::getTileBlock(unit->pos); + invaderConnectivity.insert(block->walkable[unit->pos.x&0xF][unit->pos.y&0xF]); + if ( invaderPts.size() > 0 ) + continue; + invaderPts.insert(unit->pos); + costMap[unit->pos] = 0; + fringe.insert(unit->pos); + invaders.push_back(unit->id); + } else { continue; - invaderPts.insert(unit->pos); - costMap[unit->pos] = 0; - fringe.insert(unit->pos); - invaders.push_back(unit); - } else { - continue; + } } - } - if ( invaders.empty() || localPts.empty() ) { - return -1; - } + if ( invaders.empty() || localPts.empty() ) { + activeDigging = false; + return; + } - //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 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 ) { + //still keep checking next frame: might kill a few outsiders then dig down + return; + } } - if ( overlap ) { - return -1; + + df::unit* firstInvader = df::unit::find(invaders[0]); + if ( firstInvader == NULL ) { + fringe.clear(); + return; } - df::unit* firstInvader = invaders[0]; + //TODO: check that firstInvader is an appropriate digger //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; + + uint32_t xMax, yMax, zMax; + Maps::getSize(xMax,yMax,zMax); + xMax *= 16; + yMax *= 16; + MapExtras::MapCache cache; clock_t t0 = clock(); clock_t totalEdgeTime = 0; + int32_t edgesExpanded = 0; while(!fringe.empty()) { + if ( edgesExpanded++ >= edgesPerFrame ) { + return; + } 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); @@ -400,7 +479,7 @@ int32_t findAndAssignInvasionJob(color_ostream& out) { } 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; + return; } } @@ -434,27 +513,34 @@ int32_t findAndAssignInvasionJob(color_ostream& out) { 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); + //out.print("tickTime = %d, time = %d, totalEdgeTime = %d, total points = %d, total edges = %d, time per point = %.3f, time per edge = %.3f, clocks/sec = %d\n", (int32_t)tickTime, time, totalEdgeTime, closedSet.size(), edgeCount, (float)time / closedSet.size(), (float)time / edgeCount, CLOCKS_PER_SEC); + fringe.clear(); if ( !foundTarget ) - return -1; + return; unordered_set requiresZNeg; unordered_set requiresZPos; - + //find important edges Edge firstImportantEdge(df::coord(), df::coord(), -1); + df::coord closest; + int64_t closestCostEstimate=0; + int64_t closestCostActual=0; 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; + closest = pt; + closestCostEstimate = costMap[closest]; //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]; + closestCostActual += getEdgeCost(out, parent, pt); if ( Maps::canStepBetween(parent, pt) ) { } else { @@ -477,7 +563,13 @@ int32_t findAndAssignInvasionJob(color_ostream& out) { } break; } - + if ( firstImportantEdge.p1 == df::coord() ) + return; + + if ( closestCostActual != closestCostEstimate ) { + out.print("%s,%d: closest = (%d,%d,%d), estimate = %lld != actual = %lld\n", __FILE__, __LINE__, closest.x,closest.y,closest.z, closestCostEstimate, closestCostActual); + return; + } #if 0 unordered_set toDelete; for ( auto a = requiresZNeg.begin(); a != requiresZNeg.end(); a++ ) { @@ -502,7 +594,25 @@ int32_t findAndAssignInvasionJob(color_ostream& out) { toDelete.clear(); #endif - return assignJob(out, firstImportantEdge, parentMap, costMap, invaders, requiresZNeg, requiresZPos, cache, diggingRaces); + assignJob(out, firstImportantEdge, parentMap, costMap, invaders, requiresZNeg, requiresZPos, cache); + lastInvasionDigger = firstInvader->id; + lastInvasionJob = firstInvader->job.current_job ? firstInvader->job.current_job->id : -1; + invaderJobs.erase(lastInvasionJob); + for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + if ( link->item == NULL ) + continue; + df::job* job = link->item; + if ( invaderJobs.find(job->id) == invaderJobs.end() ) { + continue; + } + + //cancel it + job->flags.bits.item_lost = 1; + out.print("%s,%d: cancelling job %d.\n", __FILE__,__LINE__, job->id); + //invaderJobs.remove(job->id); + } + invaderJobs.erase(lastInvasionJob); + return; } df::coord getRoot(df::coord point, map& rootMap) {