Skip to content

Commit

Permalink
Merge branch 'develop' into 5012-beta
Browse files Browse the repository at this point in the history
  • Loading branch information
myk002 committed Mar 3, 2024
2 parents 5e4bdd0 + 6053669 commit 547ebc8
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 227 deletions.
6 changes: 6 additions & 0 deletions docs/about/Removed.rst
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ mousequery
==========
Functionality superseded by vanilla v50 interface.

.. _petcapRemover:

petcapRemover
=============
Renamed to `pet-uncapper`.

.. _resume:

resume
Expand Down
34 changes: 27 additions & 7 deletions docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,39 @@ Template for new versions:

# Future

## New Tools

## New Features

## Fixes

## Misc Improvements

## Documentation

## API

## Lua

## Removed

# 50.12-r1

## Removed
- `burrow`: removed overlay 3D box select since it is now provided by the vanilla UI

# 50.11-r7

## New Tools
- `tweak`: (reinstated) a collection of small bugfixes and gameplay tweaks
- `pet-uncapper`: (reinstated, renamed from ``petcapRemover``) allow pets to breed beyond the default population cap of 50

## New Features
- `cleanowned`: Add a "nodump" option to allow for confiscating items without dumping
- `tweak`: Add "flask-contents", makes flasks/vials/waterskins be named according to their contents

## Fixes
- `dig`: overlay that shows damp designations in ASCII mode now propertly highlights tiles that are damp because of an aquifer in the layer above
- `dig-now`: fix digging stairs in the surface sometimes creating underworld gates.
- ``Units::getVisibleName``: don't reveal the true identities of units that are impersonating other historical figures
- ``Gui::revealInDwarfmodeMap``: properly center the zoom even when the target tile is near the edge of the map
Expand All @@ -76,10 +101,8 @@ Template for new versions:
- `zone`: animal assignment dialog now shows distance to pasture/cage and allows sorting by distance
- `zone`: animal assignment dialog shows number of creatures assigned to this pasture/cage/etc.

## Documentation

## API
- Gui module Announcement functions now use DF's new announcement alert system, popups parsed into ``markup_text_boxst``
- Gui module Announcement functions now use DF's new announcement alert system
- ``Gui::addCombatReport``, ``Gui::addCombatReportAuto``: add versions that take ``report *`` instead of report vector index
- ``Gui::MTB_clean``, ``Gui::MTB_parse``, ``Gui::MTB_set_width``: new functions for manipulating ``markup_text_boxst``
- ``toupper_cp437(char)``, ``tolower_cp437(char)``: new ``MiscUtils`` functions, return a char with case changed, respecting CP437
Expand All @@ -91,10 +114,7 @@ Template for new versions:
- Overlay framework now respects ``active`` and ``visible`` widget attributes
- ``dfhack.gui`` announcement functions use default arguments when omitted
- ``dfhack.units.getCitizens`` now only returns units that are on the map
- ``dfhack.upperCp437(string)``, ``dfhack.lowerCp437(string)``: new functions, return string with all chars changed, respecting CP437

## Removed
- `burrow`: removed overlay 3D box select since it is now provided by the vanilla UI
- ``dfhack.upperCp437(string)``, ``dfhack.lowerCp437(string)``: new functions, return string with all chars changed, respecting CP437 code page

# 50.11-r6

Expand Down
22 changes: 12 additions & 10 deletions docs/plugins/petcapRemover.rst → docs/plugins/pet-uncapper.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
petcapRemover
=============
pet-uncapper
============

.. dfhack-tool::
:summary: Modify the pet population cap.
:tags: unavailable
:tags: fort gameplay animals

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
Expand All @@ -16,18 +16,20 @@ cap. It can still go over, but only in the case of multiple births.
Usage
-----

