Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Template for new versions:
## New Features

## Fixes
- `fix/archery-practice`: now splits instead of combining ammo items in quivers, and moves quivers to end of unit's inventory list

## Misc Improvements

Expand Down
77 changes: 40 additions & 37 deletions docs/fix/archery-practice.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,66 @@ fix/archery-practice
====================

.. dfhack-tool::
:summary: Consolidate and remove extra ammo items to fix 'Soldier (no item)' issue.
:summary: Fix quivers and training ammo items to allow archery practice to take place.
:tags: fort bugfix items

Combine ammo items inside quivers that are assigned for training to allow
Make quivers the last item in the inventory of every ranged unit currently
training and split stacks of ammo items assigned for training inside the
quivers to ensure each training unit can have more than one stack to allow
archery practice to take place.

Usage
-----

``fix/archery-practice``
Combine ammo items inside quivers that are assigned for training.
Move quivers to the end of units' inventory list and split stacks of
training ammo items inside the quivers.

``fix/archery-practice -q``, ``fix/archery-practice --quiet``
Combine ammo items inside quivers that are assigned for training.
Do not print to console.
Move quivers to the end of units' inventory list and split stacks of
training ammo items inside the quivers. Do not print to console.

This tool will combine ammo items inside the quivers of units in squads
that are currently set to train with the objective of ensuring that each
unit hold only one combined stack of ammo item assigned for training in
their quiver. Any ammo items left over after the combining operation
will be dropped on the ground.
This tool will set quivers as the last item in the inventory of units in
squads that are currently set to train as well as split ammo items inside
their quivers into multiple stacks if a quiver contains only ammo item
with a stack size of 25 or larger assigned for training. The original
training ammo item with a reduced stack size will remain in the quiver
while new ammo items split from it will be placed on the ground where
the unit is located to be picked up later.

The 'Soldier (no item)' issue
-----------------------------
Why are archers not practicing archery?
---------------------------------------

Due to a bug in the game, a unit that is scheduled to train will not be
able to practice archery at the archery range when their quiver contains
more than one stack of ammo item that is assigned to them for training.
This is indicated on the unit by the 'Soldier (no item)' status.
only one stack of ammo item assigned for training. This is sometimes
indicated on the unit by the 'Soldier (no item)' status.

The issue occurs when the game assigns an ammo item with a stack size of
less than 25 to the unit, prompting the game to assign additional stacks
of ammo items to make up for the deficit.
During versions 52.03 and 52.04, the issue was the complete reverse;
units would not practice when their quivers contained more than one
stack of ammo items assigned for training.

The workaround to this issue is to ensure the squad ammo assignments
for use in training contain as few ammo items with stack sizes smaller
than 25 as possible. Since training bolts are often made from wood or
bone which are created in stacks of 5, the use of the ``combine`` tool on
ammo stockpiles is recommended to reduce the frequency of this issue
occurring, while "incomplete" stacks of ammo items that are already
picked up by training units can be managed by this tool.
Another issue in 52.05 is that units will not practice archery if their
quiver is not the last item in their inventory.

Any other stacks of ammo items inside the quiver that are not assigned
for training will not affect the unit's ability to practice archery.

As of DF version 52.05, this bug should already be fixed.
This tool provides an interim remedy by moving quivers to the end of
every training unit's inventory list and splitting stacks of ammo items
inside their quivers to prompt the game to give them multiple stacks
of training ammo items.

Limitations
-----------

Due to the very limited number of ammo items a unit's quiver might contain,
the material, quality, and maker of the items are ignored when performing
the combining operation on them. Only ammo items assigned for training will
be combined, while ammo items inside the quiver that are assigned for combat
will not be affected.
The game has a tendency to reshuffle the squad's ammo/unit pairings if
the newly split ammo items are force paired to the units holding the
original ammo item. As a compromise, the new items are placed on the
ground instead and added to the squad's training ammo assignment pool,
so that the game can distribute the items normally without causing the
pairing for ammo items already in quivers to be reshuffled.

