User:Fleeting Frames/listobituary

From Dwarf Fortress Wiki
Jump to navigation Jump to search

Requires gui/indicator-screen, run when viewing dead units list, outputs deadcounts.csv into the folder that sorts number of deaths by category and date

--Death listing shower
--[===[
listobituary
============

Displays the death date and cause in unitlist/Dead

]===]

local saveToFileEnabled = false
local indipresent, indi = pcall(function() return dfhack.script_environment('gui/indicator_screen') end)
if not indipresent then qerror("this script required gui/indicator_screen") end

function categorizeUnit(unit)
	if(df.global.world.raws.creatures.all[unit.race].flags.GENERATED) then return "Procedurals" end
	--in case of procedural invaders
	if(unit.flags1.invader_origin or unit.flags1.invades) then return "Invaders" end
	if(dfhack.units.isMerchant(unit) or (unit.flags3.unk31 and unit.flags2.visitor) ) then return "Visitors" end
	--unk31 is "guest" toggle.
	if(dfhack.units.isOwnCiv(unit) and 
	( (unit.profession>74 and unit.profession<98) or 
	   unit.profession == 110 
	or unit.profession == 65)) then return "Military" end
	--Profession is military profession or Mercenary or Administrator; not ideal but what is better?
	if(dfhack.units.isOwnCiv(unit) and (dfhack.units.isTamable(unit) or dfhack.units.isDomesticated(unit))) then return "Livestock" end
	--higher specific matches above less specific ones 
	if(dfhack.units.isOwnCiv(unit)) then return "Fort" end
	--For covering both workers and residents here. 
	if( (dfhack.units.isTamable(unit) or not df.global.world.raws.creatures.all[unit.race].caste[unit.caste].flags.CAN_LEARN)) then return "Animals" end
	--Not learning but not tamable: Unicorns, some evil and cavern creatures. 
	if(df.global.world.raws.creatures.all[unit.race].flags.CASTE_CAN_LEARN) then return "Savages" end
	--Covers animalmen, gremlins, current resident cave people and camp goblins
end

function saveToFile(deadlist, deathdates, cumulative)
	if saveToFileEnabled then
    local file, err = io.open((dfhack.getDFPath() .. "/deadcounts.csv"), "wb")
    
    local outT,outTi = {}
    outTb = {Date =0, in_years =0, Total =0, Fort =0, Military =0, Livestock =0, Animals =0, Procedurals =0, Invaders =0, Visitors =0, Savages=0}
    outTi = copyall(outTb)
    for index = 0, #deadlist-1 do 
		if index == 0 or outTi["Date"] ~= deathdates[index+1].text then
			if index ~= 0 then table.insert(outT,outTi)
			outTi = cumulative and copyall(outTi) or copyall(outTb) end
			outTi["Date"] = deathdates[index+1].text
			outTi["in_years"] = outTi["Date"]:find("Unknown") and "Unknown" or (outTi["Date"]:sub(1,4)+(outTi["Date"]:sub(6,7)-1)/12+(outTi["Date"]:sub(9,10)/(12*28)))
		end
		outTi["Total"] = outTi["Total"] + 1
		if categorizeUnit(deadlist[index]) then
		outTi[categorizeUnit(deadlist[index])] = outTi[categorizeUnit(deadlist[index])] + 1
		else
			print("No category for " .. df.global.world.raws.creatures.all[unit.race].creature_id .. " " .. dfhack.TranslateName(unit.name,true) " id " .. unit.id)
		end
    end
	table.insert(outT,outTi) --last element
	file:write("Date;in years;Total;Fort;Military;Livestock;Animals;Procedurals;Invaders;Visitors;Savages")
	file:write(NEWLINE)
	local optionorder= {}
	table.insert(optionorder,"Date")
	table.insert(optionorder,"in_years")
	table.insert(optionorder,"Total")
	table.insert(optionorder,"Fort")
	table.insert(optionorder,"Military")
	table.insert(optionorder,"Livestock")
	table.insert(optionorder,"Animals")
	table.insert(optionorder,"Procedurals")
	table.insert(optionorder,"Invaders")
	table.insert(optionorder,"Visitors")
	table.insert(optionorder,"Savages")
			
	for index = 1, #outT do
		for oindex=1,#optionorder do
			file:write((tostring(outT[index][optionorder[oindex]]) .. ";"))
		end
		file:write(NEWLINE)
	end	
	file:flush()
	file:close()
	end
