Merge remote-tracking branch 'PatrikLundell/embark-assistant' into develop

develop
lethosor 2018-02-10 17:27:13 -05:00
commit ef3adbe816
19 changed files with 5949 additions and 0 deletions

@ -111,6 +111,7 @@ if (BUILD_SUPPORTED)
add_subdirectory(diggingInvaders) add_subdirectory(diggingInvaders)
DFHACK_PLUGIN(dwarfvet dwarfvet.cpp) DFHACK_PLUGIN(dwarfvet dwarfvet.cpp)
DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua)
add_subdirectory(embark-assistant)
DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(embark-tools embark-tools.cpp)
DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(fastdwarf fastdwarf.cpp) DFHACK_PLUGIN(fastdwarf fastdwarf.cpp)

@ -0,0 +1,48 @@
PROJECT (embark-assistant)
# A list of source files
SET(PROJECT_SRCS
biome_type.cpp
embark-assistant.cpp
finder_ui.cpp
help_ui.cpp
matcher.cpp
overlay.cpp
screen.cpp
survey.cpp
)
# A list of headers
SET(PROJECT_HDRS
biome_type.h
defs.h
embark-assistant.h
finder_ui.h
help_ui.h
matcher.h
overlay.h
screen.h
survey.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})
# option to use a thread for no particular reason
#OPTION(SKELETON_THREAD "Use threads in the skeleton plugin." ON)
#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 windows libs here
${PROJECT_LIBS}
$(NOINHERIT)
)
ENDIF(UNIX)
# this makes sure all the stuff is put in proper places and linked to dfhack
DFHACK_PLUGIN(embark-assistant ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS})

