@ -41,44 +41,92 @@ DFHACK_PLUGIN_IS_ENABLED(enabled);
REQUIRE_GLOBAL ( world ) ;
REQUIRE_GLOBAL ( ui ) ;
const char * tagline = " Allow the bookkeeper to queue jobs to keep dwarfs in adequate clothing. " ;
const char * usage = (
const char * tagline = " Allow the bookkeeper to queue jobs to keep dwarfs in adequate clothing. " ;
const char * usage = (
" tailor enable \n "
" Enable the plugin. \n "
" tailor disable \n "
" Disable the plugin. \n "
" tailor status \n "
" Display plugin status \n "
" tailor materials ... \n "
" for example: tailor materials silk cloth yarn leather \n "
" Set allowed material list to the specified list. \n "
" The example sets the list to silk, cloth, yarn, leather, in that order, which is the default. \n "
" \n "
" Whenever the bookkeeper updates stockpile records, this plugin will scan every unit in the fort, \n "
" count up the number that are worn, and then order enough more made to replace all worn items. \n "
" If there are enough replacement items in inventory to replace all worn items, the units wearing them \n "
" will have the worn items confiscated (in the same manner as the _cleanowned_ plugin) so that they'll \n "
" reeequip with replacement items. \n "
) ;
) ;
class Tailor {
// ARMOR, SHOES, HELM, GLOVES, PANTS
// ARMOR, SHOES, HELM, GLOVES, PANTS
// ah, if only STL had a bimap
// ah, if only STL had a bimap
private :
static map < df : : job_type , df : : item_type > jobTypeMap = {
con st map < df : : job_type , df : : item_type > jobTypeMap = {
{ df : : job_type : : MakeArmor , df : : item_type : : ARMOR } ,
{ df : : job_type : : MakePants , df : : item_type : : PANTS } ,
{ df : : job_type : : MakeHelm , df : : item_type : : HELM } ,
{ df : : job_type : : MakeGloves , df : : item_type : : GLOVES } ,
{ df : : job_type : : MakeShoes , df : : item_type : : SHOES }
} ;
} ;
static map < df : : item_type , df : : job_type > itemTypeMap = {
con st map < df : : item_type , df : : job_type > itemTypeMap = {
{ df : : item_type : : ARMOR , df : : job_type : : MakeArmor } ,
{ df : : item_type : : PANTS , df : : job_type : : MakePants } ,
{ df : : item_type : : HELM , df : : job_type : : MakeHelm } ,
{ df : : item_type : : GLOVES , df : : job_type : : MakeGloves } ,
{ df : : item_type : : SHOES , df : : job_type : : MakeShoes }
} ;
{ df : : item_type : : HELM , df : : job_type : : MakeHelm } ,
{ df : : item_type : : GLOVES , df : : job_type : : MakeGloves } ,
{ df : : item_type : : SHOES , df : : job_type : : MakeShoes }
} ;
# define F(x) df::item_flags::mask_##x
const df : : item_flags bad_flags = {
(
F ( dump ) | F ( forbid ) | F ( garbage_collect ) |
F ( hostile ) | F ( on_fire ) | F ( rotten ) | F ( trader ) |
F ( in_building ) | F ( construction ) | F ( owned )
)
# undef F
} ;
class MatType {
public :
std : : string name ;
df : : job_material_category job_material ;
df : : armor_general_flags armor_flag ;
bool operator = = ( const MatType & m ) const
{
return name = = m . name ;
}
// operator< is required to use this as a std::map key
bool operator < ( const MatType & m ) const
{
return name < m . name ;
}
MatType ( std : : string & n , df : : job_material_category jm , df : : armor_general_flags af )
: name ( n ) , job_material ( jm ) , armor_flag ( af ) { } ;
MatType ( const char * n , df : : job_material_category jm , df : : armor_general_flags af )
: name ( std : : string ( n ) ) , job_material ( jm ) , armor_flag ( af ) { } ;
} ;
const MatType
M_SILK = MatType ( " silk " , df : : job_material_category : : mask_silk , df : : armor_general_flags : : SOFT ) ,
M_CLOTH = MatType ( " cloth " , df : : job_material_category : : mask_cloth , df : : armor_general_flags : : SOFT ) ,
M_YARN = MatType ( " yarn " , df : : job_material_category : : mask_yarn , df : : armor_general_flags : : SOFT ) ,
M_LEATHER = MatType ( " leather " , df : : job_material_category : : mask_leather , df : : armor_general_flags : : LEATHER ) ;
std : : list < MatType > all_materials = { M_SILK , M_CLOTH , M_YARN , M_LEATHER } ;
void do_scan ( color_ostream & out )
{
map < pair < df : : item_type , int > , int > available ; // key is item type & size
map < pair < df : : item_type , int > , int > needed ; // same
map < pair < df : : item_type , int > , int > queued ; // same
@ -87,24 +135,27 @@ void do_scan(color_ostream& out)
map < tuple < df : : job_type , int , int > , int > orders ; // key is item type, item subtype, size
df : : item_flags bad_flags ;
bad_flags . whole = 0 ;
std : : map < MatType , int > supply ;
# 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 ( owned ) ;
# undef F
color_ostream * out ;
available . empty ( ) ;
needed . empty ( ) ;
queued . empty ( ) ;
orders . empty ( ) ;
std : : list < MatType > material_order = { M_SILK , M_CLOTH , M_YARN , M_LEATHER } ;
std : : map < MatType , int > reserves ;
int silk = 0 , yarn = 0 , cloth = 0 , leather = 0;
int default_reserve = 10 ;
// scan for useable clothing
void reset ( )
{
available . clear ( ) ;
needed . clear ( ) ;
queued . clear ( ) ;
sizes . clear ( ) ;
orders . clear ( ) ;
supply . clear ( ) ;
}
void scan_clothing ( )
{
for ( auto i : world - > items . other [ df : : items_other_id : : ANY_GENERIC37 ] ) // GENERIC37 is "clothing"
{
if ( i - > flags . whole & bad_flags . whole )
@ -118,9 +169,10 @@ void do_scan(color_ostream& out)
available [ make_pair ( t , size ) ] + = 1 ;
}
}
// scan for clothing raw materials
void scan_materials ( )
{
for ( auto i : world - > items . other [ df : : items_other_id : : CLOTH ] )
{
if ( i - > flags . whole & bad_flags . whole )
@ -133,11 +185,11 @@ void do_scan(color_ostream& out)
if ( mat . material )
{
if ( mat . material - > flags . is_set ( df : : material_flags : : SILK ) )
silk + = ss ;
supply [ M_SILK ] + = ss ;
else if ( mat . material - > flags . is_set ( df : : material_flags : : THREAD_PLANT ) )
cloth + = ss ;
supply [ M_CLOTH ] + = ss ;
else if ( mat . material - > flags . is_set ( df : : material_flags : : YARN ) )
yarn + = ss ;
supply [ M_YARN ] + = ss ;
}
}
@ -145,13 +197,14 @@ void do_scan(color_ostream& out)
{
if ( i - > flags . whole & bad_flags . whole )
continue ;
leather + = i - > getStackSize ( ) ;
supply [ M_LEATHER ] + = i - > getStackSize ( ) ;
}
out . print ( " available: silk %d yarn %d cloth %d leather %d \n " , silk , yarn , cloth , leather ) ;
// scan for units who need replacement clothing
out - > print ( " tailor: available silk %d yarn %d cloth %d leather %d \n " , supply [ M_SILK ] , supply [ M_YARN ] , supply [ M_CLOTH ] , supply [ M_LEATHER ] ) ;
}
void scan_replacements ( )
{
for ( auto u : world - > units . active )
{
if ( ! Units : : isOwnCiv ( u ) | |
@ -161,10 +214,10 @@ void do_scan(color_ostream& out)
continue ; // skip units we don't control
set < df : : item_type > wearing ;
wearing . empty ( ) ;
wearing . clear ( ) ;
deque < df : : item * > worn ;
worn . empty ( ) ;
worn . clear ( ) ;
for ( auto inv : u - > inventory )
{
@ -188,10 +241,7 @@ void do_scan(color_ostream& out)
for ( auto w : worn )
{
auto ty = w - > getType ( ) ;
auto oo = itemTypeMap . find ( ty ) ;
if ( oo = = itemTypeMap . end ( ) )
continue ;
df : : job_type o = oo - > second ;
auto o = itemTypeMap . at ( ty ) ;
int size = world - > raws . creatures . all [ w - > getMakerRace ( ) ] - > adultsize ;
std : : string description ;
@ -203,9 +253,9 @@ void do_scan(color_ostream& out)
{
bool confiscated = Items : : setOwner ( w , NULL ) ;
out . print (
" %s %s from %s.\n " ,
( confiscated ? " Confiscated" : " C ould not confiscate" ) ,
out - > print (
" tailor: %s %s from %s.\n " ,
( confiscated ? " confiscated" : " c ould not confiscate" ) ,
description . c_str ( ) ,
Translation : : TranslateName ( & u - > name , false ) . c_str ( )
) ;
@ -219,18 +269,21 @@ void do_scan(color_ostream& out)
}
else
{
// out. print("%s worn by %s needs replacement\n",
// description.c_str(),
// Translation::TranslateName(&u->name, false).c_str()
// );
// out-> print("%s worn by %s needs replacement\n",
// description.c_str(),
// Translation::TranslateName(&u->name, false).c_str()
// );
orders [ make_tuple ( o , w - > getSubtype ( ) , size ) ] + = 1 ;
}
}
}
}
void create_orders ( )
{
auto entity = world - > entities . all [ ui - > civ_id ] ;
for ( auto a : needed )
for ( auto & a : needed )
{
df : : item_type ty = a . first . first ;
int size = a . first . second ;
@ -251,11 +304,11 @@ void do_scan(color_ostream& out)
for ( auto vv : v ) {
bool isClothing = false ;
switch ( ty ) {
case df : : item_type : : ARMOR : isClothing = world - > raws . itemdefs . armor [ vv ] - > armorlevel = = 0 ; break ;
case df : : item_type : : ARMOR : isClothing = world - > raws . itemdefs . armor [ vv ] - > armorlevel = = 0 ; break ;
case df : : item_type : : GLOVES : isClothing = world - > raws . itemdefs . gloves [ vv ] - > armorlevel = = 0 ; break ;
case df : : item_type : : HELM : isClothing = world - > raws . itemdefs . helms [ vv ] - > armorlevel = = 0 ; break ;
case df : : item_type : : PANTS : isClothing = world - > raws . itemdefs . pants [ vv ] - > armorlevel = = 0 ; break ;
case df : : item_type : : SHOES : isClothing = world - > raws . itemdefs . shoes [ vv ] - > armorlevel = = 0 ; break ;
case df : : item_type : : HELM : isClothing = world - > raws . itemdefs . helms [ vv ] - > armorlevel = = 0 ; break ;
case df : : item_type : : PANTS : isClothing = world - > raws . itemdefs . pants [ vv ] - > armorlevel = = 0 ; break ;
case df : : item_type : : SHOES : isClothing = world - > raws . itemdefs . shoes [ vv ] - > armorlevel = = 0 ; break ;
default : break ;
}
if ( isClothing )
@ -265,11 +318,13 @@ void do_scan(color_ostream& out)
}
}
orders [ make_tuple ( itemTypeMap [ ty ] , sub , size ) ] + = count ;
const df : : job_type j = itemTypeMap . at ( ty ) ;
orders [ make_tuple ( j , sub , size ) ] + = count ;
}
}
// scan orders
void scan_existing_orders ( )
{
for ( auto o : world - > manager_orders )
{
auto f = jobTypeMap . find ( o - > job_type ) ;
@ -286,9 +341,13 @@ void do_scan(color_ostream& out)
orders [ make_tuple ( o - > job_type , sub , size ) ] - = o - > amount_left ;
}
// place orders
}
void place_orders ( )
{
auto entity = world - > entities . all [ ui - > civ_id ] ;
for ( auto o : orders )
for ( auto & o : orders )
{
df : : job_type ty ;
int sub ;
@ -304,10 +363,11 @@ void do_scan(color_ostream& out)
string name_s , name_p ;
switch ( ty ) {
case df : : job_type : : MakeArmor :
v = entity - > resources . armor_type ;
name_s = world - > raws . itemdefs . armor [ sub ] - > name ;
name_p = world - > raws . itemdefs . armor [ sub ] - > name_plural ;
v = entity - > resources . armor_type ;
fl = & world - > raws . itemdefs . armor [ sub ] - > props . flags ;
break ;
case df : : job_type : : MakeGloves :
@ -338,95 +398,132 @@ void do_scan(color_ostream& out)
break ;
}
bool can_make = false ;
for ( auto vv : v )
{
if ( vv = = sub )
{
can_make = true ;
break ;
}
}
bool can_make = std : : find ( v . begin ( ) , v . end ( ) , sub ) ! = v . end ( ) ;
if ( ! can_make )
{
out . print ( " C annot make %s, skipped\n " , name_p . c_str ( ) ) ;
continue ; // this civilization does not know how to make this item, so sorry
out - > print ( " tailor: civilization cannot make %s, skipped \n " , name_p . c_str ( ) ) ;
continue ;
}
switch ( ty ) {
case df : : item_type : : ARMOR : break ;
case df : : item_type : : GLOVES : break ;
case df : : item_type : : HELM : break ;
case df : : item_type : : PANTS : break ;
case df : : item_type : : SHOES : break ;
default : break ;
}
for ( auto & m : material_order )
{
if ( count < = 0 )
break ;
df : : job_material_category mat ;
auto r = reserves . find ( m ) ;
int res = ( r = = reserves . end ( ) ) ? default_reserve : r - > second ;
if ( silk > count + 10 & & fl - > is_set ( df : : armor_general_flags : : SOFT ) ) {
mat . whole = df : : job_material_category : : mask_silk ;
silk - = count ;
}
else if ( cloth > count + 10 & & fl - > is_set ( df : : armor_general_flags : : SOFT ) ) {
mat . whole = df : : job_material_category : : mask_cloth ;
cloth - = count ;
}
else if ( yarn > count + 10 & & fl - > is_set ( df : : armor_general_flags : : SOFT ) ) {
mat . whole = df : : job_material_category : : mask_yarn ;
yarn - = count ;
}
else if ( leather > count + 10 & & fl - > is_set ( df : : armor_general_flags : : LEATHER ) ) {
mat . whole = df : : job_material_category : : mask_leather ;
leather - = count ;
}
else // not enough appropriate material available
continue ;
if ( supply [ m ] > res & & fl - > is_set ( m . armor_flag ) ) {
int c = count ;
if ( supply [ m ] < count + res )
c = supply [ m ] - res ;
supply [ m ] - = c ;
auto order = new df : : manager_order ( ) ;
auto order = new df : : manager_order ;
order - > job_type = ty ;
order - > item_type = df : : item_type : : NONE ;
order - > item_subtype = sub ;
order - > mat_type = - 1 ;
order - > mat_index = - 1 ;
order - > amount_left = c ount ;
order - > amount_total = c ount ;
order - > amount_left = c ;
order - > amount_total = c ;
order - > status . bits . validated = false ;
order - > status . bits . active = false ;
order - > id = world - > manager_order_next_id + + ;
order - > hist_figure_id = sizes [ size ] ;
order - > material_category = m at;
order - > material_category = m . job_m aterial ;
world - > manager_orders . push_back ( order ) ;
out . print ( " Added order #%d for %d %s %s (sized for %s) \n " ,
out - > print ( " tailor: added order #%d for %d %s %s, sized for %s \n " ,
order - > id ,
c ount ,
c ,
bitfield_to_string ( order - > material_category ) . c_str ( ) ,
( c ount > 1 ) ? name_p . c_str ( ) : name_s . c_str ( ) ,
( c > 1 ) ? name_p . c_str ( ) : name_s . c_str ( ) ,
world - > raws . creatures . all [ order - > hist_figure_id ] - > name [ 1 ] . c_str ( )
) ;
count - = c ;
}
}
}
}
}
}
# define DELTA_TICKS 600
public :
void do_scan ( color_ostream & o )
{
out = & o ;
DFhackCExport command_result plugin_onupdate ( color_ostream & out )
{
if ( ! enabled )
return CR_OK ;
reset ( ) ;
if ( ! Maps : : IsValid ( ) )
return CR_OK ;
// scan for useable clothing
if ( DFHack : : World : : ReadPauseState ( ) )
return CR_OK ;
scan_clothing ( ) ;
// scan for clothing raw materials
scan_materials ( ) ;
// scan for units who need replacement clothing
scan_replacements ( ) ;
// create new orders
create_orders ( ) ;
// scan existing orders and subtract
scan_existing_orders ( ) ;
// place orders
place_orders ( ) ;
}
public :
command_result set_materials ( color_ostream & out , vector < string > & parameters )
{
list < MatType > newmat ;
newmat . clear ( ) ;
for ( auto m = parameters . begin ( ) + 1 ; m ! = parameters . end ( ) ; m + + )
{
auto nameMatch = [ m ] ( MatType & m1 ) { return * m = = m1 . name ; } ;
auto mm = std : : find_if ( all_materials . begin ( ) , all_materials . end ( ) , nameMatch ) ;
if ( mm = = all_materials . end ( ) )
{
out . print ( " tailor: material %s not recognized \n " , m - > c_str ( ) ) ;
return CR_WRONG_USAGE ;
}
else {
newmat . push_back ( * mm ) ;
}
}
material_order = newmat ;
out . print ( " tailor: material list set to %s \n " , get_material_list ( ) . c_str ( ) ) ;
if ( world - > frame_counter % DELTA_TICKS ! = 0 )
return CR_OK ;
}
public :
std : : string get_material_list ( )
{
std : : string s ;
for ( const auto & m : material_order )
{
if ( ! s . empty ( ) ) s + = " , " ;
s + = m . name ;
}
return s ;
}
public :
void process ( color_ostream & out )
{
bool found = false ;
for ( df : : job_list_link * link = & world - > jobs . list ; link ! = NULL ; link = link - > next )
@ -443,48 +540,97 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
{
do_scan ( out ) ;
}
}
} ;
static std : : unique_ptr < Tailor > tailor_instance ;
# define DELTA_TICKS 600
DFhackCExport command_result plugin_onupdate ( color_ostream & out )
{
if ( ! enabled | | ! tailor_instance )
return CR_OK ;
if ( ! Maps : : IsValid ( ) )
return CR_OK ;
if ( DFHack : : World : : ReadPauseState ( ) )
return CR_OK ;
if ( world - > frame_counter % DELTA_TICKS ! = 0 )
return CR_OK ;
{
CoreSuspender suspend ;
tailor_instance - > process ( out ) ;
}
return CR_OK ;
}
static command_result tailor_cmd ( color_ostream & out , vector < string > & parameters ) {
static command_result tailor_cmd ( color_ostream & out , vector < string > & parameters ) {
bool desired = enabled ;
if ( parameters . size ( ) = = 1 )
{
if ( parameters [ 0 ] = = " enable " | | parameters [ 0 ] = = " on " | | parameters [ 0 ] = = " 1 " )
if ( parameters . size ( ) = = 1 & & parameters [ 0 ] = = " enable " | | parameters [ 0 ] = = " on " | | parameters [ 0 ] = = " 1 " )
{
desired = true ;
}
else if ( parameters [ 0 ] = = " disable " | | parameters [ 0 ] = = " off " | | parameters [ 0 ] = = " 0 " )
else if ( parameters . size ( ) = = 1 & & parameters [ 0 ] = = " disable " | | parameters [ 0 ] = = " off " | | parameters [ 0 ] = = " 0 " )
{
desired = false ;
}
else if ( parameters [ 0 ] = = " usage " | | parameters [ 0 ] = = " help " | | parameters [ 0 ] = = " ? " )
else if ( parameters . size ( ) = = 1 & & parameters [ 0 ] = = " usage " | | parameters [ 0 ] = = " help " | | parameters [ 0 ] = = " ? " )
{
out . print ( " %s: %s \n Usage: \n %s " , plugin_name , tagline , usage ) ;
return CR_OK ;
}
else if ( parameters [ 0 ] = = " test " )
else if ( parameters . size ( ) = = 1 & & parameters [ 0 ] = = " test " )
{
do_scan ( out ) ;
if ( tailor_instance )
{
tailor_instance - > do_scan ( out ) ;
return CR_OK ;
}
else if ( parameters [ 0 ] ! = " status " )
else
{
out . print ( " %s: not instantiated \n " , plugin_name ) ;
return CR_FAILURE ;
}
}
else if ( parameters . size ( ) > 1 & & parameters [ 0 ] = = " materials " )
{
if ( tailor_instance )
{
return tailor_instance - > set_materials ( out , parameters ) ;
}
else
{
out . print ( " %s: not instantiated \n " , plugin_name ) ;
return CR_FAILURE ;
}
}
else if ( parameters . size ( ) = = 1 & & parameters [ 0 ] ! = " status " )
{
return CR_WRONG_USAGE ;
}
out . print ( " Tailor is %s %s. \n " , ( desired = = enabled ) ? " currently " : " now " , desired ? " enabled " : " disabled " ) ;
if ( tailor_instance )
{
out . print ( " Material list is: %s \n " , tailor_instance - > get_material_list ( ) . c_str ( ) ) ;
}
else
return CR_WRONG_USAGE ;
{
out . print ( " %s: not instantiated \n " , plugin_name ) ;
}
out . print ( " Tailor is %s %s. \n " , ( desired = = enabled ) ? " currently " : " now " , desired ? " enabled " : " disabled " ) ;
enabled = desired ;
return CR_OK ;
}
DFhackCExport command_result plugin_onstatechange ( color_ostream & out , state_change_event event )
DFhackCExport command_result plugin_onstatechange ( color_ostream & out , state_change_event event )
{
return CR_OK ;
}
@ -495,8 +641,10 @@ DFhackCExport command_result plugin_enable(color_ostream& out, bool enable)
return CR_OK ;
}
DFhackCExport command_result plugin_init ( color_ostream & out , std : : vector < PluginCommand > & commands )
DFhackCExport command_result plugin_init ( color_ostream & out , std : : vector < PluginCommand > & commands )
{
tailor_instance = std : : move ( dts : : make_unique < Tailor > ( ) ) ;
if ( AUTOENABLE ) {
enabled = true ;
}
@ -505,6 +653,9 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
return CR_OK ;
}
DFhackCExport command_result plugin_shutdown ( color_ostream & out ) {
DFhackCExport command_result plugin_shutdown ( color_ostream & out )
{
tailor_instance . release ( ) ;
return plugin_enable ( out , false ) ;
}