end

function getBottomMostViewscreenWithFocus(text, targetscreeni)
    --Finds and returns the screen closest to root screen whose path includes text
 local targetscreen = targetscreeni or df.global.gview.view.child
 --if the target screen wasn't included, starts looking from the bottom
 if targetscreen and
    dfhack.gui.getFocusString(targetscreen):find(text) then
    return targetscreen
 elseif targetscreen and targetscreen.child then --Attempting to call nil.child will crash this
    return getBottomMostViewscreenWithFocus(text, targetscreen.child)
 end
 -- if there's no child, it didn't find a screen with text in focus and returns nil
end
orbituarylist = orbituarylist or {}
local unitscreen = getBottomMostViewscreenWithFocus("unitlist/Dead")
if not unitscreen then
qerror("This script requires you to view dead units list")
else
	print(#unitscreen.units.Dead)
	local pagelength = df.global.gps.dimy - 9
	local list_rect = {x = 86, y = 4, width = 13, height = pagelength}
	local currentpage = math.floor(unitscreen.cursor_pos.Dead / pagelength)
	orbituarylist.color=6
	
	if #orbituarylist<#unitscreen.units.Dead then --refreshing things
		for index=#orbituarylist, #unitscreen.units.Dead-1 do 
			table.insert(orbituarylist,{text = dfhack.run_command_silent("deathcause " .. unitscreen.units.Dead[index].id):sub(4,16):match('%d+-%d+-%d+') or "Unknown"})
			dfhack.println(index, orbituarylist[index].text)
		--[[if(categorizeUnit(unitscreen.units.Dead[index])) then
				print("unit's category is " .. categorizeUnit(unitscreen.units.Dead[index]))
			else
				print("No category for " .. df.global.world.raws.creatures.all[unitscreen.units.Dead[index].race].creature_id .. " " .. dfhack.TranslateName(unitscreen.units.Dead[index].name,true) .. " id " .. unitscreen.units.Dead[index].id)
			end--]]
		end
	end

	saveToFile(unitscreen.units.Dead, orbituarylist, false)
	local newscreen = indi.getScreen(orbituarylist, list_rect, nil)
	local baseonInput = newscreen.onInput
	newscreen.onInput = function(self, keys)
	baseonInput(self,keys)
		if ((keys._MOUSE_L or keys._MOUSE_L_DOWN) or
			(keys._MOUSE_R or keys._MOUSE_R_DOWN)) then
			df.global.gview.view.child.child.cursor_pos = (df.global.gps.mouse_y-2+currentpage*pagelength)
		end
	if currentpage ~= math.floor(df.global.gview.view.child.child.cursor_pos.Dead / pagelength) then
	currentpage = math.floor(df.global.gview.view.child.child.cursor_pos.Dead / pagelength)
	self.frame_body.y1 = 4-currentpage*pagelength --Hacky, but directly setting rect won't work due rollover. Might want to update indicator-screen for map scrolling.
	end
	end

	local baseOnResize = newscreen.onResize
	newscreen.onResize = function(self)
		baseOnResize(self)
		if pagelength ~= (df.global.gps.dimy - 7) then
			--If window length changed, better refresh the data.
			pagelength = df.global.gps.dimy - 7
			self:adjustDims(true,list_rect.x, list_rect.y,(df.global.gps.dimx-4), pagelength)
			--Not adjusting height here would result in situation where making screen shorter works, but taller not.
			self.frame_body.y1 = 4-currentpage*pagelength
		end
	end

	newscreen:show()
end