Import auto timetable separation patch

http://www.tt-forums.net/viewtopic.php?p=1140899#p1140899
This commit is contained in:
patch-import
2015-08-04 21:24:04 +01:00
committed by Jonathan G Rennison
parent 59db260e63
commit 80deb3c01d
16 changed files with 420 additions and 2 deletions

View File

@@ -15,6 +15,7 @@
#include "date_func.h"
#include "window_func.h"
#include "vehicle_base.h"
#include "settings_type.h"
#include "cmd_helper.h"
#include "core/sort_func.hpp"
@@ -355,6 +356,167 @@ CommandCost CmdAutofillTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1,
return CommandCost();
}
/**
* Start or stop automatic management of timetables.
* @param tile Not used.
* @param flags Operation to perform.
* @param p1 Vehicle index.
* @param p2 Various bitstuffed elements
* - p2 = (bit 0) - Set to 1 to enable, 0 to disable automation.
* - p2 = (bit 1) - Ctrl was pressed. Used when disabling to keep times.
* @param text unused
* @return the cost of this operation or an error
*/
CommandCost CmdAutomateTimetable(TileIndex index, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
if (!_settings_game.order.timetable_automated) return CMD_ERROR;
VehicleID veh = GB(p1, 0, 16);
Vehicle *v = Vehicle::GetIfValid(veh);
if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
CommandCost ret = CheckOwnership(v->owner);
if (ret.Failed()) return ret;
if (flags & DC_EXEC) {
for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) {
if (HasBit(p2, 0)) {
/* Automated timetable. Set flags and clear current times. */
SetBit(v2->vehicle_flags, VF_AUTOMATE_TIMETABLE);
ClrBit(v2->vehicle_flags, VF_AUTOFILL_TIMETABLE);
ClrBit(v2->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
ClrBit(v2->vehicle_flags, VF_TIMETABLE_STARTED);
v2->timetable_start = 0;
v2->lateness_counter = 0;
v2->current_loading_time = 0;
v2->ClearSeparation();
} else {
/* De-automate timetable. Clear flags. */
ClrBit(v2->vehicle_flags, VF_AUTOMATE_TIMETABLE);
ClrBit(v2->vehicle_flags, VF_AUTOFILL_TIMETABLE);
ClrBit(v2->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
v2->ClearSeparation();
if (!HasBit(p2, 1)) {
/* Ctrl wasn't pressed, so clear all timetabled times. */
SetBit(v2->vehicle_flags, VF_TIMETABLE_STARTED);
v2->timetable_start = 0;
v2->lateness_counter = 0;
v2->current_loading_time = 0;
OrderList *orders = v2->orders.list;
if (orders != NULL) {
for (int i = 0; i < orders->GetNumOrders(); i++) {
ChangeTimetable(v2, i, 0, MTF_WAIT_TIME, true);
ChangeTimetable(v2, i, 0, MTF_TRAVEL_TIME, true);
}
}
}
}
SetWindowDirty(WC_VEHICLE_TIMETABLE, v2->index);
}
}
return CommandCost();
}
int TimeToFinishOrder(Vehicle *v, int n)
{
int left;
Order *order = v->GetOrder(n);
int wait_time = order->GetWaitTime();
int travel_time = order->GetTravelTime();
assert(order != NULL);
if ((v->cur_real_order_index == n) && (v->last_station_visited == order->GetDestination())) {
if (wait_time == 0) return -1;
if (v->current_loading_time > 0) {
left = wait_time - v->current_order_time;
} else {
left = wait_time;
}
if (left < 0) left = 0;
} else {
left = travel_time;
if (v->cur_real_order_index == n) left -= v->current_order_time;
if (travel_time == 0 || wait_time == 0) return -1;
if (left < 0) left = 0;
left +=wait_time;
}
return left;
}
int SeparationBetween(Vehicle *v1, Vehicle *v2)
{
if (v1 == v2) return -1;
int separation = 0;
int time;
int n = v1->cur_real_order_index;
while (n != v2->cur_real_order_index) {
time = TimeToFinishOrder(v1, n);
if (time == -1) return -1;
separation += time;
n++;
if (n >= v1->GetNumOrders()) n = 0;
}
int time1 = TimeToFinishOrder(v1, n);
int time2 = TimeToFinishOrder(v2, n);
if (time1 == -1 || time2 == -1) return -1;
time = time1 - time2;
if (time < 0) {
for (n = 0; n < v1->GetNumOrders(); n++) {
Order *order = v1->GetOrder(n);
int wait_time = order->GetWaitTime();
int travel_time = order->GetTravelTime();
if (travel_time == 0 || wait_time == 0) return -1;
time += travel_time + wait_time;
}
}
separation += time;
assert(separation >= 0);
if (separation == 0) return -1;
return separation;
}
void UpdateSeparationOrder(Vehicle *v_start)
{
/* First check if we have a vehicle ahead, and if not search for one. */
if (v_start->AheadSeparation() == NULL) {
v_start->InitSeparation();
}
if (v_start->AheadSeparation() == NULL) {
return;
}
/* Switch positions if necessary. */
int swaps = 0;
bool done = false;
while (!done) {
done = true;
int min_sep = SeparationBetween(v_start, v_start->AheadSeparation());
Vehicle *v = v_start;
do {
if (v != v_start) {
int tmp_sep = SeparationBetween(v_start, v);
if (tmp_sep < min_sep && tmp_sep != -1) {
swaps++;
if (swaps >= 50) {
return;
}
done = false;
v_start->ClearSeparation();
v_start->AddToSeparationBehind(v);
break;
}
}
int separation_ahead = SeparationBetween(v, v->AheadSeparation());
int separation_behind = SeparationBetween(v->BehindSeparation(), v);
v->lateness_counter = (separation_ahead - separation_behind) / 2;
if (separation_ahead == -1 || separation_behind == -1) v->lateness_counter = 0;
v = v->AheadSeparation();
} while (v != v_start);
}
}
/**
* Update the timetable for the vehicle.
* @param v The vehicle to update the timetable for.
@@ -362,9 +524,12 @@ CommandCost CmdAutofillTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1,
*/
void UpdateVehicleTimetable(Vehicle *v, bool travelling)
{
if (!travelling) v->current_loading_time++; // +1 because this time is one tick behind
uint time_taken = v->current_order_time;
uint time_loading = v->current_loading_time;
v->current_order_time = 0;
v->current_loading_time = 0;
if (v->current_order.IsType(OT_IMPLICIT)) return; // no timetabling of auto orders
@@ -375,6 +540,18 @@ void UpdateVehicleTimetable(Vehicle *v, bool travelling)
bool just_started = false;
/* Start automated timetables at first opportunity */
if (!HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED) && HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) {
if (_settings_game.order.timetable_separation) v->ClearSeparation();
SetBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
v->lateness_counter = 0;
if (_settings_game.order.timetable_separation) UpdateSeparationOrder(v);
for (v = v->FirstShared(); v != NULL; v = v->NextShared()) {
SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
}
return;
}
/* This vehicle is arriving at the first destination in the timetable. */
if (v->cur_real_order_index == first_manual_order && travelling) {
/* If the start date hasn't been set, or it was set automatically when
@@ -437,12 +614,60 @@ void UpdateVehicleTimetable(Vehicle *v, bool travelling)
uint timetabled = travelling ? v->current_order.GetTimetabledTravel() :
v->current_order.GetTimetabledWait();
/* Update the timetable to gradually shift order times towards the actual travel times. */
if (timetabled != 0 && HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) {
int32 new_time;
if (travelling) {
new_time = time_taken;
} else {
new_time = time_loading;
}
/* Check for too large a difference from expected time, and if so don't average. */
if (!(new_time > (int32)timetabled * 2 || new_time < (int32)timetabled / 2)) {
int arrival_error = timetabled - new_time;
/* Compute running average, with sign conversion to avoid negative overflow. */
new_time = ((int32)timetabled * 4 + new_time + 2) / 5;
/* Use arrival_error to finetune order ticks. */
if (arrival_error < 0) new_time++;
if (arrival_error > 0) new_time--;
} else if (new_time > (int32)timetabled * 10 && travelling) {
/* Possible jam, clear time and restart timetable for all vehicles.
* Otherwise we risk trains blocking 1-lane stations for long times. */
ChangeTimetable(v, v->cur_real_order_index, 0, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME, true);
for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) {
if (_settings_game.order.timetable_separation) v2->ClearSeparation();
ClrBit(v2->vehicle_flags, VF_TIMETABLE_STARTED);
SetWindowDirty(WC_VEHICLE_TIMETABLE, v2->index);
}
return;
}
if (new_time < 1) new_time = 1;
if (new_time != (int32)timetabled)
ChangeTimetable(v, v->cur_real_order_index, new_time, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME, true);
} else if (timetabled == 0 && HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) {
/* Add times for orders that are not yet timetabled, even while not autofilling */
if (travelling)
ChangeTimetable(v, v->cur_real_order_index, time_taken, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME, true);
else
ChangeTimetable(v, v->cur_real_order_index, time_loading, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME, true);
}
/* Vehicles will wait at stations if they arrive early even if they are not
* timetabled to wait there, so make sure the lateness counter is updated
* when this happens. */
if (timetabled == 0 && (travelling || v->lateness_counter >= 0)) return;
v->lateness_counter -= (timetabled - time_taken);
if (_settings_game.order.timetable_separation && HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) {
v->current_order_time = time_taken;
v->current_loading_time = time_loading;
UpdateSeparationOrder(v);
v->current_order_time = 0;
v->current_loading_time = 0;
} else {
v->lateness_counter -= (timetabled - time_taken);
}
/* When we are more late than this timetabled bit takes we (somewhat expensively)
* check how many ticks the (fully filled) timetable has. If a timetable cycle is