Petr Mrázek 2012-05-20 23:01:44 +02:00
commit 8ef1423ee7
9 changed files with 375 additions and 241 deletions

@ -763,6 +763,11 @@ Items module
Returns true *x,y,z* of the item, or *nil* if invalid; may be not equal to item.pos if in inventory.
* ``dfhack.items.getDescription(item, type[, decorate])``
Returns the string description of the item, as produced by the getItemDescription
method. If decorate is true, also adds markings for quality and improvements.
* ``dfhack.items.getGeneralRef(item, type)``
Searches for a general_ref with the given type.
@ -800,6 +805,11 @@ Items module
Move the item to the building. Returns *false* if impossible.
* ``dfhack.items.moveToInventory(item,unit,use_mode,body_part)``
Move the item to the unit inventory. Returns *false* if impossible.
Maps module
-----------

@ -991,6 +991,10 @@ or raws. The <tt class="docutils literal">ignore_noble</tt> boolean disables the
<li><p class="first"><tt class="docutils literal">dfhack.items.getPosition(item)</tt></p>
<p>Returns true <em>x,y,z</em> of the item, or <em>nil</em> if invalid; may be not equal to item.pos if in inventory.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getDescription(item, type[, decorate])</tt></p>
<p>Returns the string description of the item, as produced by the getItemDescription
method. If decorate is true, also adds markings for quality and improvements.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getGeneralRef(item, type)</tt></p>
<p>Searches for a general_ref with the given type.</p>
</li>
@ -1016,6 +1020,12 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.items.moveToContainer(item,container)</tt></p>
<p>Move the item to the container. Returns <em>false</em> if impossible.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.moveToBuilding(item,building,use_mode)</tt></p>
<p>Move the item to the building. Returns <em>false</em> if impossible.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.moveToInventory(item,unit,use_mode,body_part)</tt></p>
<p>Move the item to the unit inventory. Returns <em>false</em> if impossible.</p>
</li>
</ul>
</div>
<div class="section" id="maps-module">

@ -764,15 +764,24 @@ static bool items_moveToBuilding(df::item *item, df::building_actual *building,
return Items::moveToBuilding(mc, item, building,use_mode);
}
static bool items_moveToInventory
(df::item *item, df::unit *unit, df::unit_inventory_item::T_mode mode, int body_part)
{
MapExtras::MapCache mc;
return Items::moveToInventory(mc, item, unit, mode, body_part);
}
static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getGeneralRef),
WRAPM(Items, getSpecificRef),
WRAPM(Items, getOwner),
WRAPM(Items, setOwner),
WRAPM(Items, getContainer),
WRAPM(Items, getDescription),
WRAPN(moveToGround, items_moveToGround),
WRAPN(moveToContainer, items_moveToContainer),
WRAPN(moveToBuilding, items_moveToBuilding),
WRAPN(moveToInventory, items_moveToInventory),
{ NULL, NULL }
};

@ -39,6 +39,7 @@ distribution.
#include "df/specific_ref.h"
#include "df/building_actual.h"
#include "df/body_part_raw.h"
#include "df/unit_inventory_item.h"
namespace df
{
@ -146,9 +147,13 @@ DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector<df::
/// Returns the true position of the item.
DFHACK_EXPORT df::coord getPosition(df::item *item);
/// Returns the description string of the item.
DFHACK_EXPORT std::string getDescription(df::item *item, int type = 0, bool decorate = false);
DFHACK_EXPORT bool moveToGround(MapExtras::MapCache &mc, df::item *item, df::coord pos);
DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container);
DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode);
DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit, df::body_part_raw * targetBodyPart, bool ignoreRestrictions, int multiEquipLimit, bool verbose);
DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit,
df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1);
}
}

