diff --git a/.github/workflows/generate-symbols.yml b/.github/workflows/generate-symbols.yml index 6ecfa04ff8..f73d33dca4 100644 --- a/.github/workflows/generate-symbols.yml +++ b/.github/workflows/generate-symbols.yml @@ -42,7 +42,6 @@ on: steam_branch: description: DFHack Steam branch to deploy to (leave blank to skip deploy) type: string - default: staging jobs: package: @@ -113,8 +112,6 @@ jobs: else BETA_PARAMS="-beta ${{ inputs.df_steam_branch }}" fi - touch $HOME/Steam/logs/stderr.txt - tail -F $HOME/Steam/logs/stderr.txt & ${{ steps.steamcmd.outputs.executable }} \ +@ShutdownOnFailedCommand 1 \ +force_install_dir $PWD/DF_steam \ @@ -223,8 +220,6 @@ jobs: else BETA_PARAMS="-beta ${{ inputs.df_steam_branch }}" fi - touch $HOME/Steam/logs/stderr.txt - tail -F $HOME/Steam/logs/stderr.txt & ${{ steps.steamcmd.outputs.executable }} \ +@ShutdownOnFailedCommand 1 \ +@sSteamCmdForcePlatformType windows \ diff --git a/.github/workflows/watch-df-steam.yml b/.github/workflows/watch-df-steam.yml index afd6221111..12d428f1e7 100644 --- a/.github/workflows/watch-df-steam.yml +++ b/.github/workflows/watch-df-steam.yml @@ -17,6 +17,11 @@ jobs: version: 51.01-beta df_steam_branch: beta steam_branch: adventure-beta + - dfhack_ref: testing + structures_ref: testing + version: testing + df_steam_branch: testing + steam_branch: '' steps: - name: Setup steamcmd id: steamcmd @@ -30,34 +35,54 @@ jobs: path: state key: watchstate-${{ matrix.version }}-${{ env.TIMESTAMP_SECONDS }} restore-keys: watchstate-${{ matrix.version }} - - name: Detect changes on branch - shell: bash + - name: Prep Steam auth env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }} STEAM_CONFIG_VDF: ${{ secrets.STEAM_CONFIG_VDF }} run: | mkdir -p $HOME/Steam/config echo "$STEAM_CONFIG_VDF" | base64 -d >$HOME/Steam/config/config.vdf - touch $HOME/Steam/logs/stderr.txt - tail -F $HOME/Steam/logs/stderr.txt & - timestamp=$(${{ steps.steamcmd.outputs.executable }} \ - +@ShutdownOnFailedCommand 1 \ - +login $STEAM_USERNAME \ - +app_info_request 975370 \ - +app_info_print 975370 \ - +quit | awk '/^{/,0' | awk '/^\t\t"branches"/,0' | awk '/^\t\t\t"beta"/,0' | fgrep timeupdated | head -n1 | cut -d'"' -f4) - echo "timestamp of last branch update: $timestamp" - mkdir -p state - touch state/timestamp - last_timestamp=$(cat state/timestamp) - echo "stored timestamp of last branch update: $last_timestamp" - if [ "$timestamp" != "$last_timestamp" ]; then - echo "launching generate-symbols" - echo "$timestamp" >state/timestamp - gh workflow run generate-symbols.yml -R DFHack/dfhack -r ${{ matrix.dfhack_ref }} -f structures_ref=${{ matrix.structures_ref }} -f version=${{ matrix.version }} -f platform=all -f channel=steam -f df_steam_branch=${{ matrix.df_steam_branch }} -f steam_branch=${{ matrix.steam_branch }} - echo TIMESTAMP_UPDATED=1 >> $GITHUB_ENV - fi + - name: Compare branch metadata + uses: nick-fields/retry@v3 + env: + STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }} + with: + timeout_minutes: 2 + command: | + timestamp=$(${{ steps.steamcmd.outputs.executable }} \ + +@ShutdownOnFailedCommand 1 \ + +login $STEAM_USERNAME \ + +app_info_request 975370 \ + +app_info_print 975370 \ + +quit | \ + awk '/^{/,0' | \ + awk '/^\t\t"branches"/,0' | \ + awk '/^\t\t\t"beta"/,0' | \ + fgrep timeupdated | \ + head -n1 | \ + cut -d'"' -f4) + echo "timestamp of last branch update: $timestamp" + mkdir -p state + touch state/timestamp + last_timestamp=$(cat state/timestamp) + echo "stored timestamp of last branch update: $last_timestamp" + if [ "$timestamp" != "$last_timestamp" ]; then + echo "$timestamp" >state/timestamp + echo TIMESTAMP_UPDATED=1 >> $GITHUB_ENV + fi + - name: Launch symbol generation workflow + if: env.TIMESTAMP_UPDATED + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh workflow run generate-symbols.yml \ + -R DFHack/dfhack \ + -r ${{ matrix.dfhack_ref }} \ + -f structures_ref=${{ matrix.structures_ref }} \ + -f version=${{ matrix.version }} \ + -f platform=all \ + -f channel=steam \ + -f df_steam_branch=${{ matrix.df_steam_branch }} \ + -f steam_branch=${{ matrix.steam_branch }} - name: Save state uses: actions/cache/save@v4 if: env.TIMESTAMP_UPDATED diff --git a/docs/changelog.txt b/docs/changelog.txt index 0ee1e61977..21e95d62fd 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -57,6 +57,8 @@ Template for new versions: ## Fixes - Fixed misidentification of visitors from your own civ as residents; affects all tools that iterate through citizens/residents +- `cursecheck`: don't try to rely on cursor; check for selected unit instead +- Fixed incorrect DFHack background window texture when DF is started in ascii mode and subsequently switched to graphics mode ## Misc Improvements - `suspendmanager`: Account for walls planned on the z-layer below when determining accessibility to a job diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index cc6afe21ec..29fe55120c 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -284,6 +284,10 @@ Numerical indices correspond to the shift value, and if a subfield occupies multiple bits, the ``ipairs`` order would have a gap. +Additionally, bitfields have a ``whole`` property, +which returns the value of the bitfield as an +integer. + Since currently there is no API to allocate a bitfield object fully in GC-managed lua heap, consider using the lua table assignment feature outlined below in order to diff --git a/docs/plugins/cursecheck.rst b/docs/plugins/cursecheck.rst index 94012919b7..b5d6489fba 100644 --- a/docs/plugins/cursecheck.rst +++ b/docs/plugins/cursecheck.rst @@ -5,11 +5,11 @@ cursecheck :summary: Check for cursed creatures. :tags: fort armok inspection units -This command checks a single map tile (or the whole map/world) for cursed -creatures (ghosts, vampires, necromancers, werebeasts, zombies, etc.). +This command checks a single unit or the whole map for curses (ghosts, vampires, +necromancers, werebeasts, zombies, etc.). -With an active in-game cursor, only the selected tile will be checked. Without a -cursor, the whole map will be checked. +If a unit is selected, only the selected unit will be checked. Otherwise, all +units on the map will be checked. By default, you will just see the count of cursed creatures in case you just want to find out if you have any of them running around in your fort. Dead and diff --git a/library/lua/gui.lua b/library/lua/gui.lua index ec8b9352bc..dac075f1dc 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -10,7 +10,16 @@ local getval = utils.getval local to_pen = dfhack.pen.parse -CLEAR_PEN = to_pen{tile=dfhack.internal.getAddress('init') and df.global.init.texpos_border_interior or nil, ch=32, fg=0, bg=0, write_to_lower=true} +local function getInteriorTexpos() + if not dfhack.internal.getAddress('init') then return end + if dfhack.screen.inGraphicsMode() then + return df.global.init.texpos_border_interior + else + return df.global.init.classic_texpos_border_interior + end +end + +CLEAR_PEN = to_pen{tile=getInteriorTexpos(), ch=32, fg=0, bg=0, write_to_lower=true} TRANSPARENT_PEN = to_pen{tile=0, ch=0} KEEP_LOWER_PEN = to_pen{ch=32, fg=0, bg=0, keep_lower=true} diff --git a/plugins/cursecheck.cpp b/plugins/cursecheck.cpp index f1ba62bf06..b7e5fe0f01 100644 --- a/plugins/cursecheck.cpp +++ b/plugins/cursecheck.cpp @@ -15,36 +15,23 @@ // all - don't ignore dead and inactive creatures (former ghosts, dead necromancers, ...) // verbose - acts like detail but also lists all curse tags (if you want to know it all). -#include -#include -#include -#include -#include -#include -#include -#include -using namespace std; -#include "Core.h" #include "Console.h" +#include "Core.h" +#include "MiscUtils.h" #include "PluginManager.h" -#include "modules/Units.h" -#include + #include "modules/Gui.h" -#include "MiscUtils.h" +#include "modules/Translation.h" +#include "modules/Units.h" +#include "df/incident.h" +#include "df/syndrome.h" #include "df/unit.h" #include "df/unit_soul.h" #include "df/unit_syndrome.h" -#include "df/historical_entity.h" -#include "df/historical_figure.h" -#include "df/historical_figure_info.h" -#include "df/identity.h" -#include "df/language_name.h" -#include "df/syndrome.h" #include "df/world.h" #include "df/world_raws.h" -#include "df/incident.h" using std::vector; using std::string; @@ -55,8 +42,8 @@ DFHACK_PLUGIN("cursecheck"); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(cursor); -enum class curses : int8_t { - None, +enum curses { + None = 0, Unknown, Ghost, Zombie, @@ -121,12 +108,11 @@ curses determineCurse(df::unit * unit) // werecreatures: subjected to a were syndrome. The curse effects are active only when // in were form. - for (size_t i = 0; i < unit->syndromes.active.size(); i++) - { - for (size_t k = 0; k < world->raws.syndromes.all[unit->syndromes.active[i]->type]->syn_class.size(); k++) - { - if (strcmp (world->raws.syndromes.all[unit->syndromes.active[i]->type]->syn_class[k]->c_str(), "WERECURSE") == 0) - { + for (auto active_syndrome : unit->syndromes.active) { + auto syndrome = df::syndrome::find(active_syndrome->type); + if (syndrome) { + for (auto classname : syndrome->syn_class) + if (classname && *classname == "WERECURSE") { cursetype = curses::Werebeast; break; } @@ -143,8 +129,7 @@ curses determineCurse(df::unit * unit) command_result cursecheck (color_ostream &out, vector & parameters) { CoreSuspender suspend; - int32_t cursorX, cursorY, cursorZ; - Gui::getCursorCoords(cursorX,cursorY,cursorZ); + df::unit* selected_unit = Gui::getSelectedUnit(out, true); bool giveDetails = false; bool giveUnitID = false; @@ -153,33 +138,19 @@ command_result cursecheck (color_ostream &out, vector & parameters) bool verbose = false; size_t cursecount = 0; - for(size_t i = 0; i < parameters.size();i++) + for(auto parameter : parameters) { - if(parameters[i] == "help" || parameters[i] == "?") - { - out.print( " Search for cursed creatures (ghosts, vampires, necromancers, zombies, werebeasts).\n" - " With map cursor active only the current tile will be checked.\n" - " Without an in-game cursor the whole map/world will be scanned.\n" - " By default cursed creatures are only counted to make it more interesting.\n" - " By default dead and passive creatures (aka really dead) are ignored.\n" - "Options:\n" - " detail - show details (name and age shown ingame might differ)\n" - " ids - add creature and race IDs to be show on the output\n" - " nick - try to set cursetype as nickname (does not always work)\n" - " all - include dead and passive creatures\n" - " verbose - show all curse tags (if you really want to know it all)\n" - ); - return CR_OK; - } - if(parameters[i] == "detail") + if(parameter == "help" || parameter == "?") + return CR_WRONG_USAGE; + if(parameter == "detail") giveDetails = true; - if(parameters[i] == "ids") + if(parameter == "ids") giveUnitID = true; - if(parameters[i] == "nick") + if(parameter == "nick") giveNick = true; - if(parameters[i] == "all") + if(parameter == "all") ignoreDead = false; - if(parameters[i] == "verbose") + if(parameter == "verbose") { // verbose makes no sense without enabling details giveDetails = true; @@ -187,18 +158,9 @@ command_result cursecheck (color_ostream &out, vector & parameters) } } - // check whole map if no cursor is active - bool checkWholeMap = false; - if(cursorX == -30000) + // check whole map if no unit is selected + for(df::unit *unit : selected_unit ? vector{ selected_unit } : world->units.all) { - out.print("No cursor; will check all units on the map.\n"); - checkWholeMap = true; - } - - for(size_t i = 0; i < world->units.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - // filter out all "living" units that are currently removed from play // don't spam all completely dead creatures if not explicitly wanted if((!Units::isActive(unit) && !Units::isKilled(unit)) || (Units::isKilled(unit) && ignoreDead)) @@ -206,12 +168,6 @@ command_result cursecheck (color_ostream &out, vector & parameters) continue; } - // bail out if we have a map cursor and creature is not at that specific position - if ( !checkWholeMap && (unit->pos.x != cursorX || unit->pos.y != cursorY || unit->pos.z != cursorZ) ) - { - continue; - } - curses cursetype = determineCurse(unit); if (cursetype != curses::None) @@ -220,53 +176,30 @@ command_result cursecheck (color_ostream &out, vector & parameters) if(giveNick) { - Units::setNickname(unit, curse_names[static_cast(cursetype)].c_str()); //"CURSED"); + Units::setNickname(unit, curse_names[cursetype]); //"CURSED"); } if (giveDetails) { - if (unit->name.has_name) - { - string firstname = unit->name.first_name; - string restofname = Translation::TranslateName(&unit->name, false); - firstname[0] = toupper(firstname[0]); - - // if creature has no nickname, restofname will already contain firstname - // no need for double output - if (restofname.compare(0, firstname.length(), firstname) != 0) - out.print("%s ", firstname.c_str()); - out.print("%s ", restofname.c_str()); - } - else - { - // happens with unnamed zombies and resurrected body parts - out.print("Unnamed creature "); - } + out << (Units::getReadableName(unit)); auto death = df::incident::find(unit->counters.death_id); - bool missing = false; - if (death && !death->flags.bits.discovered) - { - missing = true; - } - - out.print("born in %d, cursed in %d to be a %s. (%s%s%s)\n", + out.print(", born in %d, cursed in %d to be a %s. (%s%s)\n", unit->birth_year, unit->curse_year, - curse_names [static_cast(cursetype)].c_str(), + curse_names[cursetype].c_str(), // technically most cursed creatures are undead, // therefore output 'active' if they are not completely dead unit->flags2.bits.killed ? "deceased" : "active", - unit->flags3.bits.ghostly ? "-ghostly" : "", - missing ? "-missing" : "" + death && !death->flags.bits.discovered ? "-missing" : "" ); // dump all curse flags on demand if (verbose) { out << "Curse flags: " - << bitfield_to_string(unit->curse.add_tags1) << endl - << bitfield_to_string(unit->curse.add_tags2) << endl; + << bitfield_to_string(unit->curse.add_tags1) << std::endl + << bitfield_to_string(unit->curse.add_tags2) << std::endl; } } @@ -277,10 +210,10 @@ command_result cursecheck (color_ostream &out, vector & parameters) } } - if (checkWholeMap) - out.print("Number of cursed creatures on map: %zd \n", cursecount); - else - out.print("Number of cursed creatures on tile: %zd \n", cursecount); + if (selected_unit && !giveDetails) + out.print("Selected unit is %scursed\n", cursecount == 0 ? "not " : ""); + else if (!selected_unit) + out.print("%zd cursed creatures on map\n", cursecount); return CR_OK; } diff --git a/test/library/gui.lua b/test/library/gui.lua index 3fc8b889d6..6cc2b5a302 100644 --- a/test/library/gui.lua +++ b/test/library/gui.lua @@ -11,8 +11,11 @@ function test.getKeyDisplay() end function test.clear_pen() + local expected_tile = dfhack.screen.inGraphicsMode() and + df.global.init.texpos_border_interior or df.global.init.classic_texpos_border_interior + expect.table_eq(gui.CLEAR_PEN, { - tile = df.global.init.texpos_border_interior, + tile = expected_tile, ch = string.byte(' '), fg = COLOR_BLACK, bg = COLOR_BLACK,