Petr Mrázek 2012-01-05 23:54:33 +01:00
commit 72a4698968
10 changed files with 1217 additions and 22 deletions

@ -1093,6 +1093,8 @@
<Address name='ui_look_list'/> <Address name='ui_look_list'/>
<Address name='ui_look_cursor'/> <Address name='ui_look_cursor'/>
<Address name='ui_workshop_job_cursor'/> <Address name='ui_workshop_job_cursor'/>
<Address name='ui_workshop_in_add'/>
<Address name='job_next_id'/>
</Group> </Group>
</Offsets> </Offsets>
</Base> </Base>
@ -2343,6 +2345,9 @@
<Address name='ui_look_list' value='0x185c104'/> <Address name='ui_look_list' value='0x185c104'/>
<Address name='ui_look_cursor' value='0xedeb98'/> <Address name='ui_look_cursor' value='0xedeb98'/>
<Address name='ui_workshop_job_cursor' value='0x14ec79c'/> <Address name='ui_workshop_job_cursor' value='0x14ec79c'/>
<Address name='ui_workshop_in_add' value='0xeb0992'/>
<Address name='job_next_id' value='0xe18290'/>
</Group> </Group>
</Offsets> </Offsets>
cmake cmake
@ -3227,6 +3232,9 @@
<Address name='ui_look_list' value='0x961d840'/> <Address name='ui_look_list' value='0x961d840'/>
<!--<Address name='ui_look_cursor' value=''/>--> <!--<Address name='ui_look_cursor' value=''/>-->
<Address name='ui_workshop_job_cursor' value='0x93f0644'/> <Address name='ui_workshop_job_cursor' value='0x93f0644'/>
<Address name='ui_workshop_in_add' value='0x93f0651'/>
<Address name='job_next_id' value='0x961d940'/>
</Group> </Group>
</Offsets> </Offsets>
</Version> </Version>

