diff --git a/docs/changelog.txt b/docs/changelog.txt index 0bd39e0cbd..b94cdce955 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -57,6 +57,8 @@ Template for new versions: ## Fixes - `getplants`: will no longer crash when faced with plants with growths that do not drop seeds when processed +- `getplants`: use updated formula for calculating whether plant growths are ripe +- `getplants`: fix logic for determining whether plant growths have been picked - `gui/teleport`: adapt to new behavior in DF 51.11 to avoid a crash when teleporting items into mid-air - `script-manager`: fix lua scripts in mods not being reloaded properly upon entering a saved world on Windows @@ -66,6 +68,7 @@ Template for new versions: ## Documentation ## API +- ``Random`` module: added ``SplitmixRNG`` class, implements the Splitmix64 RNG used by Dwarf Fortress for "simple" randomness - ``Items::getDescription``: fixed display of quality levels, now displays ALL item designations (in correct order) and obeys vanilla SHOW_IMP_QUALITY setting ## Lua diff --git a/library/include/modules/Random.h b/library/include/modules/Random.h index 38fbdd0f89..8ed703be79 100644 --- a/library/include/modules/Random.h +++ b/library/include/modules/Random.h @@ -106,6 +106,37 @@ namespace Random extern template void DFHACK_IMPORT MersenneRNG::unitvector(double *p, int size); #endif + // Standard Splitmix64 RNG, as used by Dwarf Fortress's "hash_rngst" class + class SplitmixRNG + { + uint64_t state; + + public: + SplitmixRNG(uint64_t seed) { + init(seed); + } + + void init(uint64_t seed) { + state = seed; + } + + uint64_t next() { + state += 0x9e3779b97f4a7c15; + uint64_t z = state; + z ^= z >> 30; + z *= 0xbf58476d1ce4e5b9; + z ^= z >> 27; + z *= 0x94d049bb133111eb; + z ^= z >> 31; + return z; + } + + int32_t df_trandom(uint32_t max) { + uint32_t val = next() >> 32; + return (int32_t)(val % max); + } + }; + /* * Classical Perlin noise function in template form. * http://mrl.nyu.edu/~perlin/doc/oscar.html#noise diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index ab8333e51a..29c6655fc9 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -5,6 +5,7 @@ #include "modules/Designations.h" #include "modules/Maps.h" #include "modules/Materials.h" +#include "modules/Random.h" #include "df/map_block.h" #include "df/map_block_column.h" @@ -211,32 +212,31 @@ selectability selectablePlant(color_ostream& out, const df::plant_raw* plant, bo } // Formula for determination of the variance in plant growth maturation time, determined via disassembly. -// The x and y parameters are in tiles relative to the embark. -bool ripe(int32_t x, int32_t y, int32_t start, int32_t end) { - int32_t time = (((435522653 - (((y + 3) * x + 5) * ((y + 7) * y * 400181475 + 289700012))) & 0x3FFFFFFF) % 2000 + *cur_year_tick) % 403200; +// The coordinates are relative to the embark region. +bool ripe(int32_t x, int32_t y, int32_t z, int32_t start, int32_t end) { + DFHack::Random::SplitmixRNG rng((world->map.region_x * 48 + x) + (world->map.region_y * 48 + y) * 10000 + (world->map.region_z + z) * 100000000); + int32_t time = (rng.df_trandom(2000) + *cur_year_tick) % 403200; return time >= start && (end == -1 || time <= end); } -// Looks in the picked growths vector to see if a matching growth has been marked as picked. -bool picked(const df::plant* plant, int32_t growth_subtype) { - df::world_data* world_data = world->world_data; - df::world_site* site = df::world_site::find(plotinfo->site_id); - int32_t pos_x = site->global_min_x + plant->pos.x / 48; - int32_t pos_y = site->global_min_y + plant->pos.y / 48; - size_t id = pos_x + pos_y * 16 * world_data->world_width; - df::world_object_data* object_data = df::world_object_data::find(id); - if (!object_data) { +// Looks in the local creation zone's picked growths vector to see if a matching growth has been marked as picked. +bool picked(const df::plant* plant, int32_t growth_subtype, int32_t growth_density) { + int32_t pos_x = plant->pos.x / 48 + world->map.region_x; + int32_t pos_y = plant->pos.y / 48 + world->map.region_y; + size_t cz_id = pos_x + pos_y * 16 * world->world_data->world_width; + auto cz = df::world_object_data::find(cz_id); + if (!cz) { return false; } - df::map_block_column* column = world->map.map_block_columns[(plant->pos.x / 16) * world->map.x_count_block + (plant->pos.y / 16)]; - - for (size_t i = 0; i < object_data->picked_growths.x.size(); i++) { - if (object_data->picked_growths.x[i] == plant->pos.x && - object_data->picked_growths.y[i] == plant->pos.y && - object_data->picked_growths.z[i] - column->z_base == plant->pos.z && - object_data->picked_growths.subtype[i] == growth_subtype && - object_data->picked_growths.year[i] == *cur_year) { + + for (size_t i = 0; i < cz->picked_growths.x.size(); i++) { + if (cz->picked_growths.x[i] == (plant->pos.x % 48) && + cz->picked_growths.y[i] == (plant->pos.y % 48) && + cz->picked_growths.z[i] == (plant->pos.z + world->map.region_z) && + cz->picked_growths.density[i] >= growth_density && + cz->picked_growths.subtype[i] == growth_subtype && + cz->picked_growths.year[i] == *cur_year) { return true; } } @@ -310,8 +310,8 @@ bool designate(color_ostream& out, const df::plant* plant, bool farming) { } if ((!farming || seedSource) && - ripe(plant->pos.x, plant->pos.y, plant_raw->growths[i]->timing_1, plant_raw->growths[i]->timing_2) && - !picked(plant, i)) + ripe(plant->pos.x, plant->pos.y, plant->pos.z, plant_raw->growths[i]->timing_1, plant_raw->growths[i]->timing_2) && + !picked(plant, i, plant_raw->growths[i]->density)) return Designations::markPlant(plant); }