``enable petcapRemover``
``enable pet-uncapper``
Enables the plugin and starts running with default settings.
``petcapRemover cap <value>``
``pet-uncapper [status]``
Print out current settings.
``pet-uncapper now``
Impregnate adult female pets that have access to a compatible male, up to
the population cap.
``pet-uncapper cap <value>``
Set the new population cap per species to the specified value. If set to 0,
then there is no cap (good luck with all those animals!). The default cap
is 100.
``petcapRemover``
Impregnate female pets that have access to a compatible male, up to the
population cap.
``petcapRemover every <ticks>``
``pet-uncapper every <ticks>``
Set how often the plugin will cause pregnancies. The default frequency is
every 10,000 ticks (a little over 8 game days).
``petcapRemover pregtime <ticks>``
``pet-uncapper pregtime <ticks>``
Sets the pregnancy duration to the specified number of ticks. The default
value is 200,000 ticks, which is the natural pet pregnancy duration.
2 changes: 1 addition & 1 deletion library/xml
Submodule xml updated 1 files
+6 −0 changelog.txt
2 changes: 1 addition & 1 deletion plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ if(BUILD_SUPPORTED)
dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static lua)
dfhack_plugin(overlay overlay.cpp LINK_LIBRARIES lua)
dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua)
#dfhack_plugin(petcapRemover petcapRemover.cpp)
dfhack_plugin(pet-uncapper pet-uncapper.cpp)
#dfhack_plugin(plants plants.cpp)
dfhack_plugin(preserve-tombs preserve-tombs.cpp)
dfhack_plugin(probe probe.cpp LINK_LIBRARIES lua)
Expand Down
4 changes: 2 additions & 2 deletions plugins/pathable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ static bool is_damp(const df::coord &pos) {
will_leak(pos.x+1, pos.y, pos.z) ||
will_leak(pos.x-1, pos.y+1, pos.z) ||
will_leak(pos.x, pos.y+1, pos.z) ||
will_leak(pos.x+1, pos.y+1, pos.z);
will_leak(pos.x, pos.y+1, pos.z+1);
will_leak(pos.x+1, pos.y+1, pos.z) ||
will_leak(pos.x, pos.y, pos.z+1);
}

static void paintScreenWarmDamp(bool show_hidden = false) {
Expand Down
224 changes: 224 additions & 0 deletions plugins/pet-uncapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#include "Debug.h"
#include "PluginManager.h"

#include "modules/Maps.h"
#include "modules/Units.h"
#include "modules/World.h"

#include "df/unit.h"
#include "df/world.h"

#include <random>

using namespace DFHack;
using std::map;
using std::string;
using std::vector;

DFHACK_PLUGIN("pet-uncapper");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);

REQUIRE_GLOBAL(world);

namespace DFHack {
// for configuration-related logging
DBG_DECLARE(petuncapper, control, DebugCategory::LINFO);
// for logging during the periodic scan
DBG_DECLARE(petuncapper, cycle, DebugCategory::LINFO);
}

static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle

static const string CONFIG_KEY = string(plugin_name) + "/config";
static PersistentDataItem config;

enum ConfigValues {
CONFIG_IS_ENABLED = 0,
CONFIG_FREQ = 1,
CONFIG_POP_CAP = 2,
CONFIG_PREG_TIME = 3,
};

bool impregnate(df::unit* female, df::unit* male) {
if (!female || !male)
return false;
if (female->pregnancy_genes)
return false;

df::unit_genes* preg = new df::unit_genes;
*preg = male->appearance.genes;
female->pregnancy_genes = preg;
female->pregnancy_timer = config.get_int(CONFIG_PREG_TIME);
female->pregnancy_caste = male->caste;
return true;
}