@ -35,6 +35,8 @@ distribution.
// must be last due to MS stupidity // must be last due to MS stupidity
#include "DataDefs.h" #include "DataDefs.h"
#include "MiscUtils.h"
using namespace DFHack; using namespace DFHack;
/* The order of global object constructor calls is /* The order of global object constructor calls is
@ -160,6 +162,41 @@ void virtual_identity::Init(Core *core)
} }
} }
std::string DFHack::bitfieldToString(const void *p, int size, const bitfield_item_info *items)
{
std::string res;
const char *data = (const char*)p;
for (int i = 0; i < size*8; i++) {
unsigned v;
if (items[i].size > 1) {
unsigned pdv = *(unsigned*)&data[i/8];
v = (pdv >> (i%8)) & ((1 << items[i].size)-1);
} else {
v = (data[i/8]>>(i%8)) & 1;
}
if (v) {
if (!res.empty())
res += ' ';
if (items[i].name)
res += items[i].name;
else
res += stl_sprintf("UNK_%d", i);
if (items[i].size > 1)
res += stl_sprintf("=%u", v);
}
if (items[i].size > 1)
i += items[i].size-1;
}
return res;
}
#define SIMPLE_GLOBAL(name,tname) \ #define SIMPLE_GLOBAL(name,tname) \
tname *df::global::name = NULL; tname *df::global::name = NULL;
#define GLOBAL(name,tname) SIMPLE_GLOBAL(name,df::tname) #define GLOBAL(name,tname) SIMPLE_GLOBAL(name,df::tname)

@ -27,8 +27,6 @@ distribution.
#include "Core.h" #include "Core.h"
#include "MiscUtils.h" #include "MiscUtils.h"
#ifndef LINUX_BUILD #ifndef LINUX_BUILD
#include <Windows.h> #include <Windows.h>
#else #else
@ -36,6 +34,31 @@ distribution.
#include <ctime> #include <ctime>
#endif #endif
#include <stdarg.h>
std::string stl_sprintf(const char *fmt, ...) {
va_list lst;
va_start(lst, fmt);
std::string rv = stl_vsprintf(fmt, lst);
va_end(lst);
return rv;
}
std::string stl_vsprintf(const char *fmt, va_list args) {
std::vector<char> buf;
buf.resize(4096);
for (;;) {
int rsz = vsnprintf(&buf[0], buf.size(), fmt, args);
if (rsz < 0)
buf.resize(buf.size()*2);
else if (unsigned(rsz) > buf.size())
buf.resize(rsz+1);
else
return std::string(&buf[0], rsz);
}
}
#ifdef LINUX_BUILD // Linux #ifdef LINUX_BUILD // Linux
uint64_t GetTimeMs64() uint64_t GetTimeMs64()
{ {

@ -111,15 +111,37 @@ namespace DFHack
return T::_identity.is_instance(ptr) ? static_cast<T*>(ptr) : NULL; return T::_identity.is_instance(ptr) ? static_cast<T*>(ptr) : NULL;
} }
#define VIRTUAL_CAST_VAR(var,type,input) type *var = virtual_cast<type>(input)
template<class T> template<class T>
inline T *strict_virtual_cast(virtual_ptr ptr) { inline T *strict_virtual_cast(virtual_ptr ptr) {
return T::_identity.is_direct_instance(ptr) ? static_cast<T*>(ptr) : NULL; return T::_identity.is_direct_instance(ptr) ? static_cast<T*>(ptr) : NULL;
} }
#define STRICT_VIRTUAL_CAST_VAR(var,type,input) type *var = strict_virtual_cast<type>(input)
void InitDataDefGlobals(Core *core); void InitDataDefGlobals(Core *core);
template<class T> template<class T>
T *ifnull(T *a, T *b) { return a ? a : b; } T *ifnull(T *a, T *b) { return a ? a : b; }
template<class T, T start, bool (*isvalid)(T)>
inline T next_enum_item_(T v) {
v = T(int(v) + 1);
return isvalid(v) ? v : start;
}
struct bitfield_item_info {
const char *name;
int size;
};
DFHACK_EXPORT std::string bitfieldToString(const void *p, int size, const bitfield_item_info *items);
template<class T>
inline std::string bitfieldToString(const T &val) {
return bitfieldToString(&val.whole, sizeof(val.whole), val.get_items());
}
} }
namespace df namespace df
@ -127,6 +149,7 @@ namespace df
using DFHack::virtual_ptr; using DFHack::virtual_ptr;
using DFHack::virtual_identity; using DFHack::virtual_identity;
using DFHack::virtual_class; using DFHack::virtual_class;
using DFHack::bitfield_item_info;
using DFHack::BitArray; using DFHack::BitArray;
template<class T> template<class T>
@ -167,6 +190,11 @@ namespace df
#define ENUM_FIRST_ITEM(enum) (df::enums::enum::_first_item_of_##enum) #define ENUM_FIRST_ITEM(enum) (df::enums::enum::_first_item_of_##enum)
#define ENUM_LAST_ITEM(enum) (df::enums::enum::_last_item_of_##enum) #define ENUM_LAST_ITEM(enum) (df::enums::enum::_last_item_of_##enum)
#define ENUM_NEXT_ITEM(enum,val) \
(DFHack::next_enum_item_<df::enum,ENUM_FIRST_ITEM(enum),df::enums::enum::is_valid>(val))
#define FOR_ENUM_ITEMS(enum,iter) \
for(df::enum iter = ENUM_FIRST_ITEM(enum); iter < ENUM_LAST_ITEM(enum); iter = df::enum(1+int(iter)))
namespace df { namespace df {
#define DF_KNOWN_GLOBALS \ #define DF_KNOWN_GLOBALS \
GLOBAL(cursor,cursor) \ GLOBAL(cursor,cursor) \
@ -176,8 +204,10 @@ namespace df {
GLOBAL(gview,interface) \ GLOBAL(gview,interface) \
GLOBAL(init,init) \ GLOBAL(init,init) \
GLOBAL(d_init,d_init) \ GLOBAL(d_init,d_init) \
SIMPLE_GLOBAL(job_next_id,int) \
SIMPLE_GLOBAL(ui_look_cursor,int) \ SIMPLE_GLOBAL(ui_look_cursor,int) \
SIMPLE_GLOBAL(ui_workshop_job_cursor,int) \ SIMPLE_GLOBAL(ui_workshop_job_cursor,int) \
SIMPLE_GLOBAL(ui_workshop_in_add,bool) \
GLOBAL(ui_sidebar_menus,ui_sidebar_menus) \ GLOBAL(ui_sidebar_menus,ui_sidebar_menus) \
GLOBAL(ui_build_selector,ui_build_selector) \ GLOBAL(ui_build_selector,ui_build_selector) \
GLOBAL(ui_look_list,ui_look_list) GLOBAL(ui_look_list,ui_look_list)

@ -66,39 +66,163 @@ void print_bits ( T val, DFHack::Console& out )
out.print(strs.str().c_str()); out.print(strs.str().c_str());
} }
//FIXME: Error 8 error C4519: default template arguments are only allowed on a class template /*
template <typename CT, typename FT, typename AT/* = FT*/> * Binary search in vectors.
CT *binsearch_in_vector(std::vector<CT*> &vec, FT CT::*field, AT value) */
template <typename FT>
int linear_index(const std::vector<FT> &vec, FT key)
{
for (unsigned i = 0; i < vec.size(); i++)
if (vec[i] == key)
return i;
return -1;
}
template <typename FT>
int linear_index(const std::vector<FT*> &vec, const FT &key)
{
for (unsigned i = 0; i < vec.size(); i++)
if (vec[i] && *vec[i] == key)
return i;
return -1;
}
template <typename FT>
int binsearch_index(const std::vector<FT> &vec, FT key, bool exact = true)
{
// Returns the index of the value >= the key
int min = -1, max = (int)vec.size();
const FT *p = vec.data();
for (;;)
{
int mid = (min + max)>>1;
if (mid == min)
return exact ? -1 : max;
FT midv = p[mid];
if (midv == key)
return mid;
else if (midv < key)
min = mid;
else
max = mid;
}
}
template <typename CT, typename FT>
int linear_index(const std::vector<CT*> &vec, FT CT::*field, FT key)
{ {
for (unsigned i = 0; i < vec.size(); i++)
if (vec[i]->*field == key)
return i;
return -1;
}
template <typename CT, typename FT>
int binsearch_index(const std::vector<CT*> &vec, FT CT::*field, FT key, bool exact = true)
{
// Returns the index of the value >= the key
int min = -1, max = (int)vec.size(); int min = -1, max = (int)vec.size();
CT **p = vec.data(); CT *const *p = vec.data();
FT key = (FT)value;
for (;;) for (;;)
{ {
int mid = (min + max)>>1; int mid = (min + max)>>1;
if (mid == min) if (mid == min)
{ return exact ? -1 : max;
return NULL;
}
FT midv = p[mid]->*field; FT midv = p[mid]->*field;
if (midv == key) if (midv == key)
{ return mid;
return p[mid];
}
else if (midv < key) else if (midv < key)
{
min = mid; min = mid;
}
else else
{
max = mid; max = mid;
}
} }
} }
template <typename CT>
inline int binsearch_index(const std::vector<CT*> &vec, typename CT::key_field_type key, bool exact = true)
{
return CT::binsearch_index(vec, key, exact);
}
template <typename CT>
inline int binsearch_index(const std::vector<CT*> &vec, typename CT::key_pointer_type key, bool exact = true)
{
return CT::binsearch_index(vec, key, exact);
}
template<typename FT, typename KT>
inline bool vector_contains(const std::vector<FT> &vec, KT key)
{
return binsearch_index(vec, key) >= 0;
}
template<typename CT, typename FT>
inline bool vector_contains(const std::vector<CT*> &vec, FT CT::*field, FT key)
{
return binsearch_index(vec, field, key) >= 0;
}
template<typename FT>
unsigned insert_into_vector(std::vector<FT> &vec, FT key, bool *inserted = NULL)
{
unsigned pos = (unsigned)binsearch_index(vec, key, false);
bool to_ins = (pos >= vec.size() || vec[pos] != key);
if (inserted) *inserted = to_ins;
if (to_ins)
vec.insert(vec.begin()+pos,key);
return pos;
}
template<typename CT, typename FT>
unsigned insert_into_vector(std::vector<CT*> &vec, FT CT::*field, CT *obj, bool *inserted = NULL)
{
unsigned pos = (unsigned)binsearch_index(vec, field, obj->*field, false);
bool to_ins = (pos >= vec.size() || vec[pos] != obj);
if (inserted) *inserted = to_ins;
if (to_ins)
vec.insert(vec.begin()+pos,obj);
return pos;
}
template <typename CT, typename KT>
CT *binsearch_in_vector(const std::vector<CT*> &vec, KT value)
{
int idx = binsearch_index(vec, value);
return idx < 0 ? NULL : vec[idx];
}
template <typename CT, typename FT>
CT *binsearch_in_vector(const std::vector<CT*> &vec, FT CT::*field, FT value)
{
int idx = binsearch_index(vec, field, value);
return idx < 0 ? NULL : vec[idx];
}
/*
* List
*/
template<typename Link>
Link *linked_list_append(Link *head, Link *tail)
{
while (head->next)
head = head->next;
head->next = tail;
tail->prev = head;
return tail;
}
/*
* MISC
*/
/** /**
* Returns the amount of milliseconds elapsed since the UNIX epoch. * Returns the amount of milliseconds elapsed since the UNIX epoch.
* Works on both windows and linux. * Works on both windows and linux.
* source: http://stackoverflow.com/questions/1861294/how-to-calculate-execution-time-of-a-code-snippet-in-c * source: http://stackoverflow.com/questions/1861294/how-to-calculate-execution-time-of-a-code-snippet-in-c
*/ */
DFHACK_EXPORT uint64_t GetTimeMs64(); DFHACK_EXPORT uint64_t GetTimeMs64();
DFHACK_EXPORT std::string stl_sprintf(const char *fmt, ...);
DFHACK_EXPORT std::string stl_vsprintf(const char *fmt, va_list args);

