Bay 12 Games Forum

Dwarf Fortress => DF Modding => Mod Releases => Topic started by: Atkana on June 30, 2017, 09:56:59 am

Title: [DFhack] Atkana's Scripts - New: Cannibalism fixes
Post by: Atkana on June 30, 2017, 09:56:59 am
(http://i.imgur.com/5v15nXn.png)

While playing adventure mode I've often made scripts to improve my adventuring experience, and since I've now reached the skill level that I'm capable of making somewhat presentable scripts, I figured I'd share the ones I've made that might be of use to some people.

Table of contents: What do you mean, "there are no anchors"?

Adventurer Freedom - Download (http://dffd.bay12games.com/file.php?id=12986)
Brief Feature Overview:At the moment it runs through the DFhack command-prompt but I hope one day (when I actually figure GUI stuff out) it'll have a swanky GUI interface, or at the very least use the Dwarf Fortress window
I was delayed long enough before uploading the mod that there's now a whole new addition. First, there's the original command-prompt version, and now there's an additional new script that performs some of its features automatically, as well as some new features. The new automatic version automatically adds every creature to the Intelligent Wilderness Animal section, automatically makes every civ available to start from, automatically patches in any natural skills and automatically unlocks the skill list to make every skill available to purchase. Automatically automatically automatically. It also has a bunch of settings that can be altered to toggle certain features on/off, among other things. With all the extra stuff added to the menus, it's worth remembering that Page Up and Page Down can be used to help navigate :P

The download also contains a small tweak script to allow for playing as non-standard race to be a more enjoyable experience, like giving your adventurer the ability to open doors, learn skills, and speak. The features of the tweak script are fully customizable and can be toggled on/off.

I'd include the readme in this post but it sets me over the character limit ^_^;

View Allegiance
One of the things that always nags me is that there's generally no way to ask characters what groups they belong to - you usually only end up finding out when the citizens of the nearby hamlet start calling you a murderer for killing the person you thought was a bandit, or when your new companion suddenly decides to start a massacre in the town market because it turns out they're a sworn enemy of the people who live there. So I made this script. It prints all the entities a person has links to and the nature of those links, so used in conjunction with information gathered ingame and perhaps a bit of legends viewer it allows you to make informed decisions on how to act.
Spoiler (click to show/hide)

Make Companion (download (https://github.com/Atkana/Dwarf-Fortress-Mods/blob/master/make-companion.lua))
Tired of having to actually do a bunch of good deeds to cement your reputation as a legend before you can recruit a bunch of peasants to build a nice house for you? Use this script and you can turn anyone into a travelling companion!

(See script's help entry for usage)

Notes:

Clothing Optional (download (https://github.com/Atkana/Dwarf-Fortress-Mods/blob/master/clothing-optional.lua))
Mainly intended for modded creatures that should be fine not wearing particular types of clothes, this script allows you to register creatures or entities to no longer get upset if they're lacking shirts, pants, shoes, or any combination of the three. This means you no longer have to set their bashfulness to minimum

(See script's help entry for usage)

Make Citizen (download (https://github.com/Atkana/Dwarf-Fortress-Mods/blob/master/make-citizen.lua))
An attempt at replacing the functionality of the now outdated tweak makeown. The script allows for converting units and animals to belong to a different civilization. In fort mode, you can use this to make full citizens out of visiting units.

The script isn't extensively tested (and missing some extra features), but is completely functional in basic cases. I haven't tested the more weirder cases such as what happens when you convert invaders.

See help entry for usage.

There are some outstanding bugs that might cause corruption/crashes, but I don't know the cause or how to fix them :(.

World Patch (download (https://github.com/Atkana/Dwarf-Fortress-Mods/tree/master/patch))
An attempt to create a method of applying basic changes to the game via the use of non-destructive code rather than editing raw files. This makes it potentially simpler to run lots of different mods, as well as alter some aspects that wouldn't otherwise be available to edit in raws (such as the raws of generated creatures).

Here's what's currently available with the scripts I've made so far:
Announce Skills (download (https://github.com/Atkana/Dwarf-Fortress-Mods/blob/master/announce-skills.lua))
Creates announcements related to citizens levelling up their skills in fort mode. The script can make announcements for any skill increase, as well as report when a new citizen becomes the best at a skill. It can be configured to only consider certain categories of skills, in case there's some you're focused/not interested in. Unless you plan to have different configurations between saves, this only needs to be run once per session, and so is a good candidate to include within a dfhack*.init

Passive Training (download (https://github.com/Atkana/Dwarf-Fortress-Mods/blob/master/passive-training.lua))
Allows the player to select skills for their adventurer (or other chosen unit) to passively train over time. They'll gain experience as time passes, split across all the skills you have selected and accounting for their skill learn rates. See this as an alternative to simply sitting around grinding skills manually.

See help entry for usage.

Important Note: Requires script-data to be installed. If it's not already part of DFhack, you can find a copy on the repo in the Other folder.

Auto-Cannibalism (download (https://github.com/Atkana/Dwarf-Fortress-Mods/blob/master/auto-cannibalism.lua))
A workaround / fix that runs in the background that allows entities who should otherwise be able to, to eat sapient creatures / use their parts (currently Dwarf Fortress is bugged and doesn't allow it). It can also be used to allow non-sapient-eaters to butcher the corpses of their dead animals (again, another Dwarf Fortress bug), if willing to live with the side-effects of the script's workarounds.

See help entry for usage and info.


Shoutouts to Max for teaching me some of the stuff I needed to know for making Adventurer Freedom, as well as letting me steal his code :P

Feel free to post if you have any questions/know how I could be doing things better/run into problems - I do my best to make sure my scripts work properly before I share them, but they could all definitely benefit from some decent field testing.
Title: Re: [DFhack 43.05r1] Atkana's Scripts (Adventure as anything, Change appearance)
Post by: Max™ on June 30, 2017, 04:12:10 pm
Hey that is cool shit and I'm totally going to incorporate what you did there with the appearance stuff since it's exactly what I was trying to unlazy my ass and finish working on.
Title: Re: [DFhack 43.05r1] Atkana's Scripts (Adventure as anything, Change appearance)
Post by: Max™ on June 30, 2017, 11:31:34 pm
Ok, so I noticed we were both being silly here and had a great idea, I tried to use the work you did to get it done but the only thing you really lack with scripting is familiarity with the dfhack api and such cause there is some shit in there that I can immediately see better ways to achieve (like replacing "if df.global.world.raws.creature.all[k] matches foo then bar" stuff with df.creature_raw.find(k) and so on) but I ran out of oomph while trying to figure this out so I'll let you see if you can get anything going here.

This is the existing script gui/gm-unit.lua with an attempt at getting your appearance modification script folded in as an editor, it's near the very end of the script, just after the wound editor bits:
Code: (gm-unit.lua) [Select]
-- Interface powered, user friendly, unit editor

--[====[

gui/gm-unit
===========
An editor for various unit attributes.

]====]
local gui = require 'gui'
local dialog = require 'gui.dialogs'
local widgets =require 'gui.widgets'
local guiScript = require 'gui.script'
local utils = require 'utils'
local args={...}


local target
--TODO: add more ways to guess what unit you want to edit
if args[1]~= nil then
    target=df.units.find(args[1])
else
    target=dfhack.gui.getSelectedUnit(true)
end

if target==nil then
    qerror("No unit to edit") --TODO: better error message
end
local editors={}
function add_editor(editor_class)
    table.insert(editors,{text=editor_class.ATTRS.frame_title,on_submit=function ( unit )
        editor_class{target_unit=unit}:show()
    end})
end
-------------------------------various subeditors---------
--TODO set local sould or better yet skills vector to reduce long skill list access typing
editor_skills=defclass(editor_skills,gui.FramedScreen)
editor_skills.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Skill editor",
    target_unit = DEFAULT_NIL,
    learned_only= false,
}
function list_skills(unit,learned_only)
    local s_=df.job_skill
    local u_skills=unit.status.current_soul.skills
    local ret={}
    for i,v in ipairs(s_) do
        if i>=0 then
            local u_skill=utils.binsearch(u_skills,i,"id")
            if u_skill or not learned_only then
                if not u_skill then
                    u_skill={rating=-1,experience=0}
                end

                local rating
                if u_skill.rating >=0 then
                    rating=df.skill_rating.attrs[u_skill.rating]
                else
                    rating={caption="<unlearned>",xp_threshold=0}
                end

                local text=string.format("%s: %s %d %d/%d",df.job_skill.attrs[i].caption,rating.caption,u_skill.rating,u_skill.experience,rating.xp_threshold)
                table.insert(ret,{text=text,id=i})
            end
        end
    end
    return ret
end
function editor_skills:update_list(no_save_place)
    local skill_list=list_skills(self.target_unit,self.learned_only)
    if no_save_place then
        self.subviews.skills:setChoices(skill_list)
    else
        self.subviews.skills:setChoices(skill_list,self.subviews.skills:getSelected())
    end
end
function editor_skills:init( args )
    if self.target_unit.status.current_soul==nil then
        qerror("Unit does not have soul, can't edit skills")
    end

    local skill_list=list_skills(self.target_unit,self.learned_only)

    self:addviews{
    widgets.FilteredList{
        choices=skill_list,
        frame = {t=0, b=1,l=1},
        view_id="skills",
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": remove level ",
                    key = "SECONDSCROLL_UP",
                    on_activate=self:callback("level_skill",-1)},
                    {text=": add level ",
                    key = "SECONDSCROLL_DOWN",
                    on_activate=self:callback("level_skill",1)}
                    ,
                    {text=": show learned only ",
                    key = "CHANGETAB",
                    on_activate=function ()
                        self.learned_only=not self.learned_only
                        self:update_list(true)
                    end}
                    }
            },
        }
end
function editor_skills:get_cur_skill()
    local list_wid=self.subviews.skills
    local _,choice=list_wid:getSelected()
    if choice==nil then
        qerror("Nothing selected")
    end
    local u_skill=utils.binsearch(self.target_unit.status.current_soul.skills,choice.id,"id")
    return choice,u_skill
end
function editor_skills:level_skill(lvl)
    local sk_en,sk=self:get_cur_skill()
    if lvl >0 then
        local rating

        if sk then
            rating=sk.rating+lvl
        else
            rating=lvl-1
        end

        utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=rating}, 'id') --TODO set exp?
    elseif sk and sk.rating==0 and lvl<0 then
        utils.erase_sorted_key(self.target_unit.status.current_soul.skills,sk_en.id,"id")
    elseif sk and lvl<0 then
        utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=sk.rating+lvl}, 'id') --TODO set exp?
    end
    self:update_list()
end
function editor_skills:remove_rust(skill)
    --TODO
end
add_editor(editor_skills)
------- civ editor
RaceBox = defclass(RaceBox, dialog.ListBox)
RaceBox.focus_path = 'RaceBox'

RaceBox.ATTRS{
    format_name="$NAME ($TOKEN)",
    with_filter=true,
    allow_none=false,
}
function RaceBox:format_creature(creature_raw)
    local t = {NAME=creature_raw.name[0],TOKEN=creature_raw.creature_id}
    return string.gsub(self.format_name, "%$(%w+)", t)
end
function RaceBox:preinit(info)
    self.format_name=RaceBox.ATTRS.format_name or info.format_name -- preinit does not have ATTRS set yet
    local choices={}
    if RaceBox.ATTRS.allow_none or info.allow_none then
        table.insert(choices,{text="<none>",num=-1})
    end
    for i,v in ipairs(df.global.world.raws.creatures.all) do
        local text=self:format_creature(v)
        table.insert(choices,{text=text,raw=v,num=i,search_key=text:lower()})
    end
    info.choices=choices
end
function showRacePrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_none)
    RaceBox{
        frame_title = title,
        text = text,
        text_pen = tcolor,
        on_select = on_select,
        on_cancel = on_cancel,
        frame_width = min_width,
        allow_none = allow_none,
    }:show()
end
CivBox = defclass(CivBox,dialog.ListBox)
CivBox.focus_path = "CivBox"

CivBox.ATTRS={
    format_name="$NAME ($ENGLISH):$ID",
    format_no_name="<unnamed>:$ID",
    name_other="<other(-1)>",
    with_filter=true,
    allow_other=false,
}

function civ_name(id,format_name,format_no_name,name_other,name_invalid)
    if id==-1 then
        return name_other or "<other (-1)>"
    end
    local civ
    if type(id)=='userdata' then
        civ=id
    else
        civ=df.historical_entity.find(id)
        if civ==nil then
            return name_invalid or "<invalid>"
        end
    end
    local t={NAME=dfhack.TranslateName(civ.name),ENGLISH=dfhack.TranslateName(civ.name,true),ID=civ.id} --TODO race?, maybe something from raws?
    if t.NAME=="" then
        return string.gsub(format_no_name or "<unnamed>:$ID", "%$(%w+)", t)
    end
    return string.gsub(format_name or "$NAME ($ENGLISH):$ID", "%$(%w+)", t)
end
function CivBox:update_choices()
    local choices={}
    if self.allow_other then
        table.insert(choices,{text=self.name_other,num=-1})
    end

    for i,v in ipairs(df.global.world.entities.all) do
        if not self.race_filter or (v.race==self.race_filter) then --TODO filter type
            local text=civ_name(v,self.format_name,self.format_no_name,self.name_other,self.name_invalid)
            table.insert(choices,{text=text,raw=v,num=i})
        end
    end
    self.choices=choices
    if self.subviews.list then
        self.subviews.list:setChoices(self.choices)
    end
end
function CivBox:update_race_filter(id)
    local raw=df.creature_raw.find(id)
    if raw then
        self.subviews.race_label:setText(": "..raw.name[0])
        self.race_filter=id
    else
        self.subviews.race_label:setText(": <none>")
        self.race_filter=nil
    end

    self:update_choices()
end
function CivBox:choose_race()
    showRacePrompt("Choose race","Select new race:",nil,function (id,choice)
        self:update_race_filter(choice.num)
    end,nil,nil,true)
end
function CivBox:init(info)
    self.subviews.list.frame={t=3,r=0,l=0}
    self:addviews{
        widgets.Label{frame={t=1,l=0},text={
        {text="Filter race ",key="CUSTOM_CTRL_A",key_sep="()",on_activate=self:callback("choose_race")},
        }},
        widgets.Label{frame={t=1,l=21},view_id="race_label",
        text=": <none>",
        }
    }
    self:update_choices()
end
function showCivPrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_other)
    CivBox{
        frame_title = title,
        text = text,
        text_pen = tcolor,
        on_select = on_select,
        on_cancel = on_cancel,
        frame_width = min_width,
        allow_other = allow_other,
    }:show()
end

editor_civ=defclass(editor_civ,gui.FramedScreen)
editor_civ.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Civilization editor",
    target_unit = DEFAULT_NIL,
    }

function editor_civ:update_curren_civ()
    self.subviews.civ_name:setText("Currently: "..civ_name(self.target_unit.civ_id))
end
function editor_civ:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

    self:addviews{
    widgets.Label{view_id="civ_name",frame = { t=1,l=1}, text="Currently: "..civ_name(self.target_unit.civ_id)},
    widgets.Label{frame = { t=2,l=1}, text={{text=": set to other (-1, usually enemy)",key="CUSTOM_N",
        on_activate= function() self.target_unit.civ_id=-1;self:update_curren_civ() end}}},
    widgets.Label{frame = { t=3,l=1}, text={{text=": set to current civ("..df.global.ui.civ_id..")",key="CUSTOM_C",
        on_activate= function() self.target_unit.civ_id=df.global.ui.civ_id;self:update_curren_civ() end}}},
    widgets.Label{frame = { t=4,l=1}, text={{text=": manually enter",key="CUSTOM_E",
        on_activate=function ()
         dialog.showInputPrompt("Civ id","Enter new civ id:",COLOR_WHITE,
            tostring(self.target_unit.civ_id),function(new_value)
                self.target_unit.civ_id=new_value
                self:update_curren_civ()
            end)
        end}}
        },
    widgets.Label{frame= {t=5,l=1}, text={{text=": select from list",key="CUSTOM_L",
        on_activate=function (  )
            showCivPrompt("Choose civilization", "Select units civilization",nil,function ( id,choice )
                self.target_unit.civ_id=choice.num
                self:update_curren_civ()
            end,nil,nil,true)
        end
        }}},
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    }
            },
        }
end
add_editor(editor_civ)
------- counters editor
editor_counters=defclass(editor_counters,gui.FramedScreen)
editor_counters.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Counters editor",
    target_unit = DEFAULT_NIL,
    counters1={
    "think_counter",
    "job_counter",
    "swap_counter",
    "winded",
    "stunned",
    "unconscious",
    "suffocation",
    "webbed",
    "soldier_mood_countdown",
    "soldier_mood", --todo enum,
    "pain",
    "nausea",
    "dizziness",
    },
    counters2={
    "paralysis",
    "numbness",
    "fever",
    "exhaustion",
    "hunger_timer",
    "thirst_timer",
    "sleepiness_timer",
    "stomach_content",
    "stomach_food",
    "vomit_timeout",
    "stored_fat" --TODO what to reset to?
    }
}
function editor_counters:fill_counters()
    local ret={}
    local u=self.target_unit
    for i,v in ipairs(self.counters1) do
        table.insert(ret,{f=u.counters:_field(v),name=v})
    end
    for i,v in ipairs(self.counters2) do
        table.insert(ret,{f=u.counters2:_field(v),name=v})
    end
    return ret
end
function editor_counters:update_counters()
    for i,v in ipairs(self.counter_list) do
        v.text=string.format("%s:%d",v.name,v.f.value)
    end
    self.subviews.counters:setChoices(self.counter_list)
end
function editor_counters:set_cur_counter(value,index,choice)
    choice.f.value=value
    self:update_counters()
end
function editor_counters:choose_cur_counter(index,choice)
    dialog.showInputPrompt(choice.name,"Enter new value:",COLOR_WHITE,
            tostring(choice.f.value),function(new_value)
                self:set_cur_counter(new_value,index,choice)
            end)
end
function editor_counters:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

    self.counter_list=self:fill_counters()


    self:addviews{
    widgets.FilteredList{
        choices=self.counter_list,
        frame = {t=0, b=1,l=1},
        view_id="counters",
        on_submit=self:callback("choose_cur_counter"),
        on_submit2=self:callback("set_cur_counter",0),--TODO some things need to be set to different defaults
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": reset counter ",
                    key = "SEC_SELECT",
                    },
                    {text=": set counter ",
                    key = "SELECT",
                    }
                   
                    }
            },
        }
    self:update_counters()
end
add_editor(editor_counters)

wound_creator=defclass(wound_creator,gui.FramedScreen)
wound_creator.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Wound creator",
    target_wound = DEFAULT_NIL,
    --filter
}
function wound_creator:init( args )
    if self.target_wound==nil then
        qerror("invalid wound")
    end
   

    self:addviews{
    widgets.List{
       
        frame = {t=0, b=1,l=1},
        view_id="fields",
        on_submit=self:callback("edit_cur_wound"),
        on_submit2=self:callback("delete_current_wound")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")},

                    {text=": edit wound ",
                    key = "SELECT"},

                    {text=": delete wound ",
                    key = "SEC_SELECT"},
                    {text=": create wound ",
                    key = "CUSTOM_CTRL_I",
                    on_activate= self:callback("create_new_wound")},

                    }
            },
        }
    self:update_wounds()
end
-------------------
editor_wounds=defclass(editor_wounds,gui.FramedScreen)
editor_wounds.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Wound editor",
    target_unit = DEFAULT_NIL,
    --filter
}
function is_scar( wound_part )
    return wound_part.flags1.scar_cut or wound_part.flags1.scar_smashed or
        wound_part.flags1.scar_edged_shake1 or wound_part.flags1.scar_blunt_shake1
end
function format_flag_name( fname )
    return fname:sub(1,1):upper()..fname:sub(2):gsub("_"," ")
end
function name_from_flags( wp )
    for i,v in ipairs(wp.flags1) do
        if v then
            return format_flag_name(df.wound_damage_flags1[i])
        end
    end
    for i,v in ipairs(wp.flags2) do
        if v then
            return format_flag_name(df.wound_damage_flags2[i])
        end
    end
    return "<unnamed wound>"
end
function format_wound( list_id,wound, unit)

    local name="<unnamed wound>"
    if #wound.parts>0 and #wound.parts[0].effect_type>0 then --try to make wound name by effect...
        name=tostring(df.wound_effect_type[wound.parts[0].effect_type[0]])
        if #wound.parts>1 then --cheap and probably incorrect...
            name=name.."s"
        end
    elseif #wound.parts>0 and is_scar(wound.parts[0]) then
        name="Scar"
    elseif #wound.parts>0 then
        local wp=wound.parts[0]
        name=name_from_flags(wp)
    end

    return string.format("%d. %s id=%d",list_id,name,wound.id)
end
function editor_wounds:update_wounds()
    local ret={}
    for i,v in ipairs(self.trg_wounds) do
        table.insert(ret,{text=format_wound(i,v,self.target_unit),wound=v})
    end
    self.subviews.wounds:setChoices(ret)
    self.wound_list=ret
end
function editor_wounds:dirty_unit()
    print("todo: implement unit status recalculation")
end
function editor_wounds:get_cur_wound()
    local list_wid=self.subviews.wounds
    local _,choice=list_wid:getSelected()
    if choice==nil then
        qerror("Nothing selected")
    end
    local ret_wound=utils.binsearch(self.trg_wounds,choice.id,"id")
    return choice,ret_wound
end
function editor_wounds:delete_current_wound(index,choice)
   
    utils.erase_sorted(self.trg_wounds,choice.wound,"id")
    choice.wound:delete()
    self:dirty_unit()
    self:update_wounds()
end
function editor_wounds:create_new_wound()
    print("Creating")
end
function editor_wounds:edit_cur_wound(index,choice)
   
end
function editor_wounds:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end
    self.trg_wounds=self.target_unit.body.wounds

    self:addviews{
    widgets.List{
       
        frame = {t=0, b=1,l=1},
        view_id="wounds",
        on_submit=self:callback("edit_cur_wound"),
        on_submit2=self:callback("delete_current_wound")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")},

                    {text=": edit wound ",
                    key = "SELECT"},

                    {text=": delete wound ",
                    key = "SEC_SELECT"},
                    {text=": create wound ",
                    key = "CUSTOM_CTRL_I",
                    on_activate= self:callback("create_new_wound")},

                    }
            },
        }
    self:update_wounds()
