|
@@ -1157,166 +1157,166 @@ static void NormaliseTrainHead(Train *he
|
|
|
{
|
|
|
/* Not much to do! */
|
|
|
if (head == nullptr) return;
|
|
|
|
|
|
/* Tell the 'world' the train changed. */
|
|
|
head->ConsistChanged(CCF_ARRANGE);
|
|
|
UpdateTrainGroupID(head);
|
|
|
|
|
|
/* Not a front engine, i.e. a free wagon chain. No need to do more. */
|
|
|
if (!head->IsFrontEngine()) return;
|
|
|
|
|
|
/* Update the refit button and window */
|
|
|
InvalidateWindowData(WC_VEHICLE_REFIT, head->index, VIWD_CONSIST_CHANGED);
|
|
|
SetWindowWidgetDirty(WC_VEHICLE_VIEW, head->index, WID_VV_REFIT);
|
|
|
|
|
|
/* If we don't have a unit number yet, set one. */
|
|
|
if (head->unitnumber != 0) return;
|
|
|
head->unitnumber = GetFreeUnitNumber(VEH_TRAIN);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Move a rail vehicle around inside the depot.
|
|
|
* @param flags type of operation
|
|
|
* Note: DC_AUTOREPLACE is set when autoreplace tries to undo its modifications or moves vehicles to temporary locations inside the depot.
|
|
|
* @param src_veh source vehicle index
|
|
|
* @param dest_veh what wagon to put the source wagon AFTER, XXX - INVALID_VEHICLE to make a new line
|
|
|
* @param move_chain move all vehicles following the source vehicle
|
|
|
* @return the cost of this operation or an error
|
|
|
*/
|
|
|
CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID dest_veh, bool move_chain)
|
|
|
{
|
|
|
Train *src = Train::GetIfValid(src_veh);
|
|
|
if (src == nullptr) return CMD_ERROR;
|
|
|
|
|
|
CommandCost ret = CheckOwnership(src->owner);
|
|
|
if (ret.Failed()) return ret;
|
|
|
|
|
|
/* Do not allow moving crashed vehicles inside the depot, it is likely to cause asserts later */
|
|
|
if (src->vehstatus & VS_CRASHED) return CMD_ERROR;
|
|
|
|
|
|
/* if nothing is selected as destination, try and find a matching vehicle to drag to. */
|
|
|
Train *dst;
|
|
|
if (dest_veh == INVALID_VEHICLE) {
|
|
|
dst = (src->IsEngine() || (flags & DC_AUTOREPLACE)) ? nullptr : FindGoodVehiclePos(src);
|
|
|
} else {
|
|
|
dst = Train::GetIfValid(dest_veh);
|
|
|
if (dst == nullptr) return CMD_ERROR;
|
|
|
|
|
|
CommandCost ret = CheckOwnership(dst->owner);
|
|
|
ret = CheckOwnership(dst->owner);
|
|
|
if (ret.Failed()) return ret;
|
|
|
|
|
|
/* Do not allow appending to crashed vehicles, too */
|
|
|
if (dst->vehstatus & VS_CRASHED) return CMD_ERROR;
|
|
|
}
|
|
|
|
|
|
/* if an articulated part is being handled, deal with its parent vehicle */
|
|
|
src = src->GetFirstEnginePart();
|
|
|
if (dst != nullptr) {
|
|
|
dst = dst->GetFirstEnginePart();
|
|
|
}
|
|
|
|
|
|
/* don't move the same vehicle.. */
|
|
|
if (src == dst) return CommandCost();
|
|
|
|
|
|
/* locate the head of the two chains */
|
|
|
Train *src_head = src->First();
|
|
|
Train *dst_head;
|
|
|
if (dst != nullptr) {
|
|
|
dst_head = dst->First();
|
|
|
if (dst_head->tile != src_head->tile) return CMD_ERROR;
|
|
|
/* Now deal with articulated part of destination wagon */
|
|
|
dst = dst->GetLastEnginePart();
|
|
|
} else {
|
|
|
dst_head = nullptr;
|
|
|
}
|
|
|
|
|
|
if (src->IsRearDualheaded()) return_cmd_error(STR_ERROR_REAR_ENGINE_FOLLOW_FRONT);
|
|
|
|
|
|
/* When moving all wagons, we can't have the same src_head and dst_head */
|
|
|
if (move_chain && src_head == dst_head) return CommandCost();
|
|
|
|
|
|
/* When moving a multiheaded part to be place after itself, bail out. */
|
|
|
if (!move_chain && dst != nullptr && dst->IsRearDualheaded() && src == dst->other_multiheaded_part) return CommandCost();
|
|
|
|
|
|
/* Check if all vehicles in the source train are stopped inside a depot. */
|
|
|
if (!src_head->IsStoppedInDepot()) return_cmd_error(STR_ERROR_TRAINS_CAN_ONLY_BE_ALTERED_INSIDE_A_DEPOT);
|
|
|
|
|
|
/* Check if all vehicles in the destination train are stopped inside a depot. */
|
|
|
if (dst_head != nullptr && !dst_head->IsStoppedInDepot()) return_cmd_error(STR_ERROR_TRAINS_CAN_ONLY_BE_ALTERED_INSIDE_A_DEPOT);
|
|
|
|
|
|
/* First make a backup of the order of the trains. That way we can do
|
|
|
* whatever we want with the order and later on easily revert. */
|
|
|
TrainList original_src;
|
|
|
TrainList original_dst;
|
|
|
|
|
|
MakeTrainBackup(original_src, src_head);
|
|
|
MakeTrainBackup(original_dst, dst_head);
|
|
|
|
|
|
/* Also make backup of the original heads as ArrangeTrains can change them.
|
|
|
* For the destination head we do not care if it is the same as the source
|
|
|
* head because in that case it's just a copy. */
|
|
|
Train *original_src_head = src_head;
|
|
|
Train *original_dst_head = (dst_head == src_head ? nullptr : dst_head);
|
|
|
|
|
|
/* We want this information from before the rearrangement, but execute this after the validation.
|
|
|
* original_src_head can't be nullptr; src is by definition != nullptr, so src_head can't be nullptr as
|
|
|
* src->GetFirst() always yields non-nullptr, so eventually original_src_head != nullptr as well. */
|
|
|
bool original_src_head_front_engine = original_src_head->IsFrontEngine();
|
|
|
bool original_dst_head_front_engine = original_dst_head != nullptr && original_dst_head->IsFrontEngine();
|
|
|
|
|
|
/* (Re)arrange the trains in the wanted arrangement. */
|
|
|
ArrangeTrains(&dst_head, dst, &src_head, src, move_chain);
|
|
|
|
|
|
if ((flags & DC_AUTOREPLACE) == 0) {
|
|
|
/* If the autoreplace flag is set we do not need to test for the validity
|
|
|
* because we are going to revert the train to its original state. As we
|
|
|
* assume the original state was correct autoreplace can skip this. */
|
|
|
CommandCost ret = ValidateTrains(original_dst_head, dst_head, original_src_head, src_head, true);
|
|
|
ret = ValidateTrains(original_dst_head, dst_head, original_src_head, src_head, true);
|
|
|
if (ret.Failed()) {
|
|
|
/* Restore the train we had. */
|
|
|
RestoreTrainBackup(original_src);
|
|
|
RestoreTrainBackup(original_dst);
|
|
|
return ret;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* do it? */
|
|
|
if (flags & DC_EXEC) {
|
|
|
/* Remove old heads from the statistics */
|
|
|
if (original_src_head_front_engine) GroupStatistics::CountVehicle(original_src_head, -1);
|
|
|
if (original_dst_head_front_engine) GroupStatistics::CountVehicle(original_dst_head, -1);
|
|
|
|
|
|
/* First normalise the sub types of the chains. */
|
|
|
NormaliseSubtypes(src_head);
|
|
|
NormaliseSubtypes(dst_head);
|
|
|
|
|
|
/* There are 14 different cases:
|
|
|
* 1) front engine gets moved to a new train, it stays a front engine.
|
|
|
* a) the 'next' part is a wagon that becomes a free wagon chain.
|
|
|
* b) the 'next' part is an engine that becomes a front engine.
|
|
|
* c) there is no 'next' part, nothing else happens
|
|
|
* 2) front engine gets moved to another train, it is not a front engine anymore
|
|
|
* a) the 'next' part is a wagon that becomes a free wagon chain.
|
|
|
* b) the 'next' part is an engine that becomes a front engine.
|
|
|
* c) there is no 'next' part, nothing else happens
|
|
|
* 3) front engine gets moved to later in the current train, it is not a front engine anymore.
|
|
|
* a) the 'next' part is a wagon that becomes a free wagon chain.
|
|
|
* b) the 'next' part is an engine that becomes a front engine.
|
|
|
* 4) free wagon gets moved
|
|
|
* a) the 'next' part is a wagon that becomes a free wagon chain.
|
|
|
* b) the 'next' part is an engine that becomes a front engine.
|
|
|
* c) there is no 'next' part, nothing else happens
|
|
|
* 5) non front engine gets moved and becomes a new train, nothing else happens
|
|
|
* 6) non front engine gets moved within a train / to another train, nothing happens
|
|
|
* 7) wagon gets moved, nothing happens
|
|
|
*/
|
|
|
if (src == original_src_head && src->IsEngine() && !src->IsFrontEngine()) {
|
|
|
/* Cases #2 and #3: the front engine gets trashed. */
|
|
|
CloseWindowById(WC_VEHICLE_VIEW, src->index);
|
|
|
CloseWindowById(WC_VEHICLE_ORDERS, src->index);
|
|
|
CloseWindowById(WC_VEHICLE_REFIT, src->index);
|
|
|
CloseWindowById(WC_VEHICLE_DETAILS, src->index);
|
|
|
CloseWindowById(WC_VEHICLE_TIMETABLE, src->index);
|
|
|
DeleteNewGRFInspectWindow(GSF_TRAINS, src->index);
|
|
|
SetWindowDirty(WC_COMPANY, _current_company);
|
|
|
|