@ -34,11 +34,100 @@ distribution.
#include "Types.h" #include "Types.h"
#include "BitArray.h" #include "BitArray.h"
#include "DataDefs.h"
#include "df/material.h"
#include <vector> #include <vector>
#include <string> #include <string>
namespace df
{
struct item;
struct inorganic_raw;
struct plant_raw;
struct creature_raw;
struct historical_figure;
struct material_vec_ref;
struct job_item;
union job_material_category;
union job_item_flags1;
union job_item_flags2;
union job_item_flags3;
}
namespace DFHack namespace DFHack
{ {
struct DFHACK_EXPORT MaterialInfo {
static const int NUM_BUILTIN = 19;
static const int GROUP_SIZE = 200;
static const int CREATURE_BASE = NUM_BUILTIN;
static const int FIGURE_BASE = NUM_BUILTIN + GROUP_SIZE;
static const int PLANT_BASE = NUM_BUILTIN + GROUP_SIZE*2;
static const int END_BASE = NUM_BUILTIN + GROUP_SIZE*3;
int16_t type;
int32_t index;
df::material *material;
enum Mode {
Builtin,
Inorganic,
Creature,
Plant
};
Mode mode;
int16_t subtype;
df::inorganic_raw *inorganic;
df::creature_raw *creature;
df::plant_raw *plant;
df::historical_figure *figure;
public:
MaterialInfo(int16_t type = -1, int32_t index = -1) { decode(type, index); }
template<class T> MaterialInfo(T *ptr) { decode(ptr); }
bool isValid() const { return material != NULL; }
bool decode(int16_t type, int32_t index = -1);
bool decode(df::item *item);
bool decode(const df::material_vec_ref &vr, int idx);
template<class T> bool decode(T *ptr) {
// Assume and exploit a certain naming convention
return ptr ? decode(ptr->mat_type, ptr->mat_index) : decode(-1);
}
bool find(const std::string &token, const std::string &subtoken = std::string());
bool findBuiltin(const std::string &token);
bool findInorganic(const std::string &token);
bool findPlant(const std::string &token, const std::string &subtoken);
bool findCreature(const std::string &token, const std::string &subtoken);
std::string toString(uint16_t temp = 10015, bool named = true);
bool isAnyCloth();
void getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &mask);
void getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &mask);
void getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &mask);
df::craft_material_class getCraftClass();
bool matches(const df::job_material_category &cat);
bool matches(const df::job_item &item);
};
inline bool operator== (const MaterialInfo &a, const MaterialInfo &b) {
return a.type == b.type && a.index == b.index;
}
inline bool operator!= (const MaterialInfo &a, const MaterialInfo &b) {
return a.type != b.type || a.index != b.index;
}
typedef int32_t t_materialIndex; typedef int32_t t_materialIndex;
typedef int16_t t_materialType, t_itemType, t_itemSubtype; typedef int16_t t_materialType, t_itemType, t_itemSubtype;

