Bay 12 Games Forum

Please login or register.

Login with username, password and session length
Advanced search  

Author Topic: combine -food = 'Combines all food and drink across all stockpiles'  (Read 2703 times)

vettlingr

  • Bay Watcher
  • Þjóðalfur
    • View Profile

Hello.

I am a big fan of the combine-drinks and combine-plants scripts of dfhack but always found
it tedious how I needed to manually go over every stockpile to combine all drinks in my forts.
That the sqript requires you to select a stockpile or write a single stockpiles id in the editor
makes it so that the script was hard to automate.

This is why I have added an -food -all function to the combine scripts, as well as merging combine-drinks and combine-plants into a single script "combine"

Download:
https://dffd.bay12games.com/file.php?id=15735

Code: [Select]
-- Merge food stacks in the selected stockpile or every stockpile
--[====[

combine-food by Vettlingr
==============
Merge stacks of food in the selected stockpile or all stockpiles.

]====]
local utils = require 'utils'

local f = {
    validArgs = utils.invert({ 'help', 'drinks', 'plants', 'meat', 'fish', 'fat', 'food', 'roasts', 'max', 'all', 'stockpile' });
    args = utils.processArgs({...}, validArgs);
help = [====[
Combine
=============
Merge stacks of food in selected Stockpile or across all stockpiles on the map.
Valid commands:
:``-drinks``:
    Merges drinks
:``-plants``:
    Merges plants
:``-meat``:
    Merges meat and intestines
:``-fat``:
    Merges fat and tallow
:``-roasts``:
    Merges prepared food
:``-fish``:
    Merges fish
:``-food``:
    Merges all food categories
:``-all``:
    Selects all stockpiles
:``-max``:
    Selects a maximum stacksize, if unspecified it will be set to 500

Examples:
combine -drinks -fish -all
    Combines drinks and fish stacks in all stockpiles

combine -food -all
    Combines all food types across all stockpiles

combine -fat -roasts -max 50
    Combines fat and prepared food in the selected stockpile with a preferred stacksize of 50

]====]
}

local max = 30

local drinks={}
local plants={}
local meats={}
local fat={}
local roasts={}
local fish={}

--Stockpile Stack sizes:
drinks.max  =   30
plants.max  =   6
meats.max   =   20
fat.max     =   20
roasts.max  =   20
fish.max    =   10

--Not sure if these are needed.
drinks.Tot=0 drinks.xTot=0
plants.Tot=0 plants.xTot=0
meats.Tot=0 meats.xTot=0
fat.Tot=0 fat.xTot=0
roasts.Tot=0 roasts.xTot=0
fish.Tot=0 fish.xTot=0

if f.args.max then max = tonumber(f.args.max) end

local stockpile = nil
if f.args.stockpile then stockpile = df.building.find(tonumber(f.args.stockpile)) end

local function itemsCompatible(item0, item1)
    return item0:getType() == item1:getType()
        and item0.mat_type == item1.mat_type
        and item0.mat_index == item1.mat_index
end

local function FishitemsCompatible(item0, item1)
    return item0:getType() == item1:getType()
        and item0.race == item1.race
        and item0.caste == item1.caste
end

