Plugin to easily find and resume suspended constructions
parent
056bde451a
commit
ecf255243f
@ -0,0 +1,316 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <VTableInterpose.h>
|
||||
|
||||
|
||||
// DF data structure definition headers
|
||||
#include "DataDefs.h"
|
||||
#include "MiscUtils.h"
|
||||
#include "Types.h"
|
||||
#include "df/viewscreen_dwarfmodest.h"
|
||||
#include "df/world.h"
|
||||
#include "df/building_constructionst.h"
|
||||
#include "df/building.h"
|
||||
#include "df/job.h"
|
||||
#include "df/job_item.h"
|
||||
|
||||
#include "modules/Gui.h"
|
||||
#include "modules/Screen.h"
|
||||
#include "modules/Buildings.h"
|
||||
#include "modules/Maps.h"
|
||||
|
||||
#include "modules/World.h"
|
||||
|
||||
using std::map;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
using df::global::gps;
|
||||
using df::global::ui;
|
||||
using df::global::world;
|
||||
|
||||
DFHACK_PLUGIN("resume");
|
||||
#define PLUGIN_VERSION 0.1
|
||||
|
||||
#ifndef HAVE_NULLPTR
|
||||
#define nullptr 0L
|
||||
#endif
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
template <class T, typename Fn>
|
||||
static void for_each_(vector<T> &v, Fn func)
|
||||
{
|
||||
for_each(v.begin(), v.end(), func);
|
||||
}
|
||||
|
||||
template <class T, class V, typename Fn>
|
||||
static void transform_(vector<T> &src, vector<V> &dst, Fn func)
|
||||
{
|
||||
transform(src.begin(), src.end(), back_inserter(dst), func);
|
||||
}
|
||||
|
||||
void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0)
|
||||
{
|
||||
Screen::paintString(Screen::Pen(' ', color, 0), x, y, text);
|
||||
if (newline)
|
||||
{
|
||||
++y;
|
||||
x = left_margin;
|
||||
}
|
||||
else
|
||||
x += text.length();
|
||||
}
|
||||
|
||||
df::job *get_suspended_job(df::building *bld)
|
||||
{
|
||||
if (bld->getBuildStage() != 0)
|
||||
return nullptr;
|
||||
|
||||
if (bld->jobs.size() == 0)
|
||||
return nullptr;
|
||||
|
||||
auto job = bld->jobs[0];
|
||||
if (job->flags.bits.suspend)
|
||||
return job;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct SuspendedBuilding
|
||||
{
|
||||
df::building *bld;
|
||||
df::coord pos;
|
||||
bool was_resumed;
|
||||
bool is_planned;
|
||||
|
||||
SuspendedBuilding(df::building *bld_) : bld(bld_), was_resumed(false), is_planned(false)
|
||||
{
|
||||
pos = df::coord(bld->centerx, bld->centery, bld->z);
|
||||
}
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
return bld && Buildings::findAtTile(pos) == bld && get_suspended_job(bld);
|
||||
}
|
||||
};
|
||||
|
||||
static bool enabled = false;
|
||||
static bool buildings_scanned = false;
|
||||
static vector<SuspendedBuilding> suspended_buildings, resumed_buildings;
|
||||
|
||||
void scan_for_suspended_buildings()
|
||||
{
|
||||
if (buildings_scanned)
|
||||
return;
|
||||
|
||||
for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++)
|
||||
{
|
||||
auto bld = *b;
|
||||
auto job = get_suspended_job(bld);
|
||||
if (job)
|
||||
{
|
||||
SuspendedBuilding sb(bld);
|
||||
sb.is_planned = job->job_items.size() == 1 && job->job_items[0]->item_type == item_type::NONE;
|
||||
|
||||
auto it = find_if(resumed_buildings.begin(), resumed_buildings.end(),
|
||||
[&] (SuspendedBuilding &rsb) { return rsb.bld == bld; });
|
||||
|
||||
sb.was_resumed = it != resumed_buildings.end();
|
||||
|
||||
suspended_buildings.push_back(sb);
|
||||
}
|
||||
}
|
||||
|
||||
buildings_scanned = true;
|
||||
}
|
||||
|
||||
void show_suspended_buildings()
|
||||
{
|
||||
int32_t vx, vy, vz;
|
||||
if (!Gui::getViewCoords(vx, vy, vz))
|
||||
return;
|
||||
|
||||
auto dims = Gui::getDwarfmodeViewDims();
|
||||
int left_margin = vx + dims.map_x2;
|
||||
int bottom_margin = vy + dims.y2;
|
||||
|
||||
for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end();)
|
||||
{
|
||||
if (!sb->isValid())
|
||||
{
|
||||
sb = suspended_buildings.erase(sb);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sb->bld->z == vz && sb->bld->centerx >= vx && sb->bld->centerx <= left_margin &&
|
||||
sb->bld->centery >= vy && sb->bld->centery <= bottom_margin)
|
||||
{
|
||||
int x = sb->bld->centerx - vx + 1;
|
||||
int y = sb->bld->centery - vy + 1;
|
||||
auto color = COLOR_YELLOW;
|
||||
if (sb->is_planned)
|
||||
color = COLOR_GREEN;
|
||||
else if (sb->was_resumed)
|
||||
color = COLOR_RED;
|
||||
|
||||
OutputString(color, x, y, "X");
|
||||
}
|
||||
|
||||
sb++;
|
||||
}
|
||||
}
|
||||
|
||||
void clear_scanned()
|
||||
{
|
||||
buildings_scanned = false;
|
||||
suspended_buildings.clear();
|
||||
}
|
||||
|
||||
void resume_suspended_buildings(color_ostream &out)
|
||||
{
|
||||
out << "Resuming all buildings." << endl;
|
||||
|
||||
for (auto isb = resumed_buildings.begin(); isb != resumed_buildings.end();)
|
||||
{
|
||||
if (isb->isValid())
|
||||
{
|
||||
isb++;
|
||||
continue;
|
||||
}
|
||||
|
||||
isb = resumed_buildings.erase(isb);
|
||||
}
|
||||
|
||||
scan_for_suspended_buildings();
|
||||
for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end(); sb++)
|
||||
{
|
||||
if (sb->is_planned)
|
||||
continue;
|
||||
|
||||
resumed_buildings.push_back(*sb);
|
||||
sb->bld->jobs[0]->flags.bits.suspend = false;
|
||||
}
|
||||
|
||||
clear_scanned();
|
||||
|
||||
out << resumed_buildings.size() << " buildings resumed" << endl;
|
||||
}
|
||||
|
||||
|
||||
//START Viewscreen Hook
|
||||
struct resume_hook : public df::viewscreen_dwarfmodest
|
||||
{
|
||||
//START UI Methods
|
||||
typedef df::viewscreen_dwarfmodest interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
||||
{
|
||||
INTERPOSE_NEXT(render)();
|
||||
|
||||
DFHack::World *world = Core::getInstance().getWorld();
|
||||
if (enabled && world->ReadPauseState() && ui->main.mode == ui_sidebar_mode::Default)
|
||||
{
|
||||
scan_for_suspended_buildings();
|
||||
show_suspended_buildings();
|
||||
}
|
||||
else
|
||||
{
|
||||
clear_scanned();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(resume_hook, render);
|
||||
|
||||
|
||||
static command_result resume_cmd(color_ostream &out, vector <string> & parameters)
|
||||
{
|
||||
bool show_help = false;
|
||||
if (parameters.empty())
|
||||
{
|
||||
show_help = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto cmd = parameters[0][0];
|
||||
if (cmd == 'v')
|
||||
{
|
||||
out << "Resume" << endl << "Version: " << PLUGIN_VERSION << endl;
|
||||
}
|
||||
else if (cmd == 's')
|
||||
{
|
||||
enabled = true;
|
||||
out << "Overlay enabled" << endl;
|
||||
}
|
||||
else if (cmd == 'h')
|
||||
{
|
||||
enabled = false;
|
||||
out << "Overlay disabled" << endl;
|
||||
}
|
||||
else if (cmd == 'a')
|
||||
{
|
||||
resume_suspended_buildings(out);
|
||||
}
|
||||
else
|
||||
{
|
||||
show_help = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (show_help)
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
if (!gps || !INTERPOSE_HOOK(resume_hook, render).apply())
|
||||
out.printerr("Could not insert resume hooks!\n");
|
||||
|
||||
commands.push_back(
|
||||
PluginCommand(
|
||||
"resume", "A plugin to help display and resume suspended constructions conveniently",
|
||||
resume_cmd, false,
|
||||
"resume show\n"
|
||||
" Show overlay when paused:\n"
|
||||
" Yellow: Suspended construction\n"
|
||||
" Red: Suspended after resume attempt, possibly stuck\n"
|
||||
" Green: Planned building waiting for materials\n"
|
||||
"resume hide\n"
|
||||
" Hide overlay\n"
|
||||
"resume all\n"
|
||||
" Resume all suspended building constructions\n"
|
||||
));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_MAP_LOADED:
|
||||
suspended_buildings.clear();
|
||||
resumed_buildings.clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
Loading…
Reference in New Issue