|
@@ -10,193 +10,193 @@
|
|
|
#include "stdafx.h"
|
|
|
#include "debug.h"
|
|
|
#include "gui.h"
|
|
|
#include "textbuf_gui.h"
|
|
|
#include "company_func.h"
|
|
|
#include "command_func.h"
|
|
|
#include "vehicle_gui.h"
|
|
|
#include "cargotype.h"
|
|
|
#include "station_gui.h"
|
|
|
#include "strings_func.h"
|
|
|
#include "string_func.h"
|
|
|
#include "window_func.h"
|
|
|
#include "viewport_func.h"
|
|
|
#include "widgets/dropdown_func.h"
|
|
|
#include "station_base.h"
|
|
|
#include "waypoint_base.h"
|
|
|
#include "tilehighlight_func.h"
|
|
|
#include "company_base.h"
|
|
|
#include "sortlist_type.h"
|
|
|
#include "core/geometry_func.hpp"
|
|
|
#include "vehiclelist.h"
|
|
|
#include "town.h"
|
|
|
#include "linkgraph/linkgraph.h"
|
|
|
#include "zoom_func.h"
|
|
|
|
|
|
#include "widgets/station_widget.h"
|
|
|
|
|
|
#include "table/strings.h"
|
|
|
|
|
|
#include <set>
|
|
|
#include <vector>
|
|
|
|
|
|
#include "safeguards.h"
|
|
|
|
|
|
/**
|
|
|
* Calculates and draws the accepted or supplied cargo around the selected tile(s)
|
|
|
* @param left x position where the string is to be drawn
|
|
|
* @param right the right most position to draw on
|
|
|
* @param top y position where the string is to be drawn
|
|
|
* @param sct which type of cargo is to be displayed (passengers/non-passengers)
|
|
|
* @param rad radius around selected tile(s) to be searched
|
|
|
* @param supplies if supplied cargoes should be drawn, else accepted cargoes
|
|
|
* @return Returns the y value below the string that was drawn
|
|
|
*/
|
|
|
int DrawStationCoverageAreaText(int left, int right, int top, StationCoverageType sct, int rad, bool supplies)
|
|
|
{
|
|
|
TileIndex tile = TileVirtXY(_thd.pos.x, _thd.pos.y);
|
|
|
CargoTypes cargo_mask = 0;
|
|
|
if (_thd.drawstyle == HT_RECT && tile < MapSize()) {
|
|
|
CargoArray cargoes;
|
|
|
if (supplies) {
|
|
|
cargoes = GetProductionAroundTiles(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE, rad);
|
|
|
} else {
|
|
|
cargoes = GetAcceptanceAroundTiles(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE, rad);
|
|
|
}
|
|
|
|
|
|
/* Convert cargo counts to a set of cargo bits, and draw the result. */
|
|
|
for (CargoID i = 0; i < NUM_CARGO; i++) {
|
|
|
switch (sct) {
|
|
|
case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CC_PASSENGERS)) continue; break;
|
|
|
case SCT_NON_PASSENGERS_ONLY: if (IsCargoInClass(i, CC_PASSENGERS)) continue; break;
|
|
|
case SCT_ALL: break;
|
|
|
default: NOT_REACHED();
|
|
|
}
|
|
|
if (cargoes[i] >= (supplies ? 1U : 8U)) SetBit(cargo_mask, i);
|
|
|
}
|
|
|
}
|
|
|
SetDParam(0, cargo_mask);
|
|
|
return DrawStringMultiLine(left, right, top, INT32_MAX, supplies ? STR_STATION_BUILD_SUPPLIES_CARGO : STR_STATION_BUILD_ACCEPTS_CARGO);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Find stations adjacent to the current tile highlight area, so that existing coverage
|
|
|
* area can be drawn.
|
|
|
*/
|
|
|
static void FindStationsAroundSelection()
|
|
|
{
|
|
|
/* With distant join we don't know which station will be selected, so don't show any */
|
|
|
if (_ctrl_pressed) {
|
|
|
SetViewportCatchmentStation(nullptr, true);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
/* Tile area for TileHighlightData */
|
|
|
TileArea location(TileVirtXY(_thd.pos.x, _thd.pos.y), _thd.size.x / TILE_SIZE - 1, _thd.size.y / TILE_SIZE - 1);
|
|
|
|
|
|
/* Extended area by one tile */
|
|
|
uint x = TileX(location.tile);
|
|
|
uint y = TileY(location.tile);
|
|
|
|
|
|
int max_c = 1;
|
|
|
TileArea ta(TileXY(std::max<int>(0, x - max_c), std::max<int>(0, y - max_c)), TileXY(std::min<int>(MapMaxX(), x + location.w + max_c), std::min<int>(MapMaxY(), y + location.h + max_c)));
|
|
|
|
|
|
Station *adjacent = nullptr;
|
|
|
|
|
|
/* Direct loop instead of ForAllStationsAroundTiles as we are not interested in catchment area */
|
|
|
TILE_AREA_LOOP(tile, ta) {
|
|
|
for (TileIndex tile : ta) {
|
|
|
if (IsTileType(tile, MP_STATION) && GetTileOwner(tile) == _local_company) {
|
|
|
Station *st = Station::GetByTile(tile);
|
|
|
if (st == nullptr) continue;
|
|
|
if (adjacent != nullptr && st != adjacent) {
|
|
|
/* Multiple nearby, distant join is required. */
|
|
|
adjacent = nullptr;
|
|
|
break;
|
|
|
}
|
|
|
adjacent = st;
|
|
|
}
|
|
|
}
|
|
|
SetViewportCatchmentStation(adjacent, true);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Check whether we need to redraw the station coverage text.
|
|
|
* If it is needed actually make the window for redrawing.
|
|
|
* @param w the window to check.
|
|
|
*/
|
|
|
void CheckRedrawStationCoverage(const Window *w)
|
|
|
{
|
|
|
/* Test if ctrl state changed */
|
|
|
static bool _last_ctrl_pressed;
|
|
|
if (_ctrl_pressed != _last_ctrl_pressed) {
|
|
|
_thd.dirty = 0xff;
|
|
|
_last_ctrl_pressed = _ctrl_pressed;
|
|
|
}
|
|
|
|
|
|
if (_thd.dirty & 1) {
|
|
|
_thd.dirty &= ~1;
|
|
|
w->SetDirty();
|
|
|
|
|
|
if (_settings_client.gui.station_show_coverage && _thd.drawstyle == HT_RECT) {
|
|
|
FindStationsAroundSelection();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Draw small boxes of cargo amount and ratings data at the given
|
|
|
* coordinates. If amount exceeds 576 units, it is shown 'full', same
|
|
|
* goes for the rating: at above 90% orso (224) it is also 'full'
|
|
|
*
|
|
|
* @param left left most coordinate to draw the box at
|
|
|
* @param right right most coordinate to draw the box at
|
|
|
* @param y coordinate to draw the box at
|
|
|
* @param type Cargo type
|
|
|
* @param amount Cargo amount
|
|
|
* @param rating ratings data for that particular cargo
|
|
|
*/
|
|
|
static void StationsWndShowStationRating(int left, int right, int y, CargoID type, uint amount, byte rating)
|
|
|
{
|
|
|
static const uint units_full = 576; ///< number of units to show station as 'full'
|
|
|
static const uint rating_full = 224; ///< rating needed so it is shown as 'full'
|
|
|
|
|
|
const CargoSpec *cs = CargoSpec::Get(type);
|
|
|
if (!cs->IsValid()) return;
|
|
|
|
|
|
int padding = ScaleFontTrad(1);
|
|
|
int width = right - left;
|
|
|
int colour = cs->rating_colour;
|
|
|
TextColour tc = GetContrastColour(colour);
|
|
|
uint w = std::min(amount + 5, units_full) * width / units_full;
|
|
|
|
|
|
int height = GetCharacterHeight(FS_SMALL) + padding - 1;
|
|
|
|
|
|
if (amount > 30) {
|
|
|
/* Draw total cargo (limited) on station */
|
|
|
GfxFillRect(left, y, left + w - 1, y + height, colour);
|
|
|
} else {
|
|
|
/* Draw a (scaled) one pixel-wide bar of additional cargo meter, useful
|
|
|
* for stations with only a small amount (<=30) */
|
|
|
uint rest = ScaleFontTrad(amount) / 5;
|
|
|
if (rest != 0) {
|
|
|
GfxFillRect(left, y + height - rest, left + padding - 1, y + height, colour);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
DrawString(left + padding, right, y, cs->abbrev, tc);
|
|
|
|
|
|
/* Draw green/red ratings bar (fits under the waiting bar) */
|
|
|
y += height + padding + 1;
|
|
|
GfxFillRect(left + padding, y, right - padding - 1, y + padding - 1, PC_RED);
|
|
|
w = std::min<uint>(rating, rating_full) * (width - padding - padding) / rating_full;
|
|
|
if (w != 0) GfxFillRect(left + padding, y, left + w - 1, y + padding - 1, PC_GREEN);
|
|
|
}
|
|
|
|
|
|
typedef GUIList<const Station*> GUIStationList;
|
|
|
|
|
|
/**
|
|
|
* The list of stations per company.
|
|
|
*/
|
|
|
class CompanyStationsWindow : public Window
|
|
|
{
|
|
|
protected:
|
|
|
/* Runtime saved values */
|
|
@@ -2121,193 +2121,193 @@ const StringID StationViewWindow::_sort_
|
|
|
};
|
|
|
|
|
|
const StringID StationViewWindow::_group_names[] = {
|
|
|
STR_STATION_VIEW_GROUP_S_V_D,
|
|
|
STR_STATION_VIEW_GROUP_S_D_V,
|
|
|
STR_STATION_VIEW_GROUP_V_S_D,
|
|
|
STR_STATION_VIEW_GROUP_V_D_S,
|
|
|
STR_STATION_VIEW_GROUP_D_S_V,
|
|
|
STR_STATION_VIEW_GROUP_D_V_S,
|
|
|
INVALID_STRING_ID
|
|
|
};
|
|
|
|
|
|
static WindowDesc _station_view_desc(
|
|
|
WDP_AUTO, "view_station", 249, 117,
|
|
|
WC_STATION_VIEW, WC_NONE,
|
|
|
0,
|
|
|
_nested_station_view_widgets, lengthof(_nested_station_view_widgets)
|
|
|
);
|
|
|
|
|
|
/**
|
|
|
* Opens StationViewWindow for given station
|
|
|
*
|
|
|
* @param station station which window should be opened
|
|
|
*/
|
|
|
void ShowStationViewWindow(StationID station)
|
|
|
{
|
|
|
AllocateWindowDescFront<StationViewWindow>(&_station_view_desc, station);
|
|
|
}
|
|
|
|
|
|
/** Struct containing TileIndex and StationID */
|
|
|
struct TileAndStation {
|
|
|
TileIndex tile; ///< TileIndex
|
|
|
StationID station; ///< StationID
|
|
|
};
|
|
|
|
|
|
static std::vector<TileAndStation> _deleted_stations_nearby;
|
|
|
static std::vector<StationID> _stations_nearby_list;
|
|
|
|
|
|
/**
|
|
|
* Add station on this tile to _stations_nearby_list if it's fully within the
|
|
|
* station spread.
|
|
|
* @param tile Tile just being checked
|
|
|
* @param user_data Pointer to TileArea context
|
|
|
* @tparam T the type of station to look for
|
|
|
*/
|
|
|
template <class T>
|
|
|
static bool AddNearbyStation(TileIndex tile, void *user_data)
|
|
|
{
|
|
|
TileArea *ctx = (TileArea *)user_data;
|
|
|
|
|
|
/* First check if there were deleted stations here */
|
|
|
for (uint i = 0; i < _deleted_stations_nearby.size(); i++) {
|
|
|
auto ts = _deleted_stations_nearby.begin() + i;
|
|
|
if (ts->tile == tile) {
|
|
|
_stations_nearby_list.push_back(_deleted_stations_nearby[i].station);
|
|
|
_deleted_stations_nearby.erase(ts);
|
|
|
i--;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Check if own station and if we stay within station spread */
|
|
|
if (!IsTileType(tile, MP_STATION)) return false;
|
|
|
|
|
|
StationID sid = GetStationIndex(tile);
|
|
|
|
|
|
/* This station is (likely) a waypoint */
|
|
|
if (!T::IsValidID(sid)) return false;
|
|
|
|
|
|
T *st = T::Get(sid);
|
|
|
if (st->owner != _local_company || std::find(_stations_nearby_list.begin(), _stations_nearby_list.end(), sid) != _stations_nearby_list.end()) return false;
|
|
|
|
|
|
if (st->rect.BeforeAddRect(ctx->tile, ctx->w, ctx->h, StationRect::ADD_TEST).Succeeded()) {
|
|
|
_stations_nearby_list.push_back(sid);
|
|
|
}
|
|
|
|
|
|
return false; // We want to include *all* nearby stations
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Circulate around the to-be-built station to find stations we could join.
|
|
|
* Make sure that only stations are returned where joining wouldn't exceed
|
|
|
* station spread and are our own station.
|
|
|
* @param ta Base tile area of the to-be-built station
|
|
|
* @param distant_join Search for adjacent stations (false) or stations fully
|
|
|
* within station spread
|
|
|
* @tparam T the type of station to look for
|
|
|
*/
|
|
|
template <class T>
|
|
|
static const T *FindStationsNearby(TileArea ta, bool distant_join)
|
|
|
{
|
|
|
TileArea ctx = ta;
|
|
|
|
|
|
_stations_nearby_list.clear();
|
|
|
_deleted_stations_nearby.clear();
|
|
|
|
|
|
/* Check the inside, to return, if we sit on another station */
|
|
|
TILE_AREA_LOOP(t, ta) {
|
|
|
for (TileIndex t : ta) {
|
|
|
if (t < MapSize() && IsTileType(t, MP_STATION) && T::IsValidID(GetStationIndex(t))) return T::GetByTile(t);
|
|
|
}
|
|
|
|
|
|
/* Look for deleted stations */
|
|
|
for (const BaseStation *st : BaseStation::Iterate()) {
|
|
|
if (T::IsExpected(st) && !st->IsInUse() && st->owner == _local_company) {
|
|
|
/* Include only within station spread (yes, it is strictly less than) */
|
|
|
if (std::max(DistanceMax(ta.tile, st->xy), DistanceMax(TILE_ADDXY(ta.tile, ta.w - 1, ta.h - 1), st->xy)) < _settings_game.station.station_spread) {
|
|
|
_deleted_stations_nearby.push_back({st->xy, st->index});
|
|
|
|
|
|
/* Add the station when it's within where we're going to build */
|
|
|
if (IsInsideBS(TileX(st->xy), TileX(ctx.tile), ctx.w) &&
|
|
|
IsInsideBS(TileY(st->xy), TileY(ctx.tile), ctx.h)) {
|
|
|
AddNearbyStation<T>(st->xy, &ctx);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Only search tiles where we have a chance to stay within the station spread.
|
|
|
* The complete check needs to be done in the callback as we don't know the
|
|
|
* extent of the found station, yet. */
|
|
|
if (distant_join && std::min(ta.w, ta.h) >= _settings_game.station.station_spread) return nullptr;
|
|
|
uint max_dist = distant_join ? _settings_game.station.station_spread - std::min(ta.w, ta.h) : 1;
|
|
|
|
|
|
TileIndex tile = TileAddByDir(ctx.tile, DIR_N);
|
|
|
CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyStation<T>, &ctx);
|
|
|
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
static const NWidgetPart _nested_select_station_widgets[] = {
|
|
|
NWidget(NWID_HORIZONTAL),
|
|
|
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
|
|
|
NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_JS_CAPTION), SetDataTip(STR_JOIN_STATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
|
|
|
NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
|
|
|
EndContainer(),
|
|
|
NWidget(NWID_HORIZONTAL),
|
|
|
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_JS_PANEL), SetResize(1, 0), SetScrollbar(WID_JS_SCROLLBAR), EndContainer(),
|
|
|
NWidget(NWID_VERTICAL),
|
|
|
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_JS_SCROLLBAR),
|
|
|
NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
|
|
|
EndContainer(),
|
|
|
EndContainer(),
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Window for selecting stations/waypoints to (distant) join to.
|
|
|
* @tparam T The type of station to join with
|
|
|
*/
|
|
|
template <class T>
|
|
|
struct SelectStationWindow : Window {
|
|
|
CommandContainer select_station_cmd; ///< Command to build new station
|
|
|
TileArea area; ///< Location of new station
|
|
|
Scrollbar *vscroll;
|
|
|
|
|
|
SelectStationWindow(WindowDesc *desc, const CommandContainer &cmd, TileArea ta) :
|
|
|
Window(desc),
|
|
|
select_station_cmd(cmd),
|
|
|
area(ta)
|
|
|
{
|
|
|
this->CreateNestedTree();
|
|
|
this->vscroll = this->GetScrollbar(WID_JS_SCROLLBAR);
|
|
|
this->GetWidget<NWidgetCore>(WID_JS_CAPTION)->widget_data = T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_JOIN_WAYPOINT_CAPTION : STR_JOIN_STATION_CAPTION;
|
|
|
this->FinishInitNested(0);
|
|
|
this->OnInvalidateData(0);
|
|
|
|
|
|
_thd.freeze = true;
|
|
|
}
|
|
|
|
|
|
~SelectStationWindow()
|
|
|
{
|
|
|
if (_settings_client.gui.station_show_coverage) SetViewportCatchmentStation(nullptr, true);
|
|
|
|
|
|
_thd.freeze = false;
|
|
|
}
|
|
|
|
|
|
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
|
|
|
{
|
|
|
if (widget != WID_JS_PANEL) return;
|
|
|
|
|
|
/* Determine the widest string */
|
|
|
Dimension d = GetStringBoundingBox(T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT : STR_JOIN_STATION_CREATE_SPLITTED_STATION);
|
|
|
for (uint i = 0; i < _stations_nearby_list.size(); i++) {
|
|
|
const T *st = T::Get(_stations_nearby_list[i]);
|
|
|
SetDParam(0, st->index);
|
|
|
SetDParam(1, st->facilities);
|
|
|
d = maxdim(d, GetStringBoundingBox(T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_STATION_LIST_WAYPOINT : STR_STATION_LIST_STATION));
|
|
|
}
|
|
|
|
|
|
resize->height = d.height;
|
|
|
d.height *= 5;
|
|
|
d.width += WD_FRAMERECT_RIGHT + WD_FRAMERECT_LEFT;
|
|
|
d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
|
|
|
*size = d;
|
|
|
}
|