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
@ -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 }
|
@ -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()
|
@ -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 }
|
@ -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
|
Loading…
Reference in New Issue