end
add_editor(editor_wounds)
--------
editor_appearance=defclass(editor_appearance,gui.FramedScreen)
editor_appearance.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Appearance editor",
    target_unit = DEFAULT_NIL,
    appearance={
     body_modifiers = {},
     bp_modifiers = {},
     tissue_style = {},
     tissue_length = {},
     colors = {}
    }
}
local urace = df.global.world.units.active[0].race
local ucritter = df.creature_raw.find(urace)
local ucaste = ucritter.caste
function list_body(height,broad)
    local modifiers = ucaste.body_appearance_modifiers
    local height, broad
    for i = 0, #modifiers-1 do
if modifier[i].type==0 then
height = bodmod[i]
elseif modifier[i].type==1 then
broad = bodmod[i]
end
    end
    return height, broad
end
function editor_appearance:format_body()
    local bodmod = {}
    for i,v in ipairs(self.body_modifiers) do
table.insert(bodmod,{["height"] = height, ["broadness"] = broad, self.target_unit})
    end
    return bodmod
end
function list_bps(bps, bplist)
    local bps = ucaste.bp_appearance
    local ret = {}
    for i = 0, #bps.part_idx-1 do
ret[i] = {["modifierId"] = bps.modifier_idx[i], ["partId"] = bps.part_idx[i]}
    end
    bplist = ret
end
function list_bpmods(bpmods)
    local bpmods = ucaste.bp_appearance.modifiers
    for i = 0, #bpmods-1 do
     local bpm = bpmods[i]
     local bpmname
if #bpm.name > 0 then
bpmname = bpm.name
else
bpmname = unit.body_info.body_parts[bpm.body_parts[0]].name_singular[0].value
end
    end
    local changes = {}
    list_bps(bps, bplist)
    for i = 0, bpm.body_parts-1 do
  local partId = bpm.body_parts[i]
  for j = 0, #bplist-1 do
  if bplist[j].modifierId == k and modMatch[j].partId == partId then
table.insert(changes, j)
        local out = {["modifier"] = bpm, ["changes"] = changes}
  end
   end
    end
    return bpmods, out   
end
function editor_appearance:format_bpmods()
    local bpmodlist = {}
    for i,v in ipairs(self.bp_modifiers) do
table.insert(bpmodlist,{text=list_bpmods(bpmods[i], out, self.target_unit)})
    end
    return bpmodlist
end
--[[
function editor_appearance:list_colors()
    local ret={}
    local u=self.target_unit.appearance
local function getColor(id)
return df.descriptor_color.find(id)
end
local function getPattern(id)
return df.descriptor_pattern.find(id)
end
local function patternString(id)
local pattern = getPattern(id)
local prefix
if pattern.pattern == 0 then --Monochrome
return getColor(pattern.colors[0]).name
elseif pattern.pattern == 1 then --Stripes
prefix = "striped"
elseif pattern.pattern == 2 then --Iris_eye
return getColor(pattern.colors[2]).name .. " eye"
elseif pattern.pattern == 3 then --Spots
prefix = "spotted" --that's a guess
elseif pattern.pattern == 4 then --Pupil_eye
return getColor(pattern.colors[2]).name .. " eye"
elseif pattern.pattern == 5 then --mottled
prefix = "mottled"
end
local out = prefix .. " "
for i=0, #pattern.colors-1 do
if i == #pattern.colors-1 then
out = out .. "and " .. getColor(pattern.colors[i]).name
elseif i == #pattern.colors-2 then
out = out .. getColor(pattern.colors[i]).name .. " "
else
out = out .. getColor(pattern.colors[i]).name .. ", "
end
end
return out
end
    for j,k in ipairs(self.colors) do
table.insert(ret,{text=out,name=j})
    end
    return ret
end
function editor_appearance:update_colors()
    for i,v in ipairs(self.color_list) do
v.text=string.format
self.color_list=self:list_colors()
local function modifierString(mod)
local out = df.appearance_modifier_type[mod.type]
out = out:lower() --Make lowercase
out = out:gsub("_", " ") --Replace underscores with spaces
out = firstUpper(out) --capitalises first letter
return out
end]]
function editor_appearance:reduce_value()
end
function editor_appearance:raise_value()
end
function editor_appearance:init( args )
    self:addviews{
    widgets.FilteredList{
        choices=bodmod, bpmodlist,
        frame = {t=0, b=1,l=1},
        view_id="appearance",
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": reduce value ",
                    key = "SECONDSCROLL_UP",
                    on_activate=self:callback("reduce_value",-1)},
                    {text=": raise value ",
                    key = "SECONDSCROLL_DOWN",
                    on_activate=self:callback("raise_value",1)}
                    }
            },
        }
