Merge branch 'develop' of https://github.com/dfhack/dfhack into develop

develop
lethosor 2015-09-19 17:43:33 -04:00
commit 7f575a8fd1
8 changed files with 1357 additions and 66 deletions

@ -27,12 +27,16 @@ DFHack Future
fix-ster: changes fertility/sterility of animals or dwarves fix-ster: changes fertility/sterility of animals or dwarves
view-item-info: adds information and customisable descriptions to item viewscreens view-item-info: adds information and customisable descriptions to item viewscreens
warn-starving: check for starving, thirsty, or very drowsy units and pause with warning if any are found warn-starving: check for starving, thirsty, or very drowsy units and pause with warning if any are found
modtools/create-unit: create new units from nothing
modtools/equip-item: a script to equip items on units
points: set number of points available at embark screen points: set number of points available at embark screen
New tweaks New tweaks
embark-profile-name: Allows the use of lowercase letters when saving embark profiles embark-profile-name: Allows the use of lowercase letters when saving embark profiles
kitchen-keys: Fixes DF kitchen meal keybindings kitchen-keys: Fixes DF kitchen meal keybindings
kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences
kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs
scripts/modtools/create-item: arguments are named more clearly, and you can specify the creator to be the unit with id df.global.unit_next_id-1 (useful in conjunction with modtools/create-unit)
scripts/teleport.lua is now compatible with the script_environment/reqscript system.
Fixes Fixes
Plugins with vmethod hooks can now be reloaded on OS X Plugins with vmethod hooks can now be reloaded on OS X
Lua's os.system() now works on OS X Lua's os.system() now works on OS X

@ -302,6 +302,9 @@ message WorldMap
repeated int32 salinity = 14; repeated int32 salinity = 14;
optional int32 map_x = 15; optional int32 map_x = 15;
optional int32 map_y = 16; optional int32 map_y = 16;
optional int32 center_x = 17;
optional int32 center_y = 18;
optional int32 center_z = 19;
} }
message RegionMaps message RegionMaps
@ -336,3 +339,19 @@ message CreatureRawList
{ {
repeated CreatureRaw creature_raws = 1; repeated CreatureRaw creature_raws = 1;
} }
message Army
{
optional int32 id = 1;
optional int32 pos_x = 2;
optional int32 pos_y = 3;
optional int32 pos_z = 4;
optional UnitDefinition leader = 5;
repeated UnitDefinition members = 6;
optional uint32 flags = 7;
}
message ArmyList
{
repeated Army armies = 1;
}

@ -42,6 +42,8 @@
#include "df/region_map_entry.h" #include "df/region_map_entry.h"
#include "df/world_region_details.h" #include "df/world_region_details.h"
#include "df/army.h"
#include "df/army_flags.h"
#include "df/unit.h" #include "df/unit.h"
#include "df/creature_raw.h" #include "df/creature_raw.h"
@ -1256,6 +1258,23 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in,
clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front);
clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus);
} }
int32_t pos_x = 0, pos_y = 0, pos_z = 0;
if (Maps::IsValid())
Maps::getPosition(pos_x, pos_y, pos_z);
else
for (int i = 0; i < df::global::world->armies.all.size(); i++)
{
df::army * thisArmy = df::global::world->armies.all[i];
if (thisArmy->flags.is_set(df::enums::army_flags::player))
{
pos_x = (thisArmy->pos.x / 3) - 1;
pos_y = (thisArmy->pos.y / 3) - 1;
pos_z = thisArmy->pos.z;
}
}
out->set_center_x(pos_x);
out->set_center_y(pos_y);
out->set_center_z(pos_z);
return CR_OK; return CR_OK;
} }

@ -0,0 +1,596 @@
-- Interface powered (somewhat user friendly) unit editor.
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)
-------------------------------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()