@ -0,0 +1,754 @@
/* The code is copied from Ragundo's repo referenced below.
The changes are:
- The addition of a .h file reference.
- The simplification of the code using ofsub to remove the use of (and
.h reference to) that function (analysis of the code showed the
simplified code is the result, as the ofsub expressions will never be
true given the range of the values it can be passed in these functions).
- The change of the main function to take a separate y coordinate for
use in the tropicality determination to allow proper determination of
the tropicality of mid level tiles ("region tiles") referencing a
neighboring world tile's biome.
*/
/*
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
// You can always find the latest version of this plugin in Github
// https://github.com/ragundo/exportmaps
#include <utility>
#include "DataDefs.h"
#include <df/region_map_entry.h>
#include <df/world.h>
#include <df/world_data.h>
#include <df/biome_type.h>
#include "biome_type.h"
/*****************************************************************************
Local functions forward declaration
*****************************************************************************/
std::pair<bool, bool> check_tropicality(df::region_map_entry& region,
int a1
);
int get_lake_biome(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude
);
int get_ocean_biome(df::region_map_entry& region,
bool is_tropical_area_by_latitude
);
int get_desert_biome(df::region_map_entry& region);
int get_biome_grassland(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_savanna(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_shrubland(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_marsh(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_forest(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_swamp(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_desert_or_grassland_or_savanna(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_shrubland_or_marsh(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
/*****************************************************************************
Module main function.
Return the biome type, given a position coordinate expressed in world_tiles
The world ref coordinates are used for tropicality determination and may refer
to a tile neighboring the "official" one.
*****************************************************************************/
int get_biome_type(int world_coord_x,
int world_coord_y,
int world_ref_coord_y
)
{
// Biome is per region, so get the region where this biome exists
df::region_map_entry& region = df::global::world->world_data->region_map[world_coord_x][world_coord_y];
// Check if the y reference position coordinate belongs to a tropical area
std::pair<bool, bool> p = check_tropicality(region,
world_ref_coord_y
);
bool is_possible_tropical_area_by_latitude = p.first;
bool is_tropical_area_by_latitude = p.second;
// Begin the discrimination
if (region.flags.is_set(df::region_map_entry_flags::is_lake)) // is it a lake?
return get_lake_biome(region,
is_possible_tropical_area_by_latitude
);
// Not a lake. Check elevation
// Elevation greater then 149 means a mountain biome
// Elevation below 100 means a ocean biome
// Elevation between 100 and 149 are land biomes
if (region.elevation >= 150) // is it a mountain?
return df::enums::biome_type::biome_type::MOUNTAIN; // 0
if (region.elevation < 100) // is it a ocean?
return get_ocean_biome(region,
is_possible_tropical_area_by_latitude
);
// land biome. Elevation between 100 and 149
if (region.temperature <= -5)
{
if (region.drainage < 75)
return df::enums::biome_type::biome_type::TUNDRA; // 2
else
return df::enums::biome_type::biome_type::GLACIER; // 1
}
// Not a lake, mountain, ocean, glacier or tundra
// Vegetation determines the biome type
if (region.vegetation < 66)
{
if (region.vegetation < 33)
return get_biome_desert_or_grassland_or_savanna(region,
is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
world_coord_y,
world_coord_x
);
else // vegetation between 33 and 65
return get_biome_shrubland_or_marsh(region,
is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
world_coord_y,
world_coord_x
);
}
// Not a lake, mountain, ocean, glacier, tundra, desert, grassland or savanna
// vegetation >= 66
if (region.drainage >= 33)
return get_biome_forest(region,
is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
world_coord_y,
world_coord_x
);
// Not a lake, mountain, ocean, glacier, tundra, desert, grassland, savanna or forest
// vegetation >= 66, drainage < 33
return get_biome_swamp(region,
is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
world_coord_y,
world_coord_x);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
std::pair<bool, bool> check_tropicality_no_poles_world(df::region_map_entry& region,
int y_pos
)
{
bool is_possible_tropical_area_by_latitude = false;
bool is_tropical_area_by_latitude = false;
// If there're no poles, tropical area is determined by temperature
if (region.temperature >= 75)
is_possible_tropical_area_by_latitude = true;
is_tropical_area_by_latitude = region.temperature >= 85;
return std::pair<bool, bool>(is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude
);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
std::pair<bool, bool> check_tropicality_north_pole_only_world(df::region_map_entry& region,
int y_pos
)
{
int v6;
bool is_possible_tropical_area_by_latitude = false;
bool is_tropical_area_by_latitude = false;
df::world_data* wdata = df::global::world->world_data;
// Scale the smaller worlds to the big one
if (wdata->world_height == 17)
v6 = 16 * y_pos;
else if (wdata->world_height == 33)
v6 = 8 * y_pos;
else if (wdata->world_height == 65)
v6 = 4 * y_pos;
else if (wdata->world_height == 129)
v6 = 2 * y_pos;
else
v6 = y_pos;
is_possible_tropical_area_by_latitude = v6 > 170;
is_tropical_area_by_latitude = v6 >= 200;
return std::pair<bool, bool>(is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude
);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
std::pair<bool, bool> check_tropicality_south_pole_only_world(df::region_map_entry& region,
int y_pos
)
{
int v6 = df::global::world->world_data->world_height - y_pos - 1;
bool is_possible_tropical_area_by_latitude = false;
bool is_tropical_area_by_latitude = false;
df::world_data* wdata = df::global::world->world_data;
if (wdata->world_height == 17)
v6 *= 16;
else if (wdata->world_height == 33)
v6 *= 8;
else if (wdata->world_height == 65)
v6 *= 4;
else if (wdata->world_height == 129)
v6 *= 2;
else
v6 *= 1;
is_possible_tropical_area_by_latitude = v6 > 170;
is_tropical_area_by_latitude = v6 >= 200;
return std::pair<bool, bool>(is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude
);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
std::pair<bool, bool> check_tropicality_both_poles_world(df::region_map_entry& region,
int y_pos
)
{
int v6;
bool is_possible_tropical_area_by_latitude = false;
bool is_tropical_area_by_latitude = false;
df::world_data* wdata = df::global::world->world_data;
if (y_pos < wdata->world_height / 2)
v6 = 2 * y_pos;
else
{
v6 = wdata->world_height + 2 * (wdata->world_height / 2 - y_pos) - 1;
if (v6 < 0)
v6 = 0;
if (v6 >= wdata->world_height)
v6 = wdata->world_height - 1;
}
if (wdata->world_height == 17)
v6 *= 16;
else if (wdata->world_height == 33)
v6 *= 8;
else if (wdata->world_height == 65)
v6 *= 4;
else if (wdata->world_height == 129)
v6 *= 2;
else
v6 *= 1;
is_possible_tropical_area_by_latitude = v6 > 170;
is_tropical_area_by_latitude = v6 >= 200;
return std::pair<bool, bool>(is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude
);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
std::pair<bool, bool> check_tropicality(df::region_map_entry& region,
int y_pos
)
{
int flip_latitude = df::global::world->world_data->flip_latitude;
if (flip_latitude == -1) // NO POLES
return check_tropicality_no_poles_world(region,
y_pos
);
else if (flip_latitude == 0) // NORTH POLE ONLY
return check_tropicality_north_pole_only_world(region,
y_pos
);
else if (flip_latitude == 1) // SOUTH_POLE ONLY
return check_tropicality_south_pole_only_world(region,
y_pos
);
else if (flip_latitude == 2) // BOTH POLES
return check_tropicality_both_poles_world(region,
y_pos
);
return std::pair<bool, bool>(false, false);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_parameter_percentage(int flip_latitude,
int y_pos,
int rainfall,
int world_height
)
{
int result;
int ypos = y_pos;
if (flip_latitude == -1) // NO POLES
return 100;
else if (flip_latitude == 1) // SOUTH POLE
ypos = world_height - y_pos - 1;
else if (flip_latitude == 2) // NORTH & SOUTH POLE
{
if (ypos < world_height / 2)
ypos *= 2;
else
{
ypos = world_height + 2 * (world_height / 2 - ypos) - 1;
if (ypos < 0)
ypos = 0;
if (ypos >= world_height)
ypos = world_height - 1;
}
}
int latitude; // 0 - 256 (size of a large world)
switch (world_height)
{
case 17: // Pocket world
latitude = 16 * ypos;
break;
case 33: // Smaller world
latitude = 8 * ypos;
break;
case 65: // Small world
latitude = 4 * ypos;
break;
case 129: // Medium world
latitude = 2 * ypos;
break;
default: // Large world
latitude = ypos;
break;
}
// latitude > 220
if ((latitude - 171) > 49)
return 100;
// Latitude between 191 and 200
if ((latitude > 190) && (latitude < 201))
return 0;
// Latitude between 201 and 220
if ((latitude > 190) && (latitude >= 201))
result = rainfall + 16 * (latitude - 207);
else
// Latitude between 0 and 190
result = (16 * (184 - latitude) - rainfall);
if (result < 0)
return 0;
if (result > 100)
return 100;
return result;
}
//----------------------------------------------------------------------------//
// Utility function
//
// return some unknow parameter as a percentage
//----------------------------------------------------------------------------//
int get_region_parameter(int y,
int x,
char a4
)
{
int result = 100;
if ((df::global::cur_season && *df::global::cur_season != 1) || !a4)
{
int world_height = df::global::world->world_data->world_height;
if (world_height > 65) // Medium and large worlds
{
// access to region 2D array
df::region_map_entry& region = df::global::world->world_data->region_map[x][y];
return get_parameter_percentage(df::global::world->world_data->flip_latitude,
y,
region.rainfall,
world_height
);
}
}
return result;
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_lake_biome(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude
)
{
// salinity values tell us the lake type
// greater than 66 is a salt water lake
// between 33 and 65 is a brackish water lake
// less than 33 is a fresh water lake
if (region.salinity < 66)
{
if (region.salinity < 33)
if (is_possible_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::LAKE_TROPICAL_FRESHWATER; // 39
else
return df::enums::biome_type::biome_type::LAKE_TEMPERATE_FRESHWATER; // 36
else // salinity >= 33
if (is_possible_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::LAKE_TROPICAL_BRACKISHWATER; // 40
else
return df::enums::biome_type::biome_type::LAKE_TEMPERATE_BRACKISHWATER; // 37
}
else // salinity >= 66
{
if (is_possible_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::LAKE_TROPICAL_SALTWATER;// 41
else
return df::enums::biome_type::biome_type::LAKE_TEMPERATE_SALTWATER; // 38
}
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_ocean_biome(df::region_map_entry& region,
bool is_tropical_area_by_latitude
)
{
if (is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::OCEAN_TROPICAL; // 27
else
if (region.temperature <= -5)
return df::enums::biome_type::biome_type::OCEAN_ARCTIC; // 29
else
return df::enums::biome_type::biome_type::OCEAN_TEMPERATE; // 28
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_desert_biome(df::region_map_entry& region)
{
if (region.drainage < 66)
{
if (region.drainage < 33)
return df::enums::biome_type::biome_type::DESERT_SAND; // 26
else // drainage between 33 and 65
return df::enums::biome_type::biome_type::DESERT_ROCK; // 25
}
// drainage >= 66
return df::enums::biome_type::biome_type::DESERT_BADLAND; // 24
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_grassland(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::GRASSLAND_TROPICAL; // 21
else
return df::enums::biome_type::biome_type::GRASSLAND_TEMPERATE; //18;
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_savanna(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) <= 6)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::SAVANNA_TROPICAL; // 22
else
return df::enums::biome_type::biome_type::SAVANNA_TEMPERATE; //19;
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_desert_or_grassland_or_savanna(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if (region.vegetation < 20)
{
if (region.vegetation < 10)
return get_desert_biome(region);
else // vegetation between 10 and 19
return get_biome_grassland(is_possible_tropical_area_by_latitude, is_tropical_area_by_latitude, y, x);
}
// vegetation between 20 and 32
return get_biome_savanna(is_possible_tropical_area_by_latitude, is_tropical_area_by_latitude, y, x);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_shrubland(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if (is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66 || is_tropical_area_by_latitude))
return df::enums::biome_type::biome_type::SHRUBLAND_TROPICAL; // 23
else
return df::enums::biome_type::biome_type::SHRUBLAND_TEMPERATE; // 20
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_marsh(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if (region.salinity < 66)
{
if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::MARSH_TROPICAL_FRESHWATER; // 10
else
return df::enums::biome_type::biome_type::MARSH_TEMPERATE_FRESHWATER; // 5
}
else // drainage < 33, salinity >= 66
{
if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::MARSH_TROPICAL_SALTWATER; // 11
else
return df::enums::biome_type::biome_type::MARSH_TEMPERATE_SALTWATER; // 6
}
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_shrubland_or_marsh(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if (region.drainage >= 33)
return get_biome_shrubland(is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
y,
x
);
// drainage < 33
return get_biome_marsh(region,
is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
y,
x
);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_forest(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
int parameter = get_region_parameter(y, x, 0);
// drainage >= 33, not tropical area
if (!is_possible_tropical_area_by_latitude)
{
if ((region.rainfall < 75) || (region.temperature < 65))
{
if (region.temperature >= 10)
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_CONIFER; // 13
else
return df::enums::biome_type::biome_type::FOREST_TAIGA; // 12
}
else
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_BROADLEAF; // 14
}
else // drainage >= 33, tropical area
{
if (((parameter < 66) || is_tropical_area_by_latitude) && (region.rainfall < 75))
return df::enums::biome_type::biome_type::FOREST_TROPICAL_CONIFER; // 15
if (parameter < 66)
return df::enums::biome_type::biome_type::FOREST_TROPICAL_DRY_BROADLEAF; // 16
if (is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::FOREST_TROPICAL_MOIST_BROADLEAF; // 17
else
{
if ((region.rainfall < 75) || (region.temperature < 65))
{
if (region.temperature >= 10)
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_CONIFER; // 13
else
return df::enums::biome_type::biome_type::FOREST_TAIGA; // 12
}
else
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_BROADLEAF; // 14
}
}
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_swamp(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
int parameter = get_region_parameter(y, x, 0);
if (is_possible_tropical_area_by_latitude)
{
if (region.salinity < 66)
{
if ((parameter < 66) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::SWAMP_TROPICAL_FRESHWATER; // 7
else
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_FRESHWATER;// 3
}
else // elevation between 100 and 149, vegetation >= 66, drainage < 33, salinity >= 66
{
if ((parameter < 66) || is_tropical_area_by_latitude)
{
if (region.drainage < 10)
return df::enums::biome_type::biome_type::SWAMP_MANGROVE; //9
else // drainage >= 10
return df::enums::biome_type::biome_type::SWAMP_TROPICAL_SALTWATER; // 8
}
else
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_SALTWATER; // 4
}
}
else // elevation between 100 and 149, vegetation >= 66, drainage < 33, not tropical area
{
if (region.salinity >= 66)
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_SALTWATER; // 4
else
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_FRESHWATER; // 3
}
}

@ -0,0 +1,7 @@
// world_coord_x/y is the location of the tile "owning" the biome, while world_ref_coord_y is the
// location of the tile the biome appears on. They differ when a mid level tile ("region tile")
// refers to a neighboring tile for the biome parameters. The difference can affect the tropicality
// determination. Since Tropicality is determined by latitude, the x coordinate of the reference is
// omitted.
//
int get_biome_type(int world_coord_x, int world_coord_y, int world_ref_coord_y);

@ -0,0 +1,256 @@
#pragma once
#include <array>
#include <string>
#include <vector>
using namespace std;
using std::array;
using std::ostringstream;
using std::string;
using std::vector;
namespace embark_assist {
namespace defs {
// Survey types
//
enum class river_sizes {
None,
Brook,
Stream,
Minor,
Medium,
Major
};
struct mid_level_tile {
bool aquifer = false;
bool clay = false;
bool sand = false;
bool flux = false;
int8_t soil_depth;
int8_t offset;
int16_t elevation;
bool river_present = false;
int16_t river_elevation = 100;
int8_t biome_offset;
uint8_t savagery_level; // 0 - 2
uint8_t evilness_level; // 0 - 2
std::vector<bool> metals;
std::vector<bool> economics;
std::vector<bool> minerals;
};
typedef std::array<std::array<mid_level_tile, 16>, 16> mid_level_tiles;
// typedef mid_level_tile mid_level_tiles[16][16];
struct region_tile_datum {
bool surveyed = false;
uint16_t aquifer_count = 0;
uint16_t clay_count = 0;
uint16_t sand_count = 0;
uint16_t flux_count = 0;
uint8_t min_region_soil = 10;
uint8_t max_region_soil = 0;
bool waterfall = false;
river_sizes river_size;
int16_t biome_index[10]; // Indexed through biome_offset; -1 = null, Index of region, [0] not used
int16_t biome[10]; // Indexed through biome_offset; -1 = null, df::biome_type, [0] not used
uint8_t biome_count;
bool evil_weather[10];
bool evil_weather_possible;
bool evil_weather_full;
bool reanimating[10];
bool reanimating_possible;
bool reanimating_full;
bool thralling[10];
bool thralling_possible;
bool thralling_full;
uint16_t savagery_count[3];
uint16_t evilness_count[3];
std::vector<bool> metals;
std::vector<bool> economics;
std::vector<bool> minerals;
};
struct geo_datum {
uint8_t soil_size = 0;
bool top_soil_only = true;
bool top_soil_aquifer_only = true;
bool aquifer_absent = true;
bool clay_absent = true;
bool sand_absent = true;
bool flux_absent = true;
std::vector<bool> possible_metals;
std::vector<bool> possible_economics;
std::vector<bool> possible_minerals;
};
typedef std::vector<geo_datum> geo_data;
struct sites {
uint8_t x;
uint8_t y;
char type;
};
struct site_infos {
bool aquifer;
bool aquifer_full;
uint8_t min_soil;
uint8_t max_soil;
bool flat;
bool waterfall;
bool clay;
bool sand;
bool flux;
std::vector<uint16_t> metals;
std::vector<uint16_t> economics;
std::vector<uint16_t> minerals;
// Could add savagery, evilness, and biomes, but DF provides those easily.
};
typedef std::vector<sites> site_lists;
typedef std::vector<std::vector<region_tile_datum>> world_tile_data;
typedef bool mlt_matches[16][16];
// An embark region match is indicated by marking the top left corner
// tile as a match. Thus, the bottom and right side won't show matches
// unless the appropriate dimension has a width of 1.
struct matches {
bool preliminary_match;
bool contains_match;
mlt_matches mlt_match;
};
typedef std::vector<std::vector<matches>> match_results;
// matcher types
//
enum class evil_savagery_values : int8_t {
NA = -1,
All,
Present,
Absent
};
enum class evil_savagery_ranges : int8_t {
Low,
Medium,
High
};
enum class aquifer_ranges : int8_t {
NA = -1,
All,
Present,
Partial,
Not_All,
Absent
};
enum class river_ranges : int8_t {
NA = -1,
None,
Brook,
Stream,
Minor,
Medium,
Major
};
enum class yes_no_ranges : int8_t {
NA = -1,
Yes,
No
};
enum class all_present_ranges : int8_t {
All,
Present
};
enum class present_absent_ranges : int8_t {
NA = -1,
Present,
Absent
};
enum class soil_ranges : int8_t {
NA = -1,
None,
Very_Shallow,
Shallow,
Deep,
Very_Deep
};
/* // Future possible enhancement
enum class freezing_ranges : int8_t {
NA = -1,
Permanent,
At_Least_Partial,
Partial,
At_Most_Partial,
Never
};
*/
struct finders {
uint16_t x_dim;
uint16_t y_dim;
evil_savagery_values savagery[static_cast<int8_t>(evil_savagery_ranges::High) + 1];
evil_savagery_values evilness[static_cast<int8_t>(evil_savagery_ranges::High) + 1];
aquifer_ranges aquifer;
river_ranges min_river;
river_ranges max_river;
yes_no_ranges waterfall;
yes_no_ranges flat;
present_absent_ranges clay;
present_absent_ranges sand;
present_absent_ranges flux;
soil_ranges soil_min;
all_present_ranges soil_min_everywhere;
soil_ranges soil_max;
/*freezing_ranges freezing;*/
yes_no_ranges evil_weather; // Will probably blow up with the magic release arcs...
yes_no_ranges reanimation;
yes_no_ranges thralling;
int8_t biome_count_min; // N/A(-1), 1-9
int8_t biome_count_max; // N/A(-1), 1-9
int8_t region_type_1; // N/A(-1), df::world_region_type
int8_t region_type_2; // N/A(-1), df::world_region_type
int8_t region_type_3; // N/A(-1), df::world_region_type
int8_t biome_1; // N/A(-1), df::biome_type
int8_t biome_2; // N/A(-1), df::biome_type
int8_t biome_3; // N/A(-1), df::biome_type
int16_t metal_1; // N/A(-1), 0-max_inorganic;
int16_t metal_2; // N/A(-1), 0-max_inorganic;
int16_t metal_3; // N/A(-1), 0-max_inorganic;
int16_t economic_1; // N/A(-1), 0-max_inorganic;
int16_t economic_2; // N/A(-1), 0-max_inorganic;
int16_t economic_3; // N/A(-1), 0-max_inorganic;
int16_t mineral_1; // N/A(-1), 0-max_inorganic;
int16_t mineral_2; // N/A(-1), 0-max_inorganic;
int16_t mineral_3; // N/A(-1), 0-max_inorganic;
};
struct match_iterators {
bool active;
uint16_t x; // x position of focus when iteration started so we can return it.
uint16_t y; // y
uint16_t i;
uint16_t k;
bool x_right;
bool y_down;
bool inhibit_x_turn;
bool inhibit_y_turn;
uint16_t count;
finders finder;
};
typedef void(*find_callbacks) (embark_assist::defs::finders finder);
}
}

@ -0,0 +1,296 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <time.h>
#include <modules/Gui.h>
#include <modules/Screen.h>
#include "DataDefs.h"
#include "df/coord2d.h"
#include "df/inorganic_flags.h"
#include "df/inorganic_raw.h"
#include "df/interfacest.h"
#include "df/viewscreen.h"
#include "df/viewscreen_choose_start_sitest.h"
#include "df/world.h"
#include "df/world_data.h"
#include "df/world_geo_biome.h"
#include "df/world_raws.h"
#include "defs.h"
#include "embark-assistant.h"
#include "finder_ui.h"
#include "matcher.h"
#include "overlay.h"
#include "survey.h"
DFHACK_PLUGIN("embark-assistant");
using namespace DFHack;
using namespace df::enums;
using namespace Gui;
REQUIRE_GLOBAL(world);
namespace embark_assist {
namespace main {
struct states {
embark_assist::defs::geo_data geo_summary;
embark_assist::defs::world_tile_data survey_results;
embark_assist::defs::site_lists region_sites;
embark_assist::defs::site_infos site_info;
embark_assist::defs::match_results match_results;
embark_assist::defs::match_iterators match_iterator;
uint16_t max_inorganic;
};
static states *state = nullptr;
void embark_update ();
void shutdown();
//===============================================================================
void embark_update() {
auto screen = Gui::getViewscreenByType<df::viewscreen_choose_start_sitest>(0);
embark_assist::defs::mid_level_tiles mlt;
embark_assist::survey::initiate(&mlt);
embark_assist::survey::survey_mid_level_tile(&state->geo_summary,
&state->survey_results,
&mlt);
embark_assist::survey::survey_embark(&mlt, &state->site_info, false);
embark_assist::overlay::set_embark(&state->site_info);
embark_assist::survey::survey_region_sites(&state->region_sites);
embark_assist::overlay::set_sites(&state->region_sites);
embark_assist::overlay::set_mid_level_tile_match(state->match_results.at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match);
}
//===============================================================================
void match() {
// color_ostream_proxy out(Core::getInstance().getConsole());
uint16_t count = embark_assist::matcher::find(&state->match_iterator,
&state->geo_summary,
&state->survey_results,
&state->match_results);
embark_assist::overlay::match_progress(count, &state->match_results, !state->match_iterator.active);
if (!state->match_iterator.active) {
auto screen = Gui::getViewscreenByType<df::viewscreen_choose_start_sitest>(0);
embark_assist::overlay::set_mid_level_tile_match(state->match_results.at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match);
}
}
//===============================================================================
void clear_match() {
// color_ostream_proxy out(Core::getInstance().getConsole());
if (state->match_iterator.active) {
embark_assist::matcher::move_cursor(state->match_iterator.x, state->match_iterator.y);
}
embark_assist::survey::clear_results(&state->match_results);
embark_assist::overlay::clear_match_results();
embark_assist::main::state->match_iterator.active = false;
}
//===============================================================================
void find(embark_assist::defs::finders finder) {
// color_ostream_proxy out(Core::getInstance().getConsole());
state->match_iterator.x = embark_assist::survey::get_last_pos().x;
state->match_iterator.y = embark_assist::survey::get_last_pos().y;
state->match_iterator.finder = finder;
embark_assist::overlay::initiate_match();
}
//===============================================================================
void shutdown() {
// color_ostream_proxy out(Core::getInstance().getConsole());
embark_assist::survey::shutdown();
embark_assist::finder_ui::shutdown();
embark_assist::overlay::shutdown();
delete state;
state = nullptr;
}
}
}
//=======================================================================================
command_result embark_assistant (color_ostream &out, std::vector <std::string> & parameters);
//=======================================================================================
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"embark-assistant", "Embark site selection support.",
embark_assistant, true, /* true means that the command can't be used from non-interactive user interface */
// Extended help string. Used by CR_WRONG_USAGE and the help command:
" This command starts the embark-assist plugin that provides embark site\n"
" selection help. It has to be called while th pre embark screen is\n"
" displayed and shows extended (and correct(?)) resource information for\n"
" the embark rectangle as well as normally undisplayed sites in the\n"
" current embark region. It also has a site selection tool with more\n"
" options than DF's vanilla search tool. For detailed help invoke the\n"
" in game info screen. Requires 42 lines to display properly.\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)
{
switch (event) {
case DFHack::SC_UNKNOWN:
break;
case DFHack::SC_WORLD_LOADED:
break;
case DFHack::SC_WORLD_UNLOADED:
case DFHack::SC_MAP_LOADED:
if (embark_assist::main::state) {
embark_assist::main::shutdown();
}
break;
case DFHack::SC_MAP_UNLOADED:
break;
case DFHack::SC_VIEWSCREEN_CHANGED:
break;
case DFHack::SC_CORE_INITIALIZED:
break;
case DFHack::SC_BEGIN_UNLOAD:
break;
case DFHack::SC_PAUSED:
break;
case DFHack::SC_UNPAUSED:
break;
}
return CR_OK;
}
//=======================================================================================
command_result embark_assistant(color_ostream &out, std::vector <std::string> & parameters)
{
if (!parameters.empty())
return CR_WRONG_USAGE;
CoreSuspender suspend;
auto screen = Gui::getViewscreenByType<df::viewscreen_choose_start_sitest>(0);
if (!screen) {
out.printerr("This plugin works only in the embark site selection phase.\n");
return CR_WRONG_USAGE;
}
df::world_data *world_data = world->world_data;
if (embark_assist::main::state) {
out.printerr("You can't invoke the embark assistant while it's already active.\n");
return CR_WRONG_USAGE;
}
embark_assist::main::state = new embark_assist::main::states;
embark_assist::main::state->match_iterator.active = false;
// Find the end of the normal inorganic definitions.
embark_assist::main::state->max_inorganic = 0;
for (uint16_t i = 0; i < world->raws.inorganics.size(); i++) {
if (world->raws.inorganics[i]->flags.is_set(df::inorganic_flags::GENERATED)) embark_assist::main::state->max_inorganic = i;
}
embark_assist::main::state->max_inorganic++; // To allow it to be used as size() replacement
if (!embark_assist::overlay::setup(plugin_self,
embark_assist::main::embark_update,
embark_assist::main::match,
embark_assist::main::clear_match,
embark_assist::main::find,
embark_assist::main::shutdown,
embark_assist::main::state->max_inorganic)) {
return CR_FAILURE;
}
embark_assist::survey::setup(embark_assist::main::state->max_inorganic);
embark_assist::main::state->geo_summary.resize(world_data->geo_biomes.size());
embark_assist::main::state->survey_results.resize(world->worldgen.worldgen_parms.dim_x);
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
embark_assist::main::state->survey_results[i].resize(world->worldgen.worldgen_parms.dim_y);
for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) {
embark_assist::main::state->survey_results[i][k].surveyed = false;
embark_assist::main::state->survey_results[i][k].aquifer_count = 0;
embark_assist::main::state->survey_results[i][k].clay_count = 0;
embark_assist::main::state->survey_results[i][k].sand_count = 0;
embark_assist::main::state->survey_results[i][k].flux_count = 0;
embark_assist::main::state->survey_results[i][k].min_region_soil = 10;
embark_assist::main::state->survey_results[i][k].max_region_soil = 0;
embark_assist::main::state->survey_results[i][k].waterfall = false;
embark_assist::main::state->survey_results[i][k].river_size = embark_assist::defs::river_sizes::None;
for (uint8_t l = 1; l < 10; l++) {
embark_assist::main::state->survey_results[i][k].biome_index[l] = -1;
embark_assist::main::state->survey_results[i][k].biome[l] = -1;
embark_assist::main::state->survey_results[i][k].evil_weather[l] = false;
embark_assist::main::state->survey_results[i][k].reanimating[l] = false;
embark_assist::main::state->survey_results[i][k].thralling[l] = false;
}
for (uint8_t l = 0; l < 2; l++) {
embark_assist::main::state->survey_results[i][k].savagery_count[l] = 0;
embark_assist::main::state->survey_results[i][k].evilness_count[l] = 0;
}
embark_assist::main::state->survey_results[i][k].metals.resize(embark_assist::main::state->max_inorganic);
embark_assist::main::state->survey_results[i][k].economics.resize(embark_assist::main::state->max_inorganic);
embark_assist::main::state->survey_results[i][k].minerals.resize(embark_assist::main::state->max_inorganic);
}
}
embark_assist::survey::high_level_world_survey(&embark_assist::main::state->geo_summary,
&embark_assist::main::state->survey_results);
embark_assist::main::state->match_results.resize(world->worldgen.worldgen_parms.dim_x);
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
embark_assist::main::state->match_results[i].resize(world->worldgen.worldgen_parms.dim_y);
}
embark_assist::survey::clear_results(&embark_assist::main::state->match_results);
embark_assist::survey::survey_region_sites(&embark_assist::main::state->region_sites);
embark_assist::overlay::set_sites(&embark_assist::main::state->region_sites);
embark_assist::defs::mid_level_tiles mlt;
embark_assist::survey::survey_mid_level_tile(&embark_assist::main::state->geo_summary, &embark_assist::main::state->survey_results, &mlt);
embark_assist::survey::survey_embark(&mlt, &embark_assist::main::state->site_info, false);
embark_assist::overlay::set_embark(&embark_assist::main::state->site_info);
return CR_OK;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,17 @@
#pragma once
#include "PluginManager.h"
#include "DataDefs.h"
#include "defs.h"
using namespace DFHack;
namespace embark_assist {
namespace finder_ui {
void init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic);
void activate();
void shutdown();
}
}

@ -0,0 +1,314 @@
#include "Core.h"
#include <Console.h>
#include <string>
#include <set>
#include <modules/Gui.h>
#include "Types.h"
#include "help_ui.h"
#include "screen.h"
using std::vector;
namespace embark_assist{
namespace help_ui {
enum class pages {
Intro,
General,
Finder,
Caveats
};
class ViewscreenHelpUi : public dfhack_viewscreen
{
public:
ViewscreenHelpUi();
void feed(std::set<df::interface_key> *input);
void render();
std::string getFocusString() { return "Help UI"; }
private:
pages current_page = pages::Intro;
};
//===============================================================================
void ViewscreenHelpUi::feed(std::set<df::interface_key> *input) {
if (input->count(df::interface_key::LEAVESCREEN))
{
input->clear();
Screen::dismiss(this);
return;
}
else if (input->count(df::interface_key::CHANGETAB)) {
switch (current_page) {
case pages::Intro:
current_page = pages::General;
break;
case pages::General:
current_page = pages::Finder;
break;
case pages::Finder:
current_page = pages::Caveats;
break;
case pages::Caveats:
current_page = pages::Intro;
break;
}
}
else if (input->count(df::interface_key::SEC_CHANGETAB)) {
switch (current_page) {
case pages::Intro:
current_page = pages::Caveats;
break;
case pages::General:
current_page = pages::Intro;
break;
case pages::Finder:
current_page = pages::General;
break;
case pages::Caveats:
current_page = pages::Intro;
break;
}
}
}
//===============================================================================
void ViewscreenHelpUi::render() {
color_ostream_proxy out(Core::getInstance().getConsole());
auto screen_size = DFHack::Screen::getWindowSize();
Screen::Pen pen(' ', COLOR_WHITE);
Screen::Pen site_pen = Screen::Pen(' ', COLOR_YELLOW, COLOR_BLACK, false);
Screen::Pen pen_lr(' ', COLOR_LIGHTRED);
std::vector<std::string> help_text;
Screen::clear();
switch (current_page) {
case pages::Intro:
Screen::drawBorder("Embark Assistant Help/Info Introduction Page");
help_text.push_back("Embark Assistant is used on the embark selection screen to provide information");
help_text.push_back("to help selecting a suitable embark site. It provides three services:");
help_text.push_back("- Display of normally invisible sites overlayed on the region map.");
help_text.push_back("- Embark rectangle resources. More detailed and correct than vanilla DF.");
help_text.push_back("- Site find search. Richer set of selection criteria than the vanilla");
help_text.push_back(" DF Find that Embark Assistant suppresses (by using the same key).");
help_text.push_back("");
help_text.push_back("The functionality requires a screen height of at least 42 lines to display");
help_text.push_back("correctly (that's the height of the Finder screen), as fitting everything");
help_text.push_back("onto a standard 80*25 screen would be too challenging. The help is adjusted");
help_text.push_back("to fit into onto an 80*42 screen.");
help_text.push_back("This help/info is split over several screens, and you can move between them");
help_text.push_back("using the TAB/Shift-TAB keys, and leave the help from any screen using ESC.");
help_text.push_back("");
help_text.push_back("When the Embark Assistant is started it provides site information (if any)");
help_text.push_back("as an overlay over the region map. Beneath that you will find a summary of");
help_text.push_back("the resources in the current embark rectangle (explained in more detail on");
help_text.push_back("the the next screen).");
help_text.push_back("On the right side the command keys the Embark Assistant uses are listed");
help_text.push_back("(this partially overwrites the DFHack Embark Tools information until the");
help_text.push_back("screen is resized). It can also be mentioned that the DF 'f'ind key help");
help_text.push_back("at the bottom of the screen is masked as the functionality is overridden by");
help_text.push_back("the Embark Assistant.");
help_text.push_back("Main screen control keys used by the Embark Assistant:");
help_text.push_back("i: Info/Help. Brings up this display.");
help_text.push_back("f: Brings up the Find Embark screen. See the Find page for more information.");
help_text.push_back("c: Clears the results of a Find operation, and also cancels an operation if");
help_text.push_back(" one is under way.");
help_text.push_back("q: Quits the Embark Assistant and brings you back to the vanilla DF interface.");
help_text.push_back(" It can be noted that the Embark Assistant automatically cancels itself");
help_text.push_back(" when DF leaves the embark screen either through <ESC>Abort Game or by");
help_text.push_back(" embarking.");
help_text.push_back("Below this a Matching World Tiles count is displayed. It shows the number");
help_text.push_back("of World Tiles that have at least one embark matching the Find criteria.");
break;
case pages::General:
Screen::drawBorder("Embark Assistant Help/Info General Page");
help_text.push_back("The Embark Assistant overlays the region map with characters indicating sites");
help_text.push_back("normally not displayed by DF. The following key is used:");
help_text.push_back("C: Camp");
help_text.push_back("c: Cave. Only displayed if the DF worldgen parameter does not display caves.");
help_text.push_back("i: Important Location. The author doesn't actually know what those are.");
help_text.push_back("l: Lair");
help_text.push_back("L: Labyrinth");
help_text.push_back("M: Monument. The author is unsure how/if this is broken down further.");
help_text.push_back("S: Shrine");
help_text.push_back("V: Vault");
help_text.push_back("The Embark info below the region map differs from the vanilla DF display in a");
help_text.push_back("few respects. Firstly, it shows resources in the embark rectangle, rather than");
help_text.push_back("DF's display of resources in the region DF currently displays. Secondly, the");
help_text.push_back("DF display doesn't take elevation based soil erosion or the magma sea depth");
help_text.push_back("into consideration, so it can display resources that actually are cut away.");
help_text.push_back("(It can be noted that the DFHack Sand indicator does take these elements into");
help_text.push_back("account).");
help_text.push_back("The info the Embark Assistant displays is:");
help_text.push_back("Sand, if present");
help_text.push_back("Clay, if present");
help_text.push_back("Min and Max soil depth in the embark rectangle.");
help_text.push_back("Flat indicator if all the tiles in the embark have the same elevation.");
help_text.push_back("Aquifer indicator, color coded as blue if all tiles have an aquifer and light");
help_text.push_back("blue if some, but not all, tiles have one.");
help_text.push_back("Waterfall, if the embark has river elevation differences.");
help_text.push_back("Flux, if present");
help_text.push_back("A list of all metals present in the embark.");
help_text.push_back("A list of all economic minerals present in the embark. Both clays and flux");
help_text.push_back("stones are economic, so they show up here as well.");
help_text.push_back("In addition to the above, the Find functionality can also produce blinking");
help_text.push_back("overlays over the region map and the middle world map to indicate where");
help_text.push_back("matching embarks are found. The region display marks the top left corner of");
help_text.push_back("a matching embark rectangle as a matching tile.");
break;
case pages::Finder:
Screen::drawBorder("Embark Assistant Help/Info Find Page");
help_text.push_back("The Embark Assist Finder page is brought up with the f command key.");
help_text.push_back("The top of the Finder page lists the command keys available on the page:");
help_text.push_back("4/6 or horizontal arrow keys to move between the element and element values.");
help_text.push_back("8/2 or vertical arrow keys to move up/down in the current list.");
help_text.push_back("ENTER to select a value in the value list, entering it among the selections.");
help_text.push_back("f to activate the Find functionality using the values in the middle column.");
help_text.push_back("ESC to leave the screen without activating a Find operation.");
help_text.push_back("The X and Y dimensions are those of the embark to search for. Unlike DF");
help_text.push_back("itself these parameters are initiated to match the actual embark rectangle");
help_text.push_back("when a new search is initiated (prior results are cleared.");
help_text.push_back("The 6 Savagery and Evilness parameters takes some getting used to. They");
help_text.push_back("allow for searching for embarks with e.g. has both Good and Evil tiles.");
help_text.push_back("All as a parameter means every embark tile has to have this value.");
help_text.push_back("Present means at least one embark tile has to have this value.");
help_text.push_back("Absent means the feature mustn't exist in any of the embark tiles.");
help_text.push_back("N/A means no restrictions are applied.");
help_text.push_back("The Aquifer criterion introduces some new parameters:");
help_text.push_back("Partial means at least one tile has to have an aquifer, but it also has");
help_text.push_back("to be absent from at least one tile. Not All means an aquifer is tolerated");
help_text.push_back("as long as at least one tile doesn't have one, but it doesn't have to have");
help_text.push_back("any at all.");
help_text.push_back("Min/Max rivers should be self explanatory. The Yes and No values of");
help_text.push_back("Waterfall, Flat, etc. means one has to be Present and Absent respectivey.");
help_text.push_back("Min/Max soil uses the same terminology as DF for 1-4. The Min Soil");
help_text.push_back("Everywhere toggles the Min Soil parameter between acting as All and");
help_text.push_back("and Present.");
help_text.push_back("The parameters for biomes, regions, etc. all require that the required");
help_text.push_back("feature is Present in the embark, and entering the same value multiple");
help_text.push_back("times does nothing (the first match ticks all requirements off). It can be");
help_text.push_back("noted that all the Economic materials are found in the much longer Mineral");
help_text.push_back("list. Note that Find is a fairly time consuming task (is it is in vanilla).");
break;
case pages::Caveats:
Screen::drawBorder("Embark Assistant Help/Info Caveats Page");
help_text.push_back("Find searching first does a sanity check (e.g. max < min) and then a rough");
help_text.push_back("world tile match to find tiles that may have matching embarks. This results");
help_text.push_back("in an overlay of inverted yellow X on top of the middle world map. Then");
help_text.push_back("those tiles are scanned in detail, one feature shell (16*16 world tile");
help_text.push_back("block) at a time, and the results are displayed as green inverted X on");
help_text.push_back("the same map (replacing or erasing the yellow ones). region map overlay");
help_text.push_back("data is generated as well.");
help_text.push_back("");
help_text.push_back("Caveats & technical stuff:");
help_text.push_back("- The Find searching uses simulated cursor movement input to DF to get it");
help_text.push_back(" to load feature shells and detailed region data, and this costs the");
help_text.push_back(" least when done one feature shell at a time.");
help_text.push_back("- The search strategy causes detailed region data to update surveyed");
help_text.push_back(" world info, and this can cause a subsequent search to generate a smaller");
help_text.push_back(" set of preliminary matches (yellow tiles) than a previous search.");
help_text.push_back(" However, this is a bug only if it causes the search to fail to find");
help_text.push_back(" actual existing matches.");
help_text.push_back("- The site info is deduced by the author, so there may be errors and");
help_text.push_back(" there are probably site types that end up not being identified.");
help_text.push_back("- Aquifer indications are based on the author's belief that they occur");
help_text.push_back(" whenever an aquifer supporting layer is present at a depth of 3 or");
help_text.push_back(" more.");
help_text.push_back("- The biome determination logic comes from code provided by Ragundo,");
help_text.push_back(" with only marginal changes by the author. References can be found in");
help_text.push_back(" the source file.");
help_text.push_back("- Thralling is determined by weather material interactions causing");
help_text.push_back(" blinking, which the author believes is one of 4 thralling changes.");
help_text.push_back("- The geo information is gathered by code which is essentially a");
help_text.push_back(" copy of parts of prospector's code adapted for this plugin.");
help_text.push_back("- Clay determination is made by finding the reaction MAKE_CLAY_BRICKS.");
help_text.push_back(" Flux determination is made by finding the reaction PIG_IRON_MAKING.");
help_text.push_back("- Right world map overlay not implemented as author has failed to");
help_text.push_back(" emulate the sizing logic exactly.");
help_text.push_back("Version 0.1 2017-08-30");
break;
}
// Add control keys to first line.
embark_assist::screen::paintString(pen_lr, 1, 1, "TAB/Shift-TAB");
embark_assist::screen::paintString(pen, 14, 1, ":Next/Previous Page");
embark_assist::screen::paintString(pen_lr, 34, 1, "ESC");
embark_assist::screen::paintString(pen, 37, 1, ":Leave Info/Help");
for (uint16_t i = 0; i < help_text.size(); i++) {
embark_assist::screen::paintString(pen, 1, 2 + i, help_text[i]);
}
switch (current_page) {
case pages::Intro:
embark_assist::screen::paintString(pen_lr, 1, 26, "i");
embark_assist::screen::paintString(pen_lr, 1, 27, "f");
embark_assist::screen::paintString(pen_lr, 1, 28, "c");
embark_assist::screen::paintString(pen_lr, 1, 30, "q");
break;
case pages::General:
embark_assist::screen::paintString(site_pen, 1, 4, "C");
embark_assist::screen::paintString(site_pen, 1, 5, "c");
embark_assist::screen::paintString(site_pen, 1, 6, "i");
embark_assist::screen::paintString(site_pen, 1, 7, "l");
embark_assist::screen::paintString(site_pen, 1, 8, "L");
embark_assist::screen::paintString(site_pen, 1, 9, "M");
embark_assist::screen::paintString(site_pen, 1, 10, "S");
embark_assist::screen::paintString(site_pen, 1, 11, "V");
break;
case pages::Finder:
embark_assist::screen::paintString(pen_lr, 1, 4, "4/6");
embark_assist::screen::paintString(pen_lr, 1, 5, "8/2");
embark_assist::screen::paintString(pen_lr, 1, 6, "ENTER");
embark_assist::screen::paintString(pen_lr, 1, 7, "f");
embark_assist::screen::paintString(pen_lr, 1, 8, "ESC");
break;
case pages::Caveats:
break;
}
dfhack_viewscreen::render();
}
//===============================================================================
ViewscreenHelpUi::ViewscreenHelpUi() {
}
}
}
//===============================================================================
// Exported operations
//===============================================================================
void embark_assist::help_ui::init(DFHack::Plugin *plugin_self) {
Screen::show(new embark_assist::help_ui::ViewscreenHelpUi(), plugin_self);
}

@ -0,0 +1,15 @@
#pragma once
#include "PluginManager.h"
#include "DataDefs.h"
#include "defs.h"
using namespace DFHack;
namespace embark_assist {
namespace help_ui {
void init(DFHack::Plugin *plugin_self);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,21 @@
#pragma once
#include "DataDefs.h"
#include "defs.h"
using namespace DFHack;
namespace embark_assist {
namespace matcher {
void move_cursor(uint16_t x, uint16_t y);
// Used to iterate over the whole world to generate a map of world tiles
// that contain matching embarks.
//
uint16_t find(embark_assist::defs::match_iterators *iterator,
embark_assist::defs::geo_data *geo_summary,
embark_assist::defs::world_tile_data *survey_results,
embark_assist::defs::match_results *match_results);
}
}

@ -0,0 +1,439 @@
#include <modules/Gui.h>
#include "df/coord2d.h"
#include "df/inorganic_raw.h"
#include "df/dfhack_material_category.h"
#include "df/interface_key.h"
#include "df/viewscreen.h"
#include "df/viewscreen_choose_start_sitest.h"
#include "df/world.h"
#include "df/world_raws.h"
#include "finder_ui.h"
#include "help_ui.h"
#include "overlay.h"
#include "screen.h"
using df::global::world;
namespace embark_assist {
namespace overlay {
DFHack::Plugin *plugin_self;
const Screen::Pen empty_pen = Screen::Pen('\0', COLOR_YELLOW, COLOR_BLACK, false);
const Screen::Pen yellow_x_pen = Screen::Pen('X', COLOR_BLACK, COLOR_YELLOW, false);
const Screen::Pen green_x_pen = Screen::Pen('X', COLOR_BLACK, COLOR_GREEN, false);
struct display_strings {
Screen::Pen pen;
std::string text;
};
typedef Screen::Pen *pen_column;
struct states {
int blink_count = 0;
bool show = true;
bool matching = false;
bool match_active = false;
embark_update_callbacks embark_update;
match_callbacks match_callback;
clear_match_callbacks clear_match_callback;
embark_assist::defs::find_callbacks find_callback;
shutdown_callbacks shutdown_callback;
Screen::Pen site_grid[16][16];
uint8_t current_site_grid = 0;
std::vector<display_strings> embark_info;
Screen::Pen region_match_grid[16][16];
pen_column *world_match_grid = nullptr;
uint16_t match_count = 0;
uint16_t max_inorganic;
};
static states *state = nullptr;
//====================================================================
/* // Attempt to replicate the DF logic for sizing the right world map. This
// code seems to compute the values correctly, but the author hasn't been
// able to apply them at the same time as DF does to 100%.
// DF seems to round down on 0.5 values.
df::coord2d world_dimension_size(uint16_t available_screen, uint16_t map_size) {
uint16_t result;
for (uint16_t factor = 1; factor < 17; factor++) {
result = map_size / factor;
if ((map_size - result * factor) * 2 != factor) {
result = (map_size + factor / 2) / factor;
}
if (result <= available_screen) {
return {result, factor};
}
}
return{16, 16}; // Should never get here.
}
*/
//====================================================================
class ViewscreenOverlay : public df::viewscreen_choose_start_sitest
{
public:
typedef df::viewscreen_choose_start_sitest interpose_base;
void send_key(const df::interface_key &key)
{
std::set< df::interface_key > keys;
keys.insert(key);
this->feed(&keys);
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input))
{
// color_ostream_proxy out(Core::getInstance().getConsole());
if (input->count(df::interface_key::CUSTOM_Q)) {
state->shutdown_callback();
return;
}
else if (input->count(df::interface_key::SETUP_LOCAL_X_MUP) ||
input->count(df::interface_key::SETUP_LOCAL_X_MDOWN) ||
input->count(df::interface_key::SETUP_LOCAL_Y_MUP) ||
input->count(df::interface_key::SETUP_LOCAL_Y_MDOWN) ||
input->count(df::interface_key::SETUP_LOCAL_X_UP) ||
input->count(df::interface_key::SETUP_LOCAL_X_DOWN) ||
input->count(df::interface_key::SETUP_LOCAL_Y_UP) ||
input->count(df::interface_key::SETUP_LOCAL_Y_DOWN)) {
INTERPOSE_NEXT(feed)(input);
state->embark_update();
}
else if (input->count(df::interface_key::CUSTOM_C)) {
state->match_active = false;
state->matching = false;
state->clear_match_callback();
}
else if (input->count(df::interface_key::CUSTOM_F)) {
if (!state->match_active && !state->matching) {
embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic);
}
}
else if (input->count(df::interface_key::CUSTOM_I)) {
embark_assist::help_ui::init(embark_assist::overlay::plugin_self);
}
else {
INTERPOSE_NEXT(feed)(input);
}
}
//====================================================================
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
// color_ostream_proxy out(Core::getInstance().getConsole());
auto current_screen = Gui::getViewscreenByType<df::viewscreen_choose_start_sitest>(0);
int16_t x = current_screen->location.region_pos.x;
int16_t y = current_screen->location.region_pos.y;
auto width = Screen::getWindowSize().x;
auto height = Screen::getWindowSize().y;
state->blink_count++;
if (state->blink_count == 35) {
state->blink_count = 0;
state->show = !state->show;
}
if (state->matching) state->show = true;
Screen::drawBorder("Embark Assistant");
Screen::Pen pen_lr(' ', COLOR_LIGHTRED);
Screen::Pen pen_w(' ', COLOR_WHITE);
Screen::paintString(pen_lr, width - 28, 20, "i", false);
Screen::paintString(pen_w, width - 27, 20, ":Embark Assistant Info", false);
Screen::paintString(pen_lr, width - 28, 21, "f", false);
Screen::paintString(pen_w, width - 27, 21, ":Find Embark ", false);
Screen::paintString(pen_lr, width - 28, 22, "c", false);
Screen::paintString(pen_w, width - 27, 22, ":Cancel/Clear Find", false);
Screen::paintString(pen_lr, width - 28, 23, "q", false);
Screen::paintString(pen_w, width - 27, 23, ":Quit Embark Assistant", false);
Screen::paintString(pen_w, width - 28, 25, "Matching World Tiles", false);
Screen::paintString(empty_pen, width - 7, 25, to_string(state->match_count), false);
if (height > 25) { // Mask the vanilla DF find help as it's overridden.
Screen::paintString(pen_w, 50, height - 2, " ", false);
}
for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) {
if (state->site_grid[i][k].ch) {
Screen::paintTile(state->site_grid[i][k], i + 1, k + 2);
}
}
}
for (auto i = 0; i < state->embark_info.size(); i++) {
embark_assist::screen::paintString(state->embark_info[i].pen, 1, i + 19, state->embark_info[i].text, false);
}
if (state->show) {
int16_t left_x = x - (width / 2 - 7 - 18 + 1) / 2;
int16_t right_x;
int16_t top_y = y - (height - 8 - 2 + 1) / 2;
int16_t bottom_y;
if (left_x < 0) { left_x = 0; }
if (top_y < 0) { top_y = 0; }
right_x = left_x + width / 2 - 7 - 18;
bottom_y = top_y + height - 8 - 2;
if (right_x >= world->worldgen.worldgen_parms.dim_x) {
right_x = world->worldgen.worldgen_parms.dim_x - 1;
left_x = right_x - (width / 2 - 7 - 18);
}
if (bottom_y >= world->worldgen.worldgen_parms.dim_y) {
bottom_y = world->worldgen.worldgen_parms.dim_y - 1;
top_y = bottom_y - (height - 8 - 2);
}
if (left_x < 0) { left_x = 0; }
if (top_y < 0) { top_y = 0; }
for (uint16_t i = left_x; i <= right_x; i++) {
for (uint16_t k = top_y; k <= bottom_y; k++) {
if (state->world_match_grid[i][k].ch) {
Screen::paintTile(state->world_match_grid[i][k], i - left_x + 18, k - top_y + 2);
}
}
}
for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) {
if (state->region_match_grid[i][k].ch) {
Screen::paintTile(state->region_match_grid[i][k], i + 1, k + 2);
}
}
}
/* // Stuff for trying to replicate the DF right world map sizing logic. Close, but not there.
Screen::Pen pen(' ', COLOR_YELLOW);
// Boundaries of the top level world map
Screen::paintString(pen, width / 2 - 5, 2, "X", false); // Marks UL corner of right world map. Constant
// Screen::paintString(pen, width - 30, 2, "X", false); // Marks UR corner of right world map area.
// Screen::paintString(pen, width / 2 - 5, height - 8, "X", false); // BL corner of right world map area.
// Screen::paintString(pen, width - 30, height - 8, "X", false); // BR corner of right world map area.
uint16_t l_width = width - 30 - (width / 2 - 5) + 1; // Horizontal space available for right world map.
uint16_t l_height = height - 8 - 2 + 1; // Vertical space available for right world map.
df::coord2d size_factor_x = world_dimension_size(l_width, world->worldgen.worldgen_parms.dim_x);
df::coord2d size_factor_y = world_dimension_size(l_height, world->worldgen.worldgen_parms.dim_y);
Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2, "X", false);
Screen::paintString(pen, width / 2 - 5, 2 + size_factor_y.x - 1, "X", false);
Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2 + size_factor_y.x - 1, "X", false);
*/
}
if (state->matching) {
embark_assist::overlay::state->match_callback();
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(embark_assist::overlay::ViewscreenOverlay, feed);
IMPLEMENT_VMETHOD_INTERPOSE(embark_assist::overlay::ViewscreenOverlay, render);
}
}
//====================================================================
bool embark_assist::overlay::setup(DFHack::Plugin *plugin_self,
embark_update_callbacks embark_update_callback,
match_callbacks match_callback,
clear_match_callbacks clear_match_callback,
embark_assist::defs::find_callbacks find_callback,
shutdown_callbacks shutdown_callback,
uint16_t max_inorganic)
{
// color_ostream_proxy out(Core::getInstance().getConsole());
state = new(states);
embark_assist::overlay::plugin_self = plugin_self;
embark_assist::overlay::state->embark_update = embark_update_callback;
embark_assist::overlay::state->match_callback = match_callback;
embark_assist::overlay::state->clear_match_callback = clear_match_callback;
embark_assist::overlay::state->find_callback = find_callback;
embark_assist::overlay::state->shutdown_callback = shutdown_callback;
embark_assist::overlay::state->max_inorganic = max_inorganic;
embark_assist::overlay::state->match_active = false;
state->world_match_grid = new pen_column[world->worldgen.worldgen_parms.dim_x];
if (!state->world_match_grid) {
return false; // Out of memory
}
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
state->world_match_grid[i] = new Screen::Pen[world->worldgen.worldgen_parms.dim_y];
if (!state->world_match_grid[i]) { // Out of memory.
return false;
}
}
clear_match_results();
return INTERPOSE_HOOK(embark_assist::overlay::ViewscreenOverlay, feed).apply(true) &&
INTERPOSE_HOOK(embark_assist::overlay::ViewscreenOverlay, render).apply(true);
}
//====================================================================
void embark_assist::overlay::set_sites(embark_assist::defs::site_lists *site_list) {
for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) {
state->site_grid[i][k] = empty_pen;
}
}
for (uint16_t i = 0; i < site_list->size(); i++) {
state->site_grid[site_list->at(i).x][site_list->at(i).y].ch = site_list->at(i).type;
}
}
//====================================================================
void embark_assist::overlay::initiate_match() {
embark_assist::overlay::state->matching = true;
}
//====================================================================
void embark_assist::overlay::match_progress(uint16_t count, embark_assist::defs::match_results *match_results, bool done) {
// color_ostream_proxy out(Core::getInstance().getConsole());
state->matching = !done;
state->match_count = count;
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) {
if (match_results->at(i).at(k).preliminary_match) {
state->world_match_grid[i][k] = yellow_x_pen;
} else if (match_results->at(i).at(k).contains_match) {
state->world_match_grid[i][k] = green_x_pen;
}
else {
state->world_match_grid[i][k] = empty_pen;
}
}
}
}
//====================================================================
void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_info) {
state->embark_info.clear();
if (site_info->sand) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_YELLOW), "Sand" });
}
if (site_info->clay) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_RED), "Clay" });
}
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Soil " + std::to_string(site_info->min_soil) + " - " + std::to_string(site_info->max_soil) });
if (site_info->flat) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Flat" });
}
if (site_info->aquifer) {
if (site_info->aquifer_full) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Aquifer" });
}
else {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Aquifer" });
}
}
if (site_info->waterfall) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Waterfall" });
}
if (site_info->flux) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_WHITE), "Flux" });
}
for (auto const& i : site_info->metals) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_GREY), world->raws.inorganics[i]->id });
}
for (auto const& i : site_info->economics) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_WHITE), world->raws.inorganics[i]->id });
}
}
//====================================================================
void embark_assist::overlay::set_mid_level_tile_match(embark_assist::defs::mlt_matches mlt_matches) {
for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) {
if (mlt_matches[i][k]) {
state->region_match_grid[i][k] = green_x_pen;
}
else {
state->region_match_grid[i][k] = empty_pen;
}
}
}
}
//====================================================================
void embark_assist::overlay::clear_match_results() {
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) {
state->world_match_grid[i][k] = empty_pen;
}
}
for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) {
state->region_match_grid[i][k] = empty_pen;
}
}
}
//====================================================================
void embark_assist::overlay::shutdown() {
if (state &&
state->world_match_grid) {
INTERPOSE_HOOK(ViewscreenOverlay, render).remove();
INTERPOSE_HOOK(ViewscreenOverlay, feed).remove();
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
delete[] state->world_match_grid[i];
}
delete[] state->world_match_grid;
}
if (state) {
state->embark_info.clear();
delete state;
state = nullptr;
}
}

