summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dfhack.init-example3
-rw-r--r--library/lua/dfhack.lua2
-rw-r--r--library/lua/gui.lua8
-rw-r--r--library/lua/gui/dialogs.lua6
-rw-r--r--library/modules/Gui.cpp9
-rw-r--r--plugins/CMakeLists.txt1
-rw-r--r--plugins/lua/power-meter.lua11
-rw-r--r--plugins/power-meter.cpp237
-rw-r--r--scripts/gui/power-meter.lua116
9 files changed, 383 insertions, 10 deletions
diff --git a/dfhack.init-example b/dfhack.init-example
index af8b17f0..5af52709 100644
--- a/dfhack.init-example
+++ b/dfhack.init-example
@@ -56,6 +56,9 @@ keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work
# interface for the liquids plugin
keybinding add Alt-L@dwarfmode/LookAround gui/liquids
+# machine power sensitive pressure plate construction
+keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter
+
###################
# UI logic tweaks #
###################
diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua
index a1e89976..e96bb0f4 100644
--- a/library/lua/dfhack.lua
+++ b/library/lua/dfhack.lua
@@ -84,7 +84,7 @@ function mkmodule(module,env)
error("Not a table in package.loaded["..module.."]")
end
end
- local plugname = string.match(module,'^plugins%.(%w+)$')
+ local plugname = string.match(module,'^plugins%.([%w%-]+)$')
if plugname then
dfhack.open_plugin(pkg,plugname)
end
diff --git a/library/lua/gui.lua b/library/lua/gui.lua
index 23904c14..f9b6ab6d 100644
--- a/library/lua/gui.lua
+++ b/library/lua/gui.lua
@@ -162,10 +162,10 @@ function Painter:fill(x1,y1,x2,y2,pen,bg,bold)
if type(x1) == 'table' then
x1, y1, x2, y2, pen, bg, bold = x1.x1, x1.y1, x1.x2, x1.y2, y1, x2, y2
end
- x1 = math.max(x1,self.clip_x1)
- y1 = math.max(y1,self.clip_y1)
- x2 = math.min(x2,self.clip_x2)
- y2 = math.min(y2,self.clip_y2)
+ x1 = math.max(x1+self.x1,self.clip_x1)
+ y1 = math.max(y1+self.y1,self.clip_y1)
+ x2 = math.min(x2+self.x1,self.clip_x2)
+ y2 = math.min(y2+self.y1,self.clip_y2)
dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2)
return self
end
diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua
index 35720f87..c4f15c9a 100644
--- a/library/lua/gui/dialogs.lua
+++ b/library/lua/gui/dialogs.lua
@@ -127,7 +127,7 @@ function InputBox:onRenderBody(dc)
dc:newline(1)
dc:pen(self.input_pen or COLOR_LIGHTCYAN)
- dc:fill(dc.x1+1,dc.y,dc.x2-1,dc.y)
+ dc:fill(1,dc:localY(),dc.width-2,dc:localY())
local cursor = '_'
if math.floor(dfhack.getTickCount()/300) % 2 == 0 then
@@ -135,9 +135,7 @@ function InputBox:onRenderBody(dc)
end
local txt = self.input .. cursor
if #txt > dc.width-2 then
- txt = string.sub(txt, #txt-dc.width+3)
- -- Add prefix arrow
- dc:advance(-1):char(27)
+ txt = string.char(27)..string.sub(txt, #txt-dc.width+4)
end
dc:string(txt)
end
diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp
index 1ea4bf68..8de90873 100644
--- a/library/modules/Gui.cpp
+++ b/library/modules/Gui.cpp
@@ -211,7 +211,14 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
if (ui_build_selector->building_type < 0)
focus += "/Type";
else if (ui_build_selector->stage != 2)
- focus += "/Position";
+ {
+ if (ui_build_selector->stage != 1)
+ focus += "/NoMaterials";
+ else
+ focus += "/Position";
+
+ focus += "/" + enum_item_key(ui_build_selector->building_type);
+ }
else
{
focus += "/Material";
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 04da3e6c..9093a493 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -114,6 +114,7 @@ if (BUILD_SUPPORTED)
# this one exports functions to lua
DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua)
+ DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua)
# not yet. busy with other crud again...
#DFHACK_PLUGIN(versionosd versionosd.cpp)
endif()
diff --git a/plugins/lua/power-meter.lua b/plugins/lua/power-meter.lua
new file mode 100644
index 00000000..310e51c4
--- /dev/null
+++ b/plugins/lua/power-meter.lua
@@ -0,0 +1,11 @@
+local _ENV = mkmodule('plugins.power-meter')
+
+--[[
+
+ Native functions:
+
+ * makePowerMeter(plate_info,min_power,max_power,invert)
+
+--]]
+
+return _ENV \ No newline at end of file
diff --git a/plugins/power-meter.cpp b/plugins/power-meter.cpp
new file mode 100644
index 00000000..0466b68e
--- /dev/null
+++ b/plugins/power-meter.cpp
@@ -0,0 +1,237 @@
+#include "Core.h"
+#include <Console.h>
+#include <Export.h>
+#include <Error.h>
+#include <PluginManager.h>
+#include <modules/Gui.h>
+#include <modules/Screen.h>
+#include <modules/Maps.h>
+#include <modules/World.h>
+#include <TileTypes.h>
+#include <vector>
+#include <cstdio>
+#include <stack>
+#include <string>
+#include <cmath>
+#include <string.h>
+
+#include <VTableInterpose.h>
+#include "df/graphic.h"
+#include "df/building_trapst.h"
+#include "df/builtin_mats.h"
+#include "df/world.h"
+#include "df/buildings_other_id.h"
+#include "df/machine.h"
+#include "df/machine_info.h"
+#include "df/building_drawbuffer.h"
+#include "df/ui.h"
+#include "df/viewscreen_dwarfmodest.h"
+#include "df/ui_build_selector.h"
+#include "df/flow_info.h"
+#include "df/report.h"
+
+#include "MiscUtils.h"
+
+using std::vector;
+using std::string;
+using std::stack;
+using namespace DFHack;
+using namespace df::enums;
+
+using df::global::gps;
+using df::global::world;
+using df::global::ui;
+using df::global::ui_build_selector;
+
+DFHACK_PLUGIN("power-meter");
+
+static const uint32_t METER_BIT = 0x80000000U;
+
+static void init_plate_info(df::pressure_plate_info &plate_info)
+{
+ plate_info.water_min = 1;
+ plate_info.water_max = 7;
+ plate_info.flags.whole = METER_BIT;
+ plate_info.flags.bits.water = true;
+ plate_info.flags.bits.resets = true;
+}
+
+/*
+ * Hook for the pressure plate itself. Implements core logic.
+ */
+
+struct trap_hook : df::building_trapst {
+ typedef df::building_trapst interpose_base;
+
+ // Engine detection
+
+ bool is_power_meter()
+ {
+ return trap_type == trap_type::PressurePlate &&
+ (plate_info.flags.whole & METER_BIT) != 0;
+ }
+
+ inline bool is_fully_built()
+ {
+ return getBuildStage() >= getMaxBuildStage();
+ }
+
+ DEFINE_VMETHOD_INTERPOSE(void, getName, (std::string *buf))
+ {
+ if (is_power_meter())
+ {
+ buf->clear();
+ *buf += "Power Meter";
+ return;
+ }
+
+ INTERPOSE_NEXT(getName)(buf);
+ }
+
+ DEFINE_VMETHOD_INTERPOSE(void, updateAction, ())
+ {
+ if (is_power_meter())
+ {
+ auto pdsgn = Maps::getTileDesignation(centerx,centery,z);
+
+ if (pdsgn)
+ {
+ bool active = false;
+ auto &gears = world->buildings.other[buildings_other_id::GEAR_ASSEMBLY];
+
+ for (size_t i = 0; i < gears.size(); i++)
+ {
+ // Adjacent
+ auto gear = gears[i];
+ int deltaxy = abs(centerx - gear->centerx) + abs(centery - gear->centery);
+ if (gear->z != z || deltaxy != 1)
+ continue;
+ // Linked to machine
+ auto info = gears[i]->getMachineInfo();
+ if (!info || info->machine_id < 0)
+ continue;
+ // an active machine
+ auto machine = df::machine::find(info->machine_id);
+ if (!machine || !machine->flags.bits.active)
+ continue;
+ // with adequate power?
+ int power = machine->cur_power - machine->min_power;
+ if (power < 0 || machine->cur_power <= 0)
+ continue;
+ if (power < plate_info.track_min)
+ continue;
+ if (power > plate_info.track_max && plate_info.track_max >= 0)
+ continue;
+
+ active = true;
+ break;
+ }
+
+ if (plate_info.flags.bits.citizens)
+ active = !active;
+
+ // Temporarily set the tile water amount based on power state
+ auto old_dsgn = *pdsgn;
+ pdsgn->bits.liquid_type = tile_liquid::Water;
+ pdsgn->bits.flow_size = (active ? 7 : 0);
+
+ INTERPOSE_NEXT(updateAction)();
+
+ *pdsgn = old_dsgn;
+ return;
+ }
+ }
+
+ INTERPOSE_NEXT(updateAction)();
+ }
+
+ DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, void *unk))
+ {
+ INTERPOSE_NEXT(drawBuilding)(db, unk);
+
+ if (is_power_meter() && is_fully_built())
+ {
+ db->fore[0][0] = 3;
+ }
+ }
+};
+
+IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, getName);
+IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, updateAction);
+IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, drawBuilding);
+
+static bool enabled = false;
+
+static void enable_hooks(bool enable)
+{
+ enabled = enable;
+
+ INTERPOSE_HOOK(trap_hook, getName).apply(enable);
+ INTERPOSE_HOOK(trap_hook, updateAction).apply(enable);
+ INTERPOSE_HOOK(trap_hook, drawBuilding).apply(enable);
+}
+
+static bool makePowerMeter(df::pressure_plate_info *info, int min_power, int max_power, bool invert)
+{
+ CHECK_NULL_POINTER(info);
+
+ if (!enabled)
+ {
+ auto pworld = Core::getInstance().getWorld();
+ auto entry = pworld->GetPersistentData("power-meter/enabled", NULL);
+ if (!entry.isValid())
+ return false;
+
+ enable_hooks(true);
+ }
+
+ init_plate_info(*info);
+ info->track_min = min_power;
+ info->track_max = max_power;
+ info->flags.bits.citizens = invert;
+ return true;
+}
+
+DFHACK_PLUGIN_LUA_FUNCTIONS {
+ DFHACK_LUA_FUNCTION(makePowerMeter),
+ DFHACK_LUA_END
+};
+
+DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
+{
+ switch (event) {
+ case SC_MAP_LOADED:
+ {
+ auto pworld = Core::getInstance().getWorld();
+ bool enable = pworld->GetPersistentData("power-meter/enabled").isValid();
+
+ if (enable)
+ {
+ out.print("Enabling the power meter plugin.\n");
+ enable_hooks(true);
+ }
+ }
+ break;
+ case SC_MAP_UNLOADED:
+ enable_hooks(false);
+ break;
+ default:
+ break;
+ }
+
+ return CR_OK;
+}
+
+DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
+{
+ if (Core::getInstance().isMapLoaded())
+ plugin_onstatechange(out, SC_MAP_LOADED);
+
+ return CR_OK;
+}
+
+DFhackCExport command_result plugin_shutdown ( color_ostream &out )
+{
+ enable_hooks(false);
+ return CR_OK;
+}
diff --git a/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua
new file mode 100644
index 00000000..8baf43e7
--- /dev/null
+++ b/scripts/gui/power-meter.lua
@@ -0,0 +1,116 @@
+-- Interface front-end for power-meter plugin.
+
+local utils = require 'utils'
+local gui = require 'gui'
+local guidm = require 'gui.dwarfmode'
+local dlg = require 'gui.dialogs'
+
+local plugin = require('plugins.power-meter')
+local bselector = df.global.ui_build_selector
+
+PowerMeter = defclass(PowerMeter, guidm.MenuOverlay)
+
+PowerMeter.focus_path = 'power-meter'
+
+function PowerMeter:init()
+ self:init_fields{
+ min_power = 0, max_power = -1, invert = false,
+ }
+ guidm.MenuOverlay.init(self)
+ return self
+end
+
+function PowerMeter:onShow()
+ guidm.MenuOverlay.onShow(self)
+
+ -- Send an event to update the errors
+ bselector.plate_info.flags.whole = 0
+ self:sendInputToParent('BUILDING_TRIGGER_ENABLE_WATER')
+end
+
+function PowerMeter:onRenderBody(dc)
+ dc:fill(0,0,dc.width-1,13,gui.CLEAR_PEN)
+ dc:seek(1,1):pen(COLOR_WHITE)
+ dc:string("Power Meter"):newline():newline(1)
+ dc:string("Placement"):newline():newline(1)
+
+ dc:string("Excess power range:")
+
+ dc:newline(3):string("as", COLOR_LIGHTGREEN)
+ dc:string(": Min ")
+ if self.min_power <= 0 then
+ dc:string("(any)")
+ else
+ dc:string(''..self.min_power)
+ end
+
+ dc:newline(3):string("zx", COLOR_LIGHTGREEN)
+ dc:string(": Max ")
+ if self.max_power < 0 then
+ dc:string("(any)")
+ else
+ dc:string(''..self.max_power)
+ end
+ dc:newline():newline(1)
+
+ dc:string("i",COLOR_LIGHTGREEN):string(": ")
+ if self.invert then
+ dc:string("Inverted")
+ else
+ dc:string("Not inverted")
+ end
+end
+
+function PowerMeter:onInput(keys)
+ if keys.CUSTOM_I then
+ self.invert = not self.invert
+ elseif keys.BUILDING_TRIGGER_MIN_WATER_UP then
+ self.min_power = self.min_power + 10
+ elseif keys.BUILDING_TRIGGER_MIN_WATER_DOWN then
+ self.min_power = math.max(0, self.min_power - 10)
+ elseif keys.BUILDING_TRIGGER_MAX_WATER_UP then
+ if self.max_power < 0 then
+ self.max_power = 0
+ else
+ self.max_power = self.max_power + 10
+ end
+ elseif keys.BUILDING_TRIGGER_MAX_WATER_DOWN then
+ self.max_power = math.max(-1, self.max_power - 10)
+ elseif keys.LEAVESCREEN then
+ self:dismiss()
+ self:sendInputToParent('LEAVESCREEN')
+ elseif keys.SELECT then
+ if #bselector.errors == 0 then
+ if not plugin.makePowerMeter(
+ bselector.plate_info,
+ self.min_power, self.max_power, self.invert
+ )
+ then
+ dlg.showMessage(
+ 'Power Meter',
+ 'Could not initialize.', COLOR_LIGHTRED
+ )
+
+ self:dismiss()
+ self:sendInputToParent('LEAVESCREEN')
+ return
+ end
+
+ self:sendInputToParent('SELECT')
+ if bselector.stage ~= 1 then
+ self:dismiss()
+ end
+ end
+ elseif self:propagateMoveKeys(keys) then
+ return
+ end
+end
+
+if dfhack.gui.getCurFocus() ~= 'dwarfmode/Build/Position/Trap'
+or bselector.building_subtype ~= df.trap_type.PressurePlate
+then
+ qerror("This script requires the main dwarfmode view in build pressure plate mode")
+end
+
+local list = mkinstance(PowerMeter):init()
+list:show()