diff --git a/plugins/spectate.cpp b/plugins/spectate.cpp index 1161c3f32..7fc5bb2ee 100644 --- a/plugins/spectate.cpp +++ b/plugins/spectate.cpp @@ -3,6 +3,7 @@ // #include "Core.h" +#include #include #include #include @@ -14,7 +15,9 @@ #include #include #include +#include #include +#include #include #include @@ -31,11 +34,19 @@ using namespace df::enums; DFHACK_PLUGIN("spectate"); DFHACK_PLUGIN_IS_ENABLED(enabled); +bool dismiss_pause_events = false; bool following_dwarf = false; +df::unit* our_dorf = nullptr; void* job_watched = nullptr; int32_t timestamp = -1; REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(pause_state); +REQUIRE_GLOBAL(d_init); + +// todo: implement as user configurable variables +#define tick_span 50 +#define base 0.99 command_result spectate (color_ostream &out, std::vector & parameters); @@ -46,6 +57,54 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector follow_unit = our_dorf->id; + const int16_t &x = our_dorf->pos.x; + const int16_t &y = our_dorf->pos.y; + const int16_t &z = our_dorf->pos.z; + Gui::setViewCoords(x, y, z); + } +} + +void unpause(color_ostream &out) { + if (!world) return; + while (!world->status.popups.empty()) { + // dismiss? + Gui::getCurViewscreen(true)->feed_key(interface_key::CLOSE_MEGA_ANNOUNCEMENT); + } + if (*pause_state) { + // unpause? + out.print("unpause? %d", df::global::world->frame_counter); + Gui::getCurViewscreen(true)->feed_key(interface_key::D_PAUSE); + } + refresh_camera(out); +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (enabled) { + switch (event) { + case SC_PAUSED: + out.print(" === This is a pause event: %d", world->frame_counter); + if(dismiss_pause_events){ + unpause(out); + out.print("spectate: May the deities bless your dwarves."); + } + break; + case SC_UNPAUSED: + out.print(" === This is an pause event: %d", world->frame_counter); + break; + case SC_MAP_UNLOADED: + case SC_BEGIN_UNLOAD: + case SC_WORLD_UNLOADED: + our_dorf = nullptr; + default: + break; + } + } + return CR_OK; +} + DFhackCExport command_result plugin_shutdown (color_ostream &out) { return CR_OK; } @@ -66,6 +125,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { EM::registerListener(EventType::JOB_STARTED, start, plugin_self); EM::registerListener(EventType::JOB_COMPLETED, complete, plugin_self); } else if (!enable && enabled) { + // warp 8, engage! out.print("Spectate mode disabled!\n"); EM::unregisterAll(plugin_self); job_tracker.clear(); @@ -76,13 +136,39 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { } command_result spectate (color_ostream &out, std::vector & parameters) { - return plugin_enable(out, !enabled); + // todo: parse parameters + if(!parameters.empty()) { + if (parameters[0] == "spectate") { + return plugin_enable(out, !enabled); + } else if (parameters[0] == "disable") { + return plugin_enable(out, false); + } else if (parameters[0] == "enable") { + return plugin_enable(out, true); + } else if (parameters[0] == "godmode") { + out.print("todo?"); // todo: adventure as deity? + } else if (parameters[0] == "auto-unpause") { + dismiss_pause_events = !dismiss_pause_events; + out.print(dismiss_pause_events ? "auto-dismiss: on" : "auto-dismiss: off"); + if (parameters.size() == 2) { + out.print("If you want additional options open an issue on github, or mention it on discord."); + return DFHack::CR_WRONG_USAGE; + } + } + } + return DFHack::CR_WRONG_USAGE; } +// every tick check whether to decide to follow a dwarf void onTick(color_ostream& out, void* ptr) { + if (!df::global::ui) return; 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)) { + if(our_dorf){ + if(!Units::isAlive(our_dorf)){ + following_dwarf = false; + df::global::ui->follow_unit = -1; + } + } + if (!following_dwarf || (tick - timestamp) > tick_span || job_watched == nullptr) { std::vector dwarves; for (auto unit: df::global::world->units.active) { if (!Units::isCitizen(unit)) { @@ -92,9 +178,9 @@ void onTick(color_ostream& out, void* ptr) { } std::uniform_int_distribution 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; + our_dorf = dwarves[follow_any(RNG)]; + df::global::ui->follow_unit = our_dorf->id; + job_watched = our_dorf->job.current_job; following_dwarf = true; if (!job_watched) { timestamp = tick; @@ -103,19 +189,26 @@ void onTick(color_ostream& out, void* ptr) { } } +// every new worked job needs to be considered void onJobStart(color_ostream& out, void* job_ptr) { + // todo: detect mood jobs int32_t tick = df::global::world->frame_counter; auto job = (df::job*) job_ptr; + // don't forget about it int zcount = ++freq[job->pos.z]; job_tracker.emplace(job->id); - if (!following_dwarf || (job_watched == nullptr && (tick - timestamp) > 50)) { + // if we're not doing anything~ then let's pick something + if (job_watched == nullptr || (tick - timestamp) > tick_span) { following_dwarf = true; - double p = 0.99 * ((double) zcount / job_tracker.size()); + // todo: allow the user to configure b, and also revise the math + const double b = base; + double p = b * ((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) { + our_dorf = unit; df::global::ui->follow_unit = unit->id; } } else { @@ -135,13 +228,15 @@ void onJobStart(color_ostream& out, void* job_ptr) { } } +// every job completed can be forgotten about void onJobCompletion(color_ostream &out, void* job_ptr) { auto job = (df::job*)job_ptr; + // forget about it 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; + // the job doesn't exist, so we definitely need to get rid of that + if (job_watched == job_ptr) { job_watched = nullptr; } }