@ -1,14 +1,7 @@
# include <algorithm>
# include <map>
# include <string>
# include <vector>
# include "DataDefs.h"
# include "Export.h"
# include "PluginManager.h"
# include "modules/Units.h"
# include "df/emotion_type.h"
# include "df/plotinfost.h"
# include "df/unit.h"
@ -17,179 +10,266 @@
# include "df/unit_thought_type.h"
# include "df/world.h"
using namespace std ;
# include "modules/Persistence.h"
# include "modules/Units.h"
# include "modules/World.h"
# include "Core.h"
# include "Debug.h"
# include "LuaTools.h"
# include "PluginManager.h"
using std : : string ;
using std : : vector ;
using namespace DFHack ;
DFHACK_PLUGIN ( " misery " ) ;
DFHACK_PLUGIN_IS_ENABLED ( is_enabled ) ;
REQUIRE_GLOBAL ( world ) ;
REQUIRE_GLOBAL ( plotinfo ) ;
REQUIRE_GLOBAL ( cur_year ) ;
REQUIRE_GLOBAL ( cur_year_tick ) ;
REQUIRE_GLOBAL ( world ) ;
typedef df : : unit_personality : : T_emotions Emotion ;
static int factor = 1 ;
static int tick = 0 ;
const int INTERVAL = 1000 ;
namespace DFHack {
DBG_DECLARE ( misery , cycle , DebugCategory : : LINFO ) ;
DBG_DECLARE ( misery , config , DebugCategory : : LINFO ) ;
}
command_result misery ( color_ostream & out , vector < string > & parameters ) ;
void add_misery ( df : : unit * unit ) ;
void clear_misery ( df : : unit * unit ) ;
static const string CONFIG_KEY = string ( plugin_name ) + " /config " ;
static PersistentDataItem config ;
const int FAKE_EMOTION_FLAG = ( 1 < < 30 ) ;
const int STRENGTH_MULTIPLIER = 100 ;
enum ConfigValues {
CONFIG_IS_ENABLED = 0 ,
CONFIG_FACTOR = 1 ,
} ;
bool is_valid_unit ( df : : unit * unit ) {
if ( ! Units : : isOwnRace ( unit ) | | ! Units : : isOwnCiv ( unit ) )
return false ;
if ( ! Units : : isActive ( unit ) )
return false ;
return true ;
static int get_config_val ( PersistentDataItem & c , int index ) {
if ( ! c . isValid ( ) )
return - 1 ;
return c . ival ( index ) ;
}
inline bool is_fake_emotion ( Emotion * e ) {
return e - > flags . whole & FAKE_EMOTION_FLAG ;
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 ) ;
}
void add_misery ( df : : unit * unit ) {
// Add a fake miserable thought
// Remove any fake ones that already exist
if ( ! unit | | ! unit - > status . current_soul )
return ;
clear_misery ( unit ) ;
auto & emotions = unit - > status . current_soul - > personality . emotions ;
Emotion * e = new Emotion ;
e - > type = df : : emotion_type : : MISERY ;
e - > thought = df : : unit_thought_type : : SoapyBath ;
e - > flags . whole | = FAKE_EMOTION_FLAG ;
emotions . push_back ( e ) ;
static const int32_t CYCLE_TICKS = 1200 ; // one day
static int32_t cycle_timestamp = 0 ; // world->frame_counter at last cycle
for ( Emotion * e : emotions ) {
if ( is_fake_emotion ( e ) ) {
e - > year = * cur_year ;
e - > year_tick = * cur_year_tick ;
e - > strength = STRENGTH_MULTIPLIER * factor ;
e - > severity = STRENGTH_MULTIPLIER * factor ;
}
static command_result do_command ( color_ostream & out , vector < string > & parameters ) ;
static void do_cycle ( color_ostream & out ) ;
DFhackCExport command_result plugin_init ( color_ostream & out , std : : vector < PluginCommand > & commands ) {
DEBUG ( config , out ) . print ( " initializing %s \n " , plugin_name ) ;
// provide a configuration interface for the plugin
commands . push_back ( PluginCommand (
plugin_name ,
" Increase the intensity of negative dwarven thoughts. " ,
do_command ) ) ;
return CR_OK ;
}
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 ;
}
void clear_misery ( df : : unit * unit ) {
if ( ! unit | | ! unit - > status . current_soul )
return ;
auto & emotions = unit - > status . current_soul - > personality . emotions ;
auto it = remove_if ( emotions . begin ( ) , emotions . end ( ) , [ ] ( Emotion * e ) {
if ( is_fake_emotion ( e ) ) {
delete e ;
return true ;
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 )
do_cycle ( out ) ;
} else {
DEBUG ( config , out ) . print ( " %s from the API, but already %s; no action \n " ,
is_enabled ? " enabled " : " disabled " ,
is_enabled ? " enabled " : " disabled " ) ;
}
return false ;
} ) ;
emotions . erase ( it , emotions . end ( ) ) ;
return CR_OK ;
}
DFhackCExport command_result plugin_shutdown ( color_ostream & out ) {
factor = 0 ;
return CR_OK ;
}
DEBUG ( config , out ) . print ( " shutting down %s \n " , plugin_name ) ;
DFhackCExport command_result plugin_onupdate ( color_ostream & out ) {
static bool wasLoaded = false ;
if ( factor = = 0 | | ! world | | ! world - > map . block_index ) {
if ( wasLoaded ) {
//we just unloaded the game: clear all data
factor = 0 ;
is_enabled = false ;
wasLoaded = false ;
}
return CR_OK ;
}
if ( ! wasLoaded ) {
wasLoaded = true ;
DFhackCExport command_result plugin_load_data ( color_ostream & out ) {
cycle_timestamp = 0 ;
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 ) ;
set_config_val ( config , CONFIG_FACTOR , 2 ) ;
}
if ( tick < INTERVAL ) {
tick + + ;
is_enabled = get_config_bool ( config , CONFIG_IS_ENABLED ) ;
DEBUG ( config , out ) . print ( " loading persisted enabled state: %s \n " ,
is_enabled ? " true " : " false " ) ;
return CR_OK ;
}
tick = 0 ;
//TODO: consider units.active
for ( df : : unit * unit : world - > units . all ) {
if ( is_valid_unit ( unit ) ) {
add_misery ( unit ) ;
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_init ( color_ostream & out , vector < PluginCommand > & commands ) {
commands . push_back ( PluginCommand (
" misery " ,
" Increase the intensity of negative dwarven thoughts. " ,
misery ) ) ;
DFhackCExport command_result plugin_onupdate ( color_ostream & out ) {
if ( is_enabled & & world - > frame_counter - cycle_timestamp > = CYCLE_TICKS )
do_cycle ( out ) ;
return CR_OK ;
}
DFhackCExport command_result plugin_enable ( color_ostream & out , bool enable )
{
if ( enable ! = is_enabled )
{
is_enabled = enable ;
factor = enable ? 1 : 0 ;
tick = INTERVAL ;
static bool call_misery_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 ) {
DEBUG ( config ) . print ( " calling misery lua function: '%s' \n " , fn_name ) ;
CoreSuspender guard ;
auto L = Lua : : Core : : State ;
Lua : : StackUnwinder top ( L ) ;
if ( ! out )
out = & Core : : getInstance ( ) . getConsole ( ) ;
return Lua : : CallLuaModuleFunction ( * out , L , " plugins.misery " , fn_name ,
nargs , nres ,
std : : forward < Lua : : LuaLambda & & > ( args_lambda ) ,
std : : forward < Lua : : LuaLambda & & > ( res_lambda ) ) ;
}
return CR_OK ;
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 ;
}
command_result misery ( color_ostream & out , vector < string > & parameters ) {
if ( ! world | | ! world - > map . block_index ) {
out . printerr ( " misery can only be enabled in fortress mode with a fully-loaded game. \n " ) ;
bool show_help = false ;
if ( ! call_misery_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 ;
}
if ( parameters . size ( ) < 1 | | parameters . size ( ) > 2 ) {
return CR_WRONG_USAGE ;
return show_help ? CR_WRONG_USAGE : CR_OK ;
}
if ( parameters [ 0 ] = = " disable " ) {
if ( parameters . size ( ) > 1 ) {
return CR_WRONG_USAGE ;
/////////////////////////////////////////////////////
// cycle logic
//
const int FAKE_EMOTION_FLAG = ( 1 < < 30 ) ;
const int STRENGTH_MULTIPLIER = 100 ;
typedef df : : unit_personality : : T_emotions Emotion ;
static bool is_fake_emotion ( Emotion * e ) {
return e - > flags . whole & FAKE_EMOTION_FLAG ;
}
factor = 0 ;
is_enabled = false ;
return CR_OK ;
} else if ( parameters [ 0 ] = = " enable " ) {
is_enabled = true ;
factor = 1 ;
if ( parameters . size ( ) = = 2 ) {
int a = atoi ( parameters [ 1 ] . c_str ( ) ) ;
if ( a < 1 ) {
out . printerr ( " Second argument must be a positive integer. \n " ) ;
return CR_WRONG_USAGE ;
}
factor = a ;
}
tick = INTERVAL ;
} else if ( parameters [ 0 ] = = " clear " ) {
for ( df : : unit * unit : world - > units . all ) {
if ( is_valid_unit ( unit ) ) {
static void clear_misery ( df : : unit * unit ) {
if ( ! unit | | ! unit - > status . current_soul )
return ;
auto & emotions = unit - > status . current_soul - > personality . emotions ;
auto it = std : : remove_if ( emotions . begin ( ) , emotions . end ( ) , [ ] ( Emotion * e ) {
if ( is_fake_emotion ( e ) ) {
delete e ;
return true ;
}
return false ;
} ) ;
emotions . erase ( it , emotions . end ( ) ) ;
}
// clears fake negative thoughts then runs the given lambda
static void affect_units (
std : : function < void ( df : : unit * ) > & & process_unit = [ ] ( df : : unit * ) { } ) {
for ( auto unit : world - > units . active ) {
if ( ! Units : : isCitizen ( unit ) | | ! unit - > status . current_soul )
continue ;
clear_misery ( unit ) ;
std : : forward < std : : function < void ( df : : unit * ) > & & > ( process_unit ) ( unit ) ;
}
}
} else {
int a = atoi ( parameters [ 0 ] . c_str ( ) ) ;
if ( a < 0 ) {
return CR_WRONG_USAGE ;
static void do_cycle ( color_ostream & out ) {
// mark that we have recently run
cycle_timestamp = world - > frame_counter ;
DEBUG ( cycle , out ) . print ( " running %s cycle \n " , plugin_name ) ;
int strength = STRENGTH_MULTIPLIER * get_config_val ( config , CONFIG_FACTOR ) ;
affect_units ( [ & ] ( df : : unit * unit ) {
Emotion * e = new Emotion ;
e - > type = df : : emotion_type : : MISERY ;
e - > thought = df : : unit_thought_type : : SoapyBath ;
e - > flags . whole | = FAKE_EMOTION_FLAG ;
e - > year = * cur_year ;
e - > year_tick = * cur_year_tick ;
e - > strength = strength ;
e - > severity = strength ;
unit - > status . current_soul - > personality . emotions . push_back ( e ) ;
} ) ;
}
/////////////////////////////////////////////////////
// Lua API
//
static void misery_clear ( color_ostream & out ) {
DEBUG ( config , out ) . print ( " entering misery_clear \n " ) ;
affect_units ( ) ;
}
factor = a ;
is_enabled = factor > 0 ;
static void misery_setFactor ( color_ostream & out , int32_t factor ) {
DEBUG ( config , out ) . print ( " entering misery_setFactor \n " ) ;
if ( 1 > = factor ) {
out . printerr ( " factor must be at least 2 \n " ) ;
return ;
}
set_config_val ( config , CONFIG_FACTOR , factor ) ;
if ( is_enabled )
do_cycle ( out ) ;
}
return CR_OK ;
static int misery_getFactor ( color_ostream & out ) {
DEBUG ( config , out ) . print ( " entering tailor_getFactor \n " ) ;
return get_config_val ( config , CONFIG_FACTOR ) ;
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION ( misery_clear ) ,
DFHACK_LUA_FUNCTION ( misery_setFactor ) ,
DFHACK_LUA_FUNCTION ( misery_getFactor ) ,
DFHACK_LUA_END
} ;