Merge remote-tracking branch 'peterix/master'
Conflicts: plugins/CMakeLists.txt plugins/autoSyndrome.cppdevelop
commit
3403146461
@ -1 +1 @@
|
||||
Subproject commit 4d2afc3a0bcebdb17415dc2827b44fd35986a368
|
||||
Subproject commit cacb0b9a347aaadc2b4b052a7cda40e58104f04f
|
@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
DF_DIR=$(dirname "$0")
|
||||
cd "${DF_DIR}"
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
|
||||
export SDL_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch.
|
||||
#export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
|
||||
./libs/Dwarf_Fortress $* # Go, go, go! :)
|
||||
|
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
DF_DIR=$(dirname "$0")
|
||||
cd "${DF_DIR}"
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
|
||||
|
||||
./isoworld/isoworld "$@"
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,619 @@
|
||||
#include "uicommon.h"
|
||||
|
||||
#include "modules/Gui.h"
|
||||
|
||||
#include "df/world.h"
|
||||
#include "df/world_raws.h"
|
||||
#include "df/building_def.h"
|
||||
#include "df/viewscreen_dwarfmodest.h"
|
||||
#include "df/building_stockpilest.h"
|
||||
#include "modules/Items.h"
|
||||
#include "df/building_tradedepotst.h"
|
||||
#include "df/general_ref_building_holderst.h"
|
||||
#include "df/job.h"
|
||||
#include "df/job_item_ref.h"
|
||||
#include "modules/Job.h"
|
||||
#include "df/ui.h"
|
||||
#include "df/caravan_state.h"
|
||||
#include "df/mandate.h"
|
||||
#include "modules/Maps.h"
|
||||
#include "modules/World.h"
|
||||
|
||||
using df::global::world;
|
||||
using df::global::cursor;
|
||||
using df::global::ui;
|
||||
using df::building_stockpilest;
|
||||
|
||||
DFHACK_PLUGIN("autotrade");
|
||||
#define PLUGIN_VERSION 0.2
|
||||
|
||||
|
||||
/*
|
||||
* Stockpile Access
|
||||
*/
|
||||
|
||||
static building_stockpilest *get_selected_stockpile()
|
||||
{
|
||||
if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) ||
|
||||
ui->main.mode != ui_sidebar_mode::QueryBuilding)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return virtual_cast<building_stockpilest>(world->selected_building);
|
||||
}
|
||||
|
||||
static bool can_trade()
|
||||
{
|
||||
if (df::global::ui->caravans.size() == 0)
|
||||
return false;
|
||||
|
||||
for (auto it = df::global::ui->caravans.begin(); it != df::global::ui->caravans.end(); it++)
|
||||
{
|
||||
auto caravan = *it;
|
||||
auto trade_state = caravan->trade_state;
|
||||
auto time_remaining = caravan->time_remaining;
|
||||
if ((trade_state != 1 && trade_state != 2) || time_remaining == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class StockpileInfo {
|
||||
public:
|
||||
|
||||
StockpileInfo(df::building_stockpilest *sp_) : sp(sp_)
|
||||
{
|
||||
readBuilding();
|
||||
}
|
||||
|
||||
StockpileInfo(PersistentDataItem &config)
|
||||
{
|
||||
this->config = config;
|
||||
id = config.ival(1);
|
||||
}
|
||||
|
||||
bool inStockpile(df::item *i)
|
||||
{
|
||||
df::item *container = Items::getContainer(i);
|
||||
if (container)
|
||||
return inStockpile(container);
|
||||
|
||||
if (i->pos.z != z) return false;
|
||||
if (i->pos.x < x1 || i->pos.x >= x2 ||
|
||||
i->pos.y < y1 || i->pos.y >= y2) return false;
|
||||
int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width;
|
||||
return sp->room.extents[e] == 1;
|
||||
}
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
auto found = df::building::find(id);
|
||||
return found && found == sp && found->getType() == building_type::Stockpile;
|
||||
}
|
||||
|
||||
bool load()
|
||||
{
|
||||
auto found = df::building::find(id);
|
||||
if (!found || found->getType() != building_type::Stockpile)
|
||||
return false;
|
||||
|
||||
sp = virtual_cast<df::building_stockpilest>(found);
|
||||
if (!sp)
|
||||
return false;
|
||||
|
||||
readBuilding();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
bool matches(df::building_stockpilest* sp)
|
||||
{
|
||||
return this->sp == sp;
|
||||
}
|
||||
|
||||
void save()
|
||||
{
|
||||
config = DFHack::World::AddPersistentData("autotrade/stockpiles");
|
||||
config.ival(1) = id;
|
||||
}
|
||||
|
||||
void remove()
|
||||
{
|
||||
DFHack::World::DeletePersistentData(config);
|
||||
}
|
||||
|
||||
private:
|
||||
PersistentDataItem config;
|
||||
df::building_stockpilest* sp;
|
||||
int x1, x2, y1, y2, z;
|
||||
int32_t id;
|
||||
|
||||
void readBuilding()
|
||||
{
|
||||
id = sp->id;
|
||||
z = sp->z;
|
||||
x1 = sp->room.x;
|
||||
x2 = sp->room.x + sp->room.width;
|
||||
y1 = sp->room.y;
|
||||
y2 = sp->room.y + sp->room.height;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Depot Access
|
||||
*/
|
||||
|
||||
class TradeDepotInfo
|
||||
{
|
||||
public:
|
||||
TradeDepotInfo() : depot(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool findDepot()
|
||||
{
|
||||
if (isValid())
|
||||
return true;
|
||||
|
||||
reset();
|
||||
for(auto bld_it = world->buildings.all.begin(); bld_it != world->buildings.all.end(); bld_it++)
|
||||
{
|
||||
auto bld = *bld_it;
|
||||
if (!isUsableDepot(bld))
|
||||
continue;
|
||||
|
||||
depot = bld;
|
||||
id = depot->id;
|
||||
break;
|
||||
}
|
||||
|
||||
return depot;
|
||||
}
|
||||
|
||||
bool assignItem(df::item *item)
|
||||
{
|
||||
auto href = df::allocate<df::general_ref_building_holderst>();
|
||||
if (!href)
|
||||
return false;
|
||||
|
||||
auto job = new df::job();
|
||||
|
||||
df::coord tpos(depot->centerx, depot->centery, depot->z);
|
||||
job->pos = tpos;
|
||||
|
||||
job->job_type = job_type::BringItemToDepot;
|
||||
|
||||
// job <-> item link
|
||||
if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled))
|
||||
{
|
||||
delete job;
|
||||
delete href;
|
||||
return false;
|
||||
}
|
||||
|
||||
// job <-> building link
|
||||
href->building_id = id;
|
||||
depot->jobs.push_back(job);
|
||||
job->general_refs.push_back(href);
|
||||
|
||||
// add to job list
|
||||
Job::linkIntoWorld(job);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
depot = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t id;
|
||||
df::building *depot;
|
||||
|
||||
bool isUsableDepot(df::building* bld)
|
||||
{
|
||||
if (bld->getType() != building_type::TradeDepot)
|
||||
return false;
|
||||
|
||||
if (bld->getBuildStage() < bld->getMaxBuildStage())
|
||||
return false;
|
||||
|
||||
if (bld->jobs.size() == 1 && bld->jobs[0]->job_type == job_type::DestroyBuilding)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
if (!depot)
|
||||
return false;
|
||||
|
||||
auto found = df::building::find(id);
|
||||
return found && found == depot && isUsableDepot(found);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static TradeDepotInfo depot_info;
|
||||
|
||||
|
||||
/*
|
||||
* Item Manipulation
|
||||
*/
|
||||
|
||||
static bool check_mandates(df::item *item)
|
||||
{
|
||||
for (auto it = world->mandates.begin(); it != world->mandates.end(); it++)
|
||||
{
|
||||
auto mandate = *it;
|
||||
|
||||
if (mandate->mode != 0)
|
||||
continue;
|
||||
|
||||
if (item->getType() != mandate->item_type ||
|
||||
(mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype))
|
||||
continue;
|
||||
|
||||
if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type)
|
||||
continue;
|
||||
|
||||
if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index)
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_valid_item(df::item *item)
|
||||
{
|
||||
for (size_t i = 0; i < item->general_refs.size(); i++)
|
||||
{
|
||||
df::general_ref *ref = item->general_refs[i];
|
||||
|
||||
switch (ref->getType())
|
||||
{
|
||||
case general_ref_type::CONTAINED_IN_ITEM:
|
||||
return false;
|
||||
|
||||
case general_ref_type::UNIT_HOLDER:
|
||||
return false;
|
||||
|
||||
case general_ref_type::BUILDING_HOLDER:
|
||||
return false;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < item->specific_refs.size(); i++)
|
||||
{
|
||||
df::specific_ref *ref = item->specific_refs[i];
|
||||
|
||||
if (ref->type == specific_ref_type::JOB)
|
||||
{
|
||||
// Ignore any items assigned to a job
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!check_mandates(item))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mark_all_in_stockpiles(vector<StockpileInfo> &stockpiles, bool announce)
|
||||
{
|
||||
if (!depot_info.findDepot())
|
||||
{
|
||||
if (announce)
|
||||
Gui::showAnnouncement("Cannot trade, no valid depot available", COLOR_RED, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
|
||||
|
||||
|
||||
// Precompute a bitmask with the bad flags
|
||||
df::item_flags bad_flags;
|
||||
bad_flags.whole = 0;
|
||||
|
||||
#define F(x) bad_flags.bits.x = true;
|
||||
F(dump); F(forbid); F(garbage_collect);
|
||||
F(hostile); F(on_fire); F(rotten); F(trader);
|
||||
F(in_building); F(construction); F(artifact);
|
||||
F(spider_web); F(owned); F(in_job);
|
||||
#undef F
|
||||
|
||||
size_t marked_count = 0;
|
||||
size_t error_count = 0;
|
||||
for (size_t i = 0; i < items.size(); i++)
|
||||
{
|
||||
df::item *item = items[i];
|
||||
if (item->flags.whole & bad_flags.whole)
|
||||
continue;
|
||||
|
||||
if (!is_valid_item(item))
|
||||
continue;
|
||||
|
||||
for (auto it = stockpiles.begin(); it != stockpiles.end(); it++)
|
||||
{
|
||||
if (!it->inStockpile(item))
|
||||
continue;
|
||||
|
||||
// In case of container, check contained items for mandates
|
||||
bool mandates_ok = true;
|
||||
vector<df::item*> contained_items;
|
||||
Items::getContainedItems(item, &contained_items);
|
||||
for (auto cit = contained_items.begin(); cit != contained_items.end(); cit++)
|
||||
{
|
||||
if (!check_mandates(*cit))
|
||||
{
|
||||
mandates_ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mandates_ok)
|
||||
continue;
|
||||
|
||||
if (depot_info.assignItem(item))
|
||||
{
|
||||
++marked_count;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (++error_count < 5)
|
||||
{
|
||||
Gui::showZoomAnnouncement(df::announcement_type::CANCEL_JOB, item->pos,
|
||||
"Cannot trade item from stockpile " + int_to_string(it->getId()), COLOR_RED, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (marked_count)
|
||||
Gui::showAnnouncement("Marked " + int_to_string(marked_count) + " items for trade", COLOR_GREEN, false);
|
||||
else if (announce)
|
||||
Gui::showAnnouncement("No more items to mark", COLOR_RED, true);
|
||||
|
||||
if (error_count >= 5)
|
||||
{
|
||||
Gui::showAnnouncement(int_to_string(error_count) + " items were not marked", COLOR_RED, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Stockpile Monitoring
|
||||
*/
|
||||
|
||||
class StockpileMonitor
|
||||
{
|
||||
public:
|
||||
bool isMonitored(df::building_stockpilest *sp)
|
||||
{
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
|
||||
{
|
||||
if (it->matches(sp))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void add(df::building_stockpilest *sp)
|
||||
{
|
||||
auto pile = StockpileInfo(sp);
|
||||
if (pile.isValid())
|
||||
{
|
||||
monitored_stockpiles.push_back(StockpileInfo(sp));
|
||||
monitored_stockpiles.back().save();
|
||||
}
|
||||
}
|
||||
|
||||
void remove(df::building_stockpilest *sp)
|
||||
{
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
|
||||
{
|
||||
if (it->matches(sp))
|
||||
{
|
||||
it->remove();
|
||||
monitored_stockpiles.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void doCycle()
|
||||
{
|
||||
if (!can_trade())
|
||||
return;
|
||||
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end();)
|
||||
{
|
||||
if (!it->isValid())
|
||||
{
|
||||
it = monitored_stockpiles.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
mark_all_in_stockpiles(monitored_stockpiles, false);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
monitored_stockpiles.clear();
|
||||
std::vector<PersistentDataItem> items;
|
||||
DFHack::World::GetPersistentData(&items, "autotrade/stockpiles");
|
||||
|
||||
for (auto i = items.begin(); i != items.end(); i++)
|
||||
{
|
||||
auto pile = StockpileInfo(*i);
|
||||
if (pile.load())
|
||||
monitored_stockpiles.push_back(StockpileInfo(pile));
|
||||
else
|
||||
pile.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
vector<StockpileInfo> monitored_stockpiles;
|
||||
};
|
||||
|
||||
static StockpileMonitor monitor;
|
||||
|
||||
#define DELTA_TICKS 600
|
||||
|
||||
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||
{
|
||||
if(!Maps::IsValid())
|
||||
return CR_OK;
|
||||
|
||||
static decltype(world->frame_counter) last_frame_count = 0;
|
||||
|
||||
if (DFHack::World::ReadPauseState())
|
||||
return CR_OK;
|
||||
|
||||
if (world->frame_counter - last_frame_count < DELTA_TICKS)
|
||||
return CR_OK;
|
||||
|
||||
last_frame_count = world->frame_counter;
|
||||
|
||||
monitor.doCycle();
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Interface
|
||||
*/
|
||||
|
||||
struct trade_hook : public df::viewscreen_dwarfmodest
|
||||
{
|
||||
typedef df::viewscreen_dwarfmodest interpose_base;
|
||||
|
||||
bool handleInput(set<df::interface_key> *input)
|
||||
{
|
||||
building_stockpilest *sp = get_selected_stockpile();
|
||||
if (!sp)
|
||||
return false;
|
||||
|
||||
if (input->count(interface_key::CUSTOM_M))
|
||||
{
|
||||
if (!can_trade())
|
||||
return false;
|
||||
|
||||
vector<StockpileInfo> wrapper;
|
||||
wrapper.push_back(StockpileInfo(sp));
|
||||
mark_all_in_stockpiles(wrapper, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (input->count(interface_key::CUSTOM_U))
|
||||
{
|
||||
if (monitor.isMonitored(sp))
|
||||
monitor.remove(sp);
|
||||
else
|
||||
monitor.add(sp);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
||||
{
|
||||
if (!handleInput(input))
|
||||
INTERPOSE_NEXT(feed)(input);
|
||||
}
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
||||
{
|
||||
INTERPOSE_NEXT(render)();
|
||||
|
||||
building_stockpilest *sp = get_selected_stockpile();
|
||||
if (!sp)
|
||||
return;
|
||||
|
||||
auto dims = Gui::getDwarfmodeViewDims();
|
||||
int left_margin = dims.menu_x1 + 1;
|
||||
int x = left_margin;
|
||||
int y = 23;
|
||||
|
||||
if (can_trade())
|
||||
OutputHotkeyString(x, y, "Mark all for trade", "m", true, left_margin);
|
||||
|
||||
OutputToggleString(x, y, "Auto trade", "u", monitor.isMonitored(sp), true, left_margin);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, feed);
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, render);
|
||||
|
||||
static command_result autotrade_cmd(color_ostream &out, vector <string> & parameters)
|
||||
{
|
||||
if (!parameters.empty())
|
||||
{
|
||||
if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v')
|
||||
{
|
||||
out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl;
|
||||
}
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case DFHack::SC_MAP_LOADED:
|
||||
depot_info.reset();
|
||||
monitor.reset();
|
||||
break;
|
||||
case DFHack::SC_MAP_UNLOADED:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
if (!gps || !INTERPOSE_HOOK(trade_hook, feed).apply() || !INTERPOSE_HOOK(trade_hook, render).apply())
|
||||
out.printerr("Could not insert autotrade hooks!\n");
|
||||
|
||||
commands.push_back(
|
||||
PluginCommand(
|
||||
"autotrade", "Automatically send items in marked stockpiles to trade depot, when trading is possible.",
|
||||
autotrade_cmd, false, ""));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
Subproject commit aa3b1bd51f269c07b3235392fd7ed21fe9171f3f
|
@ -0,0 +1,366 @@
|
||||
// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D
|
||||
|
||||
// some headers required for a plugin. Nothing special, just the basics.
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
|
||||
// DF data structure definition headers
|
||||
#include "DataDefs.h"
|
||||
#include "df/world.h"
|
||||
#include "df/map_block.h"
|
||||
#include "df/builtin_mats.h"
|
||||
#include "df/tile_designation.h"
|
||||
|
||||
//DFhack specific headers
|
||||
#include "modules/Maps.h"
|
||||
#include "modules/MapCache.h"
|
||||
#include "modules/Materials.h"
|
||||
|
||||
//Needed for writing the protobuff stuff to a file.
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
|
||||
#include "isoworldremote.pb.h"
|
||||
|
||||
#include "RemoteServer.h"
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
using namespace isoworldremote;
|
||||
|
||||
|
||||
// Here go all the command declarations...
|
||||
// mostly to allow having the mandatory stuff on top of the file and commands on the bottom
|
||||
command_result isoWorldRemote (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
static command_result GetEmbarkTile(color_ostream &stream, const TileRequest *in, EmbarkTile *out);
|
||||
static command_result GetEmbarkInfo(color_ostream &stream, const MapRequest *in, MapReply *out);
|
||||
static command_result GetRawNames(color_ostream &stream, const MapRequest *in, RawNames *out);
|
||||
|
||||
bool gather_embark_tile_layer(int EmbX, int EmbY, int EmbZ, EmbarkTileLayer * tile, MapExtras::MapCache * MP);
|
||||
bool gather_embark_tile(int EmbX, int EmbY, EmbarkTile * tile, MapExtras::MapCache * MP);
|
||||
|
||||
|
||||
// A plugin must be able to return its name and version.
|
||||
// The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case
|
||||
DFHACK_PLUGIN("isoworldremote");
|
||||
|
||||
// Mandatory init function. If you have some global state, create it here.
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
//// Fill the command list with your commands.
|
||||
//commands.push_back(PluginCommand(
|
||||
// "isoworldremote", "Dump north-west embark tile to text file for debug purposes.",
|
||||
// isoWorldRemote, false, /* true means that the command can't be used from non-interactive user interface */
|
||||
// // Extended help string. Used by CR_WRONG_USAGE and the help command:
|
||||
// " This command does nothing at all.\n"
|
||||
// "Example:\n"
|
||||
// " isoworldremote\n"
|
||||
// " Does nothing.\n"
|
||||
//));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport RPCService *plugin_rpcconnect(color_ostream &)
|
||||
{
|
||||
RPCService *svc = new RPCService();
|
||||
svc->addFunction("GetEmbarkTile", GetEmbarkTile);
|
||||
svc->addFunction("GetEmbarkInfo", GetEmbarkInfo);
|
||||
svc->addFunction("GetRawNames", GetRawNames);
|
||||
return svc;
|
||||
}
|
||||
|
||||
// This is called right before the plugin library is removed from memory.
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
// You *MUST* kill all threads you created before this returns.
|
||||
// If everything fails, just return CR_FAILURE. Your plugin will be
|
||||
// in a zombie state, but things won't crash.
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// Called to notify the plugin about important state changes.
|
||||
// Invoked with DF suspended, and always before the matching plugin_onupdate.
|
||||
// More event codes may be added in the future.
|
||||
/*
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_GAME_LOADED:
|
||||
// initialize from the world just loaded
|
||||
break;
|
||||
case SC_GAME_UNLOADED:
|
||||
// cleanup
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
*/
|
||||
|
||||
// Whatever you put here will be done in each game step. Don't abuse it.
|
||||
// It's optional, so you can just comment it out like this if you don't need it.
|
||||
/*
|
||||
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||
{
|
||||
// whetever. You don't need to suspend DF execution here.
|
||||
return CR_OK;
|
||||
}
|
||||
*/
|
||||
|
||||
//// A command! It sits around and looks pretty. And it's nice and friendly.
|
||||
//command_result isoWorldRemote (color_ostream &out, std::vector <std::string> & parameters)
|
||||
//{
|
||||
// // It's nice to print a help message you get invalid options
|
||||
// // from the user instead of just acting strange.
|
||||
// // This can be achieved by adding the extended help string to the
|
||||
// // PluginCommand registration as show above, and then returning
|
||||
// // CR_WRONG_USAGE from the function. The same string will also
|
||||
// // be used by 'help your-command'.
|
||||
// if (!parameters.empty())
|
||||
// return CR_WRONG_USAGE;
|
||||
// // Commands are called from threads other than the DF one.
|
||||
// // Suspend this thread until DF has time for us. If you
|
||||
// // use CoreSuspender, it'll automatically resume DF when
|
||||
// // execution leaves the current scope.
|
||||
// CoreSuspender suspend;
|
||||
// // Actually do something here. Yay.
|
||||
// out.print("Doing a test...\n");
|
||||
// MapExtras::MapCache MC;
|
||||
// EmbarkTile test_tile;
|
||||
// if(!gather_embark_tile(0,0, &test_tile, &MC))
|
||||
// return CR_FAILURE;
|
||||
// //test-write the file to check it.
|
||||
// std::ofstream output_file("tile.p", std::ios_base::binary);
|
||||
// output_file << test_tile.SerializeAsString();
|
||||
// output_file.close();
|
||||
//
|
||||
// //load it again to verify.
|
||||
// std::ifstream input_file("tile.p", std::ios_base::binary);
|
||||
// std::string input_string( (std::istreambuf_iterator<char>(input_file) ),
|
||||
// (std::istreambuf_iterator<char>() ) );
|
||||
// EmbarkTile verify_tile;
|
||||
// verify_tile.ParseFromString(input_string);
|
||||
// //write contents to text file.
|
||||
// std::ofstream debug_text("tile.txt", std::ios_base::trunc);
|
||||
// debug_text << "world coords:" << verify_tile.world_x()<< "," << verify_tile.world_y()<< "," << verify_tile.world_z() << std::endl;
|
||||
// for(int i = 0; i < verify_tile.tile_layer_size(); i++) {
|
||||
// debug_text << "layer: " << i << std::endl;
|
||||
// for(int j = 0; j < 48; j++) {
|
||||
// debug_text << " ";
|
||||
// for(int k = 0; k < 48; k++) {
|
||||
// debug_text << verify_tile.tile_layer(i).mat_type_table(j*48+k) << ",";
|
||||
// }
|
||||
// debug_text << " ";
|
||||
// for(int k = 0; k < 48; k++) {
|
||||
// debug_text << std::setw(3) << verify_tile.tile_layer(i).mat_subtype_table(j*48+k) << ",";
|
||||
// }
|
||||
// debug_text << std::endl;
|
||||
// }
|
||||
// debug_text << std::endl;
|
||||
// }
|
||||
// // Give control back to DF.
|
||||
// return CR_OK;
|
||||
//}
|
||||
|
||||
static command_result GetEmbarkTile(color_ostream &stream, const TileRequest *in, EmbarkTile *out)
|
||||
{
|
||||
MapExtras::MapCache MC;
|
||||
gather_embark_tile(in->want_x() * 3, in->want_y() * 3, out, &MC);
|
||||
MC.trash();
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
static command_result GetEmbarkInfo(color_ostream &stream, const MapRequest *in, MapReply *out)
|
||||
{
|
||||
if(!Core::getInstance().isWorldLoaded()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!Core::getInstance().isMapLoaded()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!df::global::gamemode) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if((*df::global::gamemode != game_mode::ADVENTURE) && (*df::global::gamemode != game_mode::DWARF)) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!DFHack::Maps::IsValid()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!in->has_save_folder()) { //probably should send the stuff anyway, but nah.
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!(in->save_folder() == df::global::world->cur_savegame.save_dir)) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them.
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
out->set_available(true);
|
||||
out->set_current_year(*df::global::cur_year);
|
||||
out->set_current_season(*df::global::cur_season);
|
||||
out->set_region_x(df::global::world->map.region_x);
|
||||
out->set_region_y(df::global::world->map.region_y);
|
||||
out->set_region_size_x(df::global::world->map.x_count_block / 3);
|
||||
out->set_region_size_y(df::global::world->map.y_count_block / 3);
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
int coord_to_index_48(int x, int y) {
|
||||
return y*48+x;
|
||||
}
|
||||
|
||||
bool gather_embark_tile(int EmbX, int EmbY, EmbarkTile * tile, MapExtras::MapCache * MP) {
|
||||
tile->set_is_valid(false);
|
||||
tile->set_world_x(df::global::world->map.region_x + (EmbX/3));
|
||||
tile->set_world_y(df::global::world->map.region_y + (EmbY/3));
|
||||
tile->set_world_z(df::global::world->map.region_z + 1); //adding one because floors get shifted one downwards.
|
||||
tile->set_current_year(*df::global::cur_year);
|
||||
tile->set_current_season(*df::global::cur_season);
|
||||
int num_valid_layers = 0;
|
||||
for(int z = 0; z < MP->maxZ(); z++)
|
||||
{
|
||||
EmbarkTileLayer * tile_layer = tile->add_tile_layer();
|
||||
num_valid_layers += gather_embark_tile_layer(EmbX, EmbY, z, tile_layer, MP);
|
||||
}
|
||||
if(num_valid_layers > 0)
|
||||
tile->set_is_valid(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
bool gather_embark_tile_layer(int EmbX, int EmbY, int EmbZ, EmbarkTileLayer * tile, MapExtras::MapCache * MP)
|
||||
{
|
||||
for(int i = tile->mat_type_table_size(); i < 2304; i++) { //This is needed so we have a full array to work with, otherwise the size isn't updated correctly.
|
||||
tile->add_mat_type_table(AIR);
|
||||
tile->add_mat_subtype_table(0);
|
||||
}
|
||||
int num_valid_blocks = 0;
|
||||
for(int yy = 0; yy < 3; yy++) {
|
||||
for(int xx = 0; xx < 3; xx++) {
|
||||
DFCoord current_coord, upper_coord;
|
||||
current_coord.x = EmbX+xx;
|
||||
current_coord.y = EmbY+yy;
|
||||
current_coord.z = EmbZ;
|
||||
upper_coord = current_coord;
|
||||
upper_coord.z += 1;
|
||||
MapExtras::Block * b = MP->BlockAt(current_coord);
|
||||
MapExtras::Block * b_upper = MP->BlockAt(upper_coord);
|
||||
if(b && b->getRaw()) {
|
||||
for(int block_y=0; block_y<16; block_y++) {
|
||||
for(int block_x=0; block_x<16; block_x++) {
|
||||
df::coord2d block_coord;
|
||||
block_coord.x = block_x;
|
||||
block_coord.y = block_y;
|
||||
df::tiletype tile_type = b->tiletypeAt(block_coord);
|
||||
df::tiletype upper_tile = df::tiletype::Void;
|
||||
if(b_upper && b_upper->getRaw()) {
|
||||
upper_tile = b_upper->tiletypeAt(block_coord);
|
||||
}
|
||||
df::tile_designation designation = b->DesignationAt(block_coord);
|
||||
DFHack::t_matpair actual_mat;
|
||||
if(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor && (tileMaterial(tile_type) != tiletype_material::FROZEN_LIQUID) && (tileMaterial(tile_type) != tiletype_material::BROOK)) { //if the upper tile is a floor, use that material instead. Unless it's ice.
|
||||
actual_mat = b_upper->staticMaterialAt(block_coord);
|
||||
}
|
||||
else {
|
||||
actual_mat = b->staticMaterialAt(block_coord);
|
||||
}
|
||||
if(((tileMaterial(tile_type) == tiletype_material::FROZEN_LIQUID) || (tileMaterial(tile_type) == tiletype_material::BROOK)) && (tileShapeBasic(tileShape(tile_type)) == tiletype_shape_basic::Floor)) {
|
||||
tile_type = tiletype::OpenSpace;
|
||||
}
|
||||
unsigned int array_index = coord_to_index_48(xx*16+block_x, yy*16+block_y);
|
||||
//make a new fake material at the given index
|
||||
if(tileMaterial(tile_type) == tiletype_material::FROZEN_LIQUID && !((tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor) && (tileMaterial(upper_tile) != tiletype_material::FROZEN_LIQUID))) { //Ice.
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::LIQUID); //Ice is totally a liquid, shut up.
|
||||
tile->set_mat_subtype_table(array_index, LiquidType::ICE);
|
||||
num_valid_blocks++;
|
||||
}
|
||||
else if(designation.bits.flow_size && (tileShapeBasic(tileShape(upper_tile)) != tiletype_shape_basic::Floor)) { //Contains either water or lava.
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::LIQUID);
|
||||
if(designation.bits.liquid_type) //Magma
|
||||
tile->set_mat_subtype_table(array_index, LiquidType::MAGMA);
|
||||
else //water
|
||||
tile->set_mat_subtype_table(array_index, LiquidType::WATER);
|
||||
num_valid_blocks++;
|
||||
}
|
||||
else if(((tileShapeBasic(tileShape(tile_type)) != tiletype_shape_basic::Open) ||
|
||||
(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor)) &&
|
||||
((tileShapeBasic(tileShape(tile_type)) != tiletype_shape_basic::Floor) ||
|
||||
(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor))) { //if the upper tile is a floor, we don't skip, otherwise we do.
|
||||
if(actual_mat.mat_type == builtin_mats::INORGANIC) { //inorganic
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::INORGANIC);
|
||||
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
|
||||
}
|
||||
else if(actual_mat.mat_type == 419) { //Growing plants
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::PLANT);
|
||||
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
|
||||
}
|
||||
else if(actual_mat.mat_type >= 420) { //Wooden constructions. Different from growing plants.
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::WOOD);
|
||||
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
|
||||
}
|
||||
else { //Unknown and unsupported stuff. Will just be drawn as grey.
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::OTHER);
|
||||
tile->set_mat_subtype_table(array_index, actual_mat.mat_type);
|
||||
}
|
||||
num_valid_blocks++;
|
||||
}
|
||||
else {
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::AIR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (num_valid_blocks >0);
|
||||
}
|
||||
|
||||
static command_result GetRawNames(color_ostream &stream, const MapRequest *in, RawNames *out){
|
||||
if(!Core::getInstance().isWorldLoaded()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!Core::getInstance().isMapLoaded()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!df::global::gamemode) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if((*df::global::gamemode != game_mode::ADVENTURE) && (*df::global::gamemode != game_mode::DWARF)) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!DFHack::Maps::IsValid()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!in->has_save_folder()) { //probably should send the stuff anyway, but nah.
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!(in->save_folder() == df::global::world->cur_savegame.save_dir)) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them.
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
out->set_available(true);
|
||||
for(int i = 0; i < df::global::world->raws.inorganics.size(); i++){
|
||||
out->add_inorganic(df::global::world->raws.inorganics[i]->id);
|
||||
}
|
||||
|
||||
for(int i = 0; i < df::global::world->raws.plants.all.size(); i++){
|
||||
out->add_organic(df::global::world->raws.plants.all[i]->id);
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
local _ENV = mkmodule('plugins.zone')
|
||||
|
||||
--[[
|
||||
|
||||
Native functions:
|
||||
|
||||
* autobutcher_isEnabled()
|
||||
* autowatch_isEnabled()
|
||||
|
||||
--]]
|
||||
|
||||
return _ENV
|
@ -0,0 +1,263 @@
|
||||
#include <sstream>
|
||||
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <VTableInterpose.h>
|
||||
|
||||
#include "DataDefs.h"
|
||||
|
||||
#include "df/building.h"
|
||||
#include "df/enabler.h"
|
||||
#include "df/item.h"
|
||||
#include "df/ui.h"
|
||||
#include "df/unit.h"
|
||||
#include "df/viewscreen_dwarfmodest.h"
|
||||
#include "df/world.h"
|
||||
|
||||
#include "modules/Gui.h"
|
||||
#include "modules/Screen.h"
|
||||
|
||||
|
||||
using std::set;
|
||||
using std::string;
|
||||
using std::ostringstream;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
using df::global::enabler;
|
||||
using df::global::gps;
|
||||
using df::global::world;
|
||||
using df::global::ui;
|
||||
|
||||
|
||||
static int32_t last_x, last_y, last_z;
|
||||
static size_t max_list_size = 100000; // Avoid iterating over huge lists
|
||||
|
||||
struct mousequery_hook : public df::viewscreen_dwarfmodest
|
||||
{
|
||||
typedef df::viewscreen_dwarfmodest interpose_base;
|
||||
|
||||
void send_key(const df::interface_key &key)
|
||||
{
|
||||
set<df::interface_key> tmp;
|
||||
tmp.insert(key);
|
||||
//INTERPOSE_NEXT(feed)(&tmp);
|
||||
this->feed(&tmp);
|
||||
}
|
||||
|
||||
df::interface_key get_default_query_mode(const int32_t &x, const int32_t &y, const int32_t &z)
|
||||
{
|
||||
bool fallback_to_building_query = false;
|
||||
|
||||
// Check for unit under cursor
|
||||
size_t count = world->units.all.size();
|
||||
if (count <= max_list_size)
|
||||
{
|
||||
for(size_t i = 0; i < count; i++)
|
||||
{
|
||||
df::unit *unit = world->units.all[i];
|
||||
|
||||
if(unit->pos.x == x && unit->pos.y == y && unit->pos.z == z)
|
||||
return df::interface_key::D_VIEWUNIT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fallback_to_building_query = true;
|
||||
}
|
||||
|
||||
// Check for building under cursor
|
||||
count = world->buildings.all.size();
|
||||
if (count <= max_list_size)
|
||||
{
|
||||
for(size_t i = 0; i < count; i++)
|
||||
{
|
||||
df::building *bld = world->buildings.all[i];
|
||||
|
||||
if (z == bld->z &&
|
||||
x >= bld->x1 && x <= bld->x2 &&
|
||||
y >= bld->y1 && y <= bld->y2)
|
||||
{
|
||||
df::building_type type = bld->getType();
|
||||
|
||||
if (type == building_type::Stockpile)
|
||||
{
|
||||
fallback_to_building_query = true;
|
||||
break; // Check for items in stockpile first
|
||||
}
|
||||
|
||||
// For containers use item view, fir everything else, query view
|
||||
return (type == building_type::Box || type == building_type::Cabinet ||
|
||||
type == building_type::Weaponrack || type == building_type::Armorstand)
|
||||
? df::interface_key::D_BUILDITEM : df::interface_key::D_BUILDJOB;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fallback_to_building_query = true;
|
||||
}
|
||||
|
||||
|
||||
// Check for items under cursor
|
||||
count = world->items.all.size();
|
||||
if (count <= max_list_size)
|
||||
{
|
||||
for(size_t i = 0; i < count; i++)
|
||||
{
|
||||
df::item *item = world->items.all[i];
|
||||
if (z == item->pos.z && x == item->pos.x && y == item->pos.y &&
|
||||
!item->flags.bits.in_building && !item->flags.bits.hidden &&
|
||||
!item->flags.bits.in_job && !item->flags.bits.in_chest &&
|
||||
!item->flags.bits.in_inventory)
|
||||
{
|
||||
return df::interface_key::D_LOOK;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fallback_to_building_query = true;
|
||||
}
|
||||
|
||||
return (fallback_to_building_query) ? df::interface_key::D_BUILDJOB : df::interface_key::D_LOOK;
|
||||
}
|
||||
|
||||
bool handle_mouse(const set<df::interface_key> *input)
|
||||
{
|
||||
int32_t cx, cy, vz;
|
||||
if (enabler->tracking_on)
|
||||
{
|
||||
if (enabler->mouse_lbut)
|
||||
{
|
||||
int32_t mx, my;
|
||||
if (Gui::getMousePos(mx, my))
|
||||
{
|
||||
int32_t vx, vy;
|
||||
if (Gui::getViewCoords(vx, vy, vz))
|
||||
{
|
||||
cx = vx + mx - 1;
|
||||
cy = vy + my - 1;
|
||||
|
||||
using namespace df::enums::ui_sidebar_mode;
|
||||
df::interface_key key = interface_key::NONE;
|
||||
bool cursor_still_here = (last_x == cx && last_y == cy && last_z == vz);
|
||||
switch(ui->main.mode)
|
||||
{
|
||||
case QueryBuilding:
|
||||
if (cursor_still_here)
|
||||
key = df::interface_key::D_BUILDITEM;
|
||||
break;
|
||||
|
||||
case BuildingItems:
|
||||
if (cursor_still_here)
|
||||
key = df::interface_key::D_VIEWUNIT;
|
||||
break;
|
||||
|
||||
case ViewUnits:
|
||||
if (cursor_still_here)
|
||||
key = df::interface_key::D_LOOK;
|
||||
break;
|
||||
|
||||
case LookAround:
|
||||
if (cursor_still_here)
|
||||
key = df::interface_key::D_BUILDJOB;
|
||||
break;
|
||||
|
||||
case Default:
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
enabler->mouse_lbut = 0;
|
||||
|
||||
// Can't check limits earlier as we must be sure we are in query or default mode we can clear the button flag
|
||||
// Otherwise the feed gets stuck in a loop
|
||||
uint8_t menu_width, area_map_width;
|
||||
Gui::getMenuWidth(menu_width, area_map_width);
|
||||
int32_t w = gps->dimx;
|
||||
if (menu_width == 1) w -= 57; //Menu is open doubly wide
|
||||
else if (menu_width == 2 && area_map_width == 3) w -= 33; //Just the menu is open
|
||||
else if (menu_width == 2 && area_map_width == 2) w -= 26; //Just the area map is open
|
||||
|
||||
if (mx < 1 || mx > w || my < 1 || my > gps->dimy - 2)
|
||||
return false;
|
||||
|
||||
while (ui->main.mode != Default)
|
||||
{
|
||||
send_key(df::interface_key::LEAVESCREEN);
|
||||
}
|
||||
|
||||
if (key == interface_key::NONE)
|
||||
key = get_default_query_mode(cx, cy, vz);
|
||||
|
||||
send_key(key);
|
||||
|
||||
// Force UI refresh
|
||||
Gui::setCursorCoords(cx, cy, vz);
|
||||
send_key(interface_key::CURSOR_DOWN_Z);
|
||||
send_key(interface_key::CURSOR_UP_Z);
|
||||
last_x = cx;
|
||||
last_y = cy;
|
||||
last_z = vz;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (enabler->mouse_rbut)
|
||||
{
|
||||
// Escape out of query mode
|
||||
using namespace df::enums::ui_sidebar_mode;
|
||||
if (ui->main.mode == QueryBuilding || ui->main.mode == BuildingItems ||
|
||||
ui->main.mode == ViewUnits || ui->main.mode == LookAround)
|
||||
{
|
||||
while (ui->main.mode != Default)
|
||||
{
|
||||
enabler->mouse_rbut = 0;
|
||||
send_key(df::interface_key::LEAVESCREEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
||||
{
|
||||
if (!handle_mouse(input))
|
||||
INTERPOSE_NEXT(feed)(input);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(mousequery_hook, feed);
|
||||
|
||||
DFHACK_PLUGIN("mousequery");
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
if (!gps || !INTERPOSE_HOOK(mousequery_hook, feed).apply())
|
||||
out.printerr("Could not insert mousequery hooks!\n");
|
||||
|
||||
last_x = last_y = last_z = -1;
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_MAP_LOADED:
|
||||
last_x = last_y = last_z = -1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package isoworldremote;
|
||||
|
||||
//Describes a very basic material structure for the map embark
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
enum BasicMaterial {
|
||||
AIR = 0;
|
||||
OTHER = 1;
|
||||
INORGANIC = 2;
|
||||
LIQUID = 3;
|
||||
PLANT = 4;
|
||||
WOOD = 5;
|
||||
};
|
||||
|
||||
enum LiquidType {
|
||||
ICE = 0;
|
||||
WATER = 1;
|
||||
MAGMA = 2;
|
||||
}
|
||||
|
||||
message EmbarkTileLayer {
|
||||
repeated BasicMaterial mat_type_table = 4 [packed=true];
|
||||
repeated int32 mat_subtype_table = 5 [packed=true];
|
||||
}
|
||||
|
||||
message EmbarkTile {
|
||||
required int32 world_x = 1;
|
||||
required int32 world_y = 2;
|
||||
required sint32 world_z = 3;
|
||||
repeated EmbarkTileLayer tile_layer = 4;
|
||||
optional int32 current_year = 5;
|
||||
optional int32 current_season = 6;
|
||||
optional bool is_valid = 7;
|
||||
}
|
||||
|
||||
message TileRequest {
|
||||
optional int32 want_x = 1;
|
||||
optional int32 want_y = 2;
|
||||
}
|
||||
|
||||
message MapRequest {
|
||||
optional string save_folder = 1;
|
||||
}
|
||||
|
||||
message MapReply {
|
||||
required bool available = 1;
|
||||
optional int32 region_x = 2;
|
||||
optional int32 region_y = 3;
|
||||
optional int32 region_size_x = 4;
|
||||
optional int32 region_size_y = 5;
|
||||
optional int32 current_year = 6;
|
||||
optional int32 current_season = 7;
|
||||
}
|
||||
|
||||
message RawNames {
|
||||
required bool available = 1;
|
||||
repeated string inorganic = 2;
|
||||
repeated string organic = 3;
|
||||
}
|
@ -0,0 +1,315 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <VTableInterpose.h>
|
||||
|
||||
|
||||
// DF data structure definition headers
|
||||
#include "DataDefs.h"
|
||||
#include "MiscUtils.h"
|
||||
#include "Types.h"
|
||||
#include "df/viewscreen_dwarfmodest.h"
|
||||
#include "df/world.h"
|
||||
#include "df/building_constructionst.h"
|
||||
#include "df/building.h"
|
||||
#include "df/job.h"
|
||||
#include "df/job_item.h"
|
||||
|
||||
#include "modules/Gui.h"
|
||||
#include "modules/Screen.h"
|
||||
#include "modules/Buildings.h"
|
||||
#include "modules/Maps.h"
|
||||
|
||||
#include "modules/World.h"
|
||||
|
||||
using std::map;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
using df::global::gps;
|
||||
using df::global::ui;
|
||||
using df::global::world;
|
||||
|
||||
DFHACK_PLUGIN("resume");
|
||||
#define PLUGIN_VERSION 0.2
|
||||
|
||||
#ifndef HAVE_NULLPTR
|
||||
#define nullptr 0L
|
||||
#endif
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
template <class T, typename Fn>
|
||||
static void for_each_(vector<T> &v, Fn func)
|
||||
{
|
||||
for_each(v.begin(), v.end(), func);
|
||||
}
|
||||
|
||||
template <class T, class V, typename Fn>
|
||||
static void transform_(vector<T> &src, vector<V> &dst, Fn func)
|
||||
{
|
||||
transform(src.begin(), src.end(), back_inserter(dst), func);
|
||||
}
|
||||
|
||||
void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0)
|
||||
{
|
||||
Screen::paintString(Screen::Pen(' ', color, 0), x, y, text);
|
||||
if (newline)
|
||||
{
|
||||
++y;
|
||||
x = left_margin;
|
||||
}
|
||||
else
|
||||
x += text.length();
|
||||
}
|
||||
|
||||
df::job *get_suspended_job(df::building *bld)
|
||||
{
|
||||
if (bld->getBuildStage() != 0)
|
||||
return nullptr;
|
||||
|
||||
if (bld->jobs.size() == 0)
|
||||
return nullptr;
|
||||
|
||||
auto job = bld->jobs[0];
|
||||
if (job->flags.bits.suspend)
|
||||
return job;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct SuspendedBuilding
|
||||
{
|
||||
df::building *bld;
|
||||
df::coord pos;
|
||||
bool was_resumed;
|
||||
bool is_planned;
|
||||
|
||||
SuspendedBuilding(df::building *bld_) : bld(bld_), was_resumed(false), is_planned(false)
|
||||
{
|
||||
pos = df::coord(bld->centerx, bld->centery, bld->z);
|
||||
}
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
return bld && Buildings::findAtTile(pos) == bld && get_suspended_job(bld);
|
||||
}
|
||||
};
|
||||
|
||||
static bool enabled = false;
|
||||
static bool buildings_scanned = false;
|
||||
static vector<SuspendedBuilding> suspended_buildings, resumed_buildings;
|
||||
|
||||
void scan_for_suspended_buildings()
|
||||
{
|
||||
if (buildings_scanned)
|
||||
return;
|
||||
|
||||
for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++)
|
||||
{
|
||||
auto bld = *b;
|
||||
auto job = get_suspended_job(bld);
|
||||
if (job)
|
||||
{
|
||||
SuspendedBuilding sb(bld);
|
||||
sb.is_planned = job->job_items.size() == 1 && job->job_items[0]->item_type == item_type::NONE;
|
||||
|
||||
auto it = find_if(resumed_buildings.begin(), resumed_buildings.end(),
|
||||
[&] (SuspendedBuilding &rsb) { return rsb.bld == bld; });
|
||||
|
||||
sb.was_resumed = it != resumed_buildings.end();
|
||||
|
||||
suspended_buildings.push_back(sb);
|
||||
}
|
||||
}
|
||||
|
||||
buildings_scanned = true;
|
||||
}
|
||||
|
||||
void show_suspended_buildings()
|
||||
{
|
||||
int32_t vx, vy, vz;
|
||||
if (!Gui::getViewCoords(vx, vy, vz))
|
||||
return;
|
||||
|
||||
auto dims = Gui::getDwarfmodeViewDims();
|
||||
int left_margin = vx + dims.map_x2;
|
||||
int bottom_margin = vy + dims.y2;
|
||||
|
||||
for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end();)
|
||||
{
|
||||
if (!sb->isValid())
|
||||
{
|
||||
sb = suspended_buildings.erase(sb);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sb->bld->z == vz && sb->bld->centerx >= vx && sb->bld->centerx <= left_margin &&
|
||||
sb->bld->centery >= vy && sb->bld->centery <= bottom_margin)
|
||||
{
|
||||
int x = sb->bld->centerx - vx + 1;
|
||||
int y = sb->bld->centery - vy + 1;
|
||||
auto color = COLOR_YELLOW;
|
||||
if (sb->is_planned)
|
||||
color = COLOR_GREEN;
|
||||
else if (sb->was_resumed)
|
||||
color = COLOR_RED;
|
||||
|
||||
OutputString(color, x, y, "X");
|
||||
}
|
||||
|
||||
sb++;
|
||||
}
|
||||
}
|
||||
|
||||
void clear_scanned()
|
||||
{
|
||||
buildings_scanned = false;
|
||||
suspended_buildings.clear();
|
||||
}
|
||||
|
||||
void resume_suspended_buildings(color_ostream &out)
|
||||
{
|
||||
out << "Resuming all buildings." << endl;
|
||||
|
||||
for (auto isb = resumed_buildings.begin(); isb != resumed_buildings.end();)
|
||||
{
|
||||
if (isb->isValid())
|
||||
{
|
||||
isb++;
|
||||
continue;
|
||||
}
|
||||
|
||||
isb = resumed_buildings.erase(isb);
|
||||
}
|
||||
|
||||
scan_for_suspended_buildings();
|
||||
for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end(); sb++)
|
||||
{
|
||||
if (sb->is_planned)
|
||||
continue;
|
||||
|
||||
resumed_buildings.push_back(*sb);
|
||||
sb->bld->jobs[0]->flags.bits.suspend = false;
|
||||
}
|
||||
|
||||
clear_scanned();
|
||||
|
||||
out << resumed_buildings.size() << " buildings resumed" << endl;
|
||||
}
|
||||
|
||||
|
||||
//START Viewscreen Hook
|
||||
struct resume_hook : public df::viewscreen_dwarfmodest
|
||||
{
|
||||
//START UI Methods
|
||||
typedef df::viewscreen_dwarfmodest interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
||||
{
|
||||
INTERPOSE_NEXT(render)();
|
||||
|
||||
if (enabled && DFHack::World::ReadPauseState() && ui->main.mode == ui_sidebar_mode::Default)
|
||||
{
|
||||
scan_for_suspended_buildings();
|
||||
show_suspended_buildings();
|
||||
}
|
||||
else
|
||||
{
|
||||
clear_scanned();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(resume_hook, render);
|
||||
|
||||
|
||||
static command_result resume_cmd(color_ostream &out, vector <string> & parameters)
|
||||
{
|
||||
bool show_help = false;
|
||||
if (parameters.empty())
|
||||
{
|
||||
show_help = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto cmd = parameters[0][0];
|
||||
if (cmd == 'v')
|
||||
{
|
||||
out << "Resume" << endl << "Version: " << PLUGIN_VERSION << endl;
|
||||
}
|
||||
else if (cmd == 's')
|
||||
{
|
||||
enabled = true;
|
||||
out << "Overlay enabled" << endl;
|
||||
}
|
||||
else if (cmd == 'h')
|
||||
{
|
||||
enabled = false;
|
||||
out << "Overlay disabled" << endl;
|
||||
}
|
||||
else if (cmd == 'a')
|
||||
{
|
||||
resume_suspended_buildings(out);
|
||||
}
|
||||
else
|
||||
{
|
||||
show_help = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (show_help)
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
if (!gps || !INTERPOSE_HOOK(resume_hook, render).apply())
|
||||
out.printerr("Could not insert resume hooks!\n");
|
||||
|
||||
commands.push_back(
|
||||
PluginCommand(
|
||||
"resume", "A plugin to help display and resume suspended constructions conveniently",
|
||||
resume_cmd, false,
|
||||
"resume show\n"
|
||||
" Show overlay when paused:\n"
|
||||
" Yellow: Suspended construction\n"
|
||||
" Red: Suspended after resume attempt, possibly stuck\n"
|
||||
" Green: Planned building waiting for materials\n"
|
||||
"resume hide\n"
|
||||
" Hide overlay\n"
|
||||
"resume all\n"
|
||||
" Resume all suspended building constructions\n"
|
||||
));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_MAP_LOADED:
|
||||
suspended_buildings.clear();
|
||||
resumed_buildings.clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
Subproject commit 83e2b8a3754e2feb0d5bf106d2b43ff71ff63935
|
||||
Subproject commit 0d41614ff3dae9245e786ad667b0e463fe0dea3e
|
@ -0,0 +1,580 @@
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "Core.h"
|
||||
#include "MiscUtils.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <VTableInterpose.h>
|
||||
|
||||
#include "modules/Screen.h"
|
||||
|
||||
#include "df/enabler.h"
|
||||
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::map;
|
||||
using std::ostringstream;
|
||||
using std::set;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
using df::global::enabler;
|
||||
using df::global::gps;
|
||||
|
||||
|
||||
#ifndef HAVE_NULLPTR
|
||||
#define nullptr 0L
|
||||
#endif
|
||||
|
||||
#define COLOR_TITLE COLOR_BLUE
|
||||
#define COLOR_UNSELECTED COLOR_GREY
|
||||
#define COLOR_SELECTED COLOR_WHITE
|
||||
#define COLOR_HIGHLIGHTED COLOR_GREEN
|
||||
|
||||
|
||||
template <class T, typename Fn>
|
||||
static void for_each_(vector<T> &v, Fn func)
|
||||
{
|
||||
for_each(v.begin(), v.end(), func);
|
||||
}
|
||||
|
||||
template <class T, class V, typename Fn>
|
||||
static void for_each_(map<T, V> &v, Fn func)
|
||||
{
|
||||
for_each(v.begin(), v.end(), func);
|
||||
}
|
||||
|
||||
template <class T, class V, typename Fn>
|
||||
static void transform_(vector<T> &src, vector<V> &dst, Fn func)
|
||||
{
|
||||
transform(src.begin(), src.end(), back_inserter(dst), func);
|
||||
}
|
||||
|
||||
typedef int8_t UIColor;
|
||||
|
||||
void OutputString(UIColor color, int &x, int &y, const std::string &text,
|
||||
bool newline = false, int left_margin = 0, const UIColor bg_color = 0)
|
||||
{
|
||||
Screen::paintString(Screen::Pen(' ', color, bg_color), x, y, text);
|
||||
if (newline)
|
||||
{
|
||||
++y;
|
||||
x = left_margin;
|
||||
}
|
||||
else
|
||||
x += text.length();
|
||||
}
|
||||
|
||||
void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false,
|
||||
int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN)
|
||||
{
|
||||
OutputString(hotkey_color, x, y, hotkey);
|
||||
string display(": ");
|
||||
display.append(text);
|
||||
OutputString(text_color, x, y, display, newline, left_margin);
|
||||
}
|
||||
|
||||
void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = false,
|
||||
int left_margin = 0, int8_t hotkey_color = COLOR_LIGHTGREEN)
|
||||
{
|
||||
OutputString(hotkey_color, x, y, hotkey);
|
||||
OutputString(COLOR_WHITE, x, y, ": ");
|
||||
OutputString((state) ? COLOR_WHITE : COLOR_GREY, x, y, text, newline, left_margin);
|
||||
}
|
||||
|
||||
void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE)
|
||||
{
|
||||
OutputHotkeyString(x, y, text, hotkey);
|
||||
OutputString(COLOR_WHITE, x, y, ": ");
|
||||
if (state)
|
||||
OutputString(COLOR_GREEN, x, y, "Enabled", newline, left_margin);
|
||||
else
|
||||
OutputString(COLOR_GREY, x, y, "Disabled", newline, left_margin);
|
||||
}
|
||||
|
||||
const int ascii_to_enum_offset = interface_key::STRING_A048 - '0';
|
||||
|
||||
inline string int_to_string(const int n)
|
||||
{
|
||||
return static_cast<ostringstream*>( &(ostringstream() << n) )->str();
|
||||
}
|
||||
|
||||
static void set_to_limit(int &value, const int maximum, const int min = 0)
|
||||
{
|
||||
if (value < min)
|
||||
value = min;
|
||||
else if (value > maximum)
|
||||
value = maximum;
|
||||
}
|
||||
|
||||
|
||||
inline void paint_text(const UIColor color, const int &x, const int &y, const std::string &text, const UIColor background = 0)
|
||||
{
|
||||
Screen::paintString(Screen::Pen(' ', color, background), x, y, text);
|
||||
}
|
||||
|
||||
static string pad_string(string text, const int size, const bool front = true, const bool trim = false)
|
||||
{
|
||||
if (text.length() > size)
|
||||
{
|
||||
if (trim && size > 10)
|
||||
{
|
||||
text = text.substr(0, size-3);
|
||||
text.append("...");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
string aligned(size - text.length(), ' ');
|
||||
if (front)
|
||||
{
|
||||
aligned.append(text);
|
||||
return aligned;
|
||||
}
|
||||
else
|
||||
{
|
||||
text.append(aligned);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* List classes
|
||||
*/
|
||||
template <typename T>
|
||||
class ListEntry
|
||||
{
|
||||
public:
|
||||
T elem;
|
||||
string text, keywords;
|
||||
bool selected;
|
||||
|
||||
ListEntry(const string text, const T elem, const string keywords = "") :
|
||||
elem(elem), text(text), selected(false), keywords(keywords)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ListColumn
|
||||
{
|
||||
public:
|
||||
int highlighted_index;
|
||||
int display_start_offset;
|
||||
unsigned short text_clip_at;
|
||||
int32_t bottom_margin, search_margin, left_margin;
|
||||
bool multiselect;
|
||||
bool allow_null;
|
||||
bool auto_select;
|
||||
bool force_sort;
|
||||
bool allow_search;
|
||||
bool feed_changed_highlight;
|
||||
|
||||
ListColumn()
|
||||
{
|
||||
bottom_margin = 3;
|
||||
clear();
|
||||
left_margin = 2;
|
||||
search_margin = 63;
|
||||
highlighted_index = 0;
|
||||
text_clip_at = 0;
|
||||
multiselect = false;
|
||||
allow_null = true;
|
||||
auto_select = false;
|
||||
force_sort = false;
|
||||
allow_search = true;
|
||||
feed_changed_highlight = false;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
list.clear();
|
||||
display_list.clear();
|
||||
display_start_offset = 0;
|
||||
max_item_width = title.length();
|
||||
resize();
|
||||
}
|
||||
|
||||
void resize()
|
||||
{
|
||||
display_max_rows = gps->dimy - 4 - bottom_margin;
|
||||
}
|
||||
|
||||
void add(ListEntry<T> &entry)
|
||||
{
|
||||
list.push_back(entry);
|
||||
if (entry.text.length() > max_item_width)
|
||||
max_item_width = entry.text.length();
|
||||
}
|
||||
|
||||
void add(const string &text, const T &elem)
|
||||
{
|
||||
list.push_back(ListEntry<T>(text, elem));
|
||||
if (text.length() > max_item_width)
|
||||
max_item_width = text.length();
|
||||
}
|
||||
|
||||
int fixWidth()
|
||||
{
|
||||
if (text_clip_at > 0 && max_item_width > text_clip_at)
|
||||
max_item_width = text_clip_at;
|
||||
|
||||
for (auto it = list.begin(); it != list.end(); it++)
|
||||
{
|
||||
it->text = pad_string(it->text, max_item_width, false);
|
||||
}
|
||||
|
||||
return left_margin + max_item_width;
|
||||
}
|
||||
|
||||
virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {}
|
||||
|
||||
void display(const bool is_selected_column) const
|
||||
{
|
||||
int32_t y = 2;
|
||||
paint_text(COLOR_TITLE, left_margin, y, title);
|
||||
|
||||
int last_index_able_to_display = display_start_offset + display_max_rows;
|
||||
for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++)
|
||||
{
|
||||
++y;
|
||||
UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : COLOR_UNSELECTED;
|
||||
UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK;
|
||||
|
||||
string item_label = display_list[i]->text;
|
||||
if (text_clip_at > 0 && item_label.length() > text_clip_at)
|
||||
item_label.resize(text_clip_at);
|
||||
|
||||
paint_text(fg_color, left_margin, y, item_label, bg_color);
|
||||
int x = left_margin + display_list[i]->text.length() + 1;
|
||||
display_extras(display_list[i]->elem, x, y);
|
||||
}
|
||||
|
||||
if (is_selected_column && allow_search)
|
||||
{
|
||||
y = gps->dimy - 3;
|
||||
int32_t x = search_margin;
|
||||
OutputHotkeyString(x, y, "Search" ,"S");
|
||||
OutputString(COLOR_WHITE, x, y, ": ");
|
||||
OutputString(COLOR_WHITE, x, y, search_string);
|
||||
OutputString(COLOR_LIGHTGREEN, x, y, "_");
|
||||
}
|
||||
}
|
||||
|
||||
void filterDisplay()
|
||||
{
|
||||
ListEntry<T> *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL;
|
||||
display_list.clear();
|
||||
|
||||
search_string = toLower(search_string);
|
||||
vector<string> search_tokens;
|
||||
if (!search_string.empty())
|
||||
split_string(&search_tokens, search_string, " ");
|
||||
|
||||
for (size_t i = 0; i < list.size(); i++)
|
||||
{
|
||||
ListEntry<T> *entry = &list[i];
|
||||
|
||||
bool include_item = true;
|
||||
if (!search_string.empty())
|
||||
{
|
||||
string item_string = toLower(list[i].text);
|
||||
for (auto si = search_tokens.begin(); si != search_tokens.end(); si++)
|
||||
{
|
||||
if (!si->empty() && item_string.find(*si) == string::npos &&
|
||||
list[i].keywords.find(*si) == string::npos)
|
||||
{
|
||||
include_item = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (include_item)
|
||||
{
|
||||
display_list.push_back(entry);
|
||||
if (entry == prev_selected)
|
||||
highlighted_index = display_list.size() - 1;
|
||||
}
|
||||
else if (auto_select)
|
||||
{
|
||||
entry->selected = false;
|
||||
}
|
||||
}
|
||||
changeHighlight(0);
|
||||
feed_changed_highlight = true;
|
||||
}
|
||||
|
||||
void selectDefaultEntry()
|
||||
{
|
||||
for (size_t i = 0; i < display_list.size(); i++)
|
||||
{
|
||||
if (display_list[i]->selected)
|
||||
{
|
||||
highlighted_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void validateHighlight()
|
||||
{
|
||||
set_to_limit(highlighted_index, display_list.size() - 1);
|
||||
|
||||
if (highlighted_index < display_start_offset)
|
||||
display_start_offset = highlighted_index;
|
||||
else if (highlighted_index >= display_start_offset + display_max_rows)
|
||||
display_start_offset = highlighted_index - display_max_rows + 1;
|
||||
|
||||
if (auto_select || (!allow_null && list.size() == 1))
|
||||
display_list[highlighted_index]->selected = true;
|
||||
|
||||
feed_changed_highlight = true;
|
||||
}
|
||||
|
||||
void changeHighlight(const int highlight_change, const int offset_shift = 0)
|
||||
{
|
||||
if (!initHighlightChange())
|
||||
return;
|
||||
|
||||
highlighted_index += highlight_change + offset_shift * display_max_rows;
|
||||
|
||||
display_start_offset += offset_shift * display_max_rows;
|
||||
set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows));
|
||||
validateHighlight();
|
||||
}
|
||||
|
||||
void setHighlight(const int index)
|
||||
{
|
||||
if (!initHighlightChange())
|
||||
return;
|
||||
|
||||
highlighted_index = index;
|
||||
validateHighlight();
|
||||
}
|
||||
|
||||
bool initHighlightChange()
|
||||
{
|
||||
if (display_list.size() == 0)
|
||||
return false;
|
||||
|
||||
if (auto_select && !multiselect)
|
||||
{
|
||||
for (auto it = list.begin(); it != list.end(); it++)
|
||||
{
|
||||
it->selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void toggleHighlighted()
|
||||
{
|
||||
if (auto_select)
|
||||
return;
|
||||
|
||||
ListEntry<T> *entry = display_list[highlighted_index];
|
||||
if (!multiselect || !allow_null)
|
||||
{
|
||||
int selected_count = 0;
|
||||
for (size_t i = 0; i < list.size(); i++)
|
||||
{
|
||||
if (!multiselect && !entry->selected)
|
||||
list[i].selected = false;
|
||||
if (!allow_null && list[i].selected)
|
||||
selected_count++;
|
||||
}
|
||||
|
||||
if (!allow_null && entry->selected && selected_count == 1)
|
||||
return;
|
||||
}
|
||||
|
||||
entry->selected = !entry->selected;
|
||||
}
|
||||
|
||||
vector<T> getSelectedElems(bool only_one = false)
|
||||
{
|
||||
vector<T> results;
|
||||
for (auto it = list.begin(); it != list.end(); it++)
|
||||
{
|
||||
if ((*it).selected)
|
||||
{
|
||||
results.push_back(it->elem);
|
||||
if (only_one)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
T getFirstSelectedElem()
|
||||
{
|
||||
vector<T> results = getSelectedElems(true);
|
||||
if (results.size() == 0)
|
||||
return nullptr;
|
||||
else
|
||||
return results[0];
|
||||
}
|
||||
|
||||
void clearSelection()
|
||||
{
|
||||
for_each_(list, [] (ListEntry<T> &e) { e.selected = false; });
|
||||
}
|
||||
|
||||
void selectItem(const T elem)
|
||||
{
|
||||
int i = 0;
|
||||
for (; i < display_list.size(); i++)
|
||||
{
|
||||
if (display_list[i]->elem == elem)
|
||||
{
|
||||
setHighlight(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clearSearch()
|
||||
{
|
||||
search_string.clear();
|
||||
filterDisplay();
|
||||
}
|
||||
|
||||
size_t getDisplayListSize()
|
||||
{
|
||||
return display_list.size();
|
||||
}
|
||||
|
||||
vector<ListEntry<T>*> &getDisplayList()
|
||||
{
|
||||
return display_list;
|
||||
}
|
||||
|
||||
size_t getBaseListSize()
|
||||
{
|
||||
return list.size();
|
||||
}
|
||||
|
||||
bool feed(set<df::interface_key> *input)
|
||||
{
|
||||
feed_changed_highlight = false;
|
||||
if (input->count(interface_key::CURSOR_UP))
|
||||
{
|
||||
changeHighlight(-1);
|
||||
}
|
||||
else if (input->count(interface_key::CURSOR_DOWN))
|
||||
{
|
||||
changeHighlight(1);
|
||||
}
|
||||
else if (input->count(interface_key::STANDARDSCROLL_PAGEUP))
|
||||
{
|
||||
changeHighlight(0, -1);
|
||||
}
|
||||
else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN))
|
||||
{
|
||||
changeHighlight(0, 1);
|
||||
}
|
||||
else if (input->count(interface_key::SELECT) && !auto_select)
|
||||
{
|
||||
toggleHighlighted();
|
||||
}
|
||||
else if (input->count(interface_key::CUSTOM_SHIFT_S))
|
||||
{
|
||||
clearSearch();
|
||||
}
|
||||
else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut)
|
||||
{
|
||||
return setHighlightByMouse();
|
||||
}
|
||||
else if (allow_search)
|
||||
{
|
||||
// Search query typing mode always on
|
||||
df::interface_key last_token = *input->rbegin();
|
||||
if ((last_token >= interface_key::STRING_A096 && last_token <= interface_key::STRING_A123) ||
|
||||
last_token == interface_key::STRING_A032)
|
||||
{
|
||||
// Standard character
|
||||
search_string += last_token - ascii_to_enum_offset;
|
||||
filterDisplay();
|
||||
}
|
||||
else if (last_token == interface_key::STRING_A000)
|
||||
{
|
||||
// Backspace
|
||||
if (search_string.length() > 0)
|
||||
{
|
||||
search_string.erase(search_string.length()-1);
|
||||
filterDisplay();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool setHighlightByMouse()
|
||||
{
|
||||
if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 &&
|
||||
gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width)
|
||||
{
|
||||
int new_index = display_start_offset + gps->mouse_y - 3;
|
||||
if (new_index < display_list.size())
|
||||
setHighlight(new_index);
|
||||
|
||||
enabler->mouse_lbut = enabler->mouse_rbut = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sort()
|
||||
{
|
||||
if (force_sort || list.size() < 100)
|
||||
std::sort(list.begin(), list.end(),
|
||||
[] (ListEntry<T> const& a, ListEntry<T> const& b) { return a.text.compare(b.text) < 0; });
|
||||
|
||||
filterDisplay();
|
||||
}
|
||||
|
||||
void setTitle(const string t)
|
||||
{
|
||||
title = t;
|
||||
if (title.length() > max_item_width)
|
||||
max_item_width = title.length();
|
||||
}
|
||||
|
||||
size_t getDisplayedListSize()
|
||||
{
|
||||
return display_list.size();
|
||||
}
|
||||
|
||||
private:
|
||||
vector<ListEntry<T>> list;
|
||||
vector<ListEntry<T>*> display_list;
|
||||
string search_string;
|
||||
string title;
|
||||
int display_max_rows;
|
||||
int max_item_width;
|
||||
};
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,165 +1,176 @@
|
||||
class AutoFarm
|
||||
|
||||
def initialize
|
||||
@thresholds = Hash.new(50)
|
||||
@lastcounts = Hash.new(0)
|
||||
end
|
||||
|
||||
def setthreshold(id, v)
|
||||
if df.world.raws.plants.all.find { |r| r.id == id }
|
||||
@thresholds[id] = v.to_i
|
||||
else
|
||||
puts "No plant with id #{id}"
|
||||
end
|
||||
end
|
||||
|
||||
def setdefault(v)
|
||||
@thresholds.default = v.to_i
|
||||
end
|
||||
|
||||
def is_plantable (plant)
|
||||
has_seed = plant.flags[:SEED]
|
||||
season = df.cur_season
|
||||
harvest = df.cur_season_tick + plant.growdur * 10
|
||||
will_finish = harvest < 10080
|
||||
can_plant = has_seed && plant.flags[season]
|
||||
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
|
||||
can_plant
|
||||
end
|
||||
|
||||
def find_plantable_plants
|
||||
plantable = {}
|
||||
counts = Hash.new(0)
|
||||
|
||||
df.world.items.other[:SEEDS].each { |i|
|
||||
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
||||
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
||||
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
||||
!i.flags.artifact)
|
||||
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
|
||||
end
|
||||
}
|
||||
|
||||
counts.keys.each { |i|
|
||||
if df.ui.tasks.known_plants[i]
|
||||
plant = df.world.raws.plants.all[i]
|
||||
if is_plantable(plant)
|
||||
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
|
||||
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
return plantable
|
||||
end
|
||||
|
||||
def set_farms( plants, farms)
|
||||
return if farms.length == 0
|
||||
if plants.length == 0
|
||||
plants = [-1]
|
||||
end
|
||||
|
||||
season = df.cur_season
|
||||
|
||||
idx = 0
|
||||
|
||||
farms.each { |f|
|
||||
f.plant_id[season] = plants[idx]
|
||||
idx = (idx + 1) % plants.length
|
||||
}
|
||||
end
|
||||
|
||||
def process
|
||||
return false unless @running
|
||||
|
||||
plantable = find_plantable_plants
|
||||
counts = Hash.new(0)
|
||||
|
||||
df.world.items.other[:PLANT].each { |i|
|
||||
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
||||
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
||||
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
||||
!i.flags.artifact && plantable.has_key?(i.mat_index))
|
||||
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
|
||||
end
|
||||
}
|
||||
|
||||
plants_s = []
|
||||
plants_u = []
|
||||
|
||||
@lastcounts.clear
|
||||
|
||||
plantable.each_key { |k|
|
||||
plant = df.world.raws.plants.all[k]
|
||||
if (counts[k] < @thresholds[plant.id])
|
||||
plants_s.push(k) if plantable[k] == :Surface
|
||||
plants_u.push(k) if plantable[k] == :Underground
|
||||
end
|
||||
@lastcounts[plant.id] = counts[k]
|
||||
}
|
||||
|
||||
farms_s = []
|
||||
farms_u = []
|
||||
df.world.buildings.other[:FARM_PLOT].each { |f|
|
||||
if (f.flags.exists)
|
||||
underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean
|
||||
farms_s.push(f) unless underground
|
||||
farms_u.push(f) if underground
|
||||
end
|
||||
}
|
||||
|
||||
set_farms(plants_s, farms_s)
|
||||
set_farms(plants_u, farms_u)
|
||||
|
||||
end
|
||||
|
||||
def start
|
||||
@onupdate = df.onupdate_register('autofarm', 100) { process }
|
||||
@running = true
|
||||
end
|
||||
|
||||
def stop
|
||||
df.onupdate_unregister(@onupdate)
|
||||
@running = false
|
||||
end
|
||||
|
||||
def status
|
||||
stat = @running ? "Running." : "Stopped."
|
||||
@thresholds.each { |k,v|
|
||||
stat += "\n#{k} limit #{v} current #{@lastcounts[k]}"
|
||||
}
|
||||
stat += "\nDefault: #{@thresholds.default}"
|
||||
stat
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
$AutoFarm = AutoFarm.new unless $AutoFarm
|
||||
|
||||
case $script_args[0]
|
||||
when 'start'
|
||||
$AutoFarm.start
|
||||
|
||||
when 'end', 'stop'
|
||||
$AutoFarm.stop
|
||||
|
||||
when 'default'
|
||||
$AutoFarm.setdefault($script_args[1])
|
||||
|
||||
when 'threshold'
|
||||
t = $script_args[1]
|
||||
$script_args[2..-1].each {|i|
|
||||
$AutoFarm.setthreshold(i, t)
|
||||
}
|
||||
|
||||
when 'delete'
|
||||
$AutoFarm.stop
|
||||
$AutoFarm = nil
|
||||
|
||||
else
|
||||
if $AutoFarm
|
||||
puts $AutoFarm.status
|
||||
else
|
||||
puts "AI not started"
|
||||
end
|
||||
end
|
||||
class AutoFarm
|
||||
|
||||
def initialize
|
||||
@thresholds = Hash.new(50)
|
||||
@lastcounts = Hash.new(0)
|
||||
end
|
||||
|
||||
def setthreshold(id, v)
|
||||
list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id }
|
||||
if tok = df.match_rawname(id, list)
|
||||
@thresholds[tok] = v.to_i
|
||||
else
|
||||
puts "No plant with id #{id}, try one of " +
|
||||
list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
def setdefault(v)
|
||||
@thresholds.default = v.to_i
|
||||
end
|
||||
|
||||
def is_plantable(plant)
|
||||
has_seed = plant.flags[:SEED]
|
||||
season = df.cur_season
|
||||
harvest = df.cur_season_tick + plant.growdur * 10
|
||||
will_finish = harvest < 10080
|
||||
can_plant = has_seed && plant.flags[season]
|
||||
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
|
||||
can_plant
|
||||
end
|
||||
|
||||
def find_plantable_plants
|
||||
plantable = {}
|
||||
counts = Hash.new(0)
|
||||
|
||||
df.world.items.other[:SEEDS].each { |i|
|
||||
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
||||
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
||||
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
||||
!i.flags.artifact)
|
||||
counts[i.mat_index] += i.stack_size
|
||||
end
|
||||
}
|
||||
|
||||
counts.keys.each { |i|
|
||||
if df.ui.tasks.known_plants[i]
|
||||
plant = df.world.raws.plants.all[i]
|
||||
if is_plantable(plant)
|
||||
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
|
||||
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
return plantable
|
||||
end
|
||||
|
||||
def set_farms(plants, farms)
|
||||
return if farms.length == 0
|
||||
if plants.length == 0
|
||||
plants = [-1]
|
||||
end
|
||||
|
||||
season = df.cur_season
|
||||
|
||||
farms.each_with_index { |f, idx|
|
||||
f.plant_id[season] = plants[idx % plants.length]
|
||||
}
|
||||
end
|
||||
|
||||
def process
|
||||
plantable = find_plantable_plants
|
||||
@lastcounts = Hash.new(0)
|
||||
|
||||
df.world.items.other[:PLANT].each { |i|
|
||||
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
||||
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
||||
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
||||
!i.flags.artifact && plantable.has_key?(i.mat_index))
|
||||
id = df.world.raws.plants.all[i.mat_index].id
|
||||
@lastcounts[id] += i.stack_size
|
||||
end
|
||||
}
|
||||
|
||||
return unless @running
|
||||
|
||||
plants_s = []
|
||||
plants_u = []
|
||||
|
||||
plantable.each_key { |k|
|
||||
plant = df.world.raws.plants.all[k]
|
||||
if (@lastcounts[plant.id] < @thresholds[plant.id])
|
||||
plants_s.push(k) if plantable[k] == :Surface
|
||||
plants_u.push(k) if plantable[k] == :Underground
|
||||
end
|
||||
}
|
||||
|
||||
farms_s = []
|
||||
farms_u = []
|
||||
df.world.buildings.other[:FARM_PLOT].each { |f|
|
||||
if (f.flags.exists)
|
||||
underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean
|
||||
farms_s.push(f) unless underground
|
||||
farms_u.push(f) if underground
|
||||
end
|
||||
}
|
||||
|
||||
set_farms(plants_s, farms_s)
|
||||
set_farms(plants_u, farms_u)
|
||||
end
|
||||
|
||||
def start
|
||||
return if @running
|
||||
@onupdate = df.onupdate_register('autofarm', 1200) { process }
|
||||
@running = true
|
||||
end
|
||||
|
||||
def stop
|
||||
df.onupdate_unregister(@onupdate)
|
||||
@running = false
|
||||
end
|
||||
|
||||
def status
|
||||
stat = @running ? "Running." : "Stopped."
|
||||
@lastcounts.each { |k,v|
|
||||
stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}"
|
||||
}
|
||||
@thresholds.each { |k,v|
|
||||
stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k)
|
||||
}
|
||||
stat << "\nDefault: #{@thresholds.default}"
|
||||
stat
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
$AutoFarm ||= AutoFarm.new
|
||||
|
||||
case $script_args[0]
|
||||
when 'start', 'enable'
|
||||
$AutoFarm.start
|
||||
puts $AutoFarm.status
|
||||
|
||||
when 'end', 'stop', 'disable'
|
||||
$AutoFarm.stop
|
||||
puts 'Stopped.'
|
||||
|
||||
when 'default'
|
||||
$AutoFarm.setdefault($script_args[1])
|
||||
|
||||
when 'threshold'
|
||||
t = $script_args[1]
|
||||
$script_args[2..-1].each {|i|
|
||||
$AutoFarm.setthreshold(i, t)
|
||||
}
|
||||
|
||||
when 'delete'
|
||||
$AutoFarm.stop
|
||||
$AutoFarm = nil
|
||||
|
||||
when 'help', '?'
|
||||
puts <<EOS
|
||||
Automatically handle crop selection in farm plots based on current plant stocks.
|
||||
Selects a crop for planting if current stock is below a threshold.
|
||||
Selected crops are dispatched on all farmplots.
|
||||
|
||||
Usage:
|
||||
autofarm start
|
||||
autofarm default 30
|
||||
autofarm threshold 150 helmet_plump tail_pig
|
||||
EOS
|
||||
|
||||
else
|
||||
$AutoFarm.process
|
||||
puts $AutoFarm.status
|
||||
end
|
||||
|
@ -1,58 +1,58 @@
|
||||
class AutoUnsuspend
|
||||
|
||||
def initialize
|
||||
end
|
||||
|
||||
def process
|
||||
return false unless @running
|
||||
joblist = df.world.job_list.next
|
||||
count = 0
|
||||
|
||||
while joblist
|
||||
job = joblist.item
|
||||
joblist = joblist.next
|
||||
|
||||
if job.job_type == :ConstructBuilding
|
||||
if (job.flags.suspend)
|
||||
item = job.items[0].item
|
||||
job.flags.suspend = false
|
||||
count += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
puts "Unsuspended #{count} job(s)." unless count == 0
|
||||
|
||||
end
|
||||
|
||||
def start
|
||||
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
|
||||
@running = true
|
||||
end
|
||||
|
||||
def stop
|
||||
df.onupdate_unregister(@onupdate)
|
||||
@running = false
|
||||
end
|
||||
|
||||
def status
|
||||
@running ? 'Running.' : 'Stopped.'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
case $script_args[0]
|
||||
when 'start'
|
||||
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
|
||||
$AutoUnsuspend.start
|
||||
|
||||
when 'end', 'stop'
|
||||
$AutoUnsuspend.stop
|
||||
|
||||
else
|
||||
if $AutoUnsuspend
|
||||
puts $AutoUnsuspend.status
|
||||
else
|
||||
puts 'Not loaded.'
|
||||
end
|
||||
end
|
||||
class AutoUnsuspend
|
||||
|
||||
def initialize
|
||||
end
|
||||
|
||||
def process
|
||||
return false unless @running
|
||||
joblist = df.world.job_list.next
|
||||
count = 0
|
||||
|
||||
while joblist
|
||||
job = joblist.item
|
||||
joblist = joblist.next
|
||||
|
||||
if job.job_type == :ConstructBuilding
|
||||
if (job.flags.suspend)
|
||||
item = job.items[0].item
|
||||
job.flags.suspend = false
|
||||
count += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
puts "Unsuspended #{count} job(s)." unless count == 0
|
||||
|
||||
end
|
||||
|
||||
def start
|
||||
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
|
||||
@running = true
|
||||
end
|
||||
|
||||
def stop
|
||||
df.onupdate_unregister(@onupdate)
|
||||
@running = false
|
||||
end
|
||||
|
||||
def status
|
||||
@running ? 'Running.' : 'Stopped.'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
case $script_args[0]
|
||||
when 'start'
|
||||
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
|
||||
$AutoUnsuspend.start
|
||||
|
||||
when 'end', 'stop'
|
||||
$AutoUnsuspend.stop
|
||||
|
||||
else
|
||||
if $AutoUnsuspend
|
||||
puts $AutoUnsuspend.status
|
||||
else
|
||||
puts 'Not loaded.'
|
||||
end
|
||||
end
|
||||
|
@ -1,161 +1,177 @@
|
||||
# create arbitrary items under cursor
|
||||
|
||||
category = $script_args[0] || 'help'
|
||||
mat_raw = $script_args[1] || 'list'
|
||||
count = $script_args[2]
|
||||
|
||||
|
||||
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs']) || 'help'
|
||||
|
||||
if category == 'help'
|
||||
puts <<EOS
|
||||
Create items under the cursor.
|
||||
Usage:
|
||||
create [category] [raws token] [number]
|
||||
|
||||
Item categories:
|
||||
bars, boulders, plants, logs, web
|
||||
|
||||
Raw token:
|
||||
either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
|
||||
(the missing part is autocompleted depending on the item category)
|
||||
use 'list' to show all possibilities
|
||||
|
||||
Exemples:
|
||||
create boulders hematite 30
|
||||
create bars CREATURE_MAT:CAT:SOAP 10
|
||||
create web cave_giant
|
||||
create plants list
|
||||
|
||||
EOS
|
||||
throw :script_finished
|
||||
|
||||
elsif mat_raw == 'list'
|
||||
# allowed with no cursor
|
||||
|
||||
elsif df.cursor.x == -30000
|
||||
puts "Please place the game cursor somewhere"
|
||||
throw :script_finished
|
||||
|
||||
elsif !(maptile = df.map_tile_at(df.cursor))
|
||||
puts "Error: unallocated map block !"
|
||||
throw :script_finished
|
||||
|
||||
elsif !maptile.shape_passablehigh
|
||||
puts "Error: impassible tile !"
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
|
||||
def match_list(tok, list)
|
||||
if tok != 'list'
|
||||
tok = df.match_rawname(tok, list)
|
||||
if not tok
|
||||
puts "Invalid raws token, use one in:"
|
||||
tok = 'list'
|
||||
end
|
||||
end
|
||||
if tok == 'list'
|
||||
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
|
||||
throw :script_finished
|
||||
end
|
||||
tok
|
||||
end
|
||||
|
||||
|
||||
case category
|
||||
when 'bars'
|
||||
# create metal bar, eg createbar INORGANIC:IRON
|
||||
cls = DFHack::ItemBarst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.inorganics.find_all { |ino|
|
||||
ino.material.flags[:IS_METAL]
|
||||
}.map { |ino| ino.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "INORGANIC:#{mat_raw}"
|
||||
puts mat_raw
|
||||
end
|
||||
customize = lambda { |item|
|
||||
item.dimension = 150
|
||||
item.subtype = -1
|
||||
}
|
||||
|
||||
when 'boulders'
|
||||
cls = DFHack::ItemBoulderst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.inorganics.find_all { |ino|
|
||||
ino.material.flags[:IS_STONE]
|
||||
}.map { |ino| ino.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "INORGANIC:#{mat_raw}"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'plants'
|
||||
cls = DFHack::ItemPlantst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.plants.all.find_all { |plt|
|
||||
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
|
||||
}.map { |plt| plt.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'logs'
|
||||
cls = DFHack::ItemWoodst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.plants.all.find_all { |plt|
|
||||
plt.material.find { |mat| mat.id == 'WOOD' }
|
||||
}.map { |plt| plt.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'webs'
|
||||
cls = DFHack::ItemThreadst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.creatures.all.find_all { |cre|
|
||||
cre.material.find { |mat| mat.id == 'SILK' }
|
||||
}.map { |cre| cre.creature_id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
|
||||
puts mat_raw
|
||||
end
|
||||
count ||= 1
|
||||
customize = lambda { |item|
|
||||
item.flags.spider_web = true
|
||||
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
|
||||
mat = df.decode_mat mat_raw
|
||||
|
||||
count ||= 20
|
||||
count.to_i.times {
|
||||
item = cls.cpp_new
|
||||
item.id = df.item_next_id
|
||||
item.stack_size = 1
|
||||
item.mat_type = mat.mat_type
|
||||
item.mat_index = mat.mat_index
|
||||
|
||||
customize[item] if customize
|
||||
|
||||
df.item_next_id += 1
|
||||
item.categorize(true)
|
||||
df.world.items.all << item
|
||||
|
||||
item.pos = df.cursor
|
||||
item.flags.on_ground = true
|
||||
df.map_tile_at.mapblock.items << item.id
|
||||
df.map_tile_at.occupancy.item = true
|
||||
}
|
||||
|
||||
|
||||
# move game view, so that the ui menu updates
|
||||
df.curview.feed_keys(:CURSOR_UP_Z)
|
||||
df.curview.feed_keys(:CURSOR_DOWN_Z)
|
||||
|
||||
# create first necessity items under cursor
|
||||
|
||||
category = $script_args[0] || 'help'
|
||||
mat_raw = $script_args[1] || 'list'
|
||||
count = $script_args[2]
|
||||
|
||||
|
||||
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help'
|
||||
|
||||
if category == 'help'
|
||||
puts <<EOS
|
||||
Create first necessity items under the cursor.
|
||||
Usage:
|
||||
create-items [category] [raws token] [number]
|
||||
|
||||
Item categories:
|
||||
bars, boulders, plants, logs, webs, anvils
|
||||
|
||||
Raw token:
|
||||
Either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
|
||||
(the missing part is autocompleted depending on the item category)
|
||||
Use 'list' to show all possibilities
|
||||
|
||||
Exemples:
|
||||
create-items boulders hematite 30
|
||||
create-items bars CREATURE_MAT:CAT:SOAP 10
|
||||
create-items web cave_giant
|
||||
create-items plants list
|
||||
|
||||
EOS
|
||||
throw :script_finished
|
||||
|
||||
elsif mat_raw == 'list'
|
||||
# allowed with no cursor
|
||||
|
||||
elsif df.cursor.x == -30000
|
||||
puts "Please place the game cursor somewhere"
|
||||
throw :script_finished
|
||||
|
||||
elsif !(maptile = df.map_tile_at(df.cursor))
|
||||
puts "Error: unallocated map block !"
|
||||
throw :script_finished
|
||||
|
||||
elsif !maptile.shape_passablehigh
|
||||
puts "Error: impassible tile !"
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
|
||||
def match_list(tok, list)
|
||||
if tok != 'list'
|
||||
tok = df.match_rawname(tok, list)
|
||||
if not tok
|
||||
puts "Invalid raws token, use one in:"
|
||||
tok = 'list'
|
||||
end
|
||||
end
|
||||
if tok == 'list'
|
||||
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
|
||||
throw :script_finished
|
||||
end
|
||||
tok
|
||||
end
|
||||
|
||||
|
||||
case category
|
||||
when 'bars'
|
||||
# create metal bar, eg createbar INORGANIC:IRON
|
||||
cls = DFHack::ItemBarst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.inorganics.find_all { |ino|
|
||||
ino.material.flags[:IS_METAL]
|
||||
}.map { |ino| ino.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "INORGANIC:#{mat_raw}"
|
||||
puts mat_raw
|
||||
end
|
||||
customize = lambda { |item|
|
||||
item.dimension = 150
|
||||
item.subtype = -1
|
||||
}
|
||||
|
||||
when 'boulders'
|
||||
cls = DFHack::ItemBoulderst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.inorganics.find_all { |ino|
|
||||
ino.material.flags[:IS_STONE]
|
||||
}.map { |ino| ino.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "INORGANIC:#{mat_raw}"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'plants'
|
||||
cls = DFHack::ItemPlantst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.plants.all.find_all { |plt|
|
||||
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
|
||||
}.map { |plt| plt.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'logs'
|
||||
cls = DFHack::ItemWoodst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.plants.all.find_all { |plt|
|
||||
plt.material.find { |mat| mat.id == 'WOOD' }
|
||||
}.map { |plt| plt.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'webs'
|
||||
cls = DFHack::ItemThreadst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.creatures.all.find_all { |cre|
|
||||
cre.material.find { |mat| mat.id == 'SILK' }
|
||||
}.map { |cre| cre.creature_id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
|
||||
puts mat_raw
|
||||
end
|
||||
count ||= 1
|
||||
customize = lambda { |item|
|
||||
item.flags.spider_web = true
|
||||
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
|
||||
}
|
||||
|
||||
when 'anvils'
|
||||
cls = DFHack::ItemAnvilst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.inorganics.find_all { |ino|
|
||||
ino.material.flags[:IS_METAL]
|
||||
}.map { |ino| ino.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "INORGANIC:#{mat_raw}"
|
||||
puts mat_raw
|
||||
end
|
||||
count ||= 1
|
||||
|
||||
end
|
||||
|
||||
|
||||
mat = df.decode_mat mat_raw
|
||||
|
||||
count ||= 20
|
||||
count.to_i.times {
|
||||
item = cls.cpp_new
|
||||
item.id = df.item_next_id
|
||||
item.stack_size = 1
|
||||
item.mat_type = mat.mat_type
|
||||
item.mat_index = mat.mat_index
|
||||
|
||||
customize[item] if customize
|
||||
|
||||
df.item_next_id += 1
|
||||
item.categorize(true)
|
||||
df.world.items.all << item
|
||||
|
||||
item.pos = df.cursor
|
||||
item.flags.on_ground = true
|
||||
df.map_tile_at.mapblock.items << item.id
|
||||
df.map_tile_at.occupancy.item = true
|
||||
}
|
||||
|
||||
|
||||
# move game view, so that the ui menu updates
|
||||
if df.cursor.z > 5
|
||||
df.curview.feed_keys(:CURSOR_DOWN_Z)
|
||||
df.curview.feed_keys(:CURSOR_UP_Z)
|
||||
else
|
||||
df.curview.feed_keys(:CURSOR_UP_Z)
|
||||
df.curview.feed_keys(:CURSOR_DOWN_Z)
|
||||
end
|
||||
|
@ -1,67 +1,67 @@
|
||||
# show death cause of a creature
|
||||
|
||||
def display_death_event(e)
|
||||
str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}"
|
||||
str << " (cause: #{e.death_cause.to_s.downcase}),"
|
||||
str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1
|
||||
str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON
|
||||
str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON
|
||||
|
||||
puts str.chomp(',') + '.'
|
||||
end
|
||||
|
||||
def display_death_unit(u)
|
||||
death_info = u.counters.death_tg
|
||||
killer = death_info.killer_tg if death_info
|
||||
|
||||
str = "The #{u.race_tg.name[0]}"
|
||||
str << " #{u.name}" if u.name.has_name
|
||||
str << " died"
|
||||
str << " in year #{death_info.event_year}" if death_info
|
||||
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
|
||||
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
|
||||
|
||||
puts str.chomp(',') + '.'
|
||||
end
|
||||
|
||||
item = df.item_find(:selected)
|
||||
unit = df.unit_find(:selected)
|
||||
|
||||
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
|
||||
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
|
||||
end
|
||||
|
||||
if item and item.kind_of?(DFHack::ItemBodyComponent)
|
||||
hf = item.hist_figure_id
|
||||
elsif unit
|
||||
hf = unit.hist_figure_id
|
||||
end
|
||||
|
||||
if not hf
|
||||
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
|
||||
|
||||
elsif hf == -1
|
||||
if unit ||= item.unit_tg
|
||||
display_death_unit(unit)
|
||||
else
|
||||
puts "Not a historical figure, cannot death find info"
|
||||
end
|
||||
|
||||
else
|
||||
histfig = df.world.history.figures.binsearch(hf)
|
||||
unit = histfig ? df.unit_find(histfig.unit_id) : nil
|
||||
if unit and not unit.flags1.dead and not unit.flags3.ghostly
|
||||
puts "#{unit.name} is not dead yet !"
|
||||
|
||||
else
|
||||
events = df.world.history.events
|
||||
(0...events.length).reverse_each { |i|
|
||||
e = events[i]
|
||||
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
|
||||
display_death_event(e)
|
||||
break
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# show death cause of a creature
|
||||
|
||||
def display_death_event(e)
|
||||
str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}"
|
||||
str << " (cause: #{e.death_cause.to_s.downcase}),"
|
||||
str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1
|
||||
str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON
|
||||
str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON
|
||||
|
||||
puts str.chomp(',') + '.'
|
||||
end
|
||||
|
||||
def display_death_unit(u)
|
||||
death_info = u.counters.death_tg
|
||||
killer = death_info.killer_tg if death_info
|
||||
|
||||
str = "The #{u.race_tg.name[0]}"
|
||||
str << " #{u.name}" if u.name.has_name
|
||||
str << " died"
|
||||
str << " in year #{death_info.event_year}" if death_info
|
||||
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
|
||||
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
|
||||
|
||||
puts str.chomp(',') + '.'
|
||||
end
|
||||
|
||||
item = df.item_find(:selected)
|
||||
unit = df.unit_find(:selected)
|
||||
|
||||
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
|
||||
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
|
||||
end
|
||||
|
||||
if item and item.kind_of?(DFHack::ItemBodyComponent)
|
||||
hf = item.hist_figure_id
|
||||
elsif unit
|
||||
hf = unit.hist_figure_id
|
||||
end
|
||||
|
||||
if not hf
|
||||
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
|
||||
|
||||
elsif hf == -1
|
||||
if unit ||= item.unit_tg
|
||||
display_death_unit(unit)
|
||||
else
|
||||
puts "Not a historical figure, cannot death find info"
|
||||
end
|
||||
|
||||
else
|
||||
histfig = df.world.history.figures.binsearch(hf)
|
||||
unit = histfig ? df.unit_find(histfig.unit_id) : nil
|
||||
if unit and not unit.flags1.dead and not unit.flags3.ghostly
|
||||
puts "#{unit.name} is not dead yet !"
|
||||
|
||||
else
|
||||
events = df.world.history.events
|
||||
(0...events.length).reverse_each { |i|
|
||||
e = events[i]
|
||||
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
|
||||
display_death_event(e)
|
||||
break
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -0,0 +1,10 @@
|
||||
# list indexes in world.item.other[] where current selected item appears
|
||||
|
||||
tg = df.item_find
|
||||
raise 'select an item' if not tg
|
||||
|
||||
o = df.world.items.other
|
||||
# discard ANY/BAD
|
||||
o._indexenum::ENUM.sort.transpose[1][1..-2].each { |k|
|
||||
puts k if o[k].find { |i| i == tg }
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
# unforbid all items
|
||||
|
||||
df.world.items.all.each { |i| i.flags.forbid = false }
|
@ -1,38 +1,38 @@
|
||||
# designate an area for digging according to a plan in csv format
|
||||
|
||||
raise "usage: digfort <plan filename>" if not $script_args[0]
|
||||
planfile = File.read($script_args[0])
|
||||
|
||||
if df.cursor.x == -30000
|
||||
raise "place the game cursor to the top-left corner of the design"
|
||||
end
|
||||
|
||||
tiles = planfile.lines.map { |l|
|
||||
l.sub(/#.*/, '').split(';').map { |t| t.strip }
|
||||
}
|
||||
|
||||
x = x0 = df.cursor.x
|
||||
y = df.cursor.y
|
||||
z = df.cursor.z
|
||||
|
||||
tiles.each { |line|
|
||||
next if line.empty? or line == ['']
|
||||
line.each { |tile|
|
||||
t = df.map_tile_at(x, y, z)
|
||||
s = t.shape_basic
|
||||
case tile
|
||||
when 'd'; t.dig(:Default) if s == :Wall
|
||||
when 'u'; t.dig(:UpStair) if s == :Wall
|
||||
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
|
||||
when 'i'; t.dig(:UpDownStair) if s == :Wall
|
||||
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
|
||||
when 'r'; t.dig(:Ramp) if s == :Wall
|
||||
when 'x'; t.dig(:No)
|
||||
end
|
||||
x += 1
|
||||
}
|
||||
x = x0
|
||||
y += 1
|
||||
}
|
||||
|
||||
puts 'done'
|
||||
# designate an area for digging according to a plan in csv format
|
||||
|
||||
raise "usage: digfort <plan filename>" if not $script_args[0]
|
||||
planfile = File.read($script_args[0])
|
||||
|
||||
if df.cursor.x == -30000
|
||||
raise "place the game cursor to the top-left corner of the design"
|
||||
end
|
||||
|
||||
tiles = planfile.lines.map { |l|
|
||||
l.sub(/#.*/, '').split(';').map { |t| t.strip }
|
||||
}
|
||||
|
||||
x = x0 = df.cursor.x
|
||||
y = df.cursor.y
|
||||
z = df.cursor.z
|
||||
|
||||
tiles.each { |line|
|
||||
next if line.empty? or line == ['']
|
||||
line.each { |tile|
|
||||
t = df.map_tile_at(x, y, z)
|
||||
s = t.shape_basic
|
||||
case tile
|
||||
when 'd'; t.dig(:Default) if s == :Wall
|
||||
when 'u'; t.dig(:UpStair) if s == :Wall
|
||||
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
|
||||
when 'i'; t.dig(:UpDownStair) if s == :Wall
|
||||
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
|
||||
when 'r'; t.dig(:Ramp) if s == :Wall
|
||||
when 'x'; t.dig(:No)
|
||||
end
|
||||
x += 1
|
||||
}
|
||||
x = x0
|
||||
y += 1
|
||||
}
|
||||
|
||||
puts 'done'
|
||||
|
@ -1,11 +1,13 @@
|
||||
# remove all aquifers from the map
|
||||
|
||||
count = 0
|
||||
df.each_map_block { |b|
|
||||
if b.designation[0][0].water_table or b.designation[15][15].water_table
|
||||
count += 1
|
||||
b.designation.each { |dx| dx.each { |dy| dy.water_table = false } }
|
||||
end
|
||||
}
|
||||
|
||||
puts "cleared #{count} map blocks"
|
||||
# remove all aquifers from the map
|
||||
|
||||
count = 0
|
||||
df.each_map_block { |b|
|
||||
if b.designation[0][0].water_table or b.designation[8][8].water_table
|
||||
count += 1
|
||||
df.each_map_block_z(b.map_pos.z) { |bz|
|
||||
bz.designation.each { |dx| dx.each { |dy| dy.water_table = false } }
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
puts "cleared #{count} aquifer#{'s' if count > 1}"
|
||||
|
@ -1,93 +1,135 @@
|
||||
# slay all creatures of a given race
|
||||
|
||||
# race = name of the race to eradicate, use 'him' to target only the selected creature
|
||||
# use 'undead' to target all undeads
|
||||
race = $script_args[0]
|
||||
|
||||
# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death
|
||||
magma = ($script_args[1] == 'magma')
|
||||
|
||||
checkunit = lambda { |u|
|
||||
(u.body.blood_count != 0 or u.body.blood_max == 0) and
|
||||
not u.flags1.dead and
|
||||
not u.flags1.caged and not u.flags1.chained and
|
||||
#not u.flags1.hidden_in_ambush and
|
||||
not df.map_designation_at(u).hidden
|
||||
}
|
||||
|
||||
slayit = lambda { |u|
|
||||
if not magma
|
||||
# just make them drop dead
|
||||
u.body.blood_count = 0
|
||||
# some races dont mind having no blood, ensure they are still taken care of.
|
||||
u.animal.vanish_countdown = 2
|
||||
else
|
||||
# it's getting hot around here
|
||||
# !!WARNING!! do not call on a magma-safe creature
|
||||
ouh = df.onupdate_register("slayrace ensure #{u.id}", 1) {
|
||||
if u.flags1.dead
|
||||
df.onupdate_unregister(ouh)
|
||||
else
|
||||
x, y, z = u.pos.x, u.pos.y, u.pos.z
|
||||
z += 1 while tile = df.map_tile_at(x, y, z+1) and
|
||||
tile.shape_passableflow and tile.shape_passablelow
|
||||
df.map_tile_at(x, y, z).spawn_magma(7)
|
||||
end
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
all_races = Hash.new(0)
|
||||
|
||||
df.world.units.active.map { |u|
|
||||
if checkunit[u]
|
||||
if (u.enemy.undead or
|
||||
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
|
||||
u.curse.rem_tags1.OPPOSED_TO_LIFE))
|
||||
all_races['Undead'] += 1
|
||||
else
|
||||
all_races[u.race_tg.creature_id] += 1
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
case race
|
||||
when nil
|
||||
all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" }
|
||||
|
||||
when 'him'
|
||||
if him = df.unit_find
|
||||
slayit[him]
|
||||
else
|
||||
puts "Select a target ingame"
|
||||
end
|
||||
|
||||
when /^undead/i
|
||||
count = 0
|
||||
df.world.units.active.each { |u|
|
||||
if (u.enemy.undead or
|
||||
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
|
||||
u.curse.rem_tags1.OPPOSED_TO_LIFE)) and
|
||||
checkunit[u]
|
||||
slayit[u]
|
||||
count += 1
|
||||
end
|
||||
}
|
||||
puts "slain #{count} undeads"
|
||||
|
||||
else
|
||||
raw_race = df.match_rawname(race, all_races.keys)
|
||||
raise 'invalid race' if not raw_race
|
||||
|
||||
race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race }
|
||||
|
||||
count = 0
|
||||
df.world.units.active.each { |u|
|
||||
if u.race == race_nr and checkunit[u]
|
||||
slayit[u]
|
||||
count += 1
|
||||
end
|
||||
}
|
||||
puts "slain #{count} #{raw_race}"
|
||||
|
||||
end
|
||||
# exterminate creatures
|
||||
|
||||
# race = name of the race to eradicate, use 'him' to target only the selected creature
|
||||
# use 'undead' to target all undeads
|
||||
race = $script_args[0]
|
||||
|
||||
# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death
|
||||
# if it is 'butcher' mark all units for butchering (wont work with hostiles)
|
||||
kill_by = $script_args[1]
|
||||
|
||||
case kill_by
|
||||
when 'magma'
|
||||
slain = 'burning'
|
||||
when 'slaughter', 'butcher'
|
||||
slain = 'marked for butcher'
|
||||
when nil
|
||||
slain = 'slain'
|
||||
else
|
||||
race = 'help'
|
||||
end
|
||||
|
||||
checkunit = lambda { |u|
|
||||
(u.body.blood_count != 0 or u.body.blood_max == 0) and
|
||||
not u.flags1.dead and
|
||||
not u.flags1.caged and not u.flags1.chained and
|
||||
#not u.flags1.hidden_in_ambush and
|
||||
not df.map_designation_at(u).hidden
|
||||
}
|
||||
|
||||
slayit = lambda { |u|
|
||||
case kill_by
|
||||
when 'magma'
|
||||
# it's getting hot around here
|
||||
# !!WARNING!! do not call on a magma-safe creature
|
||||
ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) {
|
||||
if u.flags1.dead
|
||||
df.onupdate_unregister(ouh)
|
||||
else
|
||||
x, y, z = u.pos.x, u.pos.y, u.pos.z
|
||||
z += 1 while tile = df.map_tile_at(x, y, z+1) and
|
||||
tile.shape_passableflow and tile.shape_passablelow
|
||||
df.map_tile_at(x, y, z).spawn_magma(7)
|
||||
end
|
||||
}
|
||||
when 'butcher', 'slaughter'
|
||||
# mark for slaughter at butcher's shop
|
||||
u.flags2.slaughter = true
|
||||
else
|
||||
# just make them drop dead
|
||||
u.body.blood_count = 0
|
||||
# some races dont mind having no blood, ensure they are still taken care of.
|
||||
u.animal.vanish_countdown = 2
|
||||
end
|
||||
}
|
||||
|
||||
all_races = Hash.new(0)
|
||||
|
||||
df.world.units.active.map { |u|
|
||||
if checkunit[u]
|
||||
if (u.enemy.undead or
|
||||
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
|
||||
u.curse.rem_tags1.OPPOSED_TO_LIFE))
|
||||
all_races['Undead'] += 1
|
||||
else
|
||||
all_races[u.race_tg.creature_id] += 1
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
case race
|
||||
when nil
|
||||
all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" }
|
||||
|
||||
when 'help', '?'
|
||||
puts <<EOS
|
||||
Kills all creatures of a given race.
|
||||
With no argument, lists possible targets with their head count.
|
||||
With the special argument 'him' or 'her', kill only the currently selected creature.
|
||||
With the special argument 'undead', kill all undead creatures/thralls.
|
||||
|
||||
The targets will bleed out on the next game tick, or if they are immune to that, will vanish in a puff of smoke.
|
||||
|
||||
The special final argument 'magma' will make magma rain on the targets instead.
|
||||
The special final argument 'butcher' will mark the targets for butchering instead.
|
||||
|
||||
Ex: exterminate gob
|
||||
exterminate elve magma
|
||||
exterminate him
|
||||
exterminate pig butcher
|
||||
EOS
|
||||
|
||||
when 'him', 'her', 'it', 'that'
|
||||
if him = df.unit_find
|
||||
case him.race_tg.caste[him.caste].gender
|
||||
when 0; puts 'its a she !' if race != 'her'
|
||||
when 1; puts 'its a he !' if race != 'him'
|
||||
else; puts 'its an it !' if race != 'it' and race != 'that'
|
||||
end
|
||||
slayit[him]
|
||||
else
|
||||
puts "Select a target ingame"
|
||||
end
|
||||
|
||||
when /^undead/i
|
||||
count = 0
|
||||
df.world.units.active.each { |u|
|
||||
if (u.enemy.undead or
|
||||
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
|
||||
u.curse.rem_tags1.OPPOSED_TO_LIFE)) and
|
||||
checkunit[u]
|
||||
slayit[u]
|
||||
count += 1
|
||||
end
|
||||
}
|
||||
puts "#{slain} #{count} undeads"
|
||||
|
||||
else
|
||||
raw_race = df.match_rawname(race, all_races.keys)
|
||||
if not raw_race
|
||||
puts "Invalid race, use one of #{all_races.keys.sort.join(' ')}"
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race }
|
||||
|
||||
count = 0
|
||||
df.world.units.active.each { |u|
|
||||
if u.race == race_nr and checkunit[u]
|
||||
slayit[u]
|
||||
count += 1
|
||||
end
|
||||
}
|
||||
puts "#{slain} #{count} #{raw_race}"
|
||||
|
||||
end
|
@ -1,64 +1,64 @@
|
||||
# script to fix loyalty cascade, when you order your militia to kill friendly units
|
||||
|
||||
def fixunit(unit)
|
||||
return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id
|
||||
links = unit.hist_figure_tg.entity_links
|
||||
fixed = false
|
||||
|
||||
# check if the unit is a civ renegade
|
||||
if i1 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
|
||||
l.entity_id == df.ui.civ_id
|
||||
} and i2 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
|
||||
l.entity_id == df.ui.civ_id
|
||||
}
|
||||
fixed = true
|
||||
i1, i2 = i2, i1 if i1 > i2
|
||||
links.delete_at i2
|
||||
links.delete_at i1
|
||||
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100)
|
||||
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again"
|
||||
end
|
||||
|
||||
# check if the unit is a group renegade
|
||||
if i1 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
|
||||
l.entity_id == df.ui.group_id
|
||||
} and i2 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
|
||||
l.entity_id == df.ui.group_id
|
||||
}
|
||||
fixed = true
|
||||
i1, i2 = i2, i1 if i1 > i2
|
||||
links.delete_at i2
|
||||
links.delete_at i1
|
||||
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100)
|
||||
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again"
|
||||
end
|
||||
|
||||
# fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed)
|
||||
if fixed and unit.enemy.enemy_status_slot != -1
|
||||
i = unit.enemy.enemy_status_slot
|
||||
unit.enemy.enemy_status_slot = -1
|
||||
cache = df.world.enemy_status_cache
|
||||
cache.slot_used[i] = false
|
||||
cache.rel_map[i].map! { -1 }
|
||||
cache.rel_map.each { |a| a[i] = -1 }
|
||||
cache.next_slot = i if cache.next_slot > i
|
||||
end
|
||||
|
||||
# return true if we actually fixed the unit
|
||||
fixed
|
||||
end
|
||||
|
||||
count = 0
|
||||
df.unit_citizens.each { |u|
|
||||
count += 1 if fixunit(u)
|
||||
}
|
||||
|
||||
if count > 0
|
||||
puts "loyalty cascade fixed (#{count} dwarves)"
|
||||
else
|
||||
puts "no loyalty cascade found"
|
||||
end
|
||||
# script to fix loyalty cascade, when you order your militia to kill friendly units
|
||||
|
||||
def fixunit(unit)
|
||||
return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id
|
||||
links = unit.hist_figure_tg.entity_links
|
||||
fixed = false
|
||||
|
||||
# check if the unit is a civ renegade
|
||||
if i1 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
|
||||
l.entity_id == df.ui.civ_id
|
||||
} and i2 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
|
||||
l.entity_id == df.ui.civ_id
|
||||
}
|
||||
fixed = true
|
||||
i1, i2 = i2, i1 if i1 > i2
|
||||
links.delete_at i2
|
||||
links.delete_at i1
|
||||
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100)
|
||||
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again"
|
||||
end
|
||||
|
||||
# check if the unit is a group renegade
|
||||
if i1 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
|
||||
l.entity_id == df.ui.group_id
|
||||
} and i2 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
|
||||
l.entity_id == df.ui.group_id
|
||||
}
|
||||
fixed = true
|
||||
i1, i2 = i2, i1 if i1 > i2
|
||||
links.delete_at i2
|
||||
links.delete_at i1
|
||||
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100)
|
||||
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again"
|
||||
end
|
||||
|
||||
# fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed)
|
||||
if fixed and unit.enemy.enemy_status_slot != -1
|
||||
i = unit.enemy.enemy_status_slot
|
||||
unit.enemy.enemy_status_slot = -1
|
||||
cache = df.world.enemy_status_cache
|
||||
cache.slot_used[i] = false
|
||||
cache.rel_map[i].map! { -1 }
|
||||
cache.rel_map.each { |a| a[i] = -1 }
|
||||
cache.next_slot = i if cache.next_slot > i
|
||||
end
|
||||
|
||||
# return true if we actually fixed the unit
|
||||
fixed
|
||||
end
|
||||
|
||||
count = 0
|
||||
df.unit_citizens.each { |u|
|
||||
count += 1 if fixunit(u)
|
||||
}
|
||||
|
||||
if count > 0
|
||||
puts "loyalty cascade fixed (#{count} dwarves)"
|
||||
else
|
||||
puts "no loyalty cascade found"
|
||||
end
|
||||
|
@ -1,20 +1,25 @@
|
||||
# fix doors that are frozen in 'open' state
|
||||
|
||||
# door is stuck in open state if the map occupancy flag incorrectly indicates
|
||||
# that an unit is present (and creatures will prone to pass through)
|
||||
|
||||
count = 0
|
||||
df.world.buildings.all.each { |bld|
|
||||
# for all doors
|
||||
next if bld._rtti_classname != :building_doorst
|
||||
# check if it is open
|
||||
next if bld.close_timer == 0
|
||||
# check if occupancy is set
|
||||
occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z)
|
||||
next if not occ.unit
|
||||
# check if an unit is present
|
||||
next if df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z }
|
||||
count += 1
|
||||
occ.unit = false
|
||||
}
|
||||
puts "unstuck #{count} doors"
|
||||
# fix doors that are frozen in 'open' state
|
||||
|
||||
# this may happen after people mess with the game by (incorrectly) teleporting units or items
|
||||
# a door may stick open if the map occupancy flags are wrong
|
||||
|
||||
count = 0
|
||||
df.world.buildings.all.each { |bld|
|
||||
# for all doors
|
||||
next if bld._rtti_classname != :building_doorst
|
||||
# check if it is open
|
||||
next if bld.close_timer == 0
|
||||
# check if occupancy is set
|
||||
occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z)
|
||||
if (occ.unit or occ.unit_grounded) and not
|
||||
# check if an unit is present
|
||||
df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z }
|
||||
count += 1
|
||||
occ.unit = occ.unit_grounded = false
|
||||
end
|
||||
if occ.item and not df.world.items.all.find { |i| i.pos.x == bld.x1 and i.pos.y == bld.y1 and i.pos.z == bld.z }
|
||||
count += 1
|
||||
occ.item = false
|
||||
end
|
||||
}
|
||||
puts "unstuck #{count} doors"
|
||||
|
@ -1,49 +1,49 @@
|
||||
# grow crops in farm plots. ex: growcrops helmet_plump 20
|
||||
|
||||
material = $script_args[0]
|
||||
count_max = $script_args[1].to_i
|
||||
count_max = 100 if count_max == 0
|
||||
|
||||
# cache information from the raws
|
||||
@raws_plant_name ||= {}
|
||||
@raws_plant_growdur ||= {}
|
||||
if @raws_plant_name.empty?
|
||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||
@raws_plant_name[idx] = p.id
|
||||
@raws_plant_growdur[idx] = p.growdur
|
||||
}
|
||||
end
|
||||
|
||||
inventory = Hash.new(0)
|
||||
df.world.items.other[:SEEDS].each { |seed|
|
||||
next if not seed.flags.in_building
|
||||
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||
inventory[seed.mat_index] += 1
|
||||
}
|
||||
|
||||
if !material or material == 'help' or material == 'list'
|
||||
# show a list of available crop types
|
||||
inventory.sort_by { |mat, c| c }.each { |mat, c|
|
||||
name = df.world.raws.plants.all[mat].id
|
||||
puts " #{name} #{c}"
|
||||
}
|
||||
|
||||
else
|
||||
|
||||
mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] })
|
||||
unless wantmat = @raws_plant_name.index(mat)
|
||||
raise "invalid plant material #{material}"
|
||||
end
|
||||
|
||||
count = 0
|
||||
df.world.items.other[:SEEDS].each { |seed|
|
||||
next if seed.mat_index != wantmat
|
||||
next if not seed.flags.in_building
|
||||
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
|
||||
count += 1
|
||||
}
|
||||
puts "Grown #{count} #{mat}"
|
||||
end
|
||||
# grow crops in farm plots. ex: growcrops helmet_plump 20
|
||||
|
||||
material = $script_args[0]
|
||||
count_max = $script_args[1].to_i
|
||||
count_max = 100 if count_max == 0
|
||||
|
||||
# cache information from the raws
|
||||
@raws_plant_name ||= {}
|
||||
@raws_plant_growdur ||= {}
|
||||
if @raws_plant_name.empty?
|
||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||
@raws_plant_name[idx] = p.id
|
||||
@raws_plant_growdur[idx] = p.growdur
|
||||
}
|
||||
end
|
||||
|
||||
inventory = Hash.new(0)
|
||||
df.world.items.other[:SEEDS].each { |seed|
|
||||
next if not seed.flags.in_building
|
||||
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||
inventory[seed.mat_index] += 1
|
||||
}
|
||||
|
||||
if !material or material == 'help' or material == 'list'
|
||||
# show a list of available crop types
|
||||
inventory.sort_by { |mat, c| c }.each { |mat, c|
|
||||
name = df.world.raws.plants.all[mat].id
|
||||
puts " #{name} #{c}"
|
||||
}
|
||||
|
||||
else
|
||||
|
||||
mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] })
|
||||
unless wantmat = @raws_plant_name.index(mat)
|
||||
raise "invalid plant material #{material}"
|
||||
end
|
||||
|
||||
count = 0
|
||||
df.world.items.other[:SEEDS].each { |seed|
|
||||
next if seed.mat_index != wantmat
|
||||
next if not seed.flags.in_building
|
||||
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
|
||||
count += 1
|
||||
}
|
||||
puts "Grown #{count} #{mat}"
|
||||
end
|
||||
|
@ -0,0 +1,656 @@
|
||||
-- A GUI front-end for the autobutcher plugin.
|
||||
|
||||
local gui = require 'gui'
|
||||
local utils = require 'utils'
|
||||
local widgets = require 'gui.widgets'
|
||||
local dlg = require 'gui.dialogs'
|
||||
|
||||
local plugin = require 'plugins.zone'
|
||||
|
||||
WatchList = defclass(WatchList, gui.FramedScreen)
|
||||
|
||||
WatchList.ATTRS {
|
||||
frame_title = 'Autobutcher Watchlist',
|
||||
frame_inset = 0, -- cover full DF window
|
||||
frame_background = COLOR_BLACK,
|
||||
frame_style = gui.BOUNDARY_FRAME,
|
||||
}
|
||||
|
||||
-- width of the race name column in the UI
|
||||
local racewidth = 25
|
||||
|
||||
function nextAutowatchState()
|
||||
if(plugin.autowatch_isEnabled()) then
|
||||
return 'Stop '
|
||||
end
|
||||
return 'Start'
|
||||
end
|
||||
|
||||
function nextAutobutcherState()
|
||||
if(plugin.autobutcher_isEnabled()) then
|
||||
return 'Stop '
|
||||
end
|
||||
return 'Start'
|
||||
end
|
||||
|
||||
function getSleepTimer()
|
||||
return plugin.autobutcher_getSleep()
|
||||
end
|
||||
|
||||
function setSleepTimer(ticks)
|
||||
plugin.autobutcher_setSleep(ticks)
|
||||
end
|
||||
|
||||
function WatchList:init(args)
|
||||
local colwidth = 7
|
||||
self:addviews{
|
||||
widgets.Panel{
|
||||
frame = { l = 0, r = 0 },
|
||||
frame_inset = 1,
|
||||
subviews = {
|
||||
widgets.Label{
|
||||
frame = { l = 0, t = 0 },
|
||||
text_pen = COLOR_CYAN,
|
||||
text = {
|
||||
{ text = 'Race', width = racewidth }, ' ',
|
||||
{ text = 'female', width = colwidth }, ' ',
|
||||
{ text = ' male', width = colwidth }, ' ',
|
||||
{ text = 'Female', width = colwidth }, ' ',
|
||||
{ text = ' Male', width = colwidth }, ' ',
|
||||
{ text = 'watch? ' },
|
||||
{ text = ' butchering' },
|
||||
NEWLINE,
|
||||
{ text = '', width = racewidth }, ' ',
|
||||
{ text = ' kids', width = colwidth }, ' ',
|
||||
{ text = ' kids', width = colwidth }, ' ',
|
||||
{ text = 'adults', width = colwidth }, ' ',
|
||||
{ text = 'adults', width = colwidth }, ' ',
|
||||
{ text = ' ' },
|
||||
{ text = ' ordered' },
|
||||
}
|
||||
},
|
||||
widgets.List{
|
||||
view_id = 'list',
|
||||
frame = { t = 3, b = 5 },
|
||||
not_found_label = 'Watchlist is empty.',
|
||||
edit_pen = COLOR_LIGHTCYAN,
|
||||
text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN },
|
||||
--on_select = self:callback('onSelectEntry'),
|
||||
},
|
||||
widgets.Label{
|
||||
view_id = 'bottom_ui',
|
||||
frame = { b = 0, h = 1 },
|
||||
text = 'filled by updateBottom()'
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
self:initListChoices()
|
||||
self:updateBottom()
|
||||
end
|
||||
|
||||
-- change the viewmode for stock data displayed in left section of columns
|
||||
local viewmodes = { 'total stock', 'protected stock', 'butcherable', 'butchering ordered' }
|
||||
local viewmode = 1
|
||||
function WatchList:onToggleView()
|
||||
if viewmode < #viewmodes then
|
||||
viewmode = viewmode + 1
|
||||
else
|
||||
viewmode = 1
|
||||
end
|
||||
self:initListChoices()
|
||||
self:updateBottom()
|
||||
end
|
||||
|
||||
-- update the bottom part of the UI (after sleep timer changed etc)
|
||||
function WatchList:updateBottom()
|
||||
self.subviews.bottom_ui:setText(
|
||||
{
|
||||
{ key = 'CUSTOM_SHIFT_V', text = ': View in colums shows: '..viewmodes[viewmode]..' / target max',
|
||||
on_activate = self:callback('onToggleView') }, NEWLINE,
|
||||
{ key = 'CUSTOM_F', text = ': f kids',
|
||||
on_activate = self:callback('onEditFK') }, ', ',
|
||||
{ key = 'CUSTOM_M', text = ': m kids',
|
||||
on_activate = self:callback('onEditMK') }, ', ',
|
||||
{ key = 'CUSTOM_SHIFT_F', text = ': f adults',
|
||||
on_activate = self:callback('onEditFA') }, ', ',
|
||||
{ key = 'CUSTOM_SHIFT_M', text = ': m adults',
|
||||
on_activate = self:callback('onEditMA') }, '. ',
|
||||
{ key = 'CUSTOM_W', text = ': Toggle watch',
|
||||
on_activate = self:callback('onToggleWatching') }, '. ',
|
||||
{ key = 'CUSTOM_X', text = ': Delete',
|
||||
on_activate = self:callback('onDeleteEntry') }, '. ', NEWLINE,
|
||||
--{ key = 'CUSTOM_A', text = ': Add race',
|
||||
-- on_activate = self:callback('onAddRace') }, ', ',
|
||||
{ key = 'CUSTOM_SHIFT_R', text = ': Set whole row',
|
||||
on_activate = self:callback('onSetRow') }, '. ',
|
||||
{ key = 'CUSTOM_B', text = ': Remove butcher orders',
|
||||
on_activate = self:callback('onUnbutcherRace') }, '. ',
|
||||
{ key = 'CUSTOM_SHIFT_B', text = ': Butcher race',
|
||||
on_activate = self:callback('onButcherRace') }, '. ', NEWLINE,
|
||||
{ key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher',
|
||||
on_activate = self:callback('onToggleAutobutcher') }, '. ',
|
||||
{ key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch',
|
||||
on_activate = self:callback('onToggleAutowatch') }, '. ',
|
||||
{ key = 'CUSTOM_SHIFT_S', text = ': Sleep ('..getSleepTimer()..' ticks)',
|
||||
on_activate = self:callback('onEditSleepTimer') }, '. ',
|
||||
})
|
||||
end
|
||||
|
||||
function stringify(number)
|
||||
-- cap displayed number to 3 digits
|
||||
-- after population of 50 per race is reached pets stop breeding anyways
|
||||
-- so probably this could safely be reduced to 99
|
||||
local max = 999
|
||||
if number > max then number = max end
|
||||
return tostring(number)
|
||||
end
|
||||
|
||||
function WatchList:initListChoices()
|
||||
|
||||
local choices = {}
|
||||
|
||||
-- first two rows are for "edit all races" and "edit new races"
|
||||
local settings = plugin.autobutcher_getSettings()
|
||||
local fk = stringify(settings.fk)
|
||||
local fa = stringify(settings.fa)
|
||||
local mk = stringify(settings.mk)
|
||||
local ma = stringify(settings.ma)
|
||||
|
||||
local watched = ''
|
||||
|
||||
local colwidth = 7
|
||||
|
||||
table.insert (choices, {
|
||||
text = {
|
||||
{ text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ',
|
||||
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
|
||||
{ text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
|
||||
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
|
||||
{ text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
|
||||
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
|
||||
{ text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ',
|
||||
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
|
||||
{ text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ',
|
||||
{ text = watched, width = 6, rjustify = true }
|
||||
}
|
||||
})
|
||||
|
||||
table.insert (choices, {
|
||||
text = {
|
||||
{ text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ',
|
||||
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
|
||||
{ text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
|
||||
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
|
||||
{ text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
|
||||
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
|
||||
{ text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ',
|
||||
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
|
||||
{ text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ',
|
||||
{ text = watched, width = 6, rjustify = true }
|
||||
}
|
||||
})
|
||||
|
||||
local watchlist = plugin.autobutcher_getWatchList()
|
||||
|
||||
for i,entry in ipairs(watchlist) do
|
||||
fk = stringify(entry.fk)
|
||||
fa = stringify(entry.fa)
|
||||
mk = stringify(entry.mk)
|
||||
ma = stringify(entry.ma)
|
||||
if viewmode == 1 then
|
||||
fkc = stringify(entry.fk_total)
|
||||
fac = stringify(entry.fa_total)
|
||||
mkc = stringify(entry.mk_total)
|
||||
mac = stringify(entry.ma_total)
|
||||
end
|
||||
if viewmode == 2 then
|
||||
fkc = stringify(entry.fk_protected)
|
||||
fac = stringify(entry.fa_protected)
|
||||
mkc = stringify(entry.mk_protected)
|
||||
mac = stringify(entry.ma_protected)
|
||||
end
|
||||
if viewmode == 3 then
|
||||
fkc = stringify(entry.fk_butcherable)
|
||||
fac = stringify(entry.fa_butcherable)
|
||||
mkc = stringify(entry.mk_butcherable)
|
||||
mac = stringify(entry.ma_butcherable)
|
||||
end
|
||||
if viewmode == 4 then
|
||||
fkc = stringify(entry.fk_butcherflag)
|
||||
fac = stringify(entry.fa_butcherflag)
|
||||
mkc = stringify(entry.mk_butcherflag)
|
||||
mac = stringify(entry.ma_butcherflag)
|
||||
end
|
||||
local butcher_ordered = entry.fk_butcherflag + entry.fa_butcherflag + entry.mk_butcherflag + entry.ma_butcherflag
|
||||
local bo = ' '
|
||||
if butcher_ordered > 0 then bo = stringify(butcher_ordered) end
|
||||
|
||||
local watched = 'no'
|
||||
if entry.watched then watched = 'yes' end
|
||||
|
||||
local racestr = entry.name
|
||||
|
||||
-- highlight entries where the target quota can't be met because too many are protected
|
||||
bad_pen = COLOR_LIGHTRED
|
||||
good_pen = NONE -- this is stupid, but it works. sue me
|
||||
fk_pen = good_pen
|
||||
fa_pen = good_pen
|
||||
mk_pen = good_pen
|
||||
ma_pen = good_pen
|
||||
if entry.fk_protected > entry.fk then fk_pen = bad_pen end
|
||||
if entry.fa_protected > entry.fa then fa_pen = bad_pen end
|
||||
if entry.mk_protected > entry.mk then mk_pen = bad_pen end
|
||||
if entry.ma_protected > entry.ma then ma_pen = bad_pen end
|
||||
|
||||
table.insert (choices, {
|
||||
text = {
|
||||
{ text = racestr, width = racewidth, pad_char = ' ' }, --' ',
|
||||
{ text = fkc, width = 3, rjustify = true, pad_char = ' ' }, '/',
|
||||
{ text = fk, width = 3, rjustify = false, pad_char = ' ', pen = fk_pen }, ' ',
|
||||
{ text = mkc, width = 3, rjustify = true, pad_char = ' ' }, '/',
|
||||
{ text = mk, width = 3, rjustify = false, pad_char = ' ', pen = mk_pen }, ' ',
|
||||
{ text = fac, width = 3, rjustify = true, pad_char = ' ' }, '/',
|
||||
{ text = fa, width = 3, rjustify = false, pad_char = ' ', pen = fa_pen }, ' ',
|
||||
{ text = mac, width = 3, rjustify = true, pad_char = ' ' }, '/',
|
||||
{ text = ma, width = 3, rjustify = false, pad_char = ' ', pen = ma_pen }, ' ',
|
||||
{ text = watched, width = 6, rjustify = true, pad_char = ' ' }, ' ',
|
||||
{ text = bo, width = 8, rjustify = true, pad_char = ' ' }
|
||||
},
|
||||
obj = entry,
|
||||
})
|
||||
end
|
||||
|
||||
local list = self.subviews.list
|
||||
list:setChoices(choices)
|
||||
end
|
||||
|
||||
function WatchList:onInput(keys)
|
||||
if keys.LEAVESCREEN then
|
||||
self:dismiss()
|
||||
else
|
||||
WatchList.super.onInput(self, keys)
|
||||
end
|
||||
end
|
||||
|
||||
-- check the user input for target population values
|
||||
function WatchList:checkUserInput(count, text)
|
||||
if count == nil then
|
||||
dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED)
|
||||
return false
|
||||
end
|
||||
if count < 0 then
|
||||
dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- check the user input for sleep timer
|
||||
function WatchList:checkUserInputSleep(count, text)
|
||||
if count == nil then
|
||||
dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED)
|
||||
return false
|
||||
end
|
||||
if count < 1000 then
|
||||
dlg.showMessage('Invalid Number',
|
||||
'Minimum allowed timer value is 1000!'..NEWLINE..'Too low values could decrease performance'..NEWLINE..'and are not necessary!',
|
||||
COLOR_LIGHTRED)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function WatchList:onEditFK()
|
||||
local selidx,selobj = self.subviews.list:getSelected()
|
||||
local settings = plugin.autobutcher_getSettings()
|
||||
local fk = settings.fk
|
||||
local mk = settings.mk
|
||||
local fa = settings.fa
|
||||
local ma = settings.ma
|
||||
local race = 'ALL RACES PLUS NEW'
|
||||
local id = -1
|
||||
local watched = false
|
||||
|
||||
if selidx == 2 then
|
||||
race = 'ONLY NEW RACES'
|
||||
end
|
||||
|
||||
if selidx > 2 then
|
||||
local entry = selobj.obj
|
||||
fk = entry.fk
|
||||
mk = entry.mk
|
||||
fa = entry.fa
|
||||
ma = entry.ma
|
||||
race = entry.name
|
||||
id = entry.id
|
||||
watched = entry.watched
|
||||
end
|
||||
|
||||
dlg.showInputPrompt(
|
||||
'Race: '..race,
|
||||
'Enter desired maximum of female kids:',
|
||||
COLOR_WHITE,
|
||||
' '..fk,
|
||||
function(text)
|
||||
local count = tonumber(text)
|
||||
if self:checkUserInput(count, text) then
|
||||
fk = count
|
||||
if selidx == 1 then
|
||||
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
|
||||
end
|
||||
if selidx == 2 then
|
||||
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
|
||||
end
|
||||
if selidx > 2 then
|
||||
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
|
||||
end
|
||||
self:initListChoices()
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function WatchList:onEditMK()
|
||||
local selidx,selobj = self.subviews.list:getSelected()
|
||||
local settings = plugin.autobutcher_getSettings()
|
||||
local fk = settings.fk
|
||||
local mk = settings.mk
|
||||
local fa = settings.fa
|
||||
local ma = settings.ma
|
||||
local race = 'ALL RACES PLUS NEW'
|
||||
local id = -1
|
||||
local watched = false
|
||||
|
||||
if selidx == 2 then
|
||||
race = 'ONLY NEW RACES'
|
||||
end
|
||||
|
||||
if selidx > 2 then
|
||||
local entry = selobj.obj
|
||||
fk = entry.fk
|
||||
mk = entry.mk
|
||||
fa = entry.fa
|
||||
ma = entry.ma
|
||||
race = entry.name
|
||||
id = entry.id
|
||||
watched = entry.watched
|
||||
end
|
||||
|
||||
dlg.showInputPrompt(
|
||||
'Race: '..race,
|
||||
'Enter desired maximum of male kids:',
|
||||
COLOR_WHITE,
|
||||
' '..mk,
|
||||
function(text)
|
||||
local count = tonumber(text)
|
||||
if self:checkUserInput(count, text) then
|
||||
mk = count
|
||||
if selidx == 1 then
|
||||
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
|
||||
end
|
||||
if selidx == 2 then
|
||||
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
|
||||
end
|
||||
if selidx > 2 then
|
||||
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
|
||||
end
|
||||
self:initListChoices()
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function WatchList:onEditFA()
|
||||
local selidx,selobj = self.subviews.list:getSelected()
|
||||
local settings = plugin.autobutcher_getSettings()
|
||||
local fk = settings.fk
|
||||
local mk = settings.mk
|
||||
local fa = settings.fa
|
||||
local ma = settings.ma
|
||||
local race = 'ALL RACES PLUS NEW'
|
||||
local id = -1
|
||||
local watched = false
|
||||
|
||||
if selidx == 2 then
|
||||
race = 'ONLY NEW RACES'
|
||||
end
|
||||
|
||||
if selidx > 2 then
|
||||
local entry = selobj.obj
|
||||
fk = entry.fk
|
||||
mk = entry.mk
|
||||
fa = entry.fa
|
||||
ma = entry.ma
|
||||
race = entry.name
|
||||
id = entry.id
|
||||
watched = entry.watched
|
||||
end
|
||||
|
||||
dlg.showInputPrompt(
|
||||
'Race: '..race,
|
||||
'Enter desired maximum of female adults:',
|
||||
COLOR_WHITE,
|
||||
' '..fa,
|
||||
function(text)
|
||||
local count = tonumber(text)
|
||||
if self:checkUserInput(count, text) then
|
||||
fa = count
|
||||
if selidx == 1 then
|
||||
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
|
||||
end
|
||||
if selidx == 2 then
|
||||
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
|
||||
end
|
||||
if selidx > 2 then
|
||||
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
|
||||
end
|
||||
self:initListChoices()
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function WatchList:onEditMA()
|
||||
local selidx,selobj = self.subviews.list:getSelected()
|
||||
local settings = plugin.autobutcher_getSettings()
|
||||
local fk = settings.fk
|
||||
local mk = settings.mk
|
||||
local fa = settings.fa
|
||||
local ma = settings.ma
|
||||
local race = 'ALL RACES PLUS NEW'
|
||||
local id = -1
|
||||
local watched = false
|
||||
|
||||
if selidx == 2 then
|
||||
race = 'ONLY NEW RACES'
|
||||
end
|
||||
|
||||
if selidx > 2 then
|
||||
local entry = selobj.obj
|
||||
fk = entry.fk
|
||||
mk = entry.mk
|
||||
fa = entry.fa
|
||||
ma = entry.ma
|
||||
race = entry.name
|
||||
id = entry.id
|
||||
watched = entry.watched
|
||||
end
|
||||
|
||||
dlg.showInputPrompt(
|
||||
'Race: '..race,
|
||||
'Enter desired maximum of male adults:',
|
||||
COLOR_WHITE,
|
||||
' '..ma,
|
||||
function(text)
|
||||
local count = tonumber(text)
|
||||
if self:checkUserInput(count, text) then
|
||||
ma = count
|
||||
if selidx == 1 then
|
||||
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
|
||||
end
|
||||
if selidx == 2 then
|
||||
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
|
||||
end
|
||||
if selidx > 2 then
|
||||
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
|
||||
end
|
||||
self:initListChoices()
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function WatchList:onEditSleepTimer()
|
||||
local sleep = getSleepTimer()
|
||||
dlg.showInputPrompt(
|
||||
'Edit Sleep Timer',
|
||||
'Enter new sleep timer in ticks:'..NEWLINE..'(1 ingame day equals 1200 ticks)',
|
||||
COLOR_WHITE,
|
||||
' '..sleep,
|
||||
function(text)
|
||||
local count = tonumber(text)
|
||||
if self:checkUserInputSleep(count, text) then
|
||||
sleep = count
|
||||
setSleepTimer(sleep)
|
||||
self:updateBottom()
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function WatchList:onToggleWatching()
|
||||
local selidx,selobj = self.subviews.list:getSelected()
|
||||
if selidx > 2 then
|
||||
local entry = selobj.obj
|
||||
plugin.autobutcher_setWatchListRace(entry.id, entry.fk, entry.mk, entry.fa, entry.ma, not entry.watched)
|
||||
end
|
||||
self:initListChoices()
|
||||
end
|
||||
|
||||
function WatchList:onDeleteEntry()
|
||||
local selidx,selobj = self.subviews.list:getSelected()
|
||||
if(selidx < 3 or selobj == nil) then
|
||||
return
|
||||
end
|
||||
dlg.showYesNoPrompt(
|
||||
'Delete from Watchlist',
|
||||
'Really delete the selected entry?'..NEWLINE..'(you could just toggle watch instead)',
|
||||
COLOR_YELLOW,
|
||||
function()
|
||||
plugin.autobutcher_removeFromWatchList(selobj.obj.id)
|
||||
self:initListChoices()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function WatchList:onAddRace()
|
||||
print('onAddRace - not implemented yet')
|
||||
end
|
||||
|
||||
function WatchList:onUnbutcherRace()
|
||||
local selidx,selobj = self.subviews.list:getSelected()
|
||||
if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end
|
||||
if selidx > 2 then
|
||||
local entry = selobj.obj
|
||||
local race = entry.name
|
||||
plugin.autobutcher_unbutcherRace(entry.id)
|
||||
self:initListChoices()
|
||||
self:updateBottom()
|
||||
end
|
||||
end
|
||||
|
||||
function WatchList:onButcherRace()
|
||||
local selidx,selobj = self.subviews.list:getSelected()
|
||||
if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end
|
||||
if selidx > 2 then
|
||||
local entry = selobj.obj
|
||||
local race = entry.name
|
||||
plugin.autobutcher_butcherRace(entry.id)
|
||||
self:initListChoices()
|
||||
self:updateBottom()
|
||||
end
|
||||
end
|
||||
|
||||
-- set whole row (fk, mk, fa, ma) to one value
|
||||
function WatchList:onSetRow()
|
||||
local selidx,selobj = self.subviews.list:getSelected()
|
||||
local race = 'ALL RACES PLUS NEW'
|
||||
local id = -1
|
||||
local watched = false
|
||||
|
||||
if selidx == 2 then
|
||||
race = 'ONLY NEW RACES'
|
||||
end
|
||||
|
||||
local watchindex = selidx - 3
|
||||
if selidx > 2 then
|
||||
local entry = selobj.obj
|
||||
race = entry.name
|
||||
id = entry.id
|
||||
watched = entry.watched
|
||||
end
|
||||
|
||||
dlg.showInputPrompt(
|
||||
'Set whole row for '..race,
|
||||
'Enter desired maximum for all subtypes:',
|
||||
COLOR_WHITE,
|
||||
' ',
|
||||
function(text)
|
||||
local count = tonumber(text)
|
||||
if self:checkUserInput(count, text) then
|
||||
if selidx == 1 then
|
||||
plugin.autobutcher_setDefaultTargetAll( count, count, count, count )
|
||||
end
|
||||
if selidx == 2 then
|
||||
plugin.autobutcher_setDefaultTargetNew( count, count, count, count )
|
||||
end
|
||||
if selidx > 2 then
|
||||
plugin.autobutcher_setWatchListRace(id, count, count, count, count, watched)
|
||||
end
|
||||
self:initListChoices()
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function WatchList:onToggleAutobutcher()
|
||||
if(plugin.autobutcher_isEnabled()) then
|
||||
plugin.autobutcher_setEnabled(false)
|
||||
plugin.autobutcher_sortWatchList()
|
||||
else
|
||||
plugin.autobutcher_setEnabled(true)
|
||||
end
|
||||
self:initListChoices()
|
||||
self:updateBottom()
|
||||
end
|
||||
|
||||
function WatchList:onToggleAutowatch()
|
||||
if(plugin.autowatch_isEnabled()) then
|
||||
plugin.autowatch_setEnabled(false)
|
||||
else
|
||||
plugin.autowatch_setEnabled(true)
|
||||
end
|
||||
self:initListChoices()
|
||||
self:updateBottom()
|
||||
end
|
||||
|
||||
if not dfhack.isMapLoaded() then
|
||||
qerror('Map is not loaded.')
|
||||
end
|
||||
|
||||
if string.match(dfhack.gui.getCurFocus(), '^dfhack/lua') then
|
||||
qerror("This script must not be called while other lua gui stuff is running.")
|
||||
end
|
||||
|
||||
-- maybe this is too strict, there is not really a reason why it can only be called from the status screen
|
||||
-- (other than the hotkey might overlap with other scripts)
|
||||
if (not string.match(dfhack.gui.getCurFocus(), '^overallstatus') and not string.match(dfhack.gui.getCurFocus(), '^pet/List/Unit')) then
|
||||
qerror("This script must either be called from the overall status screen or the animal list screen.")
|
||||
end
|
||||
|
||||
|
||||
local screen = WatchList{ }
|
||||
screen:show()
|
@ -1,120 +1,120 @@
|
||||
# control your levers from the dfhack console
|
||||
|
||||
def lever_pull_job(bld)
|
||||
ref = DFHack::GeneralRefBuildingHolderst.cpp_new
|
||||
ref.building_id = bld.id
|
||||
|
||||
job = DFHack::Job.cpp_new
|
||||
job.job_type = :PullLever
|
||||
job.pos = [bld.centerx, bld.centery, bld.z]
|
||||
job.general_refs << ref
|
||||
bld.jobs << job
|
||||
df.job_link job
|
||||
|
||||
puts lever_descr(bld)
|
||||
end
|
||||
|
||||
def lever_pull_cheat(bld)
|
||||
bld.linked_mechanisms.each { |i|
|
||||
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r|
|
||||
r.building_tg.setTriggerState(bld.state)
|
||||
}
|
||||
}
|
||||
|
||||
bld.state = (bld.state == 0 ? 1 : 0)
|
||||
|
||||
puts lever_descr(bld)
|
||||
end
|
||||
|
||||
def lever_descr(bld, idx=nil)
|
||||
ret = []
|
||||
|
||||
# lever description
|
||||
descr = ''
|
||||
descr << "#{idx}: " if idx
|
||||
descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}"
|
||||
bld.jobs.each { |j|
|
||||
if j.job_type == :PullLever
|
||||
flags = ''
|
||||
flags << ', repeat' if j.flags.repeat
|
||||
flags << ', suspended' if j.flags.suspend
|
||||
descr << " (pull order#{flags})"
|
||||
end
|
||||
}
|
||||
|
||||
bld.linked_mechanisms.map { |i|
|
||||
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst)
|
||||
}.flatten.each { |r|
|
||||
# linked building description
|
||||
tg = r.building_tg
|
||||
state = ''
|
||||
if tg.respond_to?(:gate_flags)
|
||||
state << (tg.gate_flags.closed ? 'closed' : 'opened')
|
||||
state << ", closing (#{tg.timer})" if tg.gate_flags.closing
|
||||
state << ", opening (#{tg.timer})" if tg.gate_flags.opening
|
||||
end
|
||||
|
||||
ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}")
|
||||
|
||||
# indent other links
|
||||
descr = descr.gsub(/./, ' ')
|
||||
}
|
||||
|
||||
ret << descr if ret.empty?
|
||||
|
||||
ret
|
||||
end
|
||||
|
||||
def lever_list
|
||||
@lever_list = []
|
||||
df.world.buildings.other[:TRAP].find_all { |bld|
|
||||
bld.trap_type == :Lever
|
||||
}.sort_by { |bld| bld.id }.each { |bld|
|
||||
puts lever_descr(bld, @lever_list.length)
|
||||
@lever_list << bld.id
|
||||
}
|
||||
end
|
||||
|
||||
case $script_args[0]
|
||||
when 'pull'
|
||||
cheat = $script_args.delete('--cheat') || $script_args.delete('--now')
|
||||
|
||||
id = $script_args[1].to_i
|
||||
id = @lever_list[id] || id
|
||||
bld = df.building_find(id)
|
||||
raise 'invalid lever id' if not bld
|
||||
|
||||
if cheat
|
||||
lever_pull_cheat(bld)
|
||||
else
|
||||
lever_pull_job(bld)
|
||||
end
|
||||
|
||||
when 'list'
|
||||
lever_list
|
||||
|
||||
when /^\d+$/
|
||||
id = $script_args[0].to_i
|
||||
id = @lever_list[id] || id
|
||||
bld = df.building_find(id)
|
||||
raise 'invalid lever id' if not bld
|
||||
|
||||
puts lever_descr(bld)
|
||||
|
||||
else
|
||||
puts <<EOS
|
||||
Lever control from the dfhack console
|
||||
|
||||
Usage:
|
||||
lever list
|
||||
shows the list of levers in the fortress, with their id and links
|
||||
|
||||
lever pull 42
|
||||
order the dwarves to pull lever 42
|
||||
|
||||
lever pull 42 --cheat
|
||||
magically pull lever 42 immediately
|
||||
EOS
|
||||
|
||||
end
|
||||
|
||||
# control your levers from the dfhack console
|
||||
|
||||
def lever_pull_job(bld)
|
||||
ref = DFHack::GeneralRefBuildingHolderst.cpp_new
|
||||
ref.building_id = bld.id
|
||||
|
||||
job = DFHack::Job.cpp_new
|
||||
job.job_type = :PullLever
|
||||
job.pos = [bld.centerx, bld.centery, bld.z]
|
||||
job.general_refs << ref
|
||||
bld.jobs << job
|
||||
df.job_link job
|
||||
|
||||
puts lever_descr(bld)
|
||||
end
|
||||
|
||||
def lever_pull_cheat(bld)
|
||||
bld.linked_mechanisms.each { |i|
|
||||
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r|
|
||||
r.building_tg.setTriggerState(bld.state)
|
||||
}
|
||||
}
|
||||
|
||||
bld.state = (bld.state == 0 ? 1 : 0)
|
||||
|
||||
puts lever_descr(bld)
|
||||
end
|
||||
|
||||
def lever_descr(bld, idx=nil)
|
||||
ret = []
|
||||
|
||||
# lever description
|
||||
descr = ''
|
||||
descr << "#{idx}: " if idx
|
||||
descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}"
|
||||
bld.jobs.each { |j|
|
||||
if j.job_type == :PullLever
|
||||
flags = ''
|
||||
flags << ', repeat' if j.flags.repeat
|
||||
flags << ', suspended' if j.flags.suspend
|
||||
descr << " (pull order#{flags})"
|
||||
end
|
||||
}
|
||||
|
||||
bld.linked_mechanisms.map { |i|
|
||||
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst)
|
||||
}.flatten.each { |r|
|
||||
# linked building description
|
||||
tg = r.building_tg
|
||||
state = ''
|
||||
if tg.respond_to?(:gate_flags)
|
||||
state << (tg.gate_flags.closed ? 'closed' : 'opened')
|
||||
state << ", closing (#{tg.timer})" if tg.gate_flags.closing
|
||||
state << ", opening (#{tg.timer})" if tg.gate_flags.opening
|
||||
end
|
||||
|
||||
ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}")
|
||||
|
||||
# indent other links
|
||||
descr = descr.gsub(/./, ' ')
|
||||
}
|
||||
|
||||
ret << descr if ret.empty?
|
||||
|
||||
ret
|
||||
end
|
||||
|
||||
def lever_list
|
||||
@lever_list = []
|
||||
df.world.buildings.other[:TRAP].find_all { |bld|
|
||||
bld.trap_type == :Lever
|
||||
}.sort_by { |bld| bld.id }.each { |bld|
|
||||
puts lever_descr(bld, @lever_list.length)
|
||||
@lever_list << bld.id
|
||||
}
|
||||
end
|
||||
|
||||
case $script_args[0]
|
||||
when 'pull'
|
||||
cheat = $script_args.delete('--cheat') || $script_args.delete('--now')
|
||||
|
||||
id = $script_args[1].to_i
|
||||
id = @lever_list[id] || id
|
||||
bld = df.building_find(id)
|
||||
raise 'invalid lever id' if not bld
|
||||
|
||||
if cheat
|
||||
lever_pull_cheat(bld)
|
||||
else
|
||||
lever_pull_job(bld)
|
||||
end
|
||||
|
||||
when 'list'
|
||||
lever_list
|
||||
|
||||
when /^\d+$/
|
||||
id = $script_args[0].to_i
|
||||
id = @lever_list[id] || id
|
||||
bld = df.building_find(id)
|
||||
raise 'invalid lever id' if not bld
|
||||
|
||||
puts lever_descr(bld)
|
||||
|
||||
else
|
||||
puts <<EOS
|
||||
Lever control from the dfhack console
|
||||
|
||||
Usage:
|
||||
lever list
|
||||
shows the list of levers in the fortress, with their id and links
|
||||
|
||||
lever pull 42
|
||||
order the dwarves to pull lever 42
|
||||
|
||||
lever pull 42 --cheat
|
||||
magically pull lever 42 immediately
|
||||
EOS
|
||||
|
||||
end
|
||||
|
||||
|
@ -0,0 +1,84 @@
|
||||
# scan the map for ore veins
|
||||
|
||||
target_ore = $script_args[0]
|
||||
|
||||
def find_all_ore_veins
|
||||
puts 'scanning map...'
|
||||
$ore_veins = {}
|
||||
seen_mat = {}
|
||||
df.each_map_block { |block|
|
||||
block.block_events.grep(DFHack::BlockSquareEventMineralst).each { |vein|
|
||||
mat_index = vein.inorganic_mat
|
||||
if not seen_mat[mat_index] or $ore_veins[mat_index]
|
||||
seen_mat[mat_index] = true
|
||||
if df.world.raws.inorganics[mat_index].flags[:METAL_ORE]
|
||||
$ore_veins[mat_index] ||= []
|
||||
$ore_veins[mat_index] << [block.map_pos.x, block.map_pos.y, block.map_pos.z]
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
df.onstatechange_register_once { |st|
|
||||
if st == :MAP_LOADED
|
||||
$ore_veins = nil # invalidate veins cache
|
||||
true
|
||||
end
|
||||
}
|
||||
|
||||
$ore_veins
|
||||
end
|
||||
|
||||
$ore_veins ||= find_all_ore_veins
|
||||
|
||||
if not target_ore or target_ore == 'help'
|
||||
puts <<EOS
|
||||
Scan the map to find one random tile of unmined ore.
|
||||
It will center the game view on that tile and mark it for digging.
|
||||
Only works with metal ores.
|
||||
|
||||
Usage:
|
||||
locate_ore list list all existing vein materials (including mined ones)
|
||||
locate_ore hematite find one tile of unmined hematite ore
|
||||
locate_ore iron find one tile of unmined ore you can smelt into iron
|
||||
EOS
|
||||
|
||||
elsif target_ore and mats = $ore_veins.keys.find_all { |k|
|
||||
ino = df.world.raws.inorganics[k]
|
||||
ino.id =~ /#{target_ore}/i or ino.metal_ore.mat_index.find { |m|
|
||||
df.world.raws.inorganics[m].id =~ /#{target_ore}/i
|
||||
}
|
||||
} and not mats.empty?
|
||||
pos = nil
|
||||
dxs = (0..15).sort_by { rand }
|
||||
dys = (0..15).sort_by { rand }
|
||||
if found_mat = mats.sort_by { rand }.find { |mat|
|
||||
$ore_veins[mat].sort_by { rand }.find { |bx, by, bz|
|
||||
dys.find { |dy|
|
||||
dxs.find { |dx|
|
||||
tile = df.map_tile_at(bx+dx, by+dy, bz)
|
||||
if tile.tilemat == :MINERAL and tile.designation.dig == :No and tile.shape == :WALL and
|
||||
tile.mat_index_vein == mat and
|
||||
# ignore map borders
|
||||
bx+dx > 0 and bx+dx < df.world.map.x_count-1 and by+dy > 0 and by+dy < df.world.map.y_count-1
|
||||
pos = [bx+dx, by+dy, bz]
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
df.center_viewscreen(*pos)
|
||||
df.map_tile_at(*pos).dig
|
||||
puts "Here is some #{df.world.raws.inorganics[found_mat].id}"
|
||||
else
|
||||
puts "Cannot find unmined #{mats.map { |mat| df.world.raws.inorganics[mat].id }.join(', ')}"
|
||||
end
|
||||
|
||||
else
|
||||
puts "Available ores:", $ore_veins.sort_by { |mat, pos| pos.length }.map { |mat, pos|
|
||||
ore = df.world.raws.inorganics[mat]
|
||||
metals = ore.metal_ore.mat_index.map { |m| df.world.raws.inorganics[m] }
|
||||
' ' + ore.id.downcase + ' (' + metals.map { |m| m.id.downcase }.join(', ') + ')'
|
||||
}
|
||||
|
||||
end
|
@ -1,56 +0,0 @@
|
||||
# create an infinite magma source at the cursor
|
||||
|
||||
$magma_sources ||= []
|
||||
|
||||
case $script_args[0]
|
||||
when 'here'
|
||||
$magma_onupdate ||= df.onupdate_register('magmasource', 12) {
|
||||
# called every 12 game ticks (100x a dwarf day)
|
||||
if $magma_sources.empty?
|
||||
df.onupdate_unregister($magma_onupdate)
|
||||
$magma_onupdate = nil
|
||||
end
|
||||
|
||||
$magma_sources.each { |x, y, z|
|
||||
if tile = df.map_tile_at(x, y, z) and tile.shape_passableflow
|
||||
des = tile.designation
|
||||
tile.spawn_magma(des.flow_size + 1) if des.flow_size < 7
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
if df.cursor.x != -30000
|
||||
if tile = df.map_tile_at(df.cursor)
|
||||
if tile.shape_passableflow
|
||||
$magma_sources << [df.cursor.x, df.cursor.y, df.cursor.z]
|
||||
else
|
||||
puts "Impassable tile: I'm afraid I can't do that, Dave"
|
||||
end
|
||||
else
|
||||
puts "Unallocated map block - build something here first"
|
||||
end
|
||||
else
|
||||
puts "Please put the game cursor where you want a magma source"
|
||||
end
|
||||
|
||||
when 'delete-here'
|
||||
$magma_sources.delete [df.cursor.x, df.cursor.y, df.cursor.z]
|
||||
|
||||
when 'stop'
|
||||
$magma_sources.clear
|
||||
|
||||
else
|
||||
puts <<EOS
|
||||
Creates a new infinite magma source at the cursor.
|
||||
|
||||
Arguments:
|
||||
here - create a new source at the current cursor position
|
||||
(call multiple times for higher flow)
|
||||
delete-here - delete the source under the cursor
|
||||
stop - delete all created magma sources
|
||||
EOS
|
||||
|
||||
if $magma_sources.first
|
||||
puts '', 'Current magma sources:', $magma_sources.map { |s| " #{s.inspect}" }
|
||||
end
|
||||
end
|
@ -0,0 +1,40 @@
|
||||
# pit all caged creatures in a zone
|
||||
|
||||
case $script_args[0]
|
||||
when '?', 'help'
|
||||
puts <<EOS
|
||||
Run this script with the cursor on top of a pit/pond activity zone, or with a zone identifier as argument.
|
||||
It will mark all caged creatures on tiles covered by the zone to be dumped.
|
||||
Works best with an animal stockpile on top of the pit/pond zone.
|
||||
EOS
|
||||
throw :script_finished
|
||||
|
||||
when /(\d+)/
|
||||
nr = $1.to_i
|
||||
bld = df.world.buildings.other[:ACTIVITY_ZONE].find { |zone| zone.zone_num == nr }
|
||||
|
||||
else
|
||||
bld = df.world.buildings.other[:ACTIVITY_ZONE].find { |zone|
|
||||
zone.zone_flags.pit_pond and zone.z == df.cursor.z and
|
||||
zone.x1 <= df.cursor.x and zone.x2 >= df.cursor.x and zone.y1 <= df.cursor.y and zone.y2 >= df.cursor.y
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
if not bld
|
||||
puts "Please select a pit/pond zone"
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
found = 0
|
||||
df.world.items.other[:CAGE].each { |cg|
|
||||
next if not cg.flags.on_ground
|
||||
next if cg.pos.z != bld.z or cg.pos.x < bld.x1 or cg.pos.x > bld.x2 or cg.pos.y < bld.y1 or cg.pos.y > bld.y2
|
||||
next if not uref = cg.general_refs.grep(DFHack::GeneralRefContainsUnitst).first
|
||||
found += 1
|
||||
u = uref.unit_tg
|
||||
puts "Pitting #{u.race_tg.name[0]} #{u.id} #{u.name}"
|
||||
u.general_refs << DFHack::GeneralRefBuildingCivzoneAssignedst.cpp_new(:building_id => bld.id)
|
||||
bld.assigned_creature << u.id
|
||||
}
|
||||
puts "No creature available for pitting" if found == 0
|
@ -0,0 +1,4 @@
|
||||
# run many dfhack commands separated by ;
|
||||
# ex: multicmd locate-ore IRON ; digv ; digcircle 16
|
||||
|
||||
$script_args.join(' ').split(/\s*;\s*/).each { |cmd| df.dfhack_run cmd }
|
@ -1,43 +1,43 @@
|
||||
# remove bad thoughts for the selected unit or the whole fort
|
||||
|
||||
dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n')
|
||||
|
||||
$script_args << 'all' if dry_run and $script_args.empty?
|
||||
|
||||
seenbad = Hash.new(0)
|
||||
|
||||
clear_mind = lambda { |u|
|
||||
u.status.recent_events.each { |e|
|
||||
next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-'
|
||||
seenbad[e.type] += 1
|
||||
e.age = 0x1000_0000 unless dry_run
|
||||
}
|
||||
}
|
||||
|
||||
summary = lambda {
|
||||
seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt|
|
||||
puts " #{thought} #{cnt}"
|
||||
}
|
||||
count = seenbad.values.inject(0) { |sum, cnt| sum+cnt }
|
||||
puts "Removed #{count} bad thought#{'s' if count != 1}." if count > 0 and not dry_run
|
||||
}
|
||||
|
||||
case $script_args[0]
|
||||
when 'him'
|
||||
if u = df.unit_find
|
||||
clear_mind[u]
|
||||
summary[]
|
||||
else
|
||||
puts 'Please select a dwarf ingame'
|
||||
end
|
||||
|
||||
when 'all'
|
||||
df.unit_citizens.each { |uu|
|
||||
clear_mind[uu]
|
||||
}
|
||||
summary[]
|
||||
|
||||
else
|
||||
puts "Usage: removebadthoughts [--dry-run] <him|all>"
|
||||
|
||||
end
|
||||
# remove bad thoughts for the selected unit or the whole fort
|
||||
|
||||
dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n')
|
||||
|
||||
$script_args << 'all' if dry_run and $script_args.empty?
|
||||
|
||||
seenbad = Hash.new(0)
|
||||
|
||||
clear_mind = lambda { |u|
|
||||
u.status.recent_events.each { |e|
|
||||
next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-'
|
||||
seenbad[e.type] += 1
|
||||
e.age = 0x1000_0000 unless dry_run
|
||||
}
|
||||
}
|
||||
|
||||
summary = lambda {
|
||||
seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt|
|
||||
puts " #{thought} #{cnt}"
|
||||
}
|
||||
count = seenbad.values.inject(0) { |sum, cnt| sum+cnt }
|
||||
puts "Removed #{count} bad thought#{'s' if count != 1}." if count > 0 and not dry_run
|
||||
}
|
||||
|
||||
case $script_args[0]
|
||||
when 'him'
|
||||
if u = df.unit_find
|
||||
clear_mind[u]
|
||||
summary[]
|
||||
else
|
||||
puts 'Please select a dwarf ingame'
|
||||
end
|
||||
|
||||
when 'all'
|
||||
df.unit_citizens.each { |uu|
|
||||
clear_mind[uu]
|
||||
}
|
||||
summary[]
|
||||
|
||||
else
|
||||
puts "Usage: removebadthoughts [--dry-run] <him|all>"
|
||||
|
||||
end
|
||||
|
@ -0,0 +1,83 @@
|
||||
# create an infinite magma/water source/drain at the cursor
|
||||
|
||||
$sources ||= []
|
||||
|
||||
cur_source = {
|
||||
:liquid => 'water',
|
||||
:amount => 7,
|
||||
:pos => [df.cursor.x, df.cursor.y, df.cursor.z]
|
||||
}
|
||||
cmd = 'help'
|
||||
|
||||
$script_args.each { |a|
|
||||
case a.downcase
|
||||
when 'water', 'magma'
|
||||
cur_source[:liquid] = a.downcase
|
||||
when /^\d+$/
|
||||
cur_source[:amount] = a.to_i
|
||||
when 'add', 'del', 'delete', 'clear', 'help', 'list'
|
||||
cmd = a.downcase
|
||||
else
|
||||
puts "source: unhandled argument #{a}"
|
||||
end
|
||||
}
|
||||
|
||||
case cmd
|
||||
when 'add'
|
||||
$sources_onupdate ||= df.onupdate_register('sources', 12) {
|
||||
# called every 12 game ticks (100x a dwarf day)
|
||||
$sources.each { |s|
|
||||
if tile = df.map_tile_at(*s[:pos]) and tile.shape_passableflow
|
||||
# XXX does not check current liquid_type
|
||||
des = tile.designation
|
||||
cur = des.flow_size
|
||||
if cur != s[:amount]
|
||||
tile.spawn_liquid((cur > s[:amount] ? cur-1 : cur+1), s[:liquid] == 'magma')
|
||||
end
|
||||
end
|
||||
}
|
||||
if $sources.empty?
|
||||
df.onupdate_unregister($sources_onupdate)
|
||||
$sources_onupdate = nil
|
||||
end
|
||||
}
|
||||
|
||||
if cur_source[:pos][0] >= 0
|
||||
if tile = df.map_tile_at(*cur_source[:pos])
|
||||
if tile.shape_passableflow
|
||||
$sources << cur_source
|
||||
else
|
||||
puts "Impassable tile: I'm afraid I can't do that, Dave"
|
||||
end
|
||||
else
|
||||
puts "Unallocated map block - build something here first"
|
||||
end
|
||||
else
|
||||
puts "Please put the game cursor where you want a source"
|
||||
end
|
||||
|
||||
when 'del', 'delete'
|
||||
$sources.delete_if { |s| s[:pos] == cur_source[:pos] }
|
||||
|
||||
when 'clear'
|
||||
$sources.clear
|
||||
|
||||
when 'list'
|
||||
puts "Source list:", $sources.map { |s|
|
||||
" #{s[:pos].inspect} #{s[:liquid]} #{s[:amount]}"
|
||||
}
|
||||
puts "Current cursor pos: #{[df.cursor.x, df.cursor.y, df.cursor.z].inspect}" if df.cursor.x >= 0
|
||||
|
||||
else
|
||||
puts <<EOS
|
||||
Creates a new infinite liquid source at the cursor.
|
||||
|
||||
Examples:
|
||||
source add water - create a water source under cursor
|
||||
source add water 0 - create a water drain
|
||||
source add magma 5 - create a magma source, up to 5/7 deep
|
||||
source delete - delete source under cursor
|
||||
source clear - remove all sources
|
||||
source list
|
||||
EOS
|
||||
end
|
@ -1,204 +1,204 @@
|
||||
# mark stuff inside of cages for dumping.
|
||||
|
||||
def plural(nr, name)
|
||||
# '1 cage' / '4 cages'
|
||||
"#{nr} #{name}#{'s' if nr > 1}"
|
||||
end
|
||||
|
||||
def cage_dump_items(list)
|
||||
count = 0
|
||||
count_cage = 0
|
||||
list.each { |cage|
|
||||
pre_count = count
|
||||
cage.general_refs.each { |ref|
|
||||
next unless ref.kind_of?(DFHack::GeneralRefContainsItemst)
|
||||
next if ref.item_tg.flags.dump
|
||||
count += 1
|
||||
ref.item_tg.flags.dump = true
|
||||
}
|
||||
count_cage += 1 if pre_count != count
|
||||
}
|
||||
|
||||
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
|
||||
end
|
||||
|
||||
def cage_dump_armor(list)
|
||||
count = 0
|
||||
count_cage = 0
|
||||
list.each { |cage|
|
||||
pre_count = count
|
||||
cage.general_refs.each { |ref|
|
||||
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
|
||||
ref.unit_tg.inventory.each { |it|
|
||||
next if it.mode != :Worn
|
||||
next if it.item.flags.dump
|
||||
count += 1
|
||||
it.item.flags.dump = true
|
||||
}
|
||||
}
|
||||
count_cage += 1 if pre_count != count
|
||||
}
|
||||
|
||||
puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}"
|
||||
end
|
||||
|
||||
def cage_dump_weapons(list)
|
||||
count = 0
|
||||
count_cage = 0
|
||||
list.each { |cage|
|
||||
pre_count = count
|
||||
cage.general_refs.each { |ref|
|
||||
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
|
||||
ref.unit_tg.inventory.each { |it|
|
||||
next if it.mode != :Weapon
|
||||
next if it.item.flags.dump
|
||||
count += 1
|
||||
it.item.flags.dump = true
|
||||
}
|
||||
}
|
||||
count_cage += 1 if pre_count != count
|
||||
}
|
||||
|
||||
puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}"
|
||||
end
|
||||
|
||||
def cage_dump_all(list)
|
||||
count = 0
|
||||
count_cage = 0
|
||||
list.each { |cage|
|
||||
pre_count = count
|
||||
cage.general_refs.each { |ref|
|
||||
case ref
|
||||
when DFHack::GeneralRefContainsItemst
|
||||
next if ref.item_tg.flags.dump
|
||||
count += 1
|
||||
ref.item_tg.flags.dump = true
|
||||
when DFHack::GeneralRefContainsUnitst
|
||||
ref.unit_tg.inventory.each { |it|
|
||||
next if it.item.flags.dump
|
||||
count += 1
|
||||
it.item.flags.dump = true
|
||||
}
|
||||
end
|
||||
}
|
||||
count_cage += 1 if pre_count != count
|
||||
}
|
||||
|
||||
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
|
||||
end
|
||||
|
||||
|
||||
def cage_dump_list(list)
|
||||
count_total = Hash.new(0)
|
||||
empty_cages = 0
|
||||
list.each { |cage|
|
||||
count = Hash.new(0)
|
||||
|
||||
cage.general_refs.each { |ref|
|
||||
case ref
|
||||
when DFHack::GeneralRefContainsItemst
|
||||
count[ref.item_tg._rtti_classname] += 1
|
||||
when DFHack::GeneralRefContainsUnitst
|
||||
ref.unit_tg.inventory.each { |it|
|
||||
count[it.item._rtti_classname] += 1
|
||||
}
|
||||
# TODO vermin ?
|
||||
else
|
||||
puts "unhandled ref #{ref.inspect}" if $DEBUG
|
||||
end
|
||||
}
|
||||
|
||||
type = case cage
|
||||
when DFHack::ItemCagest; 'Cage'
|
||||
when DFHack::ItemAnimaltrapst; 'Animal trap'
|
||||
else cage._rtti_classname
|
||||
end
|
||||
|
||||
if count.empty?
|
||||
empty_cages += 1
|
||||
else
|
||||
puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
|
||||
end
|
||||
|
||||
count.each { |k, v| count_total[k] += v }
|
||||
}
|
||||
|
||||
if list.length > 2
|
||||
puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
|
||||
puts "with #{plural(empty_cages, 'empty cage')}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# handle magic script arguments
|
||||
here_only = $script_args.delete 'here'
|
||||
if here_only
|
||||
it = df.item_find
|
||||
list = [it]
|
||||
if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
|
||||
list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) }
|
||||
end
|
||||
if list.empty?
|
||||
puts 'Please select a cage'
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first
|
||||
list = []
|
||||
ids.each { |id|
|
||||
$script_args.delete id
|
||||
if not it = df.item_find(id.to_i)
|
||||
puts "Invalid item id #{id}"
|
||||
elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
|
||||
puts "Item ##{id} is not a cage"
|
||||
list << it
|
||||
else
|
||||
list << it
|
||||
end
|
||||
}
|
||||
if list.empty?
|
||||
puts 'Please use a valid cage id'
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
else
|
||||
list = df.world.items.other[:ANY_CAGE_OR_TRAP]
|
||||
end
|
||||
|
||||
|
||||
# act
|
||||
case $script_args[0]
|
||||
when /^it/i
|
||||
cage_dump_items(list)
|
||||
when /^arm/i
|
||||
cage_dump_armor(list)
|
||||
when /^wea/i
|
||||
cage_dump_weapons(list)
|
||||
when 'all'
|
||||
cage_dump_all(list)
|
||||
when 'list'
|
||||
cage_dump_list(list)
|
||||
else
|
||||
puts <<EOS
|
||||
Marks items inside all cages for dumping.
|
||||
Add 'here' to dump stuff only for selected cage.
|
||||
Add a cage id to dump stuff for this cage only.
|
||||
|
||||
See 'autodump' to actually dump stuff.
|
||||
|
||||
Usage:
|
||||
stripcaged items
|
||||
dump items directly in cages (eg seeds after training)
|
||||
|
||||
stripcaged [armor|weapons] here
|
||||
dump armor or weapons of caged creatures in selected cage
|
||||
|
||||
stripcaged all 28 29
|
||||
dump every item in cage id 28 and 29, along with every item worn by creatures in there too
|
||||
|
||||
stripcaged list
|
||||
show content of the cages
|
||||
|
||||
EOS
|
||||
|
||||
end
|
||||
# mark stuff inside of cages for dumping.
|
||||
|
||||
def plural(nr, name)
|
||||
# '1 cage' / '4 cages'
|
||||
"#{nr} #{name}#{'s' if nr > 1}"
|
||||
end
|
||||
|
||||
def cage_dump_items(list)
|
||||
count = 0
|
||||
count_cage = 0
|
||||
list.each { |cage|
|
||||
pre_count = count
|
||||
cage.general_refs.each { |ref|
|
||||
next unless ref.kind_of?(DFHack::GeneralRefContainsItemst)
|
||||
next if ref.item_tg.flags.dump
|
||||
count += 1
|
||||
ref.item_tg.flags.dump = true
|
||||
}
|
||||
count_cage += 1 if pre_count != count
|
||||
}
|
||||
|
||||
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
|
||||
end
|
||||
|
||||
def cage_dump_armor(list)
|
||||
count = 0
|
||||
count_cage = 0
|
||||
list.each { |cage|
|
||||
pre_count = count
|
||||
cage.general_refs.each { |ref|
|
||||
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
|
||||
ref.unit_tg.inventory.each { |it|
|
||||
next if it.mode != :Worn
|
||||
next if it.item.flags.dump
|
||||
count += 1
|
||||
it.item.flags.dump = true
|
||||
}
|
||||
}
|
||||
count_cage += 1 if pre_count != count
|
||||
}
|
||||
|
||||
puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}"
|
||||
end
|
||||
|
||||
def cage_dump_weapons(list)
|
||||
count = 0
|
||||
count_cage = 0
|
||||
list.each { |cage|
|
||||
pre_count = count
|
||||
cage.general_refs.each { |ref|
|
||||
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
|
||||
ref.unit_tg.inventory.each { |it|
|
||||
next if it.mode != :Weapon
|
||||
next if it.item.flags.dump
|
||||
count += 1
|
||||
it.item.flags.dump = true
|
||||
}
|
||||
}
|
||||
count_cage += 1 if pre_count != count
|
||||
}
|
||||
|
||||
puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}"
|
||||
end
|
||||
|
||||
def cage_dump_all(list)
|
||||
count = 0
|
||||
count_cage = 0
|
||||
list.each { |cage|
|
||||
pre_count = count
|
||||
cage.general_refs.each { |ref|
|
||||
case ref
|
||||
when DFHack::GeneralRefContainsItemst
|
||||
next if ref.item_tg.flags.dump
|
||||
count += 1
|
||||
ref.item_tg.flags.dump = true
|
||||
when DFHack::GeneralRefContainsUnitst
|
||||
ref.unit_tg.inventory.each { |it|
|
||||
next if it.item.flags.dump
|
||||
count += 1
|
||||
it.item.flags.dump = true
|
||||
}
|
||||
end
|
||||
}
|
||||
count_cage += 1 if pre_count != count
|
||||
}
|
||||
|
||||
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
|
||||
end
|
||||
|
||||
|
||||
def cage_dump_list(list)
|
||||
count_total = Hash.new(0)
|
||||
empty_cages = 0
|
||||
list.each { |cage|
|
||||
count = Hash.new(0)
|
||||
|
||||
cage.general_refs.each { |ref|
|
||||
case ref
|
||||
when DFHack::GeneralRefContainsItemst
|
||||
count[ref.item_tg._rtti_classname] += 1
|
||||
when DFHack::GeneralRefContainsUnitst
|
||||
ref.unit_tg.inventory.each { |it|
|
||||
count[it.item._rtti_classname] += 1
|
||||
}
|
||||
# TODO vermin ?
|
||||
else
|
||||
puts "unhandled ref #{ref.inspect}" if $DEBUG
|
||||
end
|
||||
}
|
||||
|
||||
type = case cage
|
||||
when DFHack::ItemCagest; 'Cage'
|
||||
when DFHack::ItemAnimaltrapst; 'Animal trap'
|
||||
else cage._rtti_classname
|
||||
end
|
||||
|
||||
if count.empty?
|
||||
empty_cages += 1
|
||||
else
|
||||
puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
|
||||
end
|
||||
|
||||
count.each { |k, v| count_total[k] += v }
|
||||
}
|
||||
|
||||
if list.length > 2
|
||||
puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
|
||||
puts "with #{plural(empty_cages, 'empty cage')}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# handle magic script arguments
|
||||
here_only = $script_args.delete 'here'
|
||||
if here_only
|
||||
it = df.item_find
|
||||
list = [it]
|
||||
if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
|
||||
list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) }
|
||||
end
|
||||
if list.empty?
|
||||
puts 'Please select a cage'
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first
|
||||
list = []
|
||||
ids.each { |id|
|
||||
$script_args.delete id
|
||||
if not it = df.item_find(id.to_i)
|
||||
puts "Invalid item id #{id}"
|
||||
elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
|
||||
puts "Item ##{id} is not a cage"
|
||||
list << it
|
||||
else
|
||||
list << it
|
||||
end
|
||||
}
|
||||
if list.empty?
|
||||
puts 'Please use a valid cage id'
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
else
|
||||
list = df.world.items.other[:ANY_CAGE_OR_TRAP]
|
||||
end
|
||||
|
||||
|
||||
# act
|
||||
case $script_args[0]
|
||||
when /^it/i
|
||||
cage_dump_items(list)
|
||||
when /^arm/i
|
||||
cage_dump_armor(list)
|
||||
when /^wea/i
|
||||
cage_dump_weapons(list)
|
||||
when 'all'
|
||||
cage_dump_all(list)
|
||||
when 'list'
|
||||
cage_dump_list(list)
|
||||
else
|
||||
puts <<EOS
|
||||
Marks items inside all cages for dumping.
|
||||
Add 'here' to dump stuff only for selected cage.
|
||||
Add a cage id to dump stuff for this cage only.
|
||||
|
||||
See 'autodump' to actually dump stuff.
|
||||
|
||||
Usage:
|
||||
stripcaged items
|
||||
dump items directly in cages (eg seeds after training)
|
||||
|
||||
stripcaged [armor|weapons] here
|
||||
dump armor or weapons of caged creatures in selected cage
|
||||
|
||||
stripcaged all 28 29
|
||||
dump every item in cage id 28 and 29, along with every item worn by creatures in there too
|
||||
|
||||
stripcaged list
|
||||
show content of the cages
|
||||
|
||||
EOS
|
||||
|
||||
end
|
||||
|
@ -1,61 +1,61 @@
|
||||
# give super-dwarven speed to an unit
|
||||
|
||||
$superdwarf_onupdate ||= nil
|
||||
$superdwarf_ids ||= []
|
||||
|
||||
case $script_args[0]
|
||||
when 'add'
|
||||
if u = df.unit_find
|
||||
$superdwarf_ids |= [u.id]
|
||||
|
||||
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) {
|
||||
if $superdwarf_ids.empty?
|
||||
df.onupdate_unregister($superdwarf_onupdate)
|
||||
$superdwarf_onupdate = nil
|
||||
else
|
||||
$superdwarf_ids.each { |id|
|
||||
if u = df.unit_find(id) and not u.flags1.dead
|
||||
# faster walk/work
|
||||
if u.counters.job_counter > 0
|
||||
u.counters.job_counter = 0
|
||||
end
|
||||
|
||||
# no sleep
|
||||
if u.counters2.sleepiness_timer > 10000
|
||||
u.counters2.sleepiness_timer = 1
|
||||
end
|
||||
|
||||
# no break
|
||||
if b = u.status.misc_traits.find { |t| t.id == :OnBreak }
|
||||
b.value = 500_000
|
||||
end
|
||||
else
|
||||
$superdwarf_ids.delete id
|
||||
end
|
||||
}
|
||||
end
|
||||
}
|
||||
else
|
||||
puts "Select a creature using 'v'"
|
||||
end
|
||||
|
||||
when 'del'
|
||||
if u = df.unit_find
|
||||
$superdwarf_ids.delete u.id
|
||||
else
|
||||
puts "Select a creature using 'v'"
|
||||
end
|
||||
|
||||
when 'clear'
|
||||
$superdwarf_ids.clear
|
||||
|
||||
when 'list'
|
||||
puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name }
|
||||
|
||||
else
|
||||
puts "Usage:",
|
||||
" - superdwarf add: give superspeed to currently selected creature",
|
||||
" - superdwarf del: remove superspeed to current creature",
|
||||
" - superdwarf clear: remove all superpowers",
|
||||
" - superdwarf list: list super-dwarves"
|
||||
end
|
||||
# give super-dwarven speed to an unit
|
||||
|
||||
$superdwarf_onupdate ||= nil
|
||||
$superdwarf_ids ||= []
|
||||
|
||||
case $script_args[0]
|
||||
when 'add'
|
||||
if u = df.unit_find
|
||||
$superdwarf_ids |= [u.id]
|
||||
|
||||
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) {
|
||||
if $superdwarf_ids.empty?
|
||||
df.onupdate_unregister($superdwarf_onupdate)
|
||||
$superdwarf_onupdate = nil
|
||||
else
|
||||
$superdwarf_ids.each { |id|
|
||||
if u = df.unit_find(id) and not u.flags1.dead
|
||||
# faster walk/work
|
||||
if u.counters.job_counter > 0
|
||||
u.counters.job_counter = 0
|
||||
end
|
||||
|
||||
# no sleep
|
||||
if u.counters2.sleepiness_timer > 10000
|
||||
u.counters2.sleepiness_timer = 1
|
||||
end
|
||||
|
||||
# no break
|
||||
if b = u.status.misc_traits.find { |t| t.id == :OnBreak }
|
||||
b.value = 500_000
|
||||
end
|
||||
else
|
||||
$superdwarf_ids.delete id
|
||||
end
|
||||
}
|
||||
end
|
||||
}
|
||||
else
|
||||
puts "Select a creature using 'v'"
|
||||
end
|
||||
|
||||
when 'del'
|
||||
if u = df.unit_find
|
||||
$superdwarf_ids.delete u.id
|
||||
else
|
||||
puts "Select a creature using 'v'"
|
||||
end
|
||||
|
||||
when 'clear'
|
||||
$superdwarf_ids.clear
|
||||
|
||||
when 'list'
|
||||
puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name }
|
||||
|
||||
else
|
||||
puts "Usage:",
|
||||
" - superdwarf add: give superspeed to currently selected creature",
|
||||
" - superdwarf del: remove superspeed to current creature",
|
||||
" - superdwarf clear: remove all superpowers",
|
||||
" - superdwarf list: list super-dwarves"
|
||||
end
|
||||
|
@ -1,17 +1,17 @@
|
||||
joblist = df.world.job_list.next
|
||||
count = 0
|
||||
|
||||
while joblist
|
||||
job = joblist.item
|
||||
joblist = joblist.next
|
||||
|
||||
if job.job_type == :ConstructBuilding
|
||||
if (job.flags.suspend && job.items && job.items[0])
|
||||
item = job.items[0].item
|
||||
job.flags.suspend = false
|
||||
count += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
puts "Unsuspended #{count} job(s)."
|
||||
joblist = df.world.job_list.next
|
||||
count = 0
|
||||
|
||||
while joblist
|
||||
job = joblist.item
|
||||
joblist = joblist.next
|
||||
|
||||
if job.job_type == :ConstructBuilding
|
||||
if (job.flags.suspend && job.items && job.items[0])
|
||||
item = job.items[0].item
|
||||
job.flags.suspend = false
|
||||
count += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
puts "Unsuspended #{count} job(s)."
|
||||
|
Loading…
Reference in New Issue