2018-08-16 08:16:52 -06:00
|
|
|
#include "Core.h"
|
|
|
|
#include "Console.h"
|
2023-01-06 19:04:54 -07:00
|
|
|
#include "Debug.h"
|
2018-08-16 08:16:52 -06:00
|
|
|
#include "Export.h"
|
|
|
|
#include "PluginManager.h"
|
|
|
|
|
|
|
|
#include "DataDefs.h"
|
|
|
|
#include "df/world.h"
|
2023-01-05 18:11:01 -07:00
|
|
|
#include "df/plotinfost.h"
|
2018-08-16 08:16:52 -06:00
|
|
|
#include "df/building_type.h"
|
|
|
|
#include "df/building_farmplotst.h"
|
|
|
|
#include "df/buildings_other_id.h"
|
|
|
|
#include "df/global_objects.h"
|
|
|
|
#include "df/item.h"
|
|
|
|
#include "df/item_plantst.h"
|
2021-08-07 14:32:43 -06:00
|
|
|
#include "df/item_plant_growthst.h"
|
|
|
|
#include "df/item_seedsst.h"
|
2018-08-16 08:16:52 -06:00
|
|
|
#include "df/items_other_id.h"
|
|
|
|
#include "df/unit.h"
|
|
|
|
#include "df/building.h"
|
|
|
|
#include "df/plant_raw.h"
|
|
|
|
#include "df/plant_raw_flags.h"
|
|
|
|
#include "df/biome_type.h"
|
|
|
|
#include "modules/Items.h"
|
|
|
|
#include "modules/Maps.h"
|
|
|
|
#include "modules/World.h"
|
|
|
|
|
2018-08-27 21:29:14 -06:00
|
|
|
#include <queue>
|
|
|
|
|
2018-08-16 08:16:52 -06:00
|
|
|
using namespace DFHack;
|
|
|
|
using namespace df::enums;
|
|
|
|
|
|
|
|
using df::global::world;
|
2023-01-05 18:11:01 -07:00
|
|
|
using df::global::plotinfo;
|
2018-08-16 08:16:52 -06:00
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
static command_result autofarm(color_ostream& out, std::vector<std::string>& parameters);
|
2018-08-16 08:16:52 -06:00
|
|
|
|
|
|
|
DFHACK_PLUGIN("autofarm");
|
|
|
|
|
|
|
|
DFHACK_PLUGIN_IS_ENABLED(enabled);
|
|
|
|
|
2023-01-06 19:04:54 -07:00
|
|
|
#define CONFIG_KEY "autofarm/config"
|
|
|
|
|
|
|
|
namespace DFHack {
|
|
|
|
DBG_DECLARE(autofarm, cycle, DebugCategory::LINFO);
|
|
|
|
DBG_DECLARE(autofarm, config, DebugCategory::LINFO);
|
|
|
|
}
|
|
|
|
|
2018-08-16 08:16:52 -06:00
|
|
|
class AutoFarm {
|
|
|
|
private:
|
2022-03-16 20:43:24 -06:00
|
|
|
std::map<int, int> thresholds;
|
2018-08-16 08:16:52 -06:00
|
|
|
int defaultThreshold = 50;
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
std::map<int, int> lastCounts;
|
2018-08-16 08:16:52 -06:00
|
|
|
|
|
|
|
public:
|
|
|
|
void initialize()
|
|
|
|
{
|
|
|
|
thresholds.clear();
|
|
|
|
defaultThreshold = 50;
|
|
|
|
|
|
|
|
lastCounts.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void setThreshold(int id, int val)
|
|
|
|
{
|
|
|
|
thresholds[id] = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getThreshold(int id)
|
|
|
|
{
|
|
|
|
return (thresholds.count(id) > 0) ? thresholds[id] : defaultThreshold;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setDefault(int val)
|
|
|
|
{
|
|
|
|
defaultThreshold = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
const df::plant_raw_flags seasons[4] = { df::plant_raw_flags::SPRING, df::plant_raw_flags::SUMMER, df::plant_raw_flags::AUTUMN, df::plant_raw_flags::WINTER };
|
|
|
|
|
|
|
|
public:
|
|
|
|
bool is_plantable(df::plant_raw* plant)
|
|
|
|
{
|
|
|
|
bool has_seed = plant->flags.is_set(df::plant_raw_flags::SEED);
|
|
|
|
bool is_tree = plant->flags.is_set(df::plant_raw_flags::TREE);
|
|
|
|
|
|
|
|
int8_t season = *df::global::cur_season;
|
|
|
|
int harvest = (*df::global::cur_season_tick) + plant->growdur * 10;
|
|
|
|
bool can_plant = has_seed && !is_tree && plant->flags.is_set(seasons[season]);
|
|
|
|
while (can_plant && harvest >= 10080) {
|
|
|
|
season = (season + 1) % 4;
|
|
|
|
harvest -= 10080;
|
|
|
|
can_plant = can_plant && plant->flags.is_set(seasons[season]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return can_plant;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2022-03-16 20:43:24 -06:00
|
|
|
std::map<int, std::set<df::biome_type>> plantable_plants;
|
2018-08-16 08:16:52 -06:00
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
const std::map<df::plant_raw_flags, df::biome_type> biomeFlagMap = {
|
2018-08-16 08:16:52 -06:00
|
|
|
{ df::plant_raw_flags::BIOME_MOUNTAIN, df::biome_type::MOUNTAIN },
|
|
|
|
{ df::plant_raw_flags::BIOME_GLACIER, df::biome_type::GLACIER },
|
|
|
|
{ df::plant_raw_flags::BIOME_TUNDRA, df::biome_type::TUNDRA },
|
|
|
|
{ df::plant_raw_flags::BIOME_SWAMP_TEMPERATE_FRESHWATER, df::biome_type::SWAMP_TEMPERATE_FRESHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_SWAMP_TEMPERATE_SALTWATER, df::biome_type::SWAMP_TEMPERATE_SALTWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_MARSH_TEMPERATE_FRESHWATER, df::biome_type::MARSH_TEMPERATE_FRESHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_MARSH_TEMPERATE_SALTWATER, df::biome_type::MARSH_TEMPERATE_SALTWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_SWAMP_TROPICAL_FRESHWATER, df::biome_type::SWAMP_TROPICAL_FRESHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_SWAMP_TROPICAL_SALTWATER, df::biome_type::SWAMP_TROPICAL_SALTWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_SWAMP_MANGROVE, df::biome_type::SWAMP_MANGROVE },
|
|
|
|
{ df::plant_raw_flags::BIOME_MARSH_TROPICAL_FRESHWATER, df::biome_type::MARSH_TROPICAL_FRESHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_MARSH_TROPICAL_SALTWATER, df::biome_type::MARSH_TROPICAL_SALTWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_FOREST_TAIGA, df::biome_type::FOREST_TAIGA },
|
|
|
|
{ df::plant_raw_flags::BIOME_FOREST_TEMPERATE_CONIFER, df::biome_type::FOREST_TEMPERATE_CONIFER },
|
|
|
|
{ df::plant_raw_flags::BIOME_FOREST_TEMPERATE_BROADLEAF, df::biome_type::FOREST_TEMPERATE_BROADLEAF },
|
|
|
|
{ df::plant_raw_flags::BIOME_FOREST_TROPICAL_CONIFER, df::biome_type::FOREST_TROPICAL_CONIFER },
|
|
|
|
{ df::plant_raw_flags::BIOME_FOREST_TROPICAL_DRY_BROADLEAF, df::biome_type::FOREST_TROPICAL_DRY_BROADLEAF },
|
|
|
|
{ df::plant_raw_flags::BIOME_FOREST_TROPICAL_MOIST_BROADLEAF, df::biome_type::FOREST_TROPICAL_MOIST_BROADLEAF },
|
|
|
|
{ df::plant_raw_flags::BIOME_GRASSLAND_TEMPERATE, df::biome_type::GRASSLAND_TEMPERATE },
|
|
|
|
{ df::plant_raw_flags::BIOME_SAVANNA_TEMPERATE, df::biome_type::SAVANNA_TEMPERATE },
|
|
|
|
{ df::plant_raw_flags::BIOME_SHRUBLAND_TEMPERATE, df::biome_type::SHRUBLAND_TEMPERATE },
|
|
|
|
{ df::plant_raw_flags::BIOME_GRASSLAND_TROPICAL, df::biome_type::GRASSLAND_TROPICAL },
|
|
|
|
{ df::plant_raw_flags::BIOME_SAVANNA_TROPICAL, df::biome_type::SAVANNA_TROPICAL },
|
|
|
|
{ df::plant_raw_flags::BIOME_SHRUBLAND_TROPICAL, df::biome_type::SHRUBLAND_TROPICAL },
|
|
|
|
{ df::plant_raw_flags::BIOME_DESERT_BADLAND, df::biome_type::DESERT_BADLAND },
|
|
|
|
{ df::plant_raw_flags::BIOME_DESERT_ROCK, df::biome_type::DESERT_ROCK },
|
|
|
|
{ df::plant_raw_flags::BIOME_DESERT_SAND, df::biome_type::DESERT_SAND },
|
|
|
|
{ df::plant_raw_flags::BIOME_OCEAN_TROPICAL, df::biome_type::OCEAN_TROPICAL },
|
|
|
|
{ df::plant_raw_flags::BIOME_OCEAN_TEMPERATE, df::biome_type::OCEAN_TEMPERATE },
|
|
|
|
{ df::plant_raw_flags::BIOME_OCEAN_ARCTIC, df::biome_type::OCEAN_ARCTIC },
|
|
|
|
{ df::plant_raw_flags::BIOME_POOL_TEMPERATE_FRESHWATER, df::biome_type::POOL_TEMPERATE_FRESHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_POOL_TEMPERATE_BRACKISHWATER, df::biome_type::POOL_TEMPERATE_BRACKISHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_POOL_TEMPERATE_SALTWATER, df::biome_type::POOL_TEMPERATE_SALTWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_POOL_TROPICAL_FRESHWATER, df::biome_type::POOL_TROPICAL_FRESHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_POOL_TROPICAL_BRACKISHWATER, df::biome_type::POOL_TROPICAL_BRACKISHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_POOL_TROPICAL_SALTWATER, df::biome_type::POOL_TROPICAL_SALTWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_LAKE_TEMPERATE_FRESHWATER, df::biome_type::LAKE_TEMPERATE_FRESHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_LAKE_TEMPERATE_BRACKISHWATER, df::biome_type::LAKE_TEMPERATE_BRACKISHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_LAKE_TEMPERATE_SALTWATER, df::biome_type::LAKE_TEMPERATE_SALTWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_LAKE_TROPICAL_FRESHWATER, df::biome_type::LAKE_TROPICAL_FRESHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_LAKE_TROPICAL_BRACKISHWATER, df::biome_type::LAKE_TROPICAL_BRACKISHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_LAKE_TROPICAL_SALTWATER, df::biome_type::LAKE_TROPICAL_SALTWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_RIVER_TEMPERATE_FRESHWATER, df::biome_type::RIVER_TEMPERATE_FRESHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_RIVER_TEMPERATE_BRACKISHWATER, df::biome_type::RIVER_TEMPERATE_BRACKISHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_RIVER_TEMPERATE_SALTWATER, df::biome_type::RIVER_TEMPERATE_SALTWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_RIVER_TROPICAL_FRESHWATER, df::biome_type::RIVER_TROPICAL_FRESHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_RIVER_TROPICAL_BRACKISHWATER, df::biome_type::RIVER_TROPICAL_BRACKISHWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_RIVER_TROPICAL_SALTWATER, df::biome_type::RIVER_TROPICAL_SALTWATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_SUBTERRANEAN_WATER, df::biome_type::SUBTERRANEAN_WATER },
|
|
|
|
{ df::plant_raw_flags::BIOME_SUBTERRANEAN_CHASM, df::biome_type::SUBTERRANEAN_CHASM },
|
|
|
|
{ df::plant_raw_flags::BIOME_SUBTERRANEAN_LAVA, df::biome_type::SUBTERRANEAN_LAVA }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
void find_plantable_plants()
|
|
|
|
{
|
|
|
|
plantable_plants.clear();
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
std::map<int, int> counts;
|
2018-08-16 08:16:52 -06:00
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
const uint32_t bad_flags{
|
|
|
|
#define F(x) (df::item_flags::Mask::mask_##x)
|
|
|
|
F(dump) | F(forbid) | F(garbage_collect) |
|
|
|
|
F(hostile) | F(on_fire) | F(rotten) | F(trader) |
|
|
|
|
F(in_building) | F(construction) | F(artifact)
|
2018-08-16 08:16:52 -06:00
|
|
|
#undef F
|
2022-03-16 20:43:24 -06:00
|
|
|
};
|
2018-08-16 08:16:52 -06:00
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
for (auto& ii : world->items.other[df::items_other_id::SEEDS])
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
2021-08-07 14:32:43 -06:00
|
|
|
auto i = virtual_cast<df::item_seedsst>(ii);
|
2022-03-16 20:43:24 -06:00
|
|
|
if (i && (i->flags.whole & bad_flags) == 0)
|
2018-08-16 08:16:52 -06:00
|
|
|
counts[i->mat_index] += i->stack_size;
|
|
|
|
}
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
for (auto& ci : counts)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
2022-03-15 08:29:33 -06:00
|
|
|
df::plant_raw* plant = world->raws.plants.all[ci.first];
|
|
|
|
if (is_plantable(plant))
|
2022-03-16 20:43:24 -06:00
|
|
|
for (auto& flagmap : biomeFlagMap)
|
2022-03-15 08:29:33 -06:00
|
|
|
if (plant->flags.is_set(flagmap.first))
|
|
|
|
plantable_plants[plant->index].insert(flagmap.second);
|
2018-08-16 08:16:52 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
std::string get_plant_name(int plant_id)
|
2021-01-13 00:55:48 -07:00
|
|
|
{
|
2022-03-16 20:43:24 -06:00
|
|
|
df::plant_raw* raw = df::plant_raw::find(plant_id);
|
|
|
|
return raw ? raw->name : "NONE";
|
2021-01-13 00:55:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void set_farm(color_ostream& out, int new_plant_id, df::building_farmplotst* farm, int season)
|
|
|
|
{
|
|
|
|
int old_plant_id = farm->plant_id[season];
|
|
|
|
if (old_plant_id != new_plant_id)
|
|
|
|
{
|
|
|
|
farm->plant_id[season] = new_plant_id;
|
2023-01-09 19:04:13 -07:00
|
|
|
INFO(cycle, out).print("autofarm: changing farm #%d from %s to %s\n", farm->id,
|
|
|
|
get_plant_name(old_plant_id).c_str(),
|
|
|
|
get_plant_name(new_plant_id).c_str());
|
2021-01-13 00:55:48 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
void set_farms(color_ostream& out, const std::set<int>& plants, const std::vector<df::building_farmplotst*>& farms)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
2019-11-21 17:58:06 -07:00
|
|
|
// this algorithm attempts to change as few farms as possible, while ensuring that
|
|
|
|
// the number of farms planting each eligible plant is "as equal as possible"
|
2021-08-07 14:32:43 -06:00
|
|
|
|
2018-08-16 08:16:52 -06:00
|
|
|
int season = *df::global::cur_season;
|
2021-08-07 14:32:43 -06:00
|
|
|
|
2021-01-12 13:37:51 -07:00
|
|
|
if (farms.empty() || plants.empty())
|
|
|
|
{
|
|
|
|
// if no more plants were requested, fallow all farms
|
|
|
|
// if there were no farms, do nothing
|
|
|
|
for (auto farm : farms)
|
|
|
|
{
|
2021-01-13 00:55:48 -07:00
|
|
|
set_farm(out, -1, farm, season);
|
2021-01-12 13:37:51 -07:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2018-08-16 08:16:52 -06:00
|
|
|
|
2018-08-27 21:29:14 -06:00
|
|
|
int min = farms.size() / plants.size(); // the number of farms that should plant each eligible plant, rounded down
|
|
|
|
int extra = farms.size() - min * plants.size(); // the remainder that cannot be evenly divided
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
std::map<int, int> counters;
|
|
|
|
std::queue<df::building_farmplotst*> toChange;
|
2018-08-27 21:29:14 -06:00
|
|
|
|
|
|
|
for (auto farm : farms)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
|
|
|
int o = farm->plant_id[season];
|
2022-03-16 20:43:24 -06:00
|
|
|
if (plants.count(o) == 0 || counters[o] > min || (counters[o] == min && extra == 0))
|
2018-08-27 21:29:14 -06:00
|
|
|
toChange.push(farm); // this farm is an excess instance for the plant it is currently planting
|
|
|
|
else
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
2019-11-21 17:58:06 -07:00
|
|
|
if (counters[o] == min)
|
2018-08-27 21:29:14 -06:00
|
|
|
extra--; // allocate off one of the remainder farms
|
|
|
|
counters[o]++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto n : plants)
|
|
|
|
{
|
|
|
|
int c = counters[n];
|
|
|
|
while (toChange.size() > 0 && (c < min || (c == min && extra > 0)))
|
|
|
|
{
|
|
|
|
// pick one of the excess farms and change it to plant this plant
|
|
|
|
df::building_farmplotst* farm = toChange.front();
|
2021-01-13 00:55:48 -07:00
|
|
|
set_farm(out, n, farm, season);
|
2018-08-27 21:29:14 -06:00
|
|
|
toChange.pop();
|
|
|
|
if (c++ == min)
|
|
|
|
extra--;
|
2018-08-16 08:16:52 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void process(color_ostream& out)
|
|
|
|
{
|
|
|
|
if (!enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
find_plantable_plants();
|
|
|
|
|
|
|
|
lastCounts.clear();
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
const uint32_t bad_flags{
|
|
|
|
#define F(x) (df::item_flags::Mask::mask_##x)
|
|
|
|
F(dump) | F(forbid) | F(garbage_collect) |
|
|
|
|
F(hostile) | F(on_fire) | F(rotten) | F(trader) |
|
|
|
|
F(in_building) | F(construction) | F(artifact)
|
|
|
|
#undef F
|
|
|
|
};
|
2018-08-16 08:16:52 -06:00
|
|
|
|
2021-08-07 14:32:43 -06:00
|
|
|
// have to scan both items[PLANT] and items[PLANT_GROWTH] because agricultural products can be either
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
auto count = [&, this](df::item* i) {
|
|
|
|
auto mat = i->getMaterialIndex();
|
|
|
|
if ((i->flags.whole & bad_flags) == 0 &&
|
|
|
|
plantable_plants.count(mat) > 0)
|
2021-08-07 14:32:43 -06:00
|
|
|
{
|
2022-03-16 20:43:24 -06:00
|
|
|
lastCounts[mat] += i->getStackSize();
|
2021-08-07 14:32:43 -06:00
|
|
|
}
|
2022-03-16 20:43:24 -06:00
|
|
|
};
|
2018-08-16 08:16:52 -06:00
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
for (auto i : world->items.other[df::items_other_id::PLANT])
|
|
|
|
count(i);
|
|
|
|
for (auto i : world->items.other[df::items_other_id::PLANT_GROWTH])
|
|
|
|
count(i);
|
2021-08-07 14:32:43 -06:00
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
std::map<df::biome_type, std::set<int>> plants;
|
2018-08-16 08:16:52 -06:00
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
for (auto& plantable : plantable_plants)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
|
|
|
df::plant_raw* plant = world->raws.plants.all[plantable.first];
|
|
|
|
if (lastCounts[plant->index] < getThreshold(plant->index))
|
|
|
|
for (auto biome : plantable.second)
|
|
|
|
{
|
2018-08-27 21:29:14 -06:00
|
|
|
plants[biome].insert(plant->index);
|
2018-08-16 08:16:52 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
std::map<df::biome_type, std::vector<df::building_farmplotst*>> farms;
|
2018-08-16 08:16:52 -06:00
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
for (auto& bb : world->buildings.other[df::buildings_other_id::FARM_PLOT])
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
2021-08-07 14:32:43 -06:00
|
|
|
auto farm = virtual_cast<df::building_farmplotst>(bb);
|
2018-08-16 08:16:52 -06:00
|
|
|
if (farm->flags.bits.exists)
|
|
|
|
{
|
|
|
|
df::biome_type biome;
|
|
|
|
if (Maps::getTileDesignation(bb->centerx, bb->centery, bb->z)->bits.subterranean)
|
|
|
|
biome = biome_type::SUBTERRANEAN_WATER;
|
|
|
|
else {
|
|
|
|
df::coord2d region(Maps::getTileBiomeRgn(df::coord(bb->centerx, bb->centery, bb->z)));
|
2023-01-21 13:31:39 -07:00
|
|
|
biome = Maps::getBiomeType(region.x, region.y);
|
2018-08-16 08:16:52 -06:00
|
|
|
}
|
|
|
|
farms[biome].push_back(farm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
for (auto& ff : farms)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
|
|
|
set_farms(out, plants[ff.first], ff.second);
|
|
|
|
}
|
2022-11-15 19:00:27 -07:00
|
|
|
|
|
|
|
out << std::flush;
|
2018-08-16 08:16:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
void status(color_ostream& out)
|
|
|
|
{
|
2022-07-18 14:49:51 -06:00
|
|
|
out << "Autofarm is " << (enabled ? "Active." : "Stopped.") << '\n';
|
2023-01-18 15:24:01 -07:00
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
for (auto& lc : lastCounts)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
|
|
|
auto plant = world->raws.plants.all[lc.first];
|
2022-03-16 20:43:24 -06:00
|
|
|
out << plant->id << " limit " << getThreshold(lc.first) << " current " << lc.second << '\n';
|
2018-08-16 08:16:52 -06:00
|
|
|
}
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
for (auto& th : thresholds)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
2023-01-18 15:54:30 -07:00
|
|
|
if (lastCounts.count(th.first) > 0)
|
2018-08-16 08:16:52 -06:00
|
|
|
continue;
|
|
|
|
auto plant = world->raws.plants.all[th.first];
|
2022-03-16 20:43:24 -06:00
|
|
|
out << plant->id << " limit " << getThreshold(th.first) << " current 0" << '\n';
|
2018-08-16 08:16:52 -06:00
|
|
|
}
|
2022-03-16 20:43:24 -06:00
|
|
|
out << "Default: " << defaultThreshold << '\n';
|
2022-11-15 19:00:27 -07:00
|
|
|
|
|
|
|
out << std::flush;
|
2018-08-16 08:16:52 -06:00
|
|
|
}
|
2023-01-06 19:04:54 -07:00
|
|
|
|
2023-01-06 22:45:16 -07:00
|
|
|
private:
|
|
|
|
|
|
|
|
PersistentDataItem cfg_default_threshold;
|
|
|
|
PersistentDataItem cfg_enabled;
|
|
|
|
|
|
|
|
public:
|
2023-01-06 19:04:54 -07:00
|
|
|
void load_state(color_ostream& out)
|
|
|
|
{
|
|
|
|
initialize();
|
2023-01-09 19:04:13 -07:00
|
|
|
if (!(Core::getInstance().isWorldLoaded()))
|
|
|
|
return;
|
2023-01-06 19:04:54 -07:00
|
|
|
|
2023-01-06 22:45:16 -07:00
|
|
|
cfg_enabled = World::GetPersistentData("autofarm/enabled");
|
|
|
|
if (cfg_enabled.isValid())
|
|
|
|
enabled = cfg_enabled.ival(0) != 0;
|
2023-01-09 19:04:13 -07:00
|
|
|
else {
|
|
|
|
cfg_enabled = World::AddPersistentData("autofarm/enabled");
|
2023-01-06 22:45:16 -07:00
|
|
|
cfg_enabled.ival(0) = enabled;
|
2023-01-09 19:04:13 -07:00
|
|
|
}
|
2023-01-06 22:45:16 -07:00
|
|
|
|
|
|
|
cfg_default_threshold = World::GetPersistentData("autofarm/default_threshold");
|
|
|
|
|
2023-01-06 19:04:54 -07:00
|
|
|
if (cfg_default_threshold.isValid())
|
|
|
|
defaultThreshold = cfg_default_threshold.ival(0);
|
2023-01-09 19:04:13 -07:00
|
|
|
else {
|
|
|
|
cfg_default_threshold = World::AddPersistentData("autofarm/default_threshold");
|
2023-01-06 22:45:16 -07:00
|
|
|
cfg_default_threshold.ival(0) = defaultThreshold;
|
2023-01-09 19:04:13 -07:00
|
|
|
}
|
2023-01-06 19:04:54 -07:00
|
|
|
|
2023-01-06 22:45:16 -07:00
|
|
|
std::vector<PersistentDataItem> items;
|
|
|
|
World::GetPersistentData(&items, "autofarm/threshold/", true);
|
|
|
|
for (auto& i: items) {
|
|
|
|
if (i.isValid())
|
2023-01-06 19:04:54 -07:00
|
|
|
{
|
2023-01-06 22:45:16 -07:00
|
|
|
const auto allPlants = world->raws.plants.all;
|
|
|
|
const std::string id = i.val();
|
|
|
|
const int val = i.ival(0);
|
|
|
|
const auto plant = std::find_if(std::begin(allPlants), std::end(allPlants), [id](df::plant_raw* p) { return p->id == id; });
|
|
|
|
if (plant != std::end(allPlants))
|
|
|
|
{
|
|
|
|
setThreshold((*plant)->index, val);
|
2023-01-09 19:04:13 -07:00
|
|
|
INFO(config, out).print("threshold of %d for plant %s in saved configuration loaded\n", val, id.c_str());
|
2023-01-06 22:45:16 -07:00
|
|
|
}
|
|
|
|
else
|
2023-01-06 19:04:54 -07:00
|
|
|
{
|
2023-01-09 19:04:13 -07:00
|
|
|
WARN(config, out).print("threshold for unknown plant %s in saved configuration ignored\n", id.c_str());
|
2023-01-06 19:04:54 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void save_state(color_ostream& out)
|
|
|
|
{
|
2023-01-06 22:45:16 -07:00
|
|
|
cfg_default_threshold.ival(0) = defaultThreshold;
|
|
|
|
cfg_enabled.ival(0) = enabled;
|
|
|
|
|
|
|
|
std::vector<PersistentDataItem> items;
|
|
|
|
World::GetPersistentData(&items, "autofarm/threshold/", true);
|
|
|
|
for (auto& i : items)
|
|
|
|
World::DeletePersistentData(i);
|
|
|
|
|
2023-01-06 19:04:54 -07:00
|
|
|
for (const auto& t : thresholds)
|
|
|
|
{
|
2023-01-06 22:45:16 -07:00
|
|
|
const std::string& plantID = world->raws.plants.all[t.first]->id;
|
|
|
|
const std::string keyName = "autofarm/threshold/" + plantID;
|
2023-01-06 19:04:54 -07:00
|
|
|
PersistentDataItem cfgThreshold = World::AddPersistentData(keyName);
|
2023-01-06 22:45:16 -07:00
|
|
|
cfgThreshold.val() = plantID;
|
2023-01-06 19:04:54 -07:00
|
|
|
cfgThreshold.ival(0) = t.second;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-16 08:16:52 -06:00
|
|
|
};
|
|
|
|
|
2021-08-07 14:32:43 -06:00
|
|
|
static std::unique_ptr<AutoFarm> autofarmInstance;
|
2018-08-16 08:16:52 -06:00
|
|
|
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
DFhackCExport command_result plugin_init(color_ostream& out, std::vector <PluginCommand>& commands)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
2023-01-05 18:11:01 -07:00
|
|
|
if (world && plotinfo) {
|
2018-08-16 08:16:52 -06:00
|
|
|
commands.push_back(
|
2022-07-18 14:49:51 -06:00
|
|
|
PluginCommand("autofarm",
|
|
|
|
"Automatically manage farm crop selection.",
|
|
|
|
autofarm));
|
2018-08-16 08:16:52 -06:00
|
|
|
}
|
2021-08-07 14:51:21 -06:00
|
|
|
autofarmInstance = std::move(dts::make_unique<AutoFarm>());
|
2023-01-06 22:45:16 -07:00
|
|
|
autofarmInstance->load_state(out);
|
2018-08-16 08:16:52 -06:00
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
DFhackCExport command_result plugin_shutdown(color_ostream& out)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
2021-08-07 14:32:43 -06:00
|
|
|
autofarmInstance.release();
|
2018-08-16 08:16:52 -06:00
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
DFhackCExport command_result plugin_onupdate(color_ostream& out)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
|
|
|
if (!autofarmInstance)
|
|
|
|
return CR_OK;
|
|
|
|
|
|
|
|
if (!Maps::IsValid())
|
|
|
|
return CR_OK;
|
|
|
|
|
|
|
|
if (DFHack::World::ReadPauseState())
|
|
|
|
return CR_OK;
|
|
|
|
|
|
|
|
if (world->frame_counter % 50 != 0) // Check every hour
|
|
|
|
return CR_OK;
|
|
|
|
|
2018-08-18 13:57:26 -06:00
|
|
|
{
|
|
|
|
CoreSuspender suspend;
|
|
|
|
autofarmInstance->process(out);
|
|
|
|
}
|
2018-08-16 08:16:52 -06:00
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
|
|
|
enabled = enable;
|
2023-01-06 19:04:54 -07:00
|
|
|
if (Core::getInstance().isWorldLoaded())
|
|
|
|
autofarmInstance->save_state(out);
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event event)
|
|
|
|
{
|
|
|
|
if (!autofarmInstance)
|
|
|
|
return CR_OK;
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case SC_WORLD_LOADED:
|
|
|
|
autofarmInstance->load_state(out);
|
|
|
|
break;
|
|
|
|
case SC_MAP_UNLOADED:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-08-16 08:16:52 -06:00
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
static command_result setThresholds(color_ostream& out, std::vector<std::string>& parameters)
|
2019-11-21 17:58:06 -07:00
|
|
|
{
|
|
|
|
int val = atoi(parameters[1].c_str());
|
2020-01-18 18:53:13 -07:00
|
|
|
for (size_t i = 2; i < parameters.size(); i++)
|
2019-11-21 17:58:06 -07:00
|
|
|
{
|
2022-03-16 20:43:24 -06:00
|
|
|
std::string id = parameters[i];
|
|
|
|
std::transform(id.begin(), id.end(), id.begin(), ::toupper);
|
2019-11-21 17:58:06 -07:00
|
|
|
|
|
|
|
bool ok = false;
|
|
|
|
for (auto plant : world->raws.plants.all)
|
|
|
|
{
|
|
|
|
if (plant->flags.is_set(df::plant_raw_flags::SEED) && (plant->id == id))
|
|
|
|
{
|
|
|
|
autofarmInstance->setThreshold(plant->index, val);
|
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!ok)
|
|
|
|
{
|
2022-11-15 19:00:27 -07:00
|
|
|
out << "Cannot find plant with id " << id << '\n' << std::flush;
|
2019-11-21 17:58:06 -07:00
|
|
|
return CR_WRONG_USAGE;
|
|
|
|
}
|
|
|
|
}
|
2023-01-06 19:04:54 -07:00
|
|
|
autofarmInstance->save_state(out);
|
2019-11-21 17:58:06 -07:00
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
2022-03-16 20:43:24 -06:00
|
|
|
static command_result autofarm(color_ostream& out, std::vector<std::string>& parameters)
|
2018-08-16 08:16:52 -06:00
|
|
|
{
|
|
|
|
CoreSuspender suspend;
|
|
|
|
|
2018-08-27 21:29:14 -06:00
|
|
|
if (parameters.size() == 1 && parameters[0] == "runonce")
|
2021-08-07 14:32:43 -06:00
|
|
|
{
|
|
|
|
if (autofarmInstance) autofarmInstance->process(out);
|
|
|
|
}
|
2018-08-27 21:29:14 -06:00
|
|
|
else if (parameters.size() == 1 && parameters[0] == "enable")
|
2018-08-16 08:16:52 -06:00
|
|
|
plugin_enable(out, true);
|
|
|
|
else if (parameters.size() == 1 && parameters[0] == "disable")
|
|
|
|
plugin_enable(out, false);
|
|
|
|
else if (parameters.size() == 2 && parameters[0] == "default")
|
2021-08-07 14:32:43 -06:00
|
|
|
{
|
2023-01-06 19:04:54 -07:00
|
|
|
if (autofarmInstance)
|
|
|
|
{
|
|
|
|
autofarmInstance->setDefault(atoi(parameters[1].c_str()));
|
|
|
|
autofarmInstance->save_state(out);
|
|
|
|
}
|
2021-08-07 14:32:43 -06:00
|
|
|
}
|
2018-08-16 08:16:52 -06:00
|
|
|
else if (parameters.size() >= 3 && parameters[0] == "threshold")
|
2021-08-07 14:32:43 -06:00
|
|
|
{
|
2023-01-06 19:04:54 -07:00
|
|
|
if (autofarmInstance)
|
|
|
|
return setThresholds(out, parameters);
|
2021-08-07 14:32:43 -06:00
|
|
|
}
|
2020-01-18 18:53:13 -07:00
|
|
|
else if (parameters.size() == 0 || (parameters.size() == 1 && parameters[0] == "status"))
|
2018-08-16 08:16:52 -06:00
|
|
|
autofarmInstance->status(out);
|
|
|
|
else
|
|
|
|
return CR_WRONG_USAGE;
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|