function getItems(items, item, index, bool)
    repeat
        local nextBatch = {}
        for _,v in pairs(items) do
            -- Skip items currently tasked
            if #v.specific_refs == 0 then
                    if bool==1 and ( v:getType() == df.item_type.DRINK )then
                        item[index] = v
                        index = index + 1
                    elseif bool==2 and ( v:getType() == df.item_type.PLANT or v:getType() == df.item_type.PLANT_GROWTH ) then
                            item[index] = v
                            index = index + 1
                    elseif bool==3 and (v:getType() == df.item_type.MEAT ) then
                            item[index] = v
                            index = index + 1
                    elseif bool==4 and (v:getType() == df.item_type.GLOB ) then
                            item[index] = v
                            index = index + 1
                    elseif bool==5 and (v:getType() == df.item_type.FOOD or v:getType() == df.item_type.CHEESE ) then
                            item[index] = v
                            index = index + 1
                    elseif bool==10 and (v:getType() == df.item_type.FISH or v:getType() == df.item_type.FISH_RAW or v:getType() == df.item_type.EGG ) then
                        item[index] = v
                        index = index + 1
                    else
                    local containedItems = dfhack.items.getContainedItems(v)
                        if (bool==1 and #containedItems == 1) or (bool>1 and #containedItems > 0) then
                            for _,w in pairs(containedItems) do
                                table.insert(nextBatch, w)
                            end
                        end
                    end
                end
            end
        items = nextBatch
    until #items == 0
    return index
end

function Combineitems(building, tabl, food, bool)
    local rootItems
    if building then
        rootItems = dfhack.buildings.getStockpileContents(building)
    else
        rootItems = dfhack.items.getContainedItems(item)
    end
    if #rootItems == 0 and not f.args.all then
        qerror("Select a non-empty container")
        return
    else
        local foodCount = getItems(rootItems, food, 0, bool)
        local removedFood = { } --as:bool[]
        food.max=max
        if f.args.max then max = tonumber(f.args.max)
            if tonumber(f.args.max)== 0 then max = 500
            end
        end
        for i=0,(foodCount-2) do
            local currentFood = food[i] --as:df.item_foodst
            local itemsNeeded = max - currentFood.stack_size

            if removedFood[currentFood.id] == nil and itemsNeeded > 0 then
                local j = i+1
                local last = foodCount
                repeat
                    local sourceFood = food[j]
                        if bool>=10 and removedFood[sourceFood.id] == nil and FishitemsCompatible(currentFood, sourceFood) then
                            local amountToMove = math.min(itemsNeeded, sourceFood.stack_size)
                            itemsNeeded = itemsNeeded - amountToMove
                            currentFood.stack_size = currentFood.stack_size + amountToMove

                            if sourceFood.stack_size == amountToMove then
                                removedFood[sourceFood.id] = true
                                sourceFood.stack_size = 1
                            else
                                sourceFood.stack_size = sourceFood.stack_size - amountToMove
                            end
                            --                    else print("failed")
                        elseif bool <10 and removedFood[sourceFood.id] == nil and itemsCompatible(currentFood, sourceFood) then
                            local amountToMove = math.min(itemsNeeded, sourceFood.stack_size)
                            itemsNeeded = itemsNeeded - amountToMove
                            currentFood.stack_size = currentFood.stack_size + amountToMove

                            if sourceFood.stack_size == amountToMove then
                                removedFood[sourceFood.id] = true
                                if bool>1 then sourceFood.stack_size = 1 end
                            else
                                sourceFood.stack_size = sourceFood.stack_size - amountToMove
                            end
                            --                    else print("failed")
                        end
                    j = j + 1
                until j == foodCount or itemsNeeded == 0
            end
        end
        local removedCount = 0
        for id,removed in pairs(removedFood) do
            if removed then
                removedCount = removedCount + 1
                local removedFood = df.item.find(id)
                dfhack.items.remove(removedFood)
            end
        end
        if food.Tot == nil then food.Tot = 0 end
        if food.xTot == nil then food.xTot = 0 end
        food.Tot = food.Tot + foodCount
        food.xTot = food.xTot + removedCount
    end
end

if f.args.help then
    print(f.help)
    return
end
if not f.args.all then
    local building = stockpile or dfhack.gui.getSelectedBuilding(true)
    if building ~= nil and building:getType() ~= 29 then building = nil
        end
    if building ~= nil then
        if f.args.drinks or f.args.food then
            Combineitems(building, f, drinks, 1)
            print("found " .. drinks.Tot .. " drinks")
            print("merged " .. drinks.xTot .. " drinks")
        end
        if f.args.plants or f.args.food then
            Combineitems(building, f, plants, 2)
            print("found " .. plants.Tot .. " plants")
            print("merged " .. plants.xTot .. " plants")
        end
        if f.args.meat or f.args.food then
            Combineitems(building, f, meats, 3)
            print("found " .. meats.Tot .. " meat")
            print("merged " .. meats.xTot .. " meat")
        end
        if f.args.fat or f.args.food then
            Combineitems(building, f, fat, 4)
            print("found " .. fat.Tot .. " fat")
            print("merged " .. fat.xTot .. " fat")
        end
        if f.args.roasts or f.args.food then
            Combineitems(building, f, roasts, 5)
            print("found " .. roasts.Tot .. " prepared food")
            print("merged " .. roasts.xTot .. " prepared food")
        end
        if f.args.fish or f.args.food then
            Combineitems(building, f, fish, 10)
            print("found " .. fish.Tot .. " fish")
            print("merged " .. fish.xTot .. " fish")
        end
    else
        print('select a stockpile')
    end
else
    if f.args.all then
        print('Combining all food...')
        for _, building in pairs(df.global.world.buildings.all) do
            if building:getType() == 29 and building ~= nil then
                if building ~= nil then
                    if f.args.drinks or f.args.food then
                        Combineitems(building, f, drinks, 1)
                    end
                    if f.args.plants or f.args.food then
                        Combineitems(building, f, plants, 2)
                    end
                    if f.args.meat or f.args.food then
                        Combineitems(building, f, meats, 3)
                    end
                    if f.args.fat or f.args.food then
                        Combineitems(building, f, fat, 4)
                    end
                    if f.args.roasts or f.args.food then
                        Combineitems(building, f, roasts, 5)
                    end
                    if f.args.fish or f.args.food then
                        Combineitems(building, f, fish, 10)
                    end
                else
                    print('invalid')
                end
            end
        end
        if f.args.drinks or f.args.food then
            print("found " .. drinks.Tot .. " drinks")
            print("merged " .. drinks.xTot .. " drinks")
        end
        if f.args.plants or f.args.food then
            print("found " .. plants.Tot .. " plants")
            print("merged " .. plants.xTot .. " plants")
        end
        if f.args.meat or f.args.food then
            print("found " .. meats.Tot .. " meat")
            print("merged " .. meats.xTot .. " meat")
        end
        if f.args.fat or f.args.food then
            print("found " .. fat.Tot .. " fat or tallow")
            print("merged " .. fat.xTot .. " fat or tallow")
        end
        if f.args.roasts or f.args.food then
            print("found " .. roasts.Tot .. " prepared food")
            print("merged " .. roasts.xTot .. " prepared food")
        end
        if f.args.fish or f.args.food then
            print("found " .. fish.Tot .. " fish")
            print("merged " .. fish.xTot .. " fish")
        end
    end
    return
end