Merge pull request #2113 from Bumber64/Bumber64-patch-2

Add functions reverse-engineered from announcement code
develop
Myk 2022-11-18 17:44:53 -08:00 committed by GitHub
commit baf58fdf25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 591 additions and 116 deletions

@ -1019,17 +1019,26 @@ Fortress mode
Resets the fortress mode sidebar menus and cursors to their default state. If
``pause`` is true, also pauses the game.
* ``dfhack.gui.revealInDwarfmodeMap(pos)``
* ``dfhack.gui.pauseRecenter(pos[,pause])``
``dfhack.gui.pauseRecenter(x,y,z[,pause])``
Centers the view on the given position, which can be a ``df.coord`` instance
or a table assignable to a ``df.coord`` (see `lua-api-table-assignment`),
Same as ``resetDwarfmodeView``, but also recenter if position is valid. If ``pause`` is false, skip pausing. Respects
``RECENTER_INTERFACE_SHUTDOWN_MS`` in DF's ``init.txt`` (the delay before input is recognized when a recenter occurs.)
* ``dfhack.gui.revealInDwarfmodeMap(pos[,center])``
``dfhack.gui.revealInDwarfmodeMap(x,y,z[,center])``
Centers the view on the given coordinates. If ``center`` is true, make sure the
position is in the exact center of the view, else just bring it on screen.
``pos`` can be a ``df.coord`` instance or a table assignable to a ``df.coord`` (see `lua-api-table-assignment`),
e.g.::
{x = 5, y = 7, z = 11}
getSelectedUnit().pos
copyall(df.global.cursor)
Returns false if unsuccessful.
If the position is invalid, the function will simply ensure the current window position is clamped between valid values.
* ``dfhack.gui.refreshSidebar()``
@ -1088,6 +1097,19 @@ Announcements
Uses the type to look up options from announcements.txt, and calls the above
operations accordingly. The units are used to call ``addCombatReportAuto``.
* ``dfhack.gui.autoDFAnnouncement(report,text)``
``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring])``
Takes a ``df.report_init`` (see: `structure definition <https://github.com/DFHack/df-structures/blob/master/df.announcements.xml>`_)
and a string and processes them just like DF does. Can also be built from parameters instead of a ``report_init``.
Setting ``is_sparring`` to *true* means the report will be added to sparring logs (if applicable) rather than hunting or combat.
The announcement will not display if units are involved and the player can't see them (or hear, for adventure mode sound announcement types.)
Text is parsed using ``&`` as an escape character, with ``&r`` adding a blank line (equivalent to ``\n \n``,)
``&&`` being just ``&``, and any other combination causing neither character to display.
If you want a guaranteed announcement without parsing, use ``dfhack.gui.showAutoAnnouncement`` instead.
* ``dfhack.gui.getMousePos()``
Returns the map coordinates of the map tile the mouse is over as a table of

@ -76,9 +76,12 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Documentation
- `spectate`: improved documentation of features and functionality
- `overlay-dev-guide`: documentation and guide for injecting functionality into DF viewscreens from Lua scripts and creating overlay widgets
- ``dfhack.gui.revealInDwarfmodeMap``: document ``center`` bool for lua API
## API
- ``Gui::anywhere_hotkey``: for plugin commands bound to keybindings that can be invoked on any screen
- add functions reverse-engineered from announcement code: ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter``
- ``Gui::revealInDwarfmodeMap``: Now enforce valid view bounds when pos invalid, add variant accepting x, y, z
- ``Lua::PushInterfaceKeys()``: transforms viewscreen ``feed()`` keys into something that can be interpreted by lua-based widgets
- ``Lua::Push()``: now handles maps with otherwise supported keys and values
- Units module: added new checks