end
add_editor(editor_appearance)

-------------------------------main window----------------
unit_editor = defclass(unit_editor, gui.FramedScreen)
unit_editor.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "GameMaster's unit editor",
    target_unit = DEFAULT_NIL,
    }


function unit_editor:init(args)

    self:addviews{
    widgets.FilteredList{
        choices=editors,
        on_submit=function (idx,choice)
            if choice.on_submit then
                choice.on_submit(self.target_unit)
            end
        end
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    }
            },
        }
end


unit_editor{target_unit=target}:show()
The pieces should be there, I had to comment out the color section while I was testing the rest but I left it in place, there's just a few more bits missing regarding getting it to display the values properly but you're clever so I will be completely unsurprised if you pop up and point out two or three lines that needed to be added to get it all working right.

Still, I got it to stop spitting errors and do this for me:
(http://i.imgur.com/xlirPea.png)
Title: Re: [DFhack 43.05r1] Atkana's Scripts (Adventure as anything, Change appearance)
Post by: Atkana on July 03, 2017, 10:28:28 am
-snip-
It took a while but I managed to get something that works, the features that reported the natural ranges for body parts and the ability to change an adventurer during creation were lost in the process but they both weren't really important. Feel free to github magic it if you want to submit the edited version, since I have no idea how to github - though it'll probably need vetting by people who a) know programming and b) have a knowledge of how the GUI API works beyond whatever they could work out from seeing how it's used in gm-unit :P Body modifications still have the problem of not actually updating size (which I'm still assuming is an actual mechanic) - a dwarf changed to be 100,000% the height of a dwarf is still technically the same volume as they were before (I think).

Code: (gm-newnit.lua) [Select]
-- Interface powered, user friendly, unit editor

--[====[

gui/gm-unit
===========
An editor for various unit attributes.

]====]
local gui = require 'gui'
local dialog = require 'gui.dialogs'
local widgets =require 'gui.widgets'
local guiScript = require 'gui.script'
local utils = require 'utils'
local args={...}


local target
--TODO: add more ways to guess what unit you want to edit
if args[1]~= nil then
    target=df.units.find(args[1])
else
    target=dfhack.gui.getSelectedUnit(true)
end

if target==nil then
    qerror("No unit to edit") --TODO: better error message
end
local editors={}
function add_editor(editor_class)
    table.insert(editors,{text=editor_class.ATTRS.frame_title,on_submit=function ( unit )
        editor_class{target_unit=unit}:show()
    end})
end
-------------------------------various subeditors---------
--TODO set local sould or better yet skills vector to reduce long skill list access typing
editor_skills=defclass(editor_skills,gui.FramedScreen)
editor_skills.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Skill editor",
    target_unit = DEFAULT_NIL,
    learned_only= false,
}
function list_skills(unit,learned_only)
    local s_=df.job_skill
    local u_skills=unit.status.current_soul.skills
    local ret={}
    for i,v in ipairs(s_) do
        if i>=0 then
            local u_skill=utils.binsearch(u_skills,i,"id")
            if u_skill or not learned_only then
                if not u_skill then
                    u_skill={rating=-1,experience=0}
                end

                local rating
                if u_skill.rating >=0 then
                    rating=df.skill_rating.attrs[u_skill.rating]
                else
                    rating={caption="<unlearned>",xp_threshold=0}
                end

                local text=string.format("%s: %s %d %d/%d",df.job_skill.attrs[i].caption,rating.caption,u_skill.rating,u_skill.experience,rating.xp_threshold)
                table.insert(ret,{text=text,id=i})
            end
        end
    end
    return ret
end
function editor_skills:update_list(no_save_place)
    local skill_list=list_skills(self.target_unit,self.learned_only)
    if no_save_place then
        self.subviews.skills:setChoices(skill_list)
    else
        self.subviews.skills:setChoices(skill_list,self.subviews.skills:getSelected())
    end
end
function editor_skills:init( args )
    if self.target_unit.status.current_soul==nil then
        qerror("Unit does not have soul, can't edit skills")
    end

    local skill_list=list_skills(self.target_unit,self.learned_only)

    self:addviews{
    widgets.FilteredList{
        choices=skill_list,
        frame = {t=0, b=1,l=1},
        view_id="skills",
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": remove level ",
                    key = "SECONDSCROLL_UP",
                    on_activate=self:callback("level_skill",-1)},
                    {text=": add level ",
                    key = "SECONDSCROLL_DOWN",
                    on_activate=self:callback("level_skill",1)}
                    ,
                    {text=": show learned only ",
                    key = "CHANGETAB",
                    on_activate=function ()
                        self.learned_only=not self.learned_only
                        self:update_list(true)
                    end}
                    }
            },
        }
end
function editor_skills:get_cur_skill()
    local list_wid=self.subviews.skills
    local _,choice=list_wid:getSelected()
    if choice==nil then
        qerror("Nothing selected")
    end
    local u_skill=utils.binsearch(self.target_unit.status.current_soul.skills,choice.id,"id")
    return choice,u_skill
end
function editor_skills:level_skill(lvl)
    local sk_en,sk=self:get_cur_skill()
    if lvl >0 then
        local rating

        if sk then
            rating=sk.rating+lvl
        else
            rating=lvl-1
        end

        utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=rating}, 'id') --TODO set exp?
    elseif sk and sk.rating==0 and lvl<0 then
        utils.erase_sorted_key(self.target_unit.status.current_soul.skills,sk_en.id,"id")
    elseif sk and lvl<0 then
        utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=sk.rating+lvl}, 'id') --TODO set exp?
    end
    self:update_list()
end
function editor_skills:remove_rust(skill)
    --TODO
end
add_editor(editor_skills)
------- civ editor
RaceBox = defclass(RaceBox, dialog.ListBox)
RaceBox.focus_path = 'RaceBox'

RaceBox.ATTRS{
    format_name="$NAME ($TOKEN)",
    with_filter=true,
    allow_none=false,
}
function RaceBox:format_creature(creature_raw)
    local t = {NAME=creature_raw.name[0],TOKEN=creature_raw.creature_id}
    return string.gsub(self.format_name, "%$(%w+)", t)
end
function RaceBox:preinit(info)
    self.format_name=RaceBox.ATTRS.format_name or info.format_name -- preinit does not have ATTRS set yet
    local choices={}
    if RaceBox.ATTRS.allow_none or info.allow_none then
        table.insert(choices,{text="<none>",num=-1})
    end
    for i,v in ipairs(df.global.world.raws.creatures.all) do
        local text=self:format_creature(v)
        table.insert(choices,{text=text,raw=v,num=i,search_key=text:lower()})
    end
    info.choices=choices
end
function showRacePrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_none)
    RaceBox{
        frame_title = title,
        text = text,
        text_pen = tcolor,
        on_select = on_select,
        on_cancel = on_cancel,
        frame_width = min_width,
        allow_none = allow_none,
    }:show()
end
CivBox = defclass(CivBox,dialog.ListBox)
CivBox.focus_path = "CivBox"

CivBox.ATTRS={
    format_name="$NAME ($ENGLISH):$ID",
    format_no_name="<unnamed>:$ID",
    name_other="<other(-1)>",
    with_filter=true,
    allow_other=false,
}

function civ_name(id,format_name,format_no_name,name_other,name_invalid)
    if id==-1 then
        return name_other or "<other (-1)>"
    end
    local civ
    if type(id)=='userdata' then
        civ=id
    else
        civ=df.historical_entity.find(id)
        if civ==nil then
            return name_invalid or "<invalid>"
        end
    end
    local t={NAME=dfhack.TranslateName(civ.name),ENGLISH=dfhack.TranslateName(civ.name,true),ID=civ.id} --TODO race?, maybe something from raws?
    if t.NAME=="" then
        return string.gsub(format_no_name or "<unnamed>:$ID", "%$(%w+)", t)
    end
    return string.gsub(format_name or "$NAME ($ENGLISH):$ID", "%$(%w+)", t)
end
function CivBox:update_choices()
    local choices={}
    if self.allow_other then
        table.insert(choices,{text=self.name_other,num=-1})
    end

    for i,v in ipairs(df.global.world.entities.all) do
        if not self.race_filter or (v.race==self.race_filter) then --TODO filter type
            local text=civ_name(v,self.format_name,self.format_no_name,self.name_other,self.name_invalid)
            table.insert(choices,{text=text,raw=v,num=i})
        end
    end
    self.choices=choices
    if self.subviews.list then
        self.subviews.list:setChoices(self.choices)
    end
end
function CivBox:update_race_filter(id)
    local raw=df.creature_raw.find(id)
    if raw then
        self.subviews.race_label:setText(": "..raw.name[0])
        self.race_filter=id
    else
        self.subviews.race_label:setText(": <none>")
        self.race_filter=nil
    end

    self:update_choices()
end
function CivBox:choose_race()
    showRacePrompt("Choose race","Select new race:",nil,function (id,choice)
        self:update_race_filter(choice.num)
    end,nil,nil,true)
end
function CivBox:init(info)
    self.subviews.list.frame={t=3,r=0,l=0}
    self:addviews{
        widgets.Label{frame={t=1,l=0},text={
        {text="Filter race ",key="CUSTOM_CTRL_A",key_sep="()",on_activate=self:callback("choose_race")},
        }},
        widgets.Label{frame={t=1,l=21},view_id="race_label",
        text=": <none>",
        }
    }
    self:update_choices()
end
function showCivPrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_other)
    CivBox{
        frame_title = title,
        text = text,
        text_pen = tcolor,
        on_select = on_select,
        on_cancel = on_cancel,
        frame_width = min_width,
        allow_other = allow_other,
    }:show()
end

editor_civ=defclass(editor_civ,gui.FramedScreen)
editor_civ.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Civilization editor",
    target_unit = DEFAULT_NIL,
    }

function editor_civ:update_curren_civ()
    self.subviews.civ_name:setText("Currently: "..civ_name(self.target_unit.civ_id))
end
function editor_civ:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

    self:addviews{
    widgets.Label{view_id="civ_name",frame = { t=1,l=1}, text="Currently: "..civ_name(self.target_unit.civ_id)},
    widgets.Label{frame = { t=2,l=1}, text={{text=": set to other (-1, usually enemy)",key="CUSTOM_N",
        on_activate= function() self.target_unit.civ_id=-1;self:update_curren_civ() end}}},
    widgets.Label{frame = { t=3,l=1}, text={{text=": set to current civ("..df.global.ui.civ_id..")",key="CUSTOM_C",
        on_activate= function() self.target_unit.civ_id=df.global.ui.civ_id;self:update_curren_civ() end}}},
    widgets.Label{frame = { t=4,l=1}, text={{text=": manually enter",key="CUSTOM_E",
        on_activate=function ()
         dialog.showInputPrompt("Civ id","Enter new civ id:",COLOR_WHITE,
            tostring(self.target_unit.civ_id),function(new_value)
                self.target_unit.civ_id=new_value
                self:update_curren_civ()
            end)
        end}}
        },
    widgets.Label{frame= {t=5,l=1}, text={{text=": select from list",key="CUSTOM_L",
        on_activate=function (  )
            showCivPrompt("Choose civilization", "Select units civilization",nil,function ( id,choice )
                self.target_unit.civ_id=choice.num
                self:update_curren_civ()
            end,nil,nil,true)
        end
        }}},
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    }
            },
        }
end
add_editor(editor_civ)
------- counters editor
editor_counters=defclass(editor_counters,gui.FramedScreen)
editor_counters.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Counters editor",
    target_unit = DEFAULT_NIL,
    counters1={
    "think_counter",
    "job_counter",
    "swap_counter",
    "winded",
    "stunned",
    "unconscious",
    "suffocation",
    "webbed",
    "soldier_mood_countdown",
    "soldier_mood", --todo enum,
    "pain",
    "nausea",
    "dizziness",
    },
    counters2={
    "paralysis",
    "numbness",
    "fever",
    "exhaustion",
    "hunger_timer",
    "thirst_timer",
    "sleepiness_timer",
    "stomach_content",
    "stomach_food",
    "vomit_timeout",
    "stored_fat" --TODO what to reset to?
    }
}
function editor_counters:fill_counters()
    local ret={}
    local u=self.target_unit
    for i,v in ipairs(self.counters1) do
        table.insert(ret,{f=u.counters:_field(v),name=v})
    end
    for i,v in ipairs(self.counters2) do
        table.insert(ret,{f=u.counters2:_field(v),name=v})
    end
    return ret
