Merge remote-tracking branch 'warmist/dev-modmanager' into develop

develop
Alexander Gavrilov 2014-03-24 20:07:02 +04:00
commit ec14b2d1e9
5 changed files with 334 additions and 1 deletions

@ -1768,6 +1768,10 @@ and are only documented here for completeness:
The oldval, newval or delta arguments may be used to specify additional constraints. The oldval, newval or delta arguments may be used to specify additional constraints.
Returns: *found_index*, or *nil* if end reached. Returns: *found_index*, or *nil* if end reached.
* ``dfhack.internal.getDir(path)``
List files in a directory.
Returns: *file_names* or empty table if not found.
Core interpreter context Core interpreter context
======================== ========================

@ -2,6 +2,9 @@ DFHack future
Internals: Internals:
- support for calling a lua function via a protobuf request (demonstrated by dfhack-run --lua). - support for calling a lua function via a protobuf request (demonstrated by dfhack-run --lua).
- Lua API for listing files in directory. Needed for mod-manager.
New scripts:
- gui/mod-manager: allows installing/uninstalling mods into df from df/mods directory.
New commands: New commands:
- move the 'grow', 'extirpate' and 'immolate' commands as 'plant' subcommands - move the 'grow', 'extirpate' and 'immolate' commands as 'plant' subcommands

@ -2382,6 +2382,11 @@ dfhack commands. Useful for hotkeys.
Example:: Example::
multicmd locate-ore iron ; digv multicmd locate-ore iron ; digv
mod-manager
===========
This mod script allows installing/removing mod that change/add multiple
files in df with one click of a button.
======================= =======================
In-game interface tools In-game interface tools
======================= =======================

