OpenLoco/src/OpenLoco/Windows/TownList.cpp

1527 lines
55 KiB
C++

#include "../Audio/Audio.h"
#include "../Config.h"
#include "../Graphics/Colour.h"
#include "../Graphics/ImageIds.h"
#include "../Input.h"
#include "../Interop/Interop.hpp"
#include "../Localisation/FormatArguments.hpp"
#include "../Objects/BuildingObject.h"
#include "../Objects/InterfaceSkinObject.h"
#include "../Objects/ObjectManager.h"
#include "../OpenLoco.h"
#include "../Town.h"
#include "../TownManager.h"
#include "../Ui/Dropdown.h"
#include "../Ui/ScrollView.h"
#include "../Ui/WindowManager.h"
#include "../Utility/Numeric.hpp"
#include "../Widget.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::Ui::Windows::TownList
{
static loco_global<uint32_t, 0x01135C34> dword_1135C34;
static loco_global<Colour_t, 0x01135C61> _buildingColour;
static loco_global<uint8_t, 0x01135C63> _buildingRotation;
static loco_global<uint8_t, 0x01135C65> byte_1135C65;
static loco_global<uint8_t, 0x01135C66> _townSize;
static loco_global<uint8_t, 0x00525FC8> _lastSelectedBuilding;
static loco_global<uint8_t, 0x00525FC9> _lastSelectedMiscBuilding;
static loco_global<Ui::WindowType, 0x00523392> _toolWindowType;
static loco_global<uint16_t, 0x00523390> _toolWindowNumber;
namespace Common
{
enum widx
{
frame,
caption,
close_button,
panel,
tab_town_list,
tab_build_town,
tab_build_buildings,
tab_build_misc_buildings,
};
const uint64_t enabledWidgets = (1 << widx::close_button) | (1 << widx::tab_town_list) | (1 << widx::tab_build_town) | (1 << widx::tab_build_buildings) | (1 << widx::tab_build_misc_buildings);
#define commonWidgets(frameWidth, frameHeight, windowCaptionId) \
makeWidget({ 0, 0 }, { frameWidth, frameHeight }, WidgetType::frame, WindowColour::primary), \
makeWidget({ 1, 1 }, { frameWidth - 2, 13 }, WidgetType::caption_25, WindowColour::primary, windowCaptionId), \
makeWidget({ frameWidth - 15, 2 }, { 13, 13 }, WidgetType::wt_9, WindowColour::primary, ImageIds::close_button, StringIds::tooltip_close_window), \
makeWidget({ 0, 41 }, { frameWidth, 155 }, WidgetType::panel, WindowColour::secondary), \
makeRemapWidget({ 3, 15 }, { 31, 27 }, WidgetType::wt_8, WindowColour::secondary, ImageIds::tab, StringIds::tooltip_town_list), \
makeRemapWidget({ 34, 15 }, { 31, 27 }, WidgetType::wt_8, WindowColour::secondary, ImageIds::tab, StringIds::tooltip_build_town), \
makeRemapWidget({ 65, 15 }, { 31, 27 }, WidgetType::wt_8, WindowColour::secondary, ImageIds::tab, StringIds::tooltip_build_buildings), \
makeRemapWidget({ 96, 15 }, { 31, 27 }, WidgetType::wt_8, WindowColour::secondary, ImageIds::tab, StringIds::tooltip_build_misc_buildings)
static void prepareDraw(Window* self);
static void repositionTabs(Window* self);
static void drawTabs(Window* self, Gfx::Context* context);
static void switchTab(Window* self, WidgetIndex_t widgetIndex);
static void initEvents();
static void refreshTownList(Window* self);
}
namespace TownList
{
static const Gfx::ui_size_t windowSize = { 600, 197 };
static const Gfx::ui_size_t maxDimensions = { 600, 900 };
static const Gfx::ui_size_t minDimensions = { 192, 100 };
static const uint8_t rowHeight = 10;
enum widx
{
sort_town_name = 8,
sort_town_type,
sort_town_population,
sort_town_stations,
scrollview,
};
const uint64_t enabledWidgets = Common::enabledWidgets | (1 << sort_town_name) | (1 << sort_town_type) | (1 << sort_town_population) | (1 << sort_town_stations) | (1 << scrollview);
Widget widgets[] = {
commonWidgets(600, 197, StringIds::title_towns),
makeWidget({ 4, 43 }, { 200, 12 }, WidgetType::wt_14, WindowColour::secondary, ImageIds::null, StringIds::tooltip_sort_by_name),
makeWidget({ 204, 43 }, { 80, 12 }, WidgetType::wt_14, WindowColour::secondary, ImageIds::null, StringIds::tooltip_sort_town_type),
makeWidget({ 284, 43 }, { 70, 12 }, WidgetType::wt_14, WindowColour::secondary, ImageIds::null, StringIds::tooltip_sort_population),
makeWidget({ 354, 43 }, { 70, 12 }, WidgetType::wt_14, WindowColour::secondary, ImageIds::null, StringIds::tooltip_sort_stations),
makeWidget({ 3, 56 }, { 594, 126 }, WidgetType::scrollview, WindowColour::secondary, 2),
widgetEnd(),
};
static WindowEventList events;
enum SortMode : uint16_t
{
Name,
Type,
Population,
Stations,
};
// 0x00499F53
static void prepareDraw(Ui::Window* self)
{
Common::prepareDraw(self);
self->widgets[widx::scrollview].right = self->width - 4;
self->widgets[widx::scrollview].bottom = self->height - 14;
// Reposition header buttons
self->widgets[widx::sort_town_name].right = std::min(203, self->width - 8);
self->widgets[widx::sort_town_type].left = std::min(204, self->width - 8);
self->widgets[widx::sort_town_type].right = std::min(283, self->width - 8);
self->widgets[widx::sort_town_population].left = std::min(284, self->width - 8);
self->widgets[widx::sort_town_population].right = std::min(353, self->width - 8);
self->widgets[widx::sort_town_stations].left = std::min(354, self->width - 8);
self->widgets[widx::sort_town_stations].right = std::min(423, self->width - 8);
// Set header button captions
self->widgets[widx::sort_town_name].text = self->sort_mode == SortMode::Name ? StringIds::table_header_name_desc : StringIds::table_header_name;
self->widgets[widx::sort_town_type].text = self->sort_mode == SortMode::Type ? StringIds::table_header_town_type_desc : StringIds::table_header_town_type;
self->widgets[widx::sort_town_population].text = self->sort_mode == SortMode::Population ? StringIds::table_header_population_desc : StringIds::table_header_population;
self->widgets[widx::sort_town_stations].text = self->sort_mode == SortMode::Stations ? StringIds::table_header_stations_desc : StringIds::table_header_stations;
Common::repositionTabs(self);
}
// 0x0049A0F8
static void drawScroll(Ui::Window* self, Gfx::Context* context, uint32_t scrollIndex)
{
auto shade = Colour::getShade(self->getColour(WindowColour::secondary), 3);
Gfx::clearSingle(*context, shade);
uint16_t yPos = 0;
for (uint16_t i = 0; i < self->var_83C; i++)
{
TownId_t townId = self->row_info[i];
// Skip items outside of view, or irrelevant to the current filter.
if (yPos + rowHeight < context->y || yPos >= yPos + rowHeight + context->height || townId == (uint16_t)-1)
{
yPos += rowHeight;
continue;
}
string_id text_colour_id = StringIds::black_stringid;
// Highlight selection.
if (townId == self->row_hover)
{
Gfx::drawRect(context, 0, yPos, self->width, rowHeight, 0x2000030);
text_colour_id = StringIds::wcolour2_stringid;
}
if (townId == 0xFFFF)
continue;
auto town = TownManager::get(townId);
// Town Name
{
auto args = FormatArguments();
args.push(town->name);
Gfx::drawString_494BBF(*context, 0, yPos, 198, Colour::black, text_colour_id, &args);
}
// Town Type
{
auto args = FormatArguments();
args.push(town->getTownSizeString());
Gfx::drawString_494BBF(*context, 200, yPos, 278, Colour::black, text_colour_id, &args);
}
// Town Population
{
auto args = FormatArguments();
args.push(StringIds::int_32);
args.push(town->population);
Gfx::drawString_494BBF(*context, 280, yPos, 68, Colour::black, text_colour_id, &args);
}
// Town Stations
{
auto args = FormatArguments();
args.push(StringIds::int_32);
args.push<int32_t>(town->num_stations);
Gfx::drawString_494BBF(*context, 350, yPos, 68, Colour::black, text_colour_id, &args);
}
yPos += rowHeight;
}
}
// 0x0049A0A7
static void draw(Ui::Window* self, Gfx::Context* context)
{
self->draw(context);
Common::drawTabs(self, context);
auto args = FormatArguments();
auto xPos = self->x + 4;
auto yPos = self->y + self->height - 12;
if (self->var_83C == 1)
args.push(StringIds::status_towns_singular);
else
args.push(StringIds::status_towns_plural);
args.push(self->var_83C);
Gfx::drawString_494B3F(*context, xPos, yPos, Colour::black, StringIds::black_stringid, &args);
}
// 0x0049A27F
static void onMouseUp(Ui::Window* self, WidgetIndex_t widgetIndex)
{
switch (widgetIndex)
{
case Common::widx::close_button:
WindowManager::close(self);
break;
case Common::widx::tab_town_list:
case Common::widx::tab_build_town:
case Common::widx::tab_build_buildings:
case Common::widx::tab_build_misc_buildings:
Common::switchTab(self, widgetIndex);
break;
case widx::sort_town_name:
case widx::sort_town_type:
case widx::sort_town_population:
case widx::sort_town_stations:
{
auto sortMode = widgetIndex - widx::sort_town_name;
if (self->sort_mode == sortMode)
return;
self->sort_mode = sortMode;
self->invalidate();
self->var_83C = 0;
self->row_hover = -1;
Common::refreshTownList(self);
break;
}
}
}
// 0x0049A56D
static void onScrollMouseDown(Ui::Window* self, int16_t x, int16_t y, uint8_t scroll_index)
{
uint16_t currentRow = y / rowHeight;
if (currentRow > self->var_83C)
return;
int16_t currentTown = self->row_info[currentRow];
if (currentTown == -1)
return;
Town::open(currentTown);
}
// 0x0049A532
static void onScrollMouseOver(Ui::Window* self, int16_t x, int16_t y, uint8_t scroll_index)
{
self->flags &= ~(WindowFlags::not_scroll_view);
uint16_t currentRow = y / rowHeight;
int16_t currentTown = -1;
if (currentRow < self->var_83C)
currentTown = self->row_info[currentRow];
if (self->row_hover == currentTown)
return;
self->row_hover = currentTown;
self->invalidate();
}
// 0x00499EC9
static bool orderByName(const OpenLoco::Town& lhs, const OpenLoco::Town& rhs)
{
char lhsString[256] = { 0 };
StringManager::formatString(lhsString, lhs.name);
char rhsString[256] = { 0 };
StringManager::formatString(rhsString, rhs.name);
return strcmp(lhsString, rhsString) < 0;
}
// 0x00499F28
static bool orderByPopulation(const OpenLoco::Town& lhs, const OpenLoco::Town& rhs)
{
auto lhsPopulation = lhs.population;
auto rhsPopulation = rhs.population;
return rhsPopulation < lhsPopulation;
}
// 0x00499F0A Left this in to match the x86 code. can be replaced with orderByPopulation
static bool orderByType(const OpenLoco::Town& lhs, const OpenLoco::Town& rhs)
{
auto lhsSize = lhs.size;
auto rhsSize = rhs.size;
if (rhsSize != lhsSize)
{
return rhsSize < lhsSize;
}
else
{
return orderByPopulation(lhs, rhs);
}
}
// 0x00499F3B
static bool orderByStations(const OpenLoco::Town& lhs, const OpenLoco::Town& rhs)
{
auto lhsStations = lhs.num_stations;
auto rhsStations = rhs.num_stations;
return rhsStations < lhsStations;
}
// 0x00499EC9, 0x00499F0A, 0x00499F28, 0x00499F3B
static bool getOrder(const SortMode mode, OpenLoco::Town& lhs, OpenLoco::Town& rhs)
{
switch (mode)
{
case SortMode::Name:
return orderByName(lhs, rhs);
case SortMode::Type:
return orderByType(lhs, rhs);
case SortMode::Population:
return orderByPopulation(lhs, rhs);
case SortMode::Stations:
return orderByStations(lhs, rhs);
}
return false;
}
// 0x00499E0B
static void updateTownList(Window* self)
{
auto chosenTown = -1;
auto i = -1;
for (auto& town : TownManager::towns())
{
i++;
if (town.empty())
continue;
if ((town.flags & TownFlags::sorted) != 0)
continue;
if (chosenTown == -1)
{
chosenTown = i;
continue;
}
if (getOrder(SortMode(self->sort_mode), town, *TownManager::get(chosenTown)))
{
chosenTown = i;
}
}
if (chosenTown != -1)
{
bool shouldInvalidate = false;
TownManager::get(chosenTown)->flags |= TownFlags::sorted;
if (chosenTown != self->row_info[self->row_count])
{
self->row_info[self->row_count] = chosenTown;
shouldInvalidate = true;
}
self->row_count += 1;
if (self->row_count > self->var_83C)
{
self->var_83C = self->row_count;
shouldInvalidate = true;
}
if (shouldInvalidate)
{
self->invalidate();
}
}
else
{
if (self->var_83C != self->row_count)
{
self->var_83C = self->row_count;
self->invalidate();
}
Common::refreshTownList(self);
}
}
// 0x0049A4A0
static void onUpdate(Window* self)
{
self->frame_no++;
self->callPrepareDraw();
WindowManager::invalidateWidget(WindowType::townList, self->number, self->current_tab + Common::widx::tab_town_list);
// Add three towns every tick.
updateTownList(self);
updateTownList(self);
updateTownList(self);
}
// 0x0049A4D0
static void event_08(Window* self)
{
self->flags |= WindowFlags::not_scroll_view;
}
// 0x0049A4D8
static void event_09(Window* self)
{
if (!(self->flags & WindowFlags::not_scroll_view))
return;
if (self->row_hover == -1)
return;
self->row_hover = -1;
self->invalidate();
}
// 0x0049A4FA
static void getScrollSize(Ui::Window* self, uint32_t scrollIndex, uint16_t* scrollWidth, uint16_t* scrollHeight)
{
*scrollHeight = rowHeight * self->var_83C;
}
// 0x00491841
static std::optional<FormatArguments> tooltip(Ui::Window* window, WidgetIndex_t widgetIndex)
{
FormatArguments args{};
args.push(StringIds::tooltip_scroll_town_list);
return args;
}
// 0x004919A4
static Ui::CursorId cursor(Window* self, int16_t widgetIdx, int16_t xPos, int16_t yPos, Ui::CursorId fallback)
{
if (widgetIdx != widx::scrollview)
return fallback;
uint16_t currentIndex = yPos / rowHeight;
if (currentIndex < self->var_83C && self->row_info[currentIndex] != -1)
return CursorId::handPointer;
return fallback;
}
// 0x0049A37E
static void tabReset(Window* self)
{
self->min_width = minDimensions.width;
self->min_height = minDimensions.height;
self->max_width = maxDimensions.width;
self->max_height = maxDimensions.height;
self->width = windowSize.width;
self->height = windowSize.height;
self->var_83C = 0;
self->row_hover = -1;
Common::refreshTownList(self);
}
static void initEvents()
{
events.draw = draw;
events.cursor = cursor;
events.draw_scroll = drawScroll;
events.event_08 = event_08;
events.event_09 = event_09;
events.get_scroll_size = getScrollSize;
events.on_mouse_up = onMouseUp;
events.on_update = onUpdate;
events.scroll_mouse_down = onScrollMouseDown;
events.scroll_mouse_over = onScrollMouseOver;
events.prepare_draw = prepareDraw;
events.tooltip = tooltip;
}
}
// 0x00499C83
Window* open()
{
auto window = WindowManager::bringToFront(WindowType::townList, 0);
if (window != nullptr)
{
window->callOnMouseUp(Common::widx::tab_town_list);
}
else
{
// 0x00499CFC
auto origin = Gfx::point_t(Ui::width() - TownList::windowSize.width, 30);
window = WindowManager::createWindow(
WindowType::townList,
origin,
TownList::windowSize,
WindowFlags::resizable,
&TownList::events);
window->number = 0;
window->current_tab = 0;
window->frame_no = 0;
window->sort_mode = 0;
window->var_83C = 0;
window->row_hover = -1;
Common::refreshTownList(window);
WindowManager::sub_4CEE0B(window);
window->min_width = TownList::minDimensions.width;
window->min_height = TownList::minDimensions.height;
window->max_width = TownList::maxDimensions.width;
window->max_height = TownList::maxDimensions.height;
window->flags |= WindowFlags::resizable;
auto skin = ObjectManager::get<InterfaceSkinObject>();
window->setColour(WindowColour::primary, skin->colour_0B);
window->setColour(WindowColour::secondary, skin->colour_0C);
// 0x00499CFC end
// TODO: only needs to be called once.
window->width = TownList::windowSize.width;
window->height = TownList::windowSize.height;
Common::initEvents();
window->invalidate();
window->widgets = TownList::widgets;
window->enabled_widgets = TownList::enabledWidgets;
if (isEditorMode() || isSandboxMode())
window->disabled_widgets = 0;
else
window->disabled_widgets |= (1 << Common::widx::tab_build_town) | (1 << Common::widx::tab_build_buildings) | (1 << Common::widx::tab_build_misc_buildings);
window->activated_widgets = 0;
window->holdable_widgets = 0;
window->callOnResize();
window->callPrepareDraw();
window->initScrollWidgets();
}
return window;
}
namespace BuildTowns
{
static const Gfx::ui_size_t windowSize = { 220, 87 };
enum widx
{
current_size = 8,
select_size,
};
const uint64_t enabledWidgets = Common::enabledWidgets | (1 << current_size) | (1 << select_size);
Widget widgets[] = {
commonWidgets(220, 87, StringIds::title_build_new_towns),
makeWidget({ 100, 45 }, { 117, 12 }, WidgetType::wt_18, WindowColour::secondary, ImageIds::null, StringIds::tooltip_select_town_size),
makeWidget({ 205, 46 }, { 11, 10 }, WidgetType::wt_11, WindowColour::secondary, StringIds::dropdown),
widgetEnd(),
};
static WindowEventList events;
// 0x0049A59A
static void prepareDraw(Ui::Window* self)
{
Common::prepareDraw(self);
Common::repositionTabs(self);
static string_id townSizeNames[9] = {
StringIds::tooltip_select_town_size,
StringIds::town_size_1,
StringIds::town_size_2,
StringIds::town_size_3,
StringIds::town_size_4,
StringIds::town_size_5,
StringIds::town_size_6,
StringIds::town_size_7,
StringIds::town_size_8,
};
self->widgets[widx::current_size].text = townSizeNames[_townSize];
}
// 0x0049A627
static void draw(Ui::Window* self, Gfx::Context* context)
{
self->draw(context);
Common::drawTabs(self, context);
Gfx::drawString_494B3F(*context, self->x + 3, self->y + self->widgets[widx::current_size].top + 1, Colour::black, StringIds::town_size_label);
Gfx::drawString_494B3F(*context, self->x + 3, self->y + self->height - 13, Colour::black, StringIds::select_town_size);
}
// 0x0049A675
static void onMouseUp(Ui::Window* self, WidgetIndex_t widgetIndex)
{
switch (widgetIndex)
{
case Common::widx::close_button:
WindowManager::close(self);
break;
case Common::widx::tab_town_list:
case Common::widx::tab_build_town:
case Common::widx::tab_build_buildings:
case Common::widx::tab_build_misc_buildings:
Common::switchTab(self, widgetIndex);
break;
}
}
// 0x0049A7F2
static void onUpdate(Window* self)
{
self->frame_no++;
self->callPrepareDraw();
WindowManager::invalidateWidget(WindowType::townList, self->number, self->current_tab + Common::widx::tab_town_list);
if ((!Input::hasFlag(Input::Flags::toolActive)) || self->type != _toolWindowType || self->number != _toolWindowNumber)
{
WindowManager::close(self);
}
}
// 0x0049A697
static void onDropdown(Window* self, Ui::WidgetIndex_t widgetIndex, int16_t itemIndex)
{
if (widgetIndex != widx::select_size)
return;
if (itemIndex != -1)
{
itemIndex++;
_townSize = itemIndex;
self->invalidate();
}
}
// 0x0049A7C1
static void onToolAbort(Window& self, const WidgetIndex_t widgetIndex)
{
Ui::Windows::hideGridlines();
}
// 0x0049A710
static void onToolUpdate(Window& self, const WidgetIndex_t widgetIndex, const int16_t x, const int16_t y)
{
registers regs;
regs.esi = (int32_t)&self;
regs.dx = widgetIndex;
regs.ax = x;
regs.bx = y;
call(0x0049A710, regs);
}
// 0x0049A75E
static void onToolDown(Window& self, const WidgetIndex_t widgetIndex, const int16_t x, const int16_t y)
{
registers regs;
regs.esi = (int32_t)&self;
regs.dx = widgetIndex;
regs.ax = x;
regs.bx = y;
call(0x0049A75E, regs);
}
// 0x0049A69E
static void populateTownSizeSelect(Window* self, Widget* widget)
{
registers regs;
regs.edi = (int32_t)widget;
regs.esi = (int32_t)self;
call(0x0049A69E, regs);
}
// 0x0049A690
static void onMouseDown(Window* self, WidgetIndex_t widgetIndex)
{
if (widgetIndex == widx::select_size)
populateTownSizeSelect(self, &self->widgets[widgetIndex]);
}
// 0x0049A844
static void onResize(Window* self)
{
self->setSize(windowSize, windowSize);
}
// 0x0049A7C7
static void onClose(Window* self)
{
if (Input::isToolActive(self->type, self->number))
Input::toolCancel();
}
// 0x0049A3BE
static void tabReset(Window* self)
{
self->min_width = windowSize.width;
self->min_height = windowSize.height;
self->max_width = windowSize.width;
self->max_width = windowSize.height;
self->width = windowSize.width;
self->height = windowSize.height;
Input::toolSet(self, Common::widx::tab_build_town, 38);
Input::setFlag(Input::Flags::flag6);
Ui::Windows::showGridlines();
}
static void initEvents()
{
events.draw = draw;
events.on_close = onClose;
events.on_dropdown = onDropdown;
events.on_resize = onResize;
events.on_tool_update = onToolUpdate;
events.on_tool_down = onToolDown;
events.on_mouse_up = onMouseUp;
events.on_mouse_down = onMouseDown;
events.on_update = onUpdate;
events.prepare_draw = prepareDraw;
events.on_tool_abort = onToolAbort;
}
}
namespace BuildBuildings
{
static const Gfx::ui_size_t windowSize = { 600, 172 };
static const uint8_t rowHeight = 112;
enum widx
{
scrollview = 8,
rotate_object,
object_colour,
};
const uint64_t enabledWidgets = Common::enabledWidgets | (1 << scrollview) | (1 << rotate_object) | (1 << object_colour);
Widget widgets[] = {
commonWidgets(640, 172, StringIds::title_build_new_buildings),
makeWidget({ 2, 45 }, { 573, 112 }, WidgetType::scrollview, WindowColour::secondary, 2),
makeWidget({ 575, 46 }, { 24, 24 }, WidgetType::wt_9, WindowColour::secondary, ImageIds::rotate_object, StringIds::rotate_object_90),
makeWidget({ 579, 91 }, { 16, 16 }, WidgetType::wt_10, WindowColour::secondary, ImageIds::null, StringIds::tooltip_object_colour),
widgetEnd(),
};
static WindowEventList events;
// 0x0049A8A6
static void prepareDraw(Ui::Window* self)
{
self->widgets[widx::object_colour].image = (1 << 30) | Gfx::recolour(ImageIds::colour_swatch_recolourable, _buildingColour);
self->widgets[widx::object_colour].type = WidgetType::none;
if (self->row_hover != -1)
{
auto buildingObj = ObjectManager::get<BuildingObject>(self->row_hover);
if (buildingObj->colours != 0)
self->widgets[widx::object_colour].type = WidgetType::wt_10;
}
Common::prepareDraw(self);
self->widgets[widx::scrollview].right = self->width - 26;
self->widgets[widx::scrollview].bottom = self->height - 14;
self->widgets[widx::rotate_object].left = self->width - 25;
self->widgets[widx::rotate_object].right = self->width - 2;
self->widgets[widx::object_colour].left = self->width - 21;
self->widgets[widx::object_colour].right = self->width - 6;
self->widgets[Common::widx::caption].text = StringIds::title_build_new_buildings;
if (self->current_tab == Common::widx::tab_build_misc_buildings - Common::widx::tab_town_list)
self->widgets[Common::widx::caption].text = StringIds::title_build_new_misc_buildings;
Common::repositionTabs(self);
}
// 0x0049A9C2
static void draw(Ui::Window* self, Gfx::Context* context)
{
self->draw(context);
Common::drawTabs(self, context);
auto buildingId = self->var_846;
if (buildingId == 0xFFFF)
{
buildingId = self->row_hover;
if (buildingId == 0xFFFF)
return;
}
auto buildingObj = ObjectManager::get<BuildingObject>(buildingId);
Gfx::drawString_494BBF(*context, self->x + 3, self->y + self->height - 13, self->width - 19, Colour::black, StringIds::black_stringid, &buildingObj->name);
}
// 0x0049AB31
static void onMouseUp(Ui::Window* self, WidgetIndex_t widgetIndex)
{
switch (widgetIndex)
{
case Common::widx::close_button:
WindowManager::close(self);
break;
case Common::widx::tab_town_list:
case Common::widx::tab_build_town:
case Common::widx::tab_build_buildings:
case Common::widx::tab_build_misc_buildings:
Common::switchTab(self, widgetIndex);
break;
case widx::rotate_object:
if (_buildingRotation < 3)
_buildingRotation++;
else
_buildingRotation = 0;
self->invalidate();
break;
}
}
// 0x0049AD51
static void onUpdate(Window* self)
{
if (!Input::hasFlag(Input::Flags::flag5))
{
auto cursor = Input::getMouseLocation();
auto xPos = cursor.x;
auto yPos = cursor.y;
Window* activeWindow = WindowManager::findAt(xPos, yPos);
if (activeWindow == self)
{
xPos -= self->x;
xPos += 26;
yPos -= self->y;
if ((yPos < 42) || (xPos <= self->width))
{
xPos = cursor.x;
yPos = cursor.y;
WidgetIndex_t activeWidget = self->findWidgetAt(xPos, yPos);
if (activeWidget > Common::widx::panel)
{
self->saved_view.mapX += 1;
if (self->saved_view.mapX >= 8)
{
auto y = std::min(self->scroll_areas[0].contentHeight - 1 + 60, 500);
if (Ui::height() < 600)
{
y = std::min(y, 276);
}
self->min_width = windowSize.width;
self->min_height = y;
self->max_width = windowSize.width;
self->max_height = y;
}
else
{
if (Input::state() != Input::State::scrollLeft)
{
self->min_width = windowSize.width;
self->min_height = windowSize.height;
self->max_width = windowSize.width;
self->max_height = windowSize.height;
}
}
}
}
}
else
{
self->saved_view.mapX = 0;
if (Input::state() != Input::State::scrollLeft)
{
self->min_width = windowSize.width;
self->min_height = windowSize.height;
self->max_width = windowSize.width;
self->max_height = windowSize.height;
}
}
}
self->frame_no++;
self->callPrepareDraw();
WindowManager::invalidateWidget(WindowType::townList, self->number, self->current_tab + Common::widx::tab_town_list);
if (!Input::isToolActive(self->type, self->number))
WindowManager::close(self);
}
// 0x0049AB59
static void onDropdown(Window* self, Ui::WidgetIndex_t widgetIndex, int16_t itemIndex)
{
if (widgetIndex != widx::object_colour)
return;
if (itemIndex == -1)
return;
_buildingColour = Dropdown::getItemArgument(itemIndex, 4);
self->invalidate();
}
// 0x0049B37F
static void sub_49B37F()
{
registers regs;
call(0x0049B37F, regs);
}
// 0x0049AD46
static void onToolAbort(Window& self, const WidgetIndex_t widgetIndex)
{
sub_49B37F();
Ui::Windows::hideGridlines();
}
// 0x0049ABF0
static void onToolUpdate(Window& self, const WidgetIndex_t widgetIndex, const int16_t x, const int16_t y)
{
registers regs;
regs.esi = (int32_t)&self;
regs.dx = widgetIndex;
regs.ax = x;
regs.bx = y;
call(0x0049ABF0, regs);
}
// 0x0049ACBD
static void onToolDown(Window& self, const WidgetIndex_t widgetIndex, const int16_t x, const int16_t y)
{
registers regs;
regs.esi = (int32_t)&self;
regs.dx = widgetIndex;
regs.ax = x;
regs.bx = y;
call(0x0049ACBD, regs);
}
// 0x0049AB52
static void onMouseDown(Window* self, WidgetIndex_t widgetIndex)
{
if (widgetIndex == widx::object_colour)
{
registers regs;
regs.edx = widgetIndex;
regs.esi = (int32_t)self;
regs.edi = (int32_t)&self->widgets[widgetIndex];
call(0x0049AB72, regs);
}
}
// 0x0049B2B5
static void updateActiveThumb(Window* self)
{
uint16_t scrollHeight = 0;
self->callGetScrollSize(0, 0, &scrollHeight);
self->scroll_areas[0].contentHeight = scrollHeight;
auto i = 0;
for (; i <= self->var_83C; i++)
{
if (self->row_info[i] == self->row_hover)
break;
}
if (i >= self->var_83C)
i = 0;
i = (i / 5) * rowHeight;
self->scroll_areas[0].contentOffsetY = i;
Ui::ScrollView::updateThumbs(self, widx::scrollview);
}
// 0x0049AF98
static void onResize(Window* self)
{
self->invalidate();
Gfx::ui_size_t minWindowSize = { self->min_width, self->min_height };
Gfx::ui_size_t maxWindowSize = { self->max_width, self->max_height };
bool hasResized = self->setSize(minWindowSize, maxWindowSize);
if (hasResized)
updateActiveThumb(self);
}
// 0x0049AE83
static void getScrollSize(Ui::Window* self, uint32_t scrollIndex, uint16_t* scrollWidth, uint16_t* scrollHeight)
{
*scrollHeight = (4 + self->var_83C) / 5;
if (*scrollHeight == 0)
*scrollHeight += 1;
*scrollHeight *= rowHeight;
}
// 0x0049ABBB
static std::optional<FormatArguments> tooltip(Ui::Window* window, WidgetIndex_t widgetIndex)
{
FormatArguments args{};
args.push(StringIds::tooltip_scroll_building_list);
return args;
}
// 0x0049AA1C
static void drawScroll(Ui::Window* self, Gfx::Context* context, uint32_t scrollIndex)
{
auto shade = Colour::getShade(self->getColour(WindowColour::secondary), 3);
Gfx::clearSingle(*context, shade);
uint16_t xPos = 0;
uint16_t yPos = 0;
for (uint16_t i = 0; i < self->var_83C; i++)
{
if (self->row_info[i] != self->row_hover)
{
if (self->row_info[i] == self->var_846)
{
Gfx::drawRectInset(context, xPos, yPos, 112, 112, self->getColour(WindowColour::secondary), Colour::translucent_flag);
}
}
else
{
Gfx::drawRectInset(context, xPos, yPos, 112, 112, self->getColour(WindowColour::secondary), (Colour::translucent_flag | Colour::outline_flag));
}
auto buildingObj = ObjectManager::get<BuildingObject>(self->row_info[i]);
Gfx::Context* clipped = nullptr;
if (Gfx::clipContext(&clipped, context, xPos + 1, yPos + 1, 110, 110))
{
Colour_t colour = _buildingColour;
if (self->row_hover != self->row_info[i])
{
colour = Utility::bitScanReverse(buildingObj->colours);
if (colour == 0xFF)
colour = 0;
}
buildingObj->drawBuilding(clipped, _buildingRotation, 56, 96, colour);
}
xPos += 112;
if (xPos >= 112 * 5) // full row
{
xPos = 0;
yPos += 112;
}
}
}
// 0x0049B304
static void updateBuildingColours(Window* self)
{
if (self->row_hover != -1)
{
auto buildingObj = ObjectManager::get<BuildingObject>(self->row_hover);
if (buildingObj->colours != 0)
{
Colour_t colour = Utility::bitScanReverse(buildingObj->colours);
if (colour == 0xFF)
colour = 0;
_buildingColour = colour;
}
}
}
static int getRowIndex(int16_t x, int16_t y)
{
return (x / 112) + (y / 112) * 5;
}
// 0x0049AEFD
static void onScrollMouseDown(Ui::Window* self, int16_t x, int16_t y, uint8_t scroll_index)
{
int16_t xPos = (x / 112);
int16_t yPos = (y / 112) * 5;
auto index = getRowIndex(x, y);
for (auto i = 0; i < self->var_83C; i++)
{
auto rowInfo = self->row_info[i];
index--;
if (index < 0)
{
self->row_hover = rowInfo;
if (self->current_tab == Common::widx::tab_build_misc_buildings - Common::widx::tab_town_list)
_lastSelectedMiscBuilding = static_cast<uint8_t>(rowInfo);
else
_lastSelectedBuilding = static_cast<uint8_t>(rowInfo);
updateBuildingColours(self);
int32_t pan = (self->width >> 1) + self->x;
Map::Pos3 loc = { xPos, yPos, static_cast<int16_t>(pan) };
Audio::playSound(Audio::SoundId::clickDown, loc, pan);
self->saved_view.mapX = -16;
dword_1135C34 = 0x80000000;
byte_1135C65 = 0;
self->invalidate();
break;
}
}
}
// 0x0049AEBA
static void onScrollMouseOver(Ui::Window* self, int16_t x, int16_t y, uint8_t scroll_index)
{
auto index = getRowIndex(x, y);
uint16_t rowInfo = y;
auto i = 0;
for (; i < self->var_83C; i++)
{
rowInfo = self->row_info[i];
index--;
if (index < 0)
{
self->var_846 = rowInfo;
self->invalidate();
break;
}
}
}
// 0x0049ABC5
static void onClose(Window* self)
{
if (Input::isToolActive(self->type, self->number))
Input::toolCancel();
}
// 0x0049AEA1
static void event_08(Window* self)
{
if (self->var_846 != 0xFFFF)
{
self->var_846 = 0xFFFF;
self->invalidate();
}
}
// 0x0049B206
static void updateBuildingList(Window* self)
{
auto buildingCount = 0;
for (auto i = 0; i < 128; i++)
{
auto buildingObj = ObjectManager::get<BuildingObject>(i);
if (buildingObj == nullptr)
continue;
if (self->current_tab == Common::widx::tab_build_misc_buildings - Common::widx::tab_town_list)
{
if (!(buildingObj->flags & BuildingObjectFlags::misc_building))
continue;
if ((buildingObj->flags & BuildingObjectFlags::is_headquarters) != 0)
continue;
}
else
{
if ((buildingObj->flags & BuildingObjectFlags::misc_building) != 0)
continue;
}
self->row_info[buildingCount] = i;
buildingCount++;
}
self->var_83C = buildingCount;
auto rowHover = -1;
auto lastSelectedBuilding = _lastSelectedBuilding;
if (self->current_tab == Common::widx::tab_build_misc_buildings - Common::widx::tab_town_list)
lastSelectedBuilding = _lastSelectedMiscBuilding;
if (lastSelectedBuilding != 0xFF)
{
for (auto i = 0; i <= self->var_83C; i++)
{
if (lastSelectedBuilding == self->row_info[i])
{
rowHover = lastSelectedBuilding;
break;
}
}
}
if (rowHover == -1 && self->var_83C != 0)
{
rowHover = self->row_info[0];
}
self->row_hover = rowHover;
updateActiveThumb(self);
updateBuildingColours(self);
}
// 0x0049A3FF
static void tabReset(Window* self)
{
self->min_width = windowSize.width;
self->min_height = windowSize.height;
self->max_width = windowSize.width;
self->max_width = windowSize.height;
self->width = windowSize.width;
self->height = windowSize.height;
auto tab = Common::widx::tab_build_buildings;
if (self->current_tab == Common::widx::tab_build_misc_buildings - Common::widx::tab_town_list)
tab = Common::widx::tab_build_misc_buildings;
Input::toolSet(self, tab, 39);
Input::setFlag(Input::Flags::flag6);
Ui::Windows::showGridlines();
static loco_global<uint8_t, 0x01135C60> byte_1135C60;
byte_1135C60 = 0;
dword_1135C34 = 0x80000000;
self->var_83C = 0;
self->row_hover = -1;
self->var_846 = -1;
updateBuildingList(self);
updateBuildingColours(self);
byte_1135C65 = 0;
}
static void initEvents()
{
events.draw = draw;
events.on_close = onClose;
events.on_resize = onResize;
events.draw_scroll = drawScroll;
events.event_08 = event_08;
events.on_dropdown = onDropdown;
events.on_tool_update = onToolUpdate;
events.on_tool_down = onToolDown;
events.on_mouse_down = onMouseDown;
events.get_scroll_size = getScrollSize;
events.on_mouse_up = onMouseUp;
events.on_update = onUpdate;
events.scroll_mouse_down = onScrollMouseDown;
events.scroll_mouse_over = onScrollMouseOver;
events.prepare_draw = prepareDraw;
events.tooltip = tooltip;
events.on_tool_abort = onToolAbort;
}
}
namespace Common
{
struct TabInformation
{
Widget* widgets;
const widx widgetIndex;
WindowEventList* events;
const uint64_t enabledWidgets;
};
static TabInformation tabInformationByTabOffset[] = {
{ TownList::widgets, widx::tab_town_list, &TownList::events, TownList::enabledWidgets },
{ BuildTowns::widgets, widx::tab_build_town, &BuildTowns::events, BuildTowns::enabledWidgets },
{ BuildBuildings::widgets, widx::tab_build_buildings, &BuildBuildings::events, BuildBuildings::enabledWidgets },
{ BuildBuildings::widgets, widx::tab_build_misc_buildings, &BuildBuildings::events, BuildBuildings::enabledWidgets },
};
static void prepareDraw(Window* self)
{
// Reset tab widgets if needed
const auto& tabWidgets = tabInformationByTabOffset[self->current_tab].widgets;
if (self->widgets != tabWidgets)
{
self->widgets = tabWidgets;
self->initScrollWidgets();
}
// Activate the current tab
self->activated_widgets &= ~((1ULL << tab_town_list) | (1ULL << tab_build_town) | (1ULL << tab_build_buildings) | (1ULL << tab_build_misc_buildings));
self->activated_widgets |= (1ULL << Common::tabInformationByTabOffset[self->current_tab].widgetIndex);
self->widgets[Common::widx::frame].right = self->width - 1;
self->widgets[Common::widx::frame].bottom = self->height - 1;
self->widgets[Common::widx::panel].right = self->width - 1;
self->widgets[Common::widx::panel].bottom = self->height - 1;
self->widgets[Common::widx::caption].right = self->width - 2;
self->widgets[Common::widx::close_button].left = self->width - 15;
self->widgets[Common::widx::close_button].right = self->width - 3;
}
// 0x0049B004 and 0x0049B00A
static void repositionTabs(Window* self)
{
int16_t new_tab_x = self->widgets[widx::tab_town_list].left;
int16_t tab_width = self->widgets[widx::tab_town_list].right - new_tab_x;
for (auto& tabInfo : tabInformationByTabOffset)
{
if (self->isDisabled(tabInfo.widgetIndex))
continue;
Widget& tab = self->widgets[tabInfo.widgetIndex];
tab.left = new_tab_x;
new_tab_x += tab_width;
tab.right = new_tab_x++;
}
}
// 0x0049B054
static void drawTabs(Window* self, Gfx::Context* context)
{
auto skin = ObjectManager::get<InterfaceSkinObject>();
// Town List Tab
{
uint32_t imageId = skin->img;
imageId += InterfaceSkin::ImageIds::toolbar_menu_towns;
Widget::drawTab(self, context, imageId, widx::tab_town_list);
}
// Build New Towns Tab
{
static const uint32_t buildNewTownsImageIds[] = {
InterfaceSkin::ImageIds::build_town_frame_0,
InterfaceSkin::ImageIds::build_town_frame_1,
InterfaceSkin::ImageIds::build_town_frame_2,
InterfaceSkin::ImageIds::build_town_frame_3,
InterfaceSkin::ImageIds::build_town_frame_4,
InterfaceSkin::ImageIds::build_town_frame_5,
InterfaceSkin::ImageIds::build_town_frame_6,
InterfaceSkin::ImageIds::build_town_frame_7,
InterfaceSkin::ImageIds::build_town_frame_8,
InterfaceSkin::ImageIds::build_town_frame_9,
InterfaceSkin::ImageIds::build_town_frame_10,
InterfaceSkin::ImageIds::build_town_frame_11,
InterfaceSkin::ImageIds::build_town_frame_12,
InterfaceSkin::ImageIds::build_town_frame_13,
InterfaceSkin::ImageIds::build_town_frame_14,
InterfaceSkin::ImageIds::build_town_frame_15,
};
uint32_t imageId = skin->img;
if (self->current_tab == widx::tab_build_town - widx::tab_town_list)
imageId += buildNewTownsImageIds[(self->frame_no / 2) % std::size(buildNewTownsImageIds)];
else
imageId += buildNewTownsImageIds[0];
Widget::drawTab(self, context, imageId, widx::tab_build_town);
}
// Build New Buildings Tab
{
static const uint32_t buildBuildingsImageIds[] = {
InterfaceSkin::ImageIds::build_buildings_frame_0,
InterfaceSkin::ImageIds::build_buildings_frame_1,
InterfaceSkin::ImageIds::build_buildings_frame_2,
InterfaceSkin::ImageIds::build_buildings_frame_3,
InterfaceSkin::ImageIds::build_buildings_frame_4,
InterfaceSkin::ImageIds::build_buildings_frame_5,
InterfaceSkin::ImageIds::build_buildings_frame_6,
InterfaceSkin::ImageIds::build_buildings_frame_7,
InterfaceSkin::ImageIds::build_buildings_frame_8,
InterfaceSkin::ImageIds::build_buildings_frame_9,
InterfaceSkin::ImageIds::build_buildings_frame_10,
InterfaceSkin::ImageIds::build_buildings_frame_11,
InterfaceSkin::ImageIds::build_buildings_frame_12,
InterfaceSkin::ImageIds::build_buildings_frame_13,
InterfaceSkin::ImageIds::build_buildings_frame_14,
InterfaceSkin::ImageIds::build_buildings_frame_15,
};
uint32_t imageId = skin->img;
if (self->current_tab == widx::tab_build_buildings - widx::tab_town_list)
imageId += buildBuildingsImageIds[(self->frame_no / 2) % std::size(buildBuildingsImageIds)];
else
imageId += buildBuildingsImageIds[0];
Widget::drawTab(self, context, imageId, widx::tab_build_buildings);
}
// Build New Misc Buildings Tab
{
static const uint32_t buildMiscBuildingsImageIds[] = {
InterfaceSkin::ImageIds::build_misc_buildings_frame_0,
InterfaceSkin::ImageIds::build_misc_buildings_frame_1,
InterfaceSkin::ImageIds::build_misc_buildings_frame_2,
InterfaceSkin::ImageIds::build_misc_buildings_frame_3,
InterfaceSkin::ImageIds::build_misc_buildings_frame_4,
InterfaceSkin::ImageIds::build_misc_buildings_frame_5,
InterfaceSkin::ImageIds::build_misc_buildings_frame_6,
InterfaceSkin::ImageIds::build_misc_buildings_frame_7,
InterfaceSkin::ImageIds::build_misc_buildings_frame_8,
InterfaceSkin::ImageIds::build_misc_buildings_frame_9,
InterfaceSkin::ImageIds::build_misc_buildings_frame_10,
InterfaceSkin::ImageIds::build_misc_buildings_frame_11,
InterfaceSkin::ImageIds::build_misc_buildings_frame_12,
InterfaceSkin::ImageIds::build_misc_buildings_frame_13,
InterfaceSkin::ImageIds::build_misc_buildings_frame_14,
InterfaceSkin::ImageIds::build_misc_buildings_frame_15,
};
uint32_t imageId = skin->img;
if (self->current_tab == widx::tab_build_misc_buildings - widx::tab_town_list)
imageId += buildMiscBuildingsImageIds[(self->frame_no / 2) % std::size(buildMiscBuildingsImageIds)];
else
imageId += buildMiscBuildingsImageIds[0];
Widget::drawTab(self, context, imageId, widx::tab_build_misc_buildings);
}
}
//0x0049A2E2
static void switchTab(Window* self, WidgetIndex_t widgetIndex)
{
if (Input::isToolActive(self->type, self->number))
Input::toolCancel();
self->current_tab = widgetIndex - widx::tab_town_list;
self->frame_no = 0;
self->flags &= ~(WindowFlags::flag_16);
self->viewportRemove(0);
const auto& tabInfo = tabInformationByTabOffset[widgetIndex - widx::tab_town_list];
self->enabled_widgets = tabInfo.enabledWidgets;
self->holdable_widgets = 0;
self->event_handlers = tabInfo.events;
self->activated_widgets = 0;
self->widgets = tabInfo.widgets;
if (isEditorMode() || isSandboxMode())
self->disabled_widgets = 0;
else
self->disabled_widgets |= (1 << Common::widx::tab_build_town) | (1 << Common::widx::tab_build_buildings) | (1 << Common::widx::tab_build_misc_buildings);
self->invalidate();
if (self->current_tab == widx::tab_town_list - widx::tab_town_list)
TownList::tabReset(self);
if (self->current_tab == widx::tab_build_town - widx::tab_town_list)
BuildTowns::tabReset(self);
if (self->current_tab == widx::tab_build_buildings - widx::tab_town_list || self->current_tab == widx::tab_build_misc_buildings - widx::tab_town_list)
BuildBuildings::tabReset(self);
self->callOnResize();
self->callPrepareDraw();
self->initScrollWidgets();
self->invalidate();
self->moveInsideScreenEdges();
}
// 0x00499DDE
static void refreshTownList(Window* self)
{
self->row_count = 0;
for (auto& town : TownManager::towns())
{
if (town.empty())
continue;
town.flags &= ~TownFlags::sorted;
}
}
static void initEvents()
{
TownList::initEvents();
BuildTowns::initEvents();
BuildBuildings::initEvents();
}
}
}