Merge branch 'master' of git://github.com/angavrilov/dfhack
commit
5474ccacb6
@ -0,0 +1,61 @@
|
||||
DFHack v0.34.11-r2 (UNRELEASED)
|
||||
|
||||
Internals:
|
||||
- full support for Mac OS X.
|
||||
- a plugin that adds scripting in ruby.
|
||||
- support for interposing virtual methods in DF from C++ plugins.
|
||||
- support for creating new interface screens from C++ and lua.
|
||||
- added various other API functions.
|
||||
Notable bugfixes:
|
||||
- better terminal reset after exit on linux.
|
||||
- seedwatch now works on reclaim.
|
||||
- the sort plugin won't crash on cages anymore.
|
||||
Misc improvements:
|
||||
- autodump: can move items to any walkable tile, not just floors.
|
||||
- stripcaged: by default keep armor, new dumparmor option.
|
||||
- zone: allow non-domesticated birds in nestboxes.
|
||||
- workflow: quality range in constraints.
|
||||
- cleanplants: new command to remove rain water from plants.
|
||||
- liquids: can paint permaflow, i.e. what makes rivers power water wheels.
|
||||
- prospect: pre-embark prospector accounts for caves & magma sea in its estimate.
|
||||
- rename: supports renaming stockpiles, workshops, traps, siege engines.
|
||||
New tweaks:
|
||||
- tweak stable-cursor: keeps exact cursor position between d/k/t/q/v etc menus.
|
||||
- tweak patrol-duty: makes Train orders reduce patrol timer, like the binary patch does.
|
||||
- tweak readable-build-plate: fix unreadable truncation in unit pressure plate build ui.
|
||||
- tweak stable-temp: fixes bug 6012; may improve FPS by 50-100% on a slow item-heavy fort.
|
||||
- tweak fast-heat: speeds up item heating & cooling, thus making stable-temp act faster.
|
||||
New scripts:
|
||||
- fixnaked: removes thoughts about nakedness.
|
||||
- setfps: set FPS cap at runtime, in case you want slow motion or speed-up.
|
||||
- fix/population-cap: run after every migrant wave to prevent exceeding the cap.
|
||||
- fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp.
|
||||
New GUI scripts:
|
||||
- gui/mechanisms: browse mechanism links of the current building.
|
||||
- gui/room-list: browse other rooms owned by the unit when assigning one.
|
||||
- gui/liquids: a GUI front-end for the liquids plugin.
|
||||
- gui/rename: renaming stockpiles, workshops and units via an in-game dialog.
|
||||
- gui/power-meter: front-end for the Power Meter plugin.
|
||||
- gui/siege-engine: front-end for the Siege Engine plugin.
|
||||
Autolabor plugin:
|
||||
- can set nonidle hauler percentage.
|
||||
- broker excluded from all labors when needed at depot.
|
||||
- likewise, anybody with a scheduled diplomat meeting.
|
||||
New Dwarf Manipulator plugin:
|
||||
Open the unit list, and press 'l' to access a Dwarf Therapist like UI in the game.
|
||||
New Steam Engine plugin:
|
||||
Dwarven Water Reactors don't make any sense whatsoever, so this is a potential
|
||||
replacement for those concerned by it. The plugin detects if a workshop with a
|
||||
certain name is in the raws used by the current world, and provides the necessary
|
||||
behavior. See hack/raw/*_steam_engine.txt for the necessary raw definitions.
|
||||
Note: Stuff like animal treadmills might be more period, but can't be done with dfhack.
|
||||
New Power Meter plugin:
|
||||
When activated, implements a pressure plate modification that detects power in gear
|
||||
boxes built on the four adjacent N/S/W/E tiles. The gui/power-meter script implements
|
||||
the build configuration UI.
|
||||
New Siege Engine plugin (INCOMPLETE):
|
||||
When enabled and configured via gui/siege-engine, allows aiming siege engines
|
||||
at a designated rectangular area across Z levels. Also supports loading catapults
|
||||
with non-boulder projectiles, taking from a stockpile, and restricting operator
|
||||
skill range, like with ordinary workshops.
|
||||
|
@ -0,0 +1,271 @@
|
||||
-- Some simple dialog screens
|
||||
|
||||
local _ENV = mkmodule('gui.dialogs')
|
||||
|
||||
local gui = require('gui')
|
||||
local utils = require('utils')
|
||||
|
||||
local dscreen = dfhack.screen
|
||||
|
||||
MessageBox = defclass(MessageBox, gui.FramedScreen)
|
||||
|
||||
MessageBox.focus_path = 'MessageBox'
|
||||
MessageBox.frame_style = gui.GREY_LINE_FRAME
|
||||
|
||||
function MessageBox:init(info)
|
||||
info = info or {}
|
||||
self:init_fields{
|
||||
text = info.text or {},
|
||||
frame_title = info.title,
|
||||
frame_width = info.frame_width,
|
||||
on_accept = info.on_accept,
|
||||
on_cancel = info.on_cancel,
|
||||
on_close = info.on_close,
|
||||
text_pen = info.text_pen
|
||||
}
|
||||
if type(self.text) == 'string' then
|
||||
self.text = utils.split_string(self.text, "\n")
|
||||
end
|
||||
gui.FramedScreen.init(self, info)
|
||||
return self
|
||||
end
|
||||
|
||||
function MessageBox:getWantedFrameSize()
|
||||
local text = self.text
|
||||
local w = #(self.frame_title or '') + 4
|
||||
w = math.max(w, 20)
|
||||
w = math.max(self.frame_width or w, w)
|
||||
for _, l in ipairs(text) do
|
||||
w = math.max(w, #l)
|
||||
end
|
||||
local h = #text+1
|
||||
if h > 1 then
|
||||
h = h+1
|
||||
end
|
||||
return w+2, #text+2
|
||||
end
|
||||
|
||||
function MessageBox:onRenderBody(dc)
|
||||
if #self.text > 0 then
|
||||
dc:newline(1):pen(self.text_pen or COLOR_GREY)
|
||||
for _, l in ipairs(self.text or {}) do
|
||||
dc:string(l):newline(1)
|
||||
end
|
||||
end
|
||||
|
||||
if self.on_accept then
|
||||
local x,y = self.frame_rect.x1+1, self.frame_rect.y2+1
|
||||
dscreen.paintString({fg=COLOR_LIGHTGREEN},x,y,'ESC')
|
||||
dscreen.paintString({fg=COLOR_GREY},x+3,y,'/')
|
||||
dscreen.paintString({fg=COLOR_LIGHTGREEN},x+4,y,'y')
|
||||
end
|
||||
end
|
||||
|
||||
function MessageBox:onDestroy()
|
||||
if self.on_close then
|
||||
self.on_close()
|
||||
end
|
||||
end
|
||||
|
||||
function MessageBox:onInput(keys)
|
||||
if keys.MENU_CONFIRM then
|
||||
self:dismiss()
|
||||
if self.on_accept then
|
||||
self.on_accept()
|
||||
end
|
||||
elseif keys.LEAVESCREEN or (keys.SELECT and not self.on_accept) then
|
||||
self:dismiss()
|
||||
if self.on_cancel then
|
||||
self.on_cancel()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function showMessage(title, text, tcolor, on_close)
|
||||
mkinstance(MessageBox):init{
|
||||
text = text,
|
||||
title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
on_close = on_close
|
||||
}:show()
|
||||
end
|
||||
|
||||
function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel)
|
||||
mkinstance(MessageBox):init{
|
||||
title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
on_accept = on_accept,
|
||||
on_cancel = on_cancel,
|
||||
}:show()
|
||||
end
|
||||
|
||||
InputBox = defclass(InputBox, MessageBox)
|
||||
|
||||
InputBox.focus_path = 'InputBox'
|
||||
|
||||
function InputBox:init(info)
|
||||
info = info or {}
|
||||
self:init_fields{
|
||||
input = info.input or '',
|
||||
input_pen = info.input_pen,
|
||||
on_input = info.on_input,
|
||||
}
|
||||
MessageBox.init(self, info)
|
||||
self.on_accept = nil
|
||||
return self
|
||||
end
|
||||
|
||||
function InputBox:getWantedFrameSize()
|
||||
local mw, mh = MessageBox.getWantedFrameSize(self)
|
||||
return mw, mh+2
|
||||
end
|
||||
|
||||
function InputBox:onRenderBody(dc)
|
||||
MessageBox.onRenderBody(self, dc)
|
||||
|
||||
dc:newline(1)
|
||||
dc:pen(self.input_pen or COLOR_LIGHTCYAN)
|
||||
dc:fill(1,dc:localY(),dc.width-2,dc:localY())
|
||||
|
||||
local cursor = '_'
|
||||
if math.floor(dfhack.getTickCount()/300) % 2 == 0 then
|
||||
cursor = ' '
|
||||
end
|
||||
local txt = self.input .. cursor
|
||||
if #txt > dc.width-2 then
|
||||
txt = string.char(27)..string.sub(txt, #txt-dc.width+4)
|
||||
end
|
||||
dc:string(txt)
|
||||
end
|
||||
|
||||
function InputBox:onInput(keys)
|
||||
if keys.SELECT then
|
||||
self:dismiss()
|
||||
if self.on_input then
|
||||
self.on_input(self.input)
|
||||
end
|
||||
elseif keys.LEAVESCREEN then
|
||||
self:dismiss()
|
||||
if self.on_cancel then
|
||||
self.on_cancel()
|
||||
end
|
||||
elseif keys._STRING then
|
||||
if keys._STRING == 0 then
|
||||
self.input = string.sub(self.input, 1, #self.input-1)
|
||||
else
|
||||
self.input = self.input .. string.char(keys._STRING)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width)
|
||||
mkinstance(InputBox):init{
|
||||
title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
input = input,
|
||||
on_input = on_input,
|
||||
on_cancel = on_cancel,
|
||||
frame_width = min_width,
|
||||
}:show()
|
||||
end
|
||||
|
||||
ListBox = defclass(ListBox, MessageBox)
|
||||
|
||||
ListBox.focus_path = 'ListBox'
|
||||
|
||||
function ListBox:init(info)
|
||||
info = info or {}
|
||||
self:init_fields{
|
||||
selection = info.selection or 0,
|
||||
choices = info.choices or {},
|
||||
select_pen = info.select_pen,
|
||||
on_input = info.on_input,
|
||||
page_top = 0
|
||||
}
|
||||
MessageBox.init(self, info)
|
||||
self.on_accept = nil
|
||||
return self
|
||||
end
|
||||
|
||||
function ListBox:getWantedFrameSize()
|
||||
local mw, mh = MessageBox.getWantedFrameSize(self)
|
||||
return mw, mh+#self.choices
|
||||
end
|
||||
|
||||
function ListBox:onRenderBody(dc)
|
||||
MessageBox.onRenderBody(self, dc)
|
||||
|
||||
dc:newline(1)
|
||||
|
||||
if self.selection>dc.height-3 then
|
||||
self.page_top=self.selection-(dc.height-3)
|
||||
elseif self.selection<self.page_top and self.selection >0 then
|
||||
self.page_top=self.selection-1
|
||||
end
|
||||
for i,entry in ipairs(self.choices) do
|
||||
if type(entry)=="table" then
|
||||
entry=entry[1]
|
||||
end
|
||||
if i>self.page_top then
|
||||
if i == self.selection then
|
||||
dc:pen(self.select_pen or COLOR_LIGHTCYAN)
|
||||
else
|
||||
dc:pen(self.text_pen or COLOR_GREY)
|
||||
end
|
||||
dc:string(entry)
|
||||
dc:newline(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
function ListBox:moveCursor(delta)
|
||||
local newsel=self.selection+delta
|
||||
if #self.choices ~=0 then
|
||||
if newsel<1 or newsel>#self.choices then
|
||||
newsel=newsel % #self.choices
|
||||
end
|
||||
end
|
||||
self.selection=newsel
|
||||
end
|
||||
function ListBox:onInput(keys)
|
||||
if keys.SELECT then
|
||||
self:dismiss()
|
||||
local choice=self.choices[self.selection]
|
||||
if self.on_input then
|
||||
self.on_input(self.selection,choice)
|
||||
end
|
||||
|
||||
if choice and choice[2] then
|
||||
choice[2](choice,self.selection) -- maybe reverse the arguments?
|
||||
end
|
||||
elseif keys.LEAVESCREEN then
|
||||
self:dismiss()
|
||||
if self.on_cancel then
|
||||
self.on_cancel()
|
||||
end
|
||||
elseif keys.CURSOR_UP then
|
||||
self:moveCursor(-1)
|
||||
elseif keys.CURSOR_DOWN then
|
||||
self:moveCursor(1)
|
||||
elseif keys.CURSOR_UP_FAST then
|
||||
self:moveCursor(-10)
|
||||
elseif keys.CURSOR_DOWN_FAST then
|
||||
self:moveCursor(10)
|
||||
end
|
||||
end
|
||||
|
||||
function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width)
|
||||
mkinstance(ListBox):init{
|
||||
title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
choices = choices,
|
||||
on_input = on_input,
|
||||
on_cancel = on_cancel,
|
||||
frame_width = min_width,
|
||||
}:show()
|
||||
end
|
||||
|
||||
return _ENV
|
@ -1 +1 @@
|
||||
Subproject commit 328a8dbdc7d9e1e838798abf79861cc18a387e3f
|
||||
Subproject commit 2bc8fbdf71143398817d31e06e169a01cce37c50
|
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
BUILD_DIR=`pwd`
|
||||
|
||||
echo "Fixing library dependencies in $BUILD_DIR/library"
|
||||
|
||||
install_name_tool -change $BUILD_DIR/library/libdfhack.1.0.0.dylib @executable_path/hack/libdfhack.1.0.0.dylib library/libdfhack.1.0.0.dylib
|
||||
install_name_tool -change $BUILD_DIR/library/libdfhack-client.dylib @executable_path/hack/libdfhack-client.dylib library/libdfhack-client.dylib
|
||||
install_name_tool -change $BUILD_DIR/library/libdfhack-client.dylib @executable_path/hack/libdfhack-client.dylib library/dfhack-run
|
||||
install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/libdfhack.1.0.0.dylib
|
||||
install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/libdfhack-client.dylib
|
||||
install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/dfhack-run
|
||||
install_name_tool -change $BUILD_DIR/depends/lua/liblua.dylib @executable_path/hack/liblua.dylib library/libdfhack.1.0.0.dylib
|
||||
install_name_tool -change @executable_path/../Frameworks/SDL.framework/Versions/A/SDL @executable_path/libs/SDL.framework/Versions/A/SDL library/libdfhack.1.0.0.dylib
|
||||
install_name_tool -change /usr/local/lib/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack.1.0.0.dylib
|
||||
install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack.1.0.0.dylib
|
||||
install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack-client.dylib
|
||||
install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/dfhack-run
|
||||
install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/libdfhack.1.0.0.dylib
|
||||
install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/libdfhack-client.dylib
|
||||
install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/dfhack-run
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
local _ENV = mkmodule('plugins.power-meter')
|
||||
|
||||
--[[
|
||||
|
||||
Native functions:
|
||||
|
||||
* makePowerMeter(plate_info,min_power,max_power,invert)
|
||||
|
||||
--]]
|
||||
|
||||
return _ENV
|
@ -0,0 +1,13 @@
|
||||
local _ENV = mkmodule('plugins.rename')
|
||||
|
||||
--[[
|
||||
|
||||
Native functions:
|
||||
|
||||
* canRenameBuilding(building)
|
||||
* isRenamingBuilding(building)
|
||||
* renameBuilding(building, name)
|
||||
|
||||
--]]
|
||||
|
||||
return _ENV
|
@ -0,0 +1,45 @@
|
||||
local _ENV = mkmodule('plugins.siege-engine')
|
||||
|
||||
--[[
|
||||
|
||||
Native functions:
|
||||
|
||||
* getTargetArea(building) -> point1, point2
|
||||
* clearTargetArea(building)
|
||||
* setTargetArea(building, point1, point2) -> true/false
|
||||
|
||||
--]]
|
||||
|
||||
Z_STEP_COUNT = 15
|
||||
Z_STEP = 1/31
|
||||
|
||||
function findShotHeight(engine, target)
|
||||
local path = { target = target, delta = 0.0 }
|
||||
|
||||
if projPathMetrics(engine, path).goal_step then
|
||||
return path
|
||||
end
|
||||
|
||||
for i = 1,Z_STEP_COUNT do
|
||||
path.delta = i*Z_STEP
|
||||
if projPathMetrics(engine, path).goal_step then
|
||||
return path
|
||||
end
|
||||
|
||||
path.delta = -i*Z_STEP
|
||||
if projPathMetrics(engine, path).goal_step then
|
||||
return path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function doAimProjectile(engine, item, target_min, target_max, skill)
|
||||
print(item, df.skill_rating[skill])
|
||||
local targets = proposeUnitHits(engine)
|
||||
if #targets > 0 then
|
||||
local rnd = math.random(#targets)
|
||||
return findShotHeight(engine, targets[rnd].pos)
|
||||
end
|
||||
end
|
||||
|
||||
return _ENV
|
@ -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;
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
building_steam_engine
|
||||
|
||||
[OBJECT:BUILDING]
|
||||
|
||||
[BUILDING_WORKSHOP:STEAM_ENGINE]
|
||||
[NAME:Steam Engine]
|
||||
[NAME_COLOR:4:0:1]
|
||||
[DIM:3:3]
|
||||
[WORK_LOCATION:2:3]
|
||||
[BUILD_LABOR:MECHANIC]
|
||||
[BUILD_KEY:CUSTOM_ALT_S]
|
||||
[BLOCK:1:1:1:1]
|
||||
[BLOCK:2:1:1:1]
|
||||
[BLOCK:3:1:0:1]
|
||||
[TILE:0:1:240:' ':254]
|
||||
[TILE:0:2:' ':' ':128]
|
||||
[TILE:0:3:246:' ':' ']
|
||||
[COLOR:0:1:6:0:0:0:0:0:7:0:0]
|
||||
[COLOR:0:2:0:0:0:0:0:0:7:0:0]
|
||||
[COLOR:0:3:MAT:0:0:0:0:0:0]
|
||||
[TILE:1:1:246:128:' ']
|
||||
[TILE:1:2:' ':' ':254]
|
||||
[TILE:1:3:254:'/':240]
|
||||
[COLOR:1:1:MAT:7:0:0:0:0:0]
|
||||
[COLOR:1:2:0:0:0:0:0:0:7:0:0]
|
||||
[COLOR:1:3:7:0:0:6:0:0:6:0:0]
|
||||
[TILE:2:1:21:' ':128]
|
||||
[TILE:2:2:128:' ':246]
|
||||
[TILE:2:3:177:19:177]
|
||||
[COLOR:2:1:6:0:0:0:0:0:7:0:0]
|
||||
[COLOR:2:2:7:0:0:0:0:0:MAT]
|
||||
[COLOR:2:3:7:0:0:6:0:0:7:0:0]
|
||||
Tile 15 marks places where machines can connect.
|
||||
Tile 19 marks the hearth (color changed to reflect power).
|
||||
[TILE:3:1:15:246:15]
|
||||
[TILE:3:2:'\':19:'/']
|
||||
[TILE:3:3:7:' ':7]
|
||||
Color 1:?:1 water indicator, 4:?:1 magma indicator:
|
||||
[COLOR:3:1:7:0:0:MAT:7:0:0]
|
||||
[COLOR:3:2:6:0:0:0:0:1:6:0:0]
|
||||
[COLOR:3:3:1:7:1:0:0:0:4:7:1]
|
||||
[BUILD_ITEM:1:BARREL:NONE:INORGANIC:NONE][EMPTY][CAN_USE_ARTIFACT]
|
||||
[BUILD_ITEM:1:PIPE_SECTION:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
|
||||
[BUILD_ITEM:1:TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON:INORGANIC:NONE][CAN_USE_ARTIFACT]
|
||||
[BUILD_ITEM:1:CHAIN:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
|
||||
[BUILD_ITEM:1:TRAPPARTS:NONE:NONE:NONE][CAN_USE_ARTIFACT]
|
||||
[BUILD_ITEM:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][FIRE_BUILD_SAFE]
|
||||
|
||||
[BUILDING_WORKSHOP:MAGMA_STEAM_ENGINE]
|
||||
[NAME:Magma Steam Engine]
|
||||
[NAME_COLOR:4:0:1]
|
||||
[DIM:3:3]
|
||||
[WORK_LOCATION:2:3]
|
||||
[BUILD_LABOR:MECHANIC]
|
||||
[BUILD_KEY:CUSTOM_ALT_E]
|
||||
[NEEDS_MAGMA]
|
||||
[BLOCK:1:1:1:1]
|
||||
[BLOCK:2:1:1:1]
|
||||
[BLOCK:3:1:0:1]
|
||||
[TILE:0:1:240:' ':254]
|
||||
[TILE:0:2:' ':' ':128]
|
||||
[TILE:0:3:246:' ':' ']
|
||||
[COLOR:0:1:6:0:0:0:0:0:7:0:0]
|
||||
[COLOR:0:2:0:0:0:0:0:0:7:0:0]
|
||||
[COLOR:0:3:MAT:0:0:0:0:0:0]
|
||||
[TILE:1:1:246:128:' ']
|
||||
[TILE:1:2:' ':' ':254]
|
||||
[TILE:1:3:254:'/':240]
|
||||
[COLOR:1:1:MAT:7:0:0:0:0:0]
|
||||
[COLOR:1:2:0:0:0:0:0:0:7:0:0]
|
||||
[COLOR:1:3:7:0:0:6:0:0:6:0:0]
|
||||
[TILE:2:1:21:' ':128]
|
||||
[TILE:2:2:128:' ':246]
|
||||
[TILE:2:3:177:19:177]
|
||||
[COLOR:2:1:6:0:0:0:0:0:7:0:0]
|
||||
[COLOR:2:2:7:0:0:0:0:0:MAT]
|
||||
[COLOR:2:3:7:0:0:6:0:0:7:0:0]
|
||||
Tile 15 marks places where machines can connect.
|
||||
Tile 19 marks the hearth (color changed to reflect power).
|
||||
[TILE:3:1:15:246:15]
|
||||
[TILE:3:2:'\':19:'/']
|
||||
[TILE:3:3:7:' ':7]
|
||||
Color 1:?:1 water indicator, 4:?:1 magma indicator:
|
||||
[COLOR:3:1:7:0:0:MAT:7:0:0]
|
||||
[COLOR:3:2:6:0:0:0:0:1:6:0:0]
|
||||
[COLOR:3:3:1:7:1:0:0:0:4:7:1]
|
||||
[BUILD_ITEM:1:BARREL:NONE:INORGANIC:NONE][EMPTY][CAN_USE_ARTIFACT]
|
||||
[BUILD_ITEM:1:PIPE_SECTION:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
|
||||
[BUILD_ITEM:1:TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON:INORGANIC:NONE][CAN_USE_ARTIFACT]
|
||||
[BUILD_ITEM:1:CHAIN:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
|
||||
[BUILD_ITEM:1:TRAPPARTS:NONE:NONE:NONE][CAN_USE_ARTIFACT]
|
||||
[BUILD_ITEM:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][MAGMA_BUILD_SAFE]
|
@ -0,0 +1,12 @@
|
||||
item_trapcomp_steam_engine
|
||||
|
||||
[OBJECT:ITEM]
|
||||
|
||||
[ITEM_TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON]
|
||||
[NAME:piston:pistons]
|
||||
[ADJECTIVE:heavy]
|
||||
[SIZE:1800]
|
||||
[HITS:1]
|
||||
[MATERIAL_SIZE:6]
|
||||
[METAL]
|
||||
[ATTACK:BLUNT:40:200:bash:bashes:NO_SUB:2000]
|
@ -0,0 +1,14 @@
|
||||
reaction_steam_engine
|
||||
|
||||
[OBJECT:REACTION]
|
||||
|
||||
[REACTION:STOKE_BOILER]
|
||||
[NAME:stoke the boiler]
|
||||
[BUILDING:STEAM_ENGINE:CUSTOM_S]
|
||||
[BUILDING:MAGMA_STEAM_ENGINE:CUSTOM_S]
|
||||
[FUEL]
|
||||
[SKILL:SMELT]
|
||||
Dimension is the number of days it can produce 100 power * 100.
|
||||
I.e. with 2000 it means energy of 1 job = 1 water wheel for 20 days.
|
||||
[PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:2000]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
Subproject commit 2a62ba5ed2607f4dbf0473e77502d4e19c19678e
|
||||
Subproject commit 37a823541538023b9f3d0d1e8039cf32851de68d
|
@ -0,0 +1,14 @@
|
||||
-- Prints memory ranges of the process.
|
||||
|
||||
for _,v in ipairs(dfhack.internal.getMemRanges()) do
|
||||
local access = { '-', '-', '-', 'p' }
|
||||
if v.read then access[1] = 'r' end
|
||||
if v.write then access[2] = 'w' end
|
||||
if v.execute then access[3] = 'x' end
|
||||
if not v.valid then
|
||||
access[4] = '?'
|
||||
elseif v.shared then
|
||||
access[4] = 's'
|
||||
end
|
||||
print(string.format('%08x-%08x %s %s', v.start_addr, v.end_addr, table.concat(access), v.name))
|
||||
end
|
@ -0,0 +1,3 @@
|
||||
-- For killing bugged out gui script screens.
|
||||
|
||||
dfhack.screen.dismiss(dfhack.gui.getCurViewscreen())
|
@ -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()
|
@ -0,0 +1,63 @@
|
||||
-- Rename various objects via gui.
|
||||
|
||||
local gui = require 'gui'
|
||||
local dlg = require 'gui.dialogs'
|
||||
local plugin = require 'plugins.rename'
|
||||
|
||||
local mode = ...
|
||||
local focus = dfhack.gui.getCurFocus()
|
||||
|
||||
local function verify_mode(expected)
|
||||
if mode ~= nil and mode ~= expected then
|
||||
qerror('Invalid UI state for mode '..mode)
|
||||
end
|
||||
end
|
||||
|
||||
if string.match(focus, '^dwarfmode/QueryBuilding/Some') then
|
||||
verify_mode('building')
|
||||
|
||||
local building = df.global.world.selected_building
|
||||
if plugin.canRenameBuilding(building) then
|
||||
dlg.showInputPrompt(
|
||||
'Rename Building',
|
||||
'Enter a new name for the building:', COLOR_GREEN,
|
||||
building.name,
|
||||
curry(plugin.renameBuilding, building)
|
||||
)
|
||||
else
|
||||
dlg.showMessage(
|
||||
'Rename Building',
|
||||
'Cannot rename this type of building.', COLOR_LIGHTRED
|
||||
)
|
||||
end
|
||||
elseif dfhack.gui.getSelectedUnit(true) then
|
||||
local unit = dfhack.gui.getSelectedUnit(true)
|
||||
|
||||
if mode == 'unit-profession' then
|
||||
dlg.showInputPrompt(
|
||||
'Rename Unit',
|
||||
'Enter a new profession for the unit:', COLOR_GREEN,
|
||||
unit.custom_profession,
|
||||
function(newval)
|
||||
unit.custom_profession = newval
|
||||
end
|
||||
)
|
||||
else
|
||||
verify_mode('unit')
|
||||
|
||||
local vname = dfhack.units.getVisibleName(unit)
|
||||
local vnick = ''
|
||||
if vname and vname.has_name then
|
||||
vnick = vname.nickname
|
||||
end
|
||||
|
||||
dlg.showInputPrompt(
|
||||
'Rename Unit',
|
||||
'Enter a new nickname for the unit:', COLOR_GREEN,
|
||||
vnick,
|
||||
curry(dfhack.units.setNickname, unit)
|
||||
)
|
||||
end
|
||||
elseif mode then
|
||||
verify_mode(nil)
|
||||
end
|
@ -0,0 +1,490 @@
|
||||
-- Front-end for the siege engine plugin.
|
||||
|
||||
local utils = require 'utils'
|
||||
local gui = require 'gui'
|
||||
local guidm = require 'gui.dwarfmode'
|
||||
local dlg = require 'gui.dialogs'
|
||||
|
||||
local plugin = require 'plugins.siege-engine'
|
||||
local wmap = df.global.world.map
|
||||
|
||||
local LEGENDARY = df.skill_rating.Legendary
|
||||
|
||||
-- Globals kept between script calls
|
||||
last_target_min = last_target_min or nil
|
||||
last_target_max = last_target_max or nil
|
||||
|
||||
local item_choices = {
|
||||
{ caption = 'boulders (default)', item_type = df.item_type.BOULDER },
|
||||
{ caption = 'blocks', item_type = df.item_type.BLOCKS },
|
||||
{ caption = 'weapons', item_type = df.item_type.WEAPON },
|
||||
{ caption = 'trap components', item_type = df.item_type.TRAPCOMP },
|
||||
{ caption = 'bins', item_type = df.item_type.BIN },
|
||||
{ caption = 'barrels', item_type = df.item_type.BARREL },
|
||||
{ caption = 'anything', item_type = -1 },
|
||||
}
|
||||
|
||||
local item_choice_idx = {}
|
||||
for i,v in ipairs(item_choices) do
|
||||
item_choice_idx[v.item_type] = i
|
||||
end
|
||||
|
||||
SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay)
|
||||
|
||||
SiegeEngine.focus_path = 'siege-engine'
|
||||
|
||||
function SiegeEngine:init(building)
|
||||
self:init_fields{
|
||||
building = building,
|
||||
center = utils.getBuildingCenter(building),
|
||||
selected_pile = 1,
|
||||
}
|
||||
guidm.MenuOverlay.init(self)
|
||||
self.mode_main = {
|
||||
render = self:callback 'onRenderBody_main',
|
||||
input = self:callback 'onInput_main',
|
||||
}
|
||||
self.mode_aim = {
|
||||
render = self:callback 'onRenderBody_aim',
|
||||
input = self:callback 'onInput_aim',
|
||||
}
|
||||
self.mode_pile = {
|
||||
render = self:callback 'onRenderBody_pile',
|
||||
input = self:callback 'onInput_pile',
|
||||
}
|
||||
return self
|
||||
end
|
||||
|
||||
function SiegeEngine:onShow()
|
||||
guidm.MenuOverlay.onShow(self)
|
||||
|
||||
self.old_cursor = guidm.getCursorPos()
|
||||
self.old_viewport = self:getViewport()
|
||||
|
||||
self.mode = self.mode_main
|
||||
self:showCursor(false)
|
||||
end
|
||||
|
||||
function SiegeEngine:onDestroy()
|
||||
if self.save_profile then
|
||||
plugin.saveWorkshopProfile(self.building)
|
||||
end
|
||||
if not self.no_select_building then
|
||||
self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10)
|
||||
end
|
||||
end
|
||||
|
||||
function SiegeEngine:showCursor(enable)
|
||||
local cursor = guidm.getCursorPos()
|
||||
if cursor and not enable then
|
||||
self.cursor = cursor
|
||||
self.target_select_first = nil
|
||||
guidm.clearCursorPos()
|
||||
elseif not cursor and enable then
|
||||
local view = self:getViewport()
|
||||
cursor = self.cursor
|
||||
if not cursor or not view:isVisible(cursor) then
|
||||
cursor = view:getCenter()
|
||||
end
|
||||
self.cursor = nil
|
||||
guidm.setCursorPos(cursor)
|
||||
end
|
||||
end
|
||||
|
||||
function SiegeEngine:centerViewOn(pos)
|
||||
local cursor = guidm.getCursorPos()
|
||||
if cursor then
|
||||
guidm.setCursorPos(pos)
|
||||
else
|
||||
self.cursor = pos
|
||||
end
|
||||
self:getViewport():centerOn(pos):set()
|
||||
end
|
||||
|
||||
function SiegeEngine:zoomToTarget()
|
||||
local target_min, target_max = plugin.getTargetArea(self.building)
|
||||
if target_min then
|
||||
local cx = math.floor((target_min.x + target_max.x)/2)
|
||||
local cy = math.floor((target_min.y + target_max.y)/2)
|
||||
local cz = math.floor((target_min.z + target_max.z)/2)
|
||||
local pos = plugin.adjustToTarget(self.building, xyz2pos(cx,cy,cz))
|
||||
self:centerViewOn(pos)
|
||||
end
|
||||
end
|
||||
|
||||
function paint_target_grid(dc, view, origin, p1, p2)
|
||||
local r1, sz, r2 = guidm.getSelectionRange(p1, p2)
|
||||
|
||||
if view.z < r1.z or view.z > r2.z then
|
||||
return
|
||||
end
|
||||
|
||||
local p1 = view:tileToScreen(r1)
|
||||
local p2 = view:tileToScreen(r2)
|
||||
local org = view:tileToScreen(origin)
|
||||
dc:pen{ fg = COLOR_CYAN, bg = COLOR_CYAN, ch = '+', bold = true }
|
||||
|
||||
-- Frame
|
||||
dc:fill(p1.x,p1.y,p1.x,p2.y)
|
||||
dc:fill(p1.x,p1.y,p2.x,p1.y)
|
||||
dc:fill(p2.x,p1.y,p2.x,p2.y)
|
||||
dc:fill(p1.x,p2.y,p2.x,p2.y)
|
||||
|
||||
-- Grid
|
||||
local gxmin = org.x+10*math.ceil((p1.x-org.x)/10)
|
||||
local gxmax = org.x+10*math.floor((p2.x-org.x)/10)
|
||||
local gymin = org.y+10*math.ceil((p1.y-org.y)/10)
|
||||
local gymax = org.y+10*math.floor((p2.y-org.y)/10)
|
||||
for x = gxmin,gxmax,10 do
|
||||
for y = gymin,gymax,10 do
|
||||
dc:fill(p1.x,y,p2.x,y)
|
||||
dc:fill(x,p1.y,x,p2.y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SiegeEngine:renderTargetView(target_min, target_max)
|
||||
local view = self:getViewport()
|
||||
local map = self.df_layout.map
|
||||
local map_dc = gui.Painter.new(map)
|
||||
|
||||
plugin.paintAimScreen(
|
||||
self.building, view:getPos(),
|
||||
xy2pos(map.x1, map.y1), view:getSize()
|
||||
)
|
||||
|
||||
if target_min and math.floor(dfhack.getTickCount()/500) % 2 == 0 then
|
||||
paint_target_grid(map_dc, view, self.center, target_min, target_max)
|
||||
end
|
||||
|
||||
local cursor = guidm.getCursorPos()
|
||||
if cursor then
|
||||
local cx, cy, cz = pos2xyz(view:tileToScreen(cursor))
|
||||
if cz == 0 then
|
||||
map_dc:seek(cx,cy):char('X', COLOR_YELLOW)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SiegeEngine:scrollPiles(delta)
|
||||
local links = plugin.getStockpileLinks(self.building)
|
||||
if links then
|
||||
self.selected_pile = 1+(self.selected_pile+delta-1) % #links
|
||||
return links[self.selected_pile]
|
||||
end
|
||||
end
|
||||
|
||||
function SiegeEngine:renderStockpiles(dc, links, nlines)
|
||||
local idx = (self.selected_pile-1) % #links
|
||||
local page = math.floor(idx/nlines)
|
||||
for i = page*nlines,math.min(#links,(page+1)*nlines)-1 do
|
||||
local color = COLOR_BROWN
|
||||
if i == idx then
|
||||
color = COLOR_YELLOW
|
||||
end
|
||||
dc:newline(2):string(utils.getBuildingName(links[i+1]), color)
|
||||
end
|
||||
end
|
||||
|
||||
function SiegeEngine:onRenderBody_main(dc)
|
||||
dc:newline(1):pen(COLOR_WHITE):string("Target: ")
|
||||
|
||||
local target_min, target_max = plugin.getTargetArea(self.building)
|
||||
if target_min then
|
||||
dc:string(
|
||||
(target_max.x-target_min.x+1).."x"..
|
||||
(target_max.y-target_min.y+1).."x"..
|
||||
(target_max.z-target_min.z+1).." Rect"
|
||||
)
|
||||
else
|
||||
dc:string("None (default)")
|
||||
end
|
||||
|
||||
dc:newline(3):string("r",COLOR_LIGHTGREEN):string(": Rectangle")
|
||||
if last_target_min then
|
||||
dc:string(", "):string("p",COLOR_LIGHTGREEN):string(": Paste")
|
||||
end
|
||||
dc:newline(3)
|
||||
if target_min then
|
||||
dc:string("x",COLOR_LIGHTGREEN):string(": Clear, ")
|
||||
dc:string("z",COLOR_LIGHTGREEN):string(": Zoom")
|
||||
end
|
||||
|
||||
dc:newline():newline(1)
|
||||
if self.building.type == df.siegeengine_type.Ballista then
|
||||
dc:string("Uses ballista arrows")
|
||||
else
|
||||
local item = plugin.getAmmoItem(self.building)
|
||||
dc:string("u",COLOR_LIGHTGREEN):string(": Use ")
|
||||
if item_choice_idx[item] then
|
||||
dc:string(item_choices[item_choice_idx[item]].caption)
|
||||
else
|
||||
dc:string(df.item_type[item])
|
||||
end
|
||||
end
|
||||
|
||||
dc:newline():newline(1)
|
||||
dc:string("t",COLOR_LIGHTGREEN):string(": Take from stockpile"):newline(3)
|
||||
local links = plugin.getStockpileLinks(self.building)
|
||||
local bottom = dc.height - 5
|
||||
if links then
|
||||
dc:string("d",COLOR_LIGHTGREEN):string(": Delete, ")
|
||||
dc:string("o",COLOR_LIGHTGREEN):string(": Zoom"):newline()
|
||||
self:renderStockpiles(dc, links, bottom-2-dc:localY())
|
||||
dc:newline():newline()
|
||||
end
|
||||
|
||||
local prof = self.building:getWorkshopProfile() or {}
|
||||
dc:seek(1,math.max(dc:localY(),19)):string('ghjk',COLOR_LIGHTGREEN)dc:string(': ')
|
||||
dc:string(df.skill_rating.attrs[prof.min_level or 0].caption):string('-')
|
||||
dc:string(df.skill_rating.attrs[math.min(LEGENDARY,prof.max_level or 3000)].caption)
|
||||
dc:newline():newline()
|
||||
|
||||
if self.target_select_first then
|
||||
self:renderTargetView(self.target_select_first, guidm.getCursorPos())
|
||||
else
|
||||
self:renderTargetView(target_min, target_max)
|
||||
end
|
||||
end
|
||||
|
||||
function SiegeEngine:setTargetArea(p1, p2)
|
||||
self.target_select_first = nil
|
||||
|
||||
if not plugin.setTargetArea(self.building, p1, p2) then
|
||||
dlg.showMessage(
|
||||
'Set Target Area',
|
||||
'Could not set the target area', COLOR_LIGHTRED
|
||||
)
|
||||
else
|
||||
last_target_min = p1
|
||||
last_target_max = p2
|
||||
end
|
||||
end
|
||||
|
||||
function SiegeEngine:setAmmoItem(choice)
|
||||
if self.building.type == df.siegeengine_type.Ballista then
|
||||
return
|
||||
end
|
||||
|
||||
if not plugin.setAmmoItem(self.building, choice.item_type) then
|
||||
dlg.showMessage(
|
||||
'Set Ammo Item',
|
||||
'Could not set the ammo item', COLOR_LIGHTRED
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function SiegeEngine:onInput_main(keys)
|
||||
if keys.CUSTOM_R then
|
||||
self:showCursor(true)
|
||||
self.target_select_first = nil
|
||||
self.mode = self.mode_aim
|
||||
elseif keys.CUSTOM_P and last_target_min then
|
||||
self:setTargetArea(last_target_min, last_target_max)
|
||||
elseif keys.CUSTOM_U then
|
||||
local item = plugin.getAmmoItem(self.building)
|
||||
local idx = 1 + (item_choice_idx[item] or 0) % #item_choices
|
||||
self:setAmmoItem(item_choices[idx])
|
||||
elseif keys.CUSTOM_Z then
|
||||
self:zoomToTarget()
|
||||
elseif keys.CUSTOM_X then
|
||||
plugin.clearTargetArea(self.building)
|
||||
elseif keys.SECONDSCROLL_UP then
|
||||
self:scrollPiles(-1)
|
||||
elseif keys.SECONDSCROLL_DOWN then
|
||||
self:scrollPiles(1)
|
||||
elseif keys.CUSTOM_D then
|
||||
local pile = self:scrollPiles(0)
|
||||
if pile then
|
||||
plugin.removeStockpileLink(self.building, pile)
|
||||
end
|
||||
elseif keys.CUSTOM_O then
|
||||
local pile = self:scrollPiles(0)
|
||||
if pile then
|
||||
self:centerViewOn(utils.getBuildingCenter(pile))
|
||||
end
|
||||
elseif keys.CUSTOM_T then
|
||||
self:showCursor(true)
|
||||
self.mode = self.mode_pile
|
||||
self:sendInputToParent('CURSOR_DOWN_Z')
|
||||
self:sendInputToParent('CURSOR_UP_Z')
|
||||
elseif keys.CUSTOM_G then
|
||||
local prof = plugin.saveWorkshopProfile(self.building)
|
||||
prof.min_level = math.max(0, prof.min_level-1)
|
||||
plugin.saveWorkshopProfile(self.building)
|
||||
elseif keys.CUSTOM_H then
|
||||
local prof = plugin.saveWorkshopProfile(self.building)
|
||||
prof.min_level = math.min(LEGENDARY, prof.min_level+1)
|
||||
plugin.saveWorkshopProfile(self.building)
|
||||
elseif keys.CUSTOM_J then
|
||||
local prof = plugin.saveWorkshopProfile(self.building)
|
||||
prof.max_level = math.max(0, math.min(LEGENDARY,prof.max_level)-1)
|
||||
plugin.saveWorkshopProfile(self.building)
|
||||
elseif keys.CUSTOM_K then
|
||||
local prof = plugin.saveWorkshopProfile(self.building)
|
||||
prof.max_level = math.min(LEGENDARY, prof.max_level+1)
|
||||
if prof.max_level >= LEGENDARY then prof.max_level = 3000 end
|
||||
plugin.saveWorkshopProfile(self.building)
|
||||
elseif self:simulateViewScroll(keys) then
|
||||
self.cursor = nil
|
||||
else
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local status_table = {
|
||||
ok = { pen = COLOR_GREEN, msg = "Target accessible" },
|
||||
out_of_range = { pen = COLOR_CYAN, msg = "Target out of range" },
|
||||
blocked = { pen = COLOR_RED, msg = "Target obstructed" },
|
||||
semi_blocked = { pen = COLOR_BROWN, msg = "Partially obstructed" },
|
||||
}
|
||||
|
||||
function SiegeEngine:onRenderBody_aim(dc)
|
||||
local cursor = guidm.getCursorPos()
|
||||
local first = self.target_select_first
|
||||
|
||||
dc:newline(1):string('Select target rectangle'):newline()
|
||||
|
||||
local info = status_table[plugin.getTileStatus(self.building, cursor)]
|
||||
if info then
|
||||
dc:newline(2):string(info.msg, info.pen)
|
||||
else
|
||||
dc:newline(2):string('ERROR', COLOR_RED)
|
||||
end
|
||||
|
||||
dc:newline():newline(1):string("Enter",COLOR_LIGHTGREEN)
|
||||
if first then
|
||||
dc:string(": Finish rectangle")
|
||||
else
|
||||
dc:string(": Start rectangle")
|
||||
end
|
||||
dc:newline()
|
||||
|
||||
local target_min, target_max = plugin.getTargetArea(self.building)
|
||||
if target_min then
|
||||
dc:newline(1):string("z",COLOR_LIGHTGREEN):string(": Zoom to current target")
|
||||
end
|
||||
|
||||
if first then
|
||||
self:renderTargetView(first, cursor)
|
||||
else
|
||||
local target_min, target_max = plugin.getTargetArea(self.building)
|
||||
self:renderTargetView(target_min, target_max)
|
||||
end
|
||||
end
|
||||
|
||||
function SiegeEngine:onInput_aim(keys)
|
||||
if keys.SELECT then
|
||||
local cursor = guidm.getCursorPos()
|
||||
if self.target_select_first then
|
||||
self:setTargetArea(self.target_select_first, cursor)
|
||||
|
||||
self.mode = self.mode_main
|
||||
self:showCursor(false)
|
||||
else
|
||||
self.target_select_first = cursor
|
||||
end
|
||||
elseif keys.CUSTOM_Z then
|
||||
self:zoomToTarget()
|
||||
elseif keys.LEAVESCREEN then
|
||||
self.mode = self.mode_main
|
||||
self:showCursor(false)
|
||||
elseif self:simulateCursorMovement(keys) then
|
||||
self.cursor = nil
|
||||
else
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function SiegeEngine:onRenderBody_pile(dc)
|
||||
dc:newline(1):string('Select pile to take from'):newline():newline(2)
|
||||
|
||||
local sel = df.global.world.selected_building
|
||||
|
||||
if df.building_stockpilest:is_instance(sel) then
|
||||
dc:string(utils.getBuildingName(sel), COLOR_GREEN):newline():newline(1)
|
||||
|
||||
if plugin.isLinkedToPile(self.building, sel) then
|
||||
dc:string("Already taking from here"):newline():newline(2)
|
||||
dc:string("d", COLOR_LIGHTGREEN):string(": Delete link")
|
||||
else
|
||||
dc:string("Enter",COLOR_LIGHTGREEN):string(": Take from this pile")
|
||||
end
|
||||
elseif sel then
|
||||
dc:string(utils.getBuildingName(sel), COLOR_DARKGREY)
|
||||
dc:newline():newline(1)
|
||||
dc:string("Not a stockpile",COLOR_LIGHTRED)
|
||||
else
|
||||
dc:string("No building selected", COLOR_DARKGREY)
|
||||
end
|
||||
end
|
||||
|
||||
function SiegeEngine:onInput_pile(keys)
|
||||
if keys.SELECT then
|
||||
local sel = df.global.world.selected_building
|
||||
if df.building_stockpilest:is_instance(sel)
|
||||
and not plugin.isLinkedToPile(self.building, sel) then
|
||||
plugin.addStockpileLink(self.building, sel)
|
||||
|
||||
df.global.world.selected_building = self.building
|
||||
self.mode = self.mode_main
|
||||
self:showCursor(false)
|
||||
end
|
||||
elseif keys.CUSTOM_D then
|
||||
local sel = df.global.world.selected_building
|
||||
if df.building_stockpilest:is_instance(sel) then
|
||||
plugin.removeStockpileLink(self.building, sel)
|
||||
end
|
||||
elseif keys.LEAVESCREEN then
|
||||
df.global.world.selected_building = self.building
|
||||
self.mode = self.mode_main
|
||||
self:showCursor(false)
|
||||
elseif self:propagateMoveKeys(keys) then
|
||||
--
|
||||
else
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function SiegeEngine:onRenderBody(dc)
|
||||
dc:clear()
|
||||
dc:seek(1,1):pen(COLOR_WHITE):string(utils.getBuildingName(self.building)):newline()
|
||||
|
||||
self.mode.render(dc)
|
||||
|
||||
dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE)
|
||||
dc:string("ESC", COLOR_LIGHTGREEN):string(": Back, ")
|
||||
dc:string("c", COLOR_LIGHTGREEN):string(": Recenter")
|
||||
end
|
||||
|
||||
function SiegeEngine:onInput(keys)
|
||||
if self.mode.input(keys) then
|
||||
--
|
||||
elseif keys.CUSTOM_C then
|
||||
self:centerViewOn(self.center)
|
||||
elseif keys.LEAVESCREEN then
|
||||
self:dismiss()
|
||||
elseif keys.LEAVESCREEN_ALL then
|
||||
self:dismiss()
|
||||
self.no_select_building = true
|
||||
guidm.clearCursorPos()
|
||||
df.global.ui.main.mode = df.ui_sidebar_mode.Default
|
||||
df.global.world.selected_building = nil
|
||||
end
|
||||
end
|
||||
|
||||
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/SiegeEngine') then
|
||||
qerror("This script requires a siege engine selected in 'q' mode")
|
||||
end
|
||||
|
||||
local building = df.global.world.selected_building
|
||||
|
||||
if not df.building_siegeenginest:is_instance(building) then
|
||||
qerror("A siege engine must be selected")
|
||||
end
|
||||
|
||||
local list = mkinstance(SiegeEngine):init(df.global.world.selected_building)
|
||||
list:show()
|
@ -0,0 +1,10 @@
|
||||
-- Set the FPS cap at runtime.
|
||||
|
||||
local cap = ...
|
||||
local capnum = tonumber(cap)
|
||||
|
||||
if not capnum or capnum < 1 then
|
||||
qerror('Invalid FPS cap value: '..cap)
|
||||
end
|
||||
|
||||
df.global.enabler.fps = capnum
|
Loading…
Reference in New Issue