Implements plugin: spectate v1.0.1a

develop
Josh Cooper 2022-11-09 21:10:18 -08:00
parent 40cbe4fe88
commit b99e948b8a
2 changed files with 74 additions and 51 deletions

@ -46,5 +46,4 @@ Features
Settings Settings
-------- --------
:tick-threshold: Set the plugin's tick interval for changing the followed dwarf. :tick-threshold: Set the plugin's tick interval for changing the followed dwarf. (default: 1000)
Acts as a maximum follow time when used with focus-jobs enabled. (default: 1000)

@ -26,6 +26,7 @@
#include <map> #include <map>
#include <set> #include <set>
#include <array>
#include <random> #include <random>
#include <cinttypes> #include <cinttypes>
#include <functional> #include <functional>
@ -78,7 +79,7 @@ static PersistentDataItem pconfig;
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable); DFhackCExport command_result plugin_enable(color_ostream &out, bool enable);
command_result spectate (color_ostream &out, std::vector <std::string> & parameters); command_result spectate (color_ostream &out, std::vector <std::string> & parameters);
#define COORDARGS(id) id.x, (id).y, id.z #define COORDARGS(id) id.x, id.y, id.z
namespace SP { namespace SP {
bool following_dwarf = false; bool following_dwarf = false;
@ -209,68 +210,91 @@ namespace SP {
} }
return true; return true;
}; };
static auto calc_extra_weight = [](size_t idx, double r1, double r2) {
switch(idx) {
case 0:
return r2;
case 1:
return (r2-r1)/1.3;
case 2:
return (r2-r1)/2;
default:
return 0.0;
}
};
/// Collecting our choice pool
///////////////////////////////
std::array<int32_t, 10> ranges{};
std::array<bool, 5> range_exists{};
static auto build_range = [&](size_t idx){
size_t first = idx * 2;
size_t second = idx * 2 + 1;
size_t previous = first - 1;
// first we get the end of the range
ranges[second] = units.size() - 1;
// then we calculate whether this indicates there is a range or not
if (first != 0) {
range_exists[idx] = ranges[second] > ranges[previous];
} else {
range_exists[idx] = ranges[second] >= 0;
}
// lastly we set the start of the range
ranges[first] = ranges[previous] + (range_exists[idx] ? 1 : 0);
};
/// RANGE 0 (in view + working)
// grab valid working units
add_if([](df::unit* unit) {
return valid(unit) && Units::isCitizen(unit, true) && unit->job.current_job;
});
// keep only those in the box
Units::getUnitsInBox(units, COORDARGS(viewMin), COORDARGS(viewMax));
build_range(0);
/// RANGE 1 (in view) /// RANGE 1 (in view)
// grab all valid units
add_if(valid); add_if(valid);
// keep only those in the box
Units::getUnitsInBox(units, COORDARGS(viewMin), COORDARGS(viewMax)); Units::getUnitsInBox(units, COORDARGS(viewMin), COORDARGS(viewMax));
int32_t inview_idx2 = units.size()-1; build_range(1);
bool range1_exists = inview_idx2 >= 0;
int32_t inview_idx1 = range1_exists ? 0 : -1;
/// RANGE 2 (citizens) /// RANGE 2 (working citizens)
add_if([](df::unit* unit) {
return valid(unit) && Units::isCitizen(unit, true) && unit->job.current_job;
});
build_range(2);
/// RANGE 3 (citizens)
add_if([](df::unit* unit) { add_if([](df::unit* unit) {
return valid(unit) && Units::isCitizen(unit, true); return valid(unit) && Units::isCitizen(unit, true);
}); });
int32_t cit_idx2 = units.size()-1; build_range(3);
bool range2_exists = cit_idx2 > inview_idx2;
int32_t cit_idx1 = range2_exists ? inview_idx2+1 : cit_idx2;
/// RANGE 3 (any valid) /// RANGE 4 (any valid)
add_if(valid); add_if(valid);
int32_t all_idx2 = units.size()-1; build_range(4);
bool range3_exists = all_idx2 > cit_idx2;
int32_t all_idx1 = range3_exists ? cit_idx2+1 : all_idx2;
// selecting from our choice pool
if (!units.empty()) { if (!units.empty()) {
std::array<double, 5> bw{23,17,13,7,1}; // probability weights for each range
std::vector<double> i; std::vector<double> i;
std::vector<double> w; std::vector<double> w;
if (!range1_exists && !range2_exists && !range3_exists) { bool at_least_one = false;
return false; // in one word, elegance
} for(size_t idx = 0; idx < range_exists.size(); ++idx) {
if (range1_exists) { if (range_exists[idx]) {
if (inview_idx1 == inview_idx2) { at_least_one = true;
i.push_back(0); const auto &r1 = ranges[idx*2];
w.push_back(17); const auto &r2 = ranges[idx*2+1];
} else { double extra = calc_extra_weight(idx, r1, r2);
i.push_back(inview_idx1); i.push_back(r1);
i.push_back(inview_idx2); w.push_back(bw[idx] + extra);
w.push_back(inview_idx2 + 1); if (r1 != r2) {
w.push_back(inview_idx2 + 1); i.push_back(r2);
w.push_back(bw[idx] + extra);
}
} }
} }
if (range2_exists) { if (!at_least_one) {
if (cit_idx1 == cit_idx2) { return false;
i.push_back(cit_idx1);
w.push_back(7);
} else {
i.push_back(cit_idx1);
i.push_back(cit_idx2);
w.push_back(7);
w.push_back(7);
}
}
if (range3_exists) {
if (all_idx1 == all_idx2) {
i.push_back(all_idx1);
w.push_back(1);
} else {
i.push_back(all_idx1);
i.push_back(all_idx2);
w.push_back(1);
w.push_back(1);
}
} }
std::piecewise_linear_distribution<> follow_any(i.begin(), i.end(), w.begin()); std::piecewise_linear_distribution<> follow_any(i.begin(), i.end(), w.begin());
// if you're looking at a warning about a local address escaping, it means the unit* from units (which aren't local) // if you're looking at a warning about a local address escaping, it means the unit* from units (which aren't local)
@ -327,7 +351,7 @@ namespace SP {
if (!World::ReadPauseState() && tick - last_tick >= 1) { if (!World::ReadPauseState() && tick - last_tick >= 1) {
last_tick = tick; last_tick = tick;
// validate follow state // validate follow state
if (!following_dwarf || !our_dorf || df::global::ui->follow_unit < 0) { if (!following_dwarf || !our_dorf || df::global::ui->follow_unit < 0 || tick - timestamp >= config.tick_threshold) {
// we're not following anyone // we're not following anyone
following_dwarf = false; following_dwarf = false;
if (!config.disengage) { if (!config.disengage) {