diff --git a/NEWS b/NEWS index fd92f620a..4e1736a86 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,9 @@ DFHack future Fixes: - autotrade: "Mark all" no longer double-marks bin contents + New plugins: + - trackstop: Shows track stop friction and dump direction in its 'q' menu + DFHack 0.40.13-r1 Internals: - unified spatter structs diff --git a/Readme.html b/Readme.html index bd9ea6aa5..4a20a419c 100644 --- a/Readme.html +++ b/Readme.html @@ -529,6 +529,7 @@ access DF memory and allow for easier development of new tools.

  • Search
  • AutoMaterial
  • Stockpile Automation
  • +
  • Track Stop Menu
  • gui/advfort
  • gui/assign-rack
  • gui/choose-weapons
  • @@ -3275,6 +3276,20 @@ enable automelt

    When querying a stockpile an option will appear to toggle automelt for this stockpile. Any items placed in this stockpile will be designated to be melted.

    +
    +

    Track Stop Menu

    +

    The q menu of track stops is completely blank by default. To enable one:

    +
    +enable trackstop
    +
    +

    This allows you to view and/or change the track stop's friction and dump direction settings. +It re-uses the keybindings from the track stop building interface:

    + +

    gui/advfort

    This script allows to perform jobs in adventure mode. For more complete help diff --git a/Readme.rst b/Readme.rst index 2eb75b6b1..fb5d39df3 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2655,6 +2655,19 @@ Enable the automelt plugin in your dfhack.init with:: When querying a stockpile an option will appear to toggle automelt for this stockpile. Any items placed in this stockpile will be designated to be melted. +Track Stop Menu +=============== + +The `q` menu of track stops is completely blank by default. To enable one:: + + enable trackstop + +This allows you to view and/or change the track stop's friction and dump direction settings. +It re-uses the keybindings from the track stop building interface: + +* BUILDING_TRACK_STOP_FRICTION_UP +* BUILDING_TRACK_STOP_FRICTION_DOWN +* BUILDING_TRACK_STOP_DUMP gui/advfort =========== diff --git a/dfhack.init-example b/dfhack.init-example index f0f380095..5338f9448 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -183,7 +183,7 @@ enable search enable automaterial # Other interface improvement tools -enable dwarfmonitor mousequery automelt autotrade buildingplan resume zone +enable dwarfmonitor mousequery automelt autotrade buildingplan resume trackstop zone # allow the fortress bookkeeper to queue jobs through the manager stockflow enable diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6ebe3ad6d..35b7ca52d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -156,6 +156,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(stocks stocks.cpp) DFHACK_PLUGIN(strangemood strangemood.cpp) DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h) + DFHACK_PLUGIN(trackstop trackstop.cpp) # DFHACK_PLUGIN(treefarm treefarm.cpp) DFHACK_PLUGIN(tubefill tubefill.cpp) DFHACK_PLUGIN(tweak tweak.cpp) diff --git a/plugins/trackstop.cpp b/plugins/trackstop.cpp new file mode 100644 index 000000000..4676a1b52 --- /dev/null +++ b/plugins/trackstop.cpp @@ -0,0 +1,301 @@ +/* + * Trackstop plugin. + * Shows track stop friction and direction in its 'q' menu. + */ + +#include "uicommon.h" +#include "LuaTools.h" + +#include "df/building_rollersst.h" +#include "df/building_trapst.h" +#include "df/viewscreen_dwarfmodest.h" + +#include "modules/Gui.h" + +using namespace DFHack; +using namespace std; + +using df::global::world; +using df::global::ui; +using df::building_rollersst; +using df::building_trapst; +using df::enums::trap_type::trap_type; +using df::enums::screw_pump_direction::screw_pump_direction; + +DFHACK_PLUGIN("trackstop"); + +#define AUTOENABLE false +DFHACK_PLUGIN_IS_ENABLED(enabled); + + +/* + * Interface hooks + */ +struct trackstop_hook : public df::viewscreen_dwarfmodest { + typedef df::viewscreen_dwarfmodest interpose_base; + + enum Friction { + Lowest = 10, + Low = 50, + Medium = 500, + High = 10000, + Highest = 50000 + }; + + building_trapst *get_selected_trackstop() { + if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) || ui->main.mode != ui_sidebar_mode::QueryBuilding) { + return nullptr; + } + + building_trapst *ts = virtual_cast(world->selected_building); + if (ts && ts->trap_type == trap_type::TrackStop && ts->construction_stage) { + return ts; + } + + return nullptr; + } + + bool handleInput(set *input) { + building_trapst *ts = get_selected_trackstop(); + if (!ts) { + return false; + } + + if (input->count(interface_key::BUILDING_TRACK_STOP_DUMP)) { + // Change track stop dump direction. + // There might be a more elegant way to do this. + + if (!ts->use_dump) { + // No -> North + ts->use_dump = 1; + ts->dump_x_shift = 0; + ts->dump_y_shift = -1; + } else if (ts->dump_x_shift == 0 && ts->dump_y_shift == -1) { + // North -> South + ts->dump_x_shift = 0; + ts->dump_y_shift = 1; + } else if (ts->dump_x_shift == 0 && ts->dump_y_shift == 1) { + // South -> East + ts->dump_x_shift = 1; + ts->dump_y_shift = 0; + } else if (ts->dump_x_shift == 1 && ts->dump_y_shift == 0) { + // East -> West + ts->dump_x_shift = -1; + ts->dump_y_shift = 0; + } else { + // West (or Elsewhere) -> No + ts->use_dump = 0; + ts->dump_x_shift = 0; + ts->dump_y_shift = 0; + } + + return true; + } else if (input->count(interface_key::BUILDING_TRACK_STOP_FRICTION_UP)) { + ts->friction = ( + (ts->friction < Friction::Lowest)? Friction::Lowest: + (ts->friction < Friction::Low)? Friction::Low: + (ts->friction < Friction::Medium)? Friction::Medium: + (ts->friction < Friction::High)? Friction::High: + (ts->friction < Friction::Highest)? Friction::Highest: + ts->friction + ); + + return true; + } else if (input->count(interface_key::BUILDING_TRACK_STOP_FRICTION_DOWN)) { + ts->friction = ( + (ts->friction > Friction::Highest)? Friction::Highest: + (ts->friction > Friction::High)? Friction::High: + (ts->friction > Friction::Medium)? Friction::Medium: + (ts->friction > Friction::Low)? Friction::Low: + (ts->friction > Friction::Lowest)? Friction::Lowest: + ts->friction + ); + + return true; + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { + if (!handleInput(input)) { + INTERPOSE_NEXT(feed)(input); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) { + INTERPOSE_NEXT(render)(); + + building_trapst *ts = get_selected_trackstop(); + if (ts) { + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = dims.y1 + 1; + + OutputString(COLOR_WHITE, x, y, "Track Stop", true, left_margin); + + y += 3; + OutputString(COLOR_WHITE, x, y, "Friction: ", false); + OutputString(COLOR_WHITE, x, y, ( + (ts->friction <= Friction::Lowest)? "Lowest": + (ts->friction <= Friction::Low)? "Low": + (ts->friction <= Friction::Medium)? "Medium": + (ts->friction <= Friction::High)? "High": + "Highest" + ), true, left_margin); + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_TRACK_STOP_FRICTION_DOWN)); + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_TRACK_STOP_FRICTION_UP)); + OutputString(COLOR_WHITE, x, y, ": Change Friction", true, left_margin); + + y += 1; + + OutputString(COLOR_WHITE, x, y, "Dump on arrival: ", false); + OutputString(COLOR_WHITE, x, y, ( + (!ts->use_dump)? "No": + (ts->dump_x_shift == 0 && ts->dump_y_shift == -1)? "North": + (ts->dump_x_shift == 0 && ts->dump_y_shift == 1)? "South": + (ts->dump_x_shift == 1 && ts->dump_y_shift == 0)? "East": + (ts->dump_x_shift == -1 && ts->dump_y_shift == 0)? "West": + "Elsewhere" + ), true, left_margin); + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_TRACK_STOP_DUMP)); + OutputString(COLOR_WHITE, x, y, ": Activate/change direction", true, left_margin); + } + } +}; + +struct roller_hook : public df::viewscreen_dwarfmodest { + typedef df::viewscreen_dwarfmodest interpose_base; + + enum Speed { + Lowest = 10000, + Low = 20000, + Medium = 30000, + High = 40000, + Highest = 50000 + }; + + building_rollersst *get_selected_roller() { + if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) || ui->main.mode != ui_sidebar_mode::QueryBuilding) { + return nullptr; + } + + building_rollersst *roller = virtual_cast(world->selected_building); + if (roller && roller->construction_stage) { + return roller; + } + + return nullptr; + } + + bool handleInput(set *input) { + building_rollersst *roller = get_selected_roller(); + if (!roller) { + return false; + } + + if (input->count(interface_key::BUILDING_ORIENT_NONE)) { + // Flip roller orientation. + // Long rollers can only be oriented along their length. + // Todo: Only add 1 to 1x1 rollers: x ^= ((x&1)<<1)|1 + // Todo: This could have been elegant without all the casting, + // but as an enum it might be better off listing each case. + roller->direction = (df::enums::screw_pump_direction::screw_pump_direction)(((int8_t)roller->direction) ^ 2); + return true; + } else if (input->count(interface_key::BUILDING_ROLLERS_SPEED_UP)) { + if (roller->speed < Speed::Highest) { + roller->speed += Speed::Lowest; + } + + return true; + } else if (input->count(interface_key::BUILDING_ROLLERS_SPEED_DOWN)) { + if (roller->speed > Speed::Lowest) { + roller->speed -= Speed::Lowest; + } + + return true; + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { + if (!handleInput(input)) { + INTERPOSE_NEXT(feed)(input); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) { + INTERPOSE_NEXT(render)(); + + building_rollersst *roller = get_selected_roller(); + if (roller) { + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = dims.y1 + 6; + + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_ORIENT_NONE)); + OutputString(COLOR_WHITE, x, y, ": Rolls ", false); + OutputString(COLOR_WHITE, x, y, ( + (roller->direction == screw_pump_direction::FromNorth)? "Southward": + (roller->direction == screw_pump_direction::FromEast)? "Westward": + (roller->direction == screw_pump_direction::FromSouth)? "Northward": + (roller->direction == screw_pump_direction::FromWest)? "Eastward": + "" + ), true, left_margin); + + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_ROLLERS_SPEED_DOWN)); + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_ROLLERS_SPEED_UP)); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString(COLOR_WHITE, x, y, ( + (roller->speed <= Speed::Lowest)? "Lowest": + (roller->speed <= Speed::Low)? "Low": + (roller->speed <= Speed::Medium)? "Medium": + (roller->speed <= Speed::High)? "High": + "Highest" + )); + OutputString(COLOR_WHITE, x, y, " Speed", true, left_margin); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(trackstop_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(trackstop_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(roller_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(roller_hook, render); + + +DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { + // Accept the "enable trackstop" / "disable trackstop" commands. + if (enable != enabled) { + // Check for global variables that, if missing, result in total failure. + // Missing enabler and ui_menu_width also produce visible effects, but not nearly as severe. + // This could be moved to the plugin_init step, but that's louder for no real benefit. + if (!(gps && ui && world)) { + out.printerr("trackstop: Missing required global variables.\n"); + return CR_FAILURE; + } + + if (!INTERPOSE_HOOK(trackstop_hook, feed).apply(enable) || + !INTERPOSE_HOOK(trackstop_hook, render).apply(enable) || + !INTERPOSE_HOOK(roller_hook, feed).apply(enable) || + !INTERPOSE_HOOK(roller_hook, render).apply(enable)) { + out.printerr("Could not %s trackstop hooks!\n", enable? "insert": "remove"); + return CR_FAILURE; + } + + enabled = enable; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + return plugin_enable(out, AUTOENABLE); +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) { + return plugin_enable(out, false); +}