Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
87 changes: 87 additions & 0 deletions plugins/timestream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@
#include "df/building_nest_boxst.h"
#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"
#include "df/unit.h"
#include "df/world.h"

#include <array>
#include <limits>
#include <type_traits>

using std::string;
using std::vector;
Expand All @@ -52,6 +58,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
Expand Down Expand Up @@ -314,12 +322,91 @@ static int32_t clamp_coverage(int32_t timeskip) {
return timeskip;
}

using df_tick_type = std::remove_reference_t<decltype(*cur_year_tick)>;

static df_tick_type flow_next_required_tick(int flow_index)
{
using namespace df::enums::flow_type;

const auto flow = (*flows)[flow_index];

if (flow == nullptr || flow->flags.bits.DEAD)
return std::numeric_limits<df_tick_type>::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<df_tick_type>::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()
{
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 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);
}

Expand Down
Loading