-
Notifications
You must be signed in to change notification settings - Fork 198
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
new tool: immortal-cravings #1301
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
a342a22
new tool: immortal-cravings
chdoc 1f56578
Merge branch 'master' into immortal-cravings
chdoc 48d19a9
implement suggestions from code review
chdoc 4e9331c
doc formatting
myk002 edc7d40
add immortal-cravings to control panel registry
myk002 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
immortal-cravings | ||
================= | ||
|
||
.. dfhack-tool:: | ||
:summary: Allow immortals to satisfy their cravings for food and drink. | ||
:tags: fort gameplay | ||
|
||
When enabled, this script watches your fort for units that have no physiological | ||
need to eat or drink but still have personality needs that can only be satisfied | ||
by eating or drinking (e.g. necromancers). This enables those units to help | ||
themselves to a drink or a meal when they crave one and are not otherwise | ||
occupied. | ||
|
||
Usage | ||
----- | ||
|
||
:: | ||
|
||
enable immortal-cravings |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
--@enable = true | ||
--@module = true | ||
|
||
local idle = reqscript('idle-crafting') | ||
local repeatutil = require("repeat-util") | ||
--- utility functions | ||
|
||
---3D city metric | ||
---@param p1 df.coord | ||
---@param p2 df.coord | ||
---@return number | ||
function distance(p1, p2) | ||
myk002 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y)) + math.abs(p1.z - p2.z) | ||
end | ||
|
||
---find closest accessible item in an item vector | ||
---@generic T : df.item | ||
---@param pos df.coord | ||
---@param item_vector T[] | ||
---@param is_good? fun(item: T): boolean | ||
---@return T? | ||
local function findClosest(pos, item_vector, is_good) | ||
local closest = nil | ||
local dclosest = -1 | ||
for _,item in ipairs(item_vector) do | ||
if not item.flags.in_job and (not is_good or is_good(item)) then | ||
local pitem = xyz2pos(dfhack.items.getPosition(item)) | ||
local ditem = distance(pos, pitem) | ||
if dfhack.maps.canWalkBetween(pos, pitem) and (not closest or ditem < dclosest) then | ||
closest = item | ||
dclosest = ditem | ||
end | ||
end | ||
end | ||
return closest | ||
end | ||
|
||
---find a drink | ||
---@param pos df.coord | ||
---@return df.item_drinkst? | ||
local function get_closest_drink(pos) | ||
local is_good = function (drink) | ||
local container = dfhack.items.getContainer(drink) | ||
return container and container:isFoodStorage() | ||
end | ||
return findClosest(pos, df.global.world.items.other.DRINK, is_good) | ||
end | ||
|
||
---find some prepared meal | ||
---@return df.item_foodst? | ||
local function get_closest_meal(pos) | ||
---@param meal df.item_foodst | ||
local function is_good(meal) | ||
if meal.flags.rotten then | ||
return false | ||
else | ||
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) | ||
end | ||
|
||
---create a Drink job for the given unit | ||
---@param unit df.unit | ||
local function goDrink(unit) | ||
local drink = get_closest_drink(unit.pos) | ||
if not drink then | ||
-- print('no accessible drink found') | ||
return | ||
end | ||
local job = idle.make_job() | ||
job.job_type = df.job_type.DrinkItem | ||
job.flags.special = true | ||
local dx, dy, dz = dfhack.items.getPosition(drink) | ||
job.pos = xyz2pos(dx, dy, dz) | ||
if not dfhack.job.attachJobItem(job, drink, df.job_item_ref.T_role.Other, -1, -1) then | ||
error('could not attach drink') | ||
return | ||
end | ||
dfhack.job.addWorker(job, unit) | ||
local name = dfhack.TranslateName(dfhack.units.getVisibleName(unit)) | ||
print(dfhack.df2console('immortal-cravings: %s is getting a drink'):format(name)) | ||
end | ||
|
||
---create Eat job for the given unit | ||
---@param unit df.unit | ||
local function goEat(unit) | ||
local meal = get_closest_meal(unit.pos) | ||
if not meal then | ||
-- print('no accessible meals found') | ||
return | ||
end | ||
local job = idle.make_job() | ||
job.job_type = df.job_type.Eat | ||
job.flags.special = true | ||
local dx, dy, dz = dfhack.items.getPosition(meal) | ||
job.pos = xyz2pos(dx, dy, dz) | ||
if not dfhack.job.attachJobItem(job, meal, df.job_item_ref.T_role.Other, -1, -1) then | ||
error('could not attach meal') | ||
return | ||
end | ||
dfhack.job.addWorker(job, unit) | ||
local name = dfhack.TranslateName(dfhack.units.getVisibleName(unit)) | ||
print(dfhack.df2console('immortal-cravings: %s is getting something to eat'):format(name)) | ||
end | ||
|
||
--- script logic | ||
|
||
local GLOBAL_KEY = 'immortal-cravings' | ||
|
||
enabled = enabled or false | ||
function isEnabled() | ||
return enabled | ||
end | ||
|
||
local function persist_state() | ||
dfhack.persistent.saveSiteData(GLOBAL_KEY, { | ||
enabled=enabled, | ||
}) | ||
end | ||
|
||
--- Load the saved state of the script | ||
local function load_state() | ||
-- load persistent data | ||
local persisted_data = dfhack.persistent.getSiteData(GLOBAL_KEY, {}) | ||
enabled = persisted_data.enabled or false | ||
end | ||
|
||
DrinkAlcohol = df.need_type.DrinkAlcohol | ||
EatGoodMeal = df.need_type.EatGoodMeal | ||
|
||
---@type integer[] | ||
watched = watched or {} | ||
|
||
local threshold = -9000 | ||
|
||
---unit loop: check for idle watched units and create eat/drink jobs for them | ||
local function unit_loop() | ||
-- print(('immortal-cravings: running unit loop (%d watched units)'):format(#watched)) | ||
---@type integer[] | ||
local kept = {} | ||
for _, unit_id in ipairs(watched) do | ||
local unit = df.unit.find(unit_id) | ||
if | ||
not unit or not dfhack.units.isActive(unit) or | ||
unit.flags1.caged or unit.flags1.chained | ||
then | ||
goto next_unit | ||
end | ||
if not idle.unitIsAvailable(unit) then | ||
table.insert(kept, unit.id) | ||
else | ||
-- unit is available for jobs; satisfy one of its needs | ||
for _, need in ipairs(unit.status.current_soul.personality.needs) do | ||
if need.id == DrinkAlcohol and need.focus_level < threshold then | ||
goDrink(unit) | ||
break | ||
elseif need.id == EatGoodMeal and need.focus_level < threshold then | ||
goEat(unit) | ||
break | ||
end | ||
end | ||
end | ||
::next_unit:: | ||
end | ||
watched = kept | ||
if #watched == 0 then | ||
-- print('immortal-cravings: no more watched units, cancelling unit loop') | ||
repeatutil.cancel(GLOBAL_KEY .. '-unit') | ||
end | ||
end | ||
|
||
---main loop: look for citizens with personality needs for food/drink but w/o physiological need | ||
local function main_loop() | ||
-- print('immortal-cravings watching:') | ||
watched = {} | ||
for _, unit in ipairs(dfhack.units.getCitizens()) do | ||
if unit.curse.add_tags1.NO_DRINK or unit.curse.add_tags1.NO_EAT then | ||
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 | ||
then | ||
table.insert(watched, unit.id) | ||
-- print(' '..dfhack.df2console(dfhack.TranslateName(dfhack.units.getVisibleName(unit)))) | ||
goto next_unit | ||
end | ||
end | ||
end | ||
::next_unit:: | ||
end | ||
|
||
if #watched > 0 then | ||
repeatutil.scheduleUnlessAlreadyScheduled(GLOBAL_KEY..'-unit', 59, 'ticks', unit_loop) | ||
end | ||
end | ||
|
||
local function start() | ||
if enabled then | ||
repeatutil.scheduleUnlessAlreadyScheduled(GLOBAL_KEY..'-main', 4003, 'ticks', main_loop) | ||
end | ||
end | ||
|
||
local function stop() | ||
repeatutil.cancel(GLOBAL_KEY..'-main') | ||
repeatutil.cancel(GLOBAL_KEY..'-unit') | ||
end | ||
|
||
|
||
|
||
-- script action | ||
|
||
--- Handles automatic loading | ||
dfhack.onStateChange[GLOBAL_KEY] = function(sc) | ||
if sc == SC_MAP_UNLOADED then | ||
enabled = false | ||
myk002 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
-- repeat-util will cancel the loops on unload | ||
return | ||
end | ||
|
||
if sc ~= SC_MAP_LOADED or df.global.gamemode ~= df.game_mode.DWARF then | ||
return | ||
end | ||
|
||
load_state() | ||
start() | ||
end | ||
|
||
if dfhack_flags.enable then | ||
if dfhack_flags.enable_state then | ||
enabled = true | ||
start() | ||
else | ||
enabled = false | ||
stop() | ||
end | ||
persist_state() | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you also add this tool to the control panel registry (
internal/control-panel/registry.lua
)?