Merge branch 'master' into digSmart
commit
4ba73bc171
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Export.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace DFHack {
|
||||||
|
namespace Once {
|
||||||
|
DFHACK_EXPORT bool alreadyDone(std::string);
|
||||||
|
DFHACK_EXPORT bool doOnce(std::string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
#include "modules/Once.h"
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
static unordered_set<string> thingsDone;
|
||||||
|
|
||||||
|
bool DFHack::Once::alreadyDone(string bob) {
|
||||||
|
return thingsDone.find(bob) != thingsDone.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DFHack::Once::doOnce(string bob) {
|
||||||
|
return thingsDone.insert(bob).second;
|
||||||
|
}
|
||||||
|
|
@ -1 +1 @@
|
|||||||
Subproject commit 4d2afc3a0bcebdb17415dc2827b44fd35986a368
|
Subproject commit 20ecaa0393df1ea111861d67c789aaaa56a37c58
|
@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
DF_DIR=$(dirname "$0")
|
DF_DIR=$(dirname "$0")
|
||||||
cd "${DF_DIR}"
|
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_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch.
|
||||||
#export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
|
#export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
|
||||||
./libs/Dwarf_Fortress $* # Go, go, go! :)
|
./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
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
#include "Core.h"
|
||||||
|
#include "Console.h"
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "modules/Once.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace df::enums;
|
||||||
|
|
||||||
|
command_result onceExample (color_ostream &out, std::vector <std::string> & parameters);
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("onceExample");
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
commands.push_back(PluginCommand(
|
||||||
|
"onceExample", "Test the doOnce command.",
|
||||||
|
onceExample, false,
|
||||||
|
" This command tests the doOnce command..\n"
|
||||||
|
));
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result onceExample (color_ostream &out, std::vector <std::string> & parameters)
|
||||||
|
{
|
||||||
|
out.print("Already done = %d.\n", DFHack::Once::alreadyDone("onceExample_1"));
|
||||||
|
if ( DFHack::Once::doOnce("onceExample_1") ) {
|
||||||
|
out.print("Printing this message once!\n");
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
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,63 @@
|
|||||||
|
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Core.h"
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "modules/Buildings.h"
|
||||||
|
#include "modules/EventManager.h"
|
||||||
|
#include "modules/Maps.h"
|
||||||
|
|
||||||
|
#include "df/coord.h"
|
||||||
|
#include "df/building.h"
|
||||||
|
#include "df/building_def.h"
|
||||||
|
#include "df/map_block.h"
|
||||||
|
#include "df/tile_designation.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("outsideOnly");
|
||||||
|
|
||||||
|
void buildingCreated(color_ostream& out, void* data);
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
EventManager::EventHandler handler(buildingCreated,1);
|
||||||
|
EventManager::registerListener(EventManager::EventType::BUILDING, handler, plugin_self);
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called right before the plugin library is removed from memory.
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void buildingCreated(color_ostream& out, void* data) {
|
||||||
|
int32_t id = (int32_t)data;
|
||||||
|
df::building* building = df::building::find(id);
|
||||||
|
if ( building == NULL )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( building->getCustomType() < 0 )
|
||||||
|
return;
|
||||||
|
string prefix("OUTSIDE_ONLY");
|
||||||
|
df::building_def* def = df::global::world->raws.buildings.all[building->getCustomType()];
|
||||||
|
if (def->code.compare(0, prefix.size(), prefix)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//now, just check if it was created inside, and if so, scuttle it
|
||||||
|
df::coord pos(building->centerx,building->centery,building->z);
|
||||||
|
|
||||||
|
df::tile_designation* des = Maps::getTileDesignation(pos);
|
||||||
|
if ( des->bits.outside )
|
||||||
|
return;
|
||||||
|
|
||||||
|
Buildings::deconstruct(building);
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
#include "Console.h"
|
||||||
|
#include "Core.h"
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
//#include "df/world.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters);
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("skeleton2");
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
commands.push_back(PluginCommand(
|
||||||
|
"skeleton2",
|
||||||
|
"shortHelpString",
|
||||||
|
skeleton2,
|
||||||
|
false, //allow non-interactive use
|
||||||
|
"longHelpString"
|
||||||
|
));
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters)
|
||||||
|
{
|
||||||
|
if (!parameters.empty())
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
CoreSuspender suspend;
|
||||||
|
out.print("blah");
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
|||||||
Subproject commit 83e2b8a3754e2feb0d5bf106d2b43ff71ff63935
|
Subproject commit 0d41614ff3dae9245e786ad667b0e463fe0dea3e
|
@ -0,0 +1,198 @@
|
|||||||
|
|
||||||
|
#include "Core.h"
|
||||||
|
#include "Console.h"
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "modules/EventManager.h"
|
||||||
|
#include "modules/Once.h"
|
||||||
|
|
||||||
|
#include "df/caste_raw.h"
|
||||||
|
#include "df/creature_raw.h"
|
||||||
|
#include "df/syndrome.h"
|
||||||
|
#include "df/unit.h"
|
||||||
|
#include "df/unit_syndrome.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
static bool enabled = false;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("syndromeTrigger");
|
||||||
|
|
||||||
|
void syndromeHandler(color_ostream& out, void* ptr);
|
||||||
|
|
||||||
|
command_result syndromeTrigger(color_ostream& out, vector<string>& parameters);
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
commands.push_back(PluginCommand("syndromeTrigger", "Run commands and enable true transformations, configured by the raw files.\n", &syndromeTrigger, false,
|
||||||
|
"syndromeTrigger:\n"
|
||||||
|
" syndromeTrigger 0 //disable\n"
|
||||||
|
" syndromeTrigger 1 //enable\n"
|
||||||
|
" syndromeTrigger disable //disable\n"
|
||||||
|
" syndromeTrigger enable //enable\n"
|
||||||
|
"\n"
|
||||||
|
"See Readme.rst for details.\n"
|
||||||
|
));
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result syndromeTrigger(color_ostream& out, vector<string>& parameters) {
|
||||||
|
if ( parameters.size() > 1 )
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
|
||||||
|
bool wasEnabled = enabled;
|
||||||
|
if ( parameters.size() == 1 ) {
|
||||||
|
if ( parameters[0] == "enable" ) {
|
||||||
|
enabled = true;
|
||||||
|
} else if ( parameters[0] == "disable" ) {
|
||||||
|
enabled = false;
|
||||||
|
} else {
|
||||||
|
int32_t a = atoi(parameters[0].c_str());
|
||||||
|
if ( a < 0 || a > 1 )
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
|
||||||
|
enabled = (bool)a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.print("syndromeTrigger is %s\n", enabled ? "enabled" : "disabled");
|
||||||
|
if ( enabled == wasEnabled )
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
EventManager::unregisterAll(plugin_self);
|
||||||
|
if ( enabled ) {
|
||||||
|
EventManager::EventHandler handle(syndromeHandler, 1);
|
||||||
|
EventManager::registerListener(EventManager::EventType::SYNDROME, handle, plugin_self);
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void syndromeHandler(color_ostream& out, void* ptr) {
|
||||||
|
EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr;
|
||||||
|
|
||||||
|
if ( !ptr ) {
|
||||||
|
if ( DFHack::Once::doOnce("syndromeTrigger_null data") ) {
|
||||||
|
out.print("%s, %d: null pointer from EventManager.\n", __FILE__, __LINE__);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex);
|
||||||
|
|
||||||
|
df::unit* unit = df::unit::find(data->unitId);
|
||||||
|
if (!unit) {
|
||||||
|
if ( DFHack::Once::doOnce("syndromeTrigger_no find unit" ) )
|
||||||
|
out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex];
|
||||||
|
//out.print(" syndrome type %d\n", unit_syndrome->type);
|
||||||
|
df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type];
|
||||||
|
|
||||||
|
bool foundPermanent = false;
|
||||||
|
bool foundCommand = false;
|
||||||
|
bool foundAutoSyndrome = false;
|
||||||
|
string commandStr;
|
||||||
|
vector<string> args;
|
||||||
|
int32_t raceId = -1;
|
||||||
|
df::creature_raw* creatureRaw = NULL;
|
||||||
|
int32_t casteId = -1;
|
||||||
|
for ( size_t a = 0; a < syndrome->syn_class.size(); a++ ) {
|
||||||
|
std::string& clazz = *syndrome->syn_class[a];
|
||||||
|
//out.print(" clazz %d = %s\n", a, clazz.c_str());
|
||||||
|
if ( foundCommand ) {
|
||||||
|
if ( commandStr == "" ) {
|
||||||
|
commandStr = clazz;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
stringstream bob;
|
||||||
|
if ( clazz == "\\LOCATION" ) {
|
||||||
|
bob << unit->pos.x;
|
||||||
|
args.push_back(bob.str());
|
||||||
|
bob.str("");
|
||||||
|
bob.clear();
|
||||||
|
|
||||||
|
bob << unit->pos.y;
|
||||||
|
args.push_back(bob.str());
|
||||||
|
bob.str("");
|
||||||
|
bob.clear();
|
||||||
|
|
||||||
|
bob << unit->pos.z;
|
||||||
|
args.push_back(bob.str());
|
||||||
|
bob.str("");
|
||||||
|
bob.clear();
|
||||||
|
} else if ( clazz == "\\UNIT_ID" ) {
|
||||||
|
bob << unit->id;
|
||||||
|
args.push_back(bob.str());
|
||||||
|
bob.str("");
|
||||||
|
bob.clear();
|
||||||
|
} else if ( clazz == "\\SYNDROME_ID" ) {
|
||||||
|
bob << unit_syndrome->type;
|
||||||
|
args.push_back(bob.str());
|
||||||
|
bob.str("");
|
||||||
|
bob.clear();
|
||||||
|
} else {
|
||||||
|
args.push_back(clazz);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( clazz == "\\AUTO_SYNDROME" ) {
|
||||||
|
foundAutoSyndrome = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( clazz == "\\COMMAND" ) {
|
||||||
|
foundCommand = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( clazz == "\\PERMANENT" ) {
|
||||||
|
foundPermanent = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( !foundPermanent )
|
||||||
|
continue;
|
||||||
|
if ( raceId == -1 ) {
|
||||||
|
//find the race with the name
|
||||||
|
string& name = *syndrome->syn_class[a];
|
||||||
|
for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) {
|
||||||
|
df::creature_raw* creature = df::global::world->raws.creatures.all[b];
|
||||||
|
if ( creature->creature_id != name )
|
||||||
|
continue;
|
||||||
|
raceId = b;
|
||||||
|
creatureRaw = creature;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( raceId != -1 && casteId == -1 ) {
|
||||||
|
string& name = *syndrome->syn_class[a];
|
||||||
|
for ( size_t b = 0; b < creatureRaw->caste.size(); b++ ) {
|
||||||
|
df::caste_raw* caste = creatureRaw->caste[b];
|
||||||
|
if ( caste->caste_id != name )
|
||||||
|
continue;
|
||||||
|
casteId = b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !foundAutoSyndrome && commandStr != "" ) {
|
||||||
|
Core::getInstance().runCommand(out, commandStr, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !foundPermanent || raceId == -1 || casteId == -1 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
unit->enemy.normal_race = raceId;
|
||||||
|
unit->enemy.normal_caste = casteId;
|
||||||
|
//that's it!
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,145 @@
|
|||||||
|
#include "Console.h"
|
||||||
|
#include "Core.h"
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "modules/EventManager.h"
|
||||||
|
#include "modules/Once.h"
|
||||||
|
|
||||||
|
#include "df/block_burrow.h"
|
||||||
|
#include "df/block_burrow_link.h"
|
||||||
|
#include "df/burrow.h"
|
||||||
|
#include "df/map_block.h"
|
||||||
|
#include "df/tile_bitmask.h"
|
||||||
|
#include "df/tile_dig_designation.h"
|
||||||
|
#include "df/tiletype.h"
|
||||||
|
#include "df/tiletype_shape.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
|
||||||
|
//#include "df/world.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
void checkFarms(color_ostream& out, void* ptr);
|
||||||
|
command_result treefarm (color_ostream &out, std::vector <std::string> & parameters);
|
||||||
|
|
||||||
|
EventManager::EventHandler handler(&checkFarms, -1);
|
||||||
|
int32_t frequency = 1200*30;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("treefarm");
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
commands.push_back(PluginCommand(
|
||||||
|
"treefarm",
|
||||||
|
"automatically manages special burrows and regularly schedules tree chopping and digging when appropriate",
|
||||||
|
treefarm,
|
||||||
|
false, //allow non-interactive use
|
||||||
|
"treefarm\n"
|
||||||
|
" enables treefarm monitoring, starting next frame\n"
|
||||||
|
"treefarm n\n"
|
||||||
|
" enables treefarm monitoring, starting next frame\n"
|
||||||
|
" sets monitoring interval to n frames\n"
|
||||||
|
" if n is less than one, disables monitoring\n"
|
||||||
|
"\n"
|
||||||
|
"Every time the plugin runs, it checks for burrows with a name containing the string \"treefarm\". For each such burrow, it checks every tile in it for fully-grown trees and for diggable walls. For each fully-grown tree it finds, it designates the tree to be chopped, and for each natural wall it finds, it designates the wall to be dug.\n"
|
||||||
|
));
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkFarms(color_ostream& out, void* ptr) {
|
||||||
|
EventManager::unregisterAll(plugin_self);
|
||||||
|
EventManager::registerTick(handler, frequency, plugin_self);
|
||||||
|
CoreSuspender suspend;
|
||||||
|
|
||||||
|
df::world* world = df::global::world;
|
||||||
|
df::ui* ui = df::global::ui;
|
||||||
|
int32_t xOffset = world->map.region_x*3;
|
||||||
|
int32_t yOffset = world->map.region_y*3;
|
||||||
|
int32_t zOffset = world->map.region_z;
|
||||||
|
//for each burrow named treefarm or obsidianfarm, check if you can dig/chop any obsidian/trees
|
||||||
|
for ( size_t a = 0; a < df::burrow::get_vector().size(); a++ ) {
|
||||||
|
df::burrow* burrow = df::burrow::get_vector()[a];
|
||||||
|
if ( !burrow || burrow->name.find("treefarm") == std::string::npos )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ( burrow->block_x.size() != burrow->block_y.size() || burrow->block_x.size() != burrow->block_z.size() )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for ( size_t b = 0; b < burrow->block_x.size(); b++ ) {
|
||||||
|
int32_t x=burrow->block_x[b] - xOffset;
|
||||||
|
int32_t y=burrow->block_y[b] - yOffset;
|
||||||
|
int32_t z=burrow->block_z[b] - zOffset;
|
||||||
|
|
||||||
|
df::map_block* block = world->map.block_index[x][y][z];
|
||||||
|
if ( !block )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
df::block_burrow_link* link = &block->block_burrows;
|
||||||
|
df::tile_bitmask mask;
|
||||||
|
for ( ; link != NULL; link = link->next ) {
|
||||||
|
if ( link->item == NULL )
|
||||||
|
continue;
|
||||||
|
if ( link->item->id == burrow->id ) {
|
||||||
|
mask = link->item->tile_bitmask;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( link == NULL )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for ( int32_t x = 0; x < 16; x++ ) {
|
||||||
|
for ( int32_t y = 0; y < 16; y++ ) {
|
||||||
|
if ( !mask.getassignment(x,y) )
|
||||||
|
continue;
|
||||||
|
df::tiletype type = block->tiletype[x][y];
|
||||||
|
df::tiletype_shape shape = ENUM_ATTR(tiletype, shape, type);
|
||||||
|
if ( !block->designation[x][y].bits.hidden &&
|
||||||
|
shape != df::enums::tiletype_shape::WALL &&
|
||||||
|
shape != df::enums::tiletype_shape::TREE )
|
||||||
|
continue;
|
||||||
|
if ( shape != df::enums::tiletype_shape::TREE ) {
|
||||||
|
if ( x == 0 && (block->map_pos.x/16) == 0 )
|
||||||
|
continue;
|
||||||
|
if ( y == 0 && (block->map_pos.y/16) == 0 )
|
||||||
|
continue;
|
||||||
|
if ( x == 15 && (block->map_pos.x/16) == world->map.x_count_block-1 )
|
||||||
|
continue;
|
||||||
|
if ( y == 15 && (block->map_pos.y/16) == world->map.y_count_block-1 )
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
block->designation[x][y].bits.dig = df::enums::tile_dig_designation::Default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result treefarm (color_ostream &out, std::vector <std::string> & parameters)
|
||||||
|
{
|
||||||
|
EventManager::unregisterAll(plugin_self);
|
||||||
|
|
||||||
|
if ( parameters.size() > 1 )
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
if ( parameters.size() == 1 ) {
|
||||||
|
int32_t i = atoi(parameters[0].c_str());
|
||||||
|
if ( i < 1 ) {
|
||||||
|
out.print("treefarm disabled\n");
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
frequency = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventManager::registerTick(handler, 1, plugin_self);
|
||||||
|
|
||||||
|
out.print("treefarm enabled with update frequency %d ticks\n", frequency);
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
@ -1,87 +0,0 @@
|
|||||||
|
|
||||||
#include "Core.h"
|
|
||||||
#include "Console.h"
|
|
||||||
#include "DataDefs.h"
|
|
||||||
#include "Export.h"
|
|
||||||
#include "PluginManager.h"
|
|
||||||
|
|
||||||
#include "modules/EventManager.h"
|
|
||||||
|
|
||||||
#include "df/caste_raw.h"
|
|
||||||
#include "df/creature_raw.h"
|
|
||||||
#include "df/syndrome.h"
|
|
||||||
#include "df/unit.h"
|
|
||||||
#include "df/unit_syndrome.h"
|
|
||||||
#include "df/world.h"
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
using namespace DFHack;
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
DFHACK_PLUGIN("trueTransformation");
|
|
||||||
|
|
||||||
void syndromeHandler(color_ostream& out, void* ptr);
|
|
||||||
|
|
||||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
|
||||||
{
|
|
||||||
EventManager::EventHandler syndrome(syndromeHandler, 1);
|
|
||||||
EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, plugin_self);
|
|
||||||
|
|
||||||
return CR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void syndromeHandler(color_ostream& out, void* ptr) {
|
|
||||||
EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr;
|
|
||||||
//out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex);
|
|
||||||
|
|
||||||
df::unit* unit = df::unit::find(data->unitId);
|
|
||||||
if (!unit) {
|
|
||||||
out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex];
|
|
||||||
df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type];
|
|
||||||
|
|
||||||
bool foundIt = false;
|
|
||||||
int32_t raceId = -1;
|
|
||||||
df::creature_raw* creatureRaw = NULL;
|
|
||||||
int32_t casteId = -1;
|
|
||||||
for ( size_t a = 0; a < syndrome->syn_class.size(); a++ ) {
|
|
||||||
if ( *syndrome->syn_class[a] == "\\PERMANENT" ) {
|
|
||||||
foundIt = true;
|
|
||||||
}
|
|
||||||
if ( foundIt && raceId == -1 ) {
|
|
||||||
//find the race with the name
|
|
||||||
string& name = *syndrome->syn_class[a];
|
|
||||||
for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) {
|
|
||||||
df::creature_raw* creature = df::global::world->raws.creatures.all[b];
|
|
||||||
if ( creature->creature_id != name )
|
|
||||||
continue;
|
|
||||||
raceId = b;
|
|
||||||
creatureRaw = creature;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ( foundIt && raceId != -1 ) {
|
|
||||||
string& name = *syndrome->syn_class[a];
|
|
||||||
for ( size_t b = 0; b < creatureRaw->caste.size(); b++ ) {
|
|
||||||
df::caste_raw* caste = creatureRaw->caste[b];
|
|
||||||
if ( caste->caste_id != name )
|
|
||||||
continue;
|
|
||||||
casteId = b;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( !foundIt || raceId == -1 || casteId == -1 )
|
|
||||||
return;
|
|
||||||
|
|
||||||
unit->enemy.normal_race = raceId;
|
|
||||||
unit->enemy.normal_caste = casteId;
|
|
||||||
//that's it!
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
class AutoFarm
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@thresholds = Hash.new(50)
|
@thresholds = Hash.new(50)
|
||||||
@lastcounts = Hash.new(0)
|
@lastcounts = Hash.new(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
def setthreshold(id, v)
|
def setthreshold(id, v)
|
||||||
if df.world.raws.plants.all.find { |r| r.id == id }
|
list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id }
|
||||||
@thresholds[id] = v.to_i
|
if tok = df.match_rawname(id, list)
|
||||||
else
|
@thresholds[tok] = v.to_i
|
||||||
puts "No plant with id #{id}"
|
else
|
||||||
end
|
puts "No plant with id #{id}, try one of " +
|
||||||
end
|
list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ')
|
||||||
|
end
|
||||||
def setdefault(v)
|
end
|
||||||
@thresholds.default = v.to_i
|
|
||||||
end
|
def setdefault(v)
|
||||||
|
@thresholds.default = v.to_i
|
||||||
def is_plantable (plant)
|
end
|
||||||
has_seed = plant.flags[:SEED]
|
|
||||||
season = df.cur_season
|
def is_plantable(plant)
|
||||||
harvest = df.cur_season_tick + plant.growdur * 10
|
has_seed = plant.flags[:SEED]
|
||||||
will_finish = harvest < 10080
|
season = df.cur_season
|
||||||
can_plant = has_seed && plant.flags[season]
|
harvest = df.cur_season_tick + plant.growdur * 10
|
||||||
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
|
will_finish = harvest < 10080
|
||||||
can_plant
|
can_plant = has_seed && plant.flags[season]
|
||||||
end
|
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
|
||||||
|
can_plant
|
||||||
def find_plantable_plants
|
end
|
||||||
plantable = {}
|
|
||||||
counts = Hash.new(0)
|
def find_plantable_plants
|
||||||
|
plantable = {}
|
||||||
df.world.items.other[:SEEDS].each { |i|
|
counts = Hash.new(0)
|
||||||
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
|
||||||
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
df.world.items.other[:SEEDS].each { |i|
|
||||||
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
||||||
!i.flags.artifact)
|
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
||||||
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
|
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
||||||
end
|
!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]
|
counts.keys.each { |i|
|
||||||
if is_plantable(plant)
|
if df.ui.tasks.known_plants[i]
|
||||||
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
|
plant = df.world.raws.plants.all[i]
|
||||||
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
|
if is_plantable(plant)
|
||||||
end
|
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
|
||||||
end
|
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
|
||||||
}
|
end
|
||||||
|
end
|
||||||
return plantable
|
}
|
||||||
end
|
|
||||||
|
return plantable
|
||||||
def set_farms( plants, farms)
|
end
|
||||||
return if farms.length == 0
|
|
||||||
if plants.length == 0
|
def set_farms(plants, farms)
|
||||||
plants = [-1]
|
return if farms.length == 0
|
||||||
end
|
if plants.length == 0
|
||||||
|
plants = [-1]
|
||||||
season = df.cur_season
|
end
|
||||||
|
|
||||||
idx = 0
|
season = df.cur_season
|
||||||
|
|
||||||
farms.each { |f|
|
farms.each_with_index { |f, idx|
|
||||||
f.plant_id[season] = plants[idx]
|
f.plant_id[season] = plants[idx % plants.length]
|
||||||
idx = (idx + 1) % plants.length
|
}
|
||||||
}
|
end
|
||||||
end
|
|
||||||
|
def process
|
||||||
def process
|
plantable = find_plantable_plants
|
||||||
return false unless @running
|
@lastcounts = Hash.new(0)
|
||||||
|
|
||||||
plantable = find_plantable_plants
|
df.world.items.other[:PLANT].each { |i|
|
||||||
counts = Hash.new(0)
|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
||||||
|
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
||||||
df.world.items.other[:PLANT].each { |i|
|
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
||||||
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
!i.flags.artifact && plantable.has_key?(i.mat_index))
|
||||||
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
id = df.world.raws.plants.all[i.mat_index].id
|
||||||
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
@lastcounts[id] += i.stack_size
|
||||||
!i.flags.artifact && plantable.has_key?(i.mat_index))
|
end
|
||||||
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
|
}
|
||||||
end
|
|
||||||
}
|
return unless @running
|
||||||
|
|
||||||
plants_s = []
|
plants_s = []
|
||||||
plants_u = []
|
plants_u = []
|
||||||
|
|
||||||
@lastcounts.clear
|
plantable.each_key { |k|
|
||||||
|
plant = df.world.raws.plants.all[k]
|
||||||
plantable.each_key { |k|
|
if (@lastcounts[plant.id] < @thresholds[plant.id])
|
||||||
plant = df.world.raws.plants.all[k]
|
plants_s.push(k) if plantable[k] == :Surface
|
||||||
if (counts[k] < @thresholds[plant.id])
|
plants_u.push(k) if plantable[k] == :Underground
|
||||||
plants_s.push(k) if plantable[k] == :Surface
|
end
|
||||||
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|
|
||||||
farms_s = []
|
if (f.flags.exists)
|
||||||
farms_u = []
|
underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean
|
||||||
df.world.buildings.other[:FARM_PLOT].each { |f|
|
farms_s.push(f) unless underground
|
||||||
if (f.flags.exists)
|
farms_u.push(f) if underground
|
||||||
underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean
|
end
|
||||||
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
|
||||||
set_farms(plants_s, farms_s)
|
|
||||||
set_farms(plants_u, farms_u)
|
def start
|
||||||
|
return if @running
|
||||||
end
|
@onupdate = df.onupdate_register('autofarm', 1200) { process }
|
||||||
|
@running = true
|
||||||
def start
|
end
|
||||||
@onupdate = df.onupdate_register('autofarm', 100) { process }
|
|
||||||
@running = true
|
def stop
|
||||||
end
|
df.onupdate_unregister(@onupdate)
|
||||||
|
@running = false
|
||||||
def stop
|
end
|
||||||
df.onupdate_unregister(@onupdate)
|
|
||||||
@running = false
|
def status
|
||||||
end
|
stat = @running ? "Running." : "Stopped."
|
||||||
|
@lastcounts.each { |k,v|
|
||||||
def status
|
stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}"
|
||||||
stat = @running ? "Running." : "Stopped."
|
}
|
||||||
@thresholds.each { |k,v|
|
@thresholds.each { |k,v|
|
||||||
stat += "\n#{k} limit #{v} current #{@lastcounts[k]}"
|
stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k)
|
||||||
}
|
}
|
||||||
stat += "\nDefault: #{@thresholds.default}"
|
stat << "\nDefault: #{@thresholds.default}"
|
||||||
stat
|
stat
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
$AutoFarm = AutoFarm.new unless $AutoFarm
|
$AutoFarm ||= AutoFarm.new
|
||||||
|
|
||||||
case $script_args[0]
|
case $script_args[0]
|
||||||
when 'start'
|
when 'start', 'enable'
|
||||||
$AutoFarm.start
|
$AutoFarm.start
|
||||||
|
puts $AutoFarm.status
|
||||||
when 'end', 'stop'
|
|
||||||
$AutoFarm.stop
|
when 'end', 'stop', 'disable'
|
||||||
|
$AutoFarm.stop
|
||||||
when 'default'
|
puts 'Stopped.'
|
||||||
$AutoFarm.setdefault($script_args[1])
|
|
||||||
|
when 'default'
|
||||||
when 'threshold'
|
$AutoFarm.setdefault($script_args[1])
|
||||||
t = $script_args[1]
|
|
||||||
$script_args[2..-1].each {|i|
|
when 'threshold'
|
||||||
$AutoFarm.setthreshold(i, t)
|
t = $script_args[1]
|
||||||
}
|
$script_args[2..-1].each {|i|
|
||||||
|
$AutoFarm.setthreshold(i, t)
|
||||||
when 'delete'
|
}
|
||||||
$AutoFarm.stop
|
|
||||||
$AutoFarm = nil
|
when 'delete'
|
||||||
|
$AutoFarm.stop
|
||||||
else
|
$AutoFarm = nil
|
||||||
if $AutoFarm
|
|
||||||
puts $AutoFarm.status
|
when 'help', '?'
|
||||||
else
|
puts <<EOS
|
||||||
puts "AI not started"
|
Automatically handle crop selection in farm plots based on current plant stocks.
|
||||||
end
|
Selects a crop for planting if current stock is below a threshold.
|
||||||
end
|
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
|
class AutoUnsuspend
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
end
|
end
|
||||||
|
|
||||||
def process
|
def process
|
||||||
return false unless @running
|
return false unless @running
|
||||||
joblist = df.world.job_list.next
|
joblist = df.world.job_list.next
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
while joblist
|
while joblist
|
||||||
job = joblist.item
|
job = joblist.item
|
||||||
joblist = joblist.next
|
joblist = joblist.next
|
||||||
|
|
||||||
if job.job_type == :ConstructBuilding
|
if job.job_type == :ConstructBuilding
|
||||||
if (job.flags.suspend)
|
if (job.flags.suspend)
|
||||||
item = job.items[0].item
|
item = job.items[0].item
|
||||||
job.flags.suspend = false
|
job.flags.suspend = false
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
puts "Unsuspended #{count} job(s)." unless count == 0
|
puts "Unsuspended #{count} job(s)." unless count == 0
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def start
|
def start
|
||||||
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
|
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
|
||||||
@running = true
|
@running = true
|
||||||
end
|
end
|
||||||
|
|
||||||
def stop
|
def stop
|
||||||
df.onupdate_unregister(@onupdate)
|
df.onupdate_unregister(@onupdate)
|
||||||
@running = false
|
@running = false
|
||||||
end
|
end
|
||||||
|
|
||||||
def status
|
def status
|
||||||
@running ? 'Running.' : 'Stopped.'
|
@running ? 'Running.' : 'Stopped.'
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
case $script_args[0]
|
case $script_args[0]
|
||||||
when 'start'
|
when 'start'
|
||||||
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
|
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
|
||||||
$AutoUnsuspend.start
|
$AutoUnsuspend.start
|
||||||
|
|
||||||
when 'end', 'stop'
|
when 'end', 'stop'
|
||||||
$AutoUnsuspend.stop
|
$AutoUnsuspend.stop
|
||||||
|
|
||||||
else
|
else
|
||||||
if $AutoUnsuspend
|
if $AutoUnsuspend
|
||||||
puts $AutoUnsuspend.status
|
puts $AutoUnsuspend.status
|
||||||
else
|
else
|
||||||
puts 'Not loaded.'
|
puts 'Not loaded.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,161 +1,177 @@
|
|||||||
# create arbitrary items under cursor
|
# create first necessity items under cursor
|
||||||
|
|
||||||
category = $script_args[0] || 'help'
|
category = $script_args[0] || 'help'
|
||||||
mat_raw = $script_args[1] || 'list'
|
mat_raw = $script_args[1] || 'list'
|
||||||
count = $script_args[2]
|
count = $script_args[2]
|
||||||
|
|
||||||
|
|
||||||
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs']) || 'help'
|
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help'
|
||||||
|
|
||||||
if category == 'help'
|
if category == 'help'
|
||||||
puts <<EOS
|
puts <<EOS
|
||||||
Create items under the cursor.
|
Create first necessity items under the cursor.
|
||||||
Usage:
|
Usage:
|
||||||
create [category] [raws token] [number]
|
create-items [category] [raws token] [number]
|
||||||
|
|
||||||
Item categories:
|
Item categories:
|
||||||
bars, boulders, plants, logs, web
|
bars, boulders, plants, logs, webs, anvils
|
||||||
|
|
||||||
Raw token:
|
Raw token:
|
||||||
either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
|
Either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
|
||||||
(the missing part is autocompleted depending on the item category)
|
(the missing part is autocompleted depending on the item category)
|
||||||
use 'list' to show all possibilities
|
Use 'list' to show all possibilities
|
||||||
|
|
||||||
Exemples:
|
Exemples:
|
||||||
create boulders hematite 30
|
create-items boulders hematite 30
|
||||||
create bars CREATURE_MAT:CAT:SOAP 10
|
create-items bars CREATURE_MAT:CAT:SOAP 10
|
||||||
create web cave_giant
|
create-items web cave_giant
|
||||||
create plants list
|
create-items plants list
|
||||||
|
|
||||||
EOS
|
EOS
|
||||||
throw :script_finished
|
throw :script_finished
|
||||||
|
|
||||||
elsif mat_raw == 'list'
|
elsif mat_raw == 'list'
|
||||||
# allowed with no cursor
|
# allowed with no cursor
|
||||||
|
|
||||||
elsif df.cursor.x == -30000
|
elsif df.cursor.x == -30000
|
||||||
puts "Please place the game cursor somewhere"
|
puts "Please place the game cursor somewhere"
|
||||||
throw :script_finished
|
throw :script_finished
|
||||||
|
|
||||||
elsif !(maptile = df.map_tile_at(df.cursor))
|
elsif !(maptile = df.map_tile_at(df.cursor))
|
||||||
puts "Error: unallocated map block !"
|
puts "Error: unallocated map block !"
|
||||||
throw :script_finished
|
throw :script_finished
|
||||||
|
|
||||||
elsif !maptile.shape_passablehigh
|
elsif !maptile.shape_passablehigh
|
||||||
puts "Error: impassible tile !"
|
puts "Error: impassible tile !"
|
||||||
throw :script_finished
|
throw :script_finished
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def match_list(tok, list)
|
def match_list(tok, list)
|
||||||
if tok != 'list'
|
if tok != 'list'
|
||||||
tok = df.match_rawname(tok, list)
|
tok = df.match_rawname(tok, list)
|
||||||
if not tok
|
if not tok
|
||||||
puts "Invalid raws token, use one in:"
|
puts "Invalid raws token, use one in:"
|
||||||
tok = 'list'
|
tok = 'list'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if tok == 'list'
|
if tok == 'list'
|
||||||
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
|
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
|
||||||
throw :script_finished
|
throw :script_finished
|
||||||
end
|
end
|
||||||
tok
|
tok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
case category
|
case category
|
||||||
when 'bars'
|
when 'bars'
|
||||||
# create metal bar, eg createbar INORGANIC:IRON
|
# create metal bar, eg createbar INORGANIC:IRON
|
||||||
cls = DFHack::ItemBarst
|
cls = DFHack::ItemBarst
|
||||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||||
list = df.world.raws.inorganics.find_all { |ino|
|
list = df.world.raws.inorganics.find_all { |ino|
|
||||||
ino.material.flags[:IS_METAL]
|
ino.material.flags[:IS_METAL]
|
||||||
}.map { |ino| ino.id }
|
}.map { |ino| ino.id }
|
||||||
mat_raw = match_list(mat_raw, list)
|
mat_raw = match_list(mat_raw, list)
|
||||||
mat_raw = "INORGANIC:#{mat_raw}"
|
mat_raw = "INORGANIC:#{mat_raw}"
|
||||||
puts mat_raw
|
puts mat_raw
|
||||||
end
|
end
|
||||||
customize = lambda { |item|
|
customize = lambda { |item|
|
||||||
item.dimension = 150
|
item.dimension = 150
|
||||||
item.subtype = -1
|
item.subtype = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
when 'boulders'
|
when 'boulders'
|
||||||
cls = DFHack::ItemBoulderst
|
cls = DFHack::ItemBoulderst
|
||||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||||
list = df.world.raws.inorganics.find_all { |ino|
|
list = df.world.raws.inorganics.find_all { |ino|
|
||||||
ino.material.flags[:IS_STONE]
|
ino.material.flags[:IS_STONE]
|
||||||
}.map { |ino| ino.id }
|
}.map { |ino| ino.id }
|
||||||
mat_raw = match_list(mat_raw, list)
|
mat_raw = match_list(mat_raw, list)
|
||||||
mat_raw = "INORGANIC:#{mat_raw}"
|
mat_raw = "INORGANIC:#{mat_raw}"
|
||||||
puts mat_raw
|
puts mat_raw
|
||||||
end
|
end
|
||||||
|
|
||||||
when 'plants'
|
when 'plants'
|
||||||
cls = DFHack::ItemPlantst
|
cls = DFHack::ItemPlantst
|
||||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||||
list = df.world.raws.plants.all.find_all { |plt|
|
list = df.world.raws.plants.all.find_all { |plt|
|
||||||
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
|
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
|
||||||
}.map { |plt| plt.id }
|
}.map { |plt| plt.id }
|
||||||
mat_raw = match_list(mat_raw, list)
|
mat_raw = match_list(mat_raw, list)
|
||||||
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
|
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
|
||||||
puts mat_raw
|
puts mat_raw
|
||||||
end
|
end
|
||||||
|
|
||||||
when 'logs'
|
when 'logs'
|
||||||
cls = DFHack::ItemWoodst
|
cls = DFHack::ItemWoodst
|
||||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||||
list = df.world.raws.plants.all.find_all { |plt|
|
list = df.world.raws.plants.all.find_all { |plt|
|
||||||
plt.material.find { |mat| mat.id == 'WOOD' }
|
plt.material.find { |mat| mat.id == 'WOOD' }
|
||||||
}.map { |plt| plt.id }
|
}.map { |plt| plt.id }
|
||||||
mat_raw = match_list(mat_raw, list)
|
mat_raw = match_list(mat_raw, list)
|
||||||
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
|
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
|
||||||
puts mat_raw
|
puts mat_raw
|
||||||
end
|
end
|
||||||
|
|
||||||
when 'webs'
|
when 'webs'
|
||||||
cls = DFHack::ItemThreadst
|
cls = DFHack::ItemThreadst
|
||||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||||
list = df.world.raws.creatures.all.find_all { |cre|
|
list = df.world.raws.creatures.all.find_all { |cre|
|
||||||
cre.material.find { |mat| mat.id == 'SILK' }
|
cre.material.find { |mat| mat.id == 'SILK' }
|
||||||
}.map { |cre| cre.creature_id }
|
}.map { |cre| cre.creature_id }
|
||||||
mat_raw = match_list(mat_raw, list)
|
mat_raw = match_list(mat_raw, list)
|
||||||
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
|
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
|
||||||
puts mat_raw
|
puts mat_raw
|
||||||
end
|
end
|
||||||
count ||= 1
|
count ||= 1
|
||||||
customize = lambda { |item|
|
customize = lambda { |item|
|
||||||
item.flags.spider_web = true
|
item.flags.spider_web = true
|
||||||
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
|
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
|
||||||
}
|
}
|
||||||
|
|
||||||
end
|
when 'anvils'
|
||||||
|
cls = DFHack::ItemAnvilst
|
||||||
|
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||||
mat = df.decode_mat mat_raw
|
list = df.world.raws.inorganics.find_all { |ino|
|
||||||
|
ino.material.flags[:IS_METAL]
|
||||||
count ||= 20
|
}.map { |ino| ino.id }
|
||||||
count.to_i.times {
|
mat_raw = match_list(mat_raw, list)
|
||||||
item = cls.cpp_new
|
mat_raw = "INORGANIC:#{mat_raw}"
|
||||||
item.id = df.item_next_id
|
puts mat_raw
|
||||||
item.stack_size = 1
|
end
|
||||||
item.mat_type = mat.mat_type
|
count ||= 1
|
||||||
item.mat_index = mat.mat_index
|
|
||||||
|
end
|
||||||
customize[item] if customize
|
|
||||||
|
|
||||||
df.item_next_id += 1
|
mat = df.decode_mat mat_raw
|
||||||
item.categorize(true)
|
|
||||||
df.world.items.all << item
|
count ||= 20
|
||||||
|
count.to_i.times {
|
||||||
item.pos = df.cursor
|
item = cls.cpp_new
|
||||||
item.flags.on_ground = true
|
item.id = df.item_next_id
|
||||||
df.map_tile_at.mapblock.items << item.id
|
item.stack_size = 1
|
||||||
df.map_tile_at.occupancy.item = true
|
item.mat_type = mat.mat_type
|
||||||
}
|
item.mat_index = mat.mat_index
|
||||||
|
|
||||||
|
customize[item] if customize
|
||||||
# move game view, so that the ui menu updates
|
|
||||||
df.curview.feed_keys(:CURSOR_UP_Z)
|
df.item_next_id += 1
|
||||||
df.curview.feed_keys(:CURSOR_DOWN_Z)
|
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
|
# show death cause of a creature
|
||||||
|
|
||||||
def display_death_event(e)
|
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 = "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 << " (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 << " 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 << " 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
|
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(',') + '.'
|
puts str.chomp(',') + '.'
|
||||||
end
|
end
|
||||||
|
|
||||||
def display_death_unit(u)
|
def display_death_unit(u)
|
||||||
death_info = u.counters.death_tg
|
death_info = u.counters.death_tg
|
||||||
killer = death_info.killer_tg if death_info
|
killer = death_info.killer_tg if death_info
|
||||||
|
|
||||||
str = "The #{u.race_tg.name[0]}"
|
str = "The #{u.race_tg.name[0]}"
|
||||||
str << " #{u.name}" if u.name.has_name
|
str << " #{u.name}" if u.name.has_name
|
||||||
str << " died"
|
str << " died"
|
||||||
str << " in year #{death_info.event_year}" if death_info
|
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 << " (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
|
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
|
||||||
|
|
||||||
puts str.chomp(',') + '.'
|
puts str.chomp(',') + '.'
|
||||||
end
|
end
|
||||||
|
|
||||||
item = df.item_find(:selected)
|
item = df.item_find(:selected)
|
||||||
unit = df.unit_find(:selected)
|
unit = df.unit_find(:selected)
|
||||||
|
|
||||||
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
|
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
|
||||||
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
|
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
|
||||||
end
|
end
|
||||||
|
|
||||||
if item and item.kind_of?(DFHack::ItemBodyComponent)
|
if item and item.kind_of?(DFHack::ItemBodyComponent)
|
||||||
hf = item.hist_figure_id
|
hf = item.hist_figure_id
|
||||||
elsif unit
|
elsif unit
|
||||||
hf = unit.hist_figure_id
|
hf = unit.hist_figure_id
|
||||||
end
|
end
|
||||||
|
|
||||||
if not hf
|
if not hf
|
||||||
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
|
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
|
||||||
|
|
||||||
elsif hf == -1
|
elsif hf == -1
|
||||||
if unit ||= item.unit_tg
|
if unit ||= item.unit_tg
|
||||||
display_death_unit(unit)
|
display_death_unit(unit)
|
||||||
else
|
else
|
||||||
puts "Not a historical figure, cannot death find info"
|
puts "Not a historical figure, cannot death find info"
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
histfig = df.world.history.figures.binsearch(hf)
|
histfig = df.world.history.figures.binsearch(hf)
|
||||||
unit = histfig ? df.unit_find(histfig.unit_id) : nil
|
unit = histfig ? df.unit_find(histfig.unit_id) : nil
|
||||||
if unit and not unit.flags1.dead and not unit.flags3.ghostly
|
if unit and not unit.flags1.dead and not unit.flags3.ghostly
|
||||||
puts "#{unit.name} is not dead yet !"
|
puts "#{unit.name} is not dead yet !"
|
||||||
|
|
||||||
else
|
else
|
||||||
events = df.world.history.events
|
events = df.world.history.events
|
||||||
(0...events.length).reverse_each { |i|
|
(0...events.length).reverse_each { |i|
|
||||||
e = events[i]
|
e = events[i]
|
||||||
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
|
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
|
||||||
display_death_event(e)
|
display_death_event(e)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
end
|
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
|
# designate an area for digging according to a plan in csv format
|
||||||
|
|
||||||
raise "usage: digfort <plan filename>" if not $script_args[0]
|
raise "usage: digfort <plan filename>" if not $script_args[0]
|
||||||
planfile = File.read($script_args[0])
|
planfile = File.read($script_args[0])
|
||||||
|
|
||||||
if df.cursor.x == -30000
|
if df.cursor.x == -30000
|
||||||
raise "place the game cursor to the top-left corner of the design"
|
raise "place the game cursor to the top-left corner of the design"
|
||||||
end
|
end
|
||||||
|
|
||||||
tiles = planfile.lines.map { |l|
|
tiles = planfile.lines.map { |l|
|
||||||
l.sub(/#.*/, '').split(';').map { |t| t.strip }
|
l.sub(/#.*/, '').split(';').map { |t| t.strip }
|
||||||
}
|
}
|
||||||
|
|
||||||
x = x0 = df.cursor.x
|
x = x0 = df.cursor.x
|
||||||
y = df.cursor.y
|
y = df.cursor.y
|
||||||
z = df.cursor.z
|
z = df.cursor.z
|
||||||
|
|
||||||
tiles.each { |line|
|
tiles.each { |line|
|
||||||
next if line.empty? or line == ['']
|
next if line.empty? or line == ['']
|
||||||
line.each { |tile|
|
line.each { |tile|
|
||||||
t = df.map_tile_at(x, y, z)
|
t = df.map_tile_at(x, y, z)
|
||||||
s = t.shape_basic
|
s = t.shape_basic
|
||||||
case tile
|
case tile
|
||||||
when 'd'; t.dig(:Default) if s == :Wall
|
when 'd'; t.dig(:Default) if s == :Wall
|
||||||
when 'u'; t.dig(:UpStair) if s == :Wall
|
when 'u'; t.dig(:UpStair) if s == :Wall
|
||||||
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
|
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
|
||||||
when 'i'; t.dig(:UpDownStair) if s == :Wall
|
when 'i'; t.dig(:UpDownStair) if s == :Wall
|
||||||
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
|
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
|
||||||
when 'r'; t.dig(:Ramp) if s == :Wall
|
when 'r'; t.dig(:Ramp) if s == :Wall
|
||||||
when 'x'; t.dig(:No)
|
when 'x'; t.dig(:No)
|
||||||
end
|
end
|
||||||
x += 1
|
x += 1
|
||||||
}
|
}
|
||||||
x = x0
|
x = x0
|
||||||
y += 1
|
y += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
puts 'done'
|
puts 'done'
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
# remove all aquifers from the map
|
# remove all aquifers from the map
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
df.each_map_block { |b|
|
df.each_map_block { |b|
|
||||||
if b.designation[0][0].water_table or b.designation[15][15].water_table
|
if b.designation[0][0].water_table or b.designation[8][8].water_table
|
||||||
count += 1
|
count += 1
|
||||||
b.designation.each { |dx| dx.each { |dy| dy.water_table = false } }
|
df.each_map_block_z(b.map_pos.z) { |bz|
|
||||||
end
|
bz.designation.each { |dx| dx.each { |dy| dy.water_table = false } }
|
||||||
}
|
}
|
||||||
|
end
|
||||||
puts "cleared #{count} map blocks"
|
}
|
||||||
|
|
||||||
|
puts "cleared #{count} aquifer#{'s' if count > 1}"
|
||||||
|
@ -1,93 +1,135 @@
|
|||||||
# slay all creatures of a given race
|
# exterminate creatures
|
||||||
|
|
||||||
# race = name of the race to eradicate, use 'him' to target only the selected creature
|
# race = name of the race to eradicate, use 'him' to target only the selected creature
|
||||||
# use 'undead' to target all undeads
|
# use 'undead' to target all undeads
|
||||||
race = $script_args[0]
|
race = $script_args[0]
|
||||||
|
|
||||||
# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death
|
# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death
|
||||||
magma = ($script_args[1] == 'magma')
|
# if it is 'butcher' mark all units for butchering (wont work with hostiles)
|
||||||
|
kill_by = $script_args[1]
|
||||||
checkunit = lambda { |u|
|
|
||||||
(u.body.blood_count != 0 or u.body.blood_max == 0) and
|
case kill_by
|
||||||
not u.flags1.dead and
|
when 'magma'
|
||||||
not u.flags1.caged and not u.flags1.chained and
|
slain = 'burning'
|
||||||
#not u.flags1.hidden_in_ambush and
|
when 'slaughter', 'butcher'
|
||||||
not df.map_designation_at(u).hidden
|
slain = 'marked for butcher'
|
||||||
}
|
when nil
|
||||||
|
slain = 'slain'
|
||||||
slayit = lambda { |u|
|
else
|
||||||
if not magma
|
race = 'help'
|
||||||
# just make them drop dead
|
end
|
||||||
u.body.blood_count = 0
|
|
||||||
# some races dont mind having no blood, ensure they are still taken care of.
|
checkunit = lambda { |u|
|
||||||
u.animal.vanish_countdown = 2
|
(u.body.blood_count != 0 or u.body.blood_max == 0) and
|
||||||
else
|
not u.flags1.dead and
|
||||||
# it's getting hot around here
|
not u.flags1.caged and not u.flags1.chained and
|
||||||
# !!WARNING!! do not call on a magma-safe creature
|
#not u.flags1.hidden_in_ambush and
|
||||||
ouh = df.onupdate_register("slayrace ensure #{u.id}", 1) {
|
not df.map_designation_at(u).hidden
|
||||||
if u.flags1.dead
|
}
|
||||||
df.onupdate_unregister(ouh)
|
|
||||||
else
|
slayit = lambda { |u|
|
||||||
x, y, z = u.pos.x, u.pos.y, u.pos.z
|
case kill_by
|
||||||
z += 1 while tile = df.map_tile_at(x, y, z+1) and
|
when 'magma'
|
||||||
tile.shape_passableflow and tile.shape_passablelow
|
# it's getting hot around here
|
||||||
df.map_tile_at(x, y, z).spawn_magma(7)
|
# !!WARNING!! do not call on a magma-safe creature
|
||||||
end
|
ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) {
|
||||||
}
|
if u.flags1.dead
|
||||||
end
|
df.onupdate_unregister(ouh)
|
||||||
}
|
else
|
||||||
|
x, y, z = u.pos.x, u.pos.y, u.pos.z
|
||||||
all_races = Hash.new(0)
|
z += 1 while tile = df.map_tile_at(x, y, z+1) and
|
||||||
|
tile.shape_passableflow and tile.shape_passablelow
|
||||||
df.world.units.active.map { |u|
|
df.map_tile_at(x, y, z).spawn_magma(7)
|
||||||
if checkunit[u]
|
end
|
||||||
if (u.enemy.undead or
|
}
|
||||||
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
|
when 'butcher', 'slaughter'
|
||||||
u.curse.rem_tags1.OPPOSED_TO_LIFE))
|
# mark for slaughter at butcher's shop
|
||||||
all_races['Undead'] += 1
|
u.flags2.slaughter = true
|
||||||
else
|
else
|
||||||
all_races[u.race_tg.creature_id] += 1
|
# just make them drop dead
|
||||||
end
|
u.body.blood_count = 0
|
||||||
end
|
# some races dont mind having no blood, ensure they are still taken care of.
|
||||||
}
|
u.animal.vanish_countdown = 2
|
||||||
|
end
|
||||||
case race
|
}
|
||||||
when nil
|
|
||||||
all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" }
|
all_races = Hash.new(0)
|
||||||
|
|
||||||
when 'him'
|
df.world.units.active.map { |u|
|
||||||
if him = df.unit_find
|
if checkunit[u]
|
||||||
slayit[him]
|
if (u.enemy.undead or
|
||||||
else
|
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
|
||||||
puts "Select a target ingame"
|
u.curse.rem_tags1.OPPOSED_TO_LIFE))
|
||||||
end
|
all_races['Undead'] += 1
|
||||||
|
else
|
||||||
when /^undead/i
|
all_races[u.race_tg.creature_id] += 1
|
||||||
count = 0
|
end
|
||||||
df.world.units.active.each { |u|
|
end
|
||||||
if (u.enemy.undead or
|
}
|
||||||
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
|
|
||||||
u.curse.rem_tags1.OPPOSED_TO_LIFE)) and
|
case race
|
||||||
checkunit[u]
|
when nil
|
||||||
slayit[u]
|
all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" }
|
||||||
count += 1
|
|
||||||
end
|
when 'help', '?'
|
||||||
}
|
puts <<EOS
|
||||||
puts "slain #{count} undeads"
|
Kills all creatures of a given race.
|
||||||
|
With no argument, lists possible targets with their head count.
|
||||||
else
|
With the special argument 'him' or 'her', kill only the currently selected creature.
|
||||||
raw_race = df.match_rawname(race, all_races.keys)
|
With the special argument 'undead', kill all undead creatures/thralls.
|
||||||
raise 'invalid race' if not raw_race
|
|
||||||
|
The targets will bleed out on the next game tick, or if they are immune to that, will vanish in a puff of smoke.
|
||||||
race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race }
|
|
||||||
|
The special final argument 'magma' will make magma rain on the targets instead.
|
||||||
count = 0
|
The special final argument 'butcher' will mark the targets for butchering instead.
|
||||||
df.world.units.active.each { |u|
|
|
||||||
if u.race == race_nr and checkunit[u]
|
Ex: exterminate gob
|
||||||
slayit[u]
|
exterminate elve magma
|
||||||
count += 1
|
exterminate him
|
||||||
end
|
exterminate pig butcher
|
||||||
}
|
EOS
|
||||||
puts "slain #{count} #{raw_race}"
|
|
||||||
|
when 'him', 'her', 'it', 'that'
|
||||||
end
|
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
|
# script to fix loyalty cascade, when you order your militia to kill friendly units
|
||||||
|
|
||||||
def fixunit(unit)
|
def fixunit(unit)
|
||||||
return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id
|
return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id
|
||||||
links = unit.hist_figure_tg.entity_links
|
links = unit.hist_figure_tg.entity_links
|
||||||
fixed = false
|
fixed = false
|
||||||
|
|
||||||
# check if the unit is a civ renegade
|
# check if the unit is a civ renegade
|
||||||
if i1 = links.index { |l|
|
if i1 = links.index { |l|
|
||||||
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
|
||||||
l.entity_id == df.ui.civ_id
|
l.entity_id == df.ui.civ_id
|
||||||
} and i2 = links.index { |l|
|
} and i2 = links.index { |l|
|
||||||
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
|
||||||
l.entity_id == df.ui.civ_id
|
l.entity_id == df.ui.civ_id
|
||||||
}
|
}
|
||||||
fixed = true
|
fixed = true
|
||||||
i1, i2 = i2, i1 if i1 > i2
|
i1, i2 = i2, i1 if i1 > i2
|
||||||
links.delete_at i2
|
links.delete_at i2
|
||||||
links.delete_at i1
|
links.delete_at i1
|
||||||
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100)
|
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"
|
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again"
|
||||||
end
|
end
|
||||||
|
|
||||||
# check if the unit is a group renegade
|
# check if the unit is a group renegade
|
||||||
if i1 = links.index { |l|
|
if i1 = links.index { |l|
|
||||||
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
|
||||||
l.entity_id == df.ui.group_id
|
l.entity_id == df.ui.group_id
|
||||||
} and i2 = links.index { |l|
|
} and i2 = links.index { |l|
|
||||||
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
|
||||||
l.entity_id == df.ui.group_id
|
l.entity_id == df.ui.group_id
|
||||||
}
|
}
|
||||||
fixed = true
|
fixed = true
|
||||||
i1, i2 = i2, i1 if i1 > i2
|
i1, i2 = i2, i1 if i1 > i2
|
||||||
links.delete_at i2
|
links.delete_at i2
|
||||||
links.delete_at i1
|
links.delete_at i1
|
||||||
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100)
|
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"
|
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again"
|
||||||
end
|
end
|
||||||
|
|
||||||
# fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed)
|
# 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
|
if fixed and unit.enemy.enemy_status_slot != -1
|
||||||
i = unit.enemy.enemy_status_slot
|
i = unit.enemy.enemy_status_slot
|
||||||
unit.enemy.enemy_status_slot = -1
|
unit.enemy.enemy_status_slot = -1
|
||||||
cache = df.world.enemy_status_cache
|
cache = df.world.enemy_status_cache
|
||||||
cache.slot_used[i] = false
|
cache.slot_used[i] = false
|
||||||
cache.rel_map[i].map! { -1 }
|
cache.rel_map[i].map! { -1 }
|
||||||
cache.rel_map.each { |a| a[i] = -1 }
|
cache.rel_map.each { |a| a[i] = -1 }
|
||||||
cache.next_slot = i if cache.next_slot > i
|
cache.next_slot = i if cache.next_slot > i
|
||||||
end
|
end
|
||||||
|
|
||||||
# return true if we actually fixed the unit
|
# return true if we actually fixed the unit
|
||||||
fixed
|
fixed
|
||||||
end
|
end
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
df.unit_citizens.each { |u|
|
df.unit_citizens.each { |u|
|
||||||
count += 1 if fixunit(u)
|
count += 1 if fixunit(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
if count > 0
|
if count > 0
|
||||||
puts "loyalty cascade fixed (#{count} dwarves)"
|
puts "loyalty cascade fixed (#{count} dwarves)"
|
||||||
else
|
else
|
||||||
puts "no loyalty cascade found"
|
puts "no loyalty cascade found"
|
||||||
end
|
end
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
# fix doors that are frozen in 'open' state
|
# fix doors that are frozen in 'open' state
|
||||||
|
|
||||||
# door is stuck in open state if the map occupancy flag incorrectly indicates
|
# this may happen after people mess with the game by (incorrectly) teleporting units or items
|
||||||
# that an unit is present (and creatures will prone to pass through)
|
# a door may stick open if the map occupancy flags are wrong
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
df.world.buildings.all.each { |bld|
|
df.world.buildings.all.each { |bld|
|
||||||
# for all doors
|
# for all doors
|
||||||
next if bld._rtti_classname != :building_doorst
|
next if bld._rtti_classname != :building_doorst
|
||||||
# check if it is open
|
# check if it is open
|
||||||
next if bld.close_timer == 0
|
next if bld.close_timer == 0
|
||||||
# check if occupancy is set
|
# check if occupancy is set
|
||||||
occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z)
|
occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z)
|
||||||
next if not occ.unit
|
if (occ.unit or occ.unit_grounded) and not
|
||||||
# check if an unit is present
|
# 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 }
|
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
|
count += 1
|
||||||
occ.unit = false
|
occ.unit = occ.unit_grounded = false
|
||||||
}
|
end
|
||||||
puts "unstuck #{count} doors"
|
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
|
# grow crops in farm plots. ex: growcrops helmet_plump 20
|
||||||
|
|
||||||
material = $script_args[0]
|
material = $script_args[0]
|
||||||
count_max = $script_args[1].to_i
|
count_max = $script_args[1].to_i
|
||||||
count_max = 100 if count_max == 0
|
count_max = 100 if count_max == 0
|
||||||
|
|
||||||
# cache information from the raws
|
# cache information from the raws
|
||||||
@raws_plant_name ||= {}
|
@raws_plant_name ||= {}
|
||||||
@raws_plant_growdur ||= {}
|
@raws_plant_growdur ||= {}
|
||||||
if @raws_plant_name.empty?
|
if @raws_plant_name.empty?
|
||||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||||
@raws_plant_name[idx] = p.id
|
@raws_plant_name[idx] = p.id
|
||||||
@raws_plant_growdur[idx] = p.growdur
|
@raws_plant_growdur[idx] = p.growdur
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
inventory = Hash.new(0)
|
inventory = Hash.new(0)
|
||||||
df.world.items.other[:SEEDS].each { |seed|
|
df.world.items.other[:SEEDS].each { |seed|
|
||||||
next if not seed.flags.in_building
|
next if not seed.flags.in_building
|
||||||
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
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]
|
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||||
inventory[seed.mat_index] += 1
|
inventory[seed.mat_index] += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if !material or material == 'help' or material == 'list'
|
if !material or material == 'help' or material == 'list'
|
||||||
# show a list of available crop types
|
# show a list of available crop types
|
||||||
inventory.sort_by { |mat, c| c }.each { |mat, c|
|
inventory.sort_by { |mat, c| c }.each { |mat, c|
|
||||||
name = df.world.raws.plants.all[mat].id
|
name = df.world.raws.plants.all[mat].id
|
||||||
puts " #{name} #{c}"
|
puts " #{name} #{c}"
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] })
|
mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] })
|
||||||
unless wantmat = @raws_plant_name.index(mat)
|
unless wantmat = @raws_plant_name.index(mat)
|
||||||
raise "invalid plant material #{material}"
|
raise "invalid plant material #{material}"
|
||||||
end
|
end
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
df.world.items.other[:SEEDS].each { |seed|
|
df.world.items.other[:SEEDS].each { |seed|
|
||||||
next if seed.mat_index != wantmat
|
next if seed.mat_index != wantmat
|
||||||
next if not seed.flags.in_building
|
next if not seed.flags.in_building
|
||||||
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
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]
|
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||||
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
|
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
|
||||||
count += 1
|
count += 1
|
||||||
}
|
}
|
||||||
puts "Grown #{count} #{mat}"
|
puts "Grown #{count} #{mat}"
|
||||||
end
|
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
|
# control your levers from the dfhack console
|
||||||
|
|
||||||
def lever_pull_job(bld)
|
def lever_pull_job(bld)
|
||||||
ref = DFHack::GeneralRefBuildingHolderst.cpp_new
|
ref = DFHack::GeneralRefBuildingHolderst.cpp_new
|
||||||
ref.building_id = bld.id
|
ref.building_id = bld.id
|
||||||
|
|
||||||
job = DFHack::Job.cpp_new
|
job = DFHack::Job.cpp_new
|
||||||
job.job_type = :PullLever
|
job.job_type = :PullLever
|
||||||
job.pos = [bld.centerx, bld.centery, bld.z]
|
job.pos = [bld.centerx, bld.centery, bld.z]
|
||||||
job.general_refs << ref
|
job.general_refs << ref
|
||||||
bld.jobs << job
|
bld.jobs << job
|
||||||
df.job_link job
|
df.job_link job
|
||||||
|
|
||||||
puts lever_descr(bld)
|
puts lever_descr(bld)
|
||||||
end
|
end
|
||||||
|
|
||||||
def lever_pull_cheat(bld)
|
def lever_pull_cheat(bld)
|
||||||
bld.linked_mechanisms.each { |i|
|
bld.linked_mechanisms.each { |i|
|
||||||
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r|
|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r|
|
||||||
r.building_tg.setTriggerState(bld.state)
|
r.building_tg.setTriggerState(bld.state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bld.state = (bld.state == 0 ? 1 : 0)
|
bld.state = (bld.state == 0 ? 1 : 0)
|
||||||
|
|
||||||
puts lever_descr(bld)
|
puts lever_descr(bld)
|
||||||
end
|
end
|
||||||
|
|
||||||
def lever_descr(bld, idx=nil)
|
def lever_descr(bld, idx=nil)
|
||||||
ret = []
|
ret = []
|
||||||
|
|
||||||
# lever description
|
# lever description
|
||||||
descr = ''
|
descr = ''
|
||||||
descr << "#{idx}: " if idx
|
descr << "#{idx}: " if idx
|
||||||
descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}"
|
descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}"
|
||||||
bld.jobs.each { |j|
|
bld.jobs.each { |j|
|
||||||
if j.job_type == :PullLever
|
if j.job_type == :PullLever
|
||||||
flags = ''
|
flags = ''
|
||||||
flags << ', repeat' if j.flags.repeat
|
flags << ', repeat' if j.flags.repeat
|
||||||
flags << ', suspended' if j.flags.suspend
|
flags << ', suspended' if j.flags.suspend
|
||||||
descr << " (pull order#{flags})"
|
descr << " (pull order#{flags})"
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
bld.linked_mechanisms.map { |i|
|
bld.linked_mechanisms.map { |i|
|
||||||
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst)
|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst)
|
||||||
}.flatten.each { |r|
|
}.flatten.each { |r|
|
||||||
# linked building description
|
# linked building description
|
||||||
tg = r.building_tg
|
tg = r.building_tg
|
||||||
state = ''
|
state = ''
|
||||||
if tg.respond_to?(:gate_flags)
|
if tg.respond_to?(:gate_flags)
|
||||||
state << (tg.gate_flags.closed ? 'closed' : 'opened')
|
state << (tg.gate_flags.closed ? 'closed' : 'opened')
|
||||||
state << ", closing (#{tg.timer})" if tg.gate_flags.closing
|
state << ", closing (#{tg.timer})" if tg.gate_flags.closing
|
||||||
state << ", opening (#{tg.timer})" if tg.gate_flags.opening
|
state << ", opening (#{tg.timer})" if tg.gate_flags.opening
|
||||||
end
|
end
|
||||||
|
|
||||||
ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}")
|
ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}")
|
||||||
|
|
||||||
# indent other links
|
# indent other links
|
||||||
descr = descr.gsub(/./, ' ')
|
descr = descr.gsub(/./, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
ret << descr if ret.empty?
|
ret << descr if ret.empty?
|
||||||
|
|
||||||
ret
|
ret
|
||||||
end
|
end
|
||||||
|
|
||||||
def lever_list
|
def lever_list
|
||||||
@lever_list = []
|
@lever_list = []
|
||||||
df.world.buildings.other[:TRAP].find_all { |bld|
|
df.world.buildings.other[:TRAP].find_all { |bld|
|
||||||
bld.trap_type == :Lever
|
bld.trap_type == :Lever
|
||||||
}.sort_by { |bld| bld.id }.each { |bld|
|
}.sort_by { |bld| bld.id }.each { |bld|
|
||||||
puts lever_descr(bld, @lever_list.length)
|
puts lever_descr(bld, @lever_list.length)
|
||||||
@lever_list << bld.id
|
@lever_list << bld.id
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
case $script_args[0]
|
case $script_args[0]
|
||||||
when 'pull'
|
when 'pull'
|
||||||
cheat = $script_args.delete('--cheat') || $script_args.delete('--now')
|
cheat = $script_args.delete('--cheat') || $script_args.delete('--now')
|
||||||
|
|
||||||
id = $script_args[1].to_i
|
id = $script_args[1].to_i
|
||||||
id = @lever_list[id] || id
|
id = @lever_list[id] || id
|
||||||
bld = df.building_find(id)
|
bld = df.building_find(id)
|
||||||
raise 'invalid lever id' if not bld
|
raise 'invalid lever id' if not bld
|
||||||
|
|
||||||
if cheat
|
if cheat
|
||||||
lever_pull_cheat(bld)
|
lever_pull_cheat(bld)
|
||||||
else
|
else
|
||||||
lever_pull_job(bld)
|
lever_pull_job(bld)
|
||||||
end
|
end
|
||||||
|
|
||||||
when 'list'
|
when 'list'
|
||||||
lever_list
|
lever_list
|
||||||
|
|
||||||
when /^\d+$/
|
when /^\d+$/
|
||||||
id = $script_args[0].to_i
|
id = $script_args[0].to_i
|
||||||
id = @lever_list[id] || id
|
id = @lever_list[id] || id
|
||||||
bld = df.building_find(id)
|
bld = df.building_find(id)
|
||||||
raise 'invalid lever id' if not bld
|
raise 'invalid lever id' if not bld
|
||||||
|
|
||||||
puts lever_descr(bld)
|
puts lever_descr(bld)
|
||||||
|
|
||||||
else
|
else
|
||||||
puts <<EOS
|
puts <<EOS
|
||||||
Lever control from the dfhack console
|
Lever control from the dfhack console
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
lever list
|
lever list
|
||||||
shows the list of levers in the fortress, with their id and links
|
shows the list of levers in the fortress, with their id and links
|
||||||
|
|
||||||
lever pull 42
|
lever pull 42
|
||||||
order the dwarves to pull lever 42
|
order the dwarves to pull lever 42
|
||||||
|
|
||||||
lever pull 42 --cheat
|
lever pull 42 --cheat
|
||||||
magically pull lever 42 immediately
|
magically pull lever 42 immediately
|
||||||
EOS
|
EOS
|
||||||
|
|
||||||
end
|
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
|
# remove bad thoughts for the selected unit or the whole fort
|
||||||
|
|
||||||
dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n')
|
dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n')
|
||||||
|
|
||||||
$script_args << 'all' if dry_run and $script_args.empty?
|
$script_args << 'all' if dry_run and $script_args.empty?
|
||||||
|
|
||||||
seenbad = Hash.new(0)
|
seenbad = Hash.new(0)
|
||||||
|
|
||||||
clear_mind = lambda { |u|
|
clear_mind = lambda { |u|
|
||||||
u.status.recent_events.each { |e|
|
u.status.recent_events.each { |e|
|
||||||
next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-'
|
next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-'
|
||||||
seenbad[e.type] += 1
|
seenbad[e.type] += 1
|
||||||
e.age = 0x1000_0000 unless dry_run
|
e.age = 0x1000_0000 unless dry_run
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
summary = lambda {
|
summary = lambda {
|
||||||
seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt|
|
seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt|
|
||||||
puts " #{thought} #{cnt}"
|
puts " #{thought} #{cnt}"
|
||||||
}
|
}
|
||||||
count = seenbad.values.inject(0) { |sum, cnt| sum+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
|
puts "Removed #{count} bad thought#{'s' if count != 1}." if count > 0 and not dry_run
|
||||||
}
|
}
|
||||||
|
|
||||||
case $script_args[0]
|
case $script_args[0]
|
||||||
when 'him'
|
when 'him'
|
||||||
if u = df.unit_find
|
if u = df.unit_find
|
||||||
clear_mind[u]
|
clear_mind[u]
|
||||||
summary[]
|
summary[]
|
||||||
else
|
else
|
||||||
puts 'Please select a dwarf ingame'
|
puts 'Please select a dwarf ingame'
|
||||||
end
|
end
|
||||||
|
|
||||||
when 'all'
|
when 'all'
|
||||||
df.unit_citizens.each { |uu|
|
df.unit_citizens.each { |uu|
|
||||||
clear_mind[uu]
|
clear_mind[uu]
|
||||||
}
|
}
|
||||||
summary[]
|
summary[]
|
||||||
|
|
||||||
else
|
else
|
||||||
puts "Usage: removebadthoughts [--dry-run] <him|all>"
|
puts "Usage: removebadthoughts [--dry-run] <him|all>"
|
||||||
|
|
||||||
end
|
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.
|
# mark stuff inside of cages for dumping.
|
||||||
|
|
||||||
def plural(nr, name)
|
def plural(nr, name)
|
||||||
# '1 cage' / '4 cages'
|
# '1 cage' / '4 cages'
|
||||||
"#{nr} #{name}#{'s' if nr > 1}"
|
"#{nr} #{name}#{'s' if nr > 1}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def cage_dump_items(list)
|
def cage_dump_items(list)
|
||||||
count = 0
|
count = 0
|
||||||
count_cage = 0
|
count_cage = 0
|
||||||
list.each { |cage|
|
list.each { |cage|
|
||||||
pre_count = count
|
pre_count = count
|
||||||
cage.general_refs.each { |ref|
|
cage.general_refs.each { |ref|
|
||||||
next unless ref.kind_of?(DFHack::GeneralRefContainsItemst)
|
next unless ref.kind_of?(DFHack::GeneralRefContainsItemst)
|
||||||
next if ref.item_tg.flags.dump
|
next if ref.item_tg.flags.dump
|
||||||
count += 1
|
count += 1
|
||||||
ref.item_tg.flags.dump = true
|
ref.item_tg.flags.dump = true
|
||||||
}
|
}
|
||||||
count_cage += 1 if pre_count != count
|
count_cage += 1 if pre_count != count
|
||||||
}
|
}
|
||||||
|
|
||||||
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
|
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def cage_dump_armor(list)
|
def cage_dump_armor(list)
|
||||||
count = 0
|
count = 0
|
||||||
count_cage = 0
|
count_cage = 0
|
||||||
list.each { |cage|
|
list.each { |cage|
|
||||||
pre_count = count
|
pre_count = count
|
||||||
cage.general_refs.each { |ref|
|
cage.general_refs.each { |ref|
|
||||||
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
|
||||||
ref.unit_tg.inventory.each { |it|
|
ref.unit_tg.inventory.each { |it|
|
||||||
next if it.mode != :Worn
|
next if it.mode != :Worn
|
||||||
next if it.item.flags.dump
|
next if it.item.flags.dump
|
||||||
count += 1
|
count += 1
|
||||||
it.item.flags.dump = true
|
it.item.flags.dump = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count_cage += 1 if pre_count != count
|
count_cage += 1 if pre_count != count
|
||||||
}
|
}
|
||||||
|
|
||||||
puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}"
|
puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def cage_dump_weapons(list)
|
def cage_dump_weapons(list)
|
||||||
count = 0
|
count = 0
|
||||||
count_cage = 0
|
count_cage = 0
|
||||||
list.each { |cage|
|
list.each { |cage|
|
||||||
pre_count = count
|
pre_count = count
|
||||||
cage.general_refs.each { |ref|
|
cage.general_refs.each { |ref|
|
||||||
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
|
||||||
ref.unit_tg.inventory.each { |it|
|
ref.unit_tg.inventory.each { |it|
|
||||||
next if it.mode != :Weapon
|
next if it.mode != :Weapon
|
||||||
next if it.item.flags.dump
|
next if it.item.flags.dump
|
||||||
count += 1
|
count += 1
|
||||||
it.item.flags.dump = true
|
it.item.flags.dump = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count_cage += 1 if pre_count != count
|
count_cage += 1 if pre_count != count
|
||||||
}
|
}
|
||||||
|
|
||||||
puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}"
|
puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def cage_dump_all(list)
|
def cage_dump_all(list)
|
||||||
count = 0
|
count = 0
|
||||||
count_cage = 0
|
count_cage = 0
|
||||||
list.each { |cage|
|
list.each { |cage|
|
||||||
pre_count = count
|
pre_count = count
|
||||||
cage.general_refs.each { |ref|
|
cage.general_refs.each { |ref|
|
||||||
case ref
|
case ref
|
||||||
when DFHack::GeneralRefContainsItemst
|
when DFHack::GeneralRefContainsItemst
|
||||||
next if ref.item_tg.flags.dump
|
next if ref.item_tg.flags.dump
|
||||||
count += 1
|
count += 1
|
||||||
ref.item_tg.flags.dump = true
|
ref.item_tg.flags.dump = true
|
||||||
when DFHack::GeneralRefContainsUnitst
|
when DFHack::GeneralRefContainsUnitst
|
||||||
ref.unit_tg.inventory.each { |it|
|
ref.unit_tg.inventory.each { |it|
|
||||||
next if it.item.flags.dump
|
next if it.item.flags.dump
|
||||||
count += 1
|
count += 1
|
||||||
it.item.flags.dump = true
|
it.item.flags.dump = true
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
count_cage += 1 if pre_count != count
|
count_cage += 1 if pre_count != count
|
||||||
}
|
}
|
||||||
|
|
||||||
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
|
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def cage_dump_list(list)
|
def cage_dump_list(list)
|
||||||
count_total = Hash.new(0)
|
count_total = Hash.new(0)
|
||||||
empty_cages = 0
|
empty_cages = 0
|
||||||
list.each { |cage|
|
list.each { |cage|
|
||||||
count = Hash.new(0)
|
count = Hash.new(0)
|
||||||
|
|
||||||
cage.general_refs.each { |ref|
|
cage.general_refs.each { |ref|
|
||||||
case ref
|
case ref
|
||||||
when DFHack::GeneralRefContainsItemst
|
when DFHack::GeneralRefContainsItemst
|
||||||
count[ref.item_tg._rtti_classname] += 1
|
count[ref.item_tg._rtti_classname] += 1
|
||||||
when DFHack::GeneralRefContainsUnitst
|
when DFHack::GeneralRefContainsUnitst
|
||||||
ref.unit_tg.inventory.each { |it|
|
ref.unit_tg.inventory.each { |it|
|
||||||
count[it.item._rtti_classname] += 1
|
count[it.item._rtti_classname] += 1
|
||||||
}
|
}
|
||||||
# TODO vermin ?
|
# TODO vermin ?
|
||||||
else
|
else
|
||||||
puts "unhandled ref #{ref.inspect}" if $DEBUG
|
puts "unhandled ref #{ref.inspect}" if $DEBUG
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
type = case cage
|
type = case cage
|
||||||
when DFHack::ItemCagest; 'Cage'
|
when DFHack::ItemCagest; 'Cage'
|
||||||
when DFHack::ItemAnimaltrapst; 'Animal trap'
|
when DFHack::ItemAnimaltrapst; 'Animal trap'
|
||||||
else cage._rtti_classname
|
else cage._rtti_classname
|
||||||
end
|
end
|
||||||
|
|
||||||
if count.empty?
|
if count.empty?
|
||||||
empty_cages += 1
|
empty_cages += 1
|
||||||
else
|
else
|
||||||
puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
|
puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
|
||||||
end
|
end
|
||||||
|
|
||||||
count.each { |k, v| count_total[k] += v }
|
count.each { |k, v| count_total[k] += v }
|
||||||
}
|
}
|
||||||
|
|
||||||
if list.length > 2
|
if list.length > 2
|
||||||
puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
|
puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
|
||||||
puts "with #{plural(empty_cages, 'empty cage')}"
|
puts "with #{plural(empty_cages, 'empty cage')}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# handle magic script arguments
|
# handle magic script arguments
|
||||||
here_only = $script_args.delete 'here'
|
here_only = $script_args.delete 'here'
|
||||||
if here_only
|
if here_only
|
||||||
it = df.item_find
|
it = df.item_find
|
||||||
list = [it]
|
list = [it]
|
||||||
if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
|
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) }
|
list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) }
|
||||||
end
|
end
|
||||||
if list.empty?
|
if list.empty?
|
||||||
puts 'Please select a cage'
|
puts 'Please select a cage'
|
||||||
throw :script_finished
|
throw :script_finished
|
||||||
end
|
end
|
||||||
|
|
||||||
elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first
|
elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first
|
||||||
list = []
|
list = []
|
||||||
ids.each { |id|
|
ids.each { |id|
|
||||||
$script_args.delete id
|
$script_args.delete id
|
||||||
if not it = df.item_find(id.to_i)
|
if not it = df.item_find(id.to_i)
|
||||||
puts "Invalid item id #{id}"
|
puts "Invalid item id #{id}"
|
||||||
elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
|
elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
|
||||||
puts "Item ##{id} is not a cage"
|
puts "Item ##{id} is not a cage"
|
||||||
list << it
|
list << it
|
||||||
else
|
else
|
||||||
list << it
|
list << it
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
if list.empty?
|
if list.empty?
|
||||||
puts 'Please use a valid cage id'
|
puts 'Please use a valid cage id'
|
||||||
throw :script_finished
|
throw :script_finished
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
list = df.world.items.other[:ANY_CAGE_OR_TRAP]
|
list = df.world.items.other[:ANY_CAGE_OR_TRAP]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# act
|
# act
|
||||||
case $script_args[0]
|
case $script_args[0]
|
||||||
when /^it/i
|
when /^it/i
|
||||||
cage_dump_items(list)
|
cage_dump_items(list)
|
||||||
when /^arm/i
|
when /^arm/i
|
||||||
cage_dump_armor(list)
|
cage_dump_armor(list)
|
||||||
when /^wea/i
|
when /^wea/i
|
||||||
cage_dump_weapons(list)
|
cage_dump_weapons(list)
|
||||||
when 'all'
|
when 'all'
|
||||||
cage_dump_all(list)
|
cage_dump_all(list)
|
||||||
when 'list'
|
when 'list'
|
||||||
cage_dump_list(list)
|
cage_dump_list(list)
|
||||||
else
|
else
|
||||||
puts <<EOS
|
puts <<EOS
|
||||||
Marks items inside all cages for dumping.
|
Marks items inside all cages for dumping.
|
||||||
Add 'here' to dump stuff only for selected cage.
|
Add 'here' to dump stuff only for selected cage.
|
||||||
Add a cage id to dump stuff for this cage only.
|
Add a cage id to dump stuff for this cage only.
|
||||||
|
|
||||||
See 'autodump' to actually dump stuff.
|
See 'autodump' to actually dump stuff.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
stripcaged items
|
stripcaged items
|
||||||
dump items directly in cages (eg seeds after training)
|
dump items directly in cages (eg seeds after training)
|
||||||
|
|
||||||
stripcaged [armor|weapons] here
|
stripcaged [armor|weapons] here
|
||||||
dump armor or weapons of caged creatures in selected cage
|
dump armor or weapons of caged creatures in selected cage
|
||||||
|
|
||||||
stripcaged all 28 29
|
stripcaged all 28 29
|
||||||
dump every item in cage id 28 and 29, along with every item worn by creatures in there too
|
dump every item in cage id 28 and 29, along with every item worn by creatures in there too
|
||||||
|
|
||||||
stripcaged list
|
stripcaged list
|
||||||
show content of the cages
|
show content of the cages
|
||||||
|
|
||||||
EOS
|
EOS
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -1,61 +1,61 @@
|
|||||||
# give super-dwarven speed to an unit
|
# give super-dwarven speed to an unit
|
||||||
|
|
||||||
$superdwarf_onupdate ||= nil
|
$superdwarf_onupdate ||= nil
|
||||||
$superdwarf_ids ||= []
|
$superdwarf_ids ||= []
|
||||||
|
|
||||||
case $script_args[0]
|
case $script_args[0]
|
||||||
when 'add'
|
when 'add'
|
||||||
if u = df.unit_find
|
if u = df.unit_find
|
||||||
$superdwarf_ids |= [u.id]
|
$superdwarf_ids |= [u.id]
|
||||||
|
|
||||||
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) {
|
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) {
|
||||||
if $superdwarf_ids.empty?
|
if $superdwarf_ids.empty?
|
||||||
df.onupdate_unregister($superdwarf_onupdate)
|
df.onupdate_unregister($superdwarf_onupdate)
|
||||||
$superdwarf_onupdate = nil
|
$superdwarf_onupdate = nil
|
||||||
else
|
else
|
||||||
$superdwarf_ids.each { |id|
|
$superdwarf_ids.each { |id|
|
||||||
if u = df.unit_find(id) and not u.flags1.dead
|
if u = df.unit_find(id) and not u.flags1.dead
|
||||||
# faster walk/work
|
# faster walk/work
|
||||||
if u.counters.job_counter > 0
|
if u.counters.job_counter > 0
|
||||||
u.counters.job_counter = 0
|
u.counters.job_counter = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
# no sleep
|
# no sleep
|
||||||
if u.counters2.sleepiness_timer > 10000
|
if u.counters2.sleepiness_timer > 10000
|
||||||
u.counters2.sleepiness_timer = 1
|
u.counters2.sleepiness_timer = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
# no break
|
# no break
|
||||||
if b = u.status.misc_traits.find { |t| t.id == :OnBreak }
|
if b = u.status.misc_traits.find { |t| t.id == :OnBreak }
|
||||||
b.value = 500_000
|
b.value = 500_000
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
$superdwarf_ids.delete id
|
$superdwarf_ids.delete id
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
puts "Select a creature using 'v'"
|
puts "Select a creature using 'v'"
|
||||||
end
|
end
|
||||||
|
|
||||||
when 'del'
|
when 'del'
|
||||||
if u = df.unit_find
|
if u = df.unit_find
|
||||||
$superdwarf_ids.delete u.id
|
$superdwarf_ids.delete u.id
|
||||||
else
|
else
|
||||||
puts "Select a creature using 'v'"
|
puts "Select a creature using 'v'"
|
||||||
end
|
end
|
||||||
|
|
||||||
when 'clear'
|
when 'clear'
|
||||||
$superdwarf_ids.clear
|
$superdwarf_ids.clear
|
||||||
|
|
||||||
when 'list'
|
when 'list'
|
||||||
puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name }
|
puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name }
|
||||||
|
|
||||||
else
|
else
|
||||||
puts "Usage:",
|
puts "Usage:",
|
||||||
" - superdwarf add: give superspeed to currently selected creature",
|
" - superdwarf add: give superspeed to currently selected creature",
|
||||||
" - superdwarf del: remove superspeed to current creature",
|
" - superdwarf del: remove superspeed to current creature",
|
||||||
" - superdwarf clear: remove all superpowers",
|
" - superdwarf clear: remove all superpowers",
|
||||||
" - superdwarf list: list super-dwarves"
|
" - superdwarf list: list super-dwarves"
|
||||||
end
|
end
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
joblist = df.world.job_list.next
|
joblist = df.world.job_list.next
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
while joblist
|
while joblist
|
||||||
job = joblist.item
|
job = joblist.item
|
||||||
joblist = joblist.next
|
joblist = joblist.next
|
||||||
|
|
||||||
if job.job_type == :ConstructBuilding
|
if job.job_type == :ConstructBuilding
|
||||||
if (job.flags.suspend && job.items && job.items[0])
|
if (job.flags.suspend && job.items && job.items[0])
|
||||||
item = job.items[0].item
|
item = job.items[0].item
|
||||||
job.flags.suspend = false
|
job.flags.suspend = false
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
puts "Unsuspended #{count} job(s)."
|
puts "Unsuspended #{count} job(s)."
|
||||||
|
Loading…
Reference in New Issue