Although this tool will consolidate ammo items inside quivers and discard
any surplus items, the training units may not immediately go for archery
practice, especially if they are still trying to collect more ammo items
that the game have assigned to them.
Although this tool would allow units to practice archery, the activity
will still be aborted once they have only one stack of training ammo
item remaining in their quivers. Practicing units will gain skill from
practice, but not the positive thought they would have gained from
having completed the activity. Once the game assigns more training
ammo items to them, they can continue practicing archery.
134 changes: 113 additions & 21 deletions fix/archery-practice.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- Consolidate and remove extra ammo items to fix 'Soldier (no item)' issue.
-- Fix quivers and training ammo items to allow archery practice to take place.

local argparse = require("argparse")
local utils = require('utils')
Expand All @@ -8,7 +8,7 @@ local function GetTrainingSquads()
for _, squad in ipairs(df.global.world.squads.all) do
if squad.entity_id == df.global.plotinfo.group_id then
if #squad.ammo.ammunition > 0 and squad.activity ~= -1 then
trainingSquads[#trainingSquads + 1] = squad
table.insert(trainingSquads, squad)
end
end
end
Expand All @@ -33,13 +33,16 @@ local function GetTrainingAmmo(quiver, squad)
local containedAmmo = generalRef
local ammoItem = containedAmmo and df.item.find(containedAmmo.item_id)
if isTrainingAmmo(ammoItem, squad) then
trainingAmmo[#trainingAmmo + 1] = ammoItem
table.insert(trainingAmmo, ammoItem)
end
end
end
return trainingAmmo
end

--[[ The following functions are commented out because the nature
-- of the bug has changed, requiring a different solution.
-- More info: https://discord.com/channels/793331351645323264/873014631315148840/1423311023254933576
local function UnassignAmmo(trainingAmmo, itemToKeep, itemsToRemove, squad, unit)
local plotEqAssignedAmmo = df.global.plotinfo.equipment.items_assigned.AMMO
local plotEqUnassignedAmmo = df.global.plotinfo.equipment.items_unassigned.AMMO
Expand Down Expand Up @@ -139,35 +142,124 @@ local function ConsolidateAmmo(trainingAmmo, squad, unit)
end
end
end
-- End of commented out functions ]]

-- Currently, only assignment to squad is practical, as pairing ammo items with
-- units directly has a tendency to cause the game to reshuffle the pairings.
local function AssignAmmoToSquad(newItems, item, squad)
local plotEqAssignedAmmo = df.global.plotinfo.equipment.items_assigned.AMMO
local assignedAmmo
for _, ammoSpec in ipairs(squad.ammo.ammunition) do
if utils.linear_index(ammoSpec.assigned, item.id) then
assignedAmmo = ammoSpec
break
end
end
for _, newItem in ipairs(newItems) do
assignedAmmo.assigned:insert('#', newItem.id)
utils.sort_vector(assignedAmmo.assigned)
plotEqAssignedAmmo:insert('#', newItem.id)
utils.sort_vector(plotEqAssignedAmmo)
end
end

local function SplitAmmo(item, squad, unit)
local newItems = {}
repeat
local items = dfhack.items.createItem(
unit,
dfhack.items.findType('AMMO'),
item.subtype.subtype,
item.mat_type,
item.mat_index
)
if items then
for _, newItem in ipairs(items) do
newItem:setStackSize(5)
newItem.maker_race = item.maker_race
newItem:setQuality(item.quality)
newItem.skill_rating = item.skill_rating
newItem.maker = item.maker
newItem.masterpiece_event = item.masterpiece_event
table.insert(newItems, newItem)
end
end
item:setStackSize(item.stack_size - 5)
until item.stack_size <= 5
AssignAmmoToSquad(newItems, item, squad)
end

local function RemoveQuiverFromInv(unit, quiver)
local equippedQuiver
for i, v in ipairs (unit.inventory) do
-- Remove quiver only if it's not the last item in the inventory.
if v.item == quiver and i ~= (#unit.inventory - 1) then
equippedQuiver = v
end
end
if equippedQuiver then
local idx = utils.linear_index(unit.inventory, equippedQuiver)
unit.inventory:erase(idx)
return true
end
return false
end

local function MoveQuiverToEnd(unit, quiver)
local caste = dfhack.units.getCasteRaw(unit)
local bodyPart
for i, v in ipairs(caste.body_info.body_parts) do
if v.category == 'BODY_UPPER' then
bodyPart = i
break
end
end
if bodyPart then dfhack.items.moveToInventory(quiver, unit, df.inv_item_role_type.Worn, bodyPart) end
end

local function FixTrainingUnits(trainingSquads, options)
local totalTrainingAmmo = 0
local consolidateCount = 0
local splitAmmoCount = 0
local fixQuiverCount = 0
for _, squad in ipairs(trainingSquads) do
for _, position in ipairs(squad.positions) do
if position.occupant == -1 then goto nextPosition end
local unit = df.unit.find(df.historical_figure.find(position.occupant).unit_id)
local quiver = unit and df.item.find(position.equipment.quiver)
if quiver then
local trainingAmmo = GetTrainingAmmo(quiver, squad)
if #trainingAmmo > 1 then
if not options.quiet then
local unitName = unit and dfhack.units.getReadableName(unit)
print(('Consolidating training ammo for %s...'):format(unitName))
if position.occupant ~= -1 then
local unit = df.unit.find(df.historical_figure.find(position.occupant).unit_id)
local quiver = unit and df.item.find(position.equipment.quiver)
if quiver then
local trainingAmmo = GetTrainingAmmo(quiver, squad)
local unitName = unit and dfhack.units.getReadableName(unit)
if #trainingAmmo == 1 then
local item = trainingAmmo[1]
-- Split ammo if it's the only training ammo item and its stack size is 25 or larger.
if item.stack_size >= 25 then
if not options.quiet then
print(('Splitting training ammo for %s...'):format(unitName))
end
SplitAmmo(item, squad, unit)
splitAmmoCount = splitAmmoCount + 1
end
end
if RemoveQuiverFromInv(unit, quiver) then
if not options.quiet then
print(('Moving quiver to the end for %s...'):format(unitName))
end
MoveQuiverToEnd(unit, quiver)
fixQuiverCount = fixQuiverCount + 1
end
totalTrainingAmmo = totalTrainingAmmo + #trainingAmmo
ConsolidateAmmo(trainingAmmo, squad, unit)
consolidateCount = consolidateCount + 1
end
end
::nextPosition::
end
end
if not options.quiet then
if consolidateCount > 0 then
print(('%d stacks of ammo items in %d quiver(s) consolidated.'):format(totalTrainingAmmo, consolidateCount))
if splitAmmoCount > 0 then
print(('%d stack(s) of ammo item(s) split into stacks of 5.'):format(splitAmmoCount))
else
print('No ammo items require splitting.')
end
if fixQuiverCount > 0 then
print(('%d quiver(s) moved to the end of each unit\'s inventory list.'):format(fixQuiverCount))
else
print('No stacks of ammo items require consolidation.')
print('No inventories require sorting.')
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions internal/control-panel/registry.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ COMMANDS_BY_IDX = {
-- can be restored here once we solve issue #4292
-- {command='craft-age-wear', help_command='tweak', group='bugfix', mode='tweak', default=true,
-- desc='Allows items crafted from organic materials to wear out over time.'},
{command='fix/archery-practice', group='bugfix', mode='repeat',
desc='Consolidate ammo items inside quivers to allow archery practice to take place.',
{command='fix/archery-practice', group='bugfix', mode='repeat', default=true,
desc='Fix quivers and training ammo items to allow archery practice to take place.',
params={'--time', '449', '--timeUnits', 'ticks', '--command', '[', 'fix/archery-practice', '-q', ']'}},
{command='fix/blood-del', group='bugfix', mode='run', default=true},
{command='fix/dead-units', group='bugfix', mode='repeat', default=true,
Expand Down