|
|
|
@ -4,218 +4,113 @@
|
|
|
|
|
// With thanks to peterix for DFHack and Quietust for information
|
|
|
|
|
// http://www.bay12forums.com/smf/index.php?topic=91166.msg2605147#msg2605147
|
|
|
|
|
|
|
|
|
|
#include <map>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <vector>
|
|
|
|
|
#include "Console.h"
|
|
|
|
|
#include "Core.h"
|
|
|
|
|
#include "Export.h"
|
|
|
|
|
#include "Debug.h"
|
|
|
|
|
#include "LuaTools.h"
|
|
|
|
|
#include "PluginManager.h"
|
|
|
|
|
#include "modules/World.h"
|
|
|
|
|
#include "modules/Materials.h"
|
|
|
|
|
#include "TileTypes.h"
|
|
|
|
|
|
|
|
|
|
#include "modules/Kitchen.h"
|
|
|
|
|
#include "VersionInfo.h"
|
|
|
|
|
#include "df/world.h"
|
|
|
|
|
#include "df/plant_raw.h"
|
|
|
|
|
#include "modules/Maps.h"
|
|
|
|
|
#include "modules/Persistence.h"
|
|
|
|
|
#include "modules/Units.h"
|
|
|
|
|
#include "modules/World.h"
|
|
|
|
|
|
|
|
|
|
#include "df/item_flags.h"
|
|
|
|
|
#include "df/items_other_id.h"
|
|
|
|
|
#include "df/plant_raw.h"
|
|
|
|
|
#include "df/world.h"
|
|
|
|
|
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
using namespace DFHack;
|
|
|
|
|
using namespace df::enums;
|
|
|
|
|
|
|
|
|
|
using std::map;
|
|
|
|
|
using std::string;
|
|
|
|
|
using std::unordered_map;
|
|
|
|
|
using std::vector;
|
|
|
|
|
|
|
|
|
|
DFHACK_PLUGIN("seedwatch");
|
|
|
|
|
DFHACK_PLUGIN_IS_ENABLED(running); // whether seedwatch is counting the seeds or not
|
|
|
|
|
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
|
|
|
|
|
|
|
|
|
REQUIRE_GLOBAL(world);
|
|
|
|
|
|
|
|
|
|
const int buffer = 20; // seed number buffer - 20 is reasonable
|
|
|
|
|
namespace DFHack {
|
|
|
|
|
// for configuration-related logging
|
|
|
|
|
DBG_DECLARE(seedwatch, config, DebugCategory::LINFO);
|
|
|
|
|
// for logging during the periodic scan
|
|
|
|
|
DBG_DECLARE(seedwatch, cycle, DebugCategory::LINFO);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// abbreviations for the standard plants
|
|
|
|
|
map<string, string> abbreviations;
|
|
|
|
|
|
|
|
|
|
bool ignoreSeeds(df::item_flags& f) // seeds with the following flags should not be counted
|
|
|
|
|
{
|
|
|
|
|
return
|
|
|
|
|
f.bits.dump ||
|
|
|
|
|
f.bits.forbid ||
|
|
|
|
|
f.bits.garbage_collect ||
|
|
|
|
|
f.bits.hidden ||
|
|
|
|
|
f.bits.hostile ||
|
|
|
|
|
f.bits.on_fire ||
|
|
|
|
|
f.bits.rotten ||
|
|
|
|
|
f.bits.trader ||
|
|
|
|
|
f.bits.in_building ||
|
|
|
|
|
f.bits.in_job;
|
|
|
|
|
};
|
|
|
|
|
static unordered_map<string, const char *> abbreviations;
|
|
|
|
|
static map<string, int32_t> world_plant_ids;
|
|
|
|
|
static const int DEFAULT_TARGET = 30;
|
|
|
|
|
static const int TARGET_BUFFER = 20; // seed number buffer; 20 is reasonable
|
|
|
|
|
|
|
|
|
|
// searches abbreviations, returns expansion if so, returns original if not
|
|
|
|
|
string searchAbbreviations(string in)
|
|
|
|
|
{
|
|
|
|
|
if(abbreviations.count(in) > 0)
|
|
|
|
|
{
|
|
|
|
|
return abbreviations[in];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return in;
|
|
|
|
|
}
|
|
|
|
|
static const string CONFIG_KEY = string(plugin_name) + "/config";
|
|
|
|
|
static const string SEED_CONFIG_KEY_PREFIX = string(plugin_name) + "/seed/";
|
|
|
|
|
static PersistentDataItem config;
|
|
|
|
|
static unordered_map<int, PersistentDataItem> watched_seeds;
|
|
|
|
|
|
|
|
|
|
enum ConfigValues {
|
|
|
|
|
CONFIG_IS_ENABLED = 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
|
|
|
|
|
{
|
|
|
|
|
if(enable == true)
|
|
|
|
|
{
|
|
|
|
|
if(Core::getInstance().isWorldLoaded())
|
|
|
|
|
{
|
|
|
|
|
running = true;
|
|
|
|
|
out.print("seedwatch supervision started.\n");
|
|
|
|
|
} else {
|
|
|
|
|
out.printerr(
|
|
|
|
|
"This plugin needs a fortress to be loaded and will deactivate automatically otherwise.\n"
|
|
|
|
|
"Activate with 'seedwatch start' after you load the game.\n"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
running = false;
|
|
|
|
|
out.print("seedwatch supervision stopped.\n");
|
|
|
|
|
}
|
|
|
|
|
enum SeedConfigValues {
|
|
|
|
|
SEED_CONFIG_ID = 0,
|
|
|
|
|
SEED_CONFIG_TARGET = 1,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
|
static int get_config_val(PersistentDataItem &c, int index) {
|
|
|
|
|
if (!c.isValid())
|
|
|
|
|
return -1;
|
|
|
|
|
return c.ival(index);
|
|
|
|
|
}
|
|
|
|
|
static bool get_config_bool(PersistentDataItem &c, int index) {
|
|
|
|
|
return get_config_val(c, index) == 1;
|
|
|
|
|
}
|
|
|
|
|
static void set_config_val(PersistentDataItem &c, int index, int value) {
|
|
|
|
|
if (c.isValid())
|
|
|
|
|
c.ival(index) = value;
|
|
|
|
|
}
|
|
|
|
|
static void set_config_bool(PersistentDataItem &c, int index, bool value) {
|
|
|
|
|
set_config_val(c, index, value ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
command_result df_seedwatch(color_ostream &out, vector<string>& parameters)
|
|
|
|
|
{
|
|
|
|
|
CoreSuspender suspend;
|
|
|
|
|
|
|
|
|
|
map<string, int32_t> plantIDs;
|
|
|
|
|
for(size_t i = 0; i < world->raws.plants.all.size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
auto & plant = world->raws.plants.all[i];
|
|
|
|
|
if (plant->material_defs.type[plant_material_def::seed] != -1)
|
|
|
|
|
plantIDs[plant->id] = i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t_gamemodes gm;
|
|
|
|
|
World::ReadGameMode(gm);// FIXME: check return value
|
|
|
|
|
|
|
|
|
|
// if game mode isn't fortress mode
|
|
|
|
|
if(gm.g_mode != game_mode::DWARF || !World::isFortressMode(gm.g_type))
|
|
|
|
|
{
|
|
|
|
|
// just print the help
|
|
|
|
|
return CR_WRONG_USAGE;
|
|
|
|
|
}
|
|
|
|
|
static PersistentDataItem & ensure_seed_config(color_ostream &out, int id) {
|
|
|
|
|
if (watched_seeds.count(id))
|
|
|
|
|
return watched_seeds[id];
|
|
|
|
|
string keyname = SEED_CONFIG_KEY_PREFIX + int_to_string(id);
|
|
|
|
|
DEBUG(config,out).print("creating new persistent key for seed type %d\n", id);
|
|
|
|
|
watched_seeds.emplace(id, World::GetPersistentData(keyname, NULL));
|
|
|
|
|
return watched_seeds[id];
|
|
|
|
|
}
|
|
|
|
|
static void remove_seed_config(color_ostream &out, int id) {
|
|
|
|
|
if (!watched_seeds.count(id))
|
|
|
|
|
return;
|
|
|
|
|
DEBUG(config,out).print("removing persistent key for seed type %d\n", id);
|
|
|
|
|
World::DeletePersistentData(watched_seeds[id]);
|
|
|
|
|
watched_seeds.erase(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string par;
|
|
|
|
|
int limit;
|
|
|
|
|
switch(parameters.size())
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
return CR_WRONG_USAGE;
|
|
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
|
par = parameters[0];
|
|
|
|
|
if ((par == "help") || (par == "?"))
|
|
|
|
|
{
|
|
|
|
|
return CR_WRONG_USAGE;
|
|
|
|
|
}
|
|
|
|
|
else if(par == "start")
|
|
|
|
|
{
|
|
|
|
|
plugin_enable(out, true);
|
|
|
|
|
static const int32_t CYCLE_TICKS = 1200;
|
|
|
|
|
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else if(par == "stop")
|
|
|
|
|
{
|
|
|
|
|
plugin_enable(out, false);
|
|
|
|
|
}
|
|
|
|
|
else if(par == "clear")
|
|
|
|
|
{
|
|
|
|
|
Kitchen::clearLimits();
|
|
|
|
|
out.print("seedwatch watchlist cleared\n");
|
|
|
|
|
}
|
|
|
|
|
else if(par == "info")
|
|
|
|
|
{
|
|
|
|
|
out.print("seedwatch Info:\n");
|
|
|
|
|
if(running)
|
|
|
|
|
{
|
|
|
|
|
out.print("seedwatch is supervising. Use 'disable seedwatch' to stop supervision.\n");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
out.print("seedwatch is not supervising. Use 'enable seedwatch' to start supervision.\n");
|
|
|
|
|
}
|
|
|
|
|
map<int32_t, int16_t> watchMap;
|
|
|
|
|
Kitchen::fillWatchMap(watchMap);
|
|
|
|
|
if(watchMap.empty())
|
|
|
|
|
{
|
|
|
|
|
out.print("The watch list is empty.\n");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
out.print("The watch list is:\n");
|
|
|
|
|
for(auto i = watchMap.begin(); i != watchMap.end(); ++i)
|
|
|
|
|
{
|
|
|
|
|
out.print("%s : %u\n", world->raws.plants.all[i->first]->id.c_str(), i->second);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(par == "debug")
|
|
|
|
|
{
|
|
|
|
|
map<int32_t, int16_t> watchMap;
|
|
|
|
|
Kitchen::fillWatchMap(watchMap);
|
|
|
|
|
Kitchen::debug_print(out);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
string token = searchAbbreviations(par);
|
|
|
|
|
if(plantIDs.count(token) > 0)
|
|
|
|
|
{
|
|
|
|
|
Kitchen::removeLimit(plantIDs[token]);
|
|
|
|
|
out.print("%s is not being watched\n", token.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
out.print("%s has not been found as a material.\n", token.c_str());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
limit = atoi(parameters[1].c_str());
|
|
|
|
|
if(limit < 0) limit = 0;
|
|
|
|
|
if(parameters[0] == "all")
|
|
|
|
|
{
|
|
|
|
|
for(auto & entry : plantIDs)
|
|
|
|
|
Kitchen::setLimit(entry.second, limit);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
string token = searchAbbreviations(parameters[0]);
|
|
|
|
|
if(plantIDs.count(token) > 0)
|
|
|
|
|
{
|
|
|
|
|
Kitchen::setLimit(plantIDs[token], limit);
|
|
|
|
|
out.print("%s is being watched.\n", token.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
out.print("%s has not been found as a material.\n", token.c_str());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return CR_WRONG_USAGE;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
static command_result do_command(color_ostream &out, vector<string> ¶meters);
|
|
|
|
|
static void do_cycle(color_ostream &out, int32_t *num_enabled_seeds, int32_t *num_disabled_seeds);
|
|
|
|
|
static void seedwatch_setTarget(color_ostream &out, string name, int32_t num);
|
|
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
|
}
|
|
|
|
|
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
|
|
|
|
|
DEBUG(config,out).print("initializing %s\n", plugin_name);
|
|
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_init(color_ostream &out, vector<PluginCommand>& commands)
|
|
|
|
|
{
|
|
|
|
|
// provide a configuration interface for the plugin
|
|
|
|
|
commands.push_back(PluginCommand(
|
|
|
|
|
"seedwatch",
|
|
|
|
|
"Toggles seed cooking based on quantity available.",
|
|
|
|
|
df_seedwatch));
|
|
|
|
|
// fill in the abbreviations map, with abbreviations for the standard plants
|
|
|
|
|
plugin_name,
|
|
|
|
|
"Automatically toggle seed cooking based on quantity available.",
|
|
|
|
|
do_command));
|
|
|
|
|
|
|
|
|
|
// fill in the abbreviations map
|
|
|
|
|
abbreviations["bs"] = "SLIVER_BARB";
|
|
|
|
|
abbreviations["bt"] = "TUBER_BLOATED";
|
|
|
|
|
abbreviations["bw"] = "WEED_BLADE";
|
|
|
|
@ -237,72 +132,285 @@ DFhackCExport command_result plugin_init(color_ostream &out, vector<PluginComman
|
|
|
|
|
abbreviations["vh"] = "HERB_VALLEY";
|
|
|
|
|
abbreviations["ws"] = "BERRIES_STRAW";
|
|
|
|
|
abbreviations["wv"] = "VINE_WHIP";
|
|
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
|
|
|
|
{
|
|
|
|
|
if (event == SC_MAP_UNLOADED) {
|
|
|
|
|
if (running)
|
|
|
|
|
out.print("seedwatch deactivated due to game unload\n");
|
|
|
|
|
running = false;
|
|
|
|
|
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
|
|
|
|
|
if (!Core::getInstance().isWorldLoaded()) {
|
|
|
|
|
out.printerr("Cannot enable %s without a loaded world.\n", plugin_name);
|
|
|
|
|
return CR_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (enable != is_enabled) {
|
|
|
|
|
is_enabled = enable;
|
|
|
|
|
DEBUG(config,out).print("%s from the API; persisting\n",
|
|
|
|
|
is_enabled ? "enabled" : "disabled");
|
|
|
|
|
set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
|
|
|
|
|
if (enable)
|
|
|
|
|
seedwatch_setTarget(out, "all", DEFAULT_TARGET);
|
|
|
|
|
} else {
|
|
|
|
|
DEBUG(config,out).print("%s from the API, but already %s; no action\n",
|
|
|
|
|
is_enabled ? "enabled" : "disabled",
|
|
|
|
|
is_enabled ? "enabled" : "disabled");
|
|
|
|
|
}
|
|
|
|
|
return CR_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_onupdate(color_ostream &out)
|
|
|
|
|
{
|
|
|
|
|
if (running)
|
|
|
|
|
{
|
|
|
|
|
// reduce processing rate
|
|
|
|
|
static int counter = 0;
|
|
|
|
|
if (++counter < 500)
|
|
|
|
|
return CR_OK;
|
|
|
|
|
counter = 0;
|
|
|
|
|
|
|
|
|
|
t_gamemodes gm;
|
|
|
|
|
World::ReadGameMode(gm);// FIXME: check return value
|
|
|
|
|
// if game mode isn't fortress mode
|
|
|
|
|
if(gm.g_mode != game_mode::DWARF || !World::isFortressMode(gm.g_type))
|
|
|
|
|
{
|
|
|
|
|
// stop running.
|
|
|
|
|
running = false;
|
|
|
|
|
out.printerr("seedwatch deactivated due to game mode switch\n");
|
|
|
|
|
return CR_OK;
|
|
|
|
|
}
|
|
|
|
|
// this is dwarf mode, continue
|
|
|
|
|
map<int32_t, int16_t> seedCount; // the number of seeds
|
|
|
|
|
|
|
|
|
|
// count all seeds and plants by RAW material
|
|
|
|
|
for(size_t i = 0; i < world->items.other[items_other_id::SEEDS].size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
df::item *item = world->items.other[items_other_id::SEEDS][i];
|
|
|
|
|
MaterialInfo mat(item);
|
|
|
|
|
if (!mat.isPlant())
|
|
|
|
|
continue;
|
|
|
|
|
if (!ignoreSeeds(item->flags))
|
|
|
|
|
++seedCount[mat.plant->index];
|
|
|
|
|
}
|
|
|
|
|
DFhackCExport command_result plugin_shutdown (color_ostream &out) {
|
|
|
|
|
DEBUG(config,out).print("shutting down %s\n", plugin_name);
|
|
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_load_data (color_ostream &out) {
|
|
|
|
|
world_plant_ids.clear();
|
|
|
|
|
for (size_t i = 0; i < world->raws.plants.all.size(); ++i) {
|
|
|
|
|
auto & plant = world->raws.plants.all[i];
|
|
|
|
|
if (plant->material_defs.type[plant_material_def::seed] != -1)
|
|
|
|
|
world_plant_ids[plant->id] = i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config = World::GetPersistentData(CONFIG_KEY);
|
|
|
|
|
|
|
|
|
|
if (!config.isValid()) {
|
|
|
|
|
DEBUG(config,out).print("no config found in this save; initializing\n");
|
|
|
|
|
config = World::AddPersistentData(CONFIG_KEY);
|
|
|
|
|
set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is_enabled = get_config_bool(config, CONFIG_IS_ENABLED);
|
|
|
|
|
DEBUG(config,out).print("loading persisted enabled state: %s\n",
|
|
|
|
|
is_enabled ? "true" : "false");
|
|
|
|
|
watched_seeds.clear();
|
|
|
|
|
vector<PersistentDataItem> seed_configs;
|
|
|
|
|
World::GetPersistentData(&seed_configs, SEED_CONFIG_KEY_PREFIX, true);
|
|
|
|
|
const size_t num_seed_configs = seed_configs.size();
|
|
|
|
|
for (size_t idx = 0; idx < num_seed_configs; ++idx) {
|
|
|
|
|
auto &c = seed_configs[idx];
|
|
|
|
|
watched_seeds.emplace(get_config_val(c, SEED_CONFIG_ID), c);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
map<int32_t, int16_t> watchMap;
|
|
|
|
|
Kitchen::fillWatchMap(watchMap);
|
|
|
|
|
for(auto i = watchMap.begin(); i != watchMap.end(); ++i)
|
|
|
|
|
{
|
|
|
|
|
if(seedCount[i->first] <= i->second)
|
|
|
|
|
{
|
|
|
|
|
Kitchen::denyPlantSeedCookery(i->first);
|
|
|
|
|
}
|
|
|
|
|
else if(i->second + buffer < seedCount[i->first])
|
|
|
|
|
{
|
|
|
|
|
Kitchen::allowPlantSeedCookery(i->first);
|
|
|
|
|
}
|
|
|
|
|
return CR_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
|
|
|
|
|
if (event == DFHack::SC_WORLD_UNLOADED) {
|
|
|
|
|
if (is_enabled) {
|
|
|
|
|
DEBUG(config,out).print("world unloaded; disabling %s\n",
|
|
|
|
|
plugin_name);
|
|
|
|
|
is_enabled = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return CR_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_shutdown(Core* pCore)
|
|
|
|
|
{
|
|
|
|
|
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
|
|
|
|
|
if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS) {
|
|
|
|
|
int32_t num_enabled_seeds, num_disabled_seeds;
|
|
|
|
|
do_cycle(out, &num_enabled_seeds, &num_disabled_seeds);
|
|
|
|
|
if (0 < num_enabled_seeds)
|
|
|
|
|
out.print("%s: enabled %d seed types for cooking\n",
|
|
|
|
|
plugin_name, num_enabled_seeds);
|
|
|
|
|
if (0 < num_disabled_seeds)
|
|
|
|
|
out.print("%s: protected %d seed types from cooking\n",
|
|
|
|
|
plugin_name, num_disabled_seeds);
|
|
|
|
|
}
|
|
|
|
|
return CR_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool call_seedwatch_lua(color_ostream *out, const char *fn_name,
|
|
|
|
|
int nargs = 0, int nres = 0,
|
|
|
|
|
Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
|
|
|
|
|
Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
|
|
|
|
|
CoreSuspender guard;
|
|
|
|
|
|
|
|
|
|
auto L = Lua::Core::State;
|
|
|
|
|
Lua::StackUnwinder top(L);
|
|
|
|
|
|
|
|
|
|
if (!out)
|
|
|
|
|
out = &Core::getInstance().getConsole();
|
|
|
|
|
|
|
|
|
|
DEBUG(config,*out).print("calling %s lua function: '%s'\n", plugin_name, fn_name);
|
|
|
|
|
|
|
|
|
|
return Lua::CallLuaModuleFunction(*out, L, "plugins.seedwatch", fn_name,
|
|
|
|
|
nargs, nres,
|
|
|
|
|
std::forward<Lua::LuaLambda&&>(args_lambda),
|
|
|
|
|
std::forward<Lua::LuaLambda&&>(res_lambda));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static command_result do_command(color_ostream &out, vector<string> ¶meters) {
|
|
|
|
|
CoreSuspender suspend;
|
|
|
|
|
|
|
|
|
|
if (!Core::getInstance().isWorldLoaded()) {
|
|
|
|
|
out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
|
|
|
|
|
return CR_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool show_help = false;
|
|
|
|
|
if (!call_seedwatch_lua(&out, "parse_commandline", parameters.size(), 1,
|
|
|
|
|
[&](lua_State *L) {
|
|
|
|
|
for (const string ¶m : parameters)
|
|
|
|
|
Lua::Push(L, param);
|
|
|
|
|
},
|
|
|
|
|
[&](lua_State *L) {
|
|
|
|
|
show_help = !lua_toboolean(L, -1);
|
|
|
|
|
})) {
|
|
|
|
|
return CR_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return show_help ? CR_WRONG_USAGE : CR_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
|
|
|
// cycle logic
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
struct BadFlags {
|
|
|
|
|
uint32_t whole;
|
|
|
|
|
|
|
|
|
|
// TODO: maybe don't filter out seeds that are in_building. that would
|
|
|
|
|
// allow us to count seeds that are in workshops. are there any negative
|
|
|
|
|
// consequences?
|
|
|
|
|
BadFlags() {
|
|
|
|
|
df::item_flags flags;
|
|
|
|
|
#define F(x) flags.bits.x = true;
|
|
|
|
|
F(dump); F(forbid); F(garbage_collect);
|
|
|
|
|
F(hostile); F(on_fire); F(rotten); F(trader);
|
|
|
|
|
F(in_building); F(construction); F(artifact);
|
|
|
|
|
F(in_job); F(owned); F(in_chest); F(removed);
|
|
|
|
|
F(encased); F(spider_web);
|
|
|
|
|
#undef F
|
|
|
|
|
whole = flags.whole;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static bool is_accessible_item(const df::coord &pos, const vector<df::unit *> &citizens) {
|
|
|
|
|
for (auto &unit : citizens) {
|
|
|
|
|
if (Maps::canWalkBetween(unit->pos, pos))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void scan_seeds(color_ostream &out, unordered_map<int32_t, int32_t> *accessible_counts,
|
|
|
|
|
unordered_map<int32_t, int32_t> *inaccessible_counts = NULL) {
|
|
|
|
|
static const BadFlags bad_flags;
|
|
|
|
|
|
|
|
|
|
vector<df::unit *> citizens;
|
|
|
|
|
Units::getCitizens(citizens);
|
|
|
|
|
|
|
|
|
|
for (auto &item : world->items.other[items_other_id::SEEDS]) {
|
|
|
|
|
MaterialInfo mat(item);
|
|
|
|
|
if (!mat.isPlant())
|
|
|
|
|
continue;
|
|
|
|
|
if ((bad_flags.whole & item->flags.whole) || !is_accessible_item(item->pos, citizens)) {
|
|
|
|
|
if (inaccessible_counts)
|
|
|
|
|
++(*inaccessible_counts)[mat.plant->index];
|
|
|
|
|
} else {
|
|
|
|
|
if (accessible_counts)
|
|
|
|
|
++(*accessible_counts)[mat.plant->index];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void do_cycle(color_ostream &out, int32_t *num_enabled_seed_types, int32_t *num_disabled_seed_types) {
|
|
|
|
|
DEBUG(cycle,out).print("running %s cycle\n", plugin_name);
|
|
|
|
|
|
|
|
|
|
// mark that we have recently run
|
|
|
|
|
cycle_timestamp = world->frame_counter;
|
|
|
|
|
|
|
|
|
|
if (num_enabled_seed_types)
|
|
|
|
|
*num_enabled_seed_types = 0;
|
|
|
|
|
if (num_disabled_seed_types)
|
|
|
|
|
*num_disabled_seed_types = 0;
|
|
|
|
|
|
|
|
|
|
unordered_map<int32_t, int32_t> accessible_counts;
|
|
|
|
|
scan_seeds(out, &accessible_counts);
|
|
|
|
|
|
|
|
|
|
for (auto &entry : watched_seeds) {
|
|
|
|
|
int32_t id = entry.first;
|
|
|
|
|
int32_t target = get_config_val(entry.second, SEED_CONFIG_TARGET);
|
|
|
|
|
if (accessible_counts[id] <= target) {
|
|
|
|
|
DEBUG(cycle,out).print("disabling seed mat: %d\n", id);
|
|
|
|
|
if (num_disabled_seed_types)
|
|
|
|
|
++*num_disabled_seed_types;
|
|
|
|
|
Kitchen::denyPlantSeedCookery(id);
|
|
|
|
|
} else if (target + TARGET_BUFFER < accessible_counts[id]) {
|
|
|
|
|
DEBUG(cycle,out).print("enabling seed mat: %d\n", id);
|
|
|
|
|
if (num_enabled_seed_types)
|
|
|
|
|
++*num_enabled_seed_types;
|
|
|
|
|
Kitchen::allowPlantSeedCookery(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
|
|
|
// Lua API
|
|
|
|
|
// core will already be suspended when coming in through here
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
static void set_target(color_ostream &out, int32_t id, int32_t target) {
|
|
|
|
|
if (target == 0)
|
|
|
|
|
remove_seed_config(out, id);
|
|
|
|
|
else {
|
|
|
|
|
PersistentDataItem &c = ensure_seed_config(out, id);
|
|
|
|
|
set_config_val(c, SEED_CONFIG_TARGET, target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// searches abbreviations, returns expansion if so, returns original if not
|
|
|
|
|
static string searchAbbreviations(string in) {
|
|
|
|
|
if(abbreviations.count(in) > 0)
|
|
|
|
|
return abbreviations[in];
|
|
|
|
|
return in;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void seedwatch_setTarget(color_ostream &out, string name, int32_t num) {
|
|
|
|
|
DEBUG(config,out).print("entering seedwatch_setTarget\n");
|
|
|
|
|
|
|
|
|
|
if (num < 0) {
|
|
|
|
|
out.printerr("target must be at least 0\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (name == "all") {
|
|
|
|
|
for (auto &entry : world_plant_ids) {
|
|
|
|
|
set_target(out, entry.second, num);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string token = searchAbbreviations(name);
|
|
|
|
|
if (!world_plant_ids.count(token)) {
|
|
|
|
|
out.printerr("%s has not been found as a material.\n", token.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_target(out, world_plant_ids[token], num);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int seedwatch_getData(lua_State *L) {
|
|
|
|
|
color_ostream *out = Lua::GetOutput(L);
|
|
|
|
|
if (!out)
|
|
|
|
|
out = &Core::getInstance().getConsole();
|
|
|
|
|
DEBUG(config,*out).print("entering seedwatch_getData\n");
|
|
|
|
|
unordered_map<int32_t, int32_t> watch_map, accessible_counts;
|
|
|
|
|
scan_seeds(*out, &accessible_counts);
|
|
|
|
|
for (auto &entry : watched_seeds) {
|
|
|
|
|
watch_map.emplace(entry.first, get_config_val(entry.second, SEED_CONFIG_TARGET));
|
|
|
|
|
}
|
|
|
|
|
Lua::Push(L, watch_map);
|
|
|
|
|
Lua::Push(L, accessible_counts);
|
|
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DFHACK_PLUGIN_LUA_FUNCTIONS {
|
|
|
|
|
DFHACK_LUA_FUNCTION(seedwatch_setTarget),
|
|
|
|
|
DFHACK_LUA_END
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
DFHACK_PLUGIN_LUA_COMMANDS {
|
|
|
|
|
DFHACK_LUA_COMMAND(seedwatch_getData),
|
|
|
|
|
DFHACK_LUA_END
|
|
|
|
|
};
|
|
|
|
|