diff --git a/NEWS b/NEWS index 170cf0b73..af14bd057 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,7 @@ DFHack v0.34.11-r3 - restrictliquid - Restrict traffic on every visible square with liquid. - restrictice - Restrict traffic on squares above visible ice. - treefarm - automatically chop trees and dig obsidian + - diggingInvaders - allows invaders to dig and/or deconstruct walls and buildings in order to get at your dwarves. New scripts: - masspit: designate caged creatures in a zone for pitting - locate_ore: scan the map for unmined ore veins diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index a32cb510f..b8d5bd7d8 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -61,8 +61,8 @@ using namespace std; #include "df/z_level_flags.h" #include "df/region_map_entry.h" #include "df/flow_info.h" -#include "df/plant.h" #include "df/building_type.h" +#include "df/plant.h" using namespace DFHack; using namespace df::enums; @@ -568,6 +568,10 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2) if ( !index_tile(block1->walkable,pos1) || !index_tile(block2->walkable,pos2) ) { return false; } + + if ( block1->designation[pos1.x&0xF][pos1.y&0xF].bits.flow_size >= 4 || + block2->designation[pos2.x&0xF][pos2.y&0xF].bits.flow_size >= 4 ) + return false; if ( dz == 0 ) return true; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9ead4d919..5af647cb6 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -79,6 +79,8 @@ add_custom_target(generate_proto DEPENDS ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_H SET_SOURCE_FILES_PROPERTIES( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE ) +add_subdirectory(diggingInvaders) + # Plugins OPTION(BUILD_SUPPORTED "Build the supported plugins (reveal, probe, etc.)." ON) if (BUILD_SUPPORTED) @@ -161,7 +163,6 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(cleanconst cleanconst.cpp) endif() - # this is the skeleton plugin. If you want to make your own, make a copy and then change it OPTION(BUILD_SKELETON "Build the skeleton plugin." OFF) if(BUILD_SKELETON) diff --git a/plugins/diggingInvaders/CMakeLists.txt b/plugins/diggingInvaders/CMakeLists.txt new file mode 100644 index 000000000..a88c93ca1 --- /dev/null +++ b/plugins/diggingInvaders/CMakeLists.txt @@ -0,0 +1,35 @@ +PROJECT (diggingInvaders) +# A list of source files +SET(PROJECT_SRCS + diggingInvaders.cpp + edgeCost.cpp + assignJob.cpp +) +# A list of headers +SET(PROJECT_HDRS + edgeCost.h + assignJob.h +) +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) + +# mash them together (headers are marked as headers and nothing will try to compile them) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) + +#linux +IF(UNIX) + add_definitions(-DLINUX_BUILD) + SET(PROJECT_LIBS + # add any extra linux libs here + ${PROJECT_LIBS} + ) +# windows +ELSE(UNIX) + SET(PROJECT_LIBS + # add any extra linux libs here + ${PROJECT_LIBS} + $(NOINHERIT) + ) +ENDIF(UNIX) +# this makes sure all the stuff is put in proper places and linked to dfhack + +DFHACK_PLUGIN(diggingInvaders ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) diff --git a/plugins/diggingInvaders/assignJob.cpp b/plugins/diggingInvaders/assignJob.cpp new file mode 100644 index 000000000..9472fb0e7 --- /dev/null +++ b/plugins/diggingInvaders/assignJob.cpp @@ -0,0 +1,300 @@ +#include "assignJob.h" + +#include "modules/Buildings.h" +#include "modules/Items.h" +#include "modules/Job.h" +#include "modules/Materials.h" + +#include "df/building.h" +#include "df/construction.h" +#include "df/coord.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/historical_entity.h" +#include "df/item.h" +#include "df/itemdef_weaponst.h" +#include "df/item_quality.h" +#include "df/item_type.h" +#include "df/item_weaponst.h" +#include "df/job.h" +#include "df/job_skill.h" +#include "df/job_type.h" +#include "df/reaction_product_itemst.h" +#include "df/reaction_reagent.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/unit_inventory_item.h" +#include "df/world_site.h" + +void getRidOfOldJob(df::unit* unit) { + if ( unit->job.current_job == NULL ) { + return; + } + + df::job* job = unit->job.current_job; + unit->job.current_job = NULL; + if ( job->list_link->prev != NULL ) { + job->list_link->prev->next = job->list_link->next; + } + if ( job->list_link->next != NULL ) { + job->list_link->next->prev = job->list_link->prev; + } + //TODO: consider building pointers? + //for now, just let the memory leak TODO: fix + //delete job->list_link; + //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, DigAbilities& abilities ) { + 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; + df::coord pt2 = firstImportantEdge.p2; + if ( costMap[pt1] > costMap[pt2] ) { + df::coord temp = pt1; + pt1 = pt2; + pt2 = temp; + } + //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; + + df::map_block* block1 = Maps::getTileBlock(pt1); + df::map_block* block2 = Maps::getTileBlock(pt2); + bool passable1 = block1->walkable[pt1.x&0xF][pt1.y&0xF]; + bool passable2 = block2->walkable[pt2.x&0xF][pt2.y&0xF]; + + df::coord location; + df::building* building = Buildings::findAtTile(pt2); + df::coord buildingPos = pt2; + if ( pt1.z > pt2.z ) { + building = Buildings::findAtTile(df::coord(pt2.x,pt2.y,pt2.z+1)); + buildingPos = df::coord(pt2.x,pt2.y,pt2.z+1); + } + if ( building != NULL ) { + df::coord destroyFrom = parentMap[buildingPos]; + if ( destroyFrom.z != buildingPos.z ) { + //TODO: deal with this + } + //out.print("%s, line %d: Destroying building %d at (%d,%d,%d) from (%d,%d,%d).\n", __FILE__, __LINE__, building->id, buildingPos.x,buildingPos.y,buildingPos.z, destroyFrom.x,destroyFrom.y,destroyFrom.z); + + df::job* job = new df::job; + job->job_type = df::enums::job_type::DestroyBuilding; + //job->flags.bits.special = 1; + df::general_ref_building_holderst* buildingRef = new df::general_ref_building_holderst; + buildingRef->building_id = building->id; + job->general_refs.push_back(buildingRef); + df::general_ref_unit_workerst* workerRef = new df::general_ref_unit_workerst; + workerRef->unit_id = firstInvader->id; + job->general_refs.push_back(workerRef); + getRidOfOldJob(firstInvader); + firstInvader->job.current_job = job; + firstInvader->path.path.x.clear(); + firstInvader->path.path.y.clear(); + firstInvader->path.path.z.clear(); + firstInvader->path.dest = destroyFrom; + location = destroyFrom; + firstInvader->job.hunt_target = NULL; + firstInvader->job.destroy_target = NULL; + + building->jobs.clear(); + building->jobs.push_back(job); + Job::linkIntoWorld(job); + jobId = job->id; + job->completion_timer = abilities.jobDelay[CostDimension::DestroyBuilding]; + } else { + df::tiletype* type1 = Maps::getTileType(pt1); + df::tiletype* type2 = Maps::getTileType(pt2); + df::tiletype_shape shape1 = ENUM_ATTR(tiletype, shape, *type1); + df::tiletype_shape shape2 = ENUM_ATTR(tiletype, shape, *type2); + bool construction2 = ENUM_ATTR(tiletype, material, *type2) == df::enums::tiletype_material::CONSTRUCTION; + if ( construction2 ) { + df::job* job = new df::job; + job->job_type = df::enums::job_type::RemoveConstruction; + df::general_ref_unit_workerst* workerRef = new df::general_ref_unit_workerst; + workerRef->unit_id = firstInvader->id; + job->general_refs.push_back(workerRef); + job->pos = pt2; + getRidOfOldJob(firstInvader); + firstInvader->job.current_job = job; + firstInvader->path.path.x.clear(); + firstInvader->path.path.y.clear(); + firstInvader->path.path.z.clear(); + firstInvader->path.dest = pt1; + location = pt1; + firstInvader->job.hunt_target = NULL; + firstInvader->job.destroy_target = NULL; + Job::linkIntoWorld(job); + jobId = job->id; + df::construction* constr = df::construction::find(pt2); + bool smooth = constr != NULL && constr->item_type != df::enums::item_type::BOULDER; + if ( smooth ) + job->completion_timer = abilities.jobDelay[CostDimension::DestroySmoothConstruction]; + else + job->completion_timer = abilities.jobDelay[CostDimension::DestroyRoughConstruction]; + } else { + bool walkable_low1 = shape1 == df::tiletype_shape::STAIR_DOWN || shape1 == df::tiletype_shape::STAIR_UPDOWN; + bool walkable_low2 = shape2 == df::tiletype_shape::STAIR_DOWN || shape2 == df::tiletype_shape::STAIR_UPDOWN; + bool walkable_high1 = shape1 == df::tiletype_shape::STAIR_UP || shape1 == df::tiletype_shape::STAIR_UPDOWN; + bool walkable_high2 = shape2 == df::tiletype_shape::STAIR_UP || shape2 == df::tiletype_shape::STAIR_UPDOWN; + //must be a dig job + bool up1 = !walkable_high1 && requiresZPos.find(pt1) != requiresZPos.end(); + bool up2 = !walkable_high2 && requiresZPos.find(pt2) != requiresZPos.end(); + bool down1 = !walkable_low1 && requiresZNeg.find(pt1) != requiresZNeg.end(); + bool down2 = !walkable_low2 && requiresZNeg.find(pt2) != requiresZNeg.end(); + bool up; + bool down; + df::coord goHere; + df::coord workHere; + if ( pt1.z == pt2.z ) { + up = up2; + down = down2; + goHere = pt1; + workHere = pt2; + } else { + if ( up1 || down1 ) { + up = up1; + down = down1; + goHere = pt1; + workHere = pt1; + } else { + up = up2; + down = down2; + goHere = pt1; + workHere = pt2; + } + } + df::job* job = new df::job; + if ( up && down ) { + job->job_type = df::enums::job_type::CarveUpDownStaircase; + //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__); + } else if ( !up && down ) { + job->job_type = df::enums::job_type::CarveDownwardStaircase; + //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: 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; + df::general_ref_unit_workerst* ref = new df::general_ref_unit_workerst; + ref->unit_id = firstInvader->id; + job->general_refs.push_back(ref); + firstInvader->job.hunt_target = NULL; + firstInvader->job.destroy_target = NULL; + getRidOfOldJob(firstInvader); + firstInvader->job.current_job = job; + firstInvader->path.path.x.clear(); + firstInvader->path.path.y.clear(); + firstInvader->path.path.z.clear(); + Job::linkIntoWorld(job); + jobId = job->id; + job->completion_timer = abilities.jobDelay[CostDimension::Dig]; + + //TODO: test if he already has a pick + bool hasPick = false; + for ( size_t a = 0; a < firstInvader->inventory.size(); a++ ) { + df::unit_inventory_item* inv_item = firstInvader->inventory[a]; + if ( inv_item->mode != df::unit_inventory_item::Weapon || inv_item->body_part_id != firstInvader->body.weapon_bp ) + continue; + df::item* oldItem = inv_item->item; + if ( oldItem->getType() != df::enums::item_type::WEAPON ) + continue; + df::item_weaponst* oldWeapon = (df::item_weaponst*)oldItem; + df::itemdef_weaponst* oldType = oldWeapon->subtype; + if ( oldType->skill_melee != df::enums::job_skill::MINING ) + continue; + hasPick = true; + break; + } + + if ( hasPick ) + return firstInvader->id; + + //create and give a pick + //based on createitem.cpp + df::reaction_product_itemst *prod = NULL; + //TODO: consider filtering based on entity/civ stuff + for ( size_t a = 0; a < df::global::world->raws.itemdefs.weapons.size(); a++ ) { + df::itemdef_weaponst* oldType = df::global::world->raws.itemdefs.weapons[a]; + if ( oldType->skill_melee != df::enums::job_skill::MINING ) + continue; + prod = df::allocate(); + prod->item_type = df::item_type::WEAPON; + prod->item_subtype = a; + break; + } + if ( prod == NULL ) { + out.print("%s, %d: no valid item.\n", __FILE__, __LINE__); + return -1; + } + + DFHack::MaterialInfo material; + if ( !material.find("OBSIDIAN") ) { + out.print("%s, %d: no water.\n", __FILE__, __LINE__); + return -1; + } + prod->mat_type = material.type; + prod->mat_index = material.index; + prod->probability = 100; + prod->count = 1; + prod->product_dimension = 1; + + vector out_items; + vector in_reag; + vector in_items; + prod->produce(firstInvader, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE, + df::historical_entity::find(firstInvader->civ_id), + df::world_site::find(df::global::ui->site_id)); + + if ( out_items.size() != 1 ) { + out.print("%s, %d: wrong size: %d.\n", __FILE__, __LINE__, out_items.size()); + return -1; + } + out_items[0]->moveToGround(firstInvader->pos.x, firstInvader->pos.y, firstInvader->pos.z); + +#if 0 + //check for existing item there + for ( size_t a = 0; a < firstInvader->inventory.size(); a++ ) { + df::unit_inventory_item* inv_item = firstInvader->inventory[a]; + if ( false || inv_item->body_part_id == part ) { + //throw it on the ground + Items::moveToGround(cache, inv_item->item, firstInvader->pos); + } + } +#endif + Items::moveToInventory(cache, out_items[0], firstInvader, df::unit_inventory_item::T_mode::Weapon, firstInvader->body.weapon_bp); + + delete prod; + } + } + +#if 0 + //tell EVERYONE to move there + for ( size_t a = 0; a < invaders.size(); a++ ) { + df::unit* invader = invaders[a]; + invader->path.path.x.clear(); + invader->path.path.y.clear(); + invader->path.path.z.clear(); + invader->path.dest = location; + //invader->flags1.bits.invades = true; + //invader->flags1.bits.marauder = true; + //invader->flags2.bits.visitor_uninvited = true; + invader->relations.group_leader_id = invader->id; + } +#endif + + return firstInvader->id; +} diff --git a/plugins/diggingInvaders/assignJob.h b/plugins/diggingInvaders/assignJob.h new file mode 100644 index 000000000..0ad6419d6 --- /dev/null +++ b/plugins/diggingInvaders/assignJob.h @@ -0,0 +1,13 @@ +#pragma once + +#include "edgeCost.h" + +#include "modules/MapCache.h" + +#include +#include + +using namespace std; + +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, DigAbilities& abilities); + diff --git a/plugins/diggingInvaders/diggingInvaders.cpp b/plugins/diggingInvaders/diggingInvaders.cpp new file mode 100644 index 000000000..22a82759f --- /dev/null +++ b/plugins/diggingInvaders/diggingInvaders.cpp @@ -0,0 +1,624 @@ +#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_list_link.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 diggingInvadersCommand(color_ostream &out, std::vector & parameters); +void watchForJobComplete(color_ostream& out, void* ptr); +void newInvasionHandler(color_ostream& out, void* ptr); +void clearDijkstra(); +void findAndAssignInvasionJob(color_ostream& out, void*); +//int32_t manageInvasion(color_ostream& out); + +DFHACK_PLUGIN("diggingInvaders"); + +//TODO: when world unloads +static int32_t lastInvasionJob=-1; +static int32_t lastInvasionDigger = -1; +static int32_t edgesPerTick = 100; +//static EventManager::EventHandler jobCompleteHandler(watchForJobComplete, 5); +static bool enabled=false; +static bool activeDigging=false; +static unordered_set diggingRaces; +static unordered_set invaderJobs; +static df::coord lastDebugEdgeCostPoint; +unordered_map digAbilities; + +static cost_t costWeightDefault[] = { +//Distance +1, +//Destroy Building +2, +//Dig +10000, +//DestroyRoughConstruction +1000, +//DestroySmoothConstruction +100, +}; + +static int32_t jobDelayDefault[] = { +//Distance +-1, +//Destroy Building +1000, +//Dig +1000, +//DestroyRoughConstruction +1000, +//DestroySmoothConstruction +100, +}; + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "diggingInvaders", "Makes invaders dig to your dwarves.", + diggingInvadersCommand, false, /* true means that the command can't be used from non-interactive user interface */ + " diggingInvaders 0\n disables the plugin\n" + " diggingInvaders 1\n enables the plugin\n" + " 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. Case-sensitive.\n" + " diggingInvaders remove GOBLIN\n unregisters the race GOBLIN as a digging invader. Case-sensitive.\n" + " diggingInvaders setCost GOBLIN walk n\n sets the walk cost in the path algorithm for the race GOBLIN\n" + " diggingInvaders setCost GOBLIN destroyBuilding n\n" + " diggingInvaders setCost GOBLIN dig n\n" + " diggingInvaders setCost GOBLIN destroyRoughConstruction n\n rough constructions are made from boulders\n" + " diggingInvaders setCost GOBLIN destroySmoothConstruction n\n smooth constructions are made from blocks or bars instead of boulders\n" + " diggingInvaders setDelay GOBLIN destroyBuilding n\n adds to the job_completion_timer of destroy building jobs that are assigned to invaders\n" + " diggingInvaders setDelay GOBLIN dig n\n" + " diggingInvaders setDelay GOBLIN destroyRoughConstruction n\n" + " diggingInvaders setDelay GOBLIN destroySmoothConstruction n\n" + " diggingInvaders now\n makes invaders try to dig now, if plugin is enabled\n" + " diggingInvaders clear\n clears all digging invader races\n" + " diggingInvaders edgesPerTick n\n makes the pathfinding algorithm work on at most n edges per tick. Set to 0 or lower to make it unlimited." +// " diggingInvaders\n Makes invaders try to dig now.\n" + )); + + //*df::global::debug_showambush = true; + 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) +{ + switch (event) { + case DFHack::SC_WORLD_LOADED: + //TODO: check game mode + //in case there are invaders when the game is loaded, we check if there's work to be done + activeDigging = enabled; + clearDijkstra(); + findAndAssignInvasionJob(out, (void*)0); + break; + case DFHack::SC_WORLD_UNLOADED: + // cleanup + lastInvasionJob = lastInvasionDigger = -1; + enabled = false; + activeDigging = false; + clearDijkstra(); + invaderJobs.clear(); + 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; + cost_t c1 = (*i1).second; + cost_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); + +void newInvasionHandler(color_ostream& out, void* ptr) { + if ( activeDigging ) + return; + activeDigging = true; + findAndAssignInvasionJob(out, (void*)0); +} + +command_result diggingInvadersCommand(color_ostream& out, std::vector& parameters) { + for ( size_t a = 0; a < parameters.size(); a++ ) { + if ( parameters[a] == "1" || parameters[a] == "enable" ) { + enabled = true; + } else if ( parameters[a] == "0" || 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]; + if ( parameters[a] == "add" ) { + diggingRaces.insert(race); + DigAbilities& abilities = digAbilities[race]; + memcpy(abilities.costWeight, costWeightDefault, costDim*sizeof(cost_t)); + memcpy(abilities.jobDelay, jobDelayDefault, costDim*sizeof(int32_t)); + } else { + diggingRaces.erase(race); + digAbilities.erase(race); + } + a++; + + } else if ( parameters[a] == "setCost" || parameters[a] == "setDelay" ) { + if ( a+3 >= parameters.size() ) + return CR_WRONG_USAGE; + + string raceString = parameters[a+1]; + if ( digAbilities.find(raceString) == digAbilities.end() ) { + DigAbilities bob; + memset(&bob, 0xFF, sizeof(bob)); + digAbilities[raceString] = bob; + } + DigAbilities& abilities = digAbilities[raceString]; + + string costStr = parameters[a+2]; + int32_t costDim = -1; + if ( costStr == "walk" ) { + costDim = CostDimension::Walk; + if ( parameters[a] == "setDelay" ) + return CR_WRONG_USAGE; + } else if ( costStr == "destroyBuilding" ) { + costDim = CostDimension::DestroyBuilding; + } else if ( costStr == "dig" ) { + costDim = CostDimension::Dig; + } else if ( costStr == "destroyRoughConstruction" ) { + costDim = CostDimension::DestroyRoughConstruction; + } else if ( costStr == "destroySmoothConstruction" ) { + costDim = CostDimension::DestroySmoothConstruction; + } else { + return CR_WRONG_USAGE; + } + + cost_t value; + stringstream asdf(parameters[a+3]); + asdf >> value; + //if ( parameters[a] == "setCost" && value <= 0 ) + // return CR_WRONG_USAGE; + if ( parameters[a] == "setCost" ) { + abilities.costWeight[costDim] = value; + } else { + abilities.jobDelay[costDim] = value; + } + a += 3; + } else if ( parameters[a] == "edgeCost" ) { + if ( a+1 >= parameters.size() ) + return CR_WRONG_USAGE; + + string raceString = parameters[a+1]; + + if ( digAbilities.find(raceString) == digAbilities.end() ) { + out.print("Race %s does not have dig abilities assigned.\n", raceString.c_str()); + return CR_WRONG_USAGE; + } + DigAbilities& abilities = digAbilities[raceString]; + + 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, abilities)); + lastDebugEdgeCostPoint = bob; + a++; + } else if ( parameters[a] == "now" ) { + activeDigging = true; + findAndAssignInvasionJob(out, (void*)0); + } else if ( parameters[a] == "clear" ) { + diggingRaces.clear(); + digAbilities.clear(); + } else if ( parameters[a] == "edgesPerTick" ) { + if ( a+1 >= parameters.size() ) + return CR_WRONG_USAGE; + stringstream asdf(parameters[a+1]); + int32_t edgeCount = 100; + asdf >> edgeCount; + edgesPerTick = edgeCount; + a++; + } + else { + return CR_WRONG_USAGE; + } + } + activeDigging = enabled; + out.print("diggingInvaders: enabled = %d, activeDigging = %d, edgesPerTick = %d\n", enabled, activeDigging, edgesPerTick); + + EventManager::unregisterAll(plugin_self); + if ( enabled ) { + EventManager::EventHandler handler(newInvasionHandler, 1000); + EventManager::registerListener(EventManager::EventType::INVASION, handler, plugin_self); + clearDijkstra(); + findAndAssignInvasionJob(out, (void*)0); + } + + return CR_OK; +} + +///////////////////////////////////////////////////////////////////////////////////////// +//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); + +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); + + if ( !enabled || !activeDigging ) { + clearDijkstra(); + return; + } + EventManager::unregister(EventManager::EventType::TICK, findJobTickHandler, plugin_self); + EventManager::registerTick(findJobTickHandler, 1, plugin_self); + + 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; + 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 ( digAbilities.find(raw->creature_id) == digAbilities.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_max <= 0 || 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; + } + } + + 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 ( overlap ) { + //still keep checking next frame: might kill a few outsiders then dig down + return; + } + } + + df::unit* firstInvader = df::unit::find(invaders[0]); + if ( firstInvader == NULL ) { + fringe.clear(); + return; + } + + df::creature_raw* creature_raw = df::creature_raw::find(firstInvader->race); + if ( creature_raw == NULL || digAbilities.find(creature_raw->creature_id) == digAbilities.end() ) { + //inappropriate digger: no dig abilities + fringe.clear(); + return; + } + DigAbilities& abilities = digAbilities[creature_raw->creature_id]; + //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; + + 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 ( edgesPerTick > 0 && edgesExpanded++ >= edgesPerTick ) { + 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); + 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; + } + } + + cost_t myCost = costMap[pt]; + clock_t edgeTime = clock(); + vector* myEdges = getEdgeSet(out, pt, cache, xMax, yMax, zMax, abilities); + 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() ) { + cost_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("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; + + unordered_set requiresZNeg; + unordered_set requiresZPos; + + //find important edges + Edge firstImportantEdge(df::coord(), df::coord(), -1); + //df::coord closest; + //cost_t closestCostEstimate=0; + //cost_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]; + cost_t cost = getEdgeCost(out, parent, pt, abilities); + if ( cost < 0 ) { + //path invalidated + return; + } + //closestCostActual += cost; + 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 ( 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; + } +*/ + + assignJob(out, firstImportantEdge, parentMap, costMap, invaders, requiresZNeg, requiresZPos, cache, abilities); + 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) { + 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; +} + + diff --git a/plugins/diggingInvaders/edgeCost.cpp b/plugins/diggingInvaders/edgeCost.cpp new file mode 100644 index 000000000..785c55922 --- /dev/null +++ b/plugins/diggingInvaders/edgeCost.cpp @@ -0,0 +1,481 @@ +#include "edgeCost.h" + +#include "modules/Buildings.h" +#include "modules/Maps.h" +#include "modules/MapCache.h" + +#include "df/building.h" +#include "df/building_bridgest.h" +#include "df/building_hatchst.h" +#include "df/building_type.h" +#include "df/construction.h" +#include "df/coord.h" +#include "df/item_type.h" +#include "df/map_block.h" +#include "df/tile_building_occ.h" +#include "df/tiletype.h" +#include "df/tiletype_material.h" +#include "df/tiletype_shape.h" + +#include + +/* +cost_t costWeight[] = { +//Distance +1, +//Destroy Building +2, +//Dig +10000, +//DestroyConstruction +100, +}; + +int32_t jobDelay[] = { +//Distance +-1, +//Destroy Building +1000, +//Dig +1000, +//DestroyConstruction +1000 +}; +*/ + +using namespace std; + +/* +limitations + ramps + cave-ins +*/ +cost_t getEdgeCost(color_ostream& out, df::coord pt1, df::coord pt2, DigAbilities& abilities) { + int32_t dx = pt2.x - pt1.x; + int32_t dy = pt2.y - pt1.y; + int32_t dz = pt2.z - pt1.z; + cost_t cost = abilities.costWeight[CostDimension::Walk]; + if ( cost < 0 ) + return -1; + + if ( Maps::getTileBlock(pt1) == NULL || Maps::getTileBlock(pt2) == NULL ) + return -1; + + df::tiletype* type2 = Maps::getTileType(pt2); + df::tiletype_shape shape2 = ENUM_ATTR(tiletype, shape, *type2); + + if ( Maps::getTileBlock(pt1)->designation[pt1.x&0xF][pt1.y&0xF].bits.flow_size >= 4 ) + return -1; + if ( Maps::getTileBlock(pt2)->designation[pt2.x&0xF][pt2.y&0xF].bits.flow_size >= 4 ) + return -1; + + if ( shape2 == df::enums::tiletype_shape::EMPTY ) { + return -1; + } + + if ( shape2 == df::enums::tiletype_shape::TREE ) + return -1; + +/* + if () { + df::map_block* temp = Maps::getTileBlock(df::coord(pt1.x,pt1.y,pt1.z-1)); + if ( temp && temp->designation[pt1.x&0xF][pt1.y&0xF] + } +*/ + + if ( Maps::canStepBetween(pt1, pt2) ) { + return cost; + } + + df::building* building2 = Buildings::findAtTile(pt2); + if ( building2 ) { + if ( abilities.costWeight[CostDimension::DestroyBuilding] < 0 ) + return -1; + cost += abilities.costWeight[CostDimension::DestroyBuilding]; + if ( dx*dx + dy*dy > 1 ) + return -1; + } + + bool construction2 = ENUM_ATTR(tiletype, material, *type2) == df::enums::tiletype_material::CONSTRUCTION; + if ( construction2 ) { + //smooth or not? + df::construction* constr = df::construction::find(pt2); + bool smooth = constr != NULL && constr->item_type != df::enums::item_type::BOULDER; + if ( smooth ) { + if ( abilities.costWeight[CostDimension::DestroySmoothConstruction] < 0 ) + return -1; + cost += abilities.costWeight[CostDimension::DestroySmoothConstruction]; + } else { + if ( abilities.costWeight[CostDimension::DestroyRoughConstruction] < 0 ) + return -1; + cost += abilities.costWeight[CostDimension::DestroyRoughConstruction]; + } + } + + if ( dz == 0 ) { + if ( !building2 && !construction2 ) { + //it has to be a wall + if ( shape2 == df::enums::tiletype_shape::RAMP_TOP ) { + return -1; + } else if ( shape2 != df::enums::tiletype_shape::WALL ) { + //out << "shape = " << (int32_t)shape2 << endl; + //out << __FILE__ << ", line " << __LINE__ << ": WTF?" << endl; + return cost; + } + if ( abilities.costWeight[CostDimension::Dig] < 0 ) { + return -1; + } + cost += abilities.costWeight[CostDimension::Dig]; + } + } else { + if ( dx == 0 && dy == 0 ) { + df::tiletype* type1 = Maps::getTileType(pt1); + df::tiletype_shape shape1 = ENUM_ATTR(tiletype, shape, *type1); + if ( dz > 0 ) { + bool walkable_low2 = shape2 == df::tiletype_shape::STAIR_DOWN || shape2 == df::tiletype_shape::STAIR_UPDOWN; + if ( !walkable_low2 ) { + if ( building2 || construction2 ) + return -1; + if ( abilities.costWeight[CostDimension::Dig] < 0 ) { + return -1; + } + cost += abilities.costWeight[CostDimension::Dig]; + } + + bool walkable_high1 = shape1 == df::tiletype_shape::STAIR_UP || shape1 == df::tiletype_shape::STAIR_UPDOWN; + if ( !walkable_high1 ) { + if ( shape1 != df::enums::tiletype_shape::WALL ) { + return -1; + } + if ( abilities.costWeight[CostDimension::Dig] < 0 ) { + return -1; + } + cost += abilities.costWeight[CostDimension::Dig]; + } + + if ( building2 ) { + //moving up through an open bridge or a usable hatch is fine. other buildings are not + bool unforbiddenHatch = false; + if ( building2->getType() == df::building_type::Hatch ) { + df::building_hatchst* hatch = (df::building_hatchst*)building2; + if ( !hatch->door_flags.bits.forbidden && !(hatch->door_flags.bits.operated_by_mechanisms&&hatch->door_flags.bits.closed) ) + unforbiddenHatch = true; + } + bool inactiveBridge = false; + if ( building2->getType() == df::building_type::Bridge ) { + df::building_bridgest* bridge = (df::building_bridgest*)building2; + bool xMin = pt2.x == bridge->x1; + bool xMax = pt2.x == bridge->x2; + bool yMin = pt2.y == bridge->y1; + bool yMax = pt2.y == bridge->y2; + if ( !bridge->gate_flags.bits.closed ) { + //if it's open, we could still be in the busy part of it + if ( bridge->direction == df::building_bridgest::T_direction::Left && !xMin ) { + inactiveBridge = true; + } else if ( bridge->direction == df::building_bridgest::T_direction::Right && !xMax ) { + inactiveBridge = true; + } else if ( bridge->direction == df::building_bridgest::T_direction::Up && !yMax ) { + inactiveBridge = true; + } else if ( bridge->direction == df::building_bridgest::T_direction::Down && !yMin ) { + inactiveBridge = true; + } else if ( bridge->direction == df::building_bridgest::T_direction::Retracting ) { + inactiveBridge = true; + } + } + } + if ( !unforbiddenHatch && !inactiveBridge ) + return -1; + } + + /*bool forbidden = false; + if ( building2 && building2->getType() == df::building_type::Hatch ) { + df::building_hatchst* hatch = (df::building_hatchst*)building2; + if ( hatch->door_flags.bits.forbidden ) + forbidden = true; + } + if ( forbidden ) + return -1;*/ + } else { + bool walkable_high2 = shape2 == df::tiletype_shape::STAIR_UP || shape2 == df::tiletype_shape::STAIR_UPDOWN; + if ( !walkable_high2 ) { + if ( building2 || construction2 ) + return -1; + + if ( shape2 != df::enums::tiletype_shape::WALL ) + return -1; + if ( abilities.costWeight[CostDimension::Dig] < 0 ) { + return -1; + } + cost += abilities.costWeight[CostDimension::Dig]; + } + bool walkable_low1 = shape1 == df::tiletype_shape::STAIR_DOWN || shape1 == df::tiletype_shape::STAIR_UPDOWN; + if ( !walkable_low1 ) { + //if ( building1 || construction1 ) + //return -1; + //TODO: consider ramps + if ( shape1 == df::tiletype_shape::RAMP ) + return -1; + if ( abilities.costWeight[CostDimension::Dig] < 0 ) { + return -1; + } + cost += abilities.costWeight[CostDimension::Dig]; + } + + df::building* building1 = Buildings::findAtTile(pt1); + //if you're moving down, and you're on a bridge, and that bridge is lowered, then you can't do it + if ( building1 && building1->getType() == df::building_type::Bridge ) { + df::building_bridgest* bridge = (df::building_bridgest*)building2; + if ( bridge->gate_flags.bits.closed ) { + return -1; + } + //open bridges moving down, standing on bad spot + if ( bridge->direction == df::building_bridgest::T_direction::Left && pt1.x == bridge->x1 ) + return -1; + if ( bridge->direction == df::building_bridgest::T_direction::Right && pt1.x == bridge->x2 ) + return -1; + if ( bridge->direction == df::building_bridgest::T_direction::Up && pt1.y == bridge->y1 ) + return -1; + if ( bridge->direction == df::building_bridgest::T_direction::Down && pt1.y == bridge->y2 ) + return -1; + } + + bool forbidden = false; + if ( building1 && building1->getType() == df::building_type::Hatch ) { + df::building_hatchst* hatch = (df::building_hatchst*)building1; + if ( hatch->door_flags.bits.forbidden || hatch->door_flags.bits.closed && hatch->door_flags.bits.operated_by_mechanisms ) + forbidden = true; + } + + //you can deconstruct a hatch from the side + if ( building1 && forbidden /*&& building1->getType() == df::building_type::Hatch*/ ) { +/* + df::coord support[] = {df::coord(pt1.x-1, pt1.y, pt1.z), df::coord(pt1.x+1,pt1.y,pt1.z), df::coord(pt1.x,pt1.y-1,pt1.z), df::coord(pt1.x,pt1.y+1,pt1.z)}; + if ( abilities.costWeight[CostDimension::DestroyBuilding] < 0 ) { + return -1; + } + cost_t minCost = -1; + for ( size_t a = 0; a < 4; a++ ) { + df::tiletype* supportType = Maps::getTileType(support[a]); + df::tiletype_shape shape = ENUM_ATTR(tiletype, shape, *supportType); + df::tiletype_shape_basic basic = ENUM_ATTR(tiletype_shape, basic_shape, shape); + cost_t cost2 = 2*abilities.costWeight[CostDimension::Walk] + abilities.costWeight[CostDimension::DestroyBuilding]; + if ( !Maps::canStepBetween(pt1, support[a]) ) { + switch(basic) { + case tiletype_shape_basic::Open: + //TODO: check for a hatch or a bridge: that makes it ok + continue; + case tiletype_shape_basic::Wall: + if ( ENUM_ATTR(tiletype, material, *supportType) == df::enums::tiletype_material::CONSTRUCTION ) { + if ( abilities.costWeight[CostDimension::DestroyConstruction] < 0 ) { + continue; + } + cost2 += abilities.costWeight[CostDimension::DestroyConstruction]; + } else { + if ( abilities.costWeight[CostDimension::Dig] < 0 ) { + continue; + } + cost2 += abilities.costWeight[CostDimension::Dig]; + } + case tiletype_shape_basic::Ramp: + //TODO: check for a hatch or a bridge: that makes it ok + if ( shape == df::enums::tiletype_shape::RAMP_TOP ) { + continue; + } + case tiletype_shape_basic::Stair: + case tiletype_shape_basic::Floor: + break; + } + if ( Buildings::findAtTile(support[a]) ) { + if ( abilities.costWeight[CostDimension::DestroyBuilding] < 0 ) { + continue; + } + cost2 += abilities.costWeight[CostDimension::DestroyBuilding]; + } + } + if ( minCost == -1 || cost2 < minCost ) + minCost = cost2; + } + if ( minCost == -1 ) + return -1; + cost += minCost; + +*/ + //note: assignJob is not ready for this level of sophistication, so don't allow it + return -1; + } + } + } else { + //nonvertical + //out.print("%s, line %d: (%d,%d,%d)->(%d,%d,%d)\n", __FILE__, __LINE__, pt1.x,pt1.y,pt1.z, pt2.x,pt2.y,pt2.z); + return -1; + } + } + + return cost; +} + +/* +cost_t getEdgeCostOld(color_ostream& out, df::coord pt1, df::coord pt2) { + //first, list all the facts + int32_t dx = pt2.x - pt1.x; + int32_t dy = pt2.y - pt1.y; + int32_t dz = pt2.z - pt1.z; + cost_t cost = costWeight[CostDimension::Walk]; + + if ( false ) { + if ( Maps::canStepBetween(pt1,pt2) ) + return cost; + return 100 + cost; + } + + Maps::ensureTileBlock(pt1); + Maps::ensureTileBlock(pt2); + df::tiletype* type1 = Maps::getTileType(pt1); + df::tiletype* type2 = Maps::getTileType(pt2); + df::map_block* block1 = Maps::getTileBlock(pt1); + df::map_block* block2 = Maps::getTileBlock(pt2); + df::tiletype_shape shape1 = ENUM_ATTR(tiletype, shape, *type1); + df::tiletype_shape shape2 = ENUM_ATTR(tiletype, shape, *type2); + + bool construction1 = ENUM_ATTR(tiletype, material, *type1) == df::enums::tiletype_material::CONSTRUCTION; + bool construction2 = ENUM_ATTR(tiletype, material, *type2) == df::enums::tiletype_material::CONSTRUCTION; + bool passable1 = block1->walkable[pt1.x&0xF][pt1.y&0xF] != 0; + bool passable2 = block2->walkable[pt2.x&0xF][pt2.y&0xF] != 0; + bool passable_high1 = ENUM_ATTR(tiletype_shape, passable_high, shape1); + bool passable_high2 = ENUM_ATTR(tiletype_shape, passable_high, shape2); + bool passable_low1 = ENUM_ATTR(tiletype_shape, passable_low, shape1); + bool passable_low2 = ENUM_ATTR(tiletype_shape, passable_low, shape2); + + bool building1, building2; + bool sameBuilding = false; + { + df::enums::tile_building_occ::tile_building_occ awk = block1->occupancy[pt1.x&0x0F][pt1.y&0x0F].bits.building; + building1 = awk == df::enums::tile_building_occ::Obstacle || awk == df::enums::tile_building_occ::Impassable; + awk = block2->occupancy[pt2.x&0x0F][pt2.y&0x0F].bits.building; + building2 = awk == df::enums::tile_building_occ::Obstacle || awk == df::enums::tile_building_occ::Impassable; + if ( building1 && building2 ) { + df::building* b1 = Buildings::findAtTile(pt1); + df::building* b2 = Buildings::findAtTile(pt2); + sameBuilding = b1 == b2; + } + } + + if ( Maps::canStepBetween(pt1, pt2) ) { + if ( building2 && !sameBuilding ) { + cost += costWeight[CostDimension::DestroyBuilding]; + } + return cost; + } + + if ( shape2 == df::enums::tiletype_shape::EMPTY ) { + return -1; + } + + //cannot step between: find out why + if ( dz == 0 ) { + if ( passable2 && !passable1 ) { + return cost; + } + if ( passable1 && passable2 ) { + out << __FILE__ << ", line " << __LINE__ << ": WTF?" << endl; + return cost; + } + //pt2 is not passable. it must be a construction, a building, or a wall. + if ( building2 ) { + if ( sameBuilding ) { + //don't charge twice for destroying the same building + return cost; + } + cost += costWeight[CostDimension::DestroyBuilding]; + return cost; + } + if ( construction2 ) { + //impassible constructions must be deconstructed + cost += costWeight[CostDimension::DestroyConstruction]; + return cost; + } + + if ( shape2 == df::enums::tiletype_shape::TREE ) { + return -1; + } + + if ( shape2 == df::enums::tiletype_shape::RAMP_TOP ) { + return -1; + } + + //it has to be a wall + if ( shape2 != df::enums::tiletype_shape::WALL ) { + out << "shape = " << (int32_t)shape2 << endl; + out << __FILE__ << ", line " << __LINE__ << ": WTF?" << endl; + return cost; + } + + cost += costWeight[CostDimension::Dig]; + return cost; + } + + //dz != 0 + if ( dx == 0 && dy == 0 ) { + if ( dz > 0 ) { + if ( passable_low2 ) + return cost; + if ( building2 || construction2 ) { + return -1; + } + cost += costWeight[CostDimension::Dig]; + return cost; + } + + //descending + if ( passable_high2 ) + return cost; + + if ( building2 || construction2 ) { + return -1; + } + + //must be a wall? + if ( shape2 != df::enums::tiletype_shape::WALL ) { + out.print("%s, line %n: WTF?\n", __FILE__, __LINE__); + return cost; + } + + cost += costWeight[CostDimension::Dig]; + return cost; + } + + //moving diagonally + return -1; +} +*/ + +vector* getEdgeSet(color_ostream &out, df::coord point, MapExtras::MapCache& cache, int32_t xMax, int32_t yMax, int32_t zMax, DigAbilities& abilities) { + //vector* result = new vector(26); + vector* result = new vector(); + result->reserve(26); + + //size_t count = 0; + for ( int32_t dx = -1; dx <= 1; dx++ ) { + for ( int32_t dy = -1; dy <= 1; dy++ ) { + for ( int32_t dz = -1; dz <= 1; dz++ ) { + df::coord neighbor(point.x+dx, point.y+dy, point.z+dz); + if ( !Maps::isValidTilePos(neighbor) ) + continue; + if ( dz != 0 && /*(point.x == 0 || point.y == 0 || point.z == 0 || point.x == xMax-1 || point.y == yMax-1 || point.z == zMax-1) ||*/ (neighbor.x == 0 || neighbor.y == 0 || neighbor.z == 0 || neighbor.x == xMax-1 || neighbor.y == yMax-1 || neighbor.z == zMax-1) ) + continue; + if ( dx == 0 && dy == 0 && dz == 0 ) + continue; + cost_t cost = getEdgeCost(out, point, neighbor, abilities); + if ( cost == -1 ) + continue; + Edge edge(point, neighbor, cost); + //(*result)[count] = edge; + result->push_back(edge); + //count++; + } + } + } + + return result; +} + diff --git a/plugins/diggingInvaders/edgeCost.h b/plugins/diggingInvaders/edgeCost.h new file mode 100644 index 000000000..80c49d882 --- /dev/null +++ b/plugins/diggingInvaders/edgeCost.h @@ -0,0 +1,98 @@ +#pragma once + +#include "Core.h" +#include "Console.h" +#include "DataDefs.h" + +#include "modules/Maps.h" +#include "modules/MapCache.h" + +#include "df/coord.h" + +#include +#include + +//cost is [path cost, building destruction cost, dig cost, construct cost]. Minimize constructions, then minimize dig cost, then minimize path cost. +enum CostDimension { + Walk, + DestroyBuilding, + Dig, + DestroyRoughConstruction, + DestroySmoothConstruction, + //Construct, + costDim +}; + +typedef int64_t cost_t; + +struct DigAbilities { + cost_t costWeight[costDim]; + int32_t jobDelay[costDim]; +}; + +//extern cost_t costWeight[costDim]; +//extern int32_t jobDelay[costDim]; +extern std::unordered_map digAbilities; +/* +const cost_t costWeight[] = { +//Distance +1, +//Destroy Building +2, +//Dig +10000, +//DestroyConstruction +100, +}; +*/ + +class Edge { +public: + //static map pointCost; + df::coord p1; + df::coord p2; + cost_t cost; + Edge() { + cost = -1; + } + Edge(const Edge& e): p1(e.p1), p2(e.p2), cost(e.cost) { + + } + Edge(df::coord p1In, df::coord p2In, cost_t costIn): cost(costIn) { + if ( p2In < p1In ) { + p1 = p2In; + p2 = p1In; + } else { + p1 = p1In; + p2 = p2In; + } + } + + bool operator==(const Edge& e) const { + return (cost == e.cost && p1 == e.p1 && p2 == e.p2); + } + + bool operator<(const Edge& e) const { + if ( cost != e.cost ) + return cost < e.cost; + if ( p1.z != e.p1.z ) + return p1.z < e.p1.z; + if ( p1 != e.p1 ) + return p1 < e.p1; + if ( p2.z != e.p2.z ) + return p2.z < e.p2.z; + if ( p2 != e.p2 ) + return p2 < e.p2; + return false; + } +}; + +struct PointHash { + size_t operator()(const df::coord c) const { + return c.x * 65537 + c.y * 17 + c.z; + } +}; + +cost_t getEdgeCost(color_ostream& out, df::coord pt1, df::coord pt2, DigAbilities& abilities); +std::vector* getEdgeSet(color_ostream &out, df::coord point, MapExtras::MapCache& cache, int32_t xMax, int32_t yMax, int32_t zMax, DigAbilities& abilities); +