update misery; persist state

develop
Myk Taylor 2023-02-08 14:02:44 -08:00
parent 5113823d8c
commit 9f76d64e42
No known key found for this signature in database
GPG Key ID: 8A39CA0FA0C16E78
4 changed files with 285 additions and 145 deletions

@ -2,18 +2,35 @@ misery
====== ======
.. dfhack-tool:: .. dfhack-tool::
:summary: Increase the intensity of negative dwarven thoughts. :summary: Increase the intensity of your citizens' negative thoughts.
:tags: fort armok auto units :tags: fort gameplay units
When enabled, negative thoughts that your dwarves have will multiply by the When enabled, negative thoughts that your citizens have will multiply by the
specified factor. specified factor. This makes it more challenging to keep them happy.
Usage Usage
----- -----
::
enable misery
misery [status]
misery <factor>
misery clear
The default misery factor is ``2``, meaning that your dwarves will become
miserable twice as fast.
Examples
--------
``enable misery`` ``enable misery``
Start multiplying negative thoughts. Start multiplying bad thoughts for your citizens!
``misery <factor>``
Change the multiplicative factor of bad thoughts. The default is ``2``. ``misery 5``
Make dwarves become unhappy 5 times faster than normal -- this is quite
challenging to handle!
``misery clear`` ``misery clear``
Clear away negative thoughts added by ``misery``. Clear away negative thoughts added by ``misery``. Note that this will not
clear negative thoughts that your dwarves accumulated "naturally".

@ -130,7 +130,7 @@ dfhack_plugin(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua)
#dfhack_plugin(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) #dfhack_plugin(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread)
#dfhack_plugin(manipulator manipulator.cpp) #dfhack_plugin(manipulator manipulator.cpp)
#dfhack_plugin(map-render map-render.cpp LINK_LIBRARIES lua) #dfhack_plugin(map-render map-render.cpp LINK_LIBRARIES lua)
dfhack_plugin(misery misery.cpp) dfhack_plugin(misery misery.cpp LINK_LIBRARIES lua)
#dfhack_plugin(mode mode.cpp) #dfhack_plugin(mode mode.cpp)
#dfhack_plugin(mousequery mousequery.cpp) #dfhack_plugin(mousequery mousequery.cpp)
dfhack_plugin(nestboxes nestboxes.cpp) dfhack_plugin(nestboxes nestboxes.cpp)

@ -0,0 +1,43 @@
local _ENV = mkmodule('plugins.misery')
local argparse = require('argparse')
local function process_args(opts, args)
if args[1] == 'help' then
opts.help = true
return
end
return argparse.processArgsGetopt(args, {
{'h', 'help', handler=function() opts.help = true end},
})
end
function status()
print(('misery is %s'):format(isEnabled() and "enabled" or "disabled"))
print(('misery factor is: %d'):format(misery_getFactor()))
end
function parse_commandline(...)
local args, opts = {...}, {}
local positionals = process_args(opts, args)
if opts.help then
return false
end
local command = table.remove(positionals, 1)
if not command or command == 'status' then
status()
elseif command == 'factor' then
misery_setFactor(positionals[1])
elseif command == 'clear' then
misery_clear()
else
return false
end
return true
end
return _ENV

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