@ -4,218 +4,114 @@
// 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/Items.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 > & parameters ) ;
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 +133,283 @@ 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 )
DFhackCExport command_result plugin_shutdown ( color_ostream & out ) {
DEBUG ( config , out ) . print ( " shutting down %s \n " , plugin_name ) ;
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 " ) ;
}
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 ) ;
}
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_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 ) ;
}
// this is dwarf mode, continue
map < int32_t , int16_t > seedCount ; // the number of 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 ) ;
// 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 ] ;
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 > & parameters ) {
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 & param : 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 ;
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 ( df : : item * item , const vector < df : : unit * > & citizens ) {
const df : : coord pos = Items : : getPosition ( item ) ;
for ( auto & unit : citizens ) {
if ( Maps : : canWalkBetween ( Units : : getPosition ( unit ) , 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 ( ! ignoreSeeds ( item - > flags ) )
+ + seedCount [ mat . plant - > index ] ;
if ( ( bad_flags . whole & item - > flags . whole ) | | ! is_accessible_item ( item , 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 ) ;
}
}
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 ) ;
// 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 ;
}
else if ( i - > second + buffer < seedCount [ i - > first ] )
{
Kitchen : : allowPlantSeedCookery ( i - > first ) ;
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 ;
}
return CR_OK ;
set_target ( out , world_plant_ids [ token ] , num ) ;
}
DFhackCExport command_result plugin_shutdown ( Core * pCore )
{
return CR_OK ;
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
} ;