Skip to content

Commit 547ebc8

Browse files
committed
Merge branch 'develop' into 5012-beta
2 parents 5e4bdd0 + 6053669 commit 547ebc8

File tree

9 files changed

+274
-227
lines changed

9 files changed

+274
-227
lines changed

docs/about/Removed.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ mousequery
229229
==========
230230
Functionality superseded by vanilla v50 interface.
231231

232+
.. _petcapRemover:
233+
234+
petcapRemover
235+
=============
236+
Renamed to `pet-uncapper`.
237+
232238
.. _resume:
233239

234240
resume

docs/changelog.txt

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,39 @@ Template for new versions:
5151

5252
# Future
5353

54+
## New Tools
55+
56+
## New Features
57+
58+
## Fixes
59+
60+
## Misc Improvements
61+
62+
## Documentation
63+
64+
## API
65+
66+
## Lua
67+
68+
## Removed
69+
70+
# 50.12-r1
71+
72+
## Removed
73+
- `burrow`: removed overlay 3D box select since it is now provided by the vanilla UI
74+
75+
# 50.11-r7
76+
5477
## New Tools
5578
- `tweak`: (reinstated) a collection of small bugfixes and gameplay tweaks
79+
- `pet-uncapper`: (reinstated, renamed from ``petcapRemover``) allow pets to breed beyond the default population cap of 50
5680

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

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

79-
## Documentation
80-
81104
## API
82-
- Gui module Announcement functions now use DF's new announcement alert system, popups parsed into ``markup_text_boxst``
105+
- Gui module Announcement functions now use DF's new announcement alert system
83106
- ``Gui::addCombatReport``, ``Gui::addCombatReportAuto``: add versions that take ``report *`` instead of report vector index
84107
- ``Gui::MTB_clean``, ``Gui::MTB_parse``, ``Gui::MTB_set_width``: new functions for manipulating ``markup_text_boxst``
85108
- ``toupper_cp437(char)``, ``tolower_cp437(char)``: new ``MiscUtils`` functions, return a char with case changed, respecting CP437
@@ -91,10 +114,7 @@ Template for new versions:
91114
- Overlay framework now respects ``active`` and ``visible`` widget attributes
92115
- ``dfhack.gui`` announcement functions use default arguments when omitted
93116
- ``dfhack.units.getCitizens`` now only returns units that are on the map
94-
- ``dfhack.upperCp437(string)``, ``dfhack.lowerCp437(string)``: new functions, return string with all chars changed, respecting CP437
95-
96-
## Removed
97-
- `burrow`: removed overlay 3D box select since it is now provided by the vanilla UI
117+
- ``dfhack.upperCp437(string)``, ``dfhack.lowerCp437(string)``: new functions, return string with all chars changed, respecting CP437 code page
98118

99119
# 50.11-r6
100120

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
petcapRemover
2-
=============
1+
pet-uncapper
2+
============
33

44
.. dfhack-tool::
55
:summary: Modify the pet population cap.
6-
:tags: unavailable
6+
:tags: fort gameplay animals
77

88
In vanilla DF, pets will not reproduce unless the population is below 50 and the
99
number of children of that species is below a certain percentage. This plugin
@@ -16,18 +16,20 @@ cap. It can still go over, but only in the case of multiple births.
1616
Usage
1717
-----
1818

19-
``enable petcapRemover``
19+
``enable pet-uncapper``
2020
Enables the plugin and starts running with default settings.
21-
``petcapRemover cap <value>``
21+
``pet-uncapper [status]``
22+
Print out current settings.
23+
``pet-uncapper now``
24+
Impregnate adult female pets that have access to a compatible male, up to
25+
the population cap.
26+
``pet-uncapper cap <value>``
2227
Set the new population cap per species to the specified value. If set to 0,
2328
then there is no cap (good luck with all those animals!). The default cap
2429
is 100.
25-
``petcapRemover``
26-
Impregnate female pets that have access to a compatible male, up to the
27-
population cap.
28-
``petcapRemover every <ticks>``
30+
``pet-uncapper every <ticks>``
2931
Set how often the plugin will cause pregnancies. The default frequency is
3032
every 10,000 ticks (a little over 8 game days).
31-
``petcapRemover pregtime <ticks>``
33+
``pet-uncapper pregtime <ticks>``
3234
Sets the pregnancy duration to the specified number of ticks. The default
3335
value is 200,000 ticks, which is the natural pet pregnancy duration.

library/xml

Submodule xml updated 1 file

plugins/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ if(BUILD_SUPPORTED)
139139
dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static lua)
140140
dfhack_plugin(overlay overlay.cpp LINK_LIBRARIES lua)
141141
dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua)
142-
#dfhack_plugin(petcapRemover petcapRemover.cpp)
142+
dfhack_plugin(pet-uncapper pet-uncapper.cpp)
143143
#dfhack_plugin(plants plants.cpp)
144144
dfhack_plugin(preserve-tombs preserve-tombs.cpp)
145145
dfhack_plugin(probe probe.cpp LINK_LIBRARIES lua)

