From 3ef4654966e1d887667a13645b05c58c55e462fa Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 5 Jun 2022 09:06:55 -0700 Subject: [PATCH 01/51] Adds unpause code to spectate --- plugins/spectate.cpp | 115 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 105 insertions(+), 10 deletions(-) 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; } } From 33b92e6f693e4ad506eba178e16b1dc7983078cc Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 24 Aug 2022 11:45:16 -0700 Subject: [PATCH 02/51] Implements auto-unpause feature for spectate plugin --- docs/Plugins.rst | 1 + docs/changelog.txt | 1 + plugins/spectate.cpp | 41 +++++++++++++++++++++++++---------------- 3 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 docs/Plugins.rst diff --git a/docs/Plugins.rst b/docs/Plugins.rst new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/docs/Plugins.rst @@ -0,0 +1 @@ + diff --git a/docs/changelog.txt b/docs/changelog.txt index d1547d34c..1620f136c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -57,6 +57,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `orders`: added useful library of manager orders. see them with ``orders list`` and import them with, for example, ``orders import library/basic`` - `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. - `seedwatch`: ``seedwatch all`` now adds all plants with seeds to the watchlist, not just the "basic" crops. +- `spectate`: ``spectate auto-unpause`` is a new feature that will auto-dismiss pause events when enabled. This does not affect the player's ability to pause at a whim. - UX: You can now move the cursor around in DFHack text fields in ``gui/`` scripts (e.g. `gui/blueprint`, `gui/quickfort`, or `gui/gm-editor`). You can move the cursor by clicking where you want it to go with the mouse or using the Left/Right arrow keys. Ctrl+Left/Right will move one word at a time, and Alt+Left/Right will move to the beginning/end of the text. - UX: You can now click on the hotkey hint text in many ``gui/`` script windows to activate the hotkey, like a button. Not all scripts have been updated to use the clickable widget yet, but you can try it in `gui/blueprint` or `gui/quickfort`. - `quickfort`: `Dreamfort ` blueprint set improvements: set traffic designations to encourage dwarves to eat in the grand hall instead of the manager's office and to eat cooked food instead of raw ingredients diff --git a/plugins/spectate.cpp b/plugins/spectate.cpp index 7fc5bb2ee..3e745d6cd 100644 --- a/plugins/spectate.cpp +++ b/plugins/spectate.cpp @@ -53,7 +53,16 @@ command_result spectate (color_ostream &out, std::vector & paramet DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("spectate", "Automated spectator mode.", - spectate)); + spectate, + false, + "" + " spectate\n" + " displays plugin status\n" + " spectate enable\n" + " spectate disable\n" + " spectate auto-unpause\n" + " toggle auto-dismissal of game pause events. e.g. a siege event pause\n" + "\n")); return CR_OK; } @@ -70,12 +79,11 @@ void refresh_camera(color_ostream &out) { void unpause(color_ostream &out) { if (!world) return; while (!world->status.popups.empty()) { - // dismiss? + // dismiss announcement(s) Gui::getCurViewscreen(true)->feed_key(interface_key::CLOSE_MEGA_ANNOUNCEMENT); } if (*pause_state) { - // unpause? - out.print("unpause? %d", df::global::world->frame_counter); + // unpause the game Gui::getCurViewscreen(true)->feed_key(interface_key::D_PAUSE); } refresh_camera(out); @@ -85,14 +93,12 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan if (enabled) { switch (event) { case SC_PAUSED: - out.print(" === This is a pause event: %d", world->frame_counter); - if(dismiss_pause_events){ + if(dismiss_pause_events && !world->status.popups.empty()){ unpause(out); - out.print("spectate: May the deities bless your dwarves."); + out.print("spectate: May the deities bless your dwarves.\n"); } break; case SC_UNPAUSED: - out.print(" === This is an pause event: %d", world->frame_counter); break; case SC_MAP_UNLOADED: case SC_BEGIN_UNLOAD: @@ -132,30 +138,33 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { freq.clear(); } enabled = enable; - return CR_OK; + return DFHack::CR_OK; } command_result spectate (color_ostream &out, std::vector & parameters) { // todo: parse parameters if(!parameters.empty()) { - if (parameters[0] == "spectate") { - return plugin_enable(out, !enabled); - } else if (parameters[0] == "disable") { + 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? + out.print("todo?\n"); // 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"); + out.print(dismiss_pause_events ? "auto-unpause: on\n" : "auto-unpause: off\n"); if (parameters.size() == 2) { - out.print("If you want additional options open an issue on github, or mention it on discord."); + out.print("If you want additional options open an issue on github, or mention it on discord.\n"); return DFHack::CR_WRONG_USAGE; } } + } else { + out.print(enabled ? "Spectate is enabled.\n" : "Spectate is disabled.\n"); + if(enabled) { + out.print(dismiss_pause_events ? "auto-unpause: on.\n" : "auto-unpause: off.\n"); + } } - return DFHack::CR_WRONG_USAGE; + return DFHack::CR_OK; } // every tick check whether to decide to follow a dwarf From 4fd6c52687541e64c70770e0202cc4baf2023c13 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 28 Aug 2022 18:08:40 -0700 Subject: [PATCH 03/51] Extends pausing API in World module --- library/Core.cpp | 3 + library/include/modules/World.h | 57 ++++++++++++++ library/modules/World.cpp | 134 ++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index 1b3f1409f..15cab5f08 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1911,6 +1911,9 @@ void Core::doUpdate(color_ostream &out, bool first_update) // Execute per-frame handlers onUpdate(out); + // Execute per-frame World module maintenance, after plugins and lua timers + World::Update(); + d->last_autosave_request = df::global::ui->main.autosave_request; d->was_load_save = is_load_save; diff --git a/library/include/modules/World.h b/library/include/modules/World.h index 659786ff5..5b87dd4cf 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -35,6 +35,7 @@ distribution. #include "Module.h" #include "modules/Persistence.h" #include +#include #include "DataDefs.h" @@ -61,6 +62,47 @@ namespace DFHack }; class DFContextShared; + //////////// + // Locking mechanisms for control over pausing + namespace Pausing + { + class Lock + { + bool locked = false; + std::string name; + public: + explicit Lock(const char* name) { this->name = name;}; + virtual ~Lock()= default; + virtual bool isAnyLocked() const = 0; + virtual bool isOnlyLocked() const = 0; + bool isLocked() const { return locked; } + void lock() { locked = true; }; + void unlock() { locked = false; }; + }; + + // non-blocking lock resource used in conjunction with the announcement functions in World + class AnnouncementLock : public Lock + { + static std::unordered_set locks; + public: + explicit AnnouncementLock(const char* name): Lock(name) { locks.emplace(this); } + ~AnnouncementLock() override { locks.erase(this); } + bool isAnyLocked() const override; // returns true if any instance of AnnouncementLock is locked + bool isOnlyLocked() const override; // returns true if locked and no other instance is locked + }; + + // non-blocking lock resource used in conjunction with the Player pause functions in World + class PlayerLock : public Lock + { + static std::unordered_set locks; + public: + explicit PlayerLock(const char* name): Lock(name) { locks.emplace(this); } + ~PlayerLock() override { locks.erase(this); } + bool isAnyLocked() const override; // returns true if any instance of PlayerLock is locked + bool isOnlyLocked() const override; // returns true if locked and no other instance is locked + }; + } + /** * The World module * \ingroup grp_modules @@ -73,6 +115,21 @@ namespace DFHack ///true if paused, false if not DFHACK_EXPORT void SetPauseState(bool paused); + // todo: prevent anything else from allocating/deleting? + // Acquire & Release pause locks controlling player pausing and announcement pause settings + DFHACK_EXPORT Pausing::AnnouncementLock* AcquireAnnouncementPauseLock(const char*); + DFHACK_EXPORT Pausing::PlayerLock* AcquirePlayerPauseLock(const char*); + DFHACK_EXPORT void ReleasePauseLock(Pausing::Lock*); + + DFHACK_EXPORT bool DisableAnnouncementPausing(); // disable announcement pausing if all locks are open + DFHACK_EXPORT bool SaveAnnouncementSettings(); // save current announcement pause settings if all locks are open + DFHACK_EXPORT bool RestoreAnnouncementSettings(); // restore saved announcement pause settings if all locks are open + + DFHACK_EXPORT bool EnablePlayerPausing(); // enable player pausing if all locks are open + DFHACK_EXPORT bool DisablePlayerPausing(); // disable player pausing if all locks are open + + void Update(); + DFHACK_EXPORT uint32_t ReadCurrentTick(); DFHACK_EXPORT uint32_t ReadCurrentYear(); DFHACK_EXPORT uint32_t ReadCurrentMonth(); diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 12b339e95..b8e2da272 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -28,6 +28,7 @@ distribution. #include #include #include +#include #include using namespace std; @@ -51,8 +52,14 @@ using namespace std; #include "df/map_block.h" #include "df/block_square_event_world_constructionst.h" #include "df/viewscreen_legendsst.h" +#include "df/d_init.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/ui.h" +#include "VTableInterpose.h" +#include "PluginManager.h" using namespace DFHack; +using namespace Pausing; using namespace df::enums; using df::global::world; @@ -68,6 +75,133 @@ void World::SetPauseState(bool paused) DF_GLOBAL_VALUE(pause_state, dummy) = paused; } +std::unordered_set PlayerLock::locks; +std::unordered_set AnnouncementLock::locks; + +bool AnnouncementLock::isAnyLocked() const { + return std::any_of(locks.begin(), locks.end(), [](Lock* lock) { return lock->isLocked(); }); +} + +bool AnnouncementLock::isOnlyLocked() const { + return std::all_of(locks.begin(), locks.end(), [&](Lock* lock) { + if (lock == this) { + return lock->isLocked(); + } + return !lock->isLocked(); + }); +} + +bool PlayerLock::isAnyLocked() const { + return std::any_of(locks.begin(), locks.end(), [](Lock* lock) { return lock->isLocked(); }); +} + +bool PlayerLock::isOnlyLocked() const { + return std::all_of(locks.begin(), locks.end(), [&](Lock* lock) { + if (lock == this) { + return lock->isLocked(); + } + return !lock->isLocked(); + }); +} + +namespace pausing { + AnnouncementLock announcementLock("monitor"); + PlayerLock playerLock("monitor"); + + const size_t array_size = sizeof(decltype(df::announcements::flags)) / sizeof(df::announcement_flags); + bool state_saved = false; // indicates whether a restore state is ok + bool announcements_disabled = false; // indicates whether disable or restore was last enacted, could use a better name + bool saved_states[array_size]; // state to restore + bool locked_states[array_size]; // locked state (re-applied each frame) + bool allow_player_pause = true; // toggles player pause ability + + using df::global::ui; + using namespace df::enums; + struct player_pause_hook : df::viewscreen_dwarfmodest { + typedef df::viewscreen_dwarfmodest interpose_base; + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set* input)) { + if ((ui->main.mode == ui_sidebar_mode::Default) && !allow_player_pause) { + input->erase(interface_key::D_PAUSE); + } + INTERPOSE_NEXT(feed)(input); + } + }; + + IMPLEMENT_VMETHOD_INTERPOSE(player_pause_hook, feed); +} +using namespace pausing; + +AnnouncementLock* World::AcquireAnnouncementPauseLock(const char* name) { + return new AnnouncementLock(name); +} + +PlayerLock* World::AcquirePlayerPauseLock(const char* name) { + return new PlayerLock(name); +} + +void World::ReleasePauseLock(Lock* lock){ + delete lock; +} + +bool World::DisableAnnouncementPausing() { + if (!announcementLock.isAnyLocked()) { + for (auto& flag : df::global::d_init->announcements.flags) { + flag.bits.PAUSE = false; + } + announcements_disabled = true; + } + return announcements_disabled; +} + +bool World::SaveAnnouncementSettings() { + if (!announcementLock.isAnyLocked()) { + for (size_t i = 0; i < array_size; ++i) { + saved_states[i] = df::global::d_init->announcements.flags[i].bits.PAUSE; + } + return true; + } + return false; +} + +bool World::RestoreAnnouncementSettings() { + if (!announcementLock.isAnyLocked() && state_saved) { + for (size_t i = 0; i < array_size; ++i) { + df::global::d_init->announcements.flags[i].bits.PAUSE = saved_states[i]; + } + announcements_disabled = false; + return true; + } + return false; +} + +bool World::EnablePlayerPausing() { + if (!playerLock.isAnyLocked()) { + allow_player_pause = true; + } + return allow_player_pause; +} + +bool World::DisablePlayerPausing() { + if (!playerLock.isAnyLocked()) { + allow_player_pause = false; + } + return !allow_player_pause; +} + +void World::Update() { + static bool did_once = false; + if (!did_once) { + did_once = true; + INTERPOSE_HOOK(player_pause_hook, feed).apply(); + } + if (announcementLock.isAnyLocked()) { + for (size_t i = 0; i < array_size; ++i) { + df::global::d_init->announcements.flags[i].bits.PAUSE = locked_states[i]; + } + } +} + + uint32_t World::ReadCurrentYear() { return DF_GLOBAL_VALUE(cur_year, 0); From 24fe4da6bab74979718daa334248bc5863683cf2 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 28 Aug 2022 18:11:50 -0700 Subject: [PATCH 04/51] Replaces spectate:auto-unpause with pausing API in World module --- plugins/spectate.cpp | 98 ++++++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/plugins/spectate.cpp b/plugins/spectate.cpp index 3e745d6cd..b61d2d7fb 100644 --- a/plugins/spectate.cpp +++ b/plugins/spectate.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -16,8 +17,6 @@ #include #include #include -#include -#include #include #include @@ -47,6 +46,8 @@ REQUIRE_GLOBAL(d_init); // todo: implement as user configurable variables #define tick_span 50 #define base 0.99 +Pausing::AnnouncementLock* pause_lock = nullptr; +bool lock_collision = false; command_result spectate (color_ostream &out, std::vector & parameters); @@ -63,39 +64,28 @@ 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 announcement(s) - Gui::getCurViewscreen(true)->feed_key(interface_key::CLOSE_MEGA_ANNOUNCEMENT); - } - if (*pause_state) { - // unpause the game - 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) { + if (enabled && world) { switch (event) { + case SC_WORLD_LOADED: + case SC_MAP_LOADED: + if (dismiss_pause_events && World::ReadPauseState()) { + *df::global::debug_nopause = false; + } + break; case SC_PAUSED: - if(dismiss_pause_events && !world->status.popups.empty()){ - unpause(out); - out.print("spectate: May the deities bless your dwarves.\n"); + if(dismiss_pause_events){ + if (our_dorf) { + df::global::ui->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); + } } break; case SC_UNPAUSED: @@ -112,9 +102,33 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } DFhackCExport command_result plugin_shutdown (color_ostream &out) { + World::ReleasePauseLock(pause_lock); return CR_OK; } +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (lock_collision) { + if (dismiss_pause_events) { + // player asked for auto-unpause enabled + World::SaveAnnouncementSettings(); + if (World::DisableAnnouncementPausing()){ + // now that we've got what we want, we can lock it down + lock_collision = false; + pause_lock->lock(); + } + } else { + if (World::RestoreAnnouncementSettings()) { + lock_collision = false; + } + } + } + while (dismiss_pause_events && !world->status.popups.empty()) { + // dismiss announcement popup(s) + Gui::getCurViewscreen(true)->feed_key(interface_key::CLOSE_MEGA_ANNOUNCEMENT); + } + return DFHack::CR_OK; +} + void onTick(color_ostream& out, void* tick); void onJobStart(color_ostream &out, void* job); void onJobCompletion(color_ostream &out, void* job); @@ -152,7 +166,31 @@ command_result spectate (color_ostream &out, std::vector & paramet out.print("todo?\n"); // todo: adventure as deity? } else if (parameters[0] == "auto-unpause") { dismiss_pause_events = !dismiss_pause_events; - out.print(dismiss_pause_events ? "auto-unpause: on\n" : "auto-unpause: off\n"); + + // update the announcement settings if we can + if (dismiss_pause_events) { + if (World::SaveAnnouncementSettings()) { + World::DisableAnnouncementPausing(); + pause_lock->lock(); + } else { + lock_collision = true; + } + } else { + pause_lock->unlock(); + if (!World::RestoreAnnouncementSettings()) { + // this in theory shouldn't happen, if others use the lock like we do in spectate + lock_collision = true; + } + } + + // report to the user how things went + if (!lock_collision){ + out.print(dismiss_pause_events ? "auto-unpause: on\n" : "auto-unpause: off\n"); + } else { + out.print("auto-unpause: must wait for another Pausing::AnnouncementLock to be lifted.\n"); + } + + // probably a typo if (parameters.size() == 2) { out.print("If you want additional options open an issue on github, or mention it on discord.\n"); return DFHack::CR_WRONG_USAGE; From 318dd4c7db33229a3517048967b3cee560aeb347 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 28 Aug 2022 18:13:20 -0700 Subject: [PATCH 05/51] Revises nopause in reveal with pause API in World module --- plugins/reveal.cpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 8d76bd54b..3aba37b95 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -62,6 +62,7 @@ struct hideblock uint32_t x_max, y_max, z_max; vector hidesaved; bool nopause_state = false; +Pausing::PlayerLock* pause_lock = nullptr; enum revealstate { @@ -106,6 +107,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector & parameters) { if (parameters.size() == 1 && (parameters[0] == "0" || parameters[0] == "1")) { - if (parameters[0] == "0") + if (parameters[0] == "0") { + if (nopause_state) { + pause_lock->unlock(); + if (!World::EnablePlayerPausing()) { + out.printerr("reveal/nopause: Player pausing is currently locked by another plugin / script. Unable to re-enable.\n"); + } + } nopause_state = 0; - else + } else { + if (!nopause_state) { + pause_lock->unlock(); + if (!World::DisablePlayerPausing()) { + out.printerr("reveal/nopause: Player pausing is currently locked by another plugin / script. Unable to disable.\n"); + } + } nopause_state = 1; + } is_active = nopause_state || (revealed == REVEALED); out.print("nopause %sactivated.\n", (nopause_state ? "" : "de")); } @@ -474,7 +490,7 @@ command_result revflood(color_ostream &out, vector & params) return CR_WRONG_USAGE; } CoreSuspender suspend; - uint32_t x_max,y_max,z_max; + uint32_t xmax,ymax,zmax; if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); @@ -493,7 +509,7 @@ command_result revflood(color_ostream &out, vector & params) return CR_FAILURE; } int32_t cx, cy, cz; - Maps::getSize(x_max,y_max,z_max); + Maps::getSize(xmax, ymax, zmax); Gui::getCursorCoords(cx,cy,cz); if(cx == -30000) @@ -511,7 +527,7 @@ command_result revflood(color_ostream &out, vector & params) return CR_FAILURE; } // hide all tiles, flush cache - Maps::getSize(x_max,y_max,z_max); + Maps::getSize(xmax, ymax, zmax); for(size_t i = 0; i < world->map.map_blocks.size(); i++) { From cd34749fec581dff037216adbbef87251662f14c Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 1 Sep 2022 16:58:12 -0700 Subject: [PATCH 06/51] Re-deletes Plugins.rst --- docs/Plugins.rst | 1 - 1 file changed, 1 deletion(-) delete mode 100644 docs/Plugins.rst diff --git a/docs/Plugins.rst b/docs/Plugins.rst deleted file mode 100644 index 8b1378917..000000000 --- a/docs/Plugins.rst +++ /dev/null @@ -1 +0,0 @@ - From b3284c9d821c9761eddfd3baccfe3681b2ae853d Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 1 Sep 2022 17:14:10 -0700 Subject: [PATCH 07/51] Updates spectate.rst --- docs/plugins/spectate.rst | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/plugins/spectate.rst b/docs/plugins/spectate.rst index d9e9d3929..d6ea84581 100644 --- a/docs/plugins/spectate.rst +++ b/docs/plugins/spectate.rst @@ -11,7 +11,30 @@ Usage :: - enable spectate + spectate + spectate