Merge branch 'master' of https://github.com/angavrilov/dfhack
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 6.3 KiB |
@ -1 +1 @@
|
|||||||
Subproject commit 327a9662be81627ebcbb3aea11ffbca3e536b7ee
|
Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc
|
@ -0,0 +1,378 @@
|
|||||||
|
// Auto Material Select
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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 "df/build_req_choice_genst.h"
|
||||||
|
#include "df/build_req_choice_specst.h"
|
||||||
|
#include "df/construction_type.h"
|
||||||
|
#include "df/item.h"
|
||||||
|
#include "df/ui.h"
|
||||||
|
#include "df/ui_build_selector.h"
|
||||||
|
#include "df/viewscreen_dwarfmodest.h"
|
||||||
|
|
||||||
|
#include "modules/Gui.h"
|
||||||
|
#include "modules/Screen.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::ui_build_selector;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("automaterial");
|
||||||
|
|
||||||
|
struct MaterialDescriptor
|
||||||
|
{
|
||||||
|
df::item_type item_type;
|
||||||
|
int16_t item_subtype;
|
||||||
|
int16_t type;
|
||||||
|
int32_t index;
|
||||||
|
bool valid;
|
||||||
|
|
||||||
|
bool matches(const MaterialDescriptor &a) const
|
||||||
|
{
|
||||||
|
return a.valid && valid &&
|
||||||
|
a.type == type &&
|
||||||
|
a.index == index &&
|
||||||
|
a.item_type == item_type &&
|
||||||
|
a.item_subtype == item_subtype;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static map<int16_t, MaterialDescriptor> last_used_material;
|
||||||
|
static map<int16_t, MaterialDescriptor> last_moved_material;
|
||||||
|
static map< int16_t, vector<MaterialDescriptor> > preferred_materials;
|
||||||
|
static map< int16_t, df::interface_key > hotkeys;
|
||||||
|
static bool last_used_moved = false;
|
||||||
|
static bool auto_choose_materials = true;
|
||||||
|
static bool auto_choose_attempted = true;
|
||||||
|
static bool revert_to_last_used_type = false;
|
||||||
|
|
||||||
|
static command_result automaterial_cmd(color_ostream &out, vector <string> & parameters)
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool in_material_choice_stage()
|
||||||
|
{
|
||||||
|
return Gui::build_selector_hotkey(Core::getTopViewscreen()) &&
|
||||||
|
ui_build_selector->building_type == df::building_type::Construction &&
|
||||||
|
ui->main.mode == ui_sidebar_mode::Build &&
|
||||||
|
ui_build_selector->stage == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool in_placement_stage()
|
||||||
|
{
|
||||||
|
return Gui::dwarfmode_hotkey(Core::getTopViewscreen()) &&
|
||||||
|
ui->main.mode == ui_sidebar_mode::Build &&
|
||||||
|
ui_build_selector &&
|
||||||
|
ui_build_selector->building_type == df::building_type::Construction &&
|
||||||
|
ui_build_selector->stage == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool in_type_choice_stage()
|
||||||
|
{
|
||||||
|
return Gui::dwarfmode_hotkey(Core::getTopViewscreen()) &&
|
||||||
|
ui->main.mode == ui_sidebar_mode::Build &&
|
||||||
|
ui_build_selector &&
|
||||||
|
ui_build_selector->building_type < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline vector<MaterialDescriptor> &get_curr_constr_prefs()
|
||||||
|
{
|
||||||
|
if (preferred_materials.find(ui_build_selector->building_subtype) == preferred_materials.end())
|
||||||
|
preferred_materials[ui_build_selector->building_subtype] = vector<MaterialDescriptor>();
|
||||||
|
|
||||||
|
return preferred_materials[ui_build_selector->building_subtype];
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline MaterialDescriptor &get_last_used_material()
|
||||||
|
{
|
||||||
|
if (last_used_material.find(ui_build_selector->building_subtype) == last_used_material.end())
|
||||||
|
last_used_material[ui_build_selector->building_subtype] = MaterialDescriptor();
|
||||||
|
|
||||||
|
return last_used_material[ui_build_selector->building_subtype];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_last_used_material(const MaterialDescriptor &matetial)
|
||||||
|
{
|
||||||
|
last_used_material[ui_build_selector->building_subtype] = matetial;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MaterialDescriptor &get_last_moved_material()
|
||||||
|
{
|
||||||
|
if (last_moved_material.find(ui_build_selector->building_subtype) == last_moved_material.end())
|
||||||
|
last_moved_material[ui_build_selector->building_subtype] = MaterialDescriptor();
|
||||||
|
|
||||||
|
return last_moved_material[ui_build_selector->building_subtype];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_last_moved_material(const MaterialDescriptor &matetial)
|
||||||
|
{
|
||||||
|
last_moved_material[ui_build_selector->building_subtype] = matetial;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MaterialDescriptor get_material_in_list(size_t i)
|
||||||
|
{
|
||||||
|
MaterialDescriptor result;
|
||||||
|
result.valid = false;
|
||||||
|
|
||||||
|
if (VIRTUAL_CAST_VAR(gen, df::build_req_choice_genst, ui_build_selector->choices[i]))
|
||||||
|
{
|
||||||
|
result.item_type = gen->item_type;
|
||||||
|
result.item_subtype = gen->item_subtype;
|
||||||
|
result.type = gen->mat_type;
|
||||||
|
result.index = gen->mat_index;
|
||||||
|
result.valid = true;
|
||||||
|
}
|
||||||
|
else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i]))
|
||||||
|
{
|
||||||
|
result.item_type = gen->item_type;
|
||||||
|
result.item_subtype = gen->item_subtype;
|
||||||
|
result.type = spec->candidate->getActualMaterial();
|
||||||
|
result.index = spec->candidate->getActualMaterialIndex();
|
||||||
|
result.valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_material_in_autoselect(size_t &i, MaterialDescriptor &material)
|
||||||
|
{
|
||||||
|
for (i = 0; i < get_curr_constr_prefs().size(); i++)
|
||||||
|
{
|
||||||
|
if (get_curr_constr_prefs()[i].matches(material))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_material_in_list(size_t &i, MaterialDescriptor &material)
|
||||||
|
{
|
||||||
|
const size_t size = ui_build_selector->choices.size(); //Just because material list could be very big
|
||||||
|
for (i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
if (get_material_in_list(i).matches(material))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool move_material_to_top(MaterialDescriptor &material)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
if (is_material_in_list(i, material))
|
||||||
|
{
|
||||||
|
auto sel_item = ui_build_selector->choices[i];
|
||||||
|
ui_build_selector->choices.erase(ui_build_selector->choices.begin() + i);
|
||||||
|
ui_build_selector->choices.insert(ui_build_selector->choices.begin(), sel_item);
|
||||||
|
|
||||||
|
ui_build_selector->sel_index = 0;
|
||||||
|
set_last_moved_material(material);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_last_moved_material(MaterialDescriptor());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_autoselect(MaterialDescriptor &material, bool toggle)
|
||||||
|
{
|
||||||
|
size_t idx;
|
||||||
|
if (is_material_in_autoselect(idx, material))
|
||||||
|
{
|
||||||
|
if (toggle)
|
||||||
|
vector_erase_at(get_curr_constr_prefs(), idx);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (toggle)
|
||||||
|
get_curr_constr_prefs().push_back(material);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct jobutils_hook : public df::viewscreen_dwarfmodest
|
||||||
|
{
|
||||||
|
typedef df::viewscreen_dwarfmodest interpose_base;
|
||||||
|
|
||||||
|
bool choose_materials()
|
||||||
|
{
|
||||||
|
size_t size = ui_build_selector->choices.size();
|
||||||
|
for (size_t i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
MaterialDescriptor material = get_material_in_list(i);
|
||||||
|
size_t j;
|
||||||
|
if (is_material_in_autoselect(j, material))
|
||||||
|
{
|
||||||
|
ui_build_selector->sel_index = i;
|
||||||
|
std::set< df::interface_key > keys;
|
||||||
|
keys.insert(df::interface_key::SELECT_ALL);
|
||||||
|
this->feed(&keys);
|
||||||
|
if (!in_material_choice_stage())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
||||||
|
{
|
||||||
|
if (in_material_choice_stage())
|
||||||
|
{
|
||||||
|
MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index);
|
||||||
|
if (material.valid)
|
||||||
|
{
|
||||||
|
if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT))
|
||||||
|
{
|
||||||
|
if (get_last_moved_material().matches(material))
|
||||||
|
last_used_moved = false;
|
||||||
|
|
||||||
|
set_last_used_material(material);
|
||||||
|
}
|
||||||
|
else if (input->count(interface_key::CUSTOM_A))
|
||||||
|
{
|
||||||
|
check_autoselect(material, true);
|
||||||
|
input->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (in_placement_stage())
|
||||||
|
{
|
||||||
|
if (input->count(interface_key::CUSTOM_A))
|
||||||
|
{
|
||||||
|
auto_choose_materials = !auto_choose_materials;
|
||||||
|
}
|
||||||
|
else if (input->count(interface_key::CUSTOM_T))
|
||||||
|
{
|
||||||
|
revert_to_last_used_type = !revert_to_last_used_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t last_used_constr_subtype = (in_material_choice_stage()) ? ui_build_selector->building_subtype : -1;
|
||||||
|
INTERPOSE_NEXT(feed)(input);
|
||||||
|
|
||||||
|
if (revert_to_last_used_type &&
|
||||||
|
last_used_constr_subtype >= 0 &&
|
||||||
|
!in_material_choice_stage() &&
|
||||||
|
hotkeys.find(last_used_constr_subtype) != hotkeys.end())
|
||||||
|
{
|
||||||
|
interface_key_set keys;
|
||||||
|
keys.insert(hotkeys[last_used_constr_subtype]);
|
||||||
|
INTERPOSE_NEXT(feed)(&keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
||||||
|
{
|
||||||
|
if (in_material_choice_stage())
|
||||||
|
{
|
||||||
|
if (!last_used_moved)
|
||||||
|
{
|
||||||
|
if (auto_choose_materials && get_curr_constr_prefs().size() > 0)
|
||||||
|
{
|
||||||
|
last_used_moved = true;
|
||||||
|
if (choose_materials())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ui_build_selector->is_grouped)
|
||||||
|
{
|
||||||
|
last_used_moved = true;
|
||||||
|
move_material_to_top(get_last_used_material());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!ui_build_selector->is_grouped)
|
||||||
|
{
|
||||||
|
last_used_moved = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
last_used_moved = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
INTERPOSE_NEXT(render)();
|
||||||
|
|
||||||
|
if (in_material_choice_stage())
|
||||||
|
{
|
||||||
|
MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index);
|
||||||
|
if (material.valid)
|
||||||
|
{
|
||||||
|
string title = "Disabled";
|
||||||
|
if (check_autoselect(material, false))
|
||||||
|
{
|
||||||
|
title = "Enabled";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dims = Gui::getDwarfmodeViewDims();
|
||||||
|
Screen::Painter dc(dims.menu());
|
||||||
|
|
||||||
|
dc.seek(1,24).key_pen(COLOR_LIGHTRED).pen(COLOR_WHITE);
|
||||||
|
dc.key(interface_key::CUSTOM_A).string(": Autoselect "+title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (in_placement_stage() && ui_build_selector->building_subtype < construction_type::TrackN)
|
||||||
|
{
|
||||||
|
string autoselect_toggle = (auto_choose_materials) ? "Disable" : "Enable";
|
||||||
|
string revert_toggle = (revert_to_last_used_type) ? "Disable" : "Enable";
|
||||||
|
|
||||||
|
auto dims = Gui::getDwarfmodeViewDims();
|
||||||
|
Screen::Painter dc(dims.menu());
|
||||||
|
|
||||||
|
dc.seek(1,23).key_pen(COLOR_LIGHTRED).pen(COLOR_WHITE);
|
||||||
|
dc.key(interface_key::CUSTOM_A).string(": "+autoselect_toggle+" Auto Mat-Select").newline(1);
|
||||||
|
dc.key(interface_key::CUSTOM_T).string(": "+revert_toggle+" Auto Type-Select");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, feed);
|
||||||
|
IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, render);
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
if (!gps || !ui_build_selector ||
|
||||||
|
!INTERPOSE_HOOK(jobutils_hook, feed).apply() ||
|
||||||
|
!INTERPOSE_HOOK(jobutils_hook, render).apply())
|
||||||
|
out.printerr("Could not insert jobutils hooks!\n");
|
||||||
|
|
||||||
|
hotkeys[construction_type::Wall] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_WALL;
|
||||||
|
hotkeys[construction_type::Floor] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FLOOR;
|
||||||
|
hotkeys[construction_type::Ramp] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_RAMP;
|
||||||
|
hotkeys[construction_type::UpStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UP;
|
||||||
|
hotkeys[construction_type::DownStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_DOWN;
|
||||||
|
hotkeys[construction_type::UpDownStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UPDOWN;
|
||||||
|
hotkeys[construction_type::Fortification] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FORTIFICATION;
|
||||||
|
//Ignore tracks, DF already returns to track menu
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
class AutoFarm
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@thresholds = Hash.new(50)
|
||||||
|
@lastcounts = Hash.new(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def setthreshold(id, v)
|
||||||
|
if df.world.raws.plants.all.find { |r| r.id == id }
|
||||||
|
@thresholds[id] = v.to_i
|
||||||
|
else
|
||||||
|
puts "No plant with id #{id}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def setdefault(v)
|
||||||
|
@thresholds.default = v.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_plantable(plant)
|
||||||
|
season = df.cur_season
|
||||||
|
harvest = df.cur_season_tick + plant.growdur * 10
|
||||||
|
will_finish = harvest < 10080
|
||||||
|
can_plant = plant.flags[season]
|
||||||
|
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
|
||||||
|
can_plant
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_plantable_plants
|
||||||
|
plantable = {}
|
||||||
|
for i in 0..df.ui.tasks.known_plants.length-1
|
||||||
|
if df.ui.tasks.known_plants[i]
|
||||||
|
plant = df.world.raws.plants.all[i]
|
||||||
|
if is_plantable(plant)
|
||||||
|
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
|
||||||
|
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return plantable
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_farms( plants, farms)
|
||||||
|
return if farms.length == 0
|
||||||
|
if plants.length == 0
|
||||||
|
plants = [-1]
|
||||||
|
end
|
||||||
|
|
||||||
|
season = df.cur_season
|
||||||
|
|
||||||
|
idx = 0
|
||||||
|
|
||||||
|
farms.each { |f|
|
||||||
|
f.plant_id[season] = plants[idx]
|
||||||
|
idx = (idx + 1) % plants.length
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def process
|
||||||
|
return false unless @running
|
||||||
|
|
||||||
|
plantable = find_plantable_plants
|
||||||
|
counts = Hash.new(0)
|
||||||
|
|
||||||
|
df.world.items.other[:PLANT].each { |i|
|
||||||
|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
||||||
|
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
||||||
|
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
||||||
|
!i.flags.artifact && plantable.has_key?(i.mat_index))
|
||||||
|
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
plants_s = []
|
||||||
|
plants_u = []
|
||||||
|
|
||||||
|
@lastcounts.clear
|
||||||
|
|
||||||
|
plantable.each_key { |k|
|
||||||
|
plant = df.world.raws.plants.all[k]
|
||||||
|
if (counts[k] < @thresholds[plant.id])
|
||||||
|
plants_s.push(k) if plantable[k] == :Surface
|
||||||
|
plants_u.push(k) if plantable[k] == :Underground
|
||||||
|
end
|
||||||
|
@lastcounts[plant.id] = counts[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
farms_s = []
|
||||||
|
farms_u = []
|
||||||
|
df.world.buildings.other[:FARM_PLOT].each { |f|
|
||||||
|
if (f.flags.exists)
|
||||||
|
outside = df.map_designation_at(f.centerx,f.centery,f.z).outside
|
||||||
|
farms_s.push(f) if outside
|
||||||
|
farms_u.push(f) unless outside
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
set_farms(plants_s, farms_s)
|
||||||
|
set_farms(plants_u, farms_u)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
@onupdate = df.onupdate_register('autofarm', 100) { process }
|
||||||
|
@running = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop
|
||||||
|
df.onupdate_unregister(@onupdate)
|
||||||
|
@running = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def status
|
||||||
|
stat = @running ? "Running." : "Stopped."
|
||||||
|
@thresholds.each { |k,v|
|
||||||
|
stat += "\n#{k} limit #{v} current #{@lastcounts[k]}"
|
||||||
|
}
|
||||||
|
stat += "\nDefault: #{@thresholds.default}"
|
||||||
|
stat
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
$AutoFarm = AutoFarm.new unless $AutoFarm
|
||||||
|
|
||||||
|
case $script_args[0]
|
||||||
|
when 'start'
|
||||||
|
$AutoFarm.start
|
||||||
|
|
||||||
|
when 'end', 'stop'
|
||||||
|
$AutoFarm.stop
|
||||||
|
|
||||||
|
when 'default'
|
||||||
|
$AutoFarm.setdefault($script_args[1])
|
||||||
|
|
||||||
|
when 'threshold'
|
||||||
|
t = $script_args[1]
|
||||||
|
$script_args[2..-1].each {|i|
|
||||||
|
$AutoFarm.setthreshold(i, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
when 'delete'
|
||||||
|
$AutoFarm.stop
|
||||||
|
$AutoFarm = nil
|
||||||
|
|
||||||
|
else
|
||||||
|
if $AutoFarm
|
||||||
|
puts $AutoFarm.status
|
||||||
|
else
|
||||||
|
puts "AI not started"
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,58 @@
|
|||||||
|
class AutoUnsuspend
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
end
|
||||||
|
|
||||||
|
def process
|
||||||
|
return false unless @running
|
||||||
|
joblist = df.world.job_list.next
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
while joblist
|
||||||
|
job = joblist.item
|
||||||
|
joblist = joblist.next
|
||||||
|
|
||||||
|
if job.job_type == :ConstructBuilding
|
||||||
|
if (job.flags.suspend)
|
||||||
|
item = job.items[0].item
|
||||||
|
job.flags.suspend = false
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "Unsuspended #{count} job(s)." unless count == 0
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
|
||||||
|
@running = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop
|
||||||
|
df.onupdate_unregister(@onupdate)
|
||||||
|
@running = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def status
|
||||||
|
@running ? 'Running.' : 'Stopped.'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
case $script_args[0]
|
||||||
|
when 'start'
|
||||||
|
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
|
||||||
|
$AutoUnsuspend.start
|
||||||
|
|
||||||
|
when 'end', 'stop'
|
||||||
|
$AutoUnsuspend.stop
|
||||||
|
|
||||||
|
else
|
||||||
|
if $AutoUnsuspend
|
||||||
|
puts $AutoUnsuspend.status
|
||||||
|
else
|
||||||
|
puts 'Not loaded.'
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,117 @@
|
|||||||
|
# control your levers from the dfhack console
|
||||||
|
|
||||||
|
def lever_pull_job(bld)
|
||||||
|
ref = DFHack::GeneralRefBuildingHolderst.cpp_new
|
||||||
|
ref.building_id = bld.id
|
||||||
|
|
||||||
|
job = DFHack::Job.cpp_new
|
||||||
|
job.job_type = :PullLever
|
||||||
|
job.pos = [bld.centerx, bld.centery, bld.z]
|
||||||
|
job.general_refs << ref
|
||||||
|
bld.jobs << job
|
||||||
|
df.job_link job
|
||||||
|
|
||||||
|
puts lever_descr(bld)
|
||||||
|
end
|
||||||
|
|
||||||
|
def lever_pull_cheat(bld)
|
||||||
|
bld.linked_mechanisms.each { |i|
|
||||||
|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r|
|
||||||
|
r.building_tg.setTriggerState(bld.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bld.state = (bld.state == 0 ? 1 : 0)
|
||||||
|
|
||||||
|
puts lever_descr(bld)
|
||||||
|
end
|
||||||
|
|
||||||
|
def lever_descr(bld, idx=nil)
|
||||||
|
ret = []
|
||||||
|
|
||||||
|
# lever description
|
||||||
|
descr = ''
|
||||||
|
descr << "#{idx}: " if idx
|
||||||
|
descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}"
|
||||||
|
bld.jobs.each { |j|
|
||||||
|
if j.job_type == :PullLever
|
||||||
|
flags = ''
|
||||||
|
flags << ', repeat' if j.flags.repeat
|
||||||
|
flags << ', suspended' if j.flags.suspend
|
||||||
|
descr << " (pull order#{flags})"
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
bld.linked_mechanisms.map { |i|
|
||||||
|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst)
|
||||||
|
}.flatten.each { |r|
|
||||||
|
# linked building description
|
||||||
|
tg = r.building_tg
|
||||||
|
state = tg.gate_flags.closed ? 'closed' : 'opened'
|
||||||
|
state << ', closing' if tg.gate_flags.closing
|
||||||
|
state << ', opening' if tg.gate_flags.opening
|
||||||
|
|
||||||
|
ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}")
|
||||||
|
|
||||||
|
# indent other links
|
||||||
|
descr = descr.gsub(/./, ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
ret << descr if ret.empty?
|
||||||
|
|
||||||
|
ret
|
||||||
|
end
|
||||||
|
|
||||||
|
def lever_list
|
||||||
|
@lever_list = []
|
||||||
|
df.world.buildings.other[:TRAP].find_all { |bld|
|
||||||
|
bld.trap_type == :Lever
|
||||||
|
}.sort_by { |bld| bld.id }.each { |bld|
|
||||||
|
puts lever_descr(bld, @lever_list.length)
|
||||||
|
@lever_list << bld.id
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
case $script_args[0]
|
||||||
|
when 'pull'
|
||||||
|
cheat = $script_args.delete('--cheat') || $script_args.delete('--now')
|
||||||
|
|
||||||
|
id = $script_args[1].to_i
|
||||||
|
id = @lever_list[id] || id
|
||||||
|
bld = df.building_find(id)
|
||||||
|
raise 'invalid lever id' if not bld
|
||||||
|
|
||||||
|
if cheat
|
||||||
|
lever_pull_cheat(bld)
|
||||||
|
else
|
||||||
|
lever_pull_job(bld)
|
||||||
|
end
|
||||||
|
|
||||||
|
when 'list'
|
||||||
|
lever_list
|
||||||
|
|
||||||
|
when /^\d+$/
|
||||||
|
id = $script_args[0].to_i
|
||||||
|
id = @lever_list[id] || id
|
||||||
|
bld = df.building_find(id)
|
||||||
|
raise 'invalid lever id' if not bld
|
||||||
|
|
||||||
|
puts lever_descr(bld)
|
||||||
|
|
||||||
|
else
|
||||||
|
puts <<EOS
|
||||||
|
Lever control from the dfhack console
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
lever list
|
||||||
|
shows the list of levers in the fortress, with their id and links
|
||||||
|
|
||||||
|
lever pull 42
|
||||||
|
order the dwarves to pull lever 42
|
||||||
|
|
||||||
|
lever pull 42 --cheat
|
||||||
|
magically pull lever 42 immediately
|
||||||
|
EOS
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,200 @@
|
|||||||
|
# mark stuff inside of cages for dumping.
|
||||||
|
|
||||||
|
def plural(nr, name)
|
||||||
|
# '1 cage' / '4 cages'
|
||||||
|
"#{nr} #{name}#{'s' if nr > 1}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def cage_dump_items(list)
|
||||||
|
count = 0
|
||||||
|
count_cage = 0
|
||||||
|
list.each { |cage|
|
||||||
|
pre_count = count
|
||||||
|
cage.general_refs.each { |ref|
|
||||||
|
next unless ref.kind_of?(DFHack::GeneralRefContainsItemst)
|
||||||
|
next if ref.item_tg.flags.dump
|
||||||
|
count += 1
|
||||||
|
ref.item_tg.flags.dump = true
|
||||||
|
}
|
||||||
|
count_cage += 1 if pre_count != count
|
||||||
|
}
|
||||||
|
|
||||||
|
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def cage_dump_armor(list)
|
||||||
|
count = 0
|
||||||
|
count_cage = 0
|
||||||
|
list.each { |cage|
|
||||||
|
pre_count = count
|
||||||
|
cage.general_refs.each { |ref|
|
||||||
|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
|
||||||
|
ref.unit_tg.inventory.each { |it|
|
||||||
|
next if it.mode != :Worn
|
||||||
|
next if it.item.flags.dump
|
||||||
|
count += 1
|
||||||
|
it.item.flags.dump = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count_cage += 1 if pre_count != count
|
||||||
|
}
|
||||||
|
|
||||||
|
puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def cage_dump_weapons(list)
|
||||||
|
count = 0
|
||||||
|
count_cage = 0
|
||||||
|
list.each { |cage|
|
||||||
|
pre_count = count
|
||||||
|
cage.general_refs.each { |ref|
|
||||||
|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
|
||||||
|
ref.unit_tg.inventory.each { |it|
|
||||||
|
next if it.mode != :Weapon
|
||||||
|
next if it.item.flags.dump
|
||||||
|
count += 1
|
||||||
|
it.item.flags.dump = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count_cage += 1 if pre_count != count
|
||||||
|
}
|
||||||
|
|
||||||
|
puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def cage_dump_all(list)
|
||||||
|
count = 0
|
||||||
|
count_cage = 0
|
||||||
|
list.each { |cage|
|
||||||
|
pre_count = count
|
||||||
|
cage.general_refs.each { |ref|
|
||||||
|
case ref
|
||||||
|
when DFHack::GeneralRefContainsItemst
|
||||||
|
next if ref.item_tg.flags.dump
|
||||||
|
count += 1
|
||||||
|
ref.item_tg.flags.dump = true
|
||||||
|
when DFHack::GeneralRefContainsUnitst
|
||||||
|
ref.unit_tg.inventory.each { |it|
|
||||||
|
next if it.item.flags.dump
|
||||||
|
count += 1
|
||||||
|
it.item.flags.dump = true
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
count_cage += 1 if pre_count != count
|
||||||
|
}
|
||||||
|
|
||||||
|
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def cage_dump_list(list)
|
||||||
|
count_total = Hash.new(0)
|
||||||
|
empty_cages = 0
|
||||||
|
list.each { |cage|
|
||||||
|
count = Hash.new(0)
|
||||||
|
|
||||||
|
cage.general_refs.each { |ref|
|
||||||
|
case ref
|
||||||
|
when DFHack::GeneralRefContainsItemst
|
||||||
|
count[ref.item_tg._rtti_classname] += 1
|
||||||
|
when DFHack::GeneralRefContainsUnitst
|
||||||
|
ref.unit_tg.inventory.each { |it|
|
||||||
|
count[it.item._rtti_classname] += 1
|
||||||
|
}
|
||||||
|
# TODO vermin ?
|
||||||
|
else
|
||||||
|
puts "unhandled ref #{ref.inspect}" if $DEBUG
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
type = case cage
|
||||||
|
when DFHack::ItemCagest; 'Cage'
|
||||||
|
when DFHack::ItemAnimaltrapst; 'Animal trap'
|
||||||
|
else cage._rtti_classname
|
||||||
|
end
|
||||||
|
|
||||||
|
if count.empty?
|
||||||
|
empty_cages += 1
|
||||||
|
else
|
||||||
|
puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
|
||||||
|
end
|
||||||
|
|
||||||
|
count.each { |k, v| count_total[k] += v }
|
||||||
|
}
|
||||||
|
|
||||||
|
if list.length > 2
|
||||||
|
puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
|
||||||
|
puts "with #{plural(empty_cages, 'empty cage')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# handle magic script arguments
|
||||||
|
here_only = $script_args.delete 'here'
|
||||||
|
if here_only
|
||||||
|
it = df.item_find
|
||||||
|
list = [it]
|
||||||
|
if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
|
||||||
|
list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) }
|
||||||
|
end
|
||||||
|
puts 'Please select a cage' if list.empty?
|
||||||
|
|
||||||
|
elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first
|
||||||
|
list = []
|
||||||
|
ids.each { |id|
|
||||||
|
$script_args.delete id
|
||||||
|
if not it = df.item_find(id.to_i)
|
||||||
|
puts "Invalid item id #{id}"
|
||||||
|
elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
|
||||||
|
puts "Item ##{id} is not a cage"
|
||||||
|
list << it
|
||||||
|
else
|
||||||
|
list << it
|
||||||
|
end
|
||||||
|
}
|
||||||
|
puts 'Please use a valid cage id' if list.empty?
|
||||||
|
|
||||||
|
else
|
||||||
|
list = df.world.items.other[:ANY_CAGE_OR_TRAP]
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# act
|
||||||
|
case $script_args[0]
|
||||||
|
when 'items'
|
||||||
|
cage_dump_items(list) if not list.empty?
|
||||||
|
when 'armor'
|
||||||
|
cage_dump_armor(list) if not list.empty?
|
||||||
|
when 'weapons'
|
||||||
|
cage_dump_weapons(list) if not list.empty?
|
||||||
|
when 'all'
|
||||||
|
cage_dump_all(list) if not list.empty?
|
||||||
|
|
||||||
|
when 'list'
|
||||||
|
cage_dump_list(list) if not list.empty?
|
||||||
|
|
||||||
|
else
|
||||||
|
puts <<EOS
|
||||||
|
Marks items inside all cages for dumping.
|
||||||
|
Add 'here' to dump stuff only for selected cage.
|
||||||
|
Add a cage id to dump stuff for this cage only.
|
||||||
|
|
||||||
|
See 'autodump' to actually dump stuff.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
stripcaged items
|
||||||
|
dump items directly in cages (eg seeds after training)
|
||||||
|
|
||||||
|
stripcaged [armor|weapons] here
|
||||||
|
dump armor or weapons of caged creatures in selected cage
|
||||||
|
|
||||||
|
stripcaged all 28 29
|
||||||
|
dump every item in cage id 28 and 29, along with every item worn by creatures in there too
|
||||||
|
|
||||||
|
stripcaged list
|
||||||
|
show content of the cages
|
||||||
|
|
||||||
|
EOS
|
||||||
|
|
||||||
|
end
|
@ -0,0 +1,17 @@
|
|||||||
|
joblist = df.world.job_list.next
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
while joblist
|
||||||
|
job = joblist.item
|
||||||
|
joblist = joblist.next
|
||||||
|
|
||||||
|
if job.job_type == :ConstructBuilding
|
||||||
|
if (job.flags.suspend && job.items && job.items[0])
|
||||||
|
item = job.items[0].item
|
||||||
|
job.flags.suspend = false
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "Unsuspended #{count} job(s)."
|