@ -98,6 +98,8 @@ distribution.
#include "df/specific_ref.h"
#include "df/specific_ref_type.h"
#include "df/vermin.h"
#include "df/report_init.h"
#include "df/report_zoom_type.h"
#include <lua.h>
#include <lauxlib.h>
@ -1481,15 +1483,136 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, showPopupAnnouncement),
WRAPM(Gui, showAutoAnnouncement),
WRAPM(Gui, resetDwarfmodeView),
WRAPM(Gui, revealInDwarfmodeMap),
WRAPM(Gui, refreshSidebar),
WRAPM(Gui, inRenameBuilding),
WRAPM(Gui, getDepthAt),
{ NULL, NULL }
};
static int gui_autoDFAnnouncement(lua_State *state)
{
bool rv;
df::report_init *r = Lua::GetDFObject<df::report_init>(state, 1);
if (r)
{
std::string message = luaL_checkstring(state, 2);
rv = Gui::autoDFAnnouncement(*r, message);
}
else
{
df::coord pos;
int color = 0; // initialize these to prevent warning
bool bright = false, is_sparring = false;
df::unit *unit1 = NULL, *unit2 = NULL;
auto type = (df::announcement_type)lua_tointeger(state, 1);
Lua::CheckDFAssign(state, &pos, 2);
std::string message = luaL_checkstring(state, 3);
switch (lua_gettop(state))
{
default:
case 8:
is_sparring = lua_toboolean(state, 8);
case 7:
unit2 = Lua::CheckDFObject<df::unit>(state, 7);
case 6:
unit1 = Lua::CheckDFObject<df::unit>(state, 6);
case 5:
bright = lua_toboolean(state, 5);
case 4:
color = lua_tointeger(state, 4);
case 3:
break;
}
switch (lua_gettop(state))
{ // Use the defaults in Gui.h
default:
case 8:
rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2, is_sparring);
break;
case 7:
rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2);
break;
case 6:
rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1);
break;
case 5:
rv = Gui::autoDFAnnouncement(type, pos, message, color, bright);
break;
case 4:
rv = Gui::autoDFAnnouncement(type, pos, message, color);
break;
case 3:
rv = Gui::autoDFAnnouncement(type, pos, message);
}
}
lua_pushboolean(state, rv);
return 1;
}
static int gui_pauseRecenter(lua_State *state)
{
bool rv;
df::coord p;
switch (lua_gettop(state))
{
default:
case 4:
rv = Gui::pauseRecenter(CheckCoordXYZ(state, 1, false), lua_toboolean(state, 4));
break;
case 3:
rv = Gui::pauseRecenter(CheckCoordXYZ(state, 1, false));
break;
case 2:
Lua::CheckDFAssign(state, &p, 1);
rv = Gui::pauseRecenter(p, lua_toboolean(state, 2));
break;
case 1:
Lua::CheckDFAssign(state, &p, 1);
rv = Gui::pauseRecenter(p);
}
lua_pushboolean(state, rv);
return 1;
}
static int gui_revealInDwarfmodeMap(lua_State *state)
{
bool rv;
df::coord p;
switch (lua_gettop(state))
{
default:
case 4:
rv = Gui::revealInDwarfmodeMap(CheckCoordXYZ(state, 1, false), lua_toboolean(state, 4));
break;
case 3:
rv = Gui::revealInDwarfmodeMap(CheckCoordXYZ(state, 1, false));
break;
case 2:
Lua::CheckDFAssign(state, &p, 1);
rv = Gui::revealInDwarfmodeMap(p, lua_toboolean(state, 2));
break;
case 1:
Lua::CheckDFAssign(state, &p, 1);
rv = Gui::revealInDwarfmodeMap(p);
}
lua_pushboolean(state, rv);
return 1;
}
static const luaL_Reg dfhack_gui_funcs[] = {
{ "autoDFAnnouncement", gui_autoDFAnnouncement },
{ "getDwarfmodeViewDims", gui_getDwarfmodeViewDims },
{ "pauseRecenter", gui_pauseRecenter },
{ "revealInDwarfmodeMap", gui_revealInDwarfmodeMap },
{ "getMousePos", gui_getMousePos },
{ NULL, NULL }
};

@ -36,6 +36,8 @@ distribution.
#include "df/ui.h"
#include "df/announcement_type.h"
#include "df/announcement_flags.h"
#include "df/report_init.h"
#include "df/report_zoom_type.h"
#include "df/unit_report_type.h"
#include "modules/GuiHooks.h"
@ -125,6 +127,11 @@ namespace DFHack
// Show an announcement with effects determined by announcements.txt
DFHACK_EXPORT void showAutoAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true, df::unit *unit1 = NULL, df::unit *unit2 = NULL);
// Process an announcement exactly like DF would, which might result in no announcement
DFHACK_EXPORT bool autoDFAnnouncement(df::report_init r, std::string message);
DFHACK_EXPORT bool autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true,
df::unit *unit1 = NULL, df::unit *unit2 = NULL, bool is_sparring = false);
/*
* Cursor and window map coords
*/
@ -148,7 +155,10 @@ namespace DFHack
DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims();
DFHACK_EXPORT void resetDwarfmodeView(bool pause = false);
DFHACK_EXPORT bool revealInDwarfmodeMap(df::coord pos, bool center = false);
DFHACK_EXPORT bool revealInDwarfmodeMap(int32_t x, int32_t y, int32_t z, bool center = false);
DFHACK_EXPORT inline bool revealInDwarfmodeMap(df::coord pos, bool center = false) { return revealInDwarfmodeMap(pos.x, pos.y, pos.z, center); };
DFHACK_EXPORT bool pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause = true);
DFHACK_EXPORT inline bool pauseRecenter(df::coord pos, bool pause = true) { return pauseRecenter(pos.x, pos.y, pos.z, pause); };
DFHACK_EXPORT bool refreshSidebar();
DFHACK_EXPORT bool inRenameBuilding();