end
function editor_counters:update_counters()
    for i,v in ipairs(self.counter_list) do
        v.text=string.format("%s:%d",v.name,v.f.value)
    end
    self.subviews.counters:setChoices(self.counter_list)
end
function editor_counters:set_cur_counter(value,index,choice)
    choice.f.value=value
    self:update_counters()
end
function editor_counters:choose_cur_counter(index,choice)
    dialog.showInputPrompt(choice.name,"Enter new value:",COLOR_WHITE,
            tostring(choice.f.value),function(new_value)
                self:set_cur_counter(new_value,index,choice)
            end)
end
function editor_counters:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

    self.counter_list=self:fill_counters()


    self:addviews{
    widgets.FilteredList{
        choices=self.counter_list,
        frame = {t=0, b=1,l=1},
        view_id="counters",
        on_submit=self:callback("choose_cur_counter"),
        on_submit2=self:callback("set_cur_counter",0),--TODO some things need to be set to different defaults
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": reset counter ",
                    key = "SEC_SELECT",
                    },
                    {text=": set counter ",
                    key = "SELECT",
                    }
                   
                    }
            },
        }
    self:update_counters()
end
add_editor(editor_counters)

wound_creator=defclass(wound_creator,gui.FramedScreen)
wound_creator.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Wound creator",
    target_wound = DEFAULT_NIL,
    --filter
}
function wound_creator:init( args )
    if self.target_wound==nil then
        qerror("invalid wound")
    end
   

    self:addviews{
    widgets.List{
       
        frame = {t=0, b=1,l=1},
        view_id="fields",
        on_submit=self:callback("edit_cur_wound"),
        on_submit2=self:callback("delete_current_wound")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")},

                    {text=": edit wound ",
                    key = "SELECT"},

                    {text=": delete wound ",
                    key = "SEC_SELECT"},
                    {text=": create wound ",
                    key = "CUSTOM_CTRL_I",
                    on_activate= self:callback("create_new_wound")},

                    }
            },
        }
    self:update_wounds()
end
-------------------
editor_wounds=defclass(editor_wounds,gui.FramedScreen)
editor_wounds.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Wound editor",
    target_unit = DEFAULT_NIL,
    --filter
}
function is_scar( wound_part )
    return wound_part.flags1.scar_cut or wound_part.flags1.scar_smashed or
        wound_part.flags1.scar_edged_shake1 or wound_part.flags1.scar_blunt_shake1
end
function format_flag_name( fname )
    return fname:sub(1,1):upper()..fname:sub(2):gsub("_"," ")
end
function name_from_flags( wp )
    for i,v in ipairs(wp.flags1) do
        if v then
            return format_flag_name(df.wound_damage_flags1[i])
        end
    end
    for i,v in ipairs(wp.flags2) do
        if v then
            return format_flag_name(df.wound_damage_flags2[i])
        end
    end
    return "<unnamed wound>"
end
function format_wound( list_id,wound, unit)

    local name="<unnamed wound>"
    if #wound.parts>0 and #wound.parts[0].effect_type>0 then --try to make wound name by effect...
        name=tostring(df.wound_effect_type[wound.parts[0].effect_type[0]])
        if #wound.parts>1 then --cheap and probably incorrect...
            name=name.."s"
        end
    elseif #wound.parts>0 and is_scar(wound.parts[0]) then
        name="Scar"
    elseif #wound.parts>0 then
        local wp=wound.parts[0]
        name=name_from_flags(wp)
    end

    return string.format("%d. %s id=%d",list_id,name,wound.id)
end
function editor_wounds:update_wounds()
    local ret={}
    for i,v in ipairs(self.trg_wounds) do
        table.insert(ret,{text=format_wound(i,v,self.target_unit),wound=v})
    end
    self.subviews.wounds:setChoices(ret)
    self.wound_list=ret
end
function editor_wounds:dirty_unit()
    print("todo: implement unit status recalculation")
end
function editor_wounds:get_cur_wound()
    local list_wid=self.subviews.wounds
    local _,choice=list_wid:getSelected()
    if choice==nil then
        qerror("Nothing selected")
    end
    local ret_wound=utils.binsearch(self.trg_wounds,choice.id,"id")
    return choice,ret_wound
end
function editor_wounds:delete_current_wound(index,choice)
   
    utils.erase_sorted(self.trg_wounds,choice.wound,"id")
    choice.wound:delete()
    self:dirty_unit()
    self:update_wounds()
end
function editor_wounds:create_new_wound()
    print("Creating")
end
function editor_wounds:edit_cur_wound(index,choice)
   
end
function editor_wounds:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end
    self.trg_wounds=self.target_unit.body.wounds

    self:addviews{
    widgets.List{
       
        frame = {t=0, b=1,l=1},
        view_id="wounds",
        on_submit=self:callback("edit_cur_wound"),
        on_submit2=self:callback("delete_current_wound")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")},

                    {text=": edit wound ",
                    key = "SELECT"},

                    {text=": delete wound ",
                    key = "SEC_SELECT"},
                    {text=": create wound ",
                    key = "CUSTOM_CTRL_I",
                    on_activate= self:callback("create_new_wound")},

                    }
            },
        }
    self:update_wounds()
end
add_editor(editor_wounds)

------ Body editor

modifier_selector = defclass(modifier_selector, gui.FramedScreen)

function modifierString(mod)
local out = df.appearance_modifier_type[mod.type]
out = out:lower() --Make lowercase
out = out:gsub("_", " ") --Replace underscores with spaces
out = out:gsub("^%l", string.upper) --capitalises first letter

return out
end

function showModifierScreen(data)
modifier_selector{
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Select a modifier",
    target_unit = DEFAULT_NIL,
data = data
    }:show()
end

function modifier_selector:set_value(value,index,choice)
for i,v in ipairs(choice.changes) do
self.changeType[v] = value
end
self:update_features()
end

function modifier_selector:on_select(index, choice)
dialog.showInputPrompt(modifierString(choice.mod),"Enter new value:",COLOR_WHITE,
            tostring(self.changeType[choice.changes[1]]),function(new_value)
                self:set_value(new_value,index,choice)
            end)
end

function modifier_selector:update_features()
local out = {}
for i, v in ipairs(self.partPicked.modList) do
table.insert(out, {text = (modifierString(v.modifier) .. ": " .. self.changeType[v.changes[1]]), mod = v.modifier, changes = v.changes})
end
self.subviews.modifiers:setChoices(out)
end

function modifier_selector:init( info )
self.partPicked = info.data.choice --The part that was picked in editor_body

if info.data.choice.isPart then
self.changeType = target.appearance.bp_modifiers
else
self.changeType = target.appearance.body_modifiers
end

    self:addviews{
    widgets.FilteredList{
        frame = {t=0, b=1,l=1},
        view_id="modifiers",
on_submit=self:callback("on_select")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": back to part selector ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": select modifier ",
                    key = "SELECT",
                    }
                   
                    }
            },
        }

    self:update_features()
end


editor_body=defclass(editor_body,gui.FramedScreen)
editor_body.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Body modifier editor",
    target_unit = DEFAULT_NIL,
}

function editor_body:bp_links()
local out = {}
local uc = self.ucaste
for i,v in ipairs(uc.bp_appearance.part_idx) do
out[i] = {["modId"] = uc.bp_appearance.modifier_idx[i], ["partId"] = uc.bp_appearance.part_idx[i]}
end

return out
end

--Following is a relic from my original change-appearance script. There's probably a more efficient way of doing this, but I'm not in the mood to be redesigning ;P
function editor_body:make_bplist()
local ret = {}
local bpm = self.ucaste.bp_appearance.modifiers
local links = self:bp_links()
local point = {}

for i, v in ipairs(bpm) do
local mod = v

local bpmname
if #mod.noun > 0 then
bpmname = mod.noun
else
bpmname = self.ucaste.body_info.body_parts[mod.body_parts[0]].name_singular[0].value
end

local changes = {}
for i2, v2 in ipairs(mod.body_parts) do
local partId = v2
for i3, v3 in ipairs(links) do
if v3.modId == i and v3.partId == partId then
table.insert(changes, i3) --?
end
end
end

if point[bpmname] then
table.insert(ret[point[bpmname]].modList, {["modifier"] = mod, ["changes"] = changes})
else
table.insert(ret, {["name"] = bpmname, ["modList"] = {[1] = {["modifier"] = mod, ["changes"] = changes}}})
point[bpmname] = #ret --Stores the index of the name for future additions
end
end
return ret
end

function editor_body:make_bodmodlist() --Version of make_bplist() to make a spoof version for body so it can be treated the same. Only makes modList
local ret = {}
local bm = self.ucaste.body_appearance_modifiers

for i, v in ipairs(bm) do
table.insert(ret, {["modifier"] = v, ["changes"] = {[1] = i}})
end

return ret
end

function editor_body:update_features()
self.bplist = self:make_bplist()
local out = {}
local uc = self.ucaste
self.bodmodlist = self:make_bodmodlist()
--Special case of body
--First check to discover if there are any body mods
if #uc.body_appearance_modifiers > 0 then
table.insert(out, {text = "Body", modList = self.bodmodlist})
end

for i,v in ipairs(self.bplist) do
table.insert(out, {text = v.name:gsub("^%l", string.upper), modList = v.modList, isPart = true})
end

self.subviews.body:setChoices(out)
end

function editor_body:choose_cur_bp(index, choice)
local data = {["choice"] = choice}

showModifierScreen(data)
end


function editor_body:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

self.urace = self.target_unit.race
self.ucritter = df.creature_raw.find(self.urace)
self.ucaste = self.ucritter.caste[self.target_unit.caste]

    self:addviews{
    widgets.FilteredList{
        choices=self.features_list,
        frame = {t=0, b=1,l=1},
        view_id="body",
on_submit=self:callback("choose_cur_bp")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": select feature ",
                    key = "SELECT",
                    }
                   
                    }
            },
        }

    self:update_features()
end
add_editor(editor_body)

------ Colors editor
ColorBox = defclass(ColorBox, dialog.ListBox)
ColorBox.focus_path = 'ColorBox'

ColorBox.ATTRS{
with_filter = true,
allow_none = false,
}

function showColorPrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_other, data)
    ColorBox{
        frame_title = title,
        text = text,
        text_pen = tcolor,
        on_select = on_select,
        on_cancel = on_cancel,
        frame_width = min_width,
        allow_other = allow_other,
data = data
    }:show()
end

function ColorBox:update_choices()
local choices = {}
for i,v in ipairs(self.mod.pattern_index) do
table.insert(choices, {text=patternString(self.mod.pattern_index[i]), index = i})
end

self.choices = choices
if self.subviews.list then
        self.subviews.list:setChoices(self.choices)
    end
end

function ColorBox:init(info)
self.mod = info.data.mod

self.target_unit = target
self:update_choices()
end

editor_colors=defclass(editor_colors,gui.FramedScreen)
editor_colors.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Colors editor",
    target_unit = DEFAULT_NIL,
}

function getColor(id)
return df.descriptor_color.find(id)
end

function getPattern(id)
return df.descriptor_pattern.find(id)
end

function patternString(id)
local pattern = getPattern(id)
local prefix
if pattern.pattern == 0 then --Monochrome
return getColor(pattern.colors[0]).name
elseif pattern.pattern == 1 then --Stripes
prefix = "striped"
elseif pattern.pattern == 2 then --Iris_eye
return getColor(pattern.colors[2]).name .. " eyes"
elseif pattern.pattern == 3 then --Spots
prefix = "spotted" --that's a guess
elseif pattern.pattern == 4 then --Pupil_eye
return getColor(pattern.colors[2]).name .. " eyes"
elseif pattern.pattern == 5 then --mottled
prefix = "mottled"
end
local out = prefix .. " "
for i=0, #pattern.colors-1 do
if i == #pattern.colors-1 then
out = out .. "and " .. getColor(pattern.colors[i]).name
elseif i == #pattern.colors-2 then
out = out .. getColor(pattern.colors[i]).name .. " "
else
out = out .. getColor(pattern.colors[i]).name .. ", "
end
end
return out
end

function editor_colors:change_color(index,patternId)
self.target_unit.appearance.colors[index] = patternId
end

function editor_colors:update_features()
local uc = self.ucaste
local out = {}
for i,v in ipairs(uc.color_modifiers) do
table.insert(out, {text=uc.color_modifiers[i].part:gsub("^%l", string.upper), mod = uc.color_modifiers[i], index = i})
end
self.subviews.colors:setChoices(out)
end

function editor_colors:choose_cur_feature(index,choice)
self.chosenFeature = choice
local data = {ucaste = self.ucaste, mod = choice.mod} --data to pass to color prompt
showColorPrompt("Choose color", "Select features color",nil,function ( id,choice )
self:change_color(self.chosenFeature.index, choice.index)
            end,nil,nil,true, data)
end


function editor_colors:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

self.urace = self.target_unit.race
self.ucritter = df.creature_raw.find(self.urace)
self.ucaste = self.ucritter.caste[self.target_unit.caste]

    self:addviews{
    widgets.FilteredList{
        choices=self.features_list,
        frame = {t=0, b=1,l=1},
        view_id="colors",
on_submit=self:callback("choose_cur_feature")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": select feature ",
                    key = "SELECT",
                    }
                   
                    }
            },
        }

    self:update_features()
