summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lang/english.txt17
-rw-r--r--src/settings.cpp5
-rw-r--r--src/settings_gui.cpp1
-rw-r--r--src/settings_type.h1
-rw-r--r--src/table/settings.h4
-rw-r--r--src/timetable_gui.cpp225
6 files changed, 242 insertions, 11 deletions
diff --git a/src/lang/english.txt b/src/lang/english.txt
index 66c3bc4d8..4ed70cedd 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -1177,6 +1177,7 @@ STR_CONFIG_SETTING_LOADING_INDICATORS_OWN :Own company
STR_CONFIG_SETTING_LOADING_INDICATORS_ALL :All companies
STR_CONFIG_SETTING_TIMETABLE_ALLOW :{LTBLUE}Enable timetabling for vehicles: {ORANGE}{STRING1}
STR_CONFIG_SETTING_TIMETABLE_IN_TICKS :{LTBLUE}Show timetable in ticks rather than days: {ORANGE}{STRING1}
+STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE :{LTBLUE}Show arrival and departure in timetables: {ORANGE}{STRING1}
STR_CONFIG_SETTING_QUICKGOTO :{LTBLUE}Quick creation of vehicle orders: {ORANGE}{STRING1}
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE :{LTBLUE}Default rail type (after new game/game load): {ORANGE}{STRING1}
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_RAIL :Normal Rail
@@ -3151,6 +3152,22 @@ STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Reset th
STR_TIMETABLE_AUTOFILL :{BLACK}Autofill
STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Fill the timetable automatically with the values from the next journey (Ctrl+Click to try to keep waiting times)
+STR_TIMETABLE_EXPECTED :{BLACK}Expected
+STR_TIMETABLE_SCHEDULED :{BLACK}Scheduled
+STR_TIMETABLE_EXPECTED_TOOLTIP :{BLACK}Switch between expected and schedule
+
+### Do not separate or reorder ###
+STR_TIMETABLE_ARRIVAL :A:{SETX 30}{STRING1}
+STR_TIMETABLE_ARRIVAL_EARLY :A:{SETX 30}{GREEN}{STRING1}
+STR_TIMETABLE_ARRIVAL_LATE :A:{SETX 30}{RED}{STRING1}
+#####
+
+### Do not separate or reorder ###
+STR_TIMETABLE_DEPARTURE :D:{SETX 30}{STRING1}
+STR_TIMETABLE_DEPARTURE_EARLY :D:{SETX 30}{GREEN}{STRING1}
+STR_TIMETABLE_DEPARTURE_LATE :D:{SETX 30}{RED}{STRING1}
+#####
+
# AI debug window
STR_AI_DEBUG :{WHITE}AI Debug
STR_AI_DEBUG_NAME_TOOLTIP :{BLACK}Name of the AI
diff --git a/src/settings.cpp b/src/settings.cpp
index f45adbe2d..94d341271 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -763,6 +763,11 @@ static bool TownFoundingChanged(int32 p1)
return true;
}
+static bool InvalidateVehTimetableWindow(int32 p1)
+{
+ InvalidateWindowClassesData(WC_VEHICLE_TIMETABLE, -2);
+ return true;
+}
/*
* A: competitors
diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp
index 6b172f2d9..cb2f780a9 100644
--- a/src/settings_gui.cpp
+++ b/src/settings_gui.cpp
@@ -1238,6 +1238,7 @@ static SettingEntry _settings_ui[] = {
SettingEntry("gui.pause_on_newgame"),
SettingEntry("gui.advanced_vehicle_list"),
SettingEntry("gui.timetable_in_ticks"),
+ SettingEntry("gui.timetable_arrival_departure"),
SettingEntry("gui.quick_goto"),
SettingEntry("gui.default_rail_type"),
SettingEntry("gui.always_build_infrastructure"),
diff --git a/src/settings_type.h b/src/settings_type.h
index 02e997f61..7bfc38ae3 100644
--- a/src/settings_type.h
+++ b/src/settings_type.h
@@ -76,6 +76,7 @@ struct GUISettings {
uint8 right_mouse_btn_emulation; ///< should we emulate right mouse clicking?
uint8 scrollwheel_scrolling; ///< scrolling using the scroll wheel?
uint8 scrollwheel_multiplier; ///< how much 'wheel' per incoming event from the OS?
+ bool timetable_arrival_departure; ///< show arrivals and departures in vehicle timetables
bool left_mouse_btn_scrolling; ///< left mouse button scroll
bool pause_on_newgame; ///< whether to start new games paused or not
bool enable_signal_gui; ///< show the signal GUI when the signal button is pressed
diff --git a/src/table/settings.h b/src/table/settings.h
index 045721de2..ad784dab4 100644
--- a/src/table/settings.h
+++ b/src/table/settings.h
@@ -33,6 +33,7 @@ static int32 CheckNoiseToleranceLevel(const char *value);
static bool CheckFreeformEdges(int32 p1);
static bool ChangeDynamicEngines(int32 p1);
static bool StationCatchmentChanged(int32 p1);
+static bool InvalidateVehTimetableWindow(int32 p1);
#ifdef ENABLE_NETWORK
static bool UpdateClientName(int32 p1);
@@ -549,7 +550,8 @@ const SettingDesc _settings[] = {
SDTC_VAR(gui.scrollwheel_multiplier, SLE_UINT8, S, 0, 5, 1, 15, 1, STR_CONFIG_SETTING_SCROLLWHEEL_MULTIPLIER, NULL),
SDTC_BOOL(gui.pause_on_newgame, S, 0, false, STR_CONFIG_SETTING_PAUSE_ON_NEW_GAME, NULL),
SDTC_VAR(gui.advanced_vehicle_list, SLE_UINT8, S, MS, 1, 0, 2, 0, STR_CONFIG_SETTING_ADVANCED_VEHICLE_LISTS, NULL),
- SDTC_BOOL(gui.timetable_in_ticks, S, 0, false, STR_CONFIG_SETTING_TIMETABLE_IN_TICKS, NULL),
+ SDTC_BOOL(gui.timetable_in_ticks, S, 0, false, STR_CONFIG_SETTING_TIMETABLE_IN_TICKS, InvalidateVehTimetableWindow),
+ SDTC_BOOL(gui.timetable_arrival_departure, S, 0, true, STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE, InvalidateVehTimetableWindow),
SDTC_BOOL(gui.quick_goto, S, 0, false, STR_CONFIG_SETTING_QUICKGOTO, NULL),
SDTC_VAR(gui.loading_indicators, SLE_UINT8, S, MS, 1, 0, 2, 0, STR_CONFIG_SETTING_LOADING_INDICATORS, RedrawScreen),
SDTC_VAR(gui.default_rail_type, SLE_UINT8, S, MS, 4, 0, 6, 0, STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE, NULL),
diff --git a/src/timetable_gui.cpp b/src/timetable_gui.cpp
index d19b4128c..f980e4882 100644
--- a/src/timetable_gui.cpp
+++ b/src/timetable_gui.cpp
@@ -20,6 +20,7 @@
#include "string_func.h"
#include "gfx_func.h"
#include "company_func.h"
+#include "date_func.h"
#include "table/strings.h"
@@ -27,12 +28,23 @@ enum TimetableViewWindowWidgets {
TTV_CAPTION,
TTV_ORDER_VIEW,
TTV_TIMETABLE_PANEL,
+ TTV_FAKE_SCROLLBAR, ///< So the timetable panel 'sees' the scrollbar too
+ TTV_ARRIVAL_DEPARTURE_PANEL, ///< Panel with the expected/scheduled arrivals
TTV_SCROLLBAR,
TTV_SUMMARY_PANEL,
TTV_CHANGE_TIME,
TTV_CLEAR_TIME,
TTV_RESET_LATENESS,
TTV_AUTOFILL,
+ TTV_EXPECTED, ///< Toggle between expected and scheduled arrivals
+ TTV_ARRIVAL_DEPARTURE_SELECTION, ///< Disable/hide the arrival departure panel
+ TTV_EXPECTED_SELECTION, ///< Disable/hide the expected selection button
+};
+
+/** Container for the arrival/departure dates of a vehicle */
+struct TimetableArrivalDeparture {
+ Ticks arrival; ///< The arrival time
+ Ticks departure; ///< The departure time
};
/**
@@ -52,21 +64,137 @@ void SetTimetableParams(int param1, int param2, Ticks ticks)
}
}
+/**
+ * Sets the arrival or departure string and parameters.
+ * @param param1 the first DParam to fill
+ * @param param2 the second DParam to fill
+ * @param ticks the number of ticks to 'draw'
+ */
+static void SetArrivalDepartParams(int param1, int param2, Ticks ticks)
+{
+ SetDParam(param1, STR_JUST_DATE_TINY);
+ SetDParam(param2, _date + (ticks / DAY_TICKS));
+}
+
+/**
+ * Check whether it is possible to determine how long the order takes.
+ * @param order the order to check.
+ * @param travelling whether we are interested in the travel or the wait part.
+ * @return true if the travel/wait time can be used.
+ */
+static bool CanDetermineTimeTaken(const Order *order, bool travelling)
+{
+ /* Current order is conditional */
+ if (order->IsType(OT_CONDITIONAL)) return false;
+ /* No travel time and we have not already finished travelling */
+ if (travelling && order->travel_time == 0) return false;
+ /* No wait time but we are loading at this timetabled station */
+ if (!travelling && order->wait_time == 0 && order->IsType(OT_GOTO_STATION) && !(order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) return false;
+
+ return true;
+}
+
+
+/**
+ * Fill the table with arrivals and departures
+ * @param v Vehicle which must have at least 2 orders.
+ * @param start order index to start at
+ * @param travelling Are we still in the travelling part of the start order
+ * @param table Fill in arrival and departures including intermediate orders
+ * @param offset Add this value to result and all arrivals and departures
+ */
+static void FillTimetableArrivalDepartureTable(const Vehicle *v, VehicleOrderID start, bool travelling, TimetableArrivalDeparture *table, Ticks offset)
+{
+ assert(table != NULL);
+ assert(v->GetNumOrders() >= 2);
+ assert(start < v->GetNumOrders());
+
+ Ticks sum = offset;
+ VehicleOrderID i = start;
+ const Order *order = v->GetOrder(i);
+
+ /* Pre-initialize with unknown time */
+ for (int i = 0; i < v->GetNumOrders(); ++i) {
+ table[i].arrival = table[i].departure = INVALID_TICKS;
+ }
+
+ /* Cyclically loop over all orders until we reach the current one again.
+ * As we may start at the current order, do a post-checking loop */
+ do {
+ if (travelling || i != start) {
+ if (!CanDetermineTimeTaken(order, true)) return;
+ sum += order->travel_time;
+ table[i].arrival = sum;
+ }
+
+ if (!CanDetermineTimeTaken(order, false)) return;
+ sum += order->wait_time;
+ table[i].departure = sum;
+
+ ++i;
+ order = order->next;
+ if (i >= v->GetNumOrders()) {
+ i = 0;
+ assert(order == NULL);
+ order = v->orders.list->GetFirstOrder();
+ }
+ } while (i != start);
+
+ /* When loading at a scheduled station we still have to treat the
+ * travelling part of the first order. */
+ if (!travelling) {
+ if (!CanDetermineTimeTaken(order, true)) return;
+ sum += order->travel_time;
+ table[i].arrival = sum;
+ }
+}
+
+
struct TimetableWindow : Window {
int sel_index;
const Vehicle *vehicle; ///< Vehicle monitored by the window.
+ bool show_expected; ///< Whether we show expected arrival or scheduled
- TimetableWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
+ TimetableWindow(const WindowDesc *desc, WindowNumber window_number) :
+ Window(),
+ sel_index(-1),
+ vehicle(Vehicle::Get(window_number)),
+ show_expected(true)
{
- this->vehicle = Vehicle::Get(window_number);
- this->InitNested(desc, window_number);
+ this->CreateNestedTree(desc);
+ this->UpdateSelectionStates();
+ this->FinishInitNested(desc, window_number);
+
this->owner = this->vehicle->owner;
- this->sel_index = -1;
+ }
+
+ /**
+ * Build the arrival-departure list for a given vehicle
+ * @param v the vehicle to make the list for
+ * @param table the table to fill
+ * @return if next arrival will be early
+ */
+ static bool BuildArrivalDepartureList(const Vehicle *v, TimetableArrivalDeparture *table)
+ {
+ assert(HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED));
+
+ bool travelling = (!v->current_order.IsType(OT_LOADING) || v->current_order.GetNonStopType() == ONSF_STOP_EVERYWHERE);
+ Ticks start_time = _date_fract - v->current_order_time;
+
+ FillTimetableArrivalDepartureTable(v, v->cur_order_index % v->GetNumOrders(), travelling, table, start_time);
+
+ return (travelling && v->lateness_counter < 0);
}
virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
{
switch (widget) {
+ case TTV_ARRIVAL_DEPARTURE_PANEL:
+ SetDParam(0, STR_JUST_DATE_TINY);
+ SetDParam(1, MAX_YEAR * DAYS_IN_YEAR);
+ size->width = GetStringBoundingBox(STR_TIMETABLE_ARRIVAL).width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
+ /* fall through */
+ case TTV_ARRIVAL_DEPARTURE_SELECTION:
case TTV_TIMETABLE_PANEL:
resize->height = FONT_HEIGHT_NORMAL;
size->height = WD_FRAMERECT_TOP + 8 * resize->height + WD_FRAMERECT_BOTTOM;
@@ -105,6 +233,11 @@ struct TimetableWindow : Window {
this->sel_index = -1;
break;
+ case -2:
+ this->UpdateSelectionStates();
+ this->ReInit();
+ break;
+
default: {
/* Moving an order. If one of these is INVALID_VEH_ORDER_ID, then
* the order is being created / removed */
@@ -187,7 +320,10 @@ struct TimetableWindow : Window {
virtual void SetStringParameters(int widget) const
{
- if (widget == TTV_CAPTION) SetDParam(0, this->vehicle->index);
+ switch (widget) {
+ case TTV_CAPTION: SetDParam(0, this->vehicle->index); break;
+ case TTV_EXPECTED: SetDParam(0, this->show_expected ? STR_TIMETABLE_EXPECTED : STR_TIMETABLE_SCHEDULED); break;
+ }
}
virtual void DrawWidget(const Rect &r, int widget) const
@@ -241,6 +377,55 @@ struct TimetableWindow : Window {
break;
}
+ case TTV_ARRIVAL_DEPARTURE_PANEL: {
+ /* Arrival and departure times are handled in an all-or-nothing approach,
+ * i.e. are only shown if we can calculate all times.
+ * Excluding order lists with only one order makes some things easier.
+ */
+ Ticks total_time = v->orders.list != NULL ? v->orders.list->GetTimetableDurationIncomplete() : 0;
+ if (total_time <= 0 || v->GetNumOrders() <= 1 || !HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) break;
+
+ TimetableArrivalDeparture *arr_dep = AllocaM(TimetableArrivalDeparture, v->GetNumOrders());
+ const VehicleOrderID cur_order = v->cur_order_index % v->GetNumOrders();
+
+ VehicleOrderID earlyID = BuildArrivalDepartureList(v, arr_dep) ? cur_order : (VehicleOrderID)INVALID_VEH_ORDER_ID;
+
+ int y = r.top + WD_FRAMERECT_TOP;
+
+ Ticks offset;
+ StringID str_offset;
+ if (this->show_expected && v->lateness_counter > DAY_TICKS) {
+ offset = 0;
+ str_offset = STR_TIMETABLE_ARRIVAL_LATE - STR_TIMETABLE_ARRIVAL;
+ } else {
+ offset = -v->lateness_counter;
+ str_offset = 0;
+ }
+
+ for (int i = this->vscroll.GetPosition(); i / 2 < v->GetNumOrders(); ++i) { // note: i is also incremented in the loop
+ /* Don't draw anything if it extends past the end of the window. */
+ if (!this->vscroll.IsVisible(i)) break;
+
+ if (i % 2 == 0) {
+ if (arr_dep[i / 2].arrival != INVALID_TICKS) {
+ if (this->show_expected && i / 2 == earlyID) {
+ SetArrivalDepartParams(0, 1, arr_dep[i / 2].arrival);
+ DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_ARRIVAL_EARLY, i == selected ? TC_WHITE : TC_BLACK);
+ } else {
+ SetArrivalDepartParams(0, 1, arr_dep[i / 2].arrival + offset);
+ DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_ARRIVAL + str_offset, i == selected ? TC_WHITE : TC_BLACK);
+ }
+ }
+ } else {
+ if (arr_dep[i / 2].departure != INVALID_TICKS) {
+ SetArrivalDepartParams(0, 1, arr_dep[i/2].departure + offset);
+ DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_DEPARTURE + str_offset, i == selected ? TC_WHITE : TC_BLACK);
+ }
+ }
+ y += FONT_HEIGHT_NORMAL;
+ }
+ } break;
+
case TTV_SUMMARY_PANEL: {
int y = r.top + WD_FRAMERECT_TOP;
@@ -325,6 +510,10 @@ struct TimetableWindow : Window {
if (_ctrl_pressed) SetBit(p2, 1);
DoCommandP(0, v->index, p2, CMD_AUTOFILL_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
} break;
+
+ case TTV_EXPECTED:
+ this->show_expected = !this->show_expected;
+ break;
}
this->SetDirty();
@@ -351,6 +540,16 @@ struct TimetableWindow : Window {
/* Update the scroll bar */
this->vscroll.SetCapacity((this->GetWidget<NWidgetBase>(TTV_TIMETABLE_PANEL)->current_y - WD_FRAMERECT_TOP - WD_FRAMERECT_BOTTOM) / this->resize.step_height);
}
+
+ /**
+ * Update the selection state of the arrival/departure data
+ */
+ void UpdateSelectionStates()
+ {
+ int plane = _settings_client.gui.timetable_arrival_departure ? 0 : STACKED_SELECTION_ZERO_SIZE;
+ this->GetWidget<NWidgetStacked>(TTV_ARRIVAL_DEPARTURE_SELECTION)->SetDisplayedPlane(plane);
+ this->GetWidget<NWidgetStacked>(TTV_EXPECTED_SELECTION)->SetDisplayedPlane(plane);
+ }
};
static const NWidgetPart _nested_timetable_widgets[] = {
@@ -362,15 +561,21 @@ static const NWidgetPart _nested_timetable_widgets[] = {
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_GREY, TTV_TIMETABLE_PANEL), SetMinimalSize(388, 82), SetResize(1, 10), SetDataTip(STR_NULL, STR_TIMETABLE_TOOLTIP), EndContainer(),
+ NWidget(WWT_SCROLLBAR, COLOUR_GREY, TTV_FAKE_SCROLLBAR), SetMinimalSize(0, 0), // Hack so the timetable panel can 'use' the scrollbar too
+ NWidget(NWID_SELECTION, INVALID_COLOUR, TTV_ARRIVAL_DEPARTURE_SELECTION),
+ NWidget(WWT_PANEL, COLOUR_GREY, TTV_ARRIVAL_DEPARTURE_PANEL), SetMinimalSize(110, 0), SetFill(0, 1), SetDataTip(STR_NULL, STR_TIMETABLE_TOOLTIP), EndContainer(),
+ EndContainer(),
NWidget(WWT_SCROLLBAR, COLOUR_GREY, TTV_SCROLLBAR),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, TTV_SUMMARY_PANEL), SetMinimalSize(400, 22), SetResize(1, 0), EndContainer(),
NWidget(NWID_HORIZONTAL),
- NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_CHANGE_TIME), SetMinimalSize(110, 12), SetDataTip(STR_TIMETABLE_CHANGE_TIME, STR_TIMETABLE_WAIT_TIME_TOOLTIP),
- NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_CLEAR_TIME), SetMinimalSize(110, 12), SetDataTip(STR_TIMETABLE_CLEAR_TIME, STR_TIMETABLE_CLEAR_TIME_TOOLTIP),
- NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_RESET_LATENESS), SetMinimalSize(118, 12), SetDataTip(STR_TIMETABLE_RESET_LATENESS, STR_TIMETABLE_RESET_LATENESS_TOOLTIP),
- NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_AUTOFILL), SetMinimalSize(50, 12), SetDataTip(STR_TIMETABLE_AUTOFILL, STR_TIMETABLE_AUTOFILL_TOOLTIP),
- NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetResize(1, 0), EndContainer(),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_CHANGE_TIME), SetMinimalSize(110, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_TIMETABLE_CHANGE_TIME, STR_TIMETABLE_WAIT_TIME_TOOLTIP),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_CLEAR_TIME), SetMinimalSize(110, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_TIMETABLE_CLEAR_TIME, STR_TIMETABLE_CLEAR_TIME_TOOLTIP),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_RESET_LATENESS), SetMinimalSize(118, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_TIMETABLE_RESET_LATENESS, STR_TIMETABLE_RESET_LATENESS_TOOLTIP),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_AUTOFILL), SetMinimalSize(50, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_TIMETABLE_AUTOFILL, STR_TIMETABLE_AUTOFILL_TOOLTIP),
+ NWidget(NWID_SELECTION, INVALID_COLOUR, TTV_EXPECTED_SELECTION),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_EXPECTED), SetMinimalSize(50, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BLACK_STRING, STR_TIMETABLE_EXPECTED_TOOLTIP),
+ EndContainer(),
NWidget(WWT_RESIZEBOX,COLOUR_GREY),
EndContainer(),
};