@ -45,6 +45,7 @@ using namespace std;
#include "Error.h"
#include "MiscUtils.h"
#include "df/ui.h"
#include "df/world.h"
#include "df/item.h"
#include "df/building.h"
@ -71,6 +72,7 @@ using namespace std;
#include "df/general_ref_contains_itemst.h"
#include "df/general_ref_contained_in_itemst.h"
#include "df/general_ref_building_holderst.h"
#include "df/viewscreen_itemst.h"
#include "df/vermin.h"
#include "df/unit_inventory_item.h"
@ -84,9 +86,8 @@ using namespace std;
using namespace DFHack;
using namespace df::enums;
using df::global::world;
const int const_GloveRightHandedness = 1;
const int const_GloveLeftHandedness = 2;
using df::global::ui;
using df::global::ui_selected_unit;
#define ITEMDEF_VECTORS \
ITEM(WEAPON, weapons, itemdef_weaponst) \
@ -579,8 +580,78 @@ df::coord Items::getPosition(df::item *item)
return item->pos;
}
static char quality_table[] = { 0, '-', '+', '*', '=', '@' };
static void addQuality(std::string &tmp, int quality)
{
if (quality > 0 && quality <= 5) {
char c = quality_table[quality];
tmp = c + tmp + c;
}
}
std::string Items::getDescription(df::item *item, int type, bool decorate)
{
CHECK_NULL_POINTER(item);
std::string tmp;
item->getItemDescription(&tmp, type);
if (decorate) {
if (item->flags.bits.foreign)
tmp = "(" + tmp + ")";
addQuality(tmp, item->getQuality());
if (item->isImproved()) {
tmp = "<" + tmp + ">";
addQuality(tmp, item->getImprovementQuality());
}
}
return tmp;
}
static void resetUnitInvFlags(df::unit *unit, df::unit_inventory_item *inv_item)
{
if (inv_item->mode == df::unit_inventory_item::Worn ||
inv_item->mode == df::unit_inventory_item::WrappedAround)
{
unit->flags2.bits.calculated_inventory = false;
unit->flags2.bits.calculated_insulation = false;
}
else if (inv_item->mode == df::unit_inventory_item::StuckIn)
{
unit->flags3.bits.unk2 = false;
}
}
static bool detachItem(MapExtras::MapCache &mc, df::item *item)
{
if (!item->specific_refs.empty())
return false;
if (item->world_data_id != -1)
return false;
for (size_t i = 0; i < item->itemrefs.size(); i++)
{
df::general_ref *ref = item->itemrefs[i];
switch (ref->getType())
{
case general_ref_type::PROJECTILE:
case general_ref_type::BUILDING_HOLDER:
case general_ref_type::BUILDING_CAGED:
case general_ref_type::BUILDING_TRIGGER:
case general_ref_type::BUILDING_TRIGGERTARGET:
case general_ref_type::BUILDING_CIVZONE_ASSIGNED:
return false;
default:
continue;
}
}
if (item->flags.bits.on_ground)
{
if (!mc.removeItemOnGround(item))
@ -603,6 +674,14 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
case general_ref_type::CONTAINED_IN_ITEM:
if (auto item2 = ref->getItem())
{
// Viewscreens hold general_ref_contains_itemst pointers
for (auto screen = Core::getTopViewscreen(); screen; screen = screen->parent)
{
auto vsitem = strict_virtual_cast<df::viewscreen_itemst>(screen);
if (vsitem && vsitem->item == item2)
return false;
}
item2->flags.bits.weight_computed = false;
removeRef(item2->itemrefs, general_ref_type::CONTAINS_ITEM, item->id);
@ -610,27 +689,27 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
break;
case general_ref_type::UNIT_HOLDER:
// Remove the item from the unit's inventory
for (int inv = 0; inv < ref->getUnit()->inventory.size(); inv++)
if (auto unit = ref->getUnit())
{
df::unit_inventory_item * currInvItem = ref->getUnit()->inventory.at(inv);
if(currInvItem->item->id == item->id)
// Unit view sidebar holds inventory item pointers
if (ui->main.mode == ui_sidebar_mode::ViewUnits &&
(!ui_selected_unit ||
vector_get(world->units.active, *ui_selected_unit) == unit))
return false;
for (int i = unit->inventory.size()-1; i >= 0; i--)
{
// Match found; remove it
ref->getUnit()->inventory.erase(ref->getUnit()->inventory.begin() + inv);
// No other pointers to this object should exist; delete it to prevent memleak
delete currInvItem;
// Note: we might expect to recalculate the unit's weight at this point, in order to account for the
// detached item. In fact, this recalculation occurs automatically during each dwarf's "turn".
// The slight delay in recalculation is probably not worth worrying about.
// Since no item will ever occur twice in a unit's inventory, further searching is unnecessary.
break;
df::unit_inventory_item *inv_item = unit->inventory[i];
if (inv_item->item != item)
continue;
resetUnitInvFlags(unit, inv_item);
vector_erase_at(unit->inventory, i);
delete inv_item;
}
}
break;
case general_ref_type::BUILDING_HOLDER:
return false;
default:
continue;
@ -681,9 +760,6 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df:
if (!cpos.isValid())
return false;
if (!detachItem(mc, item))
return false;
auto ref1 = df::allocate<df::general_ref_contains_itemst>();
auto ref2 = df::allocate<df::general_ref_contained_in_itemst>();
@ -691,18 +767,24 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df:
{
delete ref1; delete ref2;
Core::printerr("Could not allocate container refs.\n");
putOnGround(mc, item, cpos);
return false;
}
if (!detachItem(mc, item))
{
delete ref1; delete ref2;
return false;
}
item->pos = container->pos;
item->flags.bits.in_inventory = true;
container->flags.bits.container = true;
container->flags.bits.container = true;
container->flags.bits.weight_computed = false;
ref1->item_id = item->id;
container->itemrefs.push_back(ref1);
ref2->item_id = container->id;
item->itemrefs.push_back(ref2);
@ -713,6 +795,7 @@ bool DFHack::Items::moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::
{
CHECK_NULL_POINTER(item);
CHECK_NULL_POINTER(building);
CHECK_INVALID_ARGUMENT(use_mode == 0 || use_mode == 2);
auto ref = df::allocate<df::general_ref_building_holderst>();
if(!ref)
@ -721,11 +804,13 @@ bool DFHack::Items::moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::
Core::printerr("Could not allocate building holder refs.\n");
return false;
}
if (!detachItem(mc, item))
{
delete ref;
return false;
}
item->pos.x=building->centerx;
item->pos.y=building->centery;
item->pos.z=building->z;
@ -738,217 +823,46 @@ bool DFHack::Items::moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::
con->item=item;
con->use_mode=use_mode;
building->contained_items.push_back(con);
return true;
}
bool DFHack::Items::moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit, df::body_part_raw * targetBodyPart, bool ignoreRestrictions, int multiEquipLimit, bool verbose)
{
// Step 1: Check for anti-requisite conditions
df::unit * itemOwner = Items::getOwner(item);
if (ignoreRestrictions)
{
// If the ignoreRestrictions cmdline switch was specified, then skip all of the normal preventative rules
if (verbose) { Core::print("Skipping integrity checks...\n"); }
}
else if(!item->isClothing() && !item->isArmorNotClothing())
{
if (verbose) { Core::printerr("Item %d is not clothing or armor; it cannot be equipped. Please choose a different item (or use the Ignore option if you really want to equip an inappropriate item).\n", item->id); }
return false;
}
else if (item->getType() != df::enums::item_type::GLOVES &&
item->getType() != df::enums::item_type::HELM &&
item->getType() != df::enums::item_type::ARMOR &&
item->getType() != df::enums::item_type::PANTS &&
item->getType() != df::enums::item_type::SHOES &&
!targetBodyPart)
{
if (verbose) { Core::printerr("Item %d is of an unrecognized type; it cannot be equipped (because the module wouldn't know where to put it).\n", item->id); }
return false;
}
else if (itemOwner && itemOwner->id != unit->id)
{
if (verbose) { Core::printerr("Item %d is owned by someone else. Equipping it on this unit is not recommended. Please use DFHack's Confiscate plugin, choose a different item, or use the Ignore option to proceed in spite of this warning.\n", item->id); }
return false;
}
else if (item->flags.bits.in_inventory)
{
if (verbose) { Core::printerr("Item %d is already in a unit's inventory. Direct inventory transfers are not recommended; please move the item to the ground first (or use the Ignore option).\n", item->id); }
return false;
}
else if (item->flags.bits.in_job)
bool DFHack::Items::moveToInventory(
MapExtras::MapCache &mc, df::item *item, df::unit *unit,
df::unit_inventory_item::T_mode mode, int body_part
) {
CHECK_NULL_POINTER(item);
CHECK_NULL_POINTER(unit);
CHECK_NULL_POINTER(unit->body.body_plan);
CHECK_INVALID_ARGUMENT(is_valid_enum_item(mode));
int body_plan_size = unit->body.body_plan->body_parts.size();
CHECK_INVALID_ARGUMENT(body_part < 0 || body_part <= body_plan_size);
auto holderReference = df::allocate<df::general_ref_unit_holderst>();
if (!holderReference)
{
if (verbose) { Core::printerr("Item %d is reserved for use in a queued job. Equipping it is not recommended, as this might interfere with the completion of vital jobs. Use the Ignore option to ignore this warning.\n", item->id); }
Core::printerr("Could not allocate UNIT_HOLDER reference.\n");
return false;
}
// ASSERT: anti-requisite conditions have been satisfied (or disregarded)
// Step 2: Try to find a bodypart which is eligible to receive equipment AND which is appropriate for the specified item
df::body_part_raw * confirmedBodyPart = NULL;
int bpIndex;
for(bpIndex = 0; bpIndex < unit->body.body_plan->body_parts.size(); bpIndex++)
if (!detachItem(mc, item))
{
df::body_part_raw * currPart = unit->body.body_plan->body_parts[bpIndex];
// Short-circuit the search process if a BP was specified in the function call
// Note: this causes a bit of inefficient busy-looping, but the search space is tiny (<100) and we NEED to get the correct bpIndex value in order to perform inventory manipulations
if (!targetBodyPart)
{
// The function call did not specify any particular body part; proceed with normal iteration and evaluation of BP eligibility
}
else if (currPart == targetBodyPart)
{
// A specific body part was included in the function call, and we've found it; proceed with the normal BP evaluation (suitability, emptiness, etc)
}
else if (bpIndex < unit->body.body_plan->body_parts.size())
{
// The current body part is not the one that was specified in the function call, but we can keep searching
if (verbose) { Core::printerr("Found bodypart %s; not a match; continuing search.\n", currPart->part_code.c_str()); }
continue;
}
else
{
// The specified body part has not been found, and we've reached the end of the list. Report failure.
if (verbose) { Core::printerr("The specified body part (%s) does not belong to the chosen unit. Please double-check to ensure that your spelling is correct, and that you have not chosen a dismembered bodypart.\n"); }
return false;
}
if (verbose) { Core::print("Inspecting bodypart %s.\n", currPart->part_code.c_str()); }
// Inspect the current bodypart
if (item->getType() == df::enums::item_type::GLOVES && currPart->flags.is_set(df::body_part_template_flags::GRASP) &&
((item->getGloveHandedness() == const_GloveLeftHandedness && currPart->flags.is_set(df::body_part_template_flags::LEFT)) ||
(item->getGloveHandedness() == const_GloveRightHandedness && currPart->flags.is_set(df::body_part_template_flags::RIGHT))))
{
if (verbose) { Core::print("Hand found (%s)...", currPart->part_code.c_str()); }
}
else if (item->getType() == df::enums::item_type::HELM && currPart->flags.is_set(df::body_part_template_flags::HEAD))
{
if (verbose) { Core::print("Head found (%s)...", currPart->part_code.c_str()); }
}
else if (item->getType() == df::enums::item_type::ARMOR && currPart->flags.is_set(df::body_part_template_flags::UPPERBODY))
{
if (verbose) { Core::print("Upper body found (%s)...", currPart->part_code.c_str()); }
}
else if (item->getType() == df::enums::item_type::PANTS && currPart->flags.is_set(df::body_part_template_flags::LOWERBODY))
{
if (verbose) { Core::print("Lower body found (%s)...", currPart->part_code.c_str()); }
}
else if (item->getType() == df::enums::item_type::SHOES && currPart->flags.is_set(df::body_part_template_flags::STANCE))
{
if (verbose) { Core::print("Foot found (%s)...", currPart->part_code.c_str()); }
}
else if (targetBodyPart && ignoreRestrictions)
{
// The BP in question would normally be considered ineligible for equipment. But since it was deliberately specified by the user, we'll proceed anyways.
if (verbose) { Core::print("Non-standard bodypart found (%s)...", targetBodyPart->part_code.c_str()); }
}
else if (targetBodyPart)
{
// The BP in question is not eligible for equipment and the ignore flag was not specified. Report failure.
if (verbose) { Core::printerr("Non-standard bodypart found, but it is ineligible for standard equipment. Use the Ignore flag to override this warning.\n"); }
return false;
}
else
{
if (verbose) { Core::print("Skipping ineligible bodypart.\n"); }
// This body part is not eligible for the equipment in question; skip it
continue;
}
// ASSERT: The current body part is able to support the specified equipment (or the test has been overridden). Check whether it is currently empty/available.
if (multiEquipLimit == INT_MAX)
{
// Note: this loop/check is skipped if the MultiEquip option is specified; we'll simply add the item to the bodyPart even if it's already holding a dozen gloves, shoes, and millstones (or whatever)
if (verbose) { Core::print(" inventory checking skipped..."); }
confirmedBodyPart = currPart;
break;
}
else
{
confirmedBodyPart = currPart; // Assume that the bodypart is valid; we'll invalidate it if we detect too many collisions while looping
int collisions = 0;
for (int inventoryID=0; inventoryID < unit->inventory.size(); inventoryID++)
{
df::unit_inventory_item * currInvItem = unit->inventory[inventoryID];
if (currInvItem->body_part_id == bpIndex)
{
// Collision detected; have we reached the limit?
if (++collisions >= multiEquipLimit)
{
if (verbose) { Core::printerr(" but it already carries %d piece(s) of equipment. Either remove the existing equipment or use the Multi option.\n", multiEquipLimit); }
confirmedBodyPart = NULL;
break;
}
}
}
if (confirmedBodyPart)
{
// Match found; no need to examine any other BPs
if (verbose) { Core::print(" eligibility confirmed..."); }
break;
}
else if (!targetBodyPart)
{
// This body part is not eligible to receive the specified equipment; return to the loop and check the next BP
continue;
}
else
{
// A specific body part was designated in the function call, but it was found to be ineligible.
// Don't return to the BP loop; just fall-through to the failure-reporting code a few lines below.
break;
}
}
}
if (!confirmedBodyPart) {
// No matching body parts found; report failure
if (verbose) { Core::printerr("\nThe item could not be equipped because the relevant body part(s) of the unit are missing or already occupied. Try again with the Multi option if you're like to over-equip a body part, or choose a different unit-item combination (e.g. stop trying to put shoes on a trout).\n" ); }
delete holderReference;
return false;
}
// ASSERT: We've found a bodypart which is suitable for the designated item and ready to receive it (or overridden the relevant checks)
item->flags.bits.in_inventory = true;
// Step 3: Perform the manipulations
if (verbose) { Core::print("equipping item..."); }
// 3a: attempt to release the item from its current position
if (!detachItem(mc, item))
{
if (verbose) { Core::printerr("\nEquipping failed - failed to retrieve item from its current location/container/inventory. Please move it to the ground and try again.\n"); }
return false;
}
// 3b: register the item in the unit's inventory
df::unit_inventory_item * newInventoryItem = df::allocate<df::unit_inventory_item>();
newInventoryItem->body_part_id = bpIndex;
auto newInventoryItem = new df::unit_inventory_item();
newInventoryItem->item = item;
newInventoryItem->mode = newInventoryItem->Worn;
newInventoryItem->mode = mode;
newInventoryItem->body_part_id = body_part;
unit->inventory.push_back(newInventoryItem);
item->flags.bits.in_inventory = true;
// 3c: register a "unit holds item" relationship at the item level
df::general_ref_unit_holderst * holderReference = df::allocate<df::general_ref_unit_holderst>();
holderReference->setID(unit->id);
holderReference->unit_id = unit->id;
item->itemrefs.push_back(holderReference);
// 3d: tell the unit to begin "using" the item (note: if this step is skipped then the unit may not gain any actual protection from its armour)
if (item->isClothing() || item->isArmorNotClothing()) {
df::unit::T_used_items * newUsedItem = df::allocate<df::unit::T_used_items>();
newUsedItem->id = item->id;
unit->used_items.push_back(newUsedItem);
if (verbose) { Core::print("Item is clothing/armor; protection aspects have been enabled.\n"); }
}
else
{
if (verbose) { Core::print("Item is neither clothing nor armor; unit has NOT been instructed to \"use\" it as such.\n"); }
}
// 3e: Remove the item from its current location (note: if this step is skipped then the item will appear BOTH on the ground and in the unit's inventory)
mc.removeItemOnGround(item);
resetUnitInvFlags(unit, newInventoryItem);
if (verbose) { Core::print(" Success!\n"); }
return true;
}

@ -41,6 +41,8 @@ using namespace std;
#include "Core.h"
#include "MiscUtils.h"
#include "modules/Buildings.h"
#include "DataDefs.h"
#include "df/world_data.h"
#include "df/world_underground_region.h"
@ -1055,7 +1057,21 @@ bool MapExtras::Block::removeItemOnGround(df::item *item)
if (--count == 0)
{
index_tile<df::tile_occupancy&>(occupancy,item->pos).bits.item = false;
index_tile<df::tile_occupancy&>(block->occupancy,item->pos).bits.item = false;
auto &occ = index_tile<df::tile_occupancy&>(block->occupancy,item->pos);
occ.bits.item = false;
// Clear the 'site blocked' flag in the building, if any.
// Otherwise the job would be re-suspended without actually checking items.
if (occ.bits.building == tile_building_occ::Planned)
{
if (auto bld = Buildings::findAtTile(item->pos))
{
// TODO: maybe recheck other tiles like the game does.
bld->flags.bits.site_blocked = false;
}
}
}
return true;

@ -148,8 +148,6 @@ static command_result autodump_main(color_ostream &out, vector <string> & parame
}
}
bool inventoryDumpingSkipped = false;
// proceed with the dumpification operation
for(size_t i=0; i< numItems; i++)
{
@ -175,14 +173,6 @@ static command_result autodump_main(color_ostream &out, vector <string> & parame
continue;
if (!need_forbidden && itm->flags.bits.forbid)
continue;
if (itm->flags.bits.in_inventory && Gui::getSelectedUnit(out, true))
{
// Due to GUI caching/redraw rules, Dwarf Fortress tends to crash if we make any changes to a unit's inventory
// while the player is looking at the inventory menu. Therefore, we'll simply skip such items until they
// change something (e.g. switch from "v" to "k" mode).
inventoryDumpingSkipped = true;
continue;
}
if(!destroy) // move to cursor
{
@ -192,7 +182,11 @@ static command_result autodump_main(color_ostream &out, vector <string> & parame
// Don't move items if they're already at the cursor
if (pos_cursor != pos_item)
Items::moveToGround(MC, itm, pos_cursor);
{
if (!Items::moveToGround(MC, itm, pos_cursor))
out.print("Could not move item: %s\n",
Items::getDescription(itm, 0, true).c_str());
}
}
else // destroy
{
@ -213,7 +207,6 @@ static command_result autodump_main(color_ostream &out, vector <string> & parame
if(!destroy)
MC.WriteAll();
if (inventoryDumpingSkipped) { out.printerr("Some inventory items could not be autodumped because the unit/inventory screen is currently active. Please close the unit screen and repeat the operation.\n"); }
out.print("Done. %d items %s.\n", dumped_total, destroy ? "marked for destruction" : "quickdumped");
return CR_OK;
}

@ -48,6 +48,9 @@ using MapExtras::Block;
using MapExtras::MapCache;
using df::global::world;
const int const_GloveRightHandedness = 1;
const int const_GloveLeftHandedness = 2;
DFHACK_PLUGIN("forceequip");
command_result df_forceequip(color_ostream &out, vector <string> & parameters);
@ -226,6 +229,187 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK;
}
static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit, df::body_part_raw * targetBodyPart, bool ignoreRestrictions, int multiEquipLimit, bool verbose)
{
// Step 1: Check for anti-requisite conditions
df::unit * itemOwner = Items::getOwner(item);
if (ignoreRestrictions)
{
// If the ignoreRestrictions cmdline switch was specified, then skip all of the normal preventative rules
if (verbose) { Core::print("Skipping integrity checks...\n"); }
}
else if(!item->isClothing() && !item->isArmorNotClothing())
{
if (verbose) { Core::printerr("Item %d is not clothing or armor; it cannot be equipped. Please choose a different item (or use the Ignore option if you really want to equip an inappropriate item).\n", item->id); }
return false;
}
else if (item->getType() != df::enums::item_type::GLOVES &&
item->getType() != df::enums::item_type::HELM &&
item->getType() != df::enums::item_type::ARMOR &&
item->getType() != df::enums::item_type::PANTS &&
item->getType() != df::enums::item_type::SHOES &&
!targetBodyPart)
{
if (verbose) { Core::printerr("Item %d is of an unrecognized type; it cannot be equipped (because the module wouldn't know where to put it).\n", item->id); }
return false;
}
else if (itemOwner && itemOwner->id != unit->id)
{
if (verbose) { Core::printerr("Item %d is owned by someone else. Equipping it on this unit is not recommended. Please use DFHack's Confiscate plugin, choose a different item, or use the Ignore option to proceed in spite of this warning.\n", item->id); }
return false;
}
else if (item->flags.bits.in_inventory)
{
if (verbose) { Core::printerr("Item %d is already in a unit's inventory. Direct inventory transfers are not recommended; please move the item to the ground first (or use the Ignore option).\n", item->id); }
return false;
}
else if (item->flags.bits.in_job)
{
if (verbose) { Core::printerr("Item %d is reserved for use in a queued job. Equipping it is not recommended, as this might interfere with the completion of vital jobs. Use the Ignore option to ignore this warning.\n", item->id); }
return false;
}
// ASSERT: anti-requisite conditions have been satisfied (or disregarded)
// Step 2: Try to find a bodypart which is eligible to receive equipment AND which is appropriate for the specified item
df::body_part_raw * confirmedBodyPart = NULL;
int bpIndex;
for(bpIndex = 0; bpIndex < unit->body.body_plan->body_parts.size(); bpIndex++)
{
df::body_part_raw * currPart = unit->body.body_plan->body_parts[bpIndex];
// Short-circuit the search process if a BP was specified in the function call
// Note: this causes a bit of inefficient busy-looping, but the search space is tiny (<100) and we NEED to get the correct bpIndex value in order to perform inventory manipulations
if (!targetBodyPart)
{
// The function call did not specify any particular body part; proceed with normal iteration and evaluation of BP eligibility
}
else if (currPart == targetBodyPart)
{
// A specific body part was included in the function call, and we've found it; proceed with the normal BP evaluation (suitability, emptiness, etc)
}
else if (bpIndex < unit->body.body_plan->body_parts.size())
{
// The current body part is not the one that was specified in the function call, but we can keep searching
if (verbose) { Core::printerr("Found bodypart %s; not a match; continuing search.\n", currPart->part_code.c_str()); }
continue;
}
else
{
// The specified body part has not been found, and we've reached the end of the list. Report failure.
if (verbose) { Core::printerr("The specified body part (%s) does not belong to the chosen unit. Please double-check to ensure that your spelling is correct, and that you have not chosen a dismembered bodypart.\n"); }
return false;
}
if (verbose) { Core::print("Inspecting bodypart %s.\n", currPart->part_code.c_str()); }
// Inspect the current bodypart
if (item->getType() == df::enums::item_type::GLOVES && currPart->flags.is_set(df::body_part_template_flags::GRASP) &&
((item->getGloveHandedness() == const_GloveLeftHandedness && currPart->flags.is_set(df::body_part_template_flags::LEFT)) ||
(item->getGloveHandedness() == const_GloveRightHandedness && currPart->flags.is_set(df::body_part_template_flags::RIGHT))))
{
if (verbose) { Core::print("Hand found (%s)...", currPart->part_code.c_str()); }
}
else if (item->getType() == df::enums::item_type::HELM && currPart->flags.is_set(df::body_part_template_flags::HEAD))
{
if (verbose) { Core::print("Head found (%s)...", currPart->part_code.c_str()); }
}
else if (item->getType() == df::enums::item_type::ARMOR && currPart->flags.is_set(df::body_part_template_flags::UPPERBODY))
{
if (verbose) { Core::print("Upper body found (%s)...", currPart->part_code.c_str()); }
}
else if (item->getType() == df::enums::item_type::PANTS && currPart->flags.is_set(df::body_part_template_flags::LOWERBODY))
{
if (verbose) { Core::print("Lower body found (%s)...", currPart->part_code.c_str()); }
}
else if (item->getType() == df::enums::item_type::SHOES && currPart->flags.is_set(df::body_part_template_flags::STANCE))
{
if (verbose) { Core::print("Foot found (%s)...", currPart->part_code.c_str()); }
}
else if (targetBodyPart && ignoreRestrictions)
{
// The BP in question would normally be considered ineligible for equipment. But since it was deliberately specified by the user, we'll proceed anyways.
if (verbose) { Core::print("Non-standard bodypart found (%s)...", targetBodyPart->part_code.c_str()); }
}
else if (targetBodyPart)
{
// The BP in question is not eligible for equipment and the ignore flag was not specified. Report failure.
if (verbose) { Core::printerr("Non-standard bodypart found, but it is ineligible for standard equipment. Use the Ignore flag to override this warning.\n"); }
return false;
}
else
{
if (verbose) { Core::print("Skipping ineligible bodypart.\n"); }
// This body part is not eligible for the equipment in question; skip it
continue;
}
// ASSERT: The current body part is able to support the specified equipment (or the test has been overridden). Check whether it is currently empty/available.
if (multiEquipLimit == INT_MAX)
{
// Note: this loop/check is skipped if the MultiEquip option is specified; we'll simply add the item to the bodyPart even if it's already holding a dozen gloves, shoes, and millstones (or whatever)
if (verbose) { Core::print(" inventory checking skipped..."); }
confirmedBodyPart = currPart;
break;
}
else
{
confirmedBodyPart = currPart; // Assume that the bodypart is valid; we'll invalidate it if we detect too many collisions while looping
int collisions = 0;
for (int inventoryID=0; inventoryID < unit->inventory.size(); inventoryID++)
{
df::unit_inventory_item * currInvItem = unit->inventory[inventoryID];
if (currInvItem->body_part_id == bpIndex)
{
// Collision detected; have we reached the limit?
if (++collisions >= multiEquipLimit)
{
if (verbose) { Core::printerr(" but it already carries %d piece(s) of equipment. Either remove the existing equipment or use the Multi option.\n", multiEquipLimit); }
confirmedBodyPart = NULL;
break;
}
}
}
if (confirmedBodyPart)
{
// Match found; no need to examine any other BPs
if (verbose) { Core::print(" eligibility confirmed..."); }
break;
}
else if (!targetBodyPart)
{
// This body part is not eligible to receive the specified equipment; return to the loop and check the next BP
continue;
}
else
{
// A specific body part was designated in the function call, but it was found to be ineligible.
// Don't return to the BP loop; just fall-through to the failure-reporting code a few lines below.
break;
}
}
}
if (!confirmedBodyPart) {
// No matching body parts found; report failure
if (verbose) { Core::printerr("\nThe item could not be equipped because the relevant body part(s) of the unit are missing or already occupied. Try again with the Multi option if you're like to over-equip a body part, or choose a different unit-item combination (e.g. stop trying to put shoes on a trout).\n" ); }
return false;
}
if (!Items::moveToInventory(mc, item, unit, df::unit_inventory_item::Worn, bpIndex))
{
if (verbose) { Core::printerr("\nEquipping failed - failed to retrieve item from its current location/container/inventory. Please move it to the ground and try again.\n"); }
return false;
}
if (verbose) { Core::print(" Success!\n"); }
return true;
}
command_result df_forceequip(color_ostream &out, vector <string> & parameters)
{
// The "here" option is hardcoded to true, because the plugin currently doesn't support
@ -425,7 +609,7 @@ command_result df_forceequip(color_ostream &out, vector <string> & parameters)
else
{
itemsFound ++; // Track the number of items found under the cursor (for feedback purposes)
if (Items::moveToInventory(mc, currentItem, targetUnit, targetBodyPart, ignore, multiEquipLimit, verbose))
if (moveToInventory(mc, currentItem, targetUnit, targetBodyPart, ignore, multiEquipLimit, verbose))
{
// // TODO TEMP EXPERIMENTAL - try to alter the item size in order to conform to its wearer
// currentItem->getRace();

@ -19,14 +19,7 @@ orders.type = {
orders.description = {
key = function(item)
return dfhack.with_temp_object(
df.new "string",
function(str,item)
item:getItemDescription(str,0)
return str.value
end,
item
)
return dfhack.items.getDescription(item,0)
end
}