diff --git a/LUA_API.rst b/LUA_API.rst
index 18433262d..b0f1a9f31 100644
--- a/LUA_API.rst
+++ b/LUA_API.rst
@@ -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
-----------
diff --git a/Lua API.html b/Lua API.html
index e2d4ec23e..4b763af25 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -991,6 +991,10 @@ or raws. The ignore_noble boolean disables the
dfhack.items.getPosition(item)
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.
@@ -1016,6 +1020,12 @@ Returns false in case of error.
dfhack.items.moveToContainer(item,container)
Move the item to the container. Returns false if impossible.
+dfhack.items.moveToBuilding(item,building,use_mode)
+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.
+
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index 9dfb39754..6a550db87 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -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 }
};
diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h
index ab735f731..4236f068a 100644
--- a/library/include/modules/Items.h
+++ b/library/include/modules/Items.h
@@ -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::vectorpos;
}
+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(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();
auto ref2 = df::allocate();
@@ -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();
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();
+ 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();
- 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();
- 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();
- 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;
}
diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp
index 3160af75e..3ab156d77 100644
--- a/library/modules/Maps.cpp
+++ b/library/modules/Maps.cpp
@@ -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(occupancy,item->pos).bits.item = false;
- index_tile(block->occupancy,item->pos).bits.item = false;
+
+ auto &occ = index_tile(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;
diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp
index 753a8fb90..536b2501b 100644
--- a/plugins/autodump.cpp
+++ b/plugins/autodump.cpp
@@ -148,8 +148,6 @@ static command_result autodump_main(color_ostream &out, vector & 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 & 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 & 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 & 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;
}
diff --git a/plugins/forceequip.cpp b/plugins/forceequip.cpp
index dd4e213a1..0d493143b 100644
--- a/plugins/forceequip.cpp
+++ b/plugins/forceequip.cpp
@@ -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 & 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 & 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 & 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();
diff --git a/plugins/lua/sort/items.lua b/plugins/lua/sort/items.lua
index 2e1b3fd1f..13b62ff9b 100644
--- a/plugins/lua/sort/items.lua
+++ b/plugins/lua/sort/items.lua
@@ -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
}