1
0
Fork 0

Feature: Add a window for joining depots.

pull/8480/head
J0anJosep 2023-08-25 16:42:58 +02:00
parent c6e69c03c9
commit 791124f058
12 changed files with 365 additions and 0 deletions

View File

@ -100,6 +100,7 @@ void Depot::AfterAddRemove(TileArea ta, bool adding)
this->ta.Add(tile);
}
VehicleType veh_type = this->veh_type;
if (this->ta.tile != INVALID_TILE) {
this->RescanDepotTiles();
assert(!this->depot_tiles.empty());
@ -107,6 +108,8 @@ void Depot::AfterAddRemove(TileArea ta, bool adding)
} else {
delete this;
}
InvalidateWindowData(WC_SELECT_DEPOT, veh_type);
}
/**

View File

@ -75,3 +75,69 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str
}
return CommandCost();
}
/**
* Look for or check depot to join to, building a new one if necessary.
* @param ta The area of the new depot.
* @param veh_type The vehicle type of the new depot.
* @param join_to DepotID of the depot to join to.
* If INVALID_DEPOT, look whether it is possible to join to an existing depot.
* If NEW_DEPOT, directly create a new depot.
* @param depot The pointer to the depot.
* @param adjacent Whether adjacent depots are allowed
* @return command cost with the error or 'okay'
*/
CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &join_to, Depot *&depot, bool adjacent, DoCommandFlag flags)
{
/* Look for a joining depot if needed. */
if (join_to == INVALID_DEPOT) {
assert(depot == nullptr);
DepotID closest_depot = INVALID_DEPOT;
TileArea check_area(ta);
check_area.Expand(1);
/* Check around to see if there's any depot there. */
for (TileIndex tile_cur : check_area) {
if (IsValidTile(tile_cur) && IsDepotTile(tile_cur)) {
Depot *d = Depot::GetByTile(tile_cur);
assert(d != nullptr);
if (d->veh_type != veh_type) continue;
if (d->owner != _current_company) continue;
if (closest_depot == INVALID_DEPOT) {
closest_depot = d->index;
} else if (closest_depot != d->index) {
if (!adjacent) return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING_DEPOT);
}
}
}
if (closest_depot != INVALID_DEPOT) {
assert(Depot::IsValidID(closest_depot));
depot = Depot::Get(closest_depot);
}
join_to = depot == nullptr ? NEW_DEPOT : depot->index;
}
/* At this point, join_to is NEW_DEPOT or a valid DepotID. */
if (join_to == NEW_DEPOT) {
/* New depot needed. */
if (!Depot::CanAllocateItem()) return CMD_ERROR;
if (flags & DC_EXEC) {
depot = new Depot(ta.tile, veh_type, _current_company);
depot->build_date = TimerGameCalendar::date;
}
} else {
/* Joining depots. */
assert(Depot::IsValidID(join_to));
depot = Depot::Get(join_to);
assert(depot->owner == _current_company);
assert(depot->veh_type == veh_type);
return depot->BeforeAddTiles(ta);
}
return CommandCost();
}

View File

@ -10,8 +10,10 @@
#ifndef DEPOT_FUNC_H
#define DEPOT_FUNC_H
#include "depot_type.h"
#include "vehicle_type.h"
#include "slope_func.h"
#include "command_type.h"
void ShowDepotWindow(DepotID depot_id);
void InitDepotWindowBlockSizes();
@ -33,4 +35,10 @@ inline bool CanBuildDepotByTileh(DiagDirection direction, Slope tileh)
return IsSteepSlope(tileh) ? (tileh & entrance_corners) == entrance_corners : (tileh & entrance_corners) != 0;
}
struct Depot;
CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &join_to, Depot *&depot, bool adjacent, DoCommandFlag flags);
using DepotPickerCmdProc = std::function<bool(DepotID join_to)>;
void ShowSelectDepotIfNeeded(TileArea ta, DepotPickerCmdProc proc, VehicleType veh_type);
#endif /* DEPOT_FUNC_H */

