diff --git a/.gitignore b/.gitignore index 0a614b082..db4ed747c 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,9 @@ build/CPack*Config.cmake *.swp .vimrc +# VSCode files +.vscode + # ctags file tags diff --git a/docs/Authors.rst b/docs/Authors.rst index e5381bcce..1bfcf051b 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -81,6 +81,7 @@ Mason11987 Mason11987 Matt Regul mattregul Matthew Cline Matthew Lindner mlindner +Matthew Taylor ymber yutna Max maxthyme Max^TM McArcady McArcady melkor217 melkor217 @@ -150,6 +151,7 @@ thefriendlyhacker thefriendlyhacker TheHologram TheHologram therahedwig therahedwig ThiagoLira ThiagoLira +thurin thurin Tim Walberg twalberg Timothy Collett danaris Timur Kelman TymurGubayev diff --git a/docs/changelog.txt b/docs/changelog.txt index 768268388..34f5c3660 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -39,6 +39,12 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future +## Fixes +- `tweak` embark-profile-name: fixed handling of the native shift+space key + +## Lua +- ``pairs()`` now returns available class methods for DF types + # 0.47.04-beta1 ## Fixes diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 3b8471d27..497addd5c 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1608,8 +1608,8 @@ static void RenderType(lua_State *state, compound_identity *node) lua_dup(state); lua_setfield(state, ix_meta, "__index"); - // pairs table - lua_newtable(state); + // pairs table - reuse index table + lua_dup(state); int ptable = base+4; lua_pushvalue(state, ptable); diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index a6639f7b1..fd70b7ba8 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -68,6 +68,8 @@ namespace DFHack IDTYPE_UNION }; + // pointer flags (bitfield), stored in the count field of struct_field_info + // if mode is POINTER. enum pointer_identity_flags { PTRFLAG_IS_ARRAY = 1, PTRFLAG_HAS_BAD_POINTERS = 2, @@ -164,7 +166,12 @@ namespace DFHack // Bitfields struct bitfield_item_info { + // the name of the field, or null if the field is unnamed const char *name; + // size is positive for defined fields, zero for bits past the end + // of the field, and negative for padding on multi-bit fields + // + // ex. if bits[2].size is -2, then bits[0].size is at least 3 int size; }; diff --git a/library/xml b/library/xml index d51a1c4ca..b585e182c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d51a1c4ca36731db62517b699fddd2c011e6bc99 +Subproject commit b585e182c404a14130d42bf87a60f94aebbfd1fd diff --git a/plugins/tweak/tweaks/embark-profile-name.h b/plugins/tweak/tweaks/embark-profile-name.h index aa151588e..28ca73ded 100644 --- a/plugins/tweak/tweaks/embark-profile-name.h +++ b/plugins/tweak/tweaks/embark-profile-name.h @@ -1,22 +1,24 @@ #include "df/viewscreen_setupdwarfgamest.h" -using namespace DFHack; + struct embark_profile_name_hook : df::viewscreen_setupdwarfgamest { typedef df::viewscreen_setupdwarfgamest interpose_base; - DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) - { + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) { int ch = -1; - for (auto it = input->begin(); ch == -1 && it != input->end(); ++it) + for (auto it = input->begin(); ch == -1 && it != input->end(); ++it) { ch = Screen::keyToChar(*it); - if (in_save_profile && ch >= 32 && ch <= 126) - { - profile_name.push_back((char)ch); } - else - { - if (input->count(df::interface_key::LEAVESCREEN)) + // Intercept all printable characters except space. + // If space is intercepted the shift-space abort key will not work. + if (in_save_profile && ch >= 33 && ch <= 126) { + profile_name.push_back((char)ch); + } else { + if (input->count(df::interface_key::LEAVESCREEN)) { input->insert(df::interface_key::SETUPGAME_SAVE_PROFILE_ABORT); + } INTERPOSE_NEXT(feed)(input); } } }; + IMPLEMENT_VMETHOD_INTERPOSE(embark_profile_name_hook, feed); diff --git a/scripts b/scripts index e336520c0..8618cd0b0 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e336520c09dbcd55bb9f7b3ca837d102a9026acf +Subproject commit 8618cd0b0a17935fe07f329b249726cda61f5bdf diff --git a/test/library/utils.lua b/test/library/utils.lua index 309736892..f39be3459 100644 --- a/test/library/utils.lua +++ b/test/library/utils.lua @@ -13,3 +13,30 @@ function test.OrderedTable() i = i + 1 end end + +function test.invert() + local t = {} + local i = utils.invert{'a', 4.4, false, true, 5, t} + expect.eq(i.a, 1) + expect.eq(i[4.4], 2) + expect.eq(i[false], 3) + expect.eq(i[true], 4) + expect.eq(i[5], 5) + expect.eq(i[t], 6) + expect.eq(i[700], nil) + expect.eq(i.foo, nil) + expect.eq(i[{}], nil) +end + +function test.invert_nil() + local i = utils.invert{'a', nil, 'b'} + expect.eq(i.a, 1) + expect.eq(i[nil], nil) + expect.eq(i.b, 3) +end + +function test.invert_overwrite() + local i = utils.invert{'a', 'b', 'a'} + expect.eq(i.b, 2) + expect.eq(i.a, 3) +end diff --git a/test/main.lua b/test/main.lua index 143b9cadc..46a91e1ca 100644 --- a/test/main.lua +++ b/test/main.lua @@ -13,14 +13,20 @@ local TestStatus = { FAILED = 'failed', } -local VALID_MODES = utils.invert{'none', 'fortress'} +local VALID_MODES = utils.invert{'none', 'title', 'fortress'} expect = {} function expect.true_(value, comment) - return not not value, comment, 'expected true' + return not not value, comment, 'expected true, got ' .. tostring(value) end function expect.false_(value, comment) - return not value, comment, 'expected false' + return not value, comment, 'expected false, got ' .. tostring(value) +end +function expect.fail(comment) + return false, comment or 'check failed, no reason provided' +end +function expect.nil_(value, comment) + return value == nil, comment, 'expected nil, got ' .. tostring(value) end function expect.eq(a, b, comment) return a == b, comment, ('%s ~= %s'):format(a, b) @@ -63,12 +69,53 @@ function expect.error(func, ...) return true end end +function expect.pairs_contains(table, key, comment) + for k, v in pairs(table) do + if k == key then + return true + end + end + return false, comment, ('could not find key "%s" in table'):format(key) +end +function expect.not_pairs_contains(table, key, comment) + for k, v in pairs(table) do + if k == key then + return false, comment, ('found key "%s" in table'):format(key) + end + end + return true +end function delay(frames) frames = frames or 1 script.sleep(frames, 'frames') end +function ensure_title_screen() + if df.viewscreen_titlest:is_instance(dfhack.gui.getCurViewscreen()) then + return + end + print('Looking for title screen...') + for i = 0, 100 do + local scr = dfhack.gui.getCurViewscreen() + if df.viewscreen_titlest:is_instance(scr) then + print('Found title screen') + break + else + scr:feed_key(df.interface_key.LEAVESCREEN) + delay(10) + end + end + if not df.viewscreen_titlest:is_instance(dfhack.gui.getCurViewscreen()) then + error('Could not find title screen') + end +end + +local MODE_NAVIGATE_FNS = { + none = function() end, + title = ensure_title_screen, +} + function load_test_config(config_file) local config = {} if dfhack.filesystem.isfile(config_file) then @@ -166,7 +213,7 @@ function load_tests(file, tests) return false else if not VALID_MODES[env.config.mode] then - dfhack.printerr('Invalid config.mode: ' .. env.config.mode) + dfhack.printerr('Invalid config.mode: ' .. tostring(env.config.mode)) return false end for name, test_func in pairs(env.test) do @@ -184,6 +231,18 @@ function load_tests(file, tests) return true end +function sort_tests(tests) + -- to make sort stable + local test_index = utils.invert(tests) + table.sort(tests, function(a, b) + if a.config.mode ~= b.config.mode then + return VALID_MODES[a.config.mode] < VALID_MODES[b.config.mode] + else + return test_index[a] < test_index[b] + end + end) +end + function run_test(test, status, counts) test.private.checks = 0 test.private.checks_ok = 0 @@ -217,20 +276,7 @@ function main() } local passed = true - print('Looking for title screen...') - for i = 0, 100 do - local scr = dfhack.gui.getCurViewscreen() - if df.viewscreen_titlest:is_instance(scr) then - print('Found title screen') - break - else - scr:feed_key(df.interface_key.LEAVESCREEN) - delay(10) - end - end - if not df.viewscreen_titlest:is_instance(dfhack.gui.getCurViewscreen()) then - qerror('Could not find title screen') - end + ensure_title_screen() print('Loading tests') local tests = {} @@ -263,9 +309,11 @@ function main() table.remove(tests, i) end end + sort_tests(tests) print('Running ' .. #tests .. ' tests') for _, test in pairs(tests) do + MODE_NAVIGATE_FNS[test.config.mode]() local passed = run_test(test, status, counts) status[test.full_name] = passed and TestStatus.PASSED or TestStatus.FAILED save_test_status(status) diff --git a/test/structures/find.lua b/test/structures/find.lua new file mode 100644 index 000000000..57a659afa --- /dev/null +++ b/test/structures/find.lua @@ -0,0 +1,32 @@ +config = { + mode = 'title', +} + +local function clean_vec(vec) + while #vec > 0 do + if vec[0] then + expect.true_(vec[0]:delete()) + end + vec:erase(0) + end +end + +local function with_clean_vec(cls, callback) + local vec = cls.get_vector() + dfhack.call_with_finalizer(1, true, clean_vec, vec, callback, vec) +end + +function test.image_set() + -- todo: expand to other types? + with_clean_vec(df.image_set, function(vec) + vec:insert('#', {new = df.image_set, id = 1}) + vec:insert('#', {new = df.image_set, id = 2}) + vec:insert('#', {new = df.image_set, id = 4}) + expect.eq(df.image_set.find(1).id, 1) + expect.eq(df.image_set.find(2).id, 2) + expect.eq(df.image_set.find(4).id, 4) + expect.nil_(df.image_set.find(3)) + expect.nil_(df.image_set.find(5)) + expect.nil_(df.image_set.find(-1)) + end) +end diff --git a/test/structures/types_meta.lua b/test/structures/types_meta.lua new file mode 100644 index 000000000..212e9c7ed --- /dev/null +++ b/test/structures/types_meta.lua @@ -0,0 +1,65 @@ +function test.struct() + expect.eq(df.coord._kind, 'struct-type') + expect.eq(tostring(df.coord), '') + expect.eq(getmetatable(df.coord), 'coord') + expect.pairs_contains(df.coord, 'new') + expect.pairs_contains(df.coord, 'is_instance') + expect.pairs_contains(df.coord, 'sizeof') +end + +function test.class() + expect.eq(df.viewscreen._kind, 'class-type') + expect.eq(tostring(df.viewscreen), '') + expect.eq(getmetatable(df.viewscreen), 'viewscreen') + expect.pairs_contains(df.viewscreen, 'new') + expect.pairs_contains(df.viewscreen, 'is_instance') + expect.pairs_contains(df.viewscreen, 'sizeof') +end + +function test.enum() + expect.eq(df.interface_key._kind, 'enum-type') + expect.eq(tostring(df.interface_key), '') + expect.eq(getmetatable(df.interface_key), 'interface_key') + expect.pairs_contains(df.interface_key, 'new') + expect.pairs_contains(df.interface_key, 'is_instance') + expect.pairs_contains(df.interface_key, 'sizeof') +end + +function test.bitfield() + expect.eq(df.item_flags._kind, 'bitfield-type') + expect.eq(tostring(df.item_flags), '') + expect.eq(getmetatable(df.item_flags), 'item_flags') + expect.pairs_contains(df.item_flags, 'new') + expect.pairs_contains(df.item_flags, 'is_instance') + expect.pairs_contains(df.item_flags, 'sizeof') +end + +function test.global() + expect.eq(df.global._kind, 'global') + expect.eq(tostring(df.global), '') + expect.eq(getmetatable(df.global), 'global') + expect.not_pairs_contains(df.global, 'new') + expect.not_pairs_contains(df.global, 'is_instance') + expect.not_pairs_contains(df.global, 'sizeof') +end + +function test.unit() + expect.pairs_contains(df.unit, 'new') + expect.pairs_contains(df.unit, 'is_instance') + expect.pairs_contains(df.unit, 'sizeof') + expect.pairs_contains(df.unit, 'find') + expect.pairs_contains(df.unit, 'get_vector') + expect.pairs_contains(df.unit, '_kind') + + if df.unit.T_job then + expect.pairs_contains(df.unit, 'T_job') + expect.pairs_contains(df.unit.T_job, 'new') + expect.pairs_contains(df.unit.T_job, 'is_instance') + expect.pairs_contains(df.unit.T_job, 'sizeof') + expect.not_pairs_contains(df.unit.T_job, 'find') + expect.not_pairs_contains(df.unit.T_job, 'get_vector') + expect.pairs_contains(df.unit.T_job, '_kind') + else + expect.fail('unit.T_job not defined; unit has changed') + end +end