plugins/pathable.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ static bool is_damp(const df::coord &pos) {
158158
will_leak(pos.x+1, pos.y, pos.z) ||
159159
will_leak(pos.x-1, pos.y+1, pos.z) ||
160160
will_leak(pos.x, pos.y+1, pos.z) ||
161-
will_leak(pos.x+1, pos.y+1, pos.z);
162-
will_leak(pos.x, pos.y+1, pos.z+1);
161+
will_leak(pos.x+1, pos.y+1, pos.z) ||
162+
will_leak(pos.x, pos.y, pos.z+1);
163163
}
164164

165165
static void paintScreenWarmDamp(bool show_hidden = false) {

plugins/pet-uncapper.cpp

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#include "Debug.h"
2+
#include "PluginManager.h"
3+
4+
#include "modules/Maps.h"
5+
#include "modules/Units.h"
6+
#include "modules/World.h"
7+
8+
#include "df/unit.h"
9+
#include "df/world.h"
10+
11+
#include <random>
12+
13+
using namespace DFHack;
14+
using std::map;
15+
using std::string;
16+
using std::vector;
17+
18+
DFHACK_PLUGIN("pet-uncapper");
19+
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
20+
21+
REQUIRE_GLOBAL(world);
22+
23+
namespace DFHack {
24+
// for configuration-related logging
25+
DBG_DECLARE(petuncapper, control, DebugCategory::LINFO);
26+
// for logging during the periodic scan
27+
DBG_DECLARE(petuncapper, cycle, DebugCategory::LINFO);
28+
}
29+
30+
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
31+
32+
static const string CONFIG_KEY = string(plugin_name) + "/config";
33+
static PersistentDataItem config;
34+
35+
enum ConfigValues {
36+
CONFIG_IS_ENABLED = 0,
37+
CONFIG_FREQ = 1,
38+
CONFIG_POP_CAP = 2,
39+
CONFIG_PREG_TIME = 3,
40+
};
41+
42+
bool impregnate(df::unit* female, df::unit* male) {
43+
if (!female || !male)
44+
return false;
45+
if (female->pregnancy_genes)
46+
return false;
47+
48+
df::unit_genes* preg = new df::unit_genes;
49+
*preg = male->appearance.genes;
50+
female->pregnancy_genes = preg;
51+
female->pregnancy_timer = config.get_int(CONFIG_PREG_TIME);
52+
female->pregnancy_caste = male->caste;
53+
return true;
54+
}
55+
56+
void impregnateMany(color_ostream &out, bool verbose = false) {
57+
// mark that we have recently run
58+
cycle_timestamp = world->frame_counter;
59+
60+
map<int32_t, vector<df::unit *>> males;
61+
map<int32_t, vector<df::unit *>> females;
62+
map<int32_t, int32_t> popcount;
63+
64+
std::random_device seed;
65+
std::mt19937 gen{seed()};
66+
67+
const int popcap = config.get_int(CONFIG_POP_CAP);
68+
int pregnancies = 0;
69+
70+
for (auto unit : world->units.active) {
71+
// not restricted to fort pets since merchant/wild animals can participate
72+
if (!Units::isActive(unit) || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor)
73+
continue;
74+
popcount[unit->race]++;
75+
if (unit->pregnancy_genes) {
76+
// already pregnant -- if remaining time is less than the current setting, speed it up
77+
if ((int)unit->pregnancy_timer > config.get_int(CONFIG_PREG_TIME))
78+
unit->pregnancy_timer = config.get_int(CONFIG_PREG_TIME);
79+
// for player convenience and population stability, count the fetus toward the population cap
80+
popcount[unit->race]++;
81+
continue;
82+
}
83+
if (unit->flags1.bits.caged)
84+
continue;
85+
// must have PET or PET_EXOTIC
86+
if (!Units::isTamable(unit))
87+
continue;
88+
// check for adulthood
89+
if (Units::isBaby(unit) || Units::isChild(unit))
90+
continue;
91+
if (Units::isMale(unit))
92+
males[unit->race].push_back(unit);
93+
else if (Units::isFemale(unit))
94+
females[unit->race].push_back(unit);
95+
}
96+
97+
for (auto [race, femalesList] : females) {
98+
if (!males.contains(race))
99+
continue;
100+
101+
for (auto female : femalesList) {
102+
if (popcap > 0 && popcount[race] >= popcap)
103+
break;
104+
105+
vector<df::unit *> compatibles;
106+
for (auto male : males[race]) {
107+
if (Maps::canWalkBetween(female->pos, male->pos) )
108+
compatibles.push_back(male);
109+
}
110+
if (compatibles.empty())
111+
continue;
112+
113+
std::uniform_int_distribution<> dist{0, (int)compatibles.size() - 1};
114+
if (impregnate(female, compatibles[dist(gen)])) {
115+
pregnancies++;
116+
popcount[race]++;
117+
}
118+
}
119+
}
120+
121+
if (pregnancies || verbose) {
122+
INFO(cycle, out).print("%d pet pregnanc%s initiated\n",
123+
pregnancies, pregnancies == 1 ? "y" : "ies");
124+
}
125+
}
126+
127+
command_result do_command(color_ostream &out, vector<string> & parameters) {
128+
CoreSuspender suspend;
129+
130+
if (!Core::getInstance().isMapLoaded() || !World::IsSiteLoaded()) {
131+
out.printerr("Cannot run %s without a loaded fort.\n", plugin_name);
132+
return CR_FAILURE;
133+
}
134+
135+
if (parameters.size() == 0 || parameters[0] == "status") {
136+
out.print("%s is %s\n\n", plugin_name, is_enabled ? "enabled" : "not enabled");
137+
out.print("population cap per species: %d\n", config.get_int(CONFIG_POP_CAP));
138+
out.print("updating pregnancies every %d ticks\n", config.get_int(CONFIG_FREQ));
139+
out.print("pregancies last %d ticks\n", config.get_int(CONFIG_PREG_TIME));
140+
} else if (parameters[0] == "now") {
141+
impregnateMany(out, true);
142+
} else {
143+
if (parameters.size() < 2)
144+
return CR_WRONG_USAGE;
145+
string command = parameters[0];
146+
int val = std::max(0, string_to_int(parameters[1]));
147+
if (command == "cap")
148+
config.set_int(CONFIG_POP_CAP, val);
149+
else if (command == "every")
150+
config.set_int(CONFIG_FREQ, val);
151+
else if (command == "pregtime")
152+
config.set_int(CONFIG_PREG_TIME, val);
153+
else
154+
return CR_WRONG_USAGE;
155+
}
156+
157+
return CR_OK;
158+
}
159+
160+
DFhackCExport command_result plugin_init(color_ostream &out, vector<PluginCommand> &commands) {
161+
commands.push_back(PluginCommand(
162+
"pet-uncapper",
163+
"Modify the pet population cap.",
164+
do_command));
165+
return CR_OK;
166+
}
167+
168+
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
169+
if (!Core::getInstance().isMapLoaded() || !World::IsSiteLoaded()) {
170+
out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name);
171+
return CR_FAILURE;
172+
}
173+
174+
if (enable != is_enabled) {
175+
is_enabled = enable;
176+
DEBUG(control,out).print("%s from the API; persisting\n",
177+
is_enabled ? "enabled" : "disabled");
178+
config.set_bool(CONFIG_IS_ENABLED, is_enabled);
179+
if (enable)
180+
impregnateMany(out);
181+
} else {
182+
DEBUG(control,out).print("%s from the API, but already %s; no action\n",
183+
is_enabled ? "enabled" : "disabled",
184+
is_enabled ? "enabled" : "disabled");
185+
}
186+
return CR_OK;
187+
}
188+
189+
DFhackCExport command_result plugin_load_site_data (color_ostream &out) {
190+
cycle_timestamp = 0;
191+
192+
config = World::GetPersistentSiteData(CONFIG_KEY);
193+
194+
if (!config.isValid()) {
195+
DEBUG(control,out).print("no config found in this save; initializing\n");
196+
config = World::AddPersistentSiteData(CONFIG_KEY);
197+
config.set_bool(CONFIG_IS_ENABLED, is_enabled);
198+
config.set_int(CONFIG_FREQ, 10000);
199+
config.set_int(CONFIG_POP_CAP, 100);
200+
config.set_int(CONFIG_PREG_TIME, 200000);
201+
}
202+
203+
is_enabled = config.get_bool(CONFIG_IS_ENABLED);
204+
DEBUG(control,out).print("loading persisted enabled state: %s\n",
205+
is_enabled ? "true" : "false");
206+
return CR_OK;
207+
}
208+
209+
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
210+
if (event == DFHack::SC_WORLD_UNLOADED) {
211+
if (is_enabled) {
212+
DEBUG(control,out).print("world unloaded; disabling %s\n",
213+
plugin_name);
214+
is_enabled = false;
215+
}
216+
}
217+
return CR_OK;
218+
}
219+
220+
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
221+
if (world->frame_counter - cycle_timestamp >= config.get_int(CONFIG_FREQ))
222+
impregnateMany(out);
223+
return CR_OK;
224+
}

0 commit comments

Comments
 (0)