end
add_editor(editor_colors)

-------------------------------main window----------------
unit_editor = defclass(unit_editor, gui.FramedScreen)
unit_editor.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "GameMaster's unit editor",
    target_unit = DEFAULT_NIL,
    }


function unit_editor:init(args)

    self:addviews{
    widgets.FilteredList{
        choices=editors,
        on_submit=function (idx,choice)
            if choice.on_submit then
                choice.on_submit(self.target_unit)
            end
        end
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    }
            },
        }
end


unit_editor{target_unit=target}:show()

Now I've remembered the whole reason I went on this modding tangent was because I wanted to make a personality + values editor, I guess now I understand GUIs a bit that'll be my next project, probably as another addition to gm-unit. In retrospect it would've been neat to have the option to press +/- minus to jump forwards/back along description thresholds so I guess I'll see about adding that in when I add the personality + values stuff. Not sure when I'll get around to it though - I'm gonna take a short break from modding to actually play Dwarf Fortress :P
Title: Re: [DFhack 43.05r1] Atkana's Scripts (Adventure as anything, Change appearance)
Post by: Max™ on July 04, 2017, 02:28:45 am
Oh, I can try to figure out the +/- thing for you if you got it working, fucking awesome, and the brainwash script works on personality/values as I recall?

Oh my god you are awesome, that is way better than I was trying to get it to work, I'll poke at it in a little bit and see about the keybinds since I happen to know how to do that and then if you don't have a github yourself I'll put in my scripts entry with a pull request and note that you did it.

Edit: you are frustratingly better at slapping these parts together than I am. Been trying to get the +/- input function to work but it's not grabbing the right values so I took a break from that and was trying to get the styles included but I've only had a couple of weeks of doing ANY sort of fucking around with strings.

These are the bits I was trying to add in:
Code: [Select]
[snipped lines]
    {text=": raise ",
                    key = "SECONDSCROLL_UP",
                    on_activate=self:callback("change_value",rav)},
    {text=": reduce ",
                    key = "SECONDSCROLL_DOWN",
                    on_activate=self:callback("change_value",rev)},
[snipped lines]

function modifier_selector:change_value(index,rav,rev)
    local list_wid=self.subviews.modifiers
    local _,choice=list_wid:getSelected()
    local value=self.changeType
    if rav then
choice[index] = choice[index]+1
value[index] = value[index]+1
    elseif rev then
choice[index] = choice[index]-1
value[index] = value[index]-1
    end
    self:set_value(value,index,choice)
end
I tried screwing around a few ways but I have a headache and lost track of what I was trying to do at various points so I just left it where it wasn't erroring for the moment.

The styles seem like a natural fit for the color menu trick you did:
Code: [Select]
function editor_body:make_stylelist()
local ret = {}
local sl = self.ucaste.tissue_styles
for i,v in ipairs(sl) do
table.insert(ret, {text = i.styles:gsub("_"," ","^%l", string.upper)})--bleh what am I even doing
end
return ret
end

function editor_body:update_features()
[snipped stuff up to bodmodlist...]
self.styleList = self:make_stylelist()
for i,v in ipairs(uc.tissue_styles) do
if uc.tissue_styles[i].part_idx[v]==uc.bp_appearance.part_idx[v] then
table.insert(out, {text = i.token:gsub("^%l", string.upper), modList = self.styleList})
end
end
I go through periods of playing, modding, and sometimes everything clicks and I hack out scripts until I get stumped, I don't question it because it's fun whether I'm hacking at lua or goblin guts, yanno?
Spoiler (click to show/hide)
Title: Re: [DFhack 43.05r1] Atkana's Scripts (Adventure as anything, Change appearance)
Post by: Atkana on July 07, 2017, 01:37:03 pm
-snip-
Code: (gm-newnit.lua) [Select]
-- Interface powered, user friendly, unit editor

--[====[

gui/gm-unit
===========
An editor for various unit attributes.

]====]
local gui = require 'gui'
local dialog = require 'gui.dialogs'
local widgets =require 'gui.widgets'
local guiScript = require 'gui.script'
local utils = require 'utils'
local args={...}


local target
--TODO: add more ways to guess what unit you want to edit
if args[1]~= nil then
    target=df.units.find(args[1])
else
    target=dfhack.gui.getSelectedUnit(true)
end

if target==nil then
    qerror("No unit to edit") --TODO: better error message
end
local editors={}
function add_editor(editor_class)
    table.insert(editors,{text=editor_class.ATTRS.frame_title,on_submit=function ( unit )
        editor_class{target_unit=unit}:show()
    end})
end
-------------------------------various subeditors---------
--TODO set local sould or better yet skills vector to reduce long skill list access typing
editor_skills=defclass(editor_skills,gui.FramedScreen)
editor_skills.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Skill editor",
    target_unit = DEFAULT_NIL,
    learned_only= false,
}
function list_skills(unit,learned_only)
    local s_=df.job_skill
    local u_skills=unit.status.current_soul.skills
    local ret={}
    for i,v in ipairs(s_) do
        if i>=0 then
            local u_skill=utils.binsearch(u_skills,i,"id")
            if u_skill or not learned_only then
                if not u_skill then
                    u_skill={rating=-1,experience=0}
                end

                local rating
                if u_skill.rating >=0 then
                    rating=df.skill_rating.attrs[u_skill.rating]
                else
                    rating={caption="<unlearned>",xp_threshold=0}
                end

                local text=string.format("%s: %s %d %d/%d",df.job_skill.attrs[i].caption,rating.caption,u_skill.rating,u_skill.experience,rating.xp_threshold)
                table.insert(ret,{text=text,id=i})
            end
        end
    end
    return ret
end
function editor_skills:update_list(no_save_place)
    local skill_list=list_skills(self.target_unit,self.learned_only)
    if no_save_place then
        self.subviews.skills:setChoices(skill_list)
    else
        self.subviews.skills:setChoices(skill_list,self.subviews.skills:getSelected())
    end
end
function editor_skills:init( args )
    if self.target_unit.status.current_soul==nil then
        qerror("Unit does not have soul, can't edit skills")
    end

    local skill_list=list_skills(self.target_unit,self.learned_only)

    self:addviews{
    widgets.FilteredList{
        choices=skill_list,
        frame = {t=0, b=1,l=1},
        view_id="skills",
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": remove level ",
                    key = "SECONDSCROLL_UP",
                    on_activate=self:callback("level_skill",-1)},
                    {text=": add level ",
                    key = "SECONDSCROLL_DOWN",
                    on_activate=self:callback("level_skill",1)}
                    ,
                    {text=": show learned only ",
                    key = "CHANGETAB",
                    on_activate=function ()
                        self.learned_only=not self.learned_only
                        self:update_list(true)
                    end}
                    }
            },
        }
end
function editor_skills:get_cur_skill()
    local list_wid=self.subviews.skills
    local _,choice=list_wid:getSelected()
    if choice==nil then
        qerror("Nothing selected")
    end
    local u_skill=utils.binsearch(self.target_unit.status.current_soul.skills,choice.id,"id")
    return choice,u_skill
end
function editor_skills:level_skill(lvl)
    local sk_en,sk=self:get_cur_skill()
    if lvl >0 then
        local rating

        if sk then
            rating=sk.rating+lvl
        else
            rating=lvl-1
        end

        utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=rating}, 'id') --TODO set exp?
    elseif sk and sk.rating==0 and lvl<0 then
        utils.erase_sorted_key(self.target_unit.status.current_soul.skills,sk_en.id,"id")
    elseif sk and lvl<0 then
        utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=sk.rating+lvl}, 'id') --TODO set exp?
    end
    self:update_list()
end
function editor_skills:remove_rust(skill)
    --TODO
end
add_editor(editor_skills)
------- civ editor
RaceBox = defclass(RaceBox, dialog.ListBox)
RaceBox.focus_path = 'RaceBox'

RaceBox.ATTRS{
    format_name="$NAME ($TOKEN)",
    with_filter=true,
    allow_none=false,
}
function RaceBox:format_creature(creature_raw)
    local t = {NAME=creature_raw.name[0],TOKEN=creature_raw.creature_id}
    return string.gsub(self.format_name, "%$(%w+)", t)
end
function RaceBox:preinit(info)
    self.format_name=RaceBox.ATTRS.format_name or info.format_name -- preinit does not have ATTRS set yet
    local choices={}
    if RaceBox.ATTRS.allow_none or info.allow_none then
        table.insert(choices,{text="<none>",num=-1})
    end
    for i,v in ipairs(df.global.world.raws.creatures.all) do
        local text=self:format_creature(v)
        table.insert(choices,{text=text,raw=v,num=i,search_key=text:lower()})
    end
    info.choices=choices
end
function showRacePrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_none)
    RaceBox{
        frame_title = title,
        text = text,
        text_pen = tcolor,
        on_select = on_select,
        on_cancel = on_cancel,
        frame_width = min_width,
        allow_none = allow_none,
    }:show()
end
CivBox = defclass(CivBox,dialog.ListBox)
CivBox.focus_path = "CivBox"

CivBox.ATTRS={
    format_name="$NAME ($ENGLISH):$ID",
    format_no_name="<unnamed>:$ID",
    name_other="<other(-1)>",
    with_filter=true,
    allow_other=false,
}

function civ_name(id,format_name,format_no_name,name_other,name_invalid)
    if id==-1 then
        return name_other or "<other (-1)>"
    end
    local civ
    if type(id)=='userdata' then
        civ=id
    else
        civ=df.historical_entity.find(id)
        if civ==nil then
            return name_invalid or "<invalid>"
        end
    end
    local t={NAME=dfhack.TranslateName(civ.name),ENGLISH=dfhack.TranslateName(civ.name,true),ID=civ.id} --TODO race?, maybe something from raws?
    if t.NAME=="" then
        return string.gsub(format_no_name or "<unnamed>:$ID", "%$(%w+)", t)
    end
    return string.gsub(format_name or "$NAME ($ENGLISH):$ID", "%$(%w+)", t)
end
function CivBox:update_choices()
    local choices={}
    if self.allow_other then
        table.insert(choices,{text=self.name_other,num=-1})
    end

    for i,v in ipairs(df.global.world.entities.all) do
        if not self.race_filter or (v.race==self.race_filter) then --TODO filter type
            local text=civ_name(v,self.format_name,self.format_no_name,self.name_other,self.name_invalid)
            table.insert(choices,{text=text,raw=v,num=i})
        end
    end
    self.choices=choices
    if self.subviews.list then
        self.subviews.list:setChoices(self.choices)
    end
end
function CivBox:update_race_filter(id)
    local raw=df.creature_raw.find(id)
    if raw then
        self.subviews.race_label:setText(": "..raw.name[0])
        self.race_filter=id
    else
        self.subviews.race_label:setText(": <none>")
        self.race_filter=nil
    end

    self:update_choices()
end
function CivBox:choose_race()
    showRacePrompt("Choose race","Select new race:",nil,function (id,choice)
        self:update_race_filter(choice.num)
    end,nil,nil,true)
end
function CivBox:init(info)
    self.subviews.list.frame={t=3,r=0,l=0}
    self:addviews{
        widgets.Label{frame={t=1,l=0},text={
        {text="Filter race ",key="CUSTOM_CTRL_A",key_sep="()",on_activate=self:callback("choose_race")},
        }},
        widgets.Label{frame={t=1,l=21},view_id="race_label",
        text=": <none>",
        }
    }
    self:update_choices()
end
function showCivPrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_other)
    CivBox{
        frame_title = title,
        text = text,
        text_pen = tcolor,
        on_select = on_select,
        on_cancel = on_cancel,
        frame_width = min_width,
        allow_other = allow_other,
    }:show()
end

editor_civ=defclass(editor_civ,gui.FramedScreen)
editor_civ.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Civilization editor",
    target_unit = DEFAULT_NIL,
    }

function editor_civ:update_curren_civ()
    self.subviews.civ_name:setText("Currently: "..civ_name(self.target_unit.civ_id))
end
function editor_civ:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

    self:addviews{
    widgets.Label{view_id="civ_name",frame = { t=1,l=1}, text="Currently: "..civ_name(self.target_unit.civ_id)},
    widgets.Label{frame = { t=2,l=1}, text={{text=": set to other (-1, usually enemy)",key="CUSTOM_N",
        on_activate= function() self.target_unit.civ_id=-1;self:update_curren_civ() end}}},
    widgets.Label{frame = { t=3,l=1}, text={{text=": set to current civ("..df.global.ui.civ_id..")",key="CUSTOM_C",
        on_activate= function() self.target_unit.civ_id=df.global.ui.civ_id;self:update_curren_civ() end}}},
    widgets.Label{frame = { t=4,l=1}, text={{text=": manually enter",key="CUSTOM_E",
        on_activate=function ()
         dialog.showInputPrompt("Civ id","Enter new civ id:",COLOR_WHITE,
            tostring(self.target_unit.civ_id),function(new_value)
                self.target_unit.civ_id=new_value
                self:update_curren_civ()
            end)
        end}}
        },
    widgets.Label{frame= {t=5,l=1}, text={{text=": select from list",key="CUSTOM_L",
        on_activate=function (  )
            showCivPrompt("Choose civilization", "Select units civilization",nil,function ( id,choice )
                self.target_unit.civ_id=choice.num
                self:update_curren_civ()
            end,nil,nil,true)
        end
        }}},
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    }
            },
        }