@ -0,0 +1,37 @@
#pragma once
#include <VTableInterpose.h>
#include "PluginManager.h"
#include "DataDefs.h"
#include "df/viewscreen_choose_start_sitest.h"
#include "defs.h"
using df::global::enabler;
using df::global::gps;
namespace embark_assist {
namespace overlay {
typedef void(*embark_update_callbacks)();
typedef void(*match_callbacks)();
typedef void(*clear_match_callbacks)();
typedef void(*shutdown_callbacks)();
bool setup(DFHack::Plugin *plugin_self,
embark_update_callbacks embark_update_callback,
match_callbacks match_callback,
clear_match_callbacks clear_match_callback,
embark_assist::defs::find_callbacks find_callback,
shutdown_callbacks shutdown_callback,
uint16_t max_inorganic);
void set_sites(embark_assist::defs::site_lists *site_list);
void initiate_match();
void match_progress(uint16_t count, embark_assist::defs::match_results *match_results, bool done);
void set_embark(embark_assist::defs::site_infos *site_info);
void set_mid_level_tile_match(embark_assist::defs::mlt_matches mlt_matches);
void clear_match_results();
void shutdown();
}
}

@ -0,0 +1,27 @@
#include "screen.h"
namespace embark_assist {
namespace screen {
}
}
bool embark_assist::screen::paintString(const DFHack::Screen::Pen &pen, int x, int y, const std::string &text, bool map) {
auto screen_size = DFHack::Screen::getWindowSize();
if (y < 1 || y + 1 >= screen_size.y || x < 1)
{
return false; // Won't paint outside of the screen or on the frame
}
if (x + text.length() - 1 < screen_size.x - 2) {
DFHack::Screen::paintString(pen, x, y, text, map);
}
else if (x < screen_size.x - 2) {
DFHack::Screen::paintString(pen, x, y, text.substr(0, screen_size.x - 2 - x + 1), map);
}
else {
return false;
}
return true;
}

@ -0,0 +1,7 @@
#include "modules/Screen.h"
namespace embark_assist {
namespace screen {
bool paintString(const DFHack::Screen::Pen &pen, int x, int y, const std::string &text, bool map = false);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,38 @@
#pragma once
#include <map>
#include "DataDefs.h"
#include "df/coord2d.h"
#include "defs.h"
using namespace DFHack;
namespace embark_assist {
namespace survey {
void setup(uint16_t max_inorganic);
df::coord2d get_last_pos();
void initiate(embark_assist::defs::mid_level_tiles *mlt);
void clear_results(embark_assist::defs::match_results *match_results);
void high_level_world_survey(embark_assist::defs::geo_data *geo_summary,
embark_assist::defs::world_tile_data *survey_results);
void survey_mid_level_tile(embark_assist::defs::geo_data *geo_summary,
embark_assist::defs::world_tile_data *survey_results,
embark_assist::defs::mid_level_tiles *mlt);
df::coord2d apply_offset(uint16_t x, uint16_t y, int8_t offset);
void survey_region_sites(embark_assist::defs::site_lists *site_list);
void survey_embark(embark_assist::defs::mid_level_tiles *mlt,
embark_assist::defs::site_infos *site_info,
bool use_cache);
void shutdown();
}
}