View File

@ -33,6 +33,7 @@
#include "train_cmd.h"
#include "vehicle_cmd.h"
#include "core/geometry_func.hpp"
#include "depot_func.h"
#include "widgets/depot_widget.h"
@ -1167,3 +1168,244 @@ void DeleteDepotHighlightOfVehicle(const Vehicle *v)
if (w->sel == v->index) ResetObjectToPlace();
}
}
static std::vector<DepotID> _depots_nearby_list;
/** Structure with user-data for AddNearbyDepot. */
struct AddNearbyDepotData {
TileArea search_area; ///< Search area.
VehicleType type; ///< Vehicle type of the searched depots.
};
/**
* Add depot on this tile to _depots_nearby_list if it's fully within the
* depot spread.
* @param tile Tile just being checked
* @param user_data Pointer to TileArea context
*/
static bool AddNearbyDepot(TileIndex tile, void *user_data)
{
AddNearbyDepotData *andd = (AddNearbyDepotData *)user_data;
/* Check if own depot and if we stay within station spread */
if (!IsDepotTile(tile)) return false;
Depot *dep = Depot::GetByTile(tile);
if (dep->owner != _local_company || dep->veh_type != andd->type ||
(find(_depots_nearby_list.begin(), _depots_nearby_list.end(), dep->index) != _depots_nearby_list.end())) {
return false;
}
CommandCost cost = dep->BeforeAddTiles(andd->search_area);
if (cost.Succeeded()) {
_depots_nearby_list.push_back(dep->index);
}
return false;
}
/**
* Circulate around the to-be-built depot to find depots we could join.
* Make sure that only depots are returned where joining wouldn't exceed
* depot spread and are our own depot.
* @param ta Base tile area of the to-be-built depot
* @param veh_type Vehicle type depots to look for
* @param distant_join Search for adjacent depots (false) or depots fully
* within depot spread
*/
static const Depot *FindDepotsNearby(TileArea ta, VehicleType veh_type, bool distant_join)
{
_depots_nearby_list.clear();
_depots_nearby_list.push_back(NEW_DEPOT);
/* Check the inside, to return, if we sit on another big depot */
Depot *depot;
for (TileIndex t : ta) {
if (!IsDepotTile(t)) continue;
depot = Depot::GetByTile(t);
if (depot->veh_type == veh_type && depot->owner == _current_company) return depot;
}
/* Only search tiles where we have a chance to stay within the depot spread.
* The complete check needs to be done in the callback as we don't know the
* extent of the found depot, yet. */
if (distant_join && std::min(ta.w, ta.h) >= _settings_game.depot.depot_spread) return nullptr;
uint max_dist = distant_join ? _settings_game.depot.depot_spread - std::min(ta.w, ta.h) : 1;
AddNearbyDepotData andd;
andd.search_area = ta;
andd.type = veh_type;
TileIndex tile = TileAddByDir(andd.search_area.tile, DIR_N);
CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyDepot, &andd);
return nullptr;
}
/**
* Check whether we need to show the depot selection window.
* @param ta Tile area of the to-be-built depot.
* @param proc The procedure for the depot picker.
* @param veh_type the vehicle type of the depot.
* @return whether we need to show the depot selection window.
*/
static bool DepotJoinerNeeded(TileArea ta, VehicleType veh_type)
{
/* If a window is already opened and we didn't ctrl-click,
* return true (i.e. just flash the old window) */
Window *selection_window = FindWindowById(WC_SELECT_DEPOT, veh_type);
if (selection_window != nullptr) {
/* Abort current distant-join and start new one */
selection_window->Close();
UpdateTileSelection();
}
/* Only show the popup if we press ctrl. */
if (!_ctrl_pressed) return false;
/* Test for adjacent depot or depot below selection.
* If adjacent-stations is disabled and we are building next to a depot, do not show the selection window.
* but join the other depot immediately. */
const Depot *depot = FindDepotsNearby(ta, veh_type, false);
return depot == nullptr && (_settings_game.depot.adjacent_depots || std::any_of(std::begin(_depots_nearby_list), std::end(_depots_nearby_list), [](DepotID s) { return s != NEW_DEPOT; }));
}
/**
* Window for selecting depots to (distant) join to.
*/
struct SelectDepotWindow : Window {
DepotPickerCmdProc select_depot_proc; ///< The procedure params
TileArea area; ///< Location of new depot
Scrollbar *vscroll; ///< Vertical scrollbar for the window
SelectDepotWindow(WindowDesc &desc, TileArea ta, DepotPickerCmdProc& proc, VehicleType veh_type) :
Window(desc),
select_depot_proc(std::move(proc)),
area(ta)
{
this->CreateNestedTree();
this->vscroll = this->GetScrollbar(WID_JD_SCROLLBAR);
this->FinishInitNested(veh_type);
this->OnInvalidateData(0);
}
void UpdateWidgetSize(int widget, Dimension &size, const Dimension &padding, [[maybe_unused]] Dimension &fill, Dimension &resize) override
{
if (widget != WID_JD_PANEL) return;
resize.height = GetCharacterHeight(FS_NORMAL);
size.height = 5 * resize.height + padding.height;
/* Determine the widest string. */
Dimension d = GetStringBoundingBox(STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT);
for (const auto &depot : _depots_nearby_list) {
if (depot == NEW_DEPOT) continue;
const Depot *dep = Depot::Get(depot);
SetDParam(0, this->window_number);
SetDParam(1, dep->index);
d = maxdim(d, GetStringBoundingBox(STR_DEPOT_LIST_DEPOT));
}
d.height = 5 * resize.height;
d.width += padding.width;
d.height += padding.height;
size = d;
}
void DrawWidget(const Rect &r, int widget) const override
{
if (widget != WID_JD_PANEL) return;
Rect tr = r.Shrink(WidgetDimensions::scaled.framerect);
auto [first, last] = this->vscroll->GetVisibleRangeIterators(_depots_nearby_list);
for (auto it = first; it != last; ++it, tr.top += this->resize.step_height) {
if (*it == NEW_DEPOT) {
DrawString(tr, STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT);
} else {
SetDParam(0, this->window_number);
SetDParam(1, *it);
[[maybe_unused]] Depot *depot = Depot::GetIfValid(*it);
assert(depot != nullptr);
DrawString(tr, STR_DEPOT_LIST_DEPOT);
}
}
}
void OnClick(Point pt, int widget, [[maybe_unused]] int click_count) override
{
if (widget != WID_JD_PANEL) return;
auto it = this->vscroll->GetScrolledItemFromWidget(_depots_nearby_list, pt.y, this, WID_JD_PANEL, WidgetDimensions::scaled.framerect.top);
if (it == _depots_nearby_list.end()) return;
/* Execute stored Command */
this->select_depot_proc(*it);
InvalidateWindowData(WC_SELECT_DEPOT, window_number);
this->Close();
}
void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
{
if (_thd.dirty & 2) {
_thd.dirty &= ~2;
this->SetDirty();
}
}
void OnResize() override
{
this->vscroll->SetCapacityFromWidget(this, WID_JD_PANEL, WidgetDimensions::scaled.framerect.Vertical());
}
/**
* Some data on this window has become invalid.
* @param data Information about the changed data.
* @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
*/
void OnInvalidateData([[maybe_unused]] int data = 0, bool gui_scope = true) override
{
if (!gui_scope) return;
FindDepotsNearby(this->area, (VehicleType)this->window_number, true);
this->vscroll->SetCount((uint)_depots_nearby_list.size());
this->SetDirty();
}
};
static const NWidgetPart _nested_select_depot_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_JD_CAPTION), SetDataTip(STR_JOIN_DEPOT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_JD_PANEL), SetResize(1, 0), SetScrollbar(WID_JD_SCROLLBAR), EndContainer(),
NWidget(NWID_VERTICAL),
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_JD_SCROLLBAR),
NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
EndContainer(),
};
static WindowDesc _select_depot_desc(
WDP_AUTO, "build_depot_join", 200, 180,
WC_SELECT_DEPOT, WC_NONE,
WDF_CONSTRUCTION,
_nested_select_depot_widgets
);
/**
* Show the depot selection window when needed. If not, build the depot.
* @param ta Area to build the depot in.
* @param proc Details of the procedure for the depot picker.
* @param veh_type Vehicle type of the depot to be built.
*/
void ShowSelectDepotIfNeeded(TileArea ta, DepotPickerCmdProc proc, VehicleType veh_type)
{
if (DepotJoinerNeeded(ta, veh_type)) {
if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
new SelectDepotWindow(_select_depot_desc, ta, proc, veh_type);
} else {
proc(INVALID_DEPOT);
}
}

