From 0aad4ad4778ff3d3b22dce37b92393b2af74e332 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 7 Jun 2026 01:54:56 -0500 Subject: [PATCH 1/3] `timestream`: no skips during flows or caravan load/unload fixes #5811 fixes #5669 --- docs/changelog.txt | 1 + plugins/timestream.cpp | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 70e55aff58b..600bfc07060 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -59,6 +59,7 @@ Template for new versions: ## New Features ## Fixes +- `timestream`: will not skip ticks whenever a flow is active or when a caravan is loading or unloading ## Misc Improvements diff --git a/plugins/timestream.cpp b/plugins/timestream.cpp index deb6c45e4df..749d51849d7 100644 --- a/plugins/timestream.cpp +++ b/plugins/timestream.cpp @@ -33,8 +33,10 @@ #include "df/building_nest_boxst.h" #include "df/building_trapst.h" #include "df/buildingitemst.h" +#include "df/caravan_state.h" #include "df/init.h" #include "df/item_eggst.h" +#include "df/plotinfost.h" #include "df/unit.h" #include "df/world.h" @@ -52,6 +54,8 @@ REQUIRE_GLOBAL(cur_year_tick); REQUIRE_GLOBAL(cur_year_tick_advmode); REQUIRE_GLOBAL(init); REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(flows); +REQUIRE_GLOBAL(plotinfo); namespace DFHack { // for configuration-related logging @@ -314,9 +318,38 @@ static int32_t clamp_coverage(int32_t timeskip) { return timeskip; } +static bool detect_flows() +{ + return df::global::flows->size() > 0; +} + +static bool detect_caravans() +{ + auto& caravans = df::global::plotinfo->caravans; + return std::any_of(caravans.begin(), caravans.end(), [](auto caravan) { + if (caravan->trade_state != df::caravan_state::T_trade_state::AtDepot) + return false; + auto car_civ = caravan->entity; + auto& units = world->units.active; + return std::any_of(units.begin(), units.end(), [car_civ] (auto un) { + return (un->civ_id == car_civ + && DFHack::Units::isMerchant(un) + && std::any_of(un->inventory.begin(), un->inventory.end(), [] (auto inv_item) { + return inv_item->item && inv_item->item->flags.bits.trader; + })); + }); + }); +} + static int32_t clamp_timeskip(int32_t timeskip) { if (timeskip <= 0) return 0; + // timeskip cannot be applied when flows are active, since they are updated every tick and we can't predict them well enough to batch updates + if (detect_flows()) + return 0; + // timeskip cannot be applied when caravans are loading/unloading because we don't know how to jog that timer + if (detect_caravans()) + return 0; int32_t next_tick = *cur_year_tick + 1; timeskip = std::min(timeskip, get_next_trigger_year_tick(next_tick) - next_tick); timeskip = std::min(timeskip, get_next_birthday(next_tick) - next_tick); From c6f61e3880cd567305e1dfdec63ba3900ab74e0e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 7 Jun 2026 14:55:20 -0500 Subject: [PATCH 2/3] better flow tick handling Instead of just giving up when flows are present, figure out when the next flow update will occur and allow skipping up to that tick h/t to @cokernel for help in getting the tick-skipping math right, and @quietust for reverse engineering the flow update code Co-Authored-By: Quietust <1005195+quietust@users.noreply.github.com> Co-Authored-By: MLE Slone --- plugins/timestream.cpp | 64 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/plugins/timestream.cpp b/plugins/timestream.cpp index 749d51849d7..d7dca161cb7 100644 --- a/plugins/timestream.cpp +++ b/plugins/timestream.cpp @@ -34,6 +34,8 @@ #include "df/building_trapst.h" #include "df/buildingitemst.h" #include "df/caravan_state.h" +#include "df/flow_info.h" +#include "df/flow_type.h" #include "df/init.h" #include "df/item_eggst.h" #include "df/plotinfost.h" @@ -41,6 +43,8 @@ #include "df/world.h" #include +#include +#include using std::string; using std::vector; @@ -318,9 +322,61 @@ static int32_t clamp_coverage(int32_t timeskip) { return timeskip; } -static bool detect_flows() +using df_tick_type = std::remove_reference_t; + +static df_tick_type flow_next_required_tick(int flow_index) { - return df::global::flows->size() > 0; + using namespace df::enums::flow_type; + + const auto flow = (*flows)[flow_index]; + + if (flow == nullptr || flow->flags.bits.DEAD) + return std::numeric_limits::max(); + + const auto cur_tick = *cur_year_tick; + + struct update_parameters_t { + int speed; + int cycle; + }; + + const auto [speed, cycle] = [flow] () -> update_parameters_t { + switch (flow->type) + { + case ItemCloud: + case MaterialDust: + case MaterialGas: + case MaterialVapor: + if (flow->flags.bits.CREEPING) + return update_parameters_t{10, 100}; + else + return update_parameters_t{1, 5}; + case Dragonfire: + case Fire: + case Web: + return update_parameters_t{1, 3}; + default: + return update_parameters_t{10, 100}; + } + }(); + + const int stride = cycle / speed; + + const int phase = (flow_index % stride) * speed; + + const int cur_phase = cur_tick % cycle; + + return cur_tick + (phase - cur_phase) + ((cur_phase > phase) ? cycle : 0); +} + +static df_tick_type flows_next_required_tick() { + if (flows == nullptr || flows->empty()) + return std::numeric_limits::max(); + + auto flow_indices = std::views::iota(0, (int)flows->size()); + auto next_flow = std::ranges::min(flow_indices, {}, flow_next_required_tick); + + return flow_next_required_tick(next_flow); } static bool detect_caravans() @@ -344,15 +400,13 @@ static bool detect_caravans() static int32_t clamp_timeskip(int32_t timeskip) { if (timeskip <= 0) return 0; - // timeskip cannot be applied when flows are active, since they are updated every tick and we can't predict them well enough to batch updates - if (detect_flows()) - return 0; // timeskip cannot be applied when caravans are loading/unloading because we don't know how to jog that timer if (detect_caravans()) return 0; int32_t next_tick = *cur_year_tick + 1; timeskip = std::min(timeskip, get_next_trigger_year_tick(next_tick) - next_tick); timeskip = std::min(timeskip, get_next_birthday(next_tick) - next_tick); + timeskip = std::min(timeskip, flows_next_required_tick() - next_tick); return clamp_coverage(timeskip); } From d1f64b37d35e2dee0821571c57011122a1b03f2c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 7 Jun 2026 17:32:50 -0500 Subject: [PATCH 3/3] amend changelog --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 600bfc07060..16fd09ee99a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -59,7 +59,7 @@ Template for new versions: ## New Features ## Fixes -- `timestream`: will not skip ticks whenever a flow is active or when a caravan is loading or unloading +- `timestream`: do not skip ticks when a caravan is loading or unloading, and be more careful about skipping ticks when flows are active ## Misc Improvements