void impregnateMany(color_ostream &out, bool verbose = false) {
// mark that we have recently run
cycle_timestamp = world->frame_counter;

map<int32_t, vector<df::unit *>> males;
map<int32_t, vector<df::unit *>> females;
map<int32_t, int32_t> popcount;

std::random_device seed;
std::mt19937 gen{seed()};

const int popcap = config.get_int(CONFIG_POP_CAP);
int pregnancies = 0;

for (auto unit : world->units.active) {
// not restricted to fort pets since merchant/wild animals can participate
if (!Units::isActive(unit) || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor)
continue;
popcount[unit->race]++;
if (unit->pregnancy_genes) {
// already pregnant -- if remaining time is less than the current setting, speed it up
if ((int)unit->pregnancy_timer > config.get_int(CONFIG_PREG_TIME))
unit->pregnancy_timer = config.get_int(CONFIG_PREG_TIME);
// for player convenience and population stability, count the fetus toward the population cap
popcount[unit->race]++;
continue;
}
if (unit->flags1.bits.caged)
continue;
// must have PET or PET_EXOTIC
if (!Units::isTamable(unit))
continue;
// check for adulthood
if (Units::isBaby(unit) || Units::isChild(unit))
continue;
if (Units::isMale(unit))
males[unit->race].push_back(unit);
else if (Units::isFemale(unit))
females[unit->race].push_back(unit);
}

for (auto [race, femalesList] : females) {
if (!males.contains(race))
continue;

for (auto female : femalesList) {
if (popcap > 0 && popcount[race] >= popcap)
break;

vector<df::unit *> compatibles;
for (auto male : males[race]) {
if (Maps::canWalkBetween(female->pos, male->pos) )
compatibles.push_back(male);
}
if (compatibles.empty())
continue;

std::uniform_int_distribution<> dist{0, (int)compatibles.size() - 1};
if (impregnate(female, compatibles[dist(gen)])) {
pregnancies++;
popcount[race]++;
}
}
}

if (pregnancies || verbose) {
INFO(cycle, out).print("%d pet pregnanc%s initiated\n",
pregnancies, pregnancies == 1 ? "y" : "ies");
}
}

command_result do_command(color_ostream &out, vector<string> & parameters) {
CoreSuspender suspend;

if (!Core::getInstance().isMapLoaded() || !World::IsSiteLoaded()) {
out.printerr("Cannot run %s without a loaded fort.\n", plugin_name);
return CR_FAILURE;
}

if (parameters.size() == 0 || parameters[0] == "status") {
out.print("%s is %s\n\n", plugin_name, is_enabled ? "enabled" : "not enabled");
out.print("population cap per species: %d\n", config.get_int(CONFIG_POP_CAP));
out.print("updating pregnancies every %d ticks\n", config.get_int(CONFIG_FREQ));
out.print("pregancies last %d ticks\n", config.get_int(CONFIG_PREG_TIME));
} else if (parameters[0] == "now") {
impregnateMany(out, true);
} else {
if (parameters.size() < 2)
return CR_WRONG_USAGE;
string command = parameters[0];
int val = std::max(0, string_to_int(parameters[1]));
if (command == "cap")
config.set_int(CONFIG_POP_CAP, val);
else if (command == "every")
config.set_int(CONFIG_FREQ, val);
else if (command == "pregtime")
config.set_int(CONFIG_PREG_TIME, val);
else
return CR_WRONG_USAGE;
}

return CR_OK;
}

DFhackCExport command_result plugin_init(color_ostream &out, vector<PluginCommand> &commands) {
commands.push_back(PluginCommand(
"pet-uncapper",
"Modify the pet population cap.",
do_command));
return CR_OK;
}

DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
if (!Core::getInstance().isMapLoaded() || !World::IsSiteLoaded()) {
out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name);
return CR_FAILURE;
}

if (enable != is_enabled) {
is_enabled = enable;
DEBUG(control,out).print("%s from the API; persisting\n",
is_enabled ? "enabled" : "disabled");
config.set_bool(CONFIG_IS_ENABLED, is_enabled);
if (enable)
impregnateMany(out);
} else {
DEBUG(control,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_load_site_data (color_ostream &out) {
cycle_timestamp = 0;

config = World::GetPersistentSiteData(CONFIG_KEY);

if (!config.isValid()) {
DEBUG(control,out).print("no config found in this save; initializing\n");
config = World::AddPersistentSiteData(CONFIG_KEY);
config.set_bool(CONFIG_IS_ENABLED, is_enabled);
config.set_int(CONFIG_FREQ, 10000);
config.set_int(CONFIG_POP_CAP, 100);
config.set_int(CONFIG_PREG_TIME, 200000);
}

is_enabled = config.get_bool(CONFIG_IS_ENABLED);
DEBUG(control,out).print("loading persisted enabled state: %s\n",
is_enabled ? "true" : "false");
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(control,out).print("world unloaded; disabling %s\n",
plugin_name);
is_enabled = false;
}
}
return CR_OK;
}

DFhackCExport command_result plugin_onupdate(color_ostream &out) {
if (world->frame_counter - cycle_timestamp >= config.get_int(CONFIG_FREQ))
impregnateMany(out);
return CR_OK;
}
Loading

0 comments on commit 547ebc8

Please sign in to comment.