end
add_editor(editor_civ)
------- counters editor
editor_counters=defclass(editor_counters,gui.FramedScreen)
editor_counters.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Counters editor",
    target_unit = DEFAULT_NIL,
    counters1={
    "think_counter",
    "job_counter",
    "swap_counter",
    "winded",
    "stunned",
    "unconscious",
    "suffocation",
    "webbed",
    "soldier_mood_countdown",
    "soldier_mood", --todo enum,
    "pain",
    "nausea",
    "dizziness",
    },
    counters2={
    "paralysis",
    "numbness",
    "fever",
    "exhaustion",
    "hunger_timer",
    "thirst_timer",
    "sleepiness_timer",
    "stomach_content",
    "stomach_food",
    "vomit_timeout",
    "stored_fat" --TODO what to reset to?
    }
}
function editor_counters:fill_counters()
    local ret={}
    local u=self.target_unit
    for i,v in ipairs(self.counters1) do
        table.insert(ret,{f=u.counters:_field(v),name=v})
    end
    for i,v in ipairs(self.counters2) do
        table.insert(ret,{f=u.counters2:_field(v),name=v})
    end
    return ret
end
function editor_counters:update_counters()
    for i,v in ipairs(self.counter_list) do
        v.text=string.format("%s:%d",v.name,v.f.value)
    end
    self.subviews.counters:setChoices(self.counter_list)
end
function editor_counters:set_cur_counter(value,index,choice)
    choice.f.value=value
    self:update_counters()
end
function editor_counters:choose_cur_counter(index,choice)
    dialog.showInputPrompt(choice.name,"Enter new value:",COLOR_WHITE,
            tostring(choice.f.value),function(new_value)
                self:set_cur_counter(new_value,index,choice)
            end)
end
function editor_counters:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

    self.counter_list=self:fill_counters()


    self:addviews{
    widgets.FilteredList{
        choices=self.counter_list,
        frame = {t=0, b=1,l=1},
        view_id="counters",
        on_submit=self:callback("choose_cur_counter"),
        on_submit2=self:callback("set_cur_counter",0),--TODO some things need to be set to different defaults
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": reset counter ",
                    key = "SEC_SELECT",
                    },
                    {text=": set counter ",
                    key = "SELECT",
                    }
                   
                    }
            },
        }
    self:update_counters()
end
add_editor(editor_counters)

wound_creator=defclass(wound_creator,gui.FramedScreen)
wound_creator.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Wound creator",
    target_wound = DEFAULT_NIL,
    --filter
}
function wound_creator:init( args )
    if self.target_wound==nil then
        qerror("invalid wound")
    end
   

    self:addviews{
    widgets.List{
       
        frame = {t=0, b=1,l=1},
        view_id="fields",
        on_submit=self:callback("edit_cur_wound"),
        on_submit2=self:callback("delete_current_wound")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")},

                    {text=": edit wound ",
                    key = "SELECT"},

                    {text=": delete wound ",
                    key = "SEC_SELECT"},
                    {text=": create wound ",
                    key = "CUSTOM_CTRL_I",
                    on_activate= self:callback("create_new_wound")},

                    }
            },
        }
    self:update_wounds()
end
-------------------
editor_wounds=defclass(editor_wounds,gui.FramedScreen)
editor_wounds.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Wound editor",
    target_unit = DEFAULT_NIL,
    --filter
}
function is_scar( wound_part )
    return wound_part.flags1.scar_cut or wound_part.flags1.scar_smashed or
        wound_part.flags1.scar_edged_shake1 or wound_part.flags1.scar_blunt_shake1
end
function format_flag_name( fname )
    return fname:sub(1,1):upper()..fname:sub(2):gsub("_"," ")
end
function name_from_flags( wp )
    for i,v in ipairs(wp.flags1) do
        if v then
            return format_flag_name(df.wound_damage_flags1[i])
        end
    end
    for i,v in ipairs(wp.flags2) do
        if v then
            return format_flag_name(df.wound_damage_flags2[i])
        end
    end
    return "<unnamed wound>"
end
function format_wound( list_id,wound, unit)

    local name="<unnamed wound>"
    if #wound.parts>0 and #wound.parts[0].effect_type>0 then --try to make wound name by effect...
        name=tostring(df.wound_effect_type[wound.parts[0].effect_type[0]])
        if #wound.parts>1 then --cheap and probably incorrect...
            name=name.."s"
        end
    elseif #wound.parts>0 and is_scar(wound.parts[0]) then
        name="Scar"
    elseif #wound.parts>0 then
        local wp=wound.parts[0]
        name=name_from_flags(wp)
    end

    return string.format("%d. %s id=%d",list_id,name,wound.id)
end
function editor_wounds:update_wounds()
    local ret={}
    for i,v in ipairs(self.trg_wounds) do
        table.insert(ret,{text=format_wound(i,v,self.target_unit),wound=v})
    end
    self.subviews.wounds:setChoices(ret)
    self.wound_list=ret
end
function editor_wounds:dirty_unit()
    print("todo: implement unit status recalculation")
end
function editor_wounds:get_cur_wound()
    local list_wid=self.subviews.wounds
    local _,choice=list_wid:getSelected()
    if choice==nil then
        qerror("Nothing selected")
    end
    local ret_wound=utils.binsearch(self.trg_wounds,choice.id,"id")
    return choice,ret_wound
end
function editor_wounds:delete_current_wound(index,choice)
   
    utils.erase_sorted(self.trg_wounds,choice.wound,"id")
    choice.wound:delete()
    self:dirty_unit()
    self:update_wounds()
end
function editor_wounds:create_new_wound()
    print("Creating")
end
function editor_wounds:edit_cur_wound(index,choice)
   
end
function editor_wounds:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end
    self.trg_wounds=self.target_unit.body.wounds

    self:addviews{
    widgets.List{
       
        frame = {t=0, b=1,l=1},
        view_id="wounds",
        on_submit=self:callback("edit_cur_wound"),
        on_submit2=self:callback("delete_current_wound")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")},

                    {text=": edit wound ",
                    key = "SELECT"},

                    {text=": delete wound ",
                    key = "SEC_SELECT"},
                    {text=": create wound ",
                    key = "CUSTOM_CTRL_I",
                    on_activate= self:callback("create_new_wound")},

                    }
            },
        }
    self:update_wounds()
end
add_editor(editor_wounds)

------ Body editor
modifier_selector = defclass(modifier_selector, gui.FramedScreen)

function modifierString(mod)
local out = df.appearance_modifier_type[mod.type]
out = out:lower() --Make lowercase
out = out:gsub("_", " ") --Replace underscores with spaces
out = out:gsub("^%l", string.upper) --capitalises first letter

return out
end

function showModifierScreen(data)
modifier_selector{
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Select a modifier",
    target_unit = DEFAULT_NIL,
data = data
    }:show()
end

function modifier_selector:set_value(value,index,choice)
for i,v in ipairs(choice.changes) do
self.changeType[v] = value
end
self:update_features()
end

function modifier_selector:on_select(index, choice)
dialog.showInputPrompt(modifierString(choice.mod),"Enter new value:",COLOR_WHITE,
            tostring(self.changeType[choice.changes[1]]),function(new_value)
                self:set_value(new_value,index,choice)
            end)
end

function modifier_selector:update_features()
local out = {}
for i, v in ipairs(self.partPicked.modList) do
table.insert(out, {text = (modifierString(v.modifier) .. ": " .. self.changeType[v.changes[1]]), mod = v.modifier, changes = v.changes})
end
self.subviews.modifiers:setChoices(out)
end

--The following function was written on a day I couldn't brain. There's probably a simpler way to implement this but this way made sense to me at the time - Atkana
function modifier_selector:step_value(dir)
local index, choice = self.subviews.modifiers:getSelected()
if not choice then --It's possible this gets called when there isn't actually anything selected because of how filtered lists work
return
end
local ranges = {} --Records the value at every step of the description range
for i, v in ipairs(choice.mod.desc_range) do
if #ranges == 0 or v > ranges[#ranges] then --Don't bother adding any entries if the same as the previous
table.insert(ranges, v)
end
end

local cur --The index for ranges that the current modifier lies on
local curValue = self.changeType[choice.changes[1]]
for i, v in ipairs(ranges) do
if ranges[i+1] then --If there's a next entry
if curValue < ranges[i+1] then --If the current value is less than the next entry
cur = i
break
end
else --This is the last entry
cur = i
end
end

local newVal --New value the chosen modifier will be set to
if dir > 0 then --positive direction
newVal = ranges[cur+dir] or ranges[#ranges]
else
newVal = ranges[cur+dir] or ranges[1]
end

self:set_value(newVal, index, choice)
end

function modifier_selector:init( info )
self.partPicked = info.data.choice --The part that was picked in editor_body

if info.data.choice.isPart then
self.changeType = target.appearance.bp_modifiers
else
self.changeType = target.appearance.body_modifiers
end

    self:addviews{
widgets.FilteredList{
        frame = {t=0, b=1,l=1},
        view_id="modifiers",
on_submit=self:callback("on_select")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": back to part selector ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": edit modifier ",
                    key = "SELECT",
                    },
{text=": raise ",
                    --key = "SECONDSCROLL_DOWN",
key = "STANDARDSCROLL_RIGHT",
                    on_activate=self:callback("step_value",1)},
{text=": reduce ",
                    --key = "SECONDSCROLL_UP",
key = "STANDARDSCROLL_LEFT",
                    on_activate=self:callback("step_value",-1)},
                    }
            },
        }

    self:update_features()
end


editor_body=defclass(editor_body,gui.FramedScreen)
editor_body.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Body modifier editor",
    target_unit = DEFAULT_NIL,
}

function editor_body:bp_links()
local out = {}
local uc = self.ucaste
for i,v in ipairs(uc.bp_appearance.part_idx) do
out[i] = {["modId"] = uc.bp_appearance.modifier_idx[i], ["partId"] = uc.bp_appearance.part_idx[i]}
end

return out
end

--Following is a relic from my original change-appearance script. There's probably a more efficient way of doing this, but I'm not in the mood to be redesigning ;P
function editor_body:make_bplist()
local ret = {}
local bpm = self.ucaste.bp_appearance.modifiers
local links = self:bp_links()
local point = {}

for i, v in ipairs(bpm) do
local mod = v

local bpmname
if #mod.noun > 0 then
bpmname = mod.noun
else
bpmname = self.ucaste.body_info.body_parts[mod.body_parts[0]].name_singular[0].value
end

local changes = {}
for i2, v2 in ipairs(mod.body_parts) do
local partId = v2
for i3, v3 in ipairs(links) do
if v3.modId == i and v3.partId == partId then
table.insert(changes, i3) --?
end
end
end

if point[bpmname] then
table.insert(ret[point[bpmname]].modList, {["modifier"] = mod, ["changes"] = changes})
else
table.insert(ret, {["name"] = bpmname, ["modList"] = {[1] = {["modifier"] = mod, ["changes"] = changes}}})
point[bpmname] = #ret --Stores the index of the name for future additions
end
end
return ret
end

function editor_body:make_bodmodlist() --Version of make_bplist() to make a spoof version for body so it can be treated the same. Only makes modList
local ret = {}
local bm = self.ucaste.body_appearance_modifiers

for i, v in ipairs(bm) do
table.insert(ret, {["modifier"] = v, ["changes"] = {[1] = i}})
end

return ret
end

function editor_body:update_features()
self.bplist = self:make_bplist()
local out = {}
local uc = self.ucaste
self.bodmodlist = self:make_bodmodlist()

--Special case of body
--First check to discover if there are any body mods
if #uc.body_appearance_modifiers > 0 then
table.insert(out, {text = "Body", modList = self.bodmodlist})
end

for i,v in ipairs(self.bplist) do
table.insert(out, {text = v.name:gsub("^%l", string.upper), modList = v.modList, isPart = true})
end

self.subviews.body:setChoices(out)
end

function editor_body:choose_cur_bp(index, choice)
local data = {["choice"] = choice}

showModifierScreen(data)
end


function editor_body:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

self.urace = self.target_unit.race
self.ucritter = df.creature_raw.find(self.urace)
self.ucaste = self.ucritter.caste[self.target_unit.caste]

    self:addviews{
    widgets.FilteredList{
        choices=self.features_list,
        frame = {t=0, b=1,l=1},
        view_id="body",
on_submit=self:callback("choose_cur_bp")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": select feature ",
                    key = "SELECT",
                    }
                   
                    }
            },
        }

    self:update_features()
end
add_editor(editor_body)

------ Colors editor
ColorBox = defclass(ColorBox, dialog.ListBox)
ColorBox.focus_path = 'ColorBox'

ColorBox.ATTRS{
with_filter = true,
allow_none = false,
}

function showColorPrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_other, data)
    ColorBox{
        frame_title = title,
        text = text,
        text_pen = tcolor,
        on_select = on_select,
        on_cancel = on_cancel,
        frame_width = min_width,
        allow_other = allow_other,
data = data
    }:show()
end

