Skip to content

[immortal-cravings] prioritize high-value meals #1475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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 @@ -36,6 +36,7 @@ Template for new versions:
- `gui/journal`: fix typo which caused the table of contents to always be regenerated even when not needed
- `gui/mod-manager`: gracefully handle mods with missing or broken ``info.txt`` files
- `uniform-unstick`: resolve overlap with new buttons in 51.13
- `immortal-cravings`: prioritize high-value meals and don't go eating or drinking on a full stomach

## Misc Improvements

Expand Down
49 changes: 41 additions & 8 deletions immortal-cravings.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ function distance(p1, p2)
return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y)) + math.abs(p1.z - p2.z)
end

---find best item in an item vector (according to some metric)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems to me that findClosest is findBest but with a specific metric (distance) and so findClosest could be written as a specialization of findBest

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are of course right. I realized this at some point and then forgot about it. I just tried it, but the result is barely simpler and less efficient, because one now needs to compute the position (xyz2pos(dfhack.items.getPosition(item))) separately for the metric and the accessibility check. I can do it, but I don't think it is worth it.

---@generic T : df.item
---@param item_vector T[]
---@param metric fun(item: T): number
---@param is_good? fun(item: T): boolean
---@return T?
function findBest(item_vector, metric, is_good)
local best = nil
local mbest = -1
for _,item in ipairs(item_vector) do
if not item.flags.in_job and (not is_good or is_good(item)) then
mitem = metric(item)
if not best or mitem > mbest then
best = item
mbest = mitem
end
end
end
return best
end

---find closest accessible item in an item vector
---@generic T : df.item
---@param pos df.coord
Expand Down Expand Up @@ -46,19 +67,28 @@ local function get_closest_drink(pos)
return findClosest(pos, df.global.world.items.other.DRINK, is_good)
end

---find some prepared meal
---find highest-value accessible meal
---@return df.item_foodst?
local function get_closest_meal(pos)
local function get_best_meal(pos)

---@param meal df.item_foodst
local function is_good(meal)
if meal.flags.rotten then
local accessible = dfhack.maps.canWalkBetween(pos,xyz2pos(dfhack.items.getPosition(meal)))
if meal.flags.rotten or not accessible then
return false
else
-- check that meal is either on the ground or in food storage (and not in a backpack)
local container = dfhack.items.getContainer(meal)
return not container or container:isFoodStorage()
end
end
return findClosest(pos, df.global.world.items.other.FOOD, is_good)

---@param meal df.item_foodst
local function portion_value(meal)
return dfhack.items.getValue(meal) / meal.stack_size
end

return findBest(df.global.world.items.other.FOOD, portion_value, is_good)
end

---create a Drink job for the given unit
Expand Down Expand Up @@ -86,7 +116,7 @@ end
---create Eat job for the given unit
---@param unit df.unit
local function goEat(unit)
local meal = get_closest_meal(unit.pos)
local meal = get_best_meal(unit.pos)
if not meal then
-- print('no accessible meals found')
return
Expand Down Expand Up @@ -181,12 +211,15 @@ local function main_loop()
-- print('immortal-cravings watching:')
watched = {}
for _, unit in ipairs(dfhack.units.getCitizens()) do
if not is_active_caste_flag(unit, 'NO_DRINK') and not is_active_caste_flag(unit, 'NO_EAT') then
if
not (is_active_caste_flag(unit, 'NO_DRINK') or is_active_caste_flag(unit, 'NO_EAT')) or
unit.counters2.stomach_content > 0
then
goto next_unit
end
for _, need in ipairs(unit.status.current_soul.personality.needs) do
if need.id == DrinkAlcohol and need.focus_level < threshold or
need.id == EatGoodMeal and need.focus_level < threshold
if need.id == DrinkAlcohol and need.focus_level < threshold or
need.id == EatGoodMeal and need.focus_level < threshold
then
table.insert(watched, unit.id)
-- print(' '..dfhack.df2console(dfhack.units.getReadableName(unit)))
Expand Down