@ -37,6 +37,7 @@ using namespace std;
#include "Error.h"
#include "ModuleFactory.h"
#include "Core.h"
#include "Debug.h"
#include "PluginManager.h"
#include "MiscUtils.h"
using namespace DFHack;
@ -44,6 +45,7 @@ using namespace DFHack;
#include "modules/Job.h"
#include "modules/Screen.h"
#include "modules/Maps.h"
#include "modules/Units.h"
#include "DataDefs.h"
@ -70,6 +72,7 @@ using namespace DFHack;
#include "df/plant.h"
#include "df/popup_message.h"
#include "df/report.h"
#include "df/report_zoom_type.h"
#include "df/route_stockpile_link.h"
#include "df/stop_depart_condition.h"
#include "df/ui_advmode.h"
@ -111,6 +114,16 @@ using namespace DFHack;
#include "df/viewscreen_workshop_profilest.h"
#include "df/world.h"
const size_t MAX_REPORTS_SIZE = 3000; // DF clears old reports to maintain this vector size
const int32_t RECENT_REPORT_TICKS = 500; // used by UNIT_COMBAT_REPORT_ALL_ACTIVE
const int32_t ANNOUNCE_LINE_DURATION = 100; // time to display each line in announcement bar; 3.3 sec at 30 GFPS
const int16_t ANNOUNCE_DISPLAY_TIME = 2000; // DF uses this value for most announcements; 66.6 sec at 30 GFPS
namespace DFHack
{
DBG_DECLARE(core, gui, DebugCategory::LINFO);
}
using namespace df::enums;
using df::global::gamemode;
@ -673,8 +686,6 @@ bool Gui::cursor_hotkey(df::viewscreen *top)
bool Gui::workshop_job_hotkey(df::viewscreen *top)
{
using namespace ui_sidebar_mode;
using df::global::ui;
using df::global::world;
using df::global::ui_workshop_in_add;
using df::global::ui_workshop_job_cursor;
@ -711,7 +722,6 @@ bool Gui::workshop_job_hotkey(df::viewscreen *top)
bool Gui::build_selector_hotkey(df::viewscreen *top)
{
using namespace ui_sidebar_mode;
using df::global::ui;
using df::global::ui_build_selector;
if (!dwarfmode_hotkey(top))
@ -738,8 +748,6 @@ bool Gui::build_selector_hotkey(df::viewscreen *top)
bool Gui::view_unit_hotkey(df::viewscreen *top)
{
using df::global::ui;
using df::global::world;
using df::global::ui_selected_unit;
if (!dwarfmode_hotkey(top))
@ -766,7 +774,6 @@ bool Gui::unit_inventory_hotkey(df::viewscreen *top)
df::job *Gui::getSelectedWorkshopJob(color_ostream &out, bool quiet)
{
using df::global::world;
using df::global::ui_workshop_job_cursor;
if (!workshop_job_hotkey(Core::getTopViewscreen())) {
@ -834,8 +841,6 @@ df::job *Gui::getSelectedJob(color_ostream &out, bool quiet)
df::unit *Gui::getAnyUnit(df::viewscreen *top)
{
using namespace ui_sidebar_mode;
using df::global::ui;
using df::global::world;
using df::global::ui_look_cursor;
using df::global::ui_look_list;
using df::global::ui_selected_unit;
@ -1116,13 +1121,10 @@ df::unit *Gui::getSelectedUnit(color_ostream &out, bool quiet)
df::item *Gui::getAnyItem(df::viewscreen *top)
{
using namespace ui_sidebar_mode;
using df::global::ui;
using df::global::world;
using df::global::ui_look_cursor;
using df::global::ui_look_list;
using df::global::ui_unit_view_mode;
using df::global::ui_building_item_cursor;
using df::global::ui_sidebar_menus;
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_textviewerst, top))
{
@ -1257,11 +1259,8 @@ df::item *Gui::getSelectedItem(color_ostream &out, bool quiet)
df::building *Gui::getAnyBuilding(df::viewscreen *top)
{
using namespace ui_sidebar_mode;
using df::global::ui;
using df::global::ui_look_list;
using df::global::ui_look_cursor;
using df::global::world;
using df::global::ui_sidebar_menus;
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_buildinglistst, top))
return vector_get(screen->buildings, screen->cursor);
@ -1324,8 +1323,6 @@ df::building *Gui::getSelectedBuilding(color_ostream &out, bool quiet)
df::plant *Gui::getAnyPlant(df::viewscreen *top)
{
using df::global::cursor;
using df::global::ui;
using df::global::world;
if (auto dfscreen = dfhack_viewscreen::try_cast(top))
return dfscreen->getSelectedPlant();
@ -1372,98 +1369,192 @@ DFHACK_EXPORT void Gui::writeToGamelog(std::string message)
fseed.close();
}
// Utility functions for reports
static bool parseReportString(std::vector<std::string> *out, const std::string &str, size_t line_length = 73)
{ // parse a string into output strings like DF does for reports
if (str.empty() || line_length == 0)
return false;
string parsed;
size_t i = 0;
do
{
if (str[i] == '&') // escape character
{
i++; // ignore the '&' itself
if (i >= str.length())
break;
if (str[i] == 'r') // "&r" adds a blank line
{
word_wrap(out, parsed, line_length, WSMODE_TRIM_LEADING);
out->push_back(" "); // DF adds a line with a space for some reason
parsed.clear();
}
else if (str[i] == '&') // "&&" is '&'
parsed.push_back('&');
// else next char is ignored
}
else
parsed.push_back(str[i]);
}
while (++i < str.length());
if (parsed.length())
word_wrap(out, parsed, line_length, WSMODE_TRIM_LEADING);
return true;
}
static bool recent_report(df::unit *unit, df::unit_report_type slot)
{
return unit && !unit->reports.log[slot].empty() &&
*df::global::cur_year == unit->reports.last_year[slot] &&
(*df::global::cur_year_tick - unit->reports.last_year_tick[slot]) <= RECENT_REPORT_TICKS;
}
static bool recent_report_any(df::unit *unit)
{
FOR_ENUM_ITEMS(unit_report_type, slot)
{
if (recent_report(unit, slot))
return true;
}
return false;
}
static void delete_old_reports()
{
auto &reports = world->status.reports;
if (reports.size() > MAX_REPORTS_SIZE)
{
size_t excess = reports.size() - MAX_REPORTS_SIZE;
for (size_t i = 0; i < excess; i++)
{
if (reports[i] != NULL)
{ // report destructor
if (reports[i]->flags.bits.announcement)
erase_from_vector(world->status.announcements, &df::report::id, reports[i]->id);
delete reports[i];
}
}
reports.erase(reports.begin(), reports.begin() + excess);
}
}
static int32_t check_repeat_report(vector<string> &results)
{ // returns the new repeat count, else 0
if (*gamemode == game_mode::DWARF && !results.empty() && world->status.reports.size() >= results.size())
{
auto &reports = world->status.reports;
size_t base = reports.size() - results.size(); // index where a repeat would start
size_t offset = 0;
while (reports[base + offset]->text == results[offset] && ++offset < results.size()); // match each report
if (offset == results.size()) // all lines matched
{
reports[base]->duration = ANNOUNCE_LINE_DURATION; // display the last line again
return ++(reports[base]->repeat_count);
}
}
return 0;
}
static bool add_proper_report(df::unit *unit, bool is_sparring, int report_index)
{ // add report to proper category based on is_sparring and unit current job
if (is_sparring)
return Gui::addCombatReport(unit, unit_report_type::Sparring, report_index);
else if (unit->job.current_job != NULL && unit->job.current_job->job_type == job_type::Hunt)
return Gui::addCombatReport(unit, unit_report_type::Hunting, report_index);
else
return Gui::addCombatReport(unit, unit_report_type::Combat, report_index);
}
// End of utility functions for reports
DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announcement_flags flags, df::coord pos, std::string message, int color, bool bright)
{
using df::global::world;
using df::global::cur_year;
using df::global::cur_year_tick;
if (message.empty())
return -1;
int year = 0, year_time = 0;
if (cur_year && cur_year_tick)
if (gamemode == NULL || cur_year == NULL || cur_year_tick == NULL)
{
year = *cur_year;
year_time = *cur_year_tick;
return -1;
}
else if (!world->status.reports.empty())
else if (message.empty())
{
// Fallback: copy from the last report
df::report *last = world->status.reports.back();
year = last->year;
year_time = last->time;
Core::printerr("Empty announcement %u\n", type); // DF would print this to errorlog.txt
return -1;
}
// Apply the requested effects
writeToGamelog(message);
if (flags.bits.DO_MEGA || flags.bits.PAUSE || flags.bits.RECENTER)
{
resetDwarfmodeView(flags.bits.DO_MEGA || flags.bits.PAUSE);
if (flags.bits.PAUSE || flags.bits.RECENTER)
pauseRecenter((flags.bits.RECENTER ? pos : df::coord()), flags.bits.PAUSE);
if (flags.bits.RECENTER && pos.isValid())
revealInDwarfmodeMap(pos, true);
bool adv_unconscious = (*gamemode == game_mode::ADVENTURE && !world->units.active.empty() && world->units.active[0]->counters.unconscious > 0);
if (flags.bits.DO_MEGA)
showPopupAnnouncement(message, color, bright);
}
if (flags.bits.DO_MEGA && !adv_unconscious)
showPopupAnnouncement(message, color, bright);
bool display = false;
vector<string> results;
word_wrap(&results, message, init->display.grid_x - 7);
if (gamemode == NULL)
display = flags.bits.A_DISPLAY || flags.bits.D_DISPLAY;
else if (*gamemode == game_mode::ADVENTURE)
display = flags.bits.A_DISPLAY;
else
display = flags.bits.D_DISPLAY;
// Check for repeat report
int32_t repeat_count = check_repeat_report(results); // Does nothing outside dwarf mode
if (repeat_count > 0)
{
if (flags.bits.D_DISPLAY)
{
world->status.display_timer = ANNOUNCE_DISPLAY_TIME;
Gui::writeToGamelog("x" + to_string(repeat_count + 1));
}
return -1;
}
// Not a repeat, write the message to gamelog.txt
writeToGamelog(message);
// Generate the report objects
int report_idx = world->status.reports.size();
bool continued = false;
bool display = ((*gamemode == game_mode::ADVENTURE && flags.bits.A_DISPLAY) || (*gamemode == game_mode::DWARF && flags.bits.D_DISPLAY));
while (!message.empty())
for (size_t i = 0; i < results.size(); i++)
{
df::report *new_rep = new df::report();
auto new_rep = new df::report();
new_rep->type = type;
new_rep->pos = pos;
new_rep->color = color;
new_rep->bright = bright;
new_rep->year = year;
new_rep->time = year_time;
new_rep->year = *cur_year;
new_rep->time = *cur_year_tick;
new_rep->flags.bits.continuation = continued;
int size = std::min(message.size(), (size_t)73);
new_rep->text = message.substr(0, size);
message = message.substr(size);
continued = true;
// Add the object to the lists
new_rep->text = results[i];
new_rep->id = world->status.next_report_id++;
world->status.reports.push_back(new_rep);
if (adv_unconscious)
new_rep->flags.bits.unconscious = true;
if (display)
{
insert_into_vector(world->status.announcements, &df::report::id, new_rep);
new_rep->flags.bits.announcement = true;
world->status.announcements.push_back(new_rep);
world->status.display_timer = 2000;
world->status.display_timer = ANNOUNCE_DISPLAY_TIME;
}
}
delete_old_reports();
return report_idx;
}
bool Gui::addCombatReport(df::unit *unit, df::unit_report_type slot, int report_index)
{
using df::global::world;
CHECK_INVALID_ARGUMENT(is_valid_enum_item(slot));
auto &vec = world->status.reports;
@ -1504,8 +1595,6 @@ bool Gui::addCombatReport(df::unit *unit, df::unit_report_type slot, int report_
bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index)
{
using df::global::world;
auto &vec = world->status.reports;
auto report = vector_get(vec, report_index);
@ -1515,25 +1604,14 @@ bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int r
bool ok = false;
if (mode.bits.UNIT_COMBAT_REPORT)
{
if (unit->flags2.bits.sparring)
ok |= addCombatReport(unit, unit_report_type::Sparring, report_index);
else if (unit->job.current_job && unit->job.current_job->job_type == job_type::Hunt)
ok |= addCombatReport(unit, unit_report_type::Hunting, report_index);
else
ok |= addCombatReport(unit, unit_report_type::Combat, report_index);
}
ok |= add_proper_report(unit, unit->flags2.bits.sparring, report_index);
if (mode.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE)
{
FOR_ENUM_ITEMS(unit_report_type, slot)
{
if (!unit->reports.log[slot].empty() &&
unit->reports.last_year[slot] == report->year &&
(report->time - unit->reports.last_year_tick[slot]) <= 500)
{
if (recent_report(unit, slot))
ok |= addCombatReport(unit, slot, report_index);
}
}
}
@ -1559,13 +1637,20 @@ void Gui::showZoomAnnouncement(
void Gui::showPopupAnnouncement(std::string message, int color, bool bright)
{
using df::global::world;
df::popup_message *popup = new df::popup_message();
popup->text = message;
popup->color = color;
popup->bright = bright;
world->status.popups.push_back(popup);
auto &popups = world->status.popups;
popups.push_back(popup);
while (popups.size() > MAX_REPORTS_SIZE)
{ // Delete old popups
if (popups[0] != NULL)
delete popups[0];
popups.erase(popups.begin());
}
}
void Gui::showAutoAnnouncement(
@ -1586,6 +1671,208 @@ void Gui::showAutoAnnouncement(
addCombatReportAuto(unit2, flags, id);
}
bool Gui::autoDFAnnouncement(df::report_init r, string message)
{ // Reverse-engineered from DF announcement code
if (!world->allow_announcements)
{
DEBUG(gui).print("Skipped announcement because world->allow_announcements is false:\n%s\n", message.c_str());
return false;
}
else if (!is_valid_enum_item(r.type))
{
WARN(gui).print("Invalid announcement type:\n%s\n", message.c_str());
return false;
}
else if (message.empty())
{
Core::printerr("Empty announcement %u\n", r.type); // DF would print this to errorlog.txt
return false;
}
df::announcement_flags a_flags = df::global::d_init->announcements.flags[r.type];
// Check if the announcement will actually be announced
if (*gamemode == game_mode::ADVENTURE)
{
if (r.pos.x != -30000 &&
r.type != announcement_type::CREATURE_SOUND &&
r.type != announcement_type::REGULAR_CONVERSATION &&
r.type != announcement_type::CONFLICT_CONVERSATION &&
r.type != announcement_type::MECHANISM_SOUND)
{ // If not sound, make sure we can see pos
if (world->units.active.empty() || (r.unit1 != world->units.active[0] && r.unit2 != world->units.active[0]))
{ // Adventure mode reuses a dwarf mode digging designation bit to determine current visibility
if (!Maps::isValidTilePos(r.pos) || (Maps::getTileDesignation(r.pos)->whole & 0x10) == 0x0)
{
DEBUG(gui).print("Adventure mode announcement not detected:\n%s\n", message.c_str());
return false;
}
}
}
}
else
{ // Dwarf mode
if ((r.unit1 != NULL || r.unit2 != NULL) && (r.unit1 == NULL || Units::isHidden(r.unit1)) && (r.unit2 == NULL || Units::isHidden(r.unit2)))
{
DEBUG(gui).print("Dwarf mode announcement not detected:\n%s\n", message.c_str());
return false;
}
if (!a_flags.bits.D_DISPLAY)
{
if (a_flags.bits.UNIT_COMBAT_REPORT)
{
if (r.unit1 == NULL && r.unit2 == NULL)
{
DEBUG(gui).print("Skipped UNIT_COMBAT_REPORT because it has no units:\n%s\n", message.c_str());
return false;
}
}
else
{
if (!a_flags.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE)
{
DEBUG(gui).print("Skipped announcement not enabled for this game mode:\n%s\n", message.c_str());
return false;
}
else if (!recent_report_any(r.unit1) && !recent_report_any(r.unit2))
{
DEBUG(gui).print("Skipped UNIT_COMBAT_REPORT_ALL_ACTIVE because there's no active report:\n%s\n", message.c_str());
return false;
}
}
}
}
if (a_flags.bits.PAUSE || a_flags.bits.RECENTER)
pauseRecenter((a_flags.bits.RECENTER ? r.pos : df::coord()), a_flags.bits.PAUSE); // Does nothing outside dwarf mode
bool adv_unconscious = (*gamemode == game_mode::ADVENTURE && !world->units.active.empty() && world->units.active[0]->counters.unconscious > 0);
if (a_flags.bits.DO_MEGA && !adv_unconscious)
showPopupAnnouncement(message, r.color, r.bright);
vector<string> results;
size_t line_length = (r.speaker_id == -1) ? (init->display.grid_x - 7) : (init->display.grid_x - 10);
parseReportString(&results, message, line_length);
if (results.empty()) // DF doesn't do this check
{
DEBUG(gui).print("Skipped announcement because it was empty after parsing:\n%s\n", message.c_str());
return false;
}
// Check for repeat report
int32_t repeat_count = check_repeat_report(results); // always returns 0 outside dwarf mode
if (repeat_count > 0)
{
if (a_flags.bits.D_DISPLAY)
{
world->status.display_timer = r.display_timer;
Gui::writeToGamelog("x" + to_string(repeat_count + 1));
}
DEBUG(gui).print("Announcement succeeded as repeat:\n%s\n", message.c_str());
return true;
}
size_t new_report_index = world->status.reports.size(); // we need this for addCombatReport
bool success = false; // only print to gamelog if report was used
bool display = ((*gamemode == game_mode::ADVENTURE && a_flags.bits.A_DISPLAY) || (*gamemode == game_mode::DWARF && a_flags.bits.D_DISPLAY));
for (size_t i = 0; i < results.size(); i++)
{ // Generate report entries for each line
auto new_report = new df::report();
new_report->type = r.type;
new_report->text = results[i];
new_report->color = r.color;
new_report->bright = r.bright;
new_report->flags.whole = 0x0;
new_report->zoom_type = r.zoom_type;
new_report->pos = r.pos;
new_report->zoom_type2 = r.zoom_type2;
new_report->pos2 = r.pos2;
new_report->id = world->status.next_report_id++;
new_report->year = *df::global::cur_year;
new_report->time = *df::global::cur_year_tick;
new_report->unk_v40_1 = r.unk_v40_1;
new_report->unk_v40_2 = r.unk_v40_2;
new_report->speaker_id = r.speaker_id;
world->status.reports.push_back(new_report);
if (i > 0)
new_report->flags.bits.continuation = true;
if (adv_unconscious)
new_report->flags.bits.unconscious = true;
if (display)
{
insert_into_vector(world->status.announcements, &df::report::id, new_report);
new_report->flags.bits.announcement = true;
world->status.display_timer = r.display_timer;
success = true;
}
}
if (*gamemode == game_mode::DWARF) // DF does this inside the previous loop, but we're using addCombatReport instead
{
if (a_flags.bits.UNIT_COMBAT_REPORT)
{
if (r.unit1 != NULL)
success |= add_proper_report(r.unit1, !r.flags.bits.hostile_combat, new_report_index);
if (r.unit2 != NULL)
success |= add_proper_report(r.unit2, !r.flags.bits.hostile_combat, new_report_index);
}
if (a_flags.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE)
{
FOR_ENUM_ITEMS(unit_report_type, slot)
{
if (recent_report(r.unit1, slot))
success |= addCombatReport(r.unit1, slot, new_report_index);
if (recent_report(r.unit2, slot))
success |= addCombatReport(r.unit2, slot, new_report_index);
}
}
}
delete_old_reports();
if (/*debug_gamelog &&*/ success) // TODO: Add debug_gamelog to globals?
{
DEBUG(gui).print("Announcement succeeded and printed to gamelog.txt:\n%s\n", message.c_str());
Gui::writeToGamelog(message);
}
/*else if (success)
{
DEBUG(gui).print("Announcement succeeded but skipped printing to gamelog.txt because debug_gamelog is false:\n%s\n", message.c_str());
}*/
else // not sure if this can actually happen; our results.empty() check handles the one edge case I can think of that would get this far
{
DEBUG(gui).print("Announcement succeeded internally but didn't qualify to be displayed anywhere:\n%s\n", message.c_str());
}
return true;
}
bool Gui::autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color,
bool bright, df::unit *unit1, df::unit *unit2, bool is_sparring)
{
auto r = df::report_init();
r.type = type;
r.color = color;
r.bright = bright;
r.pos = pos;
r.display_timer = ANNOUNCE_DISPLAY_TIME;
r.unit1 = unit1;
r.unit2 = unit2;
r.flags.bits.hostile_combat = !is_sparring;
return autoDFAnnouncement(r, message);
}
df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed)
{
if (!gview)
@ -1655,7 +1942,7 @@ Gui::DwarfmodeDims getDwarfmodeViewDims_default()
int menu_pos = (ui_menu_width ? (*ui_menu_width)[0] : 2);
int area_pos = (ui_menu_width ? (*ui_menu_width)[1] : 3);
if (ui && ui->main.mode && menu_pos >= area_pos)
if (ui && ui->main.mode != ui_sidebar_mode::Default && ui->main.mode != ui_sidebar_mode::ArenaWeather && menu_pos >= area_pos)
{
dims.menu_forced = true;
menu_pos = area_pos-1;
@ -1715,38 +2002,68 @@ void Gui::resetDwarfmodeView(bool pause)
*df::global::pause_state = true;
}
bool Gui::revealInDwarfmodeMap(df::coord pos, bool center)
{
bool Gui::revealInDwarfmodeMap(int32_t x, int32_t y, int32_t z, bool center)
{ // Reverse-engineered from DF announcement and scrolling code
using df::global::window_x;
using df::global::window_y;
using df::global::window_z;
if (!window_x || !window_y || !window_z || !world)
return false;
if (!Maps::isValidTilePos(pos))
return false;
auto dims = getDwarfmodeViewDims();
int w = dims.map_x2 - dims.map_x1 + 1;
int h = dims.map_y2 - dims.map_y1 + 1;
*window_z = pos.z;
int32_t w = dims.map_x2 - dims.map_x1 + 1;
int32_t h = dims.map_y2 - dims.map_y1 + 1;
int32_t new_win_x, new_win_y, new_win_z;
getViewCoords(new_win_x, new_win_y, new_win_z);
if (center)
if (Maps::isValidTilePos(x, y, z))
{
*window_x = pos.x - w/2;
*window_y = pos.y - h/2;
if (center)
{
new_win_x = x - w / 2;
new_win_y = y - h / 2;
}
else // just bring it on screen
{
if (new_win_x > (x - 5)) // equivalent to: "while (new_win_x > x - 5) new_win_x -= 10;"
new_win_x -= (new_win_x - (x - 5) - 1) / 10 * 10 + 10;
if (new_win_y > (y - 5))
new_win_y -= (new_win_y - (y - 5) - 1) / 10 * 10 + 10;
if (new_win_x < (x + 5 - w))
new_win_x += ((x + 5 - w) - new_win_x - 1) / 10 * 10 + 10;
if (new_win_y < (y + 5 - h))
new_win_y += ((y + 5 - h) - new_win_y - 1) / 10 * 10 + 10;
}
new_win_z = z;
}
else
*window_x = clip_range(new_win_x, 0, (world->map.x_count - w));
*window_y = clip_range(new_win_y, 0, (world->map.y_count - h));
*window_z = clip_range(new_win_z, 0, (world->map.z_count - 1));
ui_sidebar_menus->minimap.need_render = true;
ui_sidebar_menus->minimap.need_scan = true;
return true;
}
bool Gui::pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause)
{ // Reverse-engineered from DF announcement code
if (*gamemode != game_mode::DWARF)
return false;
resetDwarfmodeView(pause);
if (Maps::isValidTilePos(x, y, z))
revealInDwarfmodeMap(x, y, z, false);
if (init->input.pause_zoom_no_interface_ms > 0)
{
while (*window_x + w < pos.x+5) *window_x += 10;
while (*window_y + h < pos.y+5) *window_y += 10;
while (*window_x + 5 > pos.x) *window_x -= 10;
while (*window_y + 5 > pos.y) *window_y -= 10;
gview->shutdown_interface_tickcount = Core::getInstance().p->getTickCount();
gview->shutdown_interface_for_ms = init->input.pause_zoom_no_interface_ms;
}
*window_x = std::max(0, std::min(*window_x, world->map.x_count-w));
*window_y = std::max(0, std::min(*window_y, world->map.y_count-h));
return true;
}
@ -1821,17 +2138,17 @@ bool Gui::setCursorCoords (const int32_t x, const int32_t y, const int32_t z)
bool Gui::getDesignationCoords (int32_t &x, int32_t &y, int32_t &z)
{
x = df::global::selection_rect->start_x;
y = df::global::selection_rect->start_y;
z = df::global::selection_rect->start_z;
x = selection_rect->start_x;
y = selection_rect->start_y;
z = selection_rect->start_z;
return (x == -30000) ? false : true;
}
bool Gui::setDesignationCoords (const int32_t x, const int32_t y, const int32_t z)
{
df::global::selection_rect->start_x = x;
df::global::selection_rect->start_y = y;
df::global::selection_rect->start_z = z;
selection_rect->start_x = x;
selection_rect->start_y = y;
selection_rect->start_z = z;
return true;
}
@ -1880,14 +2197,14 @@ bool Gui::getWindowSize (int32_t &width, int32_t &height)
bool Gui::getMenuWidth(uint8_t &menu_width, uint8_t &area_map_width)
{
menu_width = (*df::global::ui_menu_width)[0];
area_map_width = (*df::global::ui_menu_width)[1];
menu_width = (*ui_menu_width)[0];
area_map_width = (*ui_menu_width)[1];
return true;
}
bool Gui::setMenuWidth(const uint8_t menu_width, const uint8_t area_map_width)
{
(*df::global::ui_menu_width)[0] = menu_width;
(*df::global::ui_menu_width)[1] = area_map_width;
(*ui_menu_width)[0] = menu_width;
(*ui_menu_width)[1] = area_map_width;
return true;
}