diff --git a/NEWS b/NEWS index 4e20592e2..dd2acc430 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,7 @@ DFHack Future Stopped DF window from receiving input when unfocused on OS X Fixed issues with keybindings involving Ctrl-A and Ctrl-Z, as well as Alt-E/U/N on OS X Multiple contexts can now be specified when adding keybindings + Keybindings can now use F10-F12 and 0-9 Plugin system is no longer restricted to plugins that exist on startup Lua Scripts can be enabled with the built-in enable/disable commands @@ -66,6 +67,7 @@ DFHack Future - widgets' positions, formats, etc. are now customizable (see Readme) - weather display now separated from the date display - New mouse cursor widget + dfstatus: Can enable/disable individual categories and customize metal bar list full-heal: "-r" option removes corpses gui/gm-editor - Pointers can now be displaced diff --git a/Readme.rst b/Readme.rst index d68755cb2..dea56d40e 100644 --- a/Readme.rst +++ b/Readme.rst @@ -197,7 +197,7 @@ To set keybindings, use the built-in ``keybinding`` command. Like any other command it can be used at any time from the console, but it is also meaningful in the DFHack init file. -Currently it supports any combination of Ctrl/Alt/Shift with F1-F9, or A-Z. +Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, or F1-F12 are supported. Possible ways to call the command: @@ -214,7 +214,7 @@ The ** parameter above has the following *case-sensitive* syntax:: [Ctrl-][Alt-][Shift-]KEY[@context[|context...]] -where the *KEY* part can be F1-F9 or A-Z, and [] denote optional parts. +where the *KEY* part can be any recognized key and [] denote optional parts. When multiple commands are bound to the same key combination, DFHack selects the first applicable one. Later 'add' commands, and earlier entries within one @@ -2365,6 +2365,7 @@ directory. * gui/dfstatus Show a quick overview of critical stock quantities, including food, drinks, wood, and various bars. + Sections can be enabled/disabled/configured by editing ``dfhack-config/dfstatus.lua``. * gui/stockpiles diff --git a/dfhack-config/dfstatus.lua b/dfhack-config/dfstatus.lua new file mode 100644 index 000000000..f1e087d2f --- /dev/null +++ b/dfhack-config/dfstatus.lua @@ -0,0 +1,28 @@ +-- dfstatus config +-- the dfstatus script can be found in hack/scripts/gui/ +--[[ +The following variables can be set to true/false to enable/disable categories (all true by default) +* drink +* wood +* fuel +* prepared_meals +* tanned_hides +* cloth +* metals + +Example: +drink = false +fuel = true + +To add metals: +* metal 'IRON' +* metals "GOLD" 'SILVER' +* metal('COPPER') +* metals("BRONZE", 'HORN_SILVER') +Use '-' for a blank line: +* metal '-' +]] + +metals 'IRON' 'PIG_IRON' 'STEEL' +metals '-' +metals 'GOLD' 'SILVER' 'COPPER' diff --git a/library/Core.cpp b/library/Core.cpp index 3cb86896a..854f5b0d8 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1397,6 +1397,8 @@ bool Core::Init() if (std::find(config_files.begin(), config_files.end(), filename) == config_files.end()) { std::string src_file = std::string("dfhack-config/default/") + filename; + if (!Filesystem::isfile(src_file)) + continue; std::string dest_file = std::string("dfhack-config/") + filename; std::ifstream src(src_file, std::ios::binary); std::ofstream dest(dest_file, std::ios::binary); @@ -2157,9 +2159,15 @@ static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') { *psym = SDL::K_a + (keyspec[0]-'A'); return true; + } else if (keyspec.size() == 1 && keyspec[0] >= '0' && keyspec[0] <= '9') { + *psym = SDL::K_0 + (keyspec[0]-'0'); + return true; } else if (keyspec.size() == 2 && keyspec[0] == 'F' && keyspec[1] >= '1' && keyspec[1] <= '9') { *psym = SDL::K_F1 + (keyspec[1]-'1'); return true; + } else if (keyspec.size() == 3 && keyspec.substr(0, 2) == "F1" && keyspec[2] >= '0' && keyspec[2] <= '2') { + *psym = SDL::K_F10 + (keyspec[2]-'0'); + return true; } else if (keyspec == "Enter") { *psym = SDL::K_RETURN; return true; diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 54655bf59..fc7ac9a5d 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -147,9 +147,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetItemList", GetItemList); svc->addFunction("GetBuildingDefList", GetBuildingDefList); svc->addFunction("GetWorldMap", GetWorldMap); - svc->addFunction("GetRegionMaps", GetRegionMaps); - svc->addFunction("GetCreatureRaws", GetCreatureRaws); - return svc; + svc->addFunction("GetRegionMaps", GetRegionMaps); + svc->addFunction("GetCreatureRaws", GetCreatureRaws); + return svc; } // This is called right before the plugin library is removed from memory. @@ -182,20 +182,20 @@ uint16_t fletcher16(uint8_t const *data, size_t bytes) void ConvertDfColor(int16_t index, RemoteFortressReader::ColorDefinition * out) { - if (!df::global::enabler) - return; + if (!df::global::enabler) + return; - auto enabler = df::global::enabler; + auto enabler = df::global::enabler; - out->set_red((int)(enabler->ccolor[index][0] * 255)); - out->set_green((int)(enabler->ccolor[index][1] * 255)); - out->set_blue((int)(enabler->ccolor[index][2] * 255)); + out->set_red((int)(enabler->ccolor[index][0] * 255)); + out->set_green((int)(enabler->ccolor[index][1] * 255)); + out->set_blue((int)(enabler->ccolor[index][2] * 255)); } void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) { - int index = in[0] + 8 * in[1]; - ConvertDfColor(index, out); + int index = in[0] + 8 * in[1]; + ConvertDfColor(index, out); } @@ -788,7 +788,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in int max_x = in->max_x(); int max_y = in->max_y(); //stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z()); - for (int zz = in->max_z()-1; zz >= in->min_z(); zz--) + for (int zz = in->max_z() - 1; zz >= in->min_z(); zz--) { // (di, dj) is a vector - direction in which we move right now int di = 1; @@ -959,13 +959,13 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, send_unit->set_pos_x(unit->pos.x); send_unit->set_pos_y(unit->pos.y); send_unit->set_pos_z(unit->pos.z); - send_unit->mutable_race()->set_mat_type(unit->race); - send_unit->mutable_race()->set_mat_index(unit->caste); - ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); - send_unit->set_flags1(unit->flags1.whole); - send_unit->set_flags2(unit->flags2.whole); - send_unit->set_flags3(unit->flags3.whole); - } + send_unit->mutable_race()->set_mat_type(unit->race); + send_unit->mutable_race()->set_mat_index(unit->caste); + ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); + send_unit->set_flags1(unit->flags1.whole); + send_unit->set_flags2(unit->flags2.whole); + send_unit->set_flags3(unit->flags3.whole); + } return CR_OK; } @@ -1236,7 +1236,7 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, out->set_name(Translation::TranslateName(&(data->name), false)); out->set_name_english(Translation::TranslateName(&(data->name), true)); for (int yy = 0; yy < height; yy++) - for (int xx = 0; xx < width; xx ++) + for (int xx = 0; xx < width; xx++) { df::region_map_entry * map_entry = &data->region_map[xx][yy]; out->add_elevation(map_entry->elevation); @@ -1384,58 +1384,58 @@ static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *i static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out) { - if (!df::global::world) - return CR_FAILURE; + if (!df::global::world) + return CR_FAILURE; - df::world * world = df::global::world; + df::world * world = df::global::world; - for (int i = 0; i < world->raws.creatures.all.size(); i++) - { - df::creature_raw * orig_creature = world->raws.creatures.all[i]; + for (int i = 0; i < world->raws.creatures.all.size(); i++) + { + df::creature_raw * orig_creature = world->raws.creatures.all[i]; - auto send_creature = out->add_creature_raws(); + auto send_creature = out->add_creature_raws(); - send_creature->set_index(i); - send_creature->set_creature_id(orig_creature->creature_id); - send_creature->add_name(orig_creature->name[0]); - send_creature->add_name(orig_creature->name[1]); - send_creature->add_name(orig_creature->name[2]); + send_creature->set_index(i); + send_creature->set_creature_id(orig_creature->creature_id); + send_creature->add_name(orig_creature->name[0]); + send_creature->add_name(orig_creature->name[1]); + send_creature->add_name(orig_creature->name[2]); - send_creature->add_general_baby_name(orig_creature->general_baby_name[0]); - send_creature->add_general_baby_name(orig_creature->general_baby_name[1]); + send_creature->add_general_baby_name(orig_creature->general_baby_name[0]); + send_creature->add_general_baby_name(orig_creature->general_baby_name[1]); - send_creature->add_general_child_name(orig_creature->general_child_name[0]); - send_creature->add_general_child_name(orig_creature->general_child_name[1]); + send_creature->add_general_child_name(orig_creature->general_child_name[0]); + send_creature->add_general_child_name(orig_creature->general_child_name[1]); - send_creature->set_creature_tile(orig_creature->creature_tile); - send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile); + send_creature->set_creature_tile(orig_creature->creature_tile); + send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile); - ConvertDfColor(orig_creature->color, send_creature->mutable_color()); + ConvertDfColor(orig_creature->color, send_creature->mutable_color()); - send_creature->set_adultsize(orig_creature->adultsize); + send_creature->set_adultsize(orig_creature->adultsize); - for (int j = 0; j < orig_creature->caste.size(); j++) - { - auto orig_caste = orig_creature->caste[j]; - if (!orig_caste) - continue; - auto send_caste = send_creature->add_caste(); + for (int j = 0; j < orig_creature->caste.size(); j++) + { + auto orig_caste = orig_creature->caste[j]; + if (!orig_caste) + continue; + auto send_caste = send_creature->add_caste(); - send_caste->set_index(j); + send_caste->set_index(j); - send_caste->set_caste_id(orig_caste->caste_id); + send_caste->set_caste_id(orig_caste->caste_id); - send_caste->add_caste_name(orig_caste->caste_name[0]); - send_caste->add_caste_name(orig_caste->caste_name[1]); - send_caste->add_caste_name(orig_caste->caste_name[2]); + send_caste->add_caste_name(orig_caste->caste_name[0]); + send_caste->add_caste_name(orig_caste->caste_name[1]); + send_caste->add_caste_name(orig_caste->caste_name[2]); - send_caste->add_baby_name(orig_caste->baby_name[0]); - send_caste->add_baby_name(orig_caste->baby_name[1]); + send_caste->add_baby_name(orig_caste->baby_name[0]); + send_caste->add_baby_name(orig_caste->baby_name[1]); - send_caste->add_child_name(orig_caste->child_name[0]); - send_caste->add_child_name(orig_caste->child_name[1]); - } - } + send_caste->add_child_name(orig_caste->child_name[0]); + send_caste->add_child_name(orig_caste->child_name[1]); + } + } - return CR_OK; + return CR_OK; } \ No newline at end of file diff --git a/scripts/gui/dfstatus.lua b/scripts/gui/dfstatus.lua index 8d7ceeb27..32758cfbf 100644 --- a/scripts/gui/dfstatus.lua +++ b/scripts/gui/dfstatus.lua @@ -1,8 +1,87 @@ --- dfstatus 1.5 - a quick access status screen. --- originally written by enjia2000@gmail.com +-- a quick access status screen +-- originally written by enjia2000@gmail.com (stolencatkarma) local gui = require 'gui' +function warn(msg) + dfhack.color(COLOR_LIGHTRED) + print(msg) + dfhack.color(nil) +end + +config = { + flags = { + drink = true, + wood = true, + fuel = true, + prepared_meals = true, + tanned_hides = true, + cloth = true, + metals = true, + }, + metal_ids = {}, +} + +function parse_config() + local metal_map = {} + for id, raw in pairs(df.global.world.raws.inorganics) do + if raw.material.flags.IS_METAL then + metal_map[raw.id:upper()] = id + metal_map[id] = raw.id:upper() + end + end + + local function add_metal(...) + for _, m in pairs({...}) do + id = metal_map[tostring(m):upper()] + if id ~= nil then + table.insert(config.metal_ids, id) + elseif m == '-' then + table.insert(config.metal_ids, '-') + else + warn('Invalid metal: ' .. tostring(m)) + end + end + return add_metal + end + + local env = {} + setmetatable(env, { + __index = function(_, k) + if k == 'metal' or k == 'metals' then + return add_metal + elseif k == 'flags' then + return config.flags + else + error('unknown name: ' .. k, 2) + end + end, + __newindex = function(_, k, v) + if config.flags[k] ~= nil then + if v ~= nil then + config.flags[k] = v + else + config.flags[k] = false + end + else + error('unknown flag: ' .. k, 2) + end + end, + }) + local f, err = loadfile('dfhack-config/dfstatus.lua', 't', env) + if not f then + qerror('error loading config: ' .. err) + end + local ok, err = pcall(f) + if not ok then + qerror('error parsing config: ' .. err) + end +end + +function getInorganicName(id) + return (df.inorganic_raw.find(id).material.state_name.Solid:gsub('^[a-z]', string.upper)) +end + dfstatus = defclass(dfstatus, gui.FramedScreen) dfstatus.ATTRS = { frame_style = gui.GREY_LINE_FRAME, @@ -13,95 +92,126 @@ dfstatus.ATTRS = { focus_path = 'dfstatus', } -function dfstatus:onRenderBody(dc) +function dfstatus:init() + self.text = {} + self.start = 1 + local function write(line) + table.insert(self.text, line) + -- ensure that the window is wide enough for this line plus a scroll arrow + if #line + 1 > self.frame_width then + self.frame_width = #line + 1 + end + end + local function newline() write('') end + local f = config.flags + local drink = 0 local wood = 0 - --local meat = 0 - --local raw_fish = 0 - --local plants = 0 - local prepared_meals = 0 - local fuel = 0 - local pigiron = 0 - local iron = 0 - local steel = 0 - - local silver = 0 - local copper = 0 - local gold = 0 - local tannedhides = 0 + local prepared_meals = 0 + local tanned_hides = 0 local cloth = 0 + local metals = {} + for _, id in pairs(config.metal_ids) do + metals[id] = 0 + end + for _, item in ipairs(df.global.world.items.all) do if not item.flags.rotten and not item.flags.dump and not item.flags.forbid then - if (item:getType() == df.item_type.WOOD) then wood = wood + item:getStackSize() - elseif (item:getType() == df.item_type.DRINK) then drink = drink + item:getStackSize() - elseif (item:getType() == df.item_type.SKIN_TANNED) then tannedhides = tannedhides + item:getStackSize() - elseif (item:getType() == df.item_type.CLOTH) then cloth = cloth + item:getStackSize() - --elseif (item:getType() == df.item_type.MEAT) then meat = meat + item:getStackSize() - --elseif (item:getType() == df.item_type.FISH_RAW) then raw_fish = raw_fish + item:getStackSize() - --elseif (item:getType() == df.item_type.PLANT) then plants = plants + item:getStackSize() - elseif (item:getType() == df.item_type.FOOD) then prepared_meals = prepared_meals + item:getStackSize() - elseif (item:getType() == df.item_type.BAR) then - for token in string.gmatch(dfhack.items.getDescription(item,0),"[^%s]+") do - if (token == "silver") then silver = silver + item:getStackSize() - elseif (token == "charcoal" or token == "coke") then fuel = fuel + item:getStackSize() - elseif (token == "iron") then iron = iron + item:getStackSize() - elseif (token == "pig") then pigiron = pigiron + item:getStackSize() - elseif (token == "copper") then copper = copper + item:getStackSize() - elseif (token == "gold") then gold = gold + item:getStackSize() - elseif (token == "steel") then steel = steel + item:getStackSize() + if item:getType() == df.item_type.WOOD then + wood = wood + item:getStackSize() + elseif item:getType() == df.item_type.DRINK then + drink = drink + item:getStackSize() + elseif item:getType() == df.item_type.SKIN_TANNED then + tanned_hides = tanned_hides + item:getStackSize() + elseif item:getType() == df.item_type.CLOTH then + cloth = cloth + item:getStackSize() + elseif item:getType() == df.item_type.FOOD then + prepared_meals = prepared_meals + item:getStackSize() + elseif item:getType() == df.item_type.BAR then + if item:getMaterial() == df.builtin_mats.COAL then + fuel = fuel + item:getStackSize() + elseif item:getMaterial() == df.builtin_mats.INORGANIC then + local mat_idx = item:getMaterialIndex() + if metals[mat_idx] ~= nil then + metals[mat_idx] = metals[mat_idx] + item:getStackSize() end - break -- only need to look at the 1st token of each item. end end end end + if f.drink then + write("Drinks: " .. drink) + end + if f.prepared_meals then + write("Meals: " .. prepared_meals) + end + if f.drink or f.prepared_meals then + newline() + end + if f.wood then + write("Wood: " .. wood) + end + if f.fuel then + write("Fuel: " .. fuel) + end + if f.wood or f.fuel then + newline() + end + if f.tanned_hides then + write("Hides: " .. tanned_hides) + end + if f.cloth then + write("Cloth: " .. cloth) + end + if f.tanned_hides or f.cloth then + newline() + end + if f.metals then + write("Metal bars:") + for _, id in pairs(config.metal_ids) do + if id == '-' then + newline() + else + write(' ' .. ('%-10s'):format(getInorganicName(id) .. ': ') .. metals[id]) + end + end + end + self.start_min = 1 + self.start_max = #self.text - self.frame_height + 1 +end + +function dfstatus:onRenderBody(dc) dc:pen(COLOR_LIGHTGREEN) - dc:string("Drinks: " .. drink) - dc:newline(0) - dc:string("Meals: " .. prepared_meals) - dc:newline(0) - dc:newline(0) - dc:string("Wood: " .. wood) - dc:newline(0) - dc:newline(0) - dc:string("Hides: " .. tannedhides) - dc:newline(0) - dc:string("Cloth: " .. cloth) - dc:newline(0) - -- dc:string("Raw Fish: ".. raw_fish) - -- dc:newline(0) - -- dc:string("Plants: ".. plants) - -- dc:newline(0) - dc:newline(0) - dc:string("Bars:") - dc:newline(1) - dc:string("Fuel: " .. fuel) - dc:newline(1) - dc:string("Pig Iron: " .. pigiron) - dc:newline(1) - dc:string("Steel: " .. steel) - dc:newline(1) - dc:string("Iron: " .. iron) - dc:newline(1) - dc:newline(1) - dc:string("Copper: " .. copper) - dc:newline(1) - dc:string("Silver: " .. silver) - dc:newline(1) - dc:string("Gold: " .. gold) + for id, line in pairs(self.text) do + if id >= self.start then + dc:string(line):newline() + end + end + dc:pen(COLOR_LIGHTCYAN) + if self.start > self.start_min then + dc:seek(self.frame_width - 1, 0):char(24) + end + if self.start < self.start_max then + dc:seek(self.frame_width - 1, self.frame_height - 1):char(25) + end end function dfstatus:onInput(keys) if keys.LEAVESCREEN or keys.SELECT then self:dismiss() scr = nil + elseif keys.STANDARDSCROLL_UP then + self.start = math.max(self.start - 1, self.start_min) + elseif keys.STANDARDSCROLL_DOWN then + self.start = math.min(self.start + 1, self.start_max) end end if not scr then + parse_config() scr = dfstatus() scr:show() else diff --git a/travis/lint.py b/travis/lint.py index ac0f76d2f..ce28f4be3 100644 --- a/travis/lint.py +++ b/travis/lint.py @@ -85,7 +85,7 @@ class TabLinter(Linter): def fix_line(self, line): return line.replace('\t', ' ') -linters = [NewlineLinter(), TrailingWhitespaceLinter(), TabLinter()] +linters = [cls() for cls in Linter.__subclasses__()] def main(): root_path = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else '.')