@ -2202,7 +2202,21 @@ static int internal_diffscan(lua_State *L)
lua_pushnil(L); lua_pushnil(L);
return 1; return 1;
} }
static int internal_getDir(lua_State *L)
{
luaL_checktype(L,1,LUA_TSTRING);
std::string dir=lua_tostring(L,1);
std::vector<std::string> files;
DFHack::getdir(dir,files);
lua_newtable(L);
for(int i=0;i<files.size();i++)
{
lua_pushinteger(L,i+1);
lua_pushstring(L,files[i].c_str());
lua_settable(L,-3);
}
return 1;
}
static const luaL_Reg dfhack_internal_funcs[] = { static const luaL_Reg dfhack_internal_funcs[] = {
{ "getAddress", internal_getAddress }, { "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress }, { "setAddress", internal_setAddress },
@ -2215,6 +2229,7 @@ static const luaL_Reg dfhack_internal_funcs[] = {
{ "memcmp", internal_memcmp }, { "memcmp", internal_memcmp },
{ "memscan", internal_memscan }, { "memscan", internal_memscan },
{ "diffscan", internal_diffscan }, { "diffscan", internal_diffscan },
{ "getDir", internal_getDir },
{ NULL, NULL } { NULL, NULL }
}; };

@ -0,0 +1,306 @@
local gui=require 'gui'
local widgets=require 'gui.widgets'
local entity_file=dfhack.getDFPath().."/raw/objects/entity_default.txt"
local init_file=dfhack.getDFPath().."/raw/init.lua"
function fileExists(filename)
local file=io.open(filename,"rb")
if file==nil then
return
else
file:close()
end
end
function copyFile(from,to) --oh so primitive
local filefrom=io.open(from,"rb")
local fileto=io.open(to,"w+b")
local buf=filefrom:read("*a")
printall(buf)
fileto:write(buf)
filefrom:close()
fileto:close()
end
function patchInit(initFileName,patch_guard,code)
local initFile=io.open(initFileName,"a")
initFile:write(string.format("\n%s\n%s\n%s",patch_guard[1],
code,patch_guard[2]))
initFile:close()
end
function patchFile(file_name,patch_guard,after_string,code)
local input_lines=patch_guard[1].."\n"..code.."\n"..patch_guard[2]
local badchars="[%:%[%]]"
local find_string=after_string:gsub(badchars,"%%%1") --escape some bad chars
local entityFile=io.open(file_name,"r")
local buf=entityFile:read("*all")
entityFile:close()
local entityFile=io.open(file_name,"w+")
buf=string.gsub(buf,find_string,after_string.."\n"..input_lines)
entityFile:write(buf)
entityFile:close()
end
function findGuards(str,start,patch_guard)
local pStart=string.find(str,patch_guard[1],start)
if pStart==nil then return nil end
local pEnd=string.find(str,patch_guard[2],pStart)
if pEnd==nil then error("Start guard token found, but end was not found") end
return pStart-1,pEnd+#patch_guard[2]+1
end
function findGuardsFile(filename,patch_guard)
local file=io.open(filename,"r")
local buf=file:read("*all")
return findGuards(buf,1,patch_guard)
end
function unPatchFile(filename,patch_guard)
local file=io.open(filename,"r")
local buf=file:read("*all")
file:close()
local newBuf=""
local pos=1
local lastPos=1
repeat
local endPos
pos,endPos=findGuards(buf,lastPos,patch_guard)
newBuf=newBuf..string.sub(buf,lastPos,pos)
if endPos~=nil then
lastPos=endPos
end
until pos==nil
local file=io.open(filename,"w+")
file:write(newBuf)
file:close()
end
function checkInstalled(dfMod) --try to figure out if installed
if dfMod.checkInstalled then
return dfMod.checkInstalled()
else
if dfMod.raws_list then
for k,v in pairs(dfMod.raws_list) do
if fileExists(dfhack.getDFPath().."/raw/objects/"..v) then
return true,v
end
end
end
if dfMod.patch_entity then
if findGuardsFile(entity_file,dfMod.guard)~=nil then
return true,"entity_default.txt"
end
end
if dfMod.patch_files then
for k,v in pairs(dfMod.patch_files) do
if findGuardsFile(dfhack.getDFPath().."/raw/objects/"..v.filename,dfMod.guard)~=nil then
return true,"v.filename"
end
end
end
if dfMod.patch_init then
if findGuardsFile(init_file,dfMod.guard_init)~=nil then
return true,"init.lua"
end
end
end
end
manager=defclass(manager,gui.FramedScreen)
function manager:init(args)
self.mods={}
local mods=self.mods
local mlist=dfhack.internal.getDir("mods")
for k,v in ipairs(mlist) do
if v~="." and v~=".." then
local f,modData=pcall(dofile,"mods/".. v .. "/init.lua")
if f then
mods[modData.name]=modData
modData.guard=modData.guard or {">>"..modData.name.." patch","<<End "..modData.name.." patch"}
modData.guard_init={"--"..modData.guard[1],"--"..modData.guard[2]}
modData.path=dfhack.getDFPath()..'/mods/'..v..'/'
end
end
end
---show thingy
local modList={}
for k,v in pairs(self.mods) do
table.insert(modList,{text=k,data=v})
end
self:addviews{
widgets.Panel{subviews={
widgets.Label{
text="Info:",
frame={t=1,l=1}
},
widgets.Label{
text="<no-info>",
--text={text=self:callback("formDescription")},
view_id='info',
frame={t=2,l=1},
},
widgets.Label{
text={"Author:",{text=self:callback("formAuthor")}},
view_id='author',
frame={b=5,l=1}
},
widgets.Label{
text={
{text="Install",key="CUSTOM_I",key_sep="()",disabled=self:callback("curModInstalled"),
on_activate=self:callback("installCurrent")},NEWLINE,
{text="Uninstall",key="CUSTOM_U",key_sep="()",enabled=self:callback("curModInstalled"),
on_activate=self:callback("uninstallCurrent")},NEWLINE,
{text="Settings",key="CUSTOM_S",key_sep="()",enabled=self:callback("hasSettings")},NEWLINE,
{text="Exit",key="LEAVESCREEN",key_sep="()",},NEWLINE
},
frame={l=1,b=0}
},
},
frame={l=21,t=1,b=1}
},
widgets.Panel{subviews={
widgets.Label{
text="Mods:",
frame={t=1,l=1}
},
widgets.List{
choices=modList,
frame={t=2,l=1},
on_select=self:callback("selectMod")
},
},
frame={w=20,t=1,l=1,b=1}
},
}
self:updateState()
end
function manager:postinit(args)
self:selectMod(1,{data=self.selected})-- workaround for first call, now the subviews are constructed
end
function manager:curModInstalled()
return self.selected.installed
end
function manager:hasSettings()
return self.selected.settings -- somehow add the entity selection as a default, if it mods entities
end
function manager:formDescription()
local ret={}
if self.selected.description then
return self.selected.description
--[[
local str=require('utils').split_string(self.selected.description,"\n")
for _,s in ipairs(str) do
table.insert(ret,{text=s})
table.insert(ret,NEWLINE)
end
return ret]]
else
return "<no-info>"
end
end
function manager:formAuthor()
return self.selected.author or "<no-info>"
end
function manager:selectMod(idx,choice)
self.selected=choice.data
if self.subviews.info then
self.subviews.info:setText(self:formDescription())
self:updateLayout()
end
end
function manager:updateState()
for k,v in pairs(self.mods) do
v.installed=checkInstalled(v)
end
end
function manager:installCurrent()
self:install(self.selected)
end
function manager:uninstallCurrent()
self:uninstall(self.selected)
end
function manager:install(trgMod,force)
if trgMod==nil then
qerror 'Mod does not exist'
end
if not force then
local isInstalled,file=checkInstalled(trgMod) -- maybe load from .installed?
if isInstalled then
qerror("Mod already installed. File:"..file)
end
end
print("installing:"..trgMod.name)
if trgMod.pre_install then
trgMod.pre_install(args)
end
if trgMod.raws_list then
for k,v in pairs(trgMod.raws_list) do
copyFile(trgMod.path..v,dfhack.getDFPath().."/raw/objects/"..v)
end
end
if trgMod.patch_entity then
local entity_target="[ENTITY:MOUNTAIN]" --TODO configure
patchFile(entity_file,trgMod.guard,entity_target,trgMod.patch_entity)
end
if trgMod.patch_files then
for k,v in pairs(trgMod.patch_files) do
patchFile(dfhack.getDFPath().."/raw/objects/"..v.filename,trgMod.guard,v.after,v.patch)
end
end
if trgMod.patch_init then
patchInit(init_file,trgMod.guard_init,trgMod.patch_init)
end
trgMod.installed=true
if trgMod.post_install then
trgMod.post_install(self)
end
print("done")
end
function manager:uninstall(trgMod)
print("Uninstalling:"..trgMod.name)
if trgMod.pre_uninstall then
trgMod.pre_uninstall(args)
end
if trgMod.raws_list then
for k,v in pairs(trgMod.raws_list) do
os.remove(dfhack.getDFPath().."/raw/objects/"..v)
end
end
if trgMod.patch_entity then
unPatchFile(entity_file,trgMod.guard)
end
if trgMod.patch_files then
for k,v in pairs(trgMod.patch_files) do
unPatchFile(dfhack.getDFPath().."/raw/objects/"..v.filename,trgMod.guard)
end
end
if trgMod.patch_init then
unPatchFile(init_file,trgMod.guard_init)
end
trgMod.installed=false
if trgMod.post_uninstall then
trgMod.post_uninstall(args)
end
print("done")
end
function manager:onInput(keys)
if keys.LEAVESCREEN then
self:dismiss()
else
self:inputToSubviews(keys)
end
end
if dfhack.gui.getCurFocus()~='title' then
qerror("Can only be used in title screen")
end
local m=manager{}
m:show()