diff --git a/NEWS b/NEWS index 2a7508f5f..a3f959786 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ DFHack future - Is not yet known. +New command: petcapRemover - raise the pet population cap from 50 to whatever you want + DFHack v0.34.11-r4 New commands: diff --git a/Readme.rst b/Readme.rst index a2b400206..7232a7300 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1947,6 +1947,11 @@ See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic * Some of the DFusion plugins aren't completely ported yet. This can lead to crashes. * The game will be suspended while you're using dfusion. Don't panic when it doesn't respond. +petcapRemover +------------- + +This plugin allows you to remove or raise the pet population cap. In vanilla DF, pets will not reproduce unless the population is below 50 and the number of children of that species is below a certain percentage. This plugin allows removing the second restriction and removing or raising the first. Pets still require PET or PET_EXOTIC tags in order to reproduce. Type help petcapRemover for exact usage. In order to make population more stable and avoid sudden population booms as you go below the raised population cap, this plugin counts pregnancies toward the new population cap. It can still go over, but only in the case of multiple births. + misery ------ When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default). diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4de3c68bf..efd86b589 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -162,6 +162,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(treefarm treefarm.cpp) DFHACK_PLUGIN(cleanconst cleanconst.cpp) DFHACK_PLUGIN(3dveins 3dveins.cpp) + DFHACK_PLUGIN(petcapRemover petcapRemover.cpp) endif() # this is the skeleton plugin. If you want to make your own, make a copy and then change it diff --git a/plugins/petcapRemover.cpp b/plugins/petcapRemover.cpp new file mode 100644 index 000000000..f0116131e --- /dev/null +++ b/plugins/petcapRemover.cpp @@ -0,0 +1,213 @@ + +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/EventManager.h" +#include "modules/Maps.h" + +#include "df/caste_raw.h" +#include "df/caste_raw_flags.h" +#include "df/creature_raw.h" +#include "df/profession.h" +#include "df/unit.h" +#include "df/world.h" + +#include +#include + +using namespace DFHack; +using namespace std; + +static int32_t howOften = 10000; +static int32_t popcap = 100; +static int32_t pregtime = 200000; +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +command_result petcapRemover (color_ostream &out, std::vector & parameters); + +DFHACK_PLUGIN("petcapRemover"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "petcapRemover", + "Removes the pet population cap by causing pregnancies.", + petcapRemover, + false, //allow non-interactive use + "petcapRemover\n" + " does pregnancies now and schedules the next check\n" + "petcapRemover every n\n" + " set how often in ticks the plugin checks for possible pregnancies\n" + "petcapRemover cap n\n" + " sets the new cap to n. if n = 0, no cap. Caps between 1 and 50 effectively don't do anything because normal DF pregnancies will continue to happen below that cap.\n" + "petcapRemover pregtime n\n" + " sets the pregnancy duration to n ticks. Natural pregnancies are 300000 ticks for the current race and 200000 ticks for everyone else.\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +bool impregnate(df::unit* female, df::unit* male); +void impregnateMany() { + map > males; + map > females; + map popcount; + auto units = df::global::world->units.all; + for ( size_t a = 0; a < units.size(); a++ ) { + df::unit* unit = units[a]; + if ( unit->flags1.bits.dead || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor ) + continue; + popcount[unit->race]++; + if ( unit->relations.pregnancy_genes ) { + //already pregnant + //for player convenience and population stability, count the fetus toward the population cap + popcount[unit->race]++; + continue; + } + if ( unit->flags1.bits.caged ) + continue; + int32_t race = unit->race; + int16_t caste = unit->caste; + df::creature_raw* creatureRaw = df::global::world->raws.creatures.all[race]; + df::caste_raw* casteRaw = creatureRaw->caste[caste]; + //must have PET or PET_EXOTIC + if ( !(casteRaw->flags.is_set(df::enums::caste_raw_flags::PET) || casteRaw->flags.is_set(df::enums::caste_raw_flags::PET_EXOTIC) ) ) + continue; + //check for adulthood + if ( unit->profession == df::enums::profession::CHILD || unit->profession == df::enums::profession::BABY ) + continue; + if ( unit->sex == 1 ) + males[unit->race].push_back(a); + else + females[unit->race].push_back(a); + } + + for ( auto i = females.begin(); i != females.end(); i++ ) { + int32_t race = i->first; + vector& femalesList = i->second; + for ( size_t a = 0; a < femalesList.size(); a++ ) { + if ( popcap > 0 && popcount[race] >= popcap ) + break; + vector compatibles; + df::coord pos1 = units[femalesList[a]]->pos; + + if ( males.find(i->first) == males.end() ) + continue; + + vector& malesList = males[i->first]; + for ( size_t b = 0; b < malesList.size(); b++ ) { + df::coord pos2 = units[malesList[b]]->pos; + if ( Maps::canWalkBetween(pos1,pos2) ) + compatibles.push_back(malesList[b]); + } + if ( compatibles.empty() ) + continue; + + size_t maleIndex = (size_t)(compatibles.size()*((float)rand() / (1+(float)RAND_MAX))); + if ( impregnate(units[femalesList[a]], units[compatibles[maleIndex]]) ) + popcount[race]++; + } + } +} + +bool impregnate(df::unit* female, df::unit* male) { + if ( !female || !male ) + return false; + if ( female->relations.pregnancy_genes ) + return false; + + df::unit_genes* preg = new df::unit_genes; + *preg = male->appearance.genes; + female->relations.pregnancy_genes = preg; + female->relations.pregnancy_timer = pregtime; //300000 for dwarves + female->relations.pregnancy_caste = male->caste; + return true; +} + +void tickHandler(color_ostream& out, void* data) { + if ( !is_enabled ) + return; + CoreSuspender suspend; + impregnateMany(); + + EventManager::unregisterAll(plugin_self); + EventManager::EventHandler handle(tickHandler, howOften); + EventManager::registerTick(handle, howOften, plugin_self); +} + +command_result petcapRemover (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + + for ( size_t a = 0; a < parameters.size(); a++ ) { + if ( parameters[a] == "every" ) { + if ( a+1 >= parameters.size() ) + return CR_WRONG_USAGE; + int32_t old = howOften; + howOften = atoi(parameters[a+1].c_str()); + if (howOften < -1) { + howOften = old; + return CR_WRONG_USAGE; + } + a++; + continue; + } else if ( parameters[a] == "cap" ) { + if ( a+1 >= parameters.size() ) + return CR_WRONG_USAGE; + int32_t old = popcap; + popcap = atoi(parameters[a+1].c_str()); + if ( popcap < 0 ) { + popcap = old; + return CR_WRONG_USAGE; + } + a++; + continue; + } else if ( parameters[a] == "pregtime" ) { + if ( a+1 >= parameters.size() ) + return CR_WRONG_USAGE; + int32_t old = pregtime; + pregtime = atoi(parameters[a+1].c_str()); + if ( pregtime <= 0 ) { + pregtime = old; + return CR_WRONG_USAGE; + } + a++; + continue; + } + out.print("%s, line %d: invalid argument: %s\n", __FILE__, __LINE__, parameters[a].c_str()); + return CR_WRONG_USAGE; + } + + if ( howOften < 0 ) { + is_enabled = false; + return CR_OK; + } + + is_enabled = true; + EventManager::unregisterAll(plugin_self); + EventManager::EventHandler handle(tickHandler, howOften); + EventManager::registerTick(handle, howOften, plugin_self); + out.print("petcapRemover: howOften = every %d ticks, popcap per species = %d, preg time = %d ticks.\n", howOften, popcap, pregtime); + + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + if (enable != is_enabled) + { + is_enabled = enable; + if ( !is_enabled ) { + EventManager::unregisterAll(plugin_self); + } + } + + return CR_OK; +} +