@ -39,7 +39,359 @@ using namespace std;
#include "ModuleFactory.h" #include "ModuleFactory.h"
#include "Core.h" #include "Core.h"
#include "MiscUtils.h"
#include "df/world.h"
#include "df/ui.h"
#include "df/item.h"
#include "df/inorganic_raw.h"
#include "df/plant_raw.h"
#include "df/plant_raw_flags.h"
#include "df/creature_raw.h"
#include "df/historical_figure.h"
#include "df/job_item.h"
#include "df/job_material_category.h"
#include "df/matter_state.h"
#include "df/material_vec_ref.h"
using namespace DFHack; using namespace DFHack;
using namespace df::enums;
bool MaterialInfo::decode(df::item *item)
{
if (!item)
return decode(-1);
else
return decode(item->getActualMaterial(), item->getActualMaterialIndex());
}
bool MaterialInfo::decode(const df::material_vec_ref &vr, int idx)
{
if (idx < 0 || idx >= vr.mat_type.size() || idx >= vr.mat_index.size())
return decode(-1);
else
return decode(vr.mat_type[idx], vr.mat_index[idx]);
}
bool MaterialInfo::decode(int16_t type, int32_t index)
{
this->type = type;
this->index = index;
material = NULL;
mode = Builtin; subtype = 0;
inorganic = NULL; plant = NULL; creature = NULL;
figure = NULL;
df::world_raws &raws = df::global::world->raws;
if (type < 0 || type >= sizeof(raws.mat_table.builtin)/sizeof(void*))
return false;
if (index < 0)
{
material = raws.mat_table.builtin[type];
}
else if (type == 0)
{
mode = Inorganic;
inorganic = df::inorganic_raw::find(index);
if (!inorganic)
return false;
material = &inorganic->material;
}
else if (type < CREATURE_BASE)
{
material = raws.mat_table.builtin[type];
}
else if (type < FIGURE_BASE)
{
mode = Creature;
subtype = type-CREATURE_BASE;
creature = df::creature_raw::find(index);
if (!creature || subtype >= creature->material.size())
return false;
material = creature->material[subtype];
}
else if (type < PLANT_BASE)
{
mode = Creature;
subtype = type-FIGURE_BASE;
figure = df::historical_figure::find(index);
if (!figure)
return false;
creature = df::creature_raw::find(figure->race);
if (!creature || subtype >= creature->material.size())
return false;
material = creature->material[subtype];
}
else if (type < END_BASE)
{
mode = Plant;
subtype = type-PLANT_BASE;
plant = df::plant_raw::find(index);
if (!plant || subtype >= plant->material.size())
return false;
material = plant->material[subtype];
}
else
{
material = raws.mat_table.builtin[type];
}
return (material != NULL);
}
bool MaterialInfo::find(const std::string &token, const std::string &subtoken)
{
if (findBuiltin(token))
return true;
if (subtoken.empty())
{
if (findInorganic(token))
return true;
}
else
{
if (findPlant(token, subtoken))
return true;
if (findCreature(token, subtoken))
return true;
}
return false;
}
bool MaterialInfo::findBuiltin(const std::string &token)
{
df::world_raws &raws = df::global::world->raws;
for (int i = 1; i < NUM_BUILTIN; i++)
if (raws.mat_table.builtin[i]->id == token)
return decode(i, -1);
return decode(-1);
}
bool MaterialInfo::findInorganic(const std::string &token)
{
df::world_raws &raws = df::global::world->raws;
for (unsigned i = 0; i < raws.inorganics.size(); i++)
{
df::inorganic_raw *p = raws.inorganics[i];
if (p->id == token)
return decode(0, i);
}
return decode(-1);
}
bool MaterialInfo::findPlant(const std::string &token, const std::string &subtoken)
{
df::world_raws &raws = df::global::world->raws;
for (unsigned i = 0; i < raws.plants.all.size(); i++)
{
df::plant_raw *p = raws.plants.all[i];
if (p->id != token)
continue;
for (unsigned j = 0; j < p->material.size(); j++)
if (p->material[j]->id == subtoken)
return decode(PLANT_BASE+j, i);
break;
}
return decode(-1);
}
bool MaterialInfo::findCreature(const std::string &token, const std::string &subtoken)
{
df::world_raws &raws = df::global::world->raws;
for (unsigned i = 0; i < raws.creatures.all.size(); i++)
{
df::creature_raw *p = raws.creatures.all[i];
if (p->creature_id != token)
continue;
for (unsigned j = 0; j < p->material.size(); j++)
if (p->material[j]->id == subtoken)
return decode(CREATURE_BASE+j, i);
break;
}
return decode(-1);
}
std::string MaterialInfo::toString(uint16_t temp, bool named)
{
if (type == -1)
return "NONE";
if (!material)
return stl_sprintf("INVALID %d:%d", type, index);
df::matter_state state = matter_state::Solid;
if (temp >= material->heat.melting_point)
state = matter_state::Liquid;
if (temp >= material->heat.boiling_point)
state = matter_state::Gas;
std::string name = material->state_name[state];
if (!material->prefix.empty())
name = material->prefix + " " + name;
if (named && figure)
name += stl_sprintf(" of HF %d", index);
return name;
}
df::craft_material_class MaterialInfo::getCraftClass()
{
if (!material)
return craft_material_class::None;
if (type == 0 && index == -1)
return craft_material_class::Stone;
FOR_ENUM_ITEMS(material_flags, i)
{
df::craft_material_class ccv = ENUM_ATTR(material_flags, type, i);
if (ccv == craft_material_class::None)
continue;
if (material->flags.is_set(i))
return ccv;
}
return craft_material_class::None;
}
bool MaterialInfo::isAnyCloth()
{
using namespace df::enums::material_flags;
return material && (
material->flags.is_set(THREAD_PLANT) ||
material->flags.is_set(SILK) ||
material->flags.is_set(YARN)
);
}
bool MaterialInfo::matches(const df::job_material_category &cat)
{
if (!material)
return false;
using namespace df::enums::material_flags;
#define TEST(bit,flag) if (cat.bits.bit && material->flags.is_set(flag)) return true;
TEST(plant, STRUCTURAL_PLANT_MAT);
TEST(wood, WOOD);
TEST(cloth, THREAD_PLANT);
TEST(silk, SILK);
TEST(leather, LEATHER);
TEST(bone, BONE);
TEST(shell, SHELL);
TEST(wood2, WOOD);
TEST(soap, SOAP);
TEST(tooth, TOOTH);
TEST(horn, HORN);
TEST(pearl, PEARL);
TEST(yarn, YARN);
#undef TEST
return false;
}
bool MaterialInfo::matches(const df::job_item &item)
{
if (!isValid()) return false;
df::job_item_flags1 ok1, mask1;
getMatchBits(ok1, mask1);
df::job_item_flags2 ok2, mask2;
getMatchBits(ok2, mask2);
df::job_item_flags3 ok3, mask3;
getMatchBits(ok3, mask3);
return ((item.flags1.whole & mask1.whole) == (item.flags1.whole & ok1.whole)) &&
((item.flags2.whole & mask2.whole) == (item.flags2.whole & ok2.whole)) &&
((item.flags3.whole & mask3.whole) == (item.flags3.whole & ok3.whole));
}
void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &mask)
{
ok.whole = mask.whole = 0;
if (!isValid()) return;
#define MAT_FLAG(name) material->flags.is_set(df::enums::material_flags::name)
#define FLAG(field, name) (field && field->flags.is_set(name))
#define TEST(bit, check) \
mask.bits.bit = true; ok.bits.bit = !!(check);
bool structural = MAT_FLAG(STRUCTURAL_PLANT_MAT);
TEST(millable, structural && FLAG(plant, plant_raw_flags::MILL));
TEST(sharpenable, MAT_FLAG(IS_STONE));
TEST(distillable, structural && FLAG(plant, plant_raw_flags::DRINK));
TEST(processable, structural && FLAG(plant, plant_raw_flags::THREAD));
TEST(bag, isAnyCloth());
TEST(cookable, MAT_FLAG(EDIBLE_COOKED));
TEST(extract_bearing_plant, structural && FLAG(plant, plant_raw_flags::EXTRACT_STILL_VIAL));
TEST(extract_bearing_fish, false);
TEST(extract_bearing_vermin, false);
TEST(processable_to_vial, structural && FLAG(plant, plant_raw_flags::EXTRACT_VIAL));
TEST(processable_to_bag, structural && FLAG(plant, plant_raw_flags::LEAVES));
TEST(processable_to_barrel, structural && FLAG(plant, plant_raw_flags::EXTRACT_BARREL));
TEST(solid, !(MAT_FLAG(ALCOHOL_PLANT) ||
MAT_FLAG(ALCOHOL_CREATURE) ||
MAT_FLAG(LIQUID_MISC_PLANT) ||
MAT_FLAG(LIQUID_MISC_CREATURE) ||
MAT_FLAG(LIQUID_MISC_OTHER)));
TEST(tameable_vermin, false);
TEST(sharpenable, MAT_FLAG(IS_GLASS));
TEST(milk, linear_index(material->reaction_product.id, std::string("CHEESE_MAT")) >= 0);
//04000000 - "milkable" - vtable[107],1,1
}
void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &mask)
{
ok.whole = mask.whole = 0;
if (!isValid()) return;
bool is_cloth = isAnyCloth();
TEST(dye, MAT_FLAG(IS_DYE));
TEST(dyeable, is_cloth);
TEST(dyed, is_cloth);
TEST(sewn_imageless, is_cloth);
TEST(glass_making, MAT_FLAG(CRYSTAL_GLASSABLE));
TEST(fire_safe, material->heat.melting_point > 11000);
TEST(magma_safe, material->heat.melting_point > 12000);
TEST(deep_material, FLAG(inorganic, df::enums::inorganic_flags::DEEP_ANY));
TEST(non_economic, inorganic && !(df::global::ui && df::global::ui->economic_stone[index]));
TEST(plant, plant);
TEST(silk, MAT_FLAG(SILK));
TEST(leather, MAT_FLAG(LEATHER));
TEST(bone, MAT_FLAG(BONE));
TEST(shell, MAT_FLAG(SHELL));
TEST(totemable, false);
TEST(horn, MAT_FLAG(HORN));
TEST(pearl, MAT_FLAG(PEARL));
TEST(soap, MAT_FLAG(SOAP));
TEST(ivory_tooth, MAT_FLAG(TOOTH));
//TEST(hair_wool, MAT_FLAG(YARN));
TEST(yarn, MAT_FLAG(YARN));
}
void MaterialInfo::getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &mask)
{
ok.whole = mask.whole = 0;
if (!isValid()) return;
TEST(hard, MAT_FLAG(ITEMS_HARD));
}
#undef MAT_FLAG
#undef FLAG
#undef TEST
Module* DFHack::createMaterials() Module* DFHack::createMaterials()
{ {

@ -61,6 +61,7 @@ DFHACK_PLUGIN(initflags initflags.cpp)
DFHACK_PLUGIN(stockpiles stockpiles.cpp) DFHACK_PLUGIN(stockpiles stockpiles.cpp)
DFHACK_PLUGIN(rename rename.cpp) DFHACK_PLUGIN(rename rename.cpp)
DFHACK_PLUGIN(fixwagons fixwagons.cpp) DFHACK_PLUGIN(fixwagons fixwagons.cpp)
DFHACK_PLUGIN(jobutils jobutils.cpp)
#DFHACK_PLUGIN(versionosd versionosd.cpp) #DFHACK_PLUGIN(versionosd versionosd.cpp)
# this is the skeleton plugin. If you want to make your own, make a copy and then change it # this is the skeleton plugin. If you want to make your own, make a copy and then change it

@ -51,10 +51,7 @@ DFhackCExport command_result twaterlvl(Core * c, vector <string> & parameters)
DFhackCExport command_result tidlers(Core * c, vector <string> & parameters) DFhackCExport command_result tidlers(Core * c, vector <string> & parameters)
{ {
// HOTKEY COMMAND: CORE ALREADY SUSPENDED // HOTKEY COMMAND: CORE ALREADY SUSPENDED
df::d_init_idlers iv = df::d_init_idlers(int(d_init->idlers) + 1); d_init->idlers = ENUM_NEXT_ITEM(d_init_idlers, d_init->idlers);
if (!d_init_idlers::is_valid(iv)) c->con << "Toggled the display of idlers to " << ENUM_KEY_STR(d_init_idlers, d_init->idlers) << endl;
iv = ENUM_FIRST_ITEM(d_init_idlers);
d_init->idlers = iv;
c->con << "Toggled the display of idlers to " << ENUM_KEY_STR(d_init_idlers, iv) << endl;
return CR_OK; return CR_OK;
} }

@ -0,0 +1,534 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <MiscUtils.h>
#include <modules/Materials.h>
#include <DataDefs.h>
#include <df/world.h>
#include <df/ui.h>
#include <df/ui_build_selector.h>
#include <df/ui_build_item_req.h>
#include <df/build_req_choice_genst.h>
#include <df/build_req_choice_specst.h>
#include <df/building_workshopst.h>
#include <df/building_furnacest.h>
#include <df/job.h>
#include <df/job_item.h>
#include <df/job_list_link.h>
#include <df/item.h>
#include <df/tool_uses.h>
#include <df/general_ref.h>
#include <df/general_ref_unit_workerst.h>
using std::vector;
using std::string;
using std::endl;
using namespace DFHack;
using namespace df::enums;
using df::global::world;
using df::global::ui;
using df::global::ui_build_selector;
using df::global::ui_workshop_job_cursor;
using df::global::job_next_id;
/* Plugin registration */
static bool workshop_job_hotkey(Core *c, df::viewscreen *top);
static bool build_selector_hotkey(Core *c, df::viewscreen *top);
static bool job_material_hotkey(Core *c, df::viewscreen *top);
static command_result job_material(Core *c, vector <string> & parameters);
static command_result job_duplicate(Core *c, vector <string> & parameters);
static command_result job_cmd(Core *c, vector <string> & parameters);
DFhackCExport const char * plugin_name ( void )
{
return "jobutils";
}
DFhackCExport command_result plugin_init (Core *c, std::vector <PluginCommand> &commands)
{
commands.clear();
if (!world || !ui)
return CR_FAILURE;
commands.push_back(
PluginCommand(
"job", "General job query and manipulation.",
job_cmd, false,
" job query\n"
" Print details of the current job.\n"
" job list\n"
" Print details of all jobs in the workshop.\n"
" job item-material <item-idx> <material> [submaterial]\n"
" Replace the exact material id in the job item.\n"
)
);
if (ui_workshop_job_cursor || ui_build_selector) {
commands.push_back(
PluginCommand(
"job-material", "Alter the material of the selected job.",
job_material, job_material_hotkey,
" job-material <inorganic-token>\n"
"Intended to be used as a keybinding:\n"
" - In 'q' mode, when a job is highlighted within a workshop\n"
" or furnace, changes the material of the job.\n"
" - In 'b' mode, during selection of building components\n"
" positions the cursor over the first available choice\n"
" with the matching material.\n"
)
);
}
if (ui_workshop_job_cursor && job_next_id) {
commands.push_back(
PluginCommand(
"job-duplicate", "Duplicate the selected job in a workshop.",
job_duplicate, workshop_job_hotkey,
" - In 'q' mode, when a job is highlighted within a workshop\n"
" or furnace building, instantly duplicates the job.\n"
)
);
}
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( Core * c )
{
return CR_OK;
}
/* UI state guards */
static bool workshop_job_hotkey(Core *c, df::viewscreen *top)
{
using namespace ui_sidebar_mode;
if (!dwarfmode_hotkey(c,top))
return false;
switch (ui->main.mode) {
case QueryBuilding:
{
if (!ui_workshop_job_cursor) // allow missing
return false;
df::building *selected = world->selected_building;
if (!virtual_cast<df::building_workshopst>(selected) &&
!virtual_cast<df::building_furnacest>(selected))
return false;
// No jobs?
if (selected->jobs.empty() ||
selected->jobs[0]->job_type == job_type::DestroyBuilding)
return false;
// Add job gui activated?
if (df::global::ui_workshop_in_add && // allow missing
*df::global::ui_workshop_in_add)
return false;
return true;
};
default:
return false;
}
}
static bool build_selector_hotkey(Core *c, df::viewscreen *top)
{
using namespace ui_sidebar_mode;
if (!dwarfmode_hotkey(c,top))
return false;
switch (ui->main.mode) {
case Build:
{
if (!ui_build_selector) // allow missing
return false;
// Not selecting, or no choices?
if (ui_build_selector->building_type < 0 ||
ui_build_selector->stage != 2 ||
ui_build_selector->choices.empty())
return false;
return true;
};
default:
return false;
}
}
static bool job_material_hotkey(Core *c, df::viewscreen *top)
{
return workshop_job_hotkey(c, top) ||
build_selector_hotkey(c, top);
}
/* job-material implementation */
static df::job *getWorkshopJob(Core *c)
{
df::building *selected = world->selected_building;
int idx = *ui_workshop_job_cursor;
if (idx < 0 || idx >= selected->jobs.size())
{
c->con.printerr("Invalid job cursor index: %d\n", idx);
return NULL;
}
return selected->jobs[idx];
}
static command_result job_material_in_job(Core *c, MaterialInfo &new_mat)
{
df::job *job = getWorkshopJob(c);
if (!job)
return CR_FAILURE;
MaterialInfo cur_mat(job);
if (!cur_mat.isValid() || cur_mat.type != 0)
{
c->con.printerr("Current job material isn't inorganic: %s\n",
cur_mat.toString().c_str());
return CR_FAILURE;
}
df::craft_material_class old_class = cur_mat.getCraftClass();
if (old_class == craft_material_class::None)
{
c->con.printerr("Unexpected current material type: %s\n",
cur_mat.toString().c_str());
return CR_FAILURE;
}
if (new_mat.getCraftClass() != old_class)
{
c->con.printerr("New material %s does not satisfy requirement: %s\n",
new_mat.toString().c_str(), ENUM_KEY_STR(craft_material_class, old_class));
return CR_FAILURE;
}
for (unsigned i = 0; i < job->job_items.size(); i++)
{
df::job_item *item = job->job_items[i];
MaterialInfo item_mat(item);
if (item_mat != cur_mat)
{
c->con.printerr("Job item %d has different material: %s\n",
i, item_mat.toString().c_str());
return CR_FAILURE;
}
}
// Apply the substitution
job->mat_type = new_mat.type;
job->mat_index = new_mat.index;
for (unsigned i = 0; i < job->job_items.size(); i++)
{
df::job_item *item = job->job_items[i];
item->mat_type = new_mat.type;
item->mat_index = new_mat.index;
}
c->con << "Applied material '" << new_mat.toString()
<< "' to job " << ENUM_KEY_STR(job_type,job->job_type) << endl;
return CR_OK;
}
static bool build_choice_matches(df::ui_build_item_req *req, df::build_req_choicest *choice,
MaterialInfo &new_mat)
{
if (VIRTUAL_CAST_VAR(gen, df::build_req_choice_genst, choice))
{
if (gen->mat_type == new_mat.type &&
gen->mat_index == new_mat.index &&
gen->used_count < gen->candidates.size())
{
return true;
}
}
else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, choice))
{
if (spec->candidate &&
spec->candidate->getActualMaterial() == new_mat.type &&
spec->candidate->getActualMaterialIndex() == new_mat.index &&
!req->candidate_selected[spec->candidate_id])
{
return true;
}
}
return false;
}
static command_result job_material_in_build(Core *c, MaterialInfo &new_mat)
{
df::ui_build_item_req *req = ui_build_selector->requirements[ui_build_selector->req_index];
for (unsigned i = 0; i < ui_build_selector->choices.size(); i++)
{
if (build_choice_matches(req, ui_build_selector->choices[i], new_mat))
{
ui_build_selector->sel_index = i;
return CR_OK;
}
}
c->con.printerr("Could not find material in list: %s\n", new_mat.toString().c_str());
return CR_FAILURE;
}
static command_result job_material(Core * c, vector <string> & parameters)
{
// HOTKEY COMMAND: CORE ALREADY SUSPENDED
MaterialInfo new_mat;
if (parameters.size() == 1)
{
if (!new_mat.findInorganic(parameters[0])) {
c->con.printerr("Could not find inorganic material: %s\n", parameters[0].c_str());
return CR_WRONG_USAGE;
}
}
else
return CR_WRONG_USAGE;
if (ui->main.mode == ui_sidebar_mode::QueryBuilding)
return job_material_in_job(c, new_mat);
if (ui->main.mode == ui_sidebar_mode::Build)
return job_material_in_build(c, new_mat);
return CR_WRONG_USAGE;
}
/* job-duplicate implementation */
static df::job *clone_job(df::job *job)
{
df::job *pnew = new df::job(*job);
pnew->id = (*job_next_id)++;
// Clean out transient fields
pnew->flags.whole = 0;
pnew->flags.bits.repeat = job->flags.bits.repeat;
pnew->completion_timer = -1;
pnew->items.clear();
pnew->misc_links.clear();
// Link the job into the global list
pnew->list_link = new df::job_list_link();
pnew->list_link->item = pnew;
linked_list_append(&world->job_list, pnew->list_link);
// Clone refs
for (int i = pnew->references.size()-1; i >= 0; i--)
{
df::general_ref *ref = pnew->references[i];
if (virtual_cast<df::general_ref_unit_workerst>(ref))
pnew->references.erase(pnew->references.begin()+i);
else
pnew->references[i] = ref->clone();
}
// Clone items
for (int i = pnew->job_items.size()-1; i >= 0; i--)
pnew->job_items[i] = new df::job_item(*pnew->job_items[i]);
return pnew;
}
static command_result job_duplicate(Core * c, vector <string> & parameters)
{
if (!parameters.empty())
return CR_WRONG_USAGE;
df::job *job = getWorkshopJob(c);
if (!job)
return CR_FAILURE;
if (!job->misc_links.empty() || job->job_items.empty())
{
c->con.printerr("Cannot duplicate job %s\n", ENUM_KEY_STR(job_type,job->job_type));
return CR_FAILURE;
}
df::building *building = world->selected_building;
if (building->jobs.size() >= 10)
{
c->con.printerr("Job list is already full.\n");
return CR_FAILURE;
}
// Actually clone
df::job *pnew = clone_job(job);
int pos = ++*ui_workshop_job_cursor;
building->jobs.insert(building->jobs.begin()+pos, pnew);
return CR_OK;
}
/* Main job command implementation */
static void print_job_item_details(Core *c, df::job *job, unsigned idx, df::job_item *item)
{
c->con << " Input Item " << (idx+1) << ": " << ENUM_KEY_STR(item_type,item->item_type);
if (item->item_subtype != -1)
c->con << " [" << item->item_subtype << "]";
if (item->quantity != 1)
c->con << "; quantity=" << item->quantity;
if (item->min_dimension >= 0)
c->con << "; min_dimension=" << item->min_dimension;
c->con << endl;
MaterialInfo mat(item);
if (mat.isValid() || item->metal_ore >= 0) {
c->con << " material: " << mat.toString();
if (item->metal_ore >= 0)
c->con << "; ore of " << MaterialInfo(0,item->metal_ore).toString();
c->con << endl;
}
if (item->flags1.whole)
c->con << " flags1: " << bitfieldToString(item->flags1) << endl;
if (item->flags2.whole)
c->con << " flags2: " << bitfieldToString(item->flags2) << endl;
if (item->flags3.whole)
c->con << " flags3: " << bitfieldToString(item->flags3) << endl;
if (!item->reaction_class.empty())
c->con << " reaction class: " << item->reaction_class << endl;
if (!item->has_material_reaction_product.empty())
c->con << " reaction product: " << item->has_material_reaction_product << endl;
if (item->has_tool_use >= 0)
c->con << " tool use: " << ENUM_KEY_STR(tool_uses, item->has_tool_use) << endl;
}
static void print_job_details(Core *c, df::job *job)
{
c->con << "Job " << job->id << ": " << ENUM_KEY_STR(job_type,job->job_type);
if (job->flags.whole)
c->con << " (" << bitfieldToString(job->flags) << ")";
c->con << endl;
MaterialInfo mat(job);
if (mat.isValid() || job->material_category.whole)
{
c->con << " material: " << mat.toString();
if (job->material_category.whole)
c->con << " (" << bitfieldToString(job->material_category) << ")";
c->con << endl;
}
if (job->item_subtype >= 0 || job->item_category.whole)
c->con << " item: " << job->item_subtype
<< " (" << bitfieldToString(job->item_category) << ")" << endl;
if (job->hist_figure_id >= 0)
c->con << " figure: " << job->hist_figure_id << endl;
if (!job->reaction_name.empty())
c->con << " reaction: " << job->reaction_name << endl;
for (unsigned i = 0; i < job->job_items.size(); i++)
print_job_item_details(c, job, i, job->job_items[i]);
}
static df::job *getWorkshopJobSafe(Core *c)
{
if (!workshop_job_hotkey(c, c->getTopViewscreen())) {
c->con.printerr("No job is highlighted.\n");
return NULL;
}
return getWorkshopJob(c);
}
static command_result job_cmd(Core * c, vector <string> & parameters)
{
CoreSuspender suspend(c);
if (parameters.empty())
return CR_WRONG_USAGE;
std::string cmd = parameters[0];
if (cmd == "query" || cmd == "list")
{
df::job *job = getWorkshopJobSafe(c);
if (!job)
return CR_WRONG_USAGE;
if (cmd == "query") {
print_job_details(c, job);
} else {
df::building *selected = world->selected_building;
for (unsigned i = 0; i < selected->jobs.size(); i++)
print_job_details(c, selected->jobs[i]);
}
}
else if (cmd == "item-material")
{
if (parameters.size() < 1+1+1)
return CR_WRONG_USAGE;
df::job *job = getWorkshopJobSafe(c);
if (!job)
return CR_WRONG_USAGE;
int v = atoi(parameters[1].c_str());
if (v < 1 || v > job->job_items.size()) {
c->con.printerr("Invalid item index.\n");
return CR_WRONG_USAGE;
}
df::job_item *item = job->job_items[v-1];
std::string subtoken = (parameters.size()>3 ? parameters[3] : "");
MaterialInfo info;
if (!info.find(parameters[2], subtoken)) {
c->con.printerr("Could not find the specified material.\n");
return CR_FAILURE;
}
if (!info.matches(*item)) {
c->con.printerr("Material does not match the requirements.\n");
print_job_details(c, job);
return CR_FAILURE;
}
if (job->mat_type != -1 &&
job->mat_type == item->mat_type &&
job->mat_index == item->mat_index)
{
job->mat_type = info.type;
job->mat_index = info.index;
}
item->mat_type = info.type;
item->mat_index = info.index;
c->con << "Job item " << v << " updated." << endl;
print_job_details(c, job);
return CR_OK;
}
else
return CR_WRONG_USAGE;
return CR_OK;
}