View File

@ -14,6 +14,7 @@ typedef uint16_t DepotID; ///< Type for the unique identifier of depots.
struct Depot;
static const DepotID INVALID_DEPOT = UINT16_MAX;
static const DepotID NEW_DEPOT = INVALID_DEPOT - 1;
static const uint MAX_LENGTH_DEPOT_NAME_CHARS = 32; ///< The maximum length of a depot name in characters including '\0'

View File

@ -275,6 +275,7 @@ struct BuildDocksToolbarWindow : Window {
CloseWindowById(WC_BUILD_STATION, TRANSPORT_WATER);
CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_WATER);
CloseWindowById(WC_SELECT_STATION, 0);
CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP);
CloseWindowByClass(WC_BUILD_BRIDGE);
}
@ -529,6 +530,12 @@ public:
UpdateDocksDirection();
}
void Close([[maybe_unused]] int data = 0) override
{
CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP);
this->PickerWindowBase::Close();
}
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
{
switch (widget) {
@ -569,6 +576,7 @@ public:
switch (widget) {
case WID_BDD_X:
case WID_BDD_Y:
CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP);
this->RaiseWidget(WID_BDD_X + _ship_depot_direction);
_ship_depot_direction = (widget == WID_BDD_X ? AXIS_X : AXIS_Y);
this->LowerWidget(WID_BDD_X + _ship_depot_direction);

View File

@ -2769,6 +2769,11 @@ STR_JOIN_STATION_CREATE_SPLITTED_STATION :{YELLOW}Build a
STR_JOIN_WAYPOINT_CAPTION :{WHITE}Join waypoint
STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT :{YELLOW}Build a separate waypoint
# Join depot window
STR_JOIN_DEPOT_CAPTION :{WHITE}Join depot
STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT :{YELLOW}Build a separate depot
STR_DEPOT_LIST_DEPOT :{YELLOW}{DEPOT}
# Generic toolbar
STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE :{BLACK}Disabled as currently no vehicles are available for this infrastructure
@ -5120,6 +5125,7 @@ STR_ERROR_CAN_T_BUILD_ROAD_DEPOT :{WHITE}Can't bu
STR_ERROR_CAN_T_BUILD_TRAM_DEPOT :{WHITE}Can't build tram vehicle depot here...
STR_ERROR_CAN_T_BUILD_SHIP_DEPOT :{WHITE}Can't build ship depot here...
STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING_DEPOT :{WHITE}... adjoins more than one existing depot
STR_ERROR_CAN_T_RENAME_DEPOT :{WHITE}Can't rename depot...
STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT :{WHITE}... must be stopped inside a depot

View File

@ -804,6 +804,7 @@ struct BuildRailToolbarWindow : Window {
CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_RAIL);
CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_RAIL);
CloseWindowById(WC_SELECT_STATION, 0);
CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN);
CloseWindowByClass(WC_BUILD_BRIDGE);
}
@ -1705,6 +1706,12 @@ struct BuildRailDepotWindow : public PickerWindowBase {
this->LowerWidget(WID_BRAD_DEPOT_NE + _build_depot_direction);
}
void Close([[maybe_unused]] int data = 0) override
{
CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN);
this->PickerWindowBase::Close();
}
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
{
if (!IsInsideMM(widget, WID_BRAD_DEPOT_NE, WID_BRAD_DEPOT_NW + 1)) return;
@ -1734,6 +1741,7 @@ struct BuildRailDepotWindow : public PickerWindowBase {
case WID_BRAD_DEPOT_SE:
case WID_BRAD_DEPOT_SW:
case WID_BRAD_DEPOT_NW:
CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN);
this->RaiseWidget(WID_BRAD_DEPOT_NE + _build_depot_direction);
_build_depot_direction = (DiagDirection)(widget - WID_BRAD_DEPOT_NE);
this->LowerWidget(WID_BRAD_DEPOT_NE + _build_depot_direction);

View File

@ -687,6 +687,7 @@ struct BuildRoadToolbarWindow : Window {
CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_ROAD);
CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_ROAD);
CloseWindowById(WC_SELECT_STATION, 0);
CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD);
CloseWindowByClass(WC_BUILD_BRIDGE);
}
@ -1106,6 +1107,12 @@ struct BuildRoadDepotWindow : public PickerWindowBase {
this->FinishInitNested(TRANSPORT_ROAD);
}
void Close([[maybe_unused]] int data = 0) override
{
CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD);
this->PickerWindowBase::Close();
}
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
{
if (!IsInsideMM(widget, WID_BROD_DEPOT_NE, WID_BROD_DEPOT_NW + 1)) return;
@ -1135,6 +1142,7 @@ struct BuildRoadDepotWindow : public PickerWindowBase {
case WID_BROD_DEPOT_NE:
case WID_BROD_DEPOT_SW:
case WID_BROD_DEPOT_SE:
CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD);
this->RaiseWidget(WID_BROD_DEPOT_NE + _road_depot_orientation);
_road_depot_orientation = (DiagDirection)(widget - WID_BROD_DEPOT_NE);
this->LowerWidget(WID_BROD_DEPOT_NE + _road_depot_orientation);

View File

@ -157,6 +157,7 @@ from = SLV_DEPOT_SPREAD
def = true
str = STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS
strhelp = STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS_HELPTEXT
post_cb = [](auto) { CloseWindowByClass(WC_SELECT_DEPOT); }
[SDT_VAR]
var = depot.depot_spread
@ -168,6 +169,7 @@ max = 64
str = STR_CONFIG_SETTING_DEPOT_SPREAD
strhelp = STR_CONFIG_SETTING_DEPOT_SPREAD_HELPTEXT
strval = STR_CONFIG_SETTING_TILE_LENGTH
post_cb = [](auto) { CloseWindowByClass(WC_SELECT_DEPOT); }
cat = SC_BASIC
[SDT_OMANY]

View File

@ -32,4 +32,11 @@ enum DepotWidgets : WidgetID {
WID_D_START_ALL, ///< Start all button.
};
/** Widgets of the #SelectDepotWindow class. */
enum JoinDepotWidgets {
WID_JD_CAPTION, // Caption of the window.
WID_JD_PANEL, // Main panel.
WID_JD_SCROLLBAR, // Scrollbar of the panel.
};
#endif /* WIDGETS_DEPOT_WIDGET_H */

View File

@ -241,6 +241,12 @@ enum WindowClass {
*/
WC_SELECT_STATION,
/**
* Select depot (when joining depots); %Window numbers:
* - #Vehicle type = #JoinDepotWidgets
*/
WC_SELECT_DEPOT,
/**
* News window; %Window numbers:
* - 0 = #NewsWidgets