diff --git a/src/timetable.h b/src/timetable.h new file mode 100644 index 0000000000..b488054170 --- /dev/null +++ b/src/timetable.h @@ -0,0 +1,11 @@ +/* $Id$ */ + +/** @file timetable.h */ + +#ifndef TIMETABLE_H +#define TIMETABLE_H + +void ShowTimetableWindow(const Vehicle *v); +void UpdateVehicleTimetable(Vehicle *v, bool travelling); + +#endif /* TIMETABLE_H */ diff --git a/src/timetable_cmd.cpp b/src/timetable_cmd.cpp new file mode 100644 index 0000000000..c210d94201 --- /dev/null +++ b/src/timetable_cmd.cpp @@ -0,0 +1,103 @@ +/* $Id$ */ + +/** @file timetable_cmd.cpp */ + +#include "stdafx.h" +#include "openttd.h" +#include "functions.h" +#include "variables.h" +#include "table/strings.h" +#include "command.h" +#include "vehicle.h" + + +/** + * Add or remove waiting times from an order. + * @param tile Not used. + * @param flags Operation to perform. + * @param p1 Various bitstuffed elements + * - p1 = (bit 0-15) - Vehicle with the orders to change. + * - p1 = (bit 16-23) - Order index to modify. + * - p1 = (bit 24) - Whether to change the waiting time or the travelling + * time. + * @param p2 The amount of time to wait. + */ +CommandCost CmdChangeTimetable(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) +{ + if (!_patches.timetabling) return CMD_ERROR; + + VehicleID veh = GB(p1, 0, 16); + if (!IsValidVehicleID(veh)) return CMD_ERROR; + + Vehicle *v = GetVehicle(veh); + if (!CheckOwnership(v->owner)) return CMD_ERROR; + + VehicleOrderID order_number = GB(p1, 16, 8); + Order *order = GetVehicleOrder(v, order_number); + if (order == NULL) return CMD_ERROR; + + bool is_journey = HASBIT(p1, 24); + if (!is_journey) { + if (order->type != OT_GOTO_STATION) return_cmd_error(STR_TIMETABLE_ONLY_WAIT_AT_STATIONS); + if (_patches.new_nonstop && (order->flags & OF_NON_STOP)) return_cmd_error(STR_TIMETABLE_NOT_STOPPING_HERE); + } + + if (flags & DC_EXEC) { + if (is_journey) { + order->travel_time = p2; + } else { + order->wait_time = p2; + } + + if (v->cur_order_index == order_number && HASBIT(v->current_order.flags, OFB_PART_OF_ORDERS)) { + if (is_journey) { + v->current_order.travel_time = p2; + } else { + v->current_order.wait_time = p2; + } + } + + InvalidateWindow(WC_VEHICLE_TIMETABLE, v->index); + } + + return CommandCost(); +} + +/** + * Clear the lateness counter to make the vehicle on time. + * @param tile Not used. + * @param flags Operation to perform. + * @param p1 Various bitstuffed elements + * - p1 = (bit 0-15) - Vehicle with the orders to change. + */ +CommandCost CmdSetVehicleOnTime(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) +{ + if (!_patches.timetabling) return CMD_ERROR; + + VehicleID veh = GB(p1, 0, 16); + if (!IsValidVehicleID(veh)) return CMD_ERROR; + + Vehicle *v = GetVehicle(veh); + if (!CheckOwnership(v->owner)) return CMD_ERROR; + + if (flags & DC_EXEC) { + v->lateness_counter = 0; + } + + return CommandCost(); +} + + +void UpdateVehicleTimetable(Vehicle *v, bool travelling) +{ + uint timetabled = travelling ? v->current_order.travel_time : v->current_order.wait_time; + uint time_taken = v->current_order_time; + + v->current_order_time = 0; + + if (!_patches.timetabling || timetabled == 0) return; + + v->lateness_counter -= (timetabled - time_taken); + + InvalidateWindow(WC_VEHICLE_TIMETABLE, v->index); +} diff --git a/src/timetable_gui.cpp b/src/timetable_gui.cpp new file mode 100644 index 0000000000..303664c566 --- /dev/null +++ b/src/timetable_gui.cpp @@ -0,0 +1,310 @@ +/* $Id$ */ + +/** @file timetable_gui.cpp */ + +#include "stdafx.h" +#include "openttd.h" +#include "functions.h" +#include "variables.h" +#include "table/strings.h" +#include "command.h" +#include "date.h" +#include "engine.h" +#include "gui.h" +#include "string.h" +#include "window.h" +#include "vehicle.h" +#include "cargotype.h" +#include "depot.h" + +static int GetOrderFromTimetableWndPt(Window *w, int y, const Vehicle *v) +{ + /* + * Calculation description: + * 15 = 14 (w->widget[ORDER_WIDGET_ORDER_LIST].top) + 1 (frame-line) + * 10 = order text hight + */ + int sel = (y - 15) / 10; + + if ((uint)sel >= w->vscroll.cap) return INVALID_ORDER; + + sel += w->vscroll.pos; + + return (sel <= v->num_orders * 2 && sel >= 0) ? sel : INVALID_ORDER; +} + +static inline void SetTimetableParams(int param1, int param2, uint32 time) +{ + if (_patches.timetable_in_ticks) { + SetDParam(param1, STR_TIMETABLE_TICKS); + SetDParam(param2, time); + } else { + SetDParam(param1, STR_TIMETABLE_DAYS); + SetDParam(param2, time / DAY_TICKS); + } +} + +static void DrawTimetableWindow(Window *w) +{ + const Vehicle *v = GetVehicle(w->window_number); + int selected = WP(w,order_d).sel; + + SetVScrollCount(w, v->num_orders * 2); + + if (v->owner == _local_player) { + if (selected == -1) { + DisableWindowWidget(w, 6); + DisableWindowWidget(w, 7); + } else if (selected % 2 == 1) { + EnableWindowWidget(w, 6); + EnableWindowWidget(w, 7); + } else { + const Order *order = GetVehicleOrder(v, (selected + 1) / 2); + bool disable = order == NULL || order->type != OT_GOTO_STATION || (_patches.new_nonstop && (order->flags & OF_NON_STOP)); + + SetWindowWidgetDisabledState(w, 6, disable); + SetWindowWidgetDisabledState(w, 7, disable); + } + + EnableWindowWidget(w, 8); + } else { + DisableWindowWidget(w, 6); + DisableWindowWidget(w, 7); + DisableWindowWidget(w, 8); + } + + SetDParam(0, v->string_id); + SetDParam(1, v->unitnumber); + DrawWindowWidgets(w); + + int y = 15; + int i = w->vscroll.pos; + VehicleOrderID order_id = (i + 1) / 2; + bool final_order = false; + + const Order *order = GetVehicleOrder(v, order_id); + + while (order != NULL) { + /* Don't draw anything if it extends past the end of the window. */ + if (i - w->vscroll.pos >= w->vscroll.cap) break; + + if (i % 2 == 0) { + SetDParam(2, STR_EMPTY); + + switch (order->type) { + case OT_GOTO_STATION: + SetDParam(0, (order->flags & OF_NON_STOP) ? STR_880C_GO_NON_STOP_TO : STR_8806_GO_TO); + SetDParam(1, order->dest); + + if (order->wait_time > 0) { + SetDParam(2, STR_TIMETABLE_STAY_FOR); + SetTimetableParams(3, 4, order->wait_time); + } + + break; + + case OT_GOTO_DEPOT: { + StringID string = STR_EMPTY; + + if (v->type == VEH_AIRCRAFT) { + string = STR_GO_TO_AIRPORT_HANGAR; + SetDParam(1, order->dest); + } else { + SetDParam(1, GetDepot(order->dest)->town_index); + + switch (v->type) { + case VEH_TRAIN: string = (order->flags & OF_NON_STOP) ? STR_880F_GO_NON_STOP_TO_TRAIN_DEPOT : STR_GO_TO_TRAIN_DEPOT; break; + case VEH_ROAD: string = STR_9038_GO_TO_ROADVEH_DEPOT; break; + case VEH_SHIP: string = STR_GO_TO_SHIP_DEPOT; break; + default: break; + } + } + + if (order->flags & OF_FULL_LOAD) string++; // Service at orders + + SetDParam(0, string); + } break; + + case OT_GOTO_WAYPOINT: + SetDParam(0, (order->flags & OF_NON_STOP) ? STR_GO_NON_STOP_TO_WAYPOINT : STR_GO_TO_WAYPOINT); + SetDParam(1, order->dest); + break; + + default: break; + } + + byte colour = (i == selected) ? 0xC : 0x10; + + if (order->type != OT_DUMMY) { + DrawString(2, y, STR_TIMETABLE_GO_TO, colour); + } else { + SetDParam(0, STR_INVALID_ORDER); + DrawString(2, y, STR_TIMETABLE_GO_TO, colour); + } + + order_id++; + + if (order_id >= v->num_orders) { + order = GetVehicleOrder(v, 0); + final_order = true; + } else { + order = order->next; + } + } else { + StringID string; + + if (order->travel_time == 0) { + string = STR_TIMETABLE_TRAVEL_NOT_TIMETABLED; + } else { + SetTimetableParams(0, 1, order->travel_time); + string = STR_TIMETABLE_TRAVEL_FOR; + } + + byte colour = (i == selected) ? 0xC : 0x10; + DrawString(12, y, string, colour); + + if (final_order) break; + } + + i++; + y += 10; + } + + y = w->widget[5].top + 1; + + if (v->lateness_counter == 0 || (!_patches.timetable_in_ticks && v->lateness_counter / DAY_TICKS == 0)) { + DrawString(2, y, STR_TIMETABLE_STATUS_ON_TIME, 0x10); + } else { + SetTimetableParams(0, 1, abs(v->lateness_counter)); + DrawString(2, y, v->lateness_counter < 0 ? STR_TIMETABLE_STATUS_EARLY : STR_TIMETABLE_STATUS_LATE, 0x10); + } +} + +static inline uint32 PackTimetableArgs(const Vehicle *v, uint selected) +{ + uint order_number = (selected + 1) / 2; + uint is_journey = (selected % 2 == 1) ? 1 : 0; + + if (order_number >= v->num_orders) order_number = 0; + + return v->index | (order_number << 16) | (is_journey << 24); +} + +static void TimetableWndProc(Window *w, WindowEvent *we) +{ + switch (we->event) { + case WE_PAINT: + DrawTimetableWindow(w); + break; + + case WE_CLICK: { + const Vehicle *v = GetVehicle(w->window_number); + + switch (we->we.click.widget) { + case 3: { /* Main panel. */ + int selected = GetOrderFromTimetableWndPt(w, we->we.click.pt.y, v); + + if (selected == INVALID_ORDER || selected == WP(w,order_d).sel) { + /* Deselect clicked order */ + WP(w,order_d).sel = -1; + } else { + /* Select clicked order */ + WP(w,order_d).sel = selected; + } + } break; + + case 6: { /* "Wait For" button. */ + int selected = WP(w,order_d).sel; + VehicleOrderID real = (selected + 1) / 2; + + if (real >= v->num_orders) real = 0; + + const Order *order = GetVehicleOrder(v, real); + StringID current = STR_EMPTY; + + if (order != NULL) { + uint time = (selected % 2 == 1) ? order->travel_time : order->wait_time; + if (!_patches.timetable_in_ticks) time /= DAY_TICKS; + + if (time != 0) { + SetDParam(0, time); + current = STR_CONFIG_PATCHES_INT32; + } + } + + ShowQueryString(current, STR_TIMETABLE_CHANGE_TIME, 31, 150, w, CS_NUMERAL); + } break; + + case 7: { /* Clear waiting time button. */ + uint32 p1 = PackTimetableArgs(v, WP(w,order_d).sel); + DoCommandP(0, p1, 0, NULL, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE)); + } break; + + case 8: /* Reset the vehicle's late counter. */ + DoCommandP(0, v->index, 0, NULL, CMD_SET_VEHICLE_ON_TIME | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE)); + break; + } + + SetWindowDirty(w); + } break; + + case WE_ON_EDIT_TEXT: { + const Vehicle *v = GetVehicle(w->window_number); + + uint32 p1 = PackTimetableArgs(v, WP(w,order_d).sel); + + uint64 time = StrEmpty(we->we.edittext.str) ? 0 : strtoul(we->we.edittext.str, NULL, 10); + if (!_patches.timetable_in_ticks) time *= DAY_TICKS; + + uint32 p2 = minu(time, MAX_UVALUE(uint16)); + + DoCommandP(0, p1, p2, NULL, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE)); + } break; + + case WE_RESIZE: + /* Update the scroll + matrix */ + w->vscroll.cap = (w->widget[2].bottom - w->widget[2].top) / 10; + break; + + } +} + +static const Widget _timetable_widgets[] = { + { WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, + { WWT_CAPTION, RESIZE_RIGHT, 14, 11, 337, 0, 13, STR_TIMETABLE_TITLE, STR_018C_WINDOW_TITLE_DRAG_THIS}, + { WWT_STICKYBOX, RESIZE_NONE, 14, 338, 349, 0, 13, STR_NULL, STR_STICKY_BUTTON}, + + { WWT_PANEL, RESIZE_RB, 14, 0, 337, 14, 176, STR_NULL, STR_TIMETABLE_TOOLTIP}, + { WWT_SCROLLBAR, RESIZE_LRB, 14, 338, 349, 14, 176, STR_NULL, STR_0190_SCROLL_BAR_SCROLLS_LIST}, + + { WWT_PANEL, RESIZE_RTB, 14, 0, 349, 177, 188, STR_NULL, STR_NULL}, + + { WWT_PUSHTXTBTN, RESIZE_TB, 14, 0, 109, 189, 200, STR_TIMETABLE_CHANGE_TIME, STR_TIMETABLE_WAIT_TIME_TOOLTIP}, + { WWT_PUSHTXTBTN, RESIZE_TB, 14, 110, 219, 189, 200, STR_CLEAR_TIME, STR_TIMETABLE_CLEAR_TIME_TOOLTIP}, + { WWT_PUSHTXTBTN, RESIZE_TB, 14, 220, 337, 189, 200, STR_RESET_LATENESS, STR_TIMETABLE_RESET_LATENESS_TOOLTIP}, + + { WWT_PANEL, RESIZE_RTB, 14, 338, 337, 189, 200, STR_NULL, STR_NULL}, + { WWT_RESIZEBOX, RESIZE_LRTB, 14, 338, 349, 189, 200, STR_NULL, STR_RESIZE_BUTTON}, + + { WIDGETS_END } +}; + +static const WindowDesc _timetable_desc = { + WDP_AUTO, WDP_AUTO, 350, 201, + WC_VEHICLE_TIMETABLE, WC_VEHICLE_VIEW, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE, + _timetable_widgets, + TimetableWndProc +}; + +void ShowTimetableWindow(const Vehicle *v) +{ + Window *w = AllocateWindowDescFront(&_timetable_desc, v->index); + + if (w != NULL) { + w->caption_color = v->owner; + w->vscroll.cap = 16; + w->resize.step_height = 10; + WP(w,order_d).sel = -1; + } +}