Merge branch 'develop' into cmake-cleanup
Conflicts: depends/lua/CMakeLists.txt plugins/CMakeLists.txt plugins/devel/CMakeLists.txtdevelop
commit
815821eb93
@ -1 +1 @@
|
|||||||
Subproject commit 4388fbfb8f51be41777406c6e7c518f738c195c7
|
Subproject commit 4053321b202a29f667d64d824ba8339ec1b1df4f
|
@ -0,0 +1,672 @@
|
|||||||
|
|
||||||
|
// some headers required for a plugin. Nothing special, just the basics.
|
||||||
|
#include "Core.h"
|
||||||
|
#include <Console.h>
|
||||||
|
#include <Export.h>
|
||||||
|
#include <PluginManager.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
// DF data structure definition headers
|
||||||
|
#include "DataDefs.h"
|
||||||
|
|
||||||
|
#include "modules/Items.h"
|
||||||
|
#include "modules/Maps.h"
|
||||||
|
#include "modules/Materials.h"
|
||||||
|
#include "modules/Units.h"
|
||||||
|
#include "modules/World.h"
|
||||||
|
|
||||||
|
#include "df/itemdef_armorst.h"
|
||||||
|
#include "df/itemdef_glovesst.h"
|
||||||
|
#include "df/itemdef_shoesst.h"
|
||||||
|
#include "df/itemdef_helmst.h"
|
||||||
|
#include "df/itemdef_pantsst.h"
|
||||||
|
#include "df/manager_order.h"
|
||||||
|
#include "df/creature_raw.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace DFHack::Items;
|
||||||
|
using namespace DFHack::Units;
|
||||||
|
using namespace df::enums;
|
||||||
|
|
||||||
|
|
||||||
|
// A plugin must be able to return its name and version.
|
||||||
|
// The name string provided must correspond to the filename -
|
||||||
|
// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case
|
||||||
|
DFHACK_PLUGIN("autoclothing");
|
||||||
|
|
||||||
|
// Any globals a plugin requires (e.g. world) should be listed here.
|
||||||
|
// For example, this line expands to "using df::global::world" and prevents the
|
||||||
|
// plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml):
|
||||||
|
//
|
||||||
|
REQUIRE_GLOBAL(world);
|
||||||
|
|
||||||
|
// Only run if this is enabled
|
||||||
|
DFHACK_PLUGIN_IS_ENABLED(autoclothing_enabled);
|
||||||
|
|
||||||
|
// Here go all the command declarations...
|
||||||
|
// mostly to allow having the mandatory stuff on top of the file and commands on the bottom
|
||||||
|
struct ClothingRequirement;
|
||||||
|
command_result autoclothing(color_ostream &out, std::vector <std::string> & parameters);
|
||||||
|
static void init_state(color_ostream &out);
|
||||||
|
static void save_state(color_ostream &out);
|
||||||
|
static void cleanup_state(color_ostream &out);
|
||||||
|
static void do_autoclothing();
|
||||||
|
static bool validateMaterialCategory(ClothingRequirement * requirement);
|
||||||
|
static bool setItem(std::string name, ClothingRequirement* requirement);
|
||||||
|
|
||||||
|
std::vector<ClothingRequirement>clothingOrders;
|
||||||
|
|
||||||
|
struct ClothingRequirement
|
||||||
|
{
|
||||||
|
df::job_type jobType;
|
||||||
|
df::item_type itemType;
|
||||||
|
int16_t item_subtype;
|
||||||
|
df::job_material_category material_category;
|
||||||
|
int16_t needed_per_citizen;
|
||||||
|
std::map<int16_t, int32_t> total_needed_per_race;
|
||||||
|
|
||||||
|
bool matches(ClothingRequirement * b)
|
||||||
|
{
|
||||||
|
if (b->jobType != this->jobType)
|
||||||
|
return false;
|
||||||
|
if (b->itemType != this->itemType)
|
||||||
|
return false;
|
||||||
|
if (b->item_subtype != this->item_subtype)
|
||||||
|
return false;
|
||||||
|
if (b->material_category.whole != this->material_category.whole)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Serialize()
|
||||||
|
{
|
||||||
|
stringstream stream;
|
||||||
|
stream << ENUM_KEY_STR(job_type, jobType) << " ";
|
||||||
|
stream << ENUM_KEY_STR(item_type,itemType) << " ";
|
||||||
|
stream << item_subtype << " ";
|
||||||
|
stream << material_category.whole << " ";
|
||||||
|
stream << needed_per_citizen;
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Deserialize(std::string s)
|
||||||
|
{
|
||||||
|
stringstream stream(s);
|
||||||
|
std::string loadedJob;
|
||||||
|
stream >> loadedJob;
|
||||||
|
FOR_ENUM_ITEMS(job_type, job)
|
||||||
|
{
|
||||||
|
if (ENUM_KEY_STR(job_type, job) == loadedJob)
|
||||||
|
{
|
||||||
|
jobType = job;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string loadedItem;
|
||||||
|
stream >> loadedItem;
|
||||||
|
FOR_ENUM_ITEMS(item_type, item)
|
||||||
|
{
|
||||||
|
if (ENUM_KEY_STR(item_type, item) == loadedItem)
|
||||||
|
{
|
||||||
|
itemType = item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream >> item_subtype;
|
||||||
|
stream >> material_category.whole;
|
||||||
|
stream >> needed_per_citizen;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetFromParameters(color_ostream &out, std::vector <std::string> & parameters)
|
||||||
|
{
|
||||||
|
if (!set_bitfield_field(&material_category, parameters[0], 1))
|
||||||
|
{
|
||||||
|
out << "Unrecognized material type: " << parameters[0] << endl;
|
||||||
|
}
|
||||||
|
if (!setItem(parameters[1], this))
|
||||||
|
{
|
||||||
|
out << "Unrecognized item name or token: " << parameters[1] << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!validateMaterialCategory(this))
|
||||||
|
{
|
||||||
|
out << parameters[0] << " is not a valid material category for " << parameters[1] << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ToReadableLabel()
|
||||||
|
{
|
||||||
|
stringstream stream;
|
||||||
|
stream << bitfield_to_string(material_category) << " ";
|
||||||
|
std::string adjective = "";
|
||||||
|
std::string name = "";
|
||||||
|
switch (itemType)
|
||||||
|
{
|
||||||
|
case df::enums::item_type::ARMOR:
|
||||||
|
adjective = world->raws.itemdefs.armor[item_subtype]->adjective;
|
||||||
|
name = world->raws.itemdefs.armor[item_subtype]->name;
|
||||||
|
break;
|
||||||
|
case df::enums::item_type::SHOES:
|
||||||
|
adjective = world->raws.itemdefs.shoes[item_subtype]->adjective;
|
||||||
|
name = world->raws.itemdefs.shoes[item_subtype]->name;
|
||||||
|
break;
|
||||||
|
case df::enums::item_type::HELM:
|
||||||
|
adjective = world->raws.itemdefs.helms[item_subtype]->adjective;
|
||||||
|
name = world->raws.itemdefs.helms[item_subtype]->name;
|
||||||
|
break;
|
||||||
|
case df::enums::item_type::GLOVES:
|
||||||
|
adjective = world->raws.itemdefs.gloves[item_subtype]->adjective;
|
||||||
|
name = world->raws.itemdefs.gloves[item_subtype]->name;
|
||||||
|
break;
|
||||||
|
case df::enums::item_type::PANTS:
|
||||||
|
adjective = world->raws.itemdefs.pants[item_subtype]->adjective;
|
||||||
|
name = world->raws.itemdefs.pants[item_subtype]->name;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!adjective.empty())
|
||||||
|
stream << adjective << " ";
|
||||||
|
stream << name << " ";
|
||||||
|
stream << needed_per_citizen;
|
||||||
|
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
"autoclothing", "Automatically manage clothing work orders",
|
||||||
|
autoclothing, 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:
|
||||||
|
" autoclothing <material> <item> [number]\n"
|
||||||
|
"Example:\n"
|
||||||
|
" autoclothing cloth \"short skirt\" 10\n"
|
||||||
|
" Sets the desired number of cloth short skirts available per citizen to 10.\n"
|
||||||
|
" autoclothing cloth dress\n"
|
||||||
|
" Displays the currently set number of cloth dresses chosen per citizen.\n"
|
||||||
|
));
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
cleanup_state(out);
|
||||||
|
|
||||||
|
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_WORLD_LOADED:
|
||||||
|
init_state(out);
|
||||||
|
break;
|
||||||
|
case SC_WORLD_UNLOADED:
|
||||||
|
cleanup_state(out);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (!autoclothing_enabled)
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
if (!Maps::IsValid())
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
if (DFHack::World::ReadPauseState())
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
if ((world->frame_counter + 500) % 1200 != 0) // Check every day, but not the same day as other things
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
do_autoclothing();
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setItemFromName(std::string name, ClothingRequirement* requirement)
|
||||||
|
{
|
||||||
|
#define SEARCH_ITEM_RAWS(rawType, job, item) \
|
||||||
|
for (auto& itemdef : world->raws.itemdefs.rawType) \
|
||||||
|
{ \
|
||||||
|
std::string fullName = itemdef->adjective.empty() ? itemdef->name : itemdef->adjective + " " + itemdef->name; \
|
||||||
|
if (fullName == name) \
|
||||||
|
{ \
|
||||||
|
requirement->jobType = job_type::job; \
|
||||||
|
requirement->itemType = item_type::item; \
|
||||||
|
requirement->item_subtype = itemdef->subtype; \
|
||||||
|
return true; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
SEARCH_ITEM_RAWS(armor, MakeArmor, ARMOR);
|
||||||
|
SEARCH_ITEM_RAWS(gloves, MakeGloves, GLOVES);
|
||||||
|
SEARCH_ITEM_RAWS(shoes, MakeShoes, SHOES);
|
||||||
|
SEARCH_ITEM_RAWS(helms, MakeHelm, HELM);
|
||||||
|
SEARCH_ITEM_RAWS(pants, MakePants, PANTS);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setItemFromToken(std::string token, ClothingRequirement* requirement)
|
||||||
|
{
|
||||||
|
ItemTypeInfo itemInfo;
|
||||||
|
if (!itemInfo.find(token))
|
||||||
|
return false;
|
||||||
|
switch (itemInfo.type)
|
||||||
|
{
|
||||||
|
case item_type::ARMOR:
|
||||||
|
requirement->jobType = job_type::MakeArmor;
|
||||||
|
break;
|
||||||
|
case item_type::GLOVES:
|
||||||
|
requirement->jobType = job_type::MakeGloves;
|
||||||
|
break;
|
||||||
|
case item_type::SHOES:
|
||||||
|
requirement->jobType = job_type::MakeShoes;
|
||||||
|
break;
|
||||||
|
case item_type::HELM:
|
||||||
|
requirement->jobType = job_type::MakeHelm;
|
||||||
|
break;
|
||||||
|
case item_type::PANTS:
|
||||||
|
requirement->jobType = job_type::MakePants;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
requirement->itemType = itemInfo.type;
|
||||||
|
requirement->item_subtype = itemInfo.subtype;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setItem(std::string name, ClothingRequirement* requirement)
|
||||||
|
{
|
||||||
|
if (setItemFromName(name, requirement))
|
||||||
|
return true;
|
||||||
|
if (setItemFromToken(name, requirement))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool armorFlagsMatch(BitArray<df::armor_general_flags> * flags, df::job_material_category * category)
|
||||||
|
{
|
||||||
|
if (flags->is_set(df::armor_general_flags::SOFT) && category->bits.cloth)
|
||||||
|
return true;
|
||||||
|
if (flags->is_set(df::armor_general_flags::SOFT) && category->bits.yarn)
|
||||||
|
return true;
|
||||||
|
if (flags->is_set(df::armor_general_flags::SOFT) && category->bits.silk)
|
||||||
|
return true;
|
||||||
|
if (flags->is_set(df::armor_general_flags::BARRED) && category->bits.bone)
|
||||||
|
return true;
|
||||||
|
if (flags->is_set(df::armor_general_flags::SCALED) && category->bits.shell)
|
||||||
|
return true;
|
||||||
|
if (flags->is_set(df::armor_general_flags::LEATHER) && category->bits.leather)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool validateMaterialCategory(ClothingRequirement * requirement)
|
||||||
|
{
|
||||||
|
auto itemDef = getSubtypeDef(requirement->itemType, requirement->item_subtype);
|
||||||
|
switch (requirement->itemType)
|
||||||
|
{
|
||||||
|
case item_type::ARMOR:
|
||||||
|
if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_armorst, itemDef))
|
||||||
|
return armorFlagsMatch(&armor->props.flags, &requirement->material_category);
|
||||||
|
case item_type::GLOVES:
|
||||||
|
if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_glovesst, itemDef))
|
||||||
|
return armorFlagsMatch(&armor->props.flags, &requirement->material_category);
|
||||||
|
case item_type::SHOES:
|
||||||
|
if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_shoesst, itemDef))
|
||||||
|
return armorFlagsMatch(&armor->props.flags, &requirement->material_category);
|
||||||
|
case item_type::HELM:
|
||||||
|
if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_helmst, itemDef))
|
||||||
|
return armorFlagsMatch(&armor->props.flags, &requirement->material_category);
|
||||||
|
case item_type::PANTS:
|
||||||
|
if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_pantsst, itemDef))
|
||||||
|
return armorFlagsMatch(&armor->props.flags, &requirement->material_category);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// A command! It sits around and looks pretty. And it's nice and friendly.
|
||||||
|
command_result autoclothing(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.size() == 0)
|
||||||
|
{
|
||||||
|
out << "Currently set " << clothingOrders.size() << " automatic clothing orders" << endl;
|
||||||
|
for (size_t i = 0; i < clothingOrders.size(); i++)
|
||||||
|
{
|
||||||
|
out << clothingOrders[i].ToReadableLabel() << endl;
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
else if (parameters.size() < 2 || parameters.size() > 3)
|
||||||
|
{
|
||||||
|
out << "Wrong number of arguments." << endl;
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
// Create a new requirement from the available parameters.
|
||||||
|
ClothingRequirement newRequirement;
|
||||||
|
if (!newRequirement.SetFromParameters(out, parameters))
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
//all checks are passed. Now we either show or set the amount.
|
||||||
|
bool settingSize = false;
|
||||||
|
bool matchedExisting = false;
|
||||||
|
if (parameters.size() > 2)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newRequirement.needed_per_citizen = std::stoi(parameters[2]);
|
||||||
|
}
|
||||||
|
catch (const std::exception&)
|
||||||
|
{
|
||||||
|
out << parameters[2] << " is not a valid number." << endl;
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
settingSize = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < clothingOrders.size(); i++)
|
||||||
|
{
|
||||||
|
if (!clothingOrders[i].matches(&newRequirement))
|
||||||
|
continue;
|
||||||
|
matchedExisting = true;
|
||||||
|
if (settingSize)
|
||||||
|
{
|
||||||
|
if (newRequirement.needed_per_citizen == 0)
|
||||||
|
{
|
||||||
|
clothingOrders.erase(clothingOrders.begin() + i);
|
||||||
|
out << "Unset " << parameters[0] << " " << parameters[1] << endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clothingOrders[i] = newRequirement;
|
||||||
|
out << "Set " << parameters[0] << " " << parameters[1] << " to " << parameters[2] << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out << parameters[0] << " " << parameters[1] << " is set to " << clothingOrders[i].needed_per_citizen << endl;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!matchedExisting)
|
||||||
|
{
|
||||||
|
if (settingSize)
|
||||||
|
{
|
||||||
|
if (newRequirement.needed_per_citizen == 0)
|
||||||
|
{
|
||||||
|
out << parameters[0] << " " << parameters[1] << " already unset." << endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clothingOrders.push_back(newRequirement);
|
||||||
|
out << "Added order for " << parameters[0] << " " << parameters[1] << " to " << parameters[2] << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out << parameters[0] << " " << parameters[1] << " is not set." << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (settingSize)
|
||||||
|
{
|
||||||
|
if (!autoclothing_enabled)
|
||||||
|
{
|
||||||
|
out << "Enabling automatic clothing management" << endl;
|
||||||
|
autoclothing_enabled = true;
|
||||||
|
}
|
||||||
|
do_autoclothing();
|
||||||
|
}
|
||||||
|
save_state(out);
|
||||||
|
|
||||||
|
// Give control back to DF.
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void find_needed_clothing_items()
|
||||||
|
{
|
||||||
|
for (auto& unit : world->units.active)
|
||||||
|
{
|
||||||
|
//obviously we don't care about illegal aliens.
|
||||||
|
if (!isCitizen(unit))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
//now check each clothing order to see what the unit might be missing.
|
||||||
|
for (auto& clothingOrder : clothingOrders)
|
||||||
|
{
|
||||||
|
int alreadyOwnedAmount = 0;
|
||||||
|
|
||||||
|
//looping through the items first, then clothing order might be a little faster, but this way is cleaner.
|
||||||
|
for (auto& ownedItem : unit->owned_items)
|
||||||
|
{
|
||||||
|
auto item = findItemByID(ownedItem);
|
||||||
|
|
||||||
|
if (item->getType() != clothingOrder.itemType)
|
||||||
|
continue;
|
||||||
|
if (item->getSubtype() != clothingOrder.item_subtype)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
MaterialInfo matInfo;
|
||||||
|
matInfo.decode(item);
|
||||||
|
|
||||||
|
if (!matInfo.matches(clothingOrder.material_category))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
alreadyOwnedAmount++;
|
||||||
|
}
|
||||||
|
int neededAmount = clothingOrder.needed_per_citizen - alreadyOwnedAmount;
|
||||||
|
|
||||||
|
if (neededAmount <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
//technically, there's some leeway in sizes, but only caring about exact sizes is simpler.
|
||||||
|
clothingOrder.total_needed_per_race[unit->race] += neededAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove_available_clothing()
|
||||||
|
{
|
||||||
|
for (auto& item : world->items.all)
|
||||||
|
{
|
||||||
|
//skip any owned items.
|
||||||
|
if (getOwner(item))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
//again, for each item, find if any clothing order matches
|
||||||
|
for (auto& clothingOrder : clothingOrders)
|
||||||
|
{
|
||||||
|
if (item->getType() != clothingOrder.itemType)
|
||||||
|
continue;
|
||||||
|
if (item->getSubtype() != clothingOrder.item_subtype)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
MaterialInfo matInfo;
|
||||||
|
matInfo.decode(item);
|
||||||
|
|
||||||
|
if (!matInfo.matches(clothingOrder.material_category))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
clothingOrder.total_needed_per_race[item->getMakerRace()] --;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_clothing_orders()
|
||||||
|
{
|
||||||
|
for (auto& clothingOrder : clothingOrders)
|
||||||
|
{
|
||||||
|
for (auto& orderNeeded : clothingOrder.total_needed_per_race)
|
||||||
|
{
|
||||||
|
auto race = orderNeeded.first;
|
||||||
|
auto amount = orderNeeded.second;
|
||||||
|
orderNeeded.second = 0; //once we get what we need, set it back to zero so we don't add it to further counts.
|
||||||
|
//Previous operations can easily make this negative. That jus means we have more than we need already.
|
||||||
|
if (amount <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool orderExistedAlready = false;
|
||||||
|
for (auto& managerOrder : world->manager_orders)
|
||||||
|
{
|
||||||
|
//Annoyingly, the manager orders store the job type for clothing orders, and actual item type is left at -1;
|
||||||
|
if (managerOrder->job_type != clothingOrder.jobType)
|
||||||
|
continue;
|
||||||
|
if (managerOrder->item_subtype != clothingOrder.item_subtype)
|
||||||
|
continue;
|
||||||
|
if (managerOrder->hist_figure_id != race)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
//We found a work order, that means we don't need to make a new one.
|
||||||
|
orderExistedAlready = true;
|
||||||
|
amount -= managerOrder->amount_left;
|
||||||
|
if (amount > 0)
|
||||||
|
{
|
||||||
|
managerOrder->amount_left += amount;
|
||||||
|
managerOrder->amount_total += amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//if it wasn't there, we need to make a new one.
|
||||||
|
if (!orderExistedAlready)
|
||||||
|
{
|
||||||
|
df::manager_order * newOrder = new df::manager_order();
|
||||||
|
|
||||||
|
newOrder->id = world->manager_order_next_id;
|
||||||
|
world->manager_order_next_id++;
|
||||||
|
newOrder->job_type = clothingOrder.jobType;
|
||||||
|
newOrder->item_subtype = clothingOrder.item_subtype;
|
||||||
|
newOrder->hist_figure_id = race;
|
||||||
|
newOrder->material_category = clothingOrder.material_category;
|
||||||
|
newOrder->amount_left = amount;
|
||||||
|
newOrder->amount_total = amount;
|
||||||
|
world->manager_orders.push_back(newOrder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_autoclothing()
|
||||||
|
{
|
||||||
|
if (clothingOrders.size() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//first we look through all the units on the map to see who needs new clothes.
|
||||||
|
find_needed_clothing_items();
|
||||||
|
|
||||||
|
//Now we go through all the items in the map to see how many clothing items we have but aren't owned yet.
|
||||||
|
remove_available_clothing();
|
||||||
|
|
||||||
|
//Finally loop through the clothing orders to find ones that need more made.
|
||||||
|
add_clothing_orders();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanup_state(color_ostream &out)
|
||||||
|
{
|
||||||
|
clothingOrders.clear();
|
||||||
|
autoclothing_enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_state(color_ostream &out)
|
||||||
|
{
|
||||||
|
auto enabled = World::GetPersistentData("autoclothing/enabled");
|
||||||
|
if (enabled.isValid() && enabled.ival(0) == 1)
|
||||||
|
{
|
||||||
|
out << "autoclothing enabled" << endl;
|
||||||
|
autoclothing_enabled = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
autoclothing_enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Parse constraints
|
||||||
|
std::vector<PersistentDataItem> items;
|
||||||
|
World::GetPersistentData(&items, "autoclothing/clothingItems");
|
||||||
|
|
||||||
|
for (auto& item : items)
|
||||||
|
{
|
||||||
|
if (!item.isValid())
|
||||||
|
continue;
|
||||||
|
ClothingRequirement req;
|
||||||
|
req.Deserialize(item.val());
|
||||||
|
clothingOrders.push_back(req);
|
||||||
|
out << "autoclothing added " << req.ToReadableLabel() << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void save_state(color_ostream &out)
|
||||||
|
{
|
||||||
|
auto enabled = World::GetPersistentData("autoclothing/enabled");
|
||||||
|
if (!enabled.isValid())
|
||||||
|
enabled = World::AddPersistentData("autoclothing/enabled");
|
||||||
|
enabled.ival(0) = autoclothing_enabled;
|
||||||
|
|
||||||
|
for (auto& order : clothingOrders)
|
||||||
|
{
|
||||||
|
auto orderSave = World::AddPersistentData("autoclothing/clothingItems");
|
||||||
|
orderSave.val() = order.Serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Parse constraints
|
||||||
|
std::vector<PersistentDataItem> items;
|
||||||
|
World::GetPersistentData(&items, "autoclothing/clothingItems");
|
||||||
|
|
||||||
|
for (size_t i = 0; i < items.size(); i++)
|
||||||
|
{
|
||||||
|
if (i < clothingOrders.size())
|
||||||
|
{
|
||||||
|
items[i].val() = clothingOrders[i].Serialize();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
World::DeletePersistentData(items[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = items.size(); i < clothingOrders.size(); i++)
|
||||||
|
{
|
||||||
|
auto item = World::AddPersistentData("autoclothing/clothingItems");
|
||||||
|
item.val() = clothingOrders[i].Serialize();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,436 @@
|
|||||||
|
#include "Core.h"
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/ui.h"
|
||||||
|
#include "df/building_type.h"
|
||||||
|
#include "df/building_farmplotst.h"
|
||||||
|
#include "df/buildings_other_id.h"
|
||||||
|
#include "df/global_objects.h"
|
||||||
|
#include "df/item.h"
|
||||||
|
#include "df/item_plantst.h"
|
||||||
|
#include "df/items_other_id.h"
|
||||||
|
#include "df/unit.h"
|
||||||
|
#include "df/building.h"
|
||||||
|
#include "df/plant_raw.h"
|
||||||
|
#include "df/plant_raw_flags.h"
|
||||||
|
#include "df/biome_type.h"
|
||||||
|
#include "modules/Items.h"
|
||||||
|
#include "modules/Maps.h"
|
||||||
|
#include "modules/World.h"
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
using std::vector;
|
||||||
|
using std::string;
|
||||||
|
using std::map;
|
||||||
|
using std::set;
|
||||||
|
using std::queue;
|
||||||
|
using std::endl;
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace df::enums;
|
||||||
|
|
||||||
|
using df::global::world;
|
||||||
|
using df::global::ui;
|
||||||
|
|
||||||
|
static command_result autofarm(color_ostream &out, vector <string> & parameters);
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("autofarm");
|
||||||
|
|
||||||
|
DFHACK_PLUGIN_IS_ENABLED(enabled);
|
||||||
|
|
||||||
|
const char *tagline = "Automatically handle crop selection in farm plots based on current plant stocks.";
|
||||||
|
const char *usage = (
|
||||||
|
"``enable autofarm``: Enables the plugin\n"
|
||||||
|
"``autofarm runonce``: Updates farm plots (one-time only)\n"
|
||||||
|
"``autofarm status``: Prints status information\n"
|
||||||
|
"``autofarm default 30``: Sets the default threshold\n"
|
||||||
|
"``autofarm threshold 150 helmet_plump tail_pig``: Sets thresholds\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
class AutoFarm {
|
||||||
|
private:
|
||||||
|
map<int, int> thresholds;
|
||||||
|
int defaultThreshold = 50;
|
||||||
|
|
||||||
|
map<int, int> lastCounts;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void initialize()
|
||||||
|
{
|
||||||
|
thresholds.clear();
|
||||||
|
defaultThreshold = 50;
|
||||||
|
|
||||||
|
lastCounts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setThreshold(int id, int val)
|
||||||
|
{
|
||||||
|
thresholds[id] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getThreshold(int id)
|
||||||
|
{
|
||||||
|
return (thresholds.count(id) > 0) ? thresholds[id] : defaultThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDefault(int val)
|
||||||
|
{
|
||||||
|
defaultThreshold = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const df::plant_raw_flags seasons[4] = { df::plant_raw_flags::SPRING, df::plant_raw_flags::SUMMER, df::plant_raw_flags::AUTUMN, df::plant_raw_flags::WINTER };
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool is_plantable(df::plant_raw* plant)
|
||||||
|
{
|
||||||
|
bool has_seed = plant->flags.is_set(df::plant_raw_flags::SEED);
|
||||||
|
bool is_tree = plant->flags.is_set(df::plant_raw_flags::TREE);
|
||||||
|
|
||||||
|
int8_t season = *df::global::cur_season;
|
||||||
|
int harvest = (*df::global::cur_season_tick) + plant->growdur * 10;
|
||||||
|
bool can_plant = has_seed && !is_tree && plant->flags.is_set(seasons[season]);
|
||||||
|
while (can_plant && harvest >= 10080) {
|
||||||
|
season = (season + 1) % 4;
|
||||||
|
harvest -= 10080;
|
||||||
|
can_plant = can_plant && plant->flags.is_set(seasons[season]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return can_plant;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
map<int, set<df::biome_type>> plantable_plants;
|
||||||
|
|
||||||
|
const map<df::plant_raw_flags, df::biome_type> biomeFlagMap = {
|
||||||
|
{ df::plant_raw_flags::BIOME_MOUNTAIN, df::biome_type::MOUNTAIN },
|
||||||
|
{ df::plant_raw_flags::BIOME_GLACIER, df::biome_type::GLACIER },
|
||||||
|
{ df::plant_raw_flags::BIOME_TUNDRA, df::biome_type::TUNDRA },
|
||||||
|
{ df::plant_raw_flags::BIOME_SWAMP_TEMPERATE_FRESHWATER, df::biome_type::SWAMP_TEMPERATE_FRESHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_SWAMP_TEMPERATE_SALTWATER, df::biome_type::SWAMP_TEMPERATE_SALTWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_MARSH_TEMPERATE_FRESHWATER, df::biome_type::MARSH_TEMPERATE_FRESHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_MARSH_TEMPERATE_SALTWATER, df::biome_type::MARSH_TEMPERATE_SALTWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_SWAMP_TROPICAL_FRESHWATER, df::biome_type::SWAMP_TROPICAL_FRESHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_SWAMP_TROPICAL_SALTWATER, df::biome_type::SWAMP_TROPICAL_SALTWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_SWAMP_MANGROVE, df::biome_type::SWAMP_MANGROVE },
|
||||||
|
{ df::plant_raw_flags::BIOME_MARSH_TROPICAL_FRESHWATER, df::biome_type::MARSH_TROPICAL_FRESHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_MARSH_TROPICAL_SALTWATER, df::biome_type::MARSH_TROPICAL_SALTWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_FOREST_TAIGA, df::biome_type::FOREST_TAIGA },
|
||||||
|
{ df::plant_raw_flags::BIOME_FOREST_TEMPERATE_CONIFER, df::biome_type::FOREST_TEMPERATE_CONIFER },
|
||||||
|
{ df::plant_raw_flags::BIOME_FOREST_TEMPERATE_BROADLEAF, df::biome_type::FOREST_TEMPERATE_BROADLEAF },
|
||||||
|
{ df::plant_raw_flags::BIOME_FOREST_TROPICAL_CONIFER, df::biome_type::FOREST_TROPICAL_CONIFER },
|
||||||
|
{ df::plant_raw_flags::BIOME_FOREST_TROPICAL_DRY_BROADLEAF, df::biome_type::FOREST_TROPICAL_DRY_BROADLEAF },
|
||||||
|
{ df::plant_raw_flags::BIOME_FOREST_TROPICAL_MOIST_BROADLEAF, df::biome_type::FOREST_TROPICAL_MOIST_BROADLEAF },
|
||||||
|
{ df::plant_raw_flags::BIOME_GRASSLAND_TEMPERATE, df::biome_type::GRASSLAND_TEMPERATE },
|
||||||
|
{ df::plant_raw_flags::BIOME_SAVANNA_TEMPERATE, df::biome_type::SAVANNA_TEMPERATE },
|
||||||
|
{ df::plant_raw_flags::BIOME_SHRUBLAND_TEMPERATE, df::biome_type::SHRUBLAND_TEMPERATE },
|
||||||
|
{ df::plant_raw_flags::BIOME_GRASSLAND_TROPICAL, df::biome_type::GRASSLAND_TROPICAL },
|
||||||
|
{ df::plant_raw_flags::BIOME_SAVANNA_TROPICAL, df::biome_type::SAVANNA_TROPICAL },
|
||||||
|
{ df::plant_raw_flags::BIOME_SHRUBLAND_TROPICAL, df::biome_type::SHRUBLAND_TROPICAL },
|
||||||
|
{ df::plant_raw_flags::BIOME_DESERT_BADLAND, df::biome_type::DESERT_BADLAND },
|
||||||
|
{ df::plant_raw_flags::BIOME_DESERT_ROCK, df::biome_type::DESERT_ROCK },
|
||||||
|
{ df::plant_raw_flags::BIOME_DESERT_SAND, df::biome_type::DESERT_SAND },
|
||||||
|
{ df::plant_raw_flags::BIOME_OCEAN_TROPICAL, df::biome_type::OCEAN_TROPICAL },
|
||||||
|
{ df::plant_raw_flags::BIOME_OCEAN_TEMPERATE, df::biome_type::OCEAN_TEMPERATE },
|
||||||
|
{ df::plant_raw_flags::BIOME_OCEAN_ARCTIC, df::biome_type::OCEAN_ARCTIC },
|
||||||
|
{ df::plant_raw_flags::BIOME_POOL_TEMPERATE_FRESHWATER, df::biome_type::POOL_TEMPERATE_FRESHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_POOL_TEMPERATE_BRACKISHWATER, df::biome_type::POOL_TEMPERATE_BRACKISHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_POOL_TEMPERATE_SALTWATER, df::biome_type::POOL_TEMPERATE_SALTWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_POOL_TROPICAL_FRESHWATER, df::biome_type::POOL_TROPICAL_FRESHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_POOL_TROPICAL_BRACKISHWATER, df::biome_type::POOL_TROPICAL_BRACKISHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_POOL_TROPICAL_SALTWATER, df::biome_type::POOL_TROPICAL_SALTWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_LAKE_TEMPERATE_FRESHWATER, df::biome_type::LAKE_TEMPERATE_FRESHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_LAKE_TEMPERATE_BRACKISHWATER, df::biome_type::LAKE_TEMPERATE_BRACKISHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_LAKE_TEMPERATE_SALTWATER, df::biome_type::LAKE_TEMPERATE_SALTWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_LAKE_TROPICAL_FRESHWATER, df::biome_type::LAKE_TROPICAL_FRESHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_LAKE_TROPICAL_BRACKISHWATER, df::biome_type::LAKE_TROPICAL_BRACKISHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_LAKE_TROPICAL_SALTWATER, df::biome_type::LAKE_TROPICAL_SALTWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_RIVER_TEMPERATE_FRESHWATER, df::biome_type::RIVER_TEMPERATE_FRESHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_RIVER_TEMPERATE_BRACKISHWATER, df::biome_type::RIVER_TEMPERATE_BRACKISHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_RIVER_TEMPERATE_SALTWATER, df::biome_type::RIVER_TEMPERATE_SALTWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_RIVER_TROPICAL_FRESHWATER, df::biome_type::RIVER_TROPICAL_FRESHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_RIVER_TROPICAL_BRACKISHWATER, df::biome_type::RIVER_TROPICAL_BRACKISHWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_RIVER_TROPICAL_SALTWATER, df::biome_type::RIVER_TROPICAL_SALTWATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_SUBTERRANEAN_WATER, df::biome_type::SUBTERRANEAN_WATER },
|
||||||
|
{ df::plant_raw_flags::BIOME_SUBTERRANEAN_CHASM, df::biome_type::SUBTERRANEAN_CHASM },
|
||||||
|
{ df::plant_raw_flags::BIOME_SUBTERRANEAN_LAVA, df::biome_type::SUBTERRANEAN_LAVA }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
void find_plantable_plants()
|
||||||
|
{
|
||||||
|
plantable_plants.clear();
|
||||||
|
|
||||||
|
map<int, int> counts;
|
||||||
|
|
||||||
|
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);
|
||||||
|
#undef F
|
||||||
|
|
||||||
|
for (auto ii : world->items.other[df::items_other_id::SEEDS])
|
||||||
|
{
|
||||||
|
df::item_plantst* i = (df::item_plantst*)ii;
|
||||||
|
if ((i->flags.whole & bad_flags.whole) == 0)
|
||||||
|
counts[i->mat_index] += i->stack_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto ci : counts)
|
||||||
|
{
|
||||||
|
if (df::global::ui->tasks.discovered_plants[ci.first])
|
||||||
|
{
|
||||||
|
df::plant_raw* plant = world->raws.plants.all[ci.first];
|
||||||
|
if (is_plantable(plant))
|
||||||
|
for (auto flagmap : biomeFlagMap)
|
||||||
|
if (plant->flags.is_set(flagmap.first))
|
||||||
|
plantable_plants[plant->index].insert(flagmap.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_farms(color_ostream& out, set<int> plants, vector<df::building_farmplotst*> farms)
|
||||||
|
{
|
||||||
|
// this algorithm attempts to change as few farms as possible, while ensuring that
|
||||||
|
// the number of farms planting each eligible plant is "as equal as possible"
|
||||||
|
|
||||||
|
if (farms.empty() || plants.empty())
|
||||||
|
return; // do nothing if there are no farms or no plantable plants
|
||||||
|
|
||||||
|
int season = *df::global::cur_season;
|
||||||
|
|
||||||
|
int min = farms.size() / plants.size(); // the number of farms that should plant each eligible plant, rounded down
|
||||||
|
int extra = farms.size() - min * plants.size(); // the remainder that cannot be evenly divided
|
||||||
|
|
||||||
|
map<int, int> counters;
|
||||||
|
counters.empty();
|
||||||
|
|
||||||
|
queue<df::building_farmplotst*> toChange;
|
||||||
|
toChange.empty();
|
||||||
|
|
||||||
|
for (auto farm : farms)
|
||||||
|
{
|
||||||
|
int o = farm->plant_id[season];
|
||||||
|
if (plants.count(o)==0 || counters[o] > min || (counters[o] == min && extra == 0))
|
||||||
|
toChange.push(farm); // this farm is an excess instance for the plant it is currently planting
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (counters[o] == min)
|
||||||
|
extra--; // allocate off one of the remainder farms
|
||||||
|
counters[o]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto n : plants)
|
||||||
|
{
|
||||||
|
int c = counters[n];
|
||||||
|
while (toChange.size() > 0 && (c < min || (c == min && extra > 0)))
|
||||||
|
{
|
||||||
|
// pick one of the excess farms and change it to plant this plant
|
||||||
|
df::building_farmplotst* farm = toChange.front();
|
||||||
|
int o = farm->plant_id[season];
|
||||||
|
farm->plant_id[season] = n;
|
||||||
|
out << "autofarm: changing farm #" << farm->id <<
|
||||||
|
" from " << ((o == -1) ? "NONE" : world->raws.plants.all[o]->name) <<
|
||||||
|
" to " << ((n == -1) ? "NONE" : world->raws.plants.all[n]->name) << endl;
|
||||||
|
toChange.pop();
|
||||||
|
if (c++ == min)
|
||||||
|
extra--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void process(color_ostream& out)
|
||||||
|
{
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
find_plantable_plants();
|
||||||
|
|
||||||
|
lastCounts.clear();
|
||||||
|
|
||||||
|
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);
|
||||||
|
#undef F
|
||||||
|
|
||||||
|
for (auto ii : world->items.other[df::items_other_id::PLANT])
|
||||||
|
{
|
||||||
|
df::item_plantst* i = (df::item_plantst*)ii;
|
||||||
|
if ((i->flags.whole & bad_flags.whole) == 0 &&
|
||||||
|
plantable_plants.count(i->mat_index) > 0)
|
||||||
|
{
|
||||||
|
lastCounts[i->mat_index] += i->stack_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map<df::biome_type, set<int>> plants;
|
||||||
|
plants.clear();
|
||||||
|
|
||||||
|
for (auto plantable : plantable_plants)
|
||||||
|
{
|
||||||
|
df::plant_raw* plant = world->raws.plants.all[plantable.first];
|
||||||
|
if (lastCounts[plant->index] < getThreshold(plant->index))
|
||||||
|
for (auto biome : plantable.second)
|
||||||
|
{
|
||||||
|
plants[biome].insert(plant->index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map<df::biome_type, vector<df::building_farmplotst*>> farms;
|
||||||
|
farms.clear();
|
||||||
|
|
||||||
|
for (auto bb : world->buildings.other[df::buildings_other_id::FARM_PLOT])
|
||||||
|
{
|
||||||
|
df::building_farmplotst* farm = (df::building_farmplotst*) bb;
|
||||||
|
if (farm->flags.bits.exists)
|
||||||
|
{
|
||||||
|
df::biome_type biome;
|
||||||
|
if (Maps::getTileDesignation(bb->centerx, bb->centery, bb->z)->bits.subterranean)
|
||||||
|
biome = biome_type::SUBTERRANEAN_WATER;
|
||||||
|
else {
|
||||||
|
df::coord2d region(Maps::getTileBiomeRgn(df::coord(bb->centerx, bb->centery, bb->z)));
|
||||||
|
biome = Maps::GetBiomeType(region.x, region.y);
|
||||||
|
}
|
||||||
|
farms[biome].push_back(farm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto ff : farms)
|
||||||
|
{
|
||||||
|
set_farms(out, plants[ff.first], ff.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void status(color_ostream& out)
|
||||||
|
{
|
||||||
|
out << (enabled ? "Running." : "Stopped.") << endl;
|
||||||
|
for (auto lc : lastCounts)
|
||||||
|
{
|
||||||
|
auto plant = world->raws.plants.all[lc.first];
|
||||||
|
out << plant->id << " limit " << getThreshold(lc.first) << " current " << lc.second << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto th : thresholds)
|
||||||
|
{
|
||||||
|
if (lastCounts[th.first] > 0)
|
||||||
|
continue;
|
||||||
|
auto plant = world->raws.plants.all[th.first];
|
||||||
|
out << plant->id << " limit " << getThreshold(th.first) << " current 0" << endl;
|
||||||
|
}
|
||||||
|
out << "Default: " << defaultThreshold << endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static AutoFarm* autofarmInstance;
|
||||||
|
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
if (world && ui) {
|
||||||
|
commands.push_back(
|
||||||
|
PluginCommand("autofarm", tagline,
|
||||||
|
autofarm, false, usage
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
autofarmInstance = new AutoFarm();
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
delete autofarmInstance;
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_onupdate(color_ostream &out)
|
||||||
|
{
|
||||||
|
if (!autofarmInstance)
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
if (!Maps::IsValid())
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
if (DFHack::World::ReadPauseState())
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
if (world->frame_counter % 50 != 0) // Check every hour
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
{
|
||||||
|
CoreSuspender suspend;
|
||||||
|
autofarmInstance->process(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
|
||||||
|
{
|
||||||
|
enabled = enable;
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static command_result setThresholds(color_ostream& out, vector<string> & parameters)
|
||||||
|
{
|
||||||
|
int val = atoi(parameters[1].c_str());
|
||||||
|
for (int i = 2; i < parameters.size(); i++)
|
||||||
|
{
|
||||||
|
string id = parameters[i];
|
||||||
|
transform(id.begin(), id.end(), id.begin(), ::toupper);
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
for (auto plant : world->raws.plants.all)
|
||||||
|
{
|
||||||
|
if (plant->flags.is_set(df::plant_raw_flags::SEED) && (plant->id == id))
|
||||||
|
{
|
||||||
|
autofarmInstance->setThreshold(plant->index, val);
|
||||||
|
ok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
out << "Cannot find plant with id " << id << endl;
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static command_result autofarm(color_ostream &out, vector <string> & parameters)
|
||||||
|
{
|
||||||
|
CoreSuspender suspend;
|
||||||
|
|
||||||
|
if (parameters.size() == 1 && parameters[0] == "runonce")
|
||||||
|
autofarmInstance->process(out);
|
||||||
|
else if (parameters.size() == 1 && parameters[0] == "enable")
|
||||||
|
plugin_enable(out, true);
|
||||||
|
else if (parameters.size() == 1 && parameters[0] == "disable")
|
||||||
|
plugin_enable(out, false);
|
||||||
|
else if (parameters.size() == 2 && parameters[0] == "default")
|
||||||
|
autofarmInstance->setDefault(atoi(parameters[1].c_str()));
|
||||||
|
else if (parameters.size() >= 3 && parameters[0] == "threshold")
|
||||||
|
return setThresholds(out, parameters);
|
||||||
|
else if (parameters.size() == 0 || parameters.size() == 1 && parameters[0] == "status")
|
||||||
|
autofarmInstance->status(out);
|
||||||
|
else
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
@ -1 +1 @@
|
|||||||
Subproject commit 03e96477ca84e42c87db93bd2d781c73687795a8
|
Subproject commit 4fdb2be54365442b8abea86f21746795f83fbdc2
|
@ -0,0 +1,510 @@
|
|||||||
|
/*
|
||||||
|
* Tailor plugin. Automatically manages keeping your dorfs clothed.
|
||||||
|
* For best effect, place "tailor enable" in your dfhack.init configuration,
|
||||||
|
* or set AUTOENABLE to true.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Core.h"
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "df/creature_raw.h"
|
||||||
|
#include "df/global_objects.h"
|
||||||
|
#include "df/historical_entity.h"
|
||||||
|
#include "df/itemdef_armorst.h"
|
||||||
|
#include "df/itemdef_glovesst.h"
|
||||||
|
#include "df/itemdef_helmst.h"
|
||||||
|
#include "df/itemdef_pantsst.h"
|
||||||
|
#include "df/itemdef_shoesst.h"
|
||||||
|
#include "df/items_other_id.h"
|
||||||
|
#include "df/job.h"
|
||||||
|
#include "df/job_type.h"
|
||||||
|
#include "df/manager_order.h"
|
||||||
|
#include "df/ui.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
|
||||||
|
#include "modules/Maps.h"
|
||||||
|
#include "modules/Units.h"
|
||||||
|
#include "modules/Translation.h"
|
||||||
|
#include "modules/World.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
using df::global::world;
|
||||||
|
using df::global::ui;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("tailor");
|
||||||
|
#define AUTOENABLE false
|
||||||
|
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 = (
|
||||||
|
" tailor enable\n"
|
||||||
|
" Enable the plugin.\n"
|
||||||
|
" tailor disable\n"
|
||||||
|
" Disable the plugin.\n"
|
||||||
|
" tailor status\n"
|
||||||
|
" Display plugin status\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"
|
||||||
|
);
|
||||||
|
|
||||||
|
// ARMOR, SHOES, HELM, GLOVES, PANTS
|
||||||
|
|
||||||
|
// ah, if only STL had a bimap
|
||||||
|
|
||||||
|
static 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 = {
|
||||||
|
{ 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}
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
map<int, int> sizes; // this maps body size to races
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
|
available.empty();
|
||||||
|
needed.empty();
|
||||||
|
queued.empty();
|
||||||
|
orders.empty();
|
||||||
|
|
||||||
|
int silk = 0, yarn = 0, cloth = 0, leather = 0;
|
||||||
|
|
||||||
|
// scan for useable clothing
|
||||||
|
|
||||||
|
for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "clothing"
|
||||||
|
{
|
||||||
|
if (i->flags.whole & bad_flags.whole)
|
||||||
|
continue;
|
||||||
|
if (i->flags.bits.owned)
|
||||||
|
continue;
|
||||||
|
if (i->getWear() >= 1)
|
||||||
|
continue;
|
||||||
|
df::item_type t = i->getType();
|
||||||
|
int size = world->raws.creatures.all[i->getMakerRace()]->adultsize;
|
||||||
|
|
||||||
|
available[make_pair(t, size)] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan for clothing raw materials
|
||||||
|
|
||||||
|
for (auto i : world->items.other[df::items_other_id::CLOTH])
|
||||||
|
{
|
||||||
|
if (i->flags.whole & bad_flags.whole)
|
||||||
|
continue;
|
||||||
|
if (!i->hasImprovements()) // only count dyed
|
||||||
|
continue;
|
||||||
|
MaterialInfo mat(i);
|
||||||
|
int ss = i->getStackSize();
|
||||||
|
|
||||||
|
if (mat.material)
|
||||||
|
{
|
||||||
|
if (mat.material->flags.is_set(df::material_flags::SILK))
|
||||||
|
silk += ss;
|
||||||
|
else if (mat.material->flags.is_set(df::material_flags::THREAD_PLANT))
|
||||||
|
cloth += ss;
|
||||||
|
else if (mat.material->flags.is_set(df::material_flags::YARN))
|
||||||
|
yarn += ss;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i : world->items.other[df::items_other_id::SKIN_TANNED])
|
||||||
|
{
|
||||||
|
if (i->flags.whole & bad_flags.whole)
|
||||||
|
continue;
|
||||||
|
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
|
||||||
|
|
||||||
|
for (auto u : world->units.active)
|
||||||
|
{
|
||||||
|
if (!Units::isOwnCiv(u) ||
|
||||||
|
!Units::isOwnGroup(u) ||
|
||||||
|
!Units::isActive(u) ||
|
||||||
|
Units::isBaby(u))
|
||||||
|
continue; // skip units we don't control
|
||||||
|
|
||||||
|
set <df::item_type> wearing;
|
||||||
|
wearing.empty();
|
||||||
|
|
||||||
|
deque<df::item*> worn;
|
||||||
|
worn.empty();
|
||||||
|
|
||||||
|
for (auto inv : u->inventory)
|
||||||
|
{
|
||||||
|
if (inv->mode != df::unit_inventory_item::Worn)
|
||||||
|
continue;
|
||||||
|
if (inv->item->getWear() > 0)
|
||||||
|
worn.push_back(inv->item);
|
||||||
|
else
|
||||||
|
wearing.insert(inv->item->getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = world->raws.creatures.all[u->race]->adultsize;
|
||||||
|
sizes[size] = u->race;
|
||||||
|
|
||||||
|
for (auto ty : set<df::item_type>{ df::item_type::ARMOR, df::item_type::PANTS, df::item_type::SHOES })
|
||||||
|
{
|
||||||
|
if (wearing.count(ty) == 0)
|
||||||
|
needed[make_pair(ty, size)] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto w : worn)
|
||||||
|
{
|
||||||
|
auto ty = w->getType();
|
||||||
|
auto oo = itemTypeMap.find(ty);
|
||||||
|
if (oo == itemTypeMap.end())
|
||||||
|
continue;
|
||||||
|
df::job_type o = oo->second;
|
||||||
|
|
||||||
|
int size = world->raws.creatures.all[w->getMakerRace()]->adultsize;
|
||||||
|
std::string description;
|
||||||
|
w->getItemDescription(&description, 0);
|
||||||
|
|
||||||
|
if (available[make_pair(ty, size)] > 0)
|
||||||
|
{
|
||||||
|
if (w->flags.bits.owned)
|
||||||
|
{
|
||||||
|
bool confiscated = Items::setOwner(w, NULL);
|
||||||
|
|
||||||
|
out.print(
|
||||||
|
"%s %s from %s.\n",
|
||||||
|
(confiscated ? "Confiscated" : "Could not confiscate"),
|
||||||
|
description.c_str(),
|
||||||
|
Translation::TranslateName(&u->name, false).c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wearing.count(ty) == 0)
|
||||||
|
available[make_pair(ty, size)] -= 1;
|
||||||
|
|
||||||
|
if (w->getWear() > 1)
|
||||||
|
w->flags.bits.dump = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto entity = world->entities.all[ui->civ_id];
|
||||||
|
|
||||||
|
for (auto a : needed)
|
||||||
|
{
|
||||||
|
df::item_type ty = a.first.first;
|
||||||
|
int size = a.first.second;
|
||||||
|
int count = a.second;
|
||||||
|
|
||||||
|
int sub = 0;
|
||||||
|
vector<int16_t> v;
|
||||||
|
|
||||||
|
switch (ty) {
|
||||||
|
case df::item_type::ARMOR: v = entity->resources.armor_type; break;
|
||||||
|
case df::item_type::GLOVES: v = entity->resources.gloves_type; break;
|
||||||
|
case df::item_type::HELM: v = entity->resources.helm_type; break;
|
||||||
|
case df::item_type::PANTS: v = entity->resources.pants_type; break;
|
||||||
|
case df::item_type::SHOES: v = entity->resources.shoes_type; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::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;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
if (isClothing)
|
||||||
|
{
|
||||||
|
sub = vv;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
orders[make_tuple(itemTypeMap[ty], sub, size)] += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan orders
|
||||||
|
|
||||||
|
for (auto o : world->manager_orders)
|
||||||
|
{
|
||||||
|
auto f = jobTypeMap.find(o->job_type);
|
||||||
|
if (f == jobTypeMap.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto sub = o->item_subtype;
|
||||||
|
int race = o->hist_figure_id;
|
||||||
|
if (race == -1)
|
||||||
|
continue; // -1 means that the race of the worker will determine the size made; we must ignore these jobs
|
||||||
|
|
||||||
|
int size = world->raws.creatures.all[race]->adultsize;
|
||||||
|
|
||||||
|
orders[make_tuple(o->job_type, sub, size)] -= o->amount_left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// place orders
|
||||||
|
|
||||||
|
for (auto o : orders)
|
||||||
|
{
|
||||||
|
df::job_type ty;
|
||||||
|
int sub;
|
||||||
|
int size;
|
||||||
|
|
||||||
|
tie(ty, sub, size) = o.first;
|
||||||
|
int count = o.second;
|
||||||
|
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
vector<int16_t> v;
|
||||||
|
BitArray<df::armor_general_flags>* fl;
|
||||||
|
string name_s, name_p;
|
||||||
|
|
||||||
|
switch (ty) {
|
||||||
|
case df::job_type::MakeArmor:
|
||||||
|
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:
|
||||||
|
name_s = world->raws.itemdefs.gloves[sub]->name;
|
||||||
|
name_p = world->raws.itemdefs.gloves[sub]->name_plural;
|
||||||
|
v = entity->resources.gloves_type;
|
||||||
|
fl = &world->raws.itemdefs.gloves[sub]->props.flags;
|
||||||
|
break;
|
||||||
|
case df::job_type::MakeHelm:
|
||||||
|
name_s = world->raws.itemdefs.helms[sub]->name;
|
||||||
|
name_p = world->raws.itemdefs.helms[sub]->name_plural;
|
||||||
|
v = entity->resources.helm_type;
|
||||||
|
fl = &world->raws.itemdefs.helms[sub]->props.flags;
|
||||||
|
break;
|
||||||
|
case df::job_type::MakePants:
|
||||||
|
name_s = world->raws.itemdefs.pants[sub]->name;
|
||||||
|
name_p = world->raws.itemdefs.pants[sub]->name_plural;
|
||||||
|
v = entity->resources.pants_type;
|
||||||
|
fl = &world->raws.itemdefs.pants[sub]->props.flags;
|
||||||
|
break;
|
||||||
|
case df::job_type::MakeShoes:
|
||||||
|
name_s = world->raws.itemdefs.shoes[sub]->name;
|
||||||
|
name_p = world->raws.itemdefs.shoes[sub]->name_plural;
|
||||||
|
v = entity->resources.shoes_type;
|
||||||
|
fl = &world->raws.itemdefs.shoes[sub]->props.flags;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_make = false;
|
||||||
|
for (auto vv : v)
|
||||||
|
{
|
||||||
|
if (vv == sub)
|
||||||
|
{
|
||||||
|
can_make = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!can_make)
|
||||||
|
{
|
||||||
|
out.print("Cannot make %s, skipped\n", name_p.c_str());
|
||||||
|
continue; // this civilization does not know how to make this item, so sorry
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
df::job_material_category mat;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 = count;
|
||||||
|
order->amount_total = count;
|
||||||
|
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 = mat;
|
||||||
|
|
||||||
|
world->manager_orders.push_back(order);
|
||||||
|
|
||||||
|
out.print("Added order #%d for %d %s %s (sized for %s)\n",
|
||||||
|
order->id,
|
||||||
|
count,
|
||||||
|
bitfield_to_string(order->material_category).c_str(),
|
||||||
|
(count > 1) ? name_p.c_str() : name_s.c_str(),
|
||||||
|
world->raws.creatures.all[order->hist_figure_id]->name[1].c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DELTA_TICKS 600
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_onupdate(color_ostream &out)
|
||||||
|
{
|
||||||
|
if (!enabled)
|
||||||
|
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;
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
for (df::job_list_link* link = &world->jobs.list; link != NULL; link = link->next)
|
||||||
|
{
|
||||||
|
if (link->item == NULL) continue;
|
||||||
|
if (link->item->job_type == df::enums::job_type::UpdateStockpileRecords)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
do_scan(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
{
|
||||||
|
desired = true;
|
||||||
|
}
|
||||||
|
else if (parameters[0] == "disable" || parameters[0] == "off" || parameters[0] == "0")
|
||||||
|
{
|
||||||
|
desired = false;
|
||||||
|
}
|
||||||
|
else if (parameters[0] == "usage" || parameters[0] == "help" || parameters[0] == "?")
|
||||||
|
{
|
||||||
|
out.print("%s: %s\nUsage:\n%s", plugin_name, tagline, usage);
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
else if (parameters[0] == "test")
|
||||||
|
{
|
||||||
|
do_scan(out);
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
else if (parameters[0] != "status")
|
||||||
|
{
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable)
|
||||||
|
{
|
||||||
|
enabled = enable;
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
if (AUTOENABLE) {
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.push_back(PluginCommand(plugin_name, tagline, tailor_cmd, false, usage));
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
|
||||||
|
return plugin_enable(out, false);
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit 8ef283377c9830fb932ea888d89b551873af36cf
|
Subproject commit 61d8e935ffd3c705ac13082874e2a3cb0363251b
|
Loading…
Reference in New Issue