dfhack/plugins/diggingInvaders/edgeCost.cpp

485 lines
20 KiB
C++

#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 <iostream>
using namespace DFHack;
/*
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::BRANCH ||
shape2 == df::enums::tiletype_shape::TRUNK_BRANCH ||
shape2 == df::enums::tiletype_shape::TWIG )
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<Edge>* getEdgeSet(color_ostream &out, df::coord point, MapExtras::MapCache& cache, int32_t xMax, int32_t yMax, int32_t zMax, DigAbilities& abilities) {
//vector<Edge>* result = new vector<Edge>(26);
vector<Edge>* result = new vector<Edge>();
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;
}