function ColorBox:update_choices()
local choices = {}
for i,v in ipairs(self.mod.pattern_index) do
table.insert(choices, {text=patternString(self.mod.pattern_index[i]), index = i})
end

self.choices = choices
if self.subviews.list then
        self.subviews.list:setChoices(self.choices)
    end
end

function ColorBox:init(info)
self.mod = info.data.mod

self.target_unit = target
self:update_choices()
end

editor_colors=defclass(editor_colors,gui.FramedScreen)
editor_colors.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Colors editor",
    target_unit = DEFAULT_NIL,
}

function getColor(id)
return df.descriptor_color.find(id)
end

function getPattern(id)
return df.descriptor_pattern.find(id)
end

function patternString(id)
local pattern = getPattern(id)
local prefix
if pattern.pattern == 0 then --Monochrome
return getColor(pattern.colors[0]).name
elseif pattern.pattern == 1 then --Stripes
prefix = "striped"
elseif pattern.pattern == 2 then --Iris_eye
return getColor(pattern.colors[2]).name .. " eyes"
elseif pattern.pattern == 3 then --Spots
prefix = "spotted" --that's a guess
elseif pattern.pattern == 4 then --Pupil_eye
return getColor(pattern.colors[2]).name .. " eyes"
elseif pattern.pattern == 5 then --mottled
prefix = "mottled"
end
local out = prefix .. " "
for i=0, #pattern.colors-1 do
if i == #pattern.colors-1 then
out = out .. "and " .. getColor(pattern.colors[i]).name
elseif i == #pattern.colors-2 then
out = out .. getColor(pattern.colors[i]).name .. " "
else
out = out .. getColor(pattern.colors[i]).name .. ", "
end
end
return out
end

function editor_colors:change_color(index,patternId)
self.target_unit.appearance.colors[index] = patternId
end

function editor_colors:update_features()
local uc = self.ucaste
local out = {}
for i,v in ipairs(uc.color_modifiers) do
table.insert(out, {text=uc.color_modifiers[i].part:gsub("^%l", string.upper), mod = uc.color_modifiers[i], index = i})
end
self.subviews.colors:setChoices(out)
end

function editor_colors:choose_cur_feature(index,choice)
self.chosenFeature = choice
local data = {ucaste = self.ucaste, mod = choice.mod} --data to pass to color prompt
showColorPrompt("Choose color", "Select features color",nil,function ( id,choice )
self:change_color(self.chosenFeature.index, choice.index)
            end,nil,nil,true, data)
end


function editor_colors:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

self.urace = self.target_unit.race
self.ucritter = df.creature_raw.find(self.urace)
self.ucaste = self.ucritter.caste[self.target_unit.caste]

    self:addviews{
    widgets.FilteredList{
        choices=self.features_list,
        frame = {t=0, b=1,l=1},
        view_id="colors",
on_submit=self:callback("choose_cur_feature")
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor ",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    {text=": select feature ",
                    key = "SELECT",
                    }
                   
                    }
            },
        }

    self:update_features()
end
add_editor(editor_colors)

------ Values (/beliefs)
editor_beliefs=defclass(editor_beliefs,gui.FramedScreen)
editor_beliefs.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Beliefs editor",
    target_unit = DEFAULT_NIL,
}

function editor_beliefs:buildPointers()
local out = {}
for i, v in ipairs(self.target_unit.status.current_soul.personality.values) do
out[v.type] = i
end
self.pointers = out
end

function editor_beliefs:getCurBeliefValue(unit, beliefId)
local upers = unit.status.current_soul.personality
if self.pointers[beliefId] then
return upers.values[self.pointers[beliefId]].strength, false
elseif upers.cultural_identity ~= -1 then
return df.cultural_identity.find(upers.cultural_identity).values[beliefId], true
else
return 0, true --outsiders have no culture
end
end

function editor_beliefs:update_choices()
self:buildPointers()
local out = {}
for i, v in ipairs(df.value_type) do
local niceText = v
niceText = niceText:lower()
niceText = niceText:gsub("_", " ")
niceText = niceText:gsub("^%l", string.upper)

local strength, isCulture = self:getCurBeliefValue(self.target_unit, i)
local numAddition = strength
if isCulture then
numAddition = numAddition .. "*"
end
table.insert(out, {["text"] = niceText .. ": " .. numAddition, ["beliefId"] = i, ["strength"] = strength, ["name"] = niceText})
end
self.subviews.beliefs:setChoices(out)
end

function editor_beliefs:set_belief(new_value, index, choice)
dfhack.run_script("modtools/set-belief", table.unpack({"-value", '\\' .. new_value,"-target",tostring(self.target_unit.id), "-belief", tostring(choice.beliefId)}))
self:update_choices()
end

function editor_beliefs:step_belief(dir)
local index, choice = self.subviews.beliefs:getSelected()
if not choice then
return
end
dfhack.run_script("modtools/set-belief", table.unpack({"-target",tostring(self.target_unit.id), "-belief", tostring(choice.beliefId), "-step", "\\" .. dir}))
self:update_choices()
end

function editor_beliefs:edit_belief(index, choice)
dialog.showInputPrompt(choice.name,"Enter new value:",COLOR_WHITE,
            tostring(choice.strength),function(new_value)
                self:set_belief(new_value,index,choice)
            end)
--This one causes choices to flicker for some reason
end

function editor_beliefs:default_belief(index, choice)
dfhack.run_script("modtools/set-belief", table.unpack({"-target",tostring(self.target_unit.id), "-belief", tostring(choice.beliefId), "-default"}))
self:update_choices()
end

function editor_beliefs:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

    self:addviews{
widgets.FilteredList{
frame = {t=0, b=2,l=1},
view_id="beliefs",
on_submit=self:callback("edit_belief"),
on_submit2=self:callback("default_belief")
},
widgets.Label{
frame = {b=1, l=1},
text ={{text= ": exit editor ",
key  = "LEAVESCREEN",
on_activate= self:callback("dismiss")
},
{text=": edit value ",
key = "SELECT",
},
{text=": raise ",
key = "STANDARDSCROLL_RIGHT",
on_activate=self:callback("step_belief",1)},
{text=": reduce ",
key = "STANDARDSCROLL_LEFT",
on_activate=self:callback("step_belief",-1)},
}
},
widgets.Label{
frame = {b=0, l=1},
text = {
{
text = "* denotes cultural default  "
},
{
text=": set to cultural default ",
key = "SEC_SELECT",
},
}



},
    }

    self:update_choices()
end
add_editor(editor_beliefs)

------ Personality
editor_pers=defclass(editor_pers,gui.FramedScreen)
editor_pers.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "Personality editor",
    target_unit = DEFAULT_NIL,
}

function editor_pers:getCurTraitValue(unit, traitId)
return unit.status.current_soul.personality.traits[traitId]
end

function editor_pers:update_choices()
local out = {}
for i, v in ipairs(df.personality_facet_type) do
local niceText = v
niceText = niceText:lower()
niceText = niceText:gsub("_", " ")
niceText = niceText:gsub("^%l", string.upper)

local strength = self:getCurTraitValue(self.target_unit, i)

table.insert(out, {["text"] = niceText .. ": " .. strength, ["traitId"] = i, ["strength"] = strength, ["name"] = niceText})
end
self.subviews.traits:setChoices(out)
end

function editor_pers:set_trait(new_value, index, choice)
dfhack.run_script("modtools/set-personality", table.unpack({"-value", '\\' .. new_value,"-target",tostring(self.target_unit.id), "-trait", tostring(choice.traitId)}))
self:update_choices()
end

function editor_pers:step_trait(dir)
local index, choice = self.subviews.traits:getSelected()
if not choice then
return
end
dfhack.run_script("modtools/set-personality", table.unpack({"-target",tostring(self.target_unit.id), "-trait", tostring(choice.traitId), "-step", "\\" .. dir}))
self:update_choices()
end

function editor_pers:edit_trait(index, choice)
dialog.showInputPrompt(choice.name,"Enter new value:",COLOR_WHITE,
            tostring(choice.strength),function(new_value)
                self:set_trait(new_value,index,choice)
            end)
end

function editor_pers:average_trait(index, choice)
dfhack.run_script("modtools/set-personality", table.unpack({"-target",tostring(self.target_unit.id), "-trait", tostring(choice.traitId), "-average"}))
self:update_choices()
end

function editor_pers:init( args )
    if self.target_unit==nil then
        qerror("invalid unit")
    end

    self:addviews{
widgets.FilteredList{
frame = {t=0, b=2,l=1},
view_id="traits",
on_submit=self:callback("edit_trait"),
on_submit2=self:callback("average_trait")
},
widgets.Label{
frame = {b=1, l=1},
text ={{text= ": exit editor ",
key  = "LEAVESCREEN",
on_activate= self:callback("dismiss")
},
{text=": edit value ",
key = "SELECT",
},
{text=": raise ",
key = "STANDARDSCROLL_RIGHT",
on_activate=self:callback("step_trait",1)},
{text=": reduce ",
key = "STANDARDSCROLL_LEFT",
on_activate=self:callback("step_trait",-1)},
}
},
widgets.Label{
frame = {b=0, l=1},
text = {
{
text=": set to caste average",
key = "SEC_SELECT",
},
}



},
    }

    self:update_choices()
end
add_editor(editor_pers)


-------------------------------main window----------------
unit_editor = defclass(unit_editor, gui.FramedScreen)
unit_editor.ATTRS={
    frame_style = gui.GREY_LINE_FRAME,
    frame_title = "GameMaster's unit editor",
    target_unit = DEFAULT_NIL,
    }


function unit_editor:init(args)

    self:addviews{
    widgets.FilteredList{
        choices=editors,
        on_submit=function (idx,choice)
            if choice.on_submit then
                choice.on_submit(self.target_unit)
            end
        end
    },
    widgets.Label{
                frame = { b=0,l=1},
                text ={{text= ": exit editor",
                    key  = "LEAVESCREEN",
                    on_activate= self:callback("dismiss")
                    },
                    }
            },
        }
end


