Merge branch 'master' of https://github.com/angavrilov/dfhack into experimental-dontmerge
Conflicts: dfhack.init-example library/xml plugins/CMakeLists.txtdevelop
commit
a02a120e2d
@ -0,0 +1,75 @@
|
|||||||
|
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.
|
||||||
|
- tweak fix-dimensions: fixes subtracting small amounts from stacked liquids etc.
|
||||||
|
- tweak advmode-contained: fixes UI bug in custom reactions with container inputs in advmode.
|
||||||
|
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 and cause lag, so this may be
|
||||||
|
a 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 absolutely can't be
|
||||||
|
done with tools dfhack has access to.
|
||||||
|
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 necessary build configuration UI.
|
||||||
|
New Siege Engine plugin:
|
||||||
|
When enabled and configured via gui/siege-engine, allows aiming siege engines
|
||||||
|
at a designated rectangular area with 360 degree fire range and across Z levels;
|
||||||
|
this works by rewriting the projectile trajectory immediately after it appears.
|
||||||
|
Also supports loading catapults with non-boulder projectiles, taking from a stockpile,
|
||||||
|
and restricting operator skill range like with ordinary workshops.
|
||||||
|
Disclaimer: not in any way to undermine the future siege update from Toady, but
|
||||||
|
the aiming logic of existing engines hasn't been updated since 2D, and is almost
|
||||||
|
useless above ground :(. Again, things like making siegers bring their own engines
|
||||||
|
is totally out of the scope of dfhack and can only be done by Toady.
|
||||||
|
New Add Spatter plugin:
|
||||||
|
Detects reactions with certain names in the raws, and changes them from adding
|
||||||
|
improvements to adding item contaminants. This allows directly covering items
|
||||||
|
with poisons. The added spatters are immune both to water and 'clean items'.
|
||||||
|
Intended to give some use to all those giant cave spider poison barrels brought
|
||||||
|
by the caravans.
|
||||||
|
|
@ -0,0 +1,150 @@
|
|||||||
|
-- A trivial reloadable class system
|
||||||
|
|
||||||
|
local _ENV = mkmodule('class')
|
||||||
|
|
||||||
|
-- Metatable template for a class
|
||||||
|
class_obj = {} or class_obj
|
||||||
|
|
||||||
|
-- Methods shared by all classes
|
||||||
|
common_methods = {} or common_methods
|
||||||
|
|
||||||
|
-- Forbidden names for class fields and methods.
|
||||||
|
reserved_names = { super = true, ATTRS = true }
|
||||||
|
|
||||||
|
-- Attribute table metatable
|
||||||
|
attrs_meta = {} or attrs_meta
|
||||||
|
|
||||||
|
-- Create or updates a class; a class has metamethods and thus own metatable.
|
||||||
|
function defclass(class,parent)
|
||||||
|
class = class or {}
|
||||||
|
|
||||||
|
local meta = getmetatable(class)
|
||||||
|
if not meta then
|
||||||
|
meta = {}
|
||||||
|
setmetatable(class, meta)
|
||||||
|
end
|
||||||
|
|
||||||
|
for k,v in pairs(class_obj) do meta[k] = v end
|
||||||
|
|
||||||
|
meta.__index = parent or common_methods
|
||||||
|
|
||||||
|
local attrs = rawget(class, 'ATTRS') or {}
|
||||||
|
setmetatable(attrs, attrs_meta)
|
||||||
|
|
||||||
|
rawset(class, 'super', parent)
|
||||||
|
rawset(class, 'ATTRS', attrs)
|
||||||
|
rawset(class, '__index', rawget(class, '__index') or class)
|
||||||
|
|
||||||
|
return class
|
||||||
|
end
|
||||||
|
|
||||||
|
-- An instance uses the class as metatable
|
||||||
|
function mkinstance(class,table)
|
||||||
|
table = table or {}
|
||||||
|
setmetatable(table, class)
|
||||||
|
return table
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Patch the stubs in the global environment
|
||||||
|
dfhack.BASE_G.defclass = _ENV.defclass
|
||||||
|
dfhack.BASE_G.mkinstance = _ENV.mkinstance
|
||||||
|
|
||||||
|
-- Just verify the name, and then set.
|
||||||
|
function class_obj:__newindex(name,val)
|
||||||
|
if reserved_names[name] or common_methods[name] then
|
||||||
|
error('Method name '..name..' is reserved.')
|
||||||
|
end
|
||||||
|
rawset(self, name, val)
|
||||||
|
end
|
||||||
|
|
||||||
|
function attrs_meta:__call(attrs)
|
||||||
|
for k,v in pairs(attrs) do
|
||||||
|
self[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function apply_attrs(obj, attrs, init_table)
|
||||||
|
for k,v in pairs(attrs) do
|
||||||
|
if v == DEFAULT_NIL then
|
||||||
|
v = nil
|
||||||
|
end
|
||||||
|
obj[k] = init_table[k] or v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function invoke_before_rec(self, class, method, ...)
|
||||||
|
local meta = getmetatable(class)
|
||||||
|
if meta then
|
||||||
|
local fun = rawget(class, method)
|
||||||
|
if fun then
|
||||||
|
fun(self, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
invoke_before_rec(self, meta.__index, method, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function invoke_after_rec(self, class, method, ...)
|
||||||
|
local meta = getmetatable(class)
|
||||||
|
if meta then
|
||||||
|
invoke_after_rec(self, meta.__index, method, ...)
|
||||||
|
|
||||||
|
local fun = rawget(class, method)
|
||||||
|
if fun then
|
||||||
|
fun(self, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function init_attrs_rec(obj, class, init_table)
|
||||||
|
local meta = getmetatable(class)
|
||||||
|
if meta then
|
||||||
|
init_attrs_rec(obj, meta.__index, init_table)
|
||||||
|
apply_attrs(obj, rawget(class, 'ATTRS'), init_table)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Call metamethod constructs the object
|
||||||
|
function class_obj:__call(init_table)
|
||||||
|
-- The table is assumed to be a scratch temporary.
|
||||||
|
-- If it is not, copy it yourself before calling.
|
||||||
|
init_table = init_table or {}
|
||||||
|
|
||||||
|
local obj = mkinstance(self)
|
||||||
|
|
||||||
|
-- This initialization sequence is broadly based on how the
|
||||||
|
-- Common Lisp initialize-instance generic function works.
|
||||||
|
|
||||||
|
-- preinit screens input arguments in subclass to superclass order
|
||||||
|
invoke_before_rec(obj, self, 'preinit', init_table)
|
||||||
|
-- initialize the instance table from init table
|
||||||
|
init_attrs_rec(obj, self, init_table)
|
||||||
|
-- init in superclass -> subclass
|
||||||
|
invoke_after_rec(obj, self, 'init', init_table)
|
||||||
|
-- postinit in superclass -> subclass
|
||||||
|
invoke_after_rec(obj, self, 'postinit', init_table)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common methods for all instances:
|
||||||
|
|
||||||
|
function common_methods:callback(method, ...)
|
||||||
|
return dfhack.curry(self[method], self, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function common_methods:assign(data)
|
||||||
|
for k,v in pairs(data) do
|
||||||
|
self[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function common_methods:invoke_before(method, ...)
|
||||||
|
return invoke_before_rec(self, getmetatable(self), method, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function common_methods:invoke_after(method, ...)
|
||||||
|
return invoke_after_rec(self, getmetatable(self), method, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
return _ENV
|
@ -1 +1 @@
|
|||||||
Subproject commit df8178a989373ec7868d9195d82ae5f85145ef81
|
Subproject commit a914f3b7558335d53c0ac93f6e7267906a33cd29
|
@ -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
|
@ -0,0 +1,433 @@
|
|||||||
|
#include "Core.h"
|
||||||
|
#include <Console.h>
|
||||||
|
#include <Export.h>
|
||||||
|
#include <PluginManager.h>
|
||||||
|
#include <modules/Gui.h>
|
||||||
|
#include <modules/Screen.h>
|
||||||
|
#include <modules/Maps.h>
|
||||||
|
#include <modules/Job.h>
|
||||||
|
#include <modules/Items.h>
|
||||||
|
#include <modules/Units.h>
|
||||||
|
#include <TileTypes.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <stack>
|
||||||
|
#include <string>
|
||||||
|
#include <cmath>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <VTableInterpose.h>
|
||||||
|
#include "df/item_liquid_miscst.h"
|
||||||
|
#include "df/item_constructed.h"
|
||||||
|
#include "df/builtin_mats.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/job.h"
|
||||||
|
#include "df/job_item.h"
|
||||||
|
#include "df/job_item_ref.h"
|
||||||
|
#include "df/ui.h"
|
||||||
|
#include "df/report.h"
|
||||||
|
#include "df/reaction.h"
|
||||||
|
#include "df/reaction_reagent_itemst.h"
|
||||||
|
#include "df/reaction_product_item_improvementst.h"
|
||||||
|
#include "df/reaction_product_improvement_flags.h"
|
||||||
|
#include "df/matter_state.h"
|
||||||
|
#include "df/contaminant.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;
|
||||||
|
|
||||||
|
typedef df::reaction_product_item_improvementst improvement_product;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("add-spatter");
|
||||||
|
|
||||||
|
struct ReagentSource {
|
||||||
|
int idx;
|
||||||
|
df::reaction_reagent *reagent;
|
||||||
|
|
||||||
|
ReagentSource() : idx(-1), reagent(NULL) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MaterialSource : ReagentSource {
|
||||||
|
bool product;
|
||||||
|
std::string product_name;
|
||||||
|
|
||||||
|
int mat_type, mat_index;
|
||||||
|
|
||||||
|
MaterialSource() : product(false), mat_type(-1), mat_index(-1) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ProductInfo {
|
||||||
|
df::reaction *react;
|
||||||
|
improvement_product *product;
|
||||||
|
|
||||||
|
ReagentSource object;
|
||||||
|
MaterialSource material;
|
||||||
|
|
||||||
|
bool isValid() {
|
||||||
|
return object.reagent && (material.mat_type >= 0 || material.reagent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReactionInfo {
|
||||||
|
df::reaction *react;
|
||||||
|
|
||||||
|
std::vector<ProductInfo> products;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::map<std::string, ReactionInfo> reactions;
|
||||||
|
static std::map<df::reaction_product*, ProductInfo*> products;
|
||||||
|
|
||||||
|
static ReactionInfo *find_reaction(const std::string &name)
|
||||||
|
{
|
||||||
|
auto it = reactions.find(name);
|
||||||
|
return (it != reactions.end()) ? &it->second : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_add_spatter(const std::string &name)
|
||||||
|
{
|
||||||
|
return name.size() > 12 && memcmp(name.data(), "SPATTER_ADD_", 12) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void find_material(int *type, int *index, df::item *input, MaterialSource &mat)
|
||||||
|
{
|
||||||
|
if (input && mat.reagent)
|
||||||
|
{
|
||||||
|
MaterialInfo info(input);
|
||||||
|
|
||||||
|
if (mat.product)
|
||||||
|
{
|
||||||
|
if (!info.findProduct(info, mat.product_name))
|
||||||
|
{
|
||||||
|
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||||
|
out.printerr("Cannot find product '%s'\n", mat.product_name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*type = info.type;
|
||||||
|
*index = info.index;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*type = mat.mat_type;
|
||||||
|
*index = mat.mat_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int has_contaminant(df::item_actual *item, int type, int index)
|
||||||
|
{
|
||||||
|
auto cont = item->contaminants;
|
||||||
|
if (!cont)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int size = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < cont->size(); i++)
|
||||||
|
{
|
||||||
|
auto cur = (*cont)[i];
|
||||||
|
if (cur->mat_type == type && cur->mat_index == index)
|
||||||
|
size += cur->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef std::map<int, std::vector<df::item*> > item_table;
|
||||||
|
|
||||||
|
static void index_items(item_table &table, df::job *job, ReactionInfo *info)
|
||||||
|
{
|
||||||
|
for (int i = job->items.size()-1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
auto iref = job->items[i];
|
||||||
|
if (iref->job_item_idx < 0) continue;
|
||||||
|
auto iitem = job->job_items[iref->job_item_idx];
|
||||||
|
|
||||||
|
if (iitem->contains.empty())
|
||||||
|
{
|
||||||
|
table[iitem->reagent_index].push_back(iref->item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::vector<df::item*> contents;
|
||||||
|
Items::getContainedItems(iref->item, &contents);
|
||||||
|
|
||||||
|
for (int j = contents.size()-1; j >= 0; j--)
|
||||||
|
{
|
||||||
|
for (int k = iitem->contains.size()-1; k >= 0; k--)
|
||||||
|
{
|
||||||
|
int ridx = iitem->contains[k];
|
||||||
|
auto reag = info->react->reagents[ridx];
|
||||||
|
|
||||||
|
if (reag->matchesChild(contents[j], info->react, iitem->reaction_id))
|
||||||
|
table[ridx].push_back(contents[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
df::item* find_item(ReagentSource &info, item_table &table)
|
||||||
|
{
|
||||||
|
if (!info.reagent)
|
||||||
|
return NULL;
|
||||||
|
if (table[info.idx].empty())
|
||||||
|
return NULL;
|
||||||
|
return table[info.idx].back();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct item_hook : df::item_constructed {
|
||||||
|
typedef df::item_constructed interpose_base;
|
||||||
|
|
||||||
|
DEFINE_VMETHOD_INTERPOSE(bool, isImprovable, (df::job *job, int16_t mat_type, int32_t mat_index))
|
||||||
|
{
|
||||||
|
ReactionInfo *info;
|
||||||
|
|
||||||
|
if (job && job->job_type == job_type::CustomReaction &&
|
||||||
|
(info = find_reaction(job->reaction_name)) != NULL)
|
||||||
|
{
|
||||||
|
if (!contaminants || contaminants->empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
item_table table;
|
||||||
|
index_items(table, job, info);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < info->products.size(); i++)
|
||||||
|
{
|
||||||
|
auto &product = info->products[i];
|
||||||
|
|
||||||
|
int mattype, matindex;
|
||||||
|
auto material = find_item(info->products[i].material, table);
|
||||||
|
|
||||||
|
find_material(&mattype, &matindex, material, product.material);
|
||||||
|
|
||||||
|
if (mattype < 0 || has_contaminant(this, mattype, matindex) >= 50)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return INTERPOSE_NEXT(isImprovable)(job, mat_type, mat_index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IMPLEMENT_VMETHOD_INTERPOSE(item_hook, isImprovable);
|
||||||
|
|
||||||
|
df::item* find_item(
|
||||||
|
ReagentSource &info,
|
||||||
|
std::vector<df::reaction_reagent*> *in_reag,
|
||||||
|
std::vector<df::item*> *in_items
|
||||||
|
) {
|
||||||
|
if (!info.reagent)
|
||||||
|
return NULL;
|
||||||
|
for (int i = in_items->size(); i >= 0; i--)
|
||||||
|
if ((*in_reag)[i] == info.reagent)
|
||||||
|
return (*in_items)[i];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct product_hook : improvement_product {
|
||||||
|
typedef improvement_product interpose_base;
|
||||||
|
|
||||||
|
DEFINE_VMETHOD_INTERPOSE(
|
||||||
|
void, produce,
|
||||||
|
(df::unit *unit, std::vector<df::item*> *out_items,
|
||||||
|
std::vector<df::reaction_reagent*> *in_reag,
|
||||||
|
std::vector<df::item*> *in_items,
|
||||||
|
int32_t quantity, int16_t skill,
|
||||||
|
df::historical_entity *entity, df::world_site *site)
|
||||||
|
) {
|
||||||
|
if (auto product = products[this])
|
||||||
|
{
|
||||||
|
auto object = find_item(product->object, in_reag, in_items);
|
||||||
|
auto material = find_item(product->material, in_reag, in_items);
|
||||||
|
|
||||||
|
if (object && (material || !product->material.reagent))
|
||||||
|
{
|
||||||
|
using namespace df::enums::improvement_type;
|
||||||
|
|
||||||
|
int mattype, matindex;
|
||||||
|
find_material(&mattype, &matindex, material, product->material);
|
||||||
|
|
||||||
|
df::matter_state state = matter_state::Liquid;
|
||||||
|
|
||||||
|
switch (improvement_type)
|
||||||
|
{
|
||||||
|
case COVERED:
|
||||||
|
if (flags.is_set(reaction_product_improvement_flags::GLAZED))
|
||||||
|
state = matter_state::Solid;
|
||||||
|
break;
|
||||||
|
case BANDS:
|
||||||
|
state = matter_state::Paste;
|
||||||
|
break;
|
||||||
|
case SPIKES:
|
||||||
|
state = matter_state::Powder;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rating = unit ? Units::getEffectiveSkill(unit, df::job_skill(skill)) : 0;
|
||||||
|
int size = int(probability*(1.0f + 0.06f*rating)); // +90% at legendary
|
||||||
|
|
||||||
|
object->addContaminant(
|
||||||
|
mattype, matindex, state,
|
||||||
|
object->getTemperature(),
|
||||||
|
size, -1,
|
||||||
|
0x8000 // not washed by water, and 'clean items' safe.
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scan raws for matching reactions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void find_reagent(
|
||||||
|
color_ostream &out, ReagentSource &info, df::reaction *react, std::string name
|
||||||
|
) {
|
||||||
|
for (size_t i = 0; i < react->reagents.size(); i++)
|
||||||
|
{
|
||||||
|
if (react->reagents[i]->code != name)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
info.idx = i;
|
||||||
|
info.reagent = react->reagents[i];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.printerr("Invalid reagent name '%s' in '%s'\n", name.c_str(), react->code.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse_product(
|
||||||
|
color_ostream &out, ProductInfo &info, df::reaction *react, improvement_product *prod
|
||||||
|
) {
|
||||||
|
using namespace df::enums::reaction_product_improvement_flags;
|
||||||
|
|
||||||
|
info.react = react;
|
||||||
|
info.product = prod;
|
||||||
|
|
||||||
|
find_reagent(out, info.object, react, prod->target_reagent);
|
||||||
|
|
||||||
|
auto ritem = strict_virtual_cast<df::reaction_reagent_itemst>(info.object.reagent);
|
||||||
|
if (ritem)
|
||||||
|
ritem->flags1.bits.improvable = true;
|
||||||
|
|
||||||
|
info.material.mat_type = prod->mat_type;
|
||||||
|
info.material.mat_index = prod->mat_index;
|
||||||
|
|
||||||
|
if (prod->flags.is_set(GET_MATERIAL_PRODUCT))
|
||||||
|
{
|
||||||
|
find_reagent(out, info.material, react, prod->get_material.reagent_code);
|
||||||
|
|
||||||
|
info.material.product = true;
|
||||||
|
info.material.product_name = prod->get_material.product_code;
|
||||||
|
}
|
||||||
|
else if (prod->flags.is_set(GET_MATERIAL_SAME))
|
||||||
|
{
|
||||||
|
find_reagent(out, info.material, react, prod->get_material.reagent_code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool find_reactions(color_ostream &out)
|
||||||
|
{
|
||||||
|
reactions.clear();
|
||||||
|
products.clear();
|
||||||
|
|
||||||
|
auto &rlist = world->raws.reactions;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < rlist.size(); i++)
|
||||||
|
{
|
||||||
|
if (!is_add_spatter(rlist[i]->code))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
reactions[rlist[i]->code].react = rlist[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = reactions.begin(); it != reactions.end(); ++it)
|
||||||
|
{
|
||||||
|
auto &prod = it->second.react->products;
|
||||||
|
auto &out_prod = it->second.products;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < prod.size(); i++)
|
||||||
|
{
|
||||||
|
auto itprod = strict_virtual_cast<improvement_product>(prod[i]);
|
||||||
|
if (!itprod) continue;
|
||||||
|
|
||||||
|
out_prod.push_back(ProductInfo());
|
||||||
|
parse_product(out, out_prod.back(), it->second.react, itprod);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < prod.size(); i++)
|
||||||
|
{
|
||||||
|
if (out_prod[i].isValid())
|
||||||
|
products[out_prod[i].product] = &out_prod[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !products.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void enable_hooks(bool enable)
|
||||||
|
{
|
||||||
|
INTERPOSE_HOOK(item_hook, isImprovable).apply(enable);
|
||||||
|
INTERPOSE_HOOK(product_hook, produce).apply(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case SC_MAP_LOADED:
|
||||||
|
if (find_reactions(out))
|
||||||
|
{
|
||||||
|
out.print("Detected spatter add reactions - enabling plugin.\n");
|
||||||
|
enable_hooks(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
enable_hooks(false);
|
||||||
|
break;
|
||||||
|
case SC_MAP_UNLOADED:
|
||||||
|
enable_hooks(false);
|
||||||
|
reactions.clear();
|
||||||
|
products.clear();
|
||||||
|
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,229 @@
|
|||||||
|
local _ENV = mkmodule('plugins.siege-engine')
|
||||||
|
|
||||||
|
--[[
|
||||||
|
|
||||||
|
Native functions:
|
||||||
|
|
||||||
|
* getTargetArea(building) -> point1, point2
|
||||||
|
* clearTargetArea(building)
|
||||||
|
* setTargetArea(building, point1, point2) -> true/false
|
||||||
|
|
||||||
|
* isLinkedToPile(building,pile) -> true/false
|
||||||
|
* getStockpileLinks(building) -> {pile}
|
||||||
|
* addStockpileLink(building,pile) -> true/false
|
||||||
|
* removeStockpileLink(building,pile) -> true/false
|
||||||
|
|
||||||
|
* saveWorkshopProfile(building) -> profile
|
||||||
|
|
||||||
|
* getAmmoItem(building) -> item_type
|
||||||
|
* setAmmoItem(building,item_type) -> true/false
|
||||||
|
|
||||||
|
* isPassableTile(pos) -> true/false
|
||||||
|
* isTreeTile(pos) -> true/false
|
||||||
|
* isTargetableTile(pos) -> true/false
|
||||||
|
|
||||||
|
* getTileStatus(building,pos) -> 'invalid/ok/out_of_range/blocked/semiblocked'
|
||||||
|
* paintAimScreen(building,view_pos_xyz,left_top_xy,size_xy)
|
||||||
|
|
||||||
|
* canTargetUnit(unit) -> true/false
|
||||||
|
|
||||||
|
proj_info = { target = pos, [delta = float/pos], [factor = int] }
|
||||||
|
|
||||||
|
* projPosAtStep(building,proj_info,step) -> pos
|
||||||
|
* projPathMetrics(building,proj_info) -> {
|
||||||
|
hit_type = 'wall/floor/ceiling/map_edge/tree',
|
||||||
|
collision_step = int,
|
||||||
|
collision_z_step = int,
|
||||||
|
goal_distance = int,
|
||||||
|
goal_step = int/nil,
|
||||||
|
goal_z_step = int/nil,
|
||||||
|
status = 'ok/out_of_range/blocked'
|
||||||
|
}
|
||||||
|
|
||||||
|
* adjustToTarget(building,pos) -> pos,ok=true/false
|
||||||
|
|
||||||
|
* traceUnitPath(unit) -> { {x=int,y=int,z=int[,from=time][,to=time]} }
|
||||||
|
* unitPosAtTime(unit, time) -> pos
|
||||||
|
|
||||||
|
* proposeUnitHits(building) -> { {
|
||||||
|
pos=pos, unit=unit, time=float, dist=int,
|
||||||
|
[lmargin=float,] [rmargin=float,]
|
||||||
|
} }
|
||||||
|
|
||||||
|
* computeNearbyWeight(building,hits,{[id/unit]=score}[,fname])
|
||||||
|
|
||||||
|
]]
|
||||||
|
|
||||||
|
Z_STEP_COUNT = 15
|
||||||
|
Z_STEP = 1/31
|
||||||
|
|
||||||
|
function getMetrics(engine, path)
|
||||||
|
path.metrics = path.metrics or projPathMetrics(engine, path)
|
||||||
|
return path.metrics
|
||||||
|
end
|
||||||
|
|
||||||
|
function findShotHeight(engine, target)
|
||||||
|
local path = { target = target, delta = 0.0 }
|
||||||
|
|
||||||
|
if getMetrics(engine, path).goal_step then
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
|
||||||
|
local tpath = { target = target, delta = Z_STEP_COUNT*Z_STEP }
|
||||||
|
|
||||||
|
if getMetrics(engine, tpath).goal_step then
|
||||||
|
for i = 1,Z_STEP_COUNT-1 do
|
||||||
|
path = { target = target, delta = i*Z_STEP }
|
||||||
|
if getMetrics(engine, path).goal_step then
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tpath
|
||||||
|
end
|
||||||
|
|
||||||
|
tpath = { target = target, delta = -Z_STEP_COUNT*Z_STEP }
|
||||||
|
|
||||||
|
if getMetrics(engine, tpath).goal_step then
|
||||||
|
for i = 1,Z_STEP_COUNT-1 do
|
||||||
|
path = { target = target, delta = -i*Z_STEP }
|
||||||
|
if getMetrics(engine, path).goal_step then
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tpath
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function findReachableTargets(engine, targets)
|
||||||
|
local reachable = {}
|
||||||
|
for _,tgt in ipairs(targets) do
|
||||||
|
tgt.path = findShotHeight(engine, tgt.pos)
|
||||||
|
if tgt.path then
|
||||||
|
table.insert(reachable, tgt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return reachable
|
||||||
|
end
|
||||||
|
|
||||||
|
recent_targets = recent_targets or {}
|
||||||
|
|
||||||
|
if dfhack.is_core_context then
|
||||||
|
dfhack.onStateChange[_ENV] = function(code)
|
||||||
|
if code == SC_MAP_LOADED then
|
||||||
|
recent_targets = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function saveRecent(unit)
|
||||||
|
local id = unit.id
|
||||||
|
local tgt = recent_targets
|
||||||
|
tgt[id] = (tgt[id] or 0) + 1
|
||||||
|
dfhack.timeout(3, 'days', function()
|
||||||
|
tgt[id] = math.max(0, tgt[id]-1)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function getBaseUnitWeight(unit)
|
||||||
|
if dfhack.units.isCitizen(unit) then
|
||||||
|
return -10
|
||||||
|
elseif unit.flags1.diplomat or unit.flags1.merchant then
|
||||||
|
return -2
|
||||||
|
elseif unit.flags1.tame and unit.civ_id == df.global.ui.civ_id then
|
||||||
|
return -1
|
||||||
|
else
|
||||||
|
local rv = 1
|
||||||
|
if unit.flags1.marauder then rv = rv + 0.5 end
|
||||||
|
if unit.flags1.active_invader then rv = rv + 1 end
|
||||||
|
if unit.flags1.invader_origin then rv = rv + 1 end
|
||||||
|
if unit.flags1.invades then rv = rv + 1 end
|
||||||
|
if unit.flags1.hidden_ambusher then rv = rv + 1 end
|
||||||
|
return rv
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function getUnitWeight(unit)
|
||||||
|
local base = getBaseUnitWeight(unit)
|
||||||
|
return base * math.pow(0.7, recent_targets[unit.id] or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function unitWeightCache()
|
||||||
|
local cache = {}
|
||||||
|
return cache, function(unit)
|
||||||
|
local id = unit.id
|
||||||
|
cache[id] = cache[id] or getUnitWeight(unit)
|
||||||
|
return cache[id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function scoreTargets(engine, reachable)
|
||||||
|
local ucache, get_weight = unitWeightCache()
|
||||||
|
|
||||||
|
for _,tgt in ipairs(reachable) do
|
||||||
|
tgt.score = get_weight(tgt.unit)
|
||||||
|
if tgt.lmargin and tgt.lmargin < 3 then
|
||||||
|
tgt.score = tgt.score * tgt.lmargin / 3
|
||||||
|
end
|
||||||
|
if tgt.rmargin and tgt.rmargin < 3 then
|
||||||
|
tgt.score = tgt.score * tgt.rmargin / 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
computeNearbyWeight(engine, reachable, ucache)
|
||||||
|
|
||||||
|
for _,tgt in ipairs(reachable) do
|
||||||
|
tgt.score = (tgt.score + tgt.nearby_weight*0.7) * math.pow(0.995, tgt.time/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(reachable, function(a,b)
|
||||||
|
return a.score > b.score or (a.score == b.score and a.time < b.time)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function pickUniqueTargets(reachable)
|
||||||
|
local unique = {}
|
||||||
|
|
||||||
|
if #reachable > 0 then
|
||||||
|
local pos_table = {}
|
||||||
|
local first_score = reachable[1].score
|
||||||
|
|
||||||
|
for i,tgt in ipairs(reachable) do
|
||||||
|
if tgt.score < 0 or tgt.score < 0.1*first_score then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
local x,y,z = pos2xyz(tgt.pos)
|
||||||
|
local key = x..':'..y..':'..z
|
||||||
|
if pos_table[key] then
|
||||||
|
table.insert(pos_table[key].units, tgt.unit)
|
||||||
|
else
|
||||||
|
table.insert(unique, tgt)
|
||||||
|
pos_table[key] = tgt
|
||||||
|
tgt.units = { tgt.unit }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return unique
|
||||||
|
end
|
||||||
|
|
||||||
|
function doAimProjectile(engine, item, target_min, target_max, skill)
|
||||||
|
print(item, df.skill_rating[skill])
|
||||||
|
|
||||||
|
local targets = proposeUnitHits(engine)
|
||||||
|
local reachable = findReachableTargets(engine, targets)
|
||||||
|
scoreTargets(engine, reachable)
|
||||||
|
local unique = pickUniqueTargets(reachable)
|
||||||
|
|
||||||
|
if #unique > 0 then
|
||||||
|
local cnt = math.max(math.min(#unique,5), math.min(10, math.floor(#unique/2)))
|
||||||
|
local rnd = math.random(cnt)
|
||||||
|
for _,u in ipairs(unique[rnd].units) do
|
||||||
|
saveRecent(u)
|
||||||
|
end
|
||||||
|
return unique[rnd].path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return _ENV
|
@ -0,0 +1,29 @@
|
|||||||
|
--- ../objects.old/entity_default.txt 2012-09-17 17:59:28.853898702 +0400
|
||||||
|
+++ entity_default.txt 2012-09-17 17:59:28.684899429 +0400
|
||||||
|
@@ -49,6 +49,7 @@
|
||||||
|
[TRAPCOMP:ITEM_TRAPCOMP_SPIKEDBALL]
|
||||||
|
[TRAPCOMP:ITEM_TRAPCOMP_LARGESERRATEDDISC]
|
||||||
|
[TRAPCOMP:ITEM_TRAPCOMP_MENACINGSPIKE]
|
||||||
|
+ [TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON]
|
||||||
|
[TOY:ITEM_TOY_PUZZLEBOX]
|
||||||
|
[TOY:ITEM_TOY_BOAT]
|
||||||
|
[TOY:ITEM_TOY_HAMMER]
|
||||||
|
@@ -204,6 +205,8 @@
|
||||||
|
[PERMITTED_JOB:WAX_WORKER]
|
||||||
|
[PERMITTED_BUILDING:SOAP_MAKER]
|
||||||
|
[PERMITTED_BUILDING:SCREW_PRESS]
|
||||||
|
+ [PERMITTED_BUILDING:STEAM_ENGINE]
|
||||||
|
+ [PERMITTED_BUILDING:MAGMA_STEAM_ENGINE]
|
||||||
|
[PERMITTED_REACTION:TAN_A_HIDE]
|
||||||
|
[PERMITTED_REACTION:RENDER_FAT]
|
||||||
|
[PERMITTED_REACTION:MAKE_SOAP_FROM_TALLOW]
|
||||||
|
@@ -248,6 +251,9 @@
|
||||||
|
[PERMITTED_REACTION:ROSE_GOLD_MAKING]
|
||||||
|
[PERMITTED_REACTION:BISMUTH_BRONZE_MAKING]
|
||||||
|
[PERMITTED_REACTION:ADAMANTINE_WAFERS]
|
||||||
|
+ [PERMITTED_REACTION:STOKE_BOILER]
|
||||||
|
+ [PERMITTED_REACTION:SPATTER_ADD_EXTRACT_WEAPON]
|
||||||
|
+ [PERMITTED_REACTION:SPATTER_ADD_EXTRACT_AMMO]
|
||||||
|
[WORLD_CONSTRUCTION:TUNNEL]
|
||||||
|
[WORLD_CONSTRUCTION:BRIDGE]
|
||||||
|
[WORLD_CONSTRUCTION:ROAD]
|
@ -0,0 +1,10 @@
|
|||||||
|
--- ../objects.old/material_template_default.txt 2012-09-17 17:59:28.907898469 +0400
|
||||||
|
+++ material_template_default.txt 2012-09-17 17:59:28.695899382 +0400
|
||||||
|
@@ -2374,6 +2374,7 @@
|
||||||
|
[MAX_EDGE:500]
|
||||||
|
[ABSORPTION:100]
|
||||||
|
[LIQUID_MISC_CREATURE]
|
||||||
|
+ [REACTION_CLASS:CREATURE_EXTRACT]
|
||||||
|
[ROTS]
|
||||||
|
|
||||||
|
This is for creatures that are "made of fire". Right now there isn't a good format for that.
|
@ -0,0 +1,144 @@
|
|||||||
|
reaction_spatter
|
||||||
|
|
||||||
|
[OBJECT:REACTION]
|
||||||
|
|
||||||
|
Reaction name must start with 'SPATTER_ADD_':
|
||||||
|
|
||||||
|
[REACTION:SPATTER_ADD_OBJECT_LIQUID]
|
||||||
|
[NAME:coat object with liquid]
|
||||||
|
[ADVENTURE_MODE_ENABLED]
|
||||||
|
[SKILL:WAX_WORKING]
|
||||||
|
[REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE]
|
||||||
|
[MIN_DIMENSION:150]
|
||||||
|
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
|
||||||
|
[CONTAINS:extract]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
|
||||||
|
The object to improve must be after the input mat, so that it is known:
|
||||||
|
[REAGENT:object:1:NONE:NONE:NONE:NONE]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
Need some excuse why the spatter is water-resistant:
|
||||||
|
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:FAT][UNROTTEN]
|
||||||
|
The probability is used as spatter size; Legendary gives +90%:
|
||||||
|
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
|
||||||
|
[IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
|
||||||
|
|
||||||
|
[REACTION:SPATTER_ADD_WEAPON_EXTRACT]
|
||||||
|
[NAME:coat weapon with extract]
|
||||||
|
[BUILDING:CRAFTSMAN:NONE]
|
||||||
|
[SKILL:WAX_WORKING]
|
||||||
|
[REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE]
|
||||||
|
[MIN_DIMENSION:150]
|
||||||
|
[REACTION_CLASS:CREATURE_EXTRACT]
|
||||||
|
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
|
||||||
|
[CONTAINS:extract]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
|
||||||
|
The object to improve must be after the input mat, so that it is known:
|
||||||
|
[REAGENT:object:1:WEAPON:NONE:NONE:NONE]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
Need some excuse why the spatter is water-resistant:
|
||||||
|
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
|
||||||
|
The probability is used as spatter size; Legendary gives +90%:
|
||||||
|
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
|
||||||
|
[IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
|
||||||
|
|
||||||
|
[REACTION:SPATTER_ADD_AMMO_EXTRACT]
|
||||||
|
[NAME:coat ammo with extract]
|
||||||
|
[BUILDING:CRAFTSMAN:NONE]
|
||||||
|
[SKILL:WAX_WORKING]
|
||||||
|
[REAGENT:extract:50:LIQUID_MISC:NONE:NONE:NONE]
|
||||||
|
[MIN_DIMENSION:50]
|
||||||
|
[REACTION_CLASS:CREATURE_EXTRACT]
|
||||||
|
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
|
||||||
|
[CONTAINS:extract]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
|
||||||
|
The object to improve must be after the input mat, so that it is known:
|
||||||
|
[REAGENT:object:1:AMMO:NONE:NONE:NONE]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
Need some excuse why the spatter is water-resistant:
|
||||||
|
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
|
||||||
|
The probability is used as spatter size; Legendary gives +90%:
|
||||||
|
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
|
||||||
|
[IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
|
||||||
|
|
||||||
|
[REACTION:SPATTER_ADD_WEAPON_GCS]
|
||||||
|
[NAME:coat weapon with GCS venom]
|
||||||
|
[BUILDING:CRAFTSMAN:NONE]
|
||||||
|
[SKILL:WAX_WORKING]
|
||||||
|
[REAGENT:extract:150:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON]
|
||||||
|
[MIN_DIMENSION:150]
|
||||||
|
[REACTION_CLASS:CREATURE_EXTRACT]
|
||||||
|
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
|
||||||
|
[CONTAINS:extract]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
|
||||||
|
The object to improve must be after the input mat, so that it is known:
|
||||||
|
[REAGENT:object:1:WEAPON:NONE:NONE:NONE]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
Need some excuse why the spatter is water-resistant:
|
||||||
|
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
|
||||||
|
The probability is used as spatter size; Legendary gives +90%:
|
||||||
|
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
|
||||||
|
[IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
|
||||||
|
|
||||||
|
[REACTION:SPATTER_ADD_AMMO_GCS]
|
||||||
|
[NAME:coat ammo with GCS venom]
|
||||||
|
[BUILDING:CRAFTSMAN:NONE]
|
||||||
|
[SKILL:WAX_WORKING]
|
||||||
|
[REAGENT:extract:50:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON]
|
||||||
|
[MIN_DIMENSION:50]
|
||||||
|
[REACTION_CLASS:CREATURE_EXTRACT]
|
||||||
|
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
|
||||||
|
[CONTAINS:extract]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
|
||||||
|
The object to improve must be after the input mat, so that it is known:
|
||||||
|
[REAGENT:object:1:AMMO:NONE:NONE:NONE]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
Need some excuse why the spatter is water-resistant:
|
||||||
|
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
|
||||||
|
The probability is used as spatter size; Legendary gives +90%:
|
||||||
|
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
|
||||||
|
[IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
|
||||||
|
|
||||||
|
[REACTION:SPATTER_ADD_WEAPON_GDS]
|
||||||
|
[NAME:coat weapon with GDS venom]
|
||||||
|
[BUILDING:CRAFTSMAN:NONE]
|
||||||
|
[SKILL:WAX_WORKING]
|
||||||
|
[REAGENT:extract:150:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON]
|
||||||
|
[MIN_DIMENSION:150]
|
||||||
|
[REACTION_CLASS:CREATURE_EXTRACT]
|
||||||
|
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
|
||||||
|
[CONTAINS:extract]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
|
||||||
|
The object to improve must be after the input mat, so that it is known:
|
||||||
|
[REAGENT:object:1:WEAPON:NONE:NONE:NONE]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
Need some excuse why the spatter is water-resistant:
|
||||||
|
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
|
||||||
|
The probability is used as spatter size; Legendary gives +90%:
|
||||||
|
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
|
||||||
|
[IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
|
||||||
|
|
||||||
|
[REACTION:SPATTER_ADD_AMMO_GDS]
|
||||||
|
[NAME:coat ammo with GDS venom]
|
||||||
|
[BUILDING:CRAFTSMAN:NONE]
|
||||||
|
[SKILL:WAX_WORKING]
|
||||||
|
[REAGENT:extract:50:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON]
|
||||||
|
[MIN_DIMENSION:50]
|
||||||
|
[REACTION_CLASS:CREATURE_EXTRACT]
|
||||||
|
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
|
||||||
|
[CONTAINS:extract]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
|
||||||
|
The object to improve must be after the input mat, so that it is known:
|
||||||
|
[REAGENT:object:1:AMMO:NONE:NONE:NONE]
|
||||||
|
[PRESERVE_REAGENT]
|
||||||
|
Need some excuse why the spatter is water-resistant:
|
||||||
|
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
|
||||||
|
The probability is used as spatter size; Legendary gives +90%:
|
||||||
|
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
|
||||||
|
[IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
|
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
|||||||
Subproject commit 2a62ba5ed2607f4dbf0473e77502d4e19c19678e
|
Subproject commit 37a823541538023b9f3d0d1e8039cf32851de68d
|
@ -0,0 +1,3 @@
|
|||||||
|
-- For killing bugged out gui script screens.
|
||||||
|
|
||||||
|
dfhack.screen.dismiss(dfhack.gui.getCurViewscreen())
|
@ -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 = 'cages', item_type = df.item_type.CAGE },
|
||||||
|
{ 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'
|
||||||
|
|
||||||
|
SiegeEngine.ATTRS{ building = DEFAULT_NIL }
|
||||||
|
|
||||||
|
function SiegeEngine:init()
|
||||||
|
self:assign{
|
||||||
|
center = utils.getBuildingCenter(self.building),
|
||||||
|
selected_pile = 1,
|
||||||
|
mode_main = {
|
||||||
|
render = self:callback 'onRenderBody_main',
|
||||||
|
input = self:callback 'onInput_main',
|
||||||
|
},
|
||||||
|
mode_aim = {
|
||||||
|
render = self:callback 'onRenderBody_aim',
|
||||||
|
input = self:callback 'onInput_aim',
|
||||||
|
},
|
||||||
|
mode_pile = {
|
||||||
|
render = self:callback 'onRenderBody_pile',
|
||||||
|
input = self:callback 'onInput_pile',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function SiegeEngine:onShow()
|
||||||
|
SiegeEngine.super.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 = SiegeEngine{ building = 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