Adds new plugin: Spectate (#1918)

* Adds spectate plugin

* Updates spectate.cpp

* Changes spectate toggle message

* Updates changelog.txt

* Adds spectate to Plugins.rst

* Adds requested changes

- foreach syntax replaces active units loops
- removes CR_FAILURE return on double enable/disable usage
- removes disabled code
- implements a few clion clang tidy suggestions (auto declare when casting)
- Updates zcount to have accurate count for performing RNG
- adds eof newline
- adds todo comment about a redundant if condition

* Declares spectate section in Plugins.rst
develop
Josh Cooper 2022-03-14 19:33:41 -07:00 committed by GitHub
parent 88b403ec7a
commit dda487a535
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 162 additions and 0 deletions

@ -289,6 +289,14 @@ showmood
========
Shows all items needed for the currently active strange mood.
.. _spectate:
spectate
========
Simple plugin to automate following random dwarves. Most of the time things will
be weighted towards z-levels with the highest job activity. Simply enter the
``spectate`` command to toggle the plugin's state.
========
Bugfixes

@ -34,6 +34,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
# Future
## New Plugins
- `spectate`: automates the following of dwarves more often than not based on job zlevel activity levels, sometimes randomly though.
## Removed

@ -159,6 +159,7 @@ if(BUILD_SUPPORTED)
dfhack_plugin(siege-engine siege-engine.cpp LINK_LIBRARIES lua)
dfhack_plugin(sort sort.cpp LINK_LIBRARIES lua)
dfhack_plugin(steam-engine steam-engine.cpp)
dfhack_plugin(spectate spectate.cpp)
dfhack_plugin(stockflow stockflow.cpp LINK_LIBRARIES lua)
add_subdirectory(stockpiles)
dfhack_plugin(stocks stocks.cpp)

@ -0,0 +1,152 @@
//
// Created by josh on 7/28/21.
//
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <modules/EventManager.h>
#include <modules/Job.h>
#include <modules/Units.h>
#include <df/job.h>
#include <df/unit.h>
#include <df/historical_figure.h>
#include <df/global_objects.h>
#include <df/world.h>
#include <df/ui.h>
#include <map>
#include <set>
#include <random>
std::map<uint16_t,uint16_t> freq;
std::set<int32_t> job_tracker;
std::default_random_engine RNG;
//#include "df/world.h"
using namespace DFHack;
using namespace df::enums;
DFHACK_PLUGIN("spectate");
DFHACK_PLUGIN_IS_ENABLED(enabled);
bool following_dwarf = false;
void* job_watched = nullptr;
int32_t timestamp = -1;
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(ui);
command_result spectate (color_ostream &out, std::vector <std::string> & parameters);
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) {
commands.push_back(PluginCommand("spectate",
"Automated spectator mode.",
spectate,
false,
""
" spectate\n"
" toggles spectator mode\n"
"\n"));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out) {
return CR_OK;
}
void onTick(color_ostream& out, void* tick);
void onJobStart(color_ostream &out, void* job);
void onJobCompletion(color_ostream &out, void* job);
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
namespace EM = EventManager;
if (enable && !enabled) {
out.print("Spectate mode enabled!\n");
using namespace EM::EventType;
EM::EventHandler ticking(onTick, 15);
EM::EventHandler start(onJobStart, 0);
EM::EventHandler complete(onJobCompletion, 0);
EM::registerListener(EventType::TICK, ticking, plugin_self);
EM::registerListener(EventType::JOB_STARTED, start, plugin_self);
EM::registerListener(EventType::JOB_COMPLETED, complete, plugin_self);
} else if (!enable && enabled) {
out.print("Spectate mode disabled!\n");
EM::unregisterAll(plugin_self);
job_tracker.clear();
freq.clear();
}
enabled = enable;
return CR_OK;
}
command_result spectate (color_ostream &out, std::vector <std::string> & parameters) {
return plugin_enable(out, !enabled);
}
void onTick(color_ostream& out, void* ptr) {
int32_t tick = df::global::world->frame_counter;
// || seems to be redundant as the first always evaluates true when job_watched is nullptr.. todo: figure what is supposed to happen
if (!following_dwarf || (job_watched == nullptr && (tick - timestamp) > 50)) {
std::vector<df::unit*> dwarves;
for (auto unit: df::global::world->units.active) {
if (!Units::isCitizen(unit)) {
continue;
}
dwarves.push_back(unit);
}
std::uniform_int_distribution<uint64_t> follow_any(0, dwarves.size() - 1);
if (df::global::ui) {
df::unit* unit = dwarves[follow_any(RNG)];
df::global::ui->follow_unit = unit->id;
job_watched = unit->job.current_job;
following_dwarf = true;
if (!job_watched) {
timestamp = tick;
}
}
}
}
void onJobStart(color_ostream& out, void* job_ptr) {
int32_t tick = df::global::world->frame_counter;
auto job = (df::job*) job_ptr;
int zcount = ++freq[job->pos.z];
job_tracker.emplace(job->id);
if (!following_dwarf || (job_watched == nullptr && (tick - timestamp) > 50)) {
following_dwarf = true;
double p = 0.99 * ((double) zcount / job_tracker.size());
std::bernoulli_distribution follow_job(p);
if (!job->flags.bits.special && follow_job(RNG)) {
job_watched = job_ptr;
df::unit* unit = Job::getWorker(job);
if (df::global::ui && unit) {
df::global::ui->follow_unit = unit->id;
}
} else {
timestamp = tick;
std::vector<df::unit*> nonworkers;
for (auto unit: df::global::world->units.active) {
if (!Units::isCitizen(unit) || unit->job.current_job) {
continue;
}
nonworkers.push_back(unit);
}
std::uniform_int_distribution<> follow_drunk(0, nonworkers.size() - 1);
if (df::global::ui) {
df::global::ui->follow_unit = nonworkers[follow_drunk(RNG)]->id;
}
}
}
}
void onJobCompletion(color_ostream &out, void* job_ptr) {
auto job = (df::job*)job_ptr;
freq[job->pos.z]--;
freq[job->pos.z] = freq[job->pos.z] < 0 ? 0 : freq[job->pos.z];
job_tracker.erase(job->id);
if (following_dwarf && job_ptr == job_watched) {
following_dwarf = false;
job_watched = nullptr;
}
}