unit_editor{target_unit=target}:show()
I got +/- to jump description ranges added, though I chose left/right instead (which're annoyingly prompted as 4/6 in the ui and I'm not sure if I can change it). While I was at it, I added personality and beliefs editing too, and also gave them the +/- feature. I forgot about styles (and don't really know much about them), I guess I'll have to look into those at some point #.# Note: this version relies on the current iteration of my modtools which, in the interest of screwing with anyone attempting to follow all this, is in an entirely different thread (here (http://www.bay12forums.com/smf/index.php?topic=164123.msg7505098#msg7505098)) :P
Title: Re: [DFhack 43.05r1] Atkana's Scripts (Adventure as anything, Change appearance)
Post by: Max™ on July 09, 2017, 01:42:42 pm
Awesome, I'm curious what your coding background is btw.
Title: Re: [DFhack 43.05r1] Atkana's Scripts (Adventure as anything, Change appearance)
Post by: Atkana on July 10, 2017, 12:28:16 pm
Awesome, I'm curious what your coding background is btw.
A while ago I read a book on/the manual for lua so I could get the computers in a Minecraft mod to tell jokes (as well as do some progressively more useful things) and also an introduction book to Unity which covered a bit about C# scripting. The rest of what I know is basically just a mix of learning from experience, picking apart what other people do to see how it works, and a healthy amount of Google-fu :P
Title: Re: [DFhack 43.05r1] Atkana's Scripts (Adventure as anything, Change appearance)
Post by: Max™ on July 10, 2017, 06:09:27 pm
Well hell, still say you should set up a github and push stuff to the main dfhack repo.
Title: Re: [DFhack] Atkana's Scripts - New: Make anyone a companion, Combat harden units
Post by: Atkana on August 11, 2019, 07:44:44 am
The scripter gestures!
The thread shudders and begins to move!

After a brief dive back into Dwarf Fortress, I've developed a couple of new scripts to add to the thread: combat-harden and make-companion.

Using make-companion, you can turn anyone into an adventuring companion for your adventurer, and combat-harden lets you change the combat harden value of a unit (or your entire fort), which governs how well/badly they react to witnessing death.
Title: Re: [DFhack] Atkana's Scripts - New: Make anyone a companion, Combat harden units
Post by: Kiloku on August 14, 2019, 01:26:17 pm
Any idea how I could go about making (or editing your) scripts that allow me to see someone else's skills/attributes?

Maybe even full info on the selected unit, like when you check your own info with [z].

I have an ally who's vomiting and dizzy non-stop, but I can't see anything that shows why in his info screen.
Title: Re: [DFhack] Atkana's Scripts - New: Make anyone a companion, Combat harden units
Post by: Atkana on August 15, 2019, 02:32:56 am
Any idea how I could go about making (or editing your) scripts that allow me to see someone else's skills/attributes?

Maybe even full info on the selected unit, like when you check your own info with [z].

I have an ally who's vomiting and dizzy non-stop, but I can't see anything that shows why in his info screen.
I'd be surprised if there wasn't an already existing script that displays anyone's status, I just don't know of one :b

It sounds like your companion is drunk/has some kind of injury. In this case you could always use dfhack's bodyswap script and temporarily switch to your companion so you can view their full details. If the cause is an injury and you don't mind cheating, you can use dfhack's full-heal to fix them up.
Title: Re: [DFhack] Atkana's Scripts - New: Make anyone a companion, Combat harden units
Post by: Kiloku on August 15, 2019, 07:03:38 am
Oh, I hadn't thought of using bodyswap for that! Thanks!
Title: Re: [DFhack] Atkana's Scripts - New: Make anyone a companion, Combat harden units
Post by: Korgoth on August 28, 2019, 11:29:46 am
First of all, thank you so much for the scripts! i'm a complete noob on using dfhack on DF, could tell how can I use the "make companion" script more specifically? I just need to target the creature putting on the description screen of the said creature and them put the make-companion.lua on the dfhack and press enter?
How can a target a unit? cause even if I put on the dfhack 'make-companion' nothing happens :(
Title: Re: [DFhack] Atkana's Scripts - New: Make anyone a companion, Combat harden units
Post by: Atkana on August 29, 2019, 02:04:18 am
First of all, thank you so much for the scripts! i'm a complete noob on using dfhack on DF, could tell how can I use the "make companion" script more specifically? I just need to target the creature putting on the description screen of the said creature and them put the make-companion.lua on the dfhack and press enter?
How can a target a unit? cause even if I put on the dfhack 'make-companion' nothing happens :(

You can type make-companion -help into the console to get the help info, which shows additional commands. Supposing the script is installed correctly, the only time you won't see any messages printed in the console is when it's worked, since the only messages it'll print are error messages :P
Title: Re: [DFhack] Atkana's Scripts - New: Make anyone a companion, Combat harden units
Post by: Korgoth on September 22, 2019, 09:33:42 pm
i did every step here, and I can see to make it work at all. I go to the description page of a humam monk, type the "make-companion" on dfhack and nothing happens :(
the command "make-companion -help" doesnt show any new massegs on the dfhack screen when i tried to do it
Title: Re: [DFhack] Atkana's Scripts - New: Make anyone a companion, Combat harden units
Post by: Atkana on September 24, 2019, 06:46:55 am
i did every step here, and I can see to make it work at all. I go to the description page of a humam monk, type the "make-companion" on dfhack and nothing happens :(
the command "make-companion -help" doesnt show any new massegs on the dfhack screen when i tried to do it
Do you get an error message when you try to use the command (i.e." _____ is not a recognized command."), or nothing at all?

If there is no error:

If there is an error, then chances are there's a problem with the installation:
Title: Re: [DFhack] Atkana's Scripts - New: Make anyone a companion, Combat harden units
Post by: Fleeting Frames on November 18, 2019, 05:15:57 am
Hello. I've found your view-allegiance pretty useful, thanks for writing it.

An annoying thing is that sometimes....Sometimes people end up dead. Well, there's dead unit list, oooor the script could start something like this so you could use it while looking at their corpse:

Spoiler (click to show/hide)
Title: Re: [DFhack] Atkana's Scripts - New: Make anyone a companion, Combat harden units
Post by: Atkana on November 29, 2019, 03:25:04 am
Hello. I've found your view-allegiance pretty useful, thanks for writing it.

An annoying thing is that sometimes....Sometimes people end up dead. Well, there's dead unit list, oooor the script could start something like this so you could use it while looking at their corpse:

Spoiler (click to show/hide)
That sounds pretty useful. view-allegiance is pretty old now, so if I ever write up a new version, I'll be sure to include that :p
Title: Re: [DFhack] Atkana's Scripts - New: Make creatures fine with missing clothing
Post by: Atkana on January 25, 2020, 06:54:17 am
So one day I was playing a modded fort as centaurs (because centaurs are cool), but inevitably practically all of them went insane because they didn't have shoes to wear on their hooves. Their entity didn't even have access to shoes - why should they care!? Aside from giving me inspiration for a thief civ - some dwarf-size creatures who have no access to clothes, and so must steal clothes from dwarves to cover their shame - I found it pretty annoying, and the only workaround is to set all of the creature's bashfulness to its minimum. I didn't really want to have to do that, so instead I made a script to fix the problem. Using Clothing Optional, you can register creatures on a creature or entity level basis to no longer care about lacking shirts, pants, shoes, or any combination of the three. It's intended for use with modded creatures, but you can apply it to any normal creatures too.

I've added Clothing Optional to the OP, and removed both Combat Harden and Change Appearance, since both are now included in DFHack :D You'll find the Change Appearance stuff as part of the new features within gui/gm-unit.
Title: Re: [DFhack] Atkana's Scripts - New: Convert units to your fort
Post by: Atkana on October 25, 2020, 09:44:36 am
I had a brief moment of being back into Dwarf Fortress where I made a new script, which I figured I might as well release here. Make Citizen does what tweak makeown used to: convert a unit into a full citizen of your fort, with full access to labours, and no need to petition or anything like that. I never got around to fully testing it in more niche cases before moving on to other things, but hopefully is fully functional and of use to somebody.
Title: Re: [DFhack] Atkana's Scripts - New: Code-based patches for some standard edits
Post by: Atkana on November 17, 2020, 09:03:07 am
I was feeling a bit experimental, so I've made a few scripts that are about applying some raw edits entirely via scripts, as well as some generally useful gameplay changes. It should have the added bonus of not requiring you to make a whole bunch of edits if you want to include lots of creatures from different mods, as well as let you edit some things that aren't normally available. Here's the stuff that's currently available with the patch scripts:
I might later expand on the selection of patches and add support for additional styles of patch scripts in World Patch, but knowing me, probably not :P See the entry for World Patch in the OP for the download.
Title: Re: [DFhack] Atkana's Scripts - New: Code-based patches for some standard edits
Post by: ArchimedesWojak on November 17, 2020, 09:09:39 am
This is really cool but how do i add the scripts
Title: Re: [DFhack] Atkana's Scripts - New: Code-based patches for some standard edits
Post by: Atkana on November 17, 2020, 09:32:53 am
This is really cool but how do i add the scripts
In general for the github downloads:
Alternatively, if you navigate to the main folder of the github repo, you should see a green dropdown near the top right of the list of files. If you click that and select Download ZIP, it'll download everything there. Then, you just pick out the parts that you want from that ZIP folder.

Just be aware that a few of the scripts are outdated, and either no longer work (I think Adventurer Freedom still needs updating :c) or no longer necessary (some of them are included in DFhack now, or a newer script covers what they did).
Title: Re: [DFhack] Atkana's Scripts - New: Code-based patches for some standard edits
Post by: Korgoth on December 15, 2020, 12:42:49 am
Hello, I made The scricpt work properly this time. I've made a brend new full installations of the dfhack and it worked!
Do you know if there is a way to make animals my companions? more comom animals, like dogs on the citys for example?
Every time I tried to "make-companion" on this more simplistic creatures I get the message " Target isn't a historical figure and so can't be made into a companion". Do I have to do someting else to be able to recruit animals as my companions in adventure mode?
Title: Re: [DFhack] Atkana's Scripts - New: Code-based patches for some standard edits
Post by: Atkana on December 15, 2020, 02:29:53 am
Hello, I made The scricpt work properly this time. I've made a brend new full installations of the dfhack and it worked!
Do you know if there is a way to make animals my companions? more comom animals, like dogs on the citys for example?
Every time I tried to "make-companion" on this more simplistic creatures I get the message " Target isn't a historical figure and so can't be made into a companion". Do I have to do someting else to be able to recruit animals as my companions in adventure mode?
I've not really made any changes to the script since pets and such were added to adventure mode, so it'd probably need an update to handle claiming things as pets instead of companions. However, if those dogs you see in cities are strays, there's already functionality to claim them as your own pets in vanilla! I forget the key, and google is failing me, but I think it might be that you have to walk up next to the animal and press h?
Title: Re: [DFhack] Atkana's Scripts - New: Code-based patches for some standard edits
Post by: Toxicshadow on February 21, 2021, 01:06:24 pm
Hey this clothing optional script sounds really helpful! I hadn't thought about it until now but, if a creature doesn't have the body parts (eg feet) to wear a certain type of clothing (eg shoes) do they still get the negative thought from not having said clothing? Is that what this script is for?
Title: Re: [DFhack] Atkana's Scripts - New: Code-based patches for some standard edits
Post by: Atkana on February 23, 2021, 04:04:02 am
Hey this clothing optional script sounds really helpful! I hadn't thought about it until now but, if a creature doesn't have the body parts (eg feet) to wear a certain type of clothing (eg shoes) do they still get the negative thought from not having said clothing? Is that what this script is for?
I've also never really thought about that. I'd imagine they probably don't get the bad thoughts if they don't have the parts, but then again regular creatures will still get bad thoughts in vanilla even if their civilization doesn't make clothes that cover particular parts, so who knows? Either way, if it is something that happens, the script should be able to cover that, though the original intention was for allowing you to have slightly more control over what clothes your civilization cares about without having to worry about the creatures constantly getting bad thoughts all the time if you choose to leave some out (e.g. hobbits who don't mind walking around barefoot, intelligent animals that don't care if they don't have clothes at all, etc.).
Title: Re: [DFhack] Atkana's Scripts - New: Code-based patches for some standard edits
Post by: Cathar on February 23, 2021, 08:12:09 am
Ooooooh
OOOOOOOOOOOOOH

That make citizen is a probable life saver. Very nice ! Thanks so much !
Title: Re: [DFhack] Atkana's Scripts - New: Code-based patches for some standard edits
Post by: Atkana on March 04, 2022, 10:43:17 am
I made a script for someone in Discord, and figured I might as well add it to my collection of releases. announce-skills (https://github.com/Atkana/Dwarf-Fortress-Mods#announce-skills) is a simple script that makes announcements whenever a citizen levels up one of their skills. Nice and straightforward, with some configuration options for what sort of skills you want announcements for.

I think it might be an auto-include for any forts I play from now on.
Title: Re: [DFhack] Atkana's Scripts - New: Passively train skills in adventure mode
Post by: Atkana on April 28, 2022, 04:05:01 am
This script has just been sitting around while I was waiting for one of the utilities I made that it requires to be added into DFhack. Since that's been taking a while, I've decided to just go ahead and release it and that utility for anyone to use, otherwise it'll just be gathering dust for who knows how long. It's been ages since I've actually used it, but I assume it still works! :p

passive-training (https://github.com/Atkana/Dwarf-Fortress-Mods#passive-training) lets you select skills to passively train over time as you play in adventure mode, and its required utility script-data (https://github.com/Atkana/Dwarf-Fortress-Mods/tree/master/Other#script-data) helps scripts store any important data with the world whenever the game is saved.
Title: Re: [DFhack] Atkana's Scripts - New: Cannibalism fixes
Post by: Atkana on July 17, 2022, 07:45:00 am
Update for a minor new addition: auto-cannibalism (https://github.com/Atkana/Dwarf-Fortress-Mods#auto-cannibalism), which should work as an okay workaround until all the sapient-eating bugs are fixed.
Title: Re: [DFhack] Atkana's Scripts - New: Cannibalism fixes
Post by: Jack_Caboose on July 21, 2022, 11:57:59 am
How do the Modest Mod-esque leather changes work? Does it just include the extra yield + scale tanning, or does it also include the name changes? E.g. if I install the script, will things still be called "cow leather waterskin" or will it just be "waterskin", like in the Modest Mod? I've always liked how vanilla tracks which creature things are made of, but getting the extra changes would be nice.
Title: Re: [DFhack] Atkana's Scripts - New: Cannibalism fixes
Post by: Atkana on July 22, 2022, 01:22:47 am
How do the Modest Mod-esque leather changes work? Does it just include the extra yield + scale tanning, or does it also include the name changes? E.g. if I install the script, will things still be called "cow leather waterskin" or will it just be "waterskin", like in the Modest Mod? I've always liked how vanilla tracks which creature things are made of, but getting the extra changes would be nice.
If I'm remembering it correctly, this doesn't actually replace the creature materials with standardised ones, so yes, the individual animal stuff will still be tracked. In fact, I remember this is the case because of the unavoidable bug (Dwarf Fortress bug, not a bug with the script necessarily) where the game will sometimes combine the leathers of different animals while crafting (it ends up being made of the first material added to the reaction from what I remember, in case anybody's curious).
Title: Re: [DFhack] Atkana's Scripts - New: Cannibalism fixes
Post by: Lawaern on April 25, 2023, 02:34:35 am
Sorry to  bother, but would Auto-Cannibalism work in the Steam version? I keep getting "Auto-cannibalism is not a recognized command" errors, and I can't tell if I'm just doing everything wrong or if the the script isn't recognizable by the new DFHack.
Title: Re: [DFhack] Atkana's Scripts - New: Cannibalism fixes
Post by: Atkana on April 25, 2023, 04:05:02 am
Sorry to  bother, but would Auto-Cannibalism work in the Steam version? I keep getting "Auto-cannibalism is not a recognized command" errors, and I can't tell if I'm just doing everything wrong or if the the script isn't recognizable by the new DFHack.
It (and all my other scripts) likely need some amount of translating to work in post steam release versions. Unfortunately I've not really had any interest in DF modding since then to update any of my things. It may be simple enough that someone experienced would be able to tell you the edits that need to be made, though.
Title: Re: [DFhack] Atkana's Scripts - New: Cannibalism fixes
Post by: Lawaern on April 26, 2023, 05:18:42 am
Ah, that makes sense. Thanks for the reply, I might look into updating the script if I can find the time and motivation later.