@ -4,17 +4,97 @@
local utils = require 'utils' local utils = require 'utils'
validArgs = validArgs or utils.invert({ validArgs = --[[validArgs or--]] utils.invert({
'help', 'help',
'creator', 'creator',
'material', 'material',
'item', 'item',
-- 'creature', -- 'creature',
-- 'caste', -- 'caste',
'matchingGloves', 'leftHand',
'matchingShoes' 'rightHand',
'quality'
}) })
organicTypes = organicTypes or utils.invert({
df.item_type.REMAINS,
df.item_type.FISH,
df.item_type.FISH_RAW,
df.item_type.VERMIN,
df.item_type.PET,
df.item_type.EGG,
})
badTypes = badTypes or utils.invert({
df.item_type.CORPSE,
df.item_type.CORPSEPIECE,
df.item_type.FOOD,
})
function createItem(creatorID, item, material, leftHand, rightHand, quality)
local itemQuality = quality and df.item_quality[quality]
print(itemQuality)
local creator = df.unit.find(creatorID)
if not creator then
error 'Invalid creator.'
end
if not item then
error 'Invalid item.'
end
local itemType = dfhack.items.findType(item)
if itemType == -1 then
error 'Invalid item.'
end
local itemSubtype = dfhack.items.findSubtype(item)
if organicTypes[itemType] then
--TODO: look up creature and caste
error 'Not yet supported.'
end
if badTypes[itemType] then
error 'Not supported.'
end
if not material then
error 'Invalid material.'
end
local materialInfo = dfhack.matinfo.find(material)
if not materialInfo then
error 'Invalid material.'
end
local item1 = dfhack.items.createItem(itemType, itemSubtype, materialInfo['type'], materialInfo.index, creator)
local item = df.item.find(item1)
if leftHand then
item:setGloveHandedness(2)
elseif rightHand then
item:setGloveHandedness(1)
end
if itemQuality then
item:setQuality(itemQuality)
end
--[[if matchingGloves or matchingShoes then
if matchingGloves then
item1 = df.item.find(item1)
item1:setGloveHandedness(1);
end
local item2 = dfhack.items.createItem(itemType, itemSubtype, materialInfo['type'], materialInfo.index, creator)
if matchingGloves then
item2 = df.item.find(item2)
item2:setGloveHandedness(2);
end
end --]]
return item1
end
if moduleMode then
return
end
local args = utils.processArgs({...}, validArgs) local args = utils.processArgs({...}, validArgs)
if args.help then if args.help then
@ -24,10 +104,11 @@ arguments:
-help -help
print this help message print this help message
-creator id -creator id
specify the id of the unit who will create the item specify the id of the unit who will create the item, or \\LAST to indicate the unit with id df.global.unit_next_id-1
examples: examples:
0 0
2 2
\\LAST
-material matstring -material matstring
specify the material of the item to be created specify the material of the item to be created
examples: examples:
@ -46,65 +127,8 @@ arguments:
return return
end end
if not args.creator or not tonumber(args.creator) or not df.unit.find(tonumber(args.creator)) then if args.creator == '\\LAST' then
error 'Invalid creator.' args.creator = tostring(df.global.unit_next_id-1)
end
args.creator = df.unit.find(tonumber(args.creator))
if not args.creator then
error 'Invalid creator.'
end
if not args.item then
error 'Invalid item.'
end
local itemType = dfhack.items.findType(args.item)
if itemType == -1 then
error 'Invalid item.'
end
local itemSubtype = dfhack.items.findSubtype(args.item)
organicTypes = organicTypes or utils.invert({
df.item_type.REMAINS,
df.item_type.FISH,
df.item_type.FISH_RAW,
df.item_type.VERMIN,
df.item_type.PET,
df.item_type.EGG,
})
if organicTypes[itemType] then
--TODO: look up creature and caste
error 'Not yet supported.'
end
badTypes = badTypes or utils.invert({
df.item_type.CORPSE,
df.item_type.CORPSEPIECE,
df.item_type.FOOD,
})
if badTypes[itemType] then
error 'Not supported.'
end
if not args.material then
error 'Invalid material.'
end
args.material = dfhack.matinfo.find(args.material)
if not args.material then
error 'Invalid material.'
end
local item1 = dfhack.items.createItem(itemType, itemSubtype, args.material['type'], args.material.index, args.creator)
if args.matchingGloves or args.matchingShoes then
if args.matchingGloves then
item1 = df.item.find(item1)
item1:setGloveHandedness(1);
end
local item2 = dfhack.items.createItem(itemType, itemSubtype, args.material['type'], args.material.index, args.creator)
if args.matchingGloves then
item2 = df.item.find(item2)
item2:setGloveHandedness(2);
end
end end
createItem(tonumber(args.creator), args.item, args.material, args.leftHand, args.rightHand, args.quality)

@ -0,0 +1,526 @@
-- create-unit.lua
-- Originally created by warmist, edited by Putnam for the dragon ball mod to be used in reactions, modified by Dirst for use in The Earth Strikes Back mod, incorporating fixes discovered by Boltgun then Mifiki wrote the bit where it switches to arena mode briefly to do some of the messy work, then Expwnent combined that with the old script to make it function for histfigs
-- version 0.5
-- This is a beta version. Use at your own risk.
--[[
TODO
children and babies: set child/baby job
confirm body size is computed appropriately for different ages / life stages
incarnate pre-existing historical figures
some sort of invasion helper script
set invasion_id, etc
announcement for fake natural birth if appropriate
]]
--[[
if dfhack.gui.getCurViewscreen()._type ~= df.viewscreen_dwarfmodest or df.global.ui.main.mode ~= df.ui_sidebar_mode.LookAround then
print 'activate loo[k] mode'
return
end
--]]
local utils=require 'utils'
function createUnit(race_id, caste_id)
local curViewscreen = dfhack.gui.getCurViewscreen()
local dwarfmodeScreen = df.viewscreen_dwarfmodest:new()
curViewscreen.child = dwarfmodeScreen
dwarfmodeScreen.parent = curViewscreen
local oldMode = df.global.ui.main.mode
df.global.ui.main.mode = df.ui_sidebar_mode.LookAround
local gui = require 'gui'
df.global.world.arena_spawn.race:resize(0)
df.global.world.arena_spawn.race:insert(0,race_id) --df.global.ui.race_id)
df.global.world.arena_spawn.caste:resize(0)
df.global.world.arena_spawn.caste:insert(0,caste_id)
df.global.world.arena_spawn.creature_cnt:resize(0)
df.global.world.arena_spawn.creature_cnt:insert(0,0)
--df.global.world.arena_spawn.equipment.skills:insert(0,99)
--df.global.world.arena_spawn.equipment.skill_levels:insert(0,0)
df.global.gametype = 4
gui.simulateInput(dfhack.gui.getCurViewscreen(), 'D_LOOK_ARENA_CREATURE')
gui.simulateInput(dfhack.gui.getCurViewscreen(), 'SELECT')
df.global.gametype = 0
curViewscreen.child = nil
dwarfmodeScreen:delete()
df.global.ui.main.mode = oldMode
local id = df.global.unit_next_id-1
return id
end
--local u = df.unit.find(df.global.unit_next_id-1)
--u.civ_id = df.global.ui.civ_id
--u.population_id = df.historical_entity.find(df.global.ui.civ_id).populations[0]
--local group = df.global.ui.group_id
-- Picking a caste or gender at random
function getRandomCasteId(race_id)
local cr = df.creature_raw.find(race_id)
local caste_id, casteMax
casteMax = #cr.caste - 1
if casteMax > 0 then
return math.random(0, casteMax)
end
return 0
end
local function allocateNewChunk(hist_entity)
hist_entity.save_file_id=df.global.unit_chunk_next_id
df.global.unit_chunk_next_id=df.global.unit_chunk_next_id+1
hist_entity.next_member_idx=0
print("allocating chunk:",hist_entity.save_file_id)
end
local function allocateIds(nemesis_record,hist_entity)
if hist_entity.next_member_idx==100 then
allocateNewChunk(hist_entity)
end
nemesis_record.save_file_id=hist_entity.save_file_id
nemesis_record.member_idx=hist_entity.next_member_idx
hist_entity.next_member_idx=hist_entity.next_member_idx+1
end
function createFigure(trgunit,he,he_group)
local hf=df.historical_figure:new()
hf.id=df.global.hist_figure_next_id
hf.race=trgunit.race
hf.caste=trgunit.caste
hf.profession = trgunit.profession
hf.sex = trgunit.sex
df.global.hist_figure_next_id=df.global.hist_figure_next_id+1
hf.appeared_year = df.global.cur_year
hf.born_year = trgunit.relations.birth_year
hf.born_seconds = trgunit.relations.birth_time
hf.curse_year = trgunit.relations.curse_year
hf.curse_seconds = trgunit.relations.curse_time
hf.birth_year_bias = trgunit.relations.birth_year_bias
hf.birth_time_bias = trgunit.relations.birth_time_bias
hf.old_year = trgunit.relations.old_year
hf.old_seconds = trgunit.relations.old_time
hf.died_year = -1
hf.died_seconds = -1
hf.name:assign(trgunit.name)
hf.civ_id = trgunit.civ_id
hf.population_id = trgunit.population_id
hf.breed_id = -1
hf.unit_id = trgunit.id
df.global.world.history.figures:insert("#",hf)
hf.info = df.historical_figure_info:new()
hf.info.unk_14 = df.historical_figure_info.T_unk_14:new() -- hf state?
--unk_14.region_id = -1; unk_14.beast_id = -1; unk_14.unk_14 = 0
hf.info.unk_14.unk_18 = -1; hf.info.unk_14.unk_1c = -1
-- set values that seem related to state and do event
--change_state(hf, dfg.ui.site_id, region_pos)
--lets skip skills for now
--local skills = df.historical_figure_info.T_skills:new() -- skills snap shot
-- ...
-- note that innate skills are automaticaly set by DF
hf.info.skills = {new=true}
he.histfig_ids:insert('#', hf.id)
he.hist_figures:insert('#', hf)
if he_group then
he_group.histfig_ids:insert('#', hf.id)
he_group.hist_figures:insert('#', hf)
hf.entity_links:insert("#",{new=df.histfig_entity_link_memberst,entity_id=he_group.id,link_strength=100})
end
trgunit.flags1.important_historical_figure = true
trgunit.flags2.important_historical_figure = true
trgunit.hist_figure_id = hf.id
trgunit.hist_figure_id2 = hf.id
hf.entity_links:insert("#",{new=df.histfig_entity_link_memberst,entity_id=trgunit.civ_id,link_strength=100})
--add entity event
local hf_event_id=df.global.hist_event_next_id
df.global.hist_event_next_id=df.global.hist_event_next_id+1
df.global.world.history.events:insert("#",{new=df.history_event_add_hf_entity_linkst,year=trgunit.relations.birth_year,
seconds=trgunit.relations.birth_time,id=hf_event_id,civ=hf.civ_id,histfig=hf.id,link_type=0})
return hf
end
function createNemesis(trgunit,civ_id,group_id)
local id=df.global.nemesis_next_id
local nem=df.nemesis_record:new()
nem.id=id
nem.unit_id=trgunit.id
nem.unit=trgunit
nem.flags:resize(4)
--not sure about these flags...
-- [[
nem.flags[4]=true
nem.flags[5]=true
nem.flags[6]=true
nem.flags[7]=true
nem.flags[8]=true
nem.flags[9]=true
--]]
--[[for k=4,8 do
nem.flags[k]=true
end]]
nem.unk10=-1
nem.unk11=-1
nem.unk12=-1
df.global.world.nemesis.all:insert("#",nem)
df.global.nemesis_next_id=id+1
trgunit.general_refs:insert("#",{new=df.general_ref_is_nemesisst,nemesis_id=id})
trgunit.flags1.important_historical_figure=true
nem.save_file_id=-1
local he=df.historical_entity.find(civ_id)
he.nemesis_ids:insert("#",id)
he.nemesis:insert("#",nem)
local he_group
if group_id and group_id~=-1 then
he_group=df.historical_entity.find(group_id)
end
if he_group then
he_group.nemesis_ids:insert("#",id)
he_group.nemesis:insert("#",nem)
end
allocateIds(nem,he)
nem.figure=createFigure(trgunit,he,he_group)
end
--createNemesis(u, u.civ_id,group)
function createUnitInCiv(race_id, caste_id, civ_id, group_id)
local uid = createUnit(race_id, caste_id)
local unit = df.unit.find(uid)
if ( civ_id ) then
createNemesis(unit, civ_id, group_id)
end
return uid
end
function createUnitInFortCiv(race_id, caste_id)
return createUnitInCiv(race_id, caste_id, df.global.ui.civ_id)
end
function createUnitInFortCivAndGroup(race_id, caste_id)
return createUnitInCiv(race_id, caste_id, df.global.ui.civ_id, df.global.ui.group_id)
end
function domesticate(uid, group_id)
local u = df.unit.find(uid)
group_id = group_id or df.global.ui.group_id
-- If a friendly animal, make it domesticated. From Boltgun & Dirst
local caste=df.creature_raw.find(u.race).caste[u.caste]
if not(caste.flags.CAN_SPEAK and caste.flags.CAN_LEARN) then
-- Fix friendly animals (from Boltgun)
u.flags2.resident = false;
u.flags3.body_temp_in_range = true;
u.population_id = -1
u.status.current_soul.unit_id = u.id
u.animal.population.region_x = -1
u.animal.population.region_y = -1
u.animal.population.unk_28 = -1
u.animal.population.population_idx = -1
u.animal.population.depth = -1
u.counters.soldier_mood_countdown = -1
u.counters.death_cause = -1
u.enemy.anon_4 = -1
u.enemy.anon_5 = -1
u.enemy.anon_6 = -1
-- And make them tame (from Dirst)
u.flags1.tame = true
u.training_level = 7
end
end
function nameUnit(id, entityRawName, civ_id)
--pick a random appropriate name
--choose three random words in the appropriate things
local unit = df.unit.find(id)
local entity_raw
if entityRawName then
for k,v in ipairs(df.global.world.raws.entities) do
if v.code == entityRawName then
entity_raw = v
break
end
end
else
local entity = df.historical_entity.find(civ_id)
entity_raw = entity.entity_raw
end
if not entity_raw then
error('entity raw = nil: ', id, entityRawName, civ_id)
end
local translation = entity_raw.translation
local translationIndex
for k,v in ipairs(df.global.world.raws.language.translations) do
if v.name == translation then
translationIndex = k
break
end
end
--translation = df.language_translation.find(translation)
local language_word_table = entity_raw.symbols.symbols1[0] --educated guess
function randomWord()
local index = math.random(0, #language_word_table.words[0] - 1)
return index
end
local firstName = randomWord()
local lastName1 = randomWord()
local lastName2 = randomWord()
local name = unit.status.current_soul.name
name.words[0] = language_word_table.words[0][lastName1]
name.parts_of_speech[0] = language_word_table.parts[0][lastName1]
name.words[1] = language_word_table.words[0][lastName2]
name.parts_of_speech[1] = language_word_table.parts[0][lastName2]
name.first_name = df.language_word.find(language_word_table.words[0][firstName]).forms[language_word_table.parts[0][firstName]]
name.has_name = true
name.language = translationIndex
name = unit.name
name.words[0] = language_word_table.words[0][lastName1]
name.parts_of_speech[0] = language_word_table.parts[0][lastName1]
name.words[1] = language_word_table.words[0][lastName2]
name.parts_of_speech[1] = language_word_table.parts[0][lastName2]
name.first_name = df.language_word.find(language_word_table.words[0][firstName]).forms[language_word_table.parts[0][firstName]]
name.has_name = true
name.language = translationIndex
if unit.hist_figure_id ~= -1 then
local histfig = df.historical_figure.find(unit.hist_figure_id)
name = histfig.name
name.words[0] = language_word_table.words[0][lastName1]
name.parts_of_speech[0] = language_word_table.parts[0][lastName1]
name.words[1] = language_word_table.words[0][lastName2]
name.parts_of_speech[1] = language_word_table.parts[0][lastName2]
name.first_name = df.language_word.find(language_word_table.words[0][firstName]).forms[language_word_table.parts[0][firstName]]
name.has_name = true
name.language = translationIndex
end
end
validArgs = --[[validArgs or]]utils.invert({
'help',
'race',
'caste',
'domesticate',
'civId',
'groupId',
'flagSet',
'flagClear',
'name',
'location',
'age'
})
if moduleMode then
return
end
local args = utils.processArgs({...}, validArgs)
if args.help then
print(
[[scripts/modtools/create-unit.lua
arguments:
-help
print this help message
-race raceName
specify the race of the unit to be created
examples:
DWARF
HUMAN
-caste casteName
specify the caste of the unit to be created
examples:
MALE
FEMALE
-domesticate
if the unit can't learn or can't speak, then make it a friendly animal
-civId id
make the created unit a member of the specified civ (or none if id = -1)
if id is \\LOCAL, then make it a member of the civ associated with the current fort
otherwise id must be an integer
-groupId id
make the created unit a member of the specified group (or none if id = -1)
if id is \\LOCAL, then make it a member of the group associated with the current fort
otherwise id must be an integer
-name entityRawName
set the unit's name to be a random name appropriate for the given entity
examples:
MOUNTAIN
-location [ x y z ]
create the unit at the specified coordinates
-age howOld
set the birth date of the unit to the specified number of years ago
-flagSet [ flag1 flag2 ... ]
set the specified unit flags in the new unit to true
flags may be selected from df.unit_flags1, df.unit_flags2, or df.unit_flags3
-flagClear [ flag1 flag2 ... ]
set the specified unit flags in the new unit to false
flags may be selected from df.unit_flags1, df.unit_flags2, or df.unit_flags3
]])
return
end
local race
local raceIndex
local casteIndex
if not args.race or not args.caste then
error 'Specfiy a race and caste for the new unit.'
end
--find race
for i,v in ipairs(df.global.world.raws.creatures.all) do
if v.creature_id == args.race then
raceIndex = i
race = v
break
end
end
if not race then
error 'Invalid race.'
end
for i,v in ipairs(race.caste) do
if v.caste_id == args.caste then
casteIndex = i
break
end
end
if not casteIndex then
error 'Invalid caste.'
end
local age
if args.age then
age = tonumber(args.age)
if not age and not age == 0 then
error('Invalid age: ' .. args.age)
end
end
local civ_id
if args.civId == '\\LOCAL' then
civ_id = df.global.ui.civ_id
elseif args.civId and tonumber(args.civId) then
civ_id = tonumber(args.civId)
end
local group_id
if args.groupId == '\\LOCAL' then
group_id = df.global.ui.group_id
elseif args.groupId and tonumber(args.groupId) then
group_id = tonumber(args.groupId)
end
local unitId = createUnitInCiv(raceIndex, casteIndex, civ_id, group_id)
if args.domesticate then
domesticate(unitId, group_id)
end
if age or age == 0 then
local u = df.unit.find(unitId)
local oldYearDelta = u.relations.old_year - u.relations.birth_year
u.relations.birth_year = df.global.cur_year - age
u.relations.old_year = u.relations.birth_year + oldYearDelta
--these flags are an educated guess of how to get the game to compute sizes correctly: use -flagSet and -flagClear arguments to override or supplement
u.flags2.calculated_nerves = false
u.flags2.calculated_bodyparts = false
u.flags3.body_part_relsize_computed = false
u.flags3.size_modifier_computed = false
u.flags3.compute_health = true
u.flags3.weight_computed = false
--TODO: if the unit is a child or baby it will still behave like an adult
end
if args.flagSet or args.flagClear then
local u = df.unit.find(unitId)
local flagsToSet = {}
local flagsToClear = {}
for _,v in ipairs(args.flagSet or {}) do
flagsToSet[v] = true
end
for _,v in ipairs(args.flagClear or {}) do
flagsToClear[v] = true
end
for _,k in ipairs(df.unit_flags1) do
if flagsToSet[k] then
u.flags1[k] = true;
elseif flagsToClear[k] then
u.flags1[k] = false;
end
end
for _,k in ipairs(df.unit_flags2) do
if flagsToSet[k] then
u.flags2[k] = true;
elseif flagsToClear[k] then
u.flags2[k] = false;
end
end
for _,k in ipairs(df.unit_flags3) do
if flagsToSet[k] then
u.flags3[k] = true;
elseif flagsToClear[k] then
u.flags3[k] = false;
end
end
end
if args.name then
nameUnit(unitId, args.name, civ_id)
else
local unit = df.unit.find(unitId)
unit.name.has_name = false
if unit.status.current_soul then
unit.status.current_soul.name.has_name = false
end
--[[if unit.hist_figure_id ~= -1 then
local histfig = df.historical_figure.find(unit.hist_figure_id)
histfig.name.has_name = false
end--]]
end
if civ_id then
local u = df.unit.find(unitId)
u.civ_id = civ_id
end
if args.location then
local u = df.unit.find(unitId)
local pos = df.coord:new()
pos.x = tonumber(args.location[1])
pos.y = tonumber(args.location[2])
pos.z = tonumber(args.location[3])
local teleport = dfhack.script_environment('teleport')
teleport.teleport(u, pos)
end
--[[if group_id then
local u = df.unit.find(unitId)
u.group_id = group_id
end--]]

@ -0,0 +1,99 @@
-- modtools/equip-item.lua
-- equip an item on a unit with a particular body part
local utils = require 'utils'
function equipItem(unit, item, bodyPart, mode)
--it is assumed that the item is on the ground
item.flags.on_ground = false
item.flags.in_inventory = true
local block = dfhack.maps.getTileBlock(item.pos)
local occupancy = block.occupancy[item.pos.x%16][item.pos.y%16]
for k,v in ipairs(block.items) do
--local blockItem = df.item.find(v)
if v == item.id then
block.items:erase(k)
break
end
end
local foundItem = false
for k,v in ipairs(block.items) do
local blockItem = df.item.find(v)
if blockItem.pos.x == item.pos.x and blockItem.pos.y == item.pos.y then
foundItem = true
break
end
end
if not foundItem then
occupancy.item = false
end
local inventoryItem = df.unit_inventory_item:new()
inventoryItem.item = item
inventoryItem.mode = mode
inventoryItem.body_part_id = bodyPart
unit.inventory:insert(#unit.inventory,inventoryItem)
end
validArgs = --[[validArgs or--]] utils.invert({
'help',
'unit',
'item',
'bodyPart',
'mode'
})
if moduleMode then
return
end
local args = utils.processArgs({...}, validArgs)
if args.help then
print(
[[scripts/modtools/equip-item.lua
arguments:
-help
print this help message
]])
return
end
local unitId = tonumber(args.unit) or ((args.unit == '\\LAST') and (df.global.unit_next_id-1))
local unit = df.unit.find(unitId)
if not unit then
error('invalid unit!', args.unit)
end
local itemId = tonumber(args.item) or ((args.item == '\\LAST') and (df.global.item_next_id-1))
local item = df.item.find(itemId)
if not item then
error('invalid item!', args.item)
end
local bodyPartName = args.bodyPart
local creature_raw = df.global.world.raws.creatures.all[unit.race]
local caste_raw = creature_raw.caste[unit.caste]
local body_info = caste_raw.body_info
local partId
local part
for k,v in ipairs(body_info.body_parts) do
if v.token == bodyPartName then
partId = k
part = v
break
end
end
if not part then
error('invalid body part name: ', bodyPartName)
end
local mode = args.mode
mode = df.unit_inventory_item.T_mode[mode]
equipItem(unit, item, partId, mode)

@ -3,7 +3,7 @@
-- author Putnam -- author Putnam
-- edited by expwnent -- edited by expwnent
local function teleport(unit,pos) function teleport(unit,pos)
local unitoccupancy = dfhack.maps.getTileBlock(unit.pos).occupancy[unit.pos.x%16][unit.pos.y%16] local unitoccupancy = dfhack.maps.getTileBlock(unit.pos).occupancy[unit.pos.x%16][unit.pos.y%16]
local newoccupancy = dfhack.maps.getTileBlock(pos).occupancy[pos.x%16][pos.y%16] local newoccupancy = dfhack.maps.getTileBlock(pos).occupancy[pos.x%16][pos.y%16]
if newoccupancy.unit then if newoccupancy.unit then
@ -26,6 +26,10 @@ validArgs = validArgs or utils.invert({
'showpos' 'showpos'
}) })
if moduleMode then
return
end
local args = utils.processArgs({...}, validArgs) local args = utils.processArgs({...}, validArgs)
if args.showunitid or args.showpos then if args.showunitid or args.showpos then