diff --git a/openttd.h b/openttd.h index 14ecc6dcb6..6d53aa7b8b 100644 --- a/openttd.h +++ b/openttd.h @@ -279,6 +279,7 @@ enum { NUM_CARGO = 12, + CT_NO_REFIT = 0xFE, CT_INVALID = 0xFF }; diff --git a/vehicle.c b/vehicle.c index 1753181cdd..76d78168b3 100644 --- a/vehicle.c +++ b/vehicle.c @@ -729,7 +729,7 @@ CargoID FindFirstRefittableCargo(EngineID engine_type) */ int32 GetRefitCost(EngineID engine_type) { - int32 base_cost = 0; + int32 base_cost; switch (GetEngine(engine_type)->type) { case VEH_Ship: base_cost = _price.ship_base; break; @@ -1660,6 +1660,65 @@ static void MoveVehicleCargo(Vehicle *dest, Vehicle *source) } while ((source = source->next) != NULL); } +/** + * Function to find what type of cargo to refit to when autoreplacing + * @param *v Original vehicle, that is being replaced + * @param engine_type The EngineID of the vehicle that is being replaced to + * @return The cargo type to replace to + * CT_NO_REFIT is returned if no refit is needed + * CT_INVALID is returned when both old and new vehicle got cargo capacity and refitting the new one to the old one's cargo type isn't possible + */ +static CargoID GetNewCargoTypeForReplace(Vehicle *v, EngineID engine_type) +{ + bool new_cargo_capacity = true; + CargoID new_cargo_type = CT_INVALID; + + switch (v->type) { + case VEH_Train: + new_cargo_capacity = (RailVehInfo(engine_type)->capacity > 0); + new_cargo_type = RailVehInfo(engine_type)->cargo_type; + break; + + case VEH_Road: + new_cargo_capacity = (RoadVehInfo(engine_type)->capacity > 0); + new_cargo_type = RoadVehInfo(engine_type)->cargo_type; + break; + case VEH_Ship: + new_cargo_capacity = (ShipVehInfo(engine_type)->capacity > 0); + new_cargo_type = ShipVehInfo(engine_type)->cargo_type; + break; + + case VEH_Aircraft: + /* all aircraft starts as passenger planes with cargo capacity + * new_cargo_capacity is always true for aircraft, which is the init value. No need to set it here */ + new_cargo_type = CT_PASSENGERS; + break; + + default: NOT_REACHED(); break; + } + + if (!new_cargo_capacity) return CT_NO_REFIT; // Don't try to refit an engine with no cargo capacity + + if (v->cargo_type == new_cargo_type) return CT_NO_REFIT; + if (CanRefitTo(engine_type, v->cargo_type)) return v->cargo_type; + if (v->type != VEH_Train) return CT_INVALID; // We can't refit the vehicle to carry the cargo we want + + /* Below this line it's safe to assume that the vehicle in question is a train */ + + if (v->cargo_cap != 0) return CT_INVALID; // trying to replace a vehicle with cargo capacity into another one with incompatible cargo type + + /* the old engine didn't have cargo capacity, but the new one does + * now we will figure out what cargo the train is carrying and refit to fit this */ + v = GetFirstVehicleInChain(v); + do { + if (v->cargo_cap == 0) continue; + /* Now we found a cargo type being carried on the train and we will see if it is possible to carry to this one */ + if (v->cargo_type == new_cargo_type) return CT_NO_REFIT; + if (CanRefitTo(engine_type, v->cargo_type)) return v->cargo_type; + } while ((v=v->next) != NULL); + return CT_NO_REFIT; // We failed to find a cargo type on the old vehicle and we will not refit the new one +} + /* Replaces a vehicle (used to be called autorenew) * This function is only called from MaybeReplaceVehicle() * Must be called with _current_player set to the owner of the vehicle @@ -1667,7 +1726,7 @@ static void MoveVehicleCargo(Vehicle *dest, Vehicle *source) * @param flags is the flags to use when calling DoCommand(). Mainly DC_EXEC counts * @return value is cost of the replacement or CMD_ERROR */ -static int32 ReplaceVehicle(Vehicle **w, byte flags) +static int32 ReplaceVehicle(Vehicle **w, byte flags, int32 total_cost) { int32 cost; Vehicle *old_v = *w; @@ -1677,43 +1736,29 @@ static int32 ReplaceVehicle(Vehicle **w, byte flags) bool new_front = false; Vehicle *new_v = NULL; char vehicle_name[32]; + CargoID replacement_cargo_type; new_engine_type = EngineReplacementForPlayer(p, old_v->engine_type); if (new_engine_type == INVALID_ENGINE) new_engine_type = old_v->engine_type; + replacement_cargo_type = GetNewCargoTypeForReplace(old_v, new_engine_type); + + /* check if we can't refit to the needed type, so no replace takes place to prevent the vehicle from altering cargo type */ + if (replacement_cargo_type == CT_INVALID) return 0; + cost = DoCommand(old_v->tile, new_engine_type, 3, flags, CMD_BUILD_VEH(old_v->type)); if (CmdFailed(cost)) return cost; + if (replacement_cargo_type != CT_NO_REFIT) cost += GetRefitCost(new_engine_type); // add refit cost + if (flags & DC_EXEC) { - CargoID new_cargo_type = old_v->cargo_type; new_v = GetVehicle(_new_vehicle_id); *w = new_v; //we changed the vehicle, so MaybeReplaceVehicle needs to work on the new one. Now we tell it what the new one is /* refit if needed */ - if (old_v->type == VEH_Train && old_v->cargo_cap == 0 && new_v->cargo_cap != 0) { - // the old engine didn't have cargo capacity, but the new one does - // now we will figure out what cargo the train is carrying and refit to fit this - Vehicle *v = old_v; - CargoID cargo_type_buffer = new_v->cargo_type; - do { - if (v->cargo_cap == 0) continue; - if (v->cargo_type == new_v->cargo_type) { - // the default type is already being carried on the train. No need to refit - cargo_type_buffer = new_v->cargo_type; - break; - } - // now we know that the vehicle is carrying cargo and that it's not the same as - cargo_type_buffer = v->cargo_type; - } while ((v=v->next) != NULL); - new_cargo_type = cargo_type_buffer; - } - - if (new_cargo_type != new_v->cargo_type && new_v->cargo_cap != 0) { - // we add the refit cost to cost, so it's added to the cost animation - // it's not in the calculation of having enough money to actually do the replace since it's rather hard to do by design, but since - // we pay for it, it's nice to make the cost animation include it - int32 temp_cost = DoCommand(0, new_v->index, new_cargo_type, DC_EXEC, CMD_REFIT_VEH(new_v->type)); - if (!CmdFailed(temp_cost)) cost += temp_cost; + if (replacement_cargo_type != CT_NO_REFIT) { + int32 temp_cost = DoCommand(0, new_v->index, replacement_cargo_type, DC_EXEC, CMD_REFIT_VEH(new_v->type)); + assert(!CmdFailed(temp_cost)); // assert failure here indicates a bug in GetNewCargoTypeForReplace() } if (new_v->type == VEH_Train && HASBIT(old_v->u.rail.flags, VRF_REVERSE_DIRECTION) && !IsMultiheaded(new_v) && !(new_v->next != NULL && IsArticulatedPart(new_v->next))) { // we are autorenewing to a single engine, so we will turn it as the old one was turned as well @@ -1763,17 +1808,21 @@ static int32 ReplaceVehicle(Vehicle **w, byte flags) } else { GetName(old_v->string_id & 0x7FF, vehicle_name); } + } else { // flags & DC_EXEC not set + /* Ensure that the player will not end up having negative money while autoreplacing + * This is needed because the only other check is done after the income from selling the old vehicle is substracted from the cost */ + if (p->money64 < (cost + total_cost)) return CMD_ERROR; } - // sell the engine/ find out how much you get for the old engine + /* sell the engine/ find out how much you get for the old engine (income is returned as negative cost) */ cost += DoCommand(0, old_v->index, 0, flags, CMD_SELL_VEH(old_v->type)); if (new_front) { - // now we assign the old unitnumber to the new vehicle + /* now we assign the old unitnumber to the new vehicle */ new_v->unitnumber = cached_unitnumber; } - // Transfer the name of the old vehicle. + /* Transfer the name of the old vehicle */ if ((flags & DC_EXEC) && vehicle_name[0] != '\0') { _cmd_text = vehicle_name; DoCommand(0, new_v->index, 0, DC_EXEC, CMD_NAME_VEHICLE); @@ -1835,17 +1884,8 @@ static void MaybeReplaceVehicle(Vehicle *v) continue; } - if (w->type == VEH_Train && IsTrainWagon(w)) { - EngineID e = EngineReplacementForPlayer(p, w->engine_type); - - if (w->cargo_type != RailVehInfo(e)->cargo_type && !CanRefitTo(e, w->cargo_type)) { - // we can't replace this wagon since the cargo type is incorrent, and we can't refit it - continue; - } - } - /* Now replace the vehicle */ - temp_cost = ReplaceVehicle(&w, flags); + temp_cost = ReplaceVehicle(&w, flags, cost); if (flags & DC_EXEC && (w->type != VEH_Train || w->u.rail.first_engine == INVALID_ENGINE)) { @@ -1919,14 +1959,14 @@ static void MaybeReplaceVehicle(Vehicle *v) } /** - * @param sort_list list to store the list in. Note: it's presumed that it is big enough to store all vehicles in the game (worst case) and it will not check size - * @param type type of vehicle - * @param owner PlayerID of owner to generate a list for - * @param station index of station to generate a list for. INVALID_STATION when not used - * @param order index of oder to generate a list for. INVALID_ORDER when not used - * @param window_type tells what kind of window the list is for. Use the VLW flags in vehicle_gui.h - * @return the number of vehicles added to the list - */ +* @param sort_list list to store the list in. Note: it's presumed that it is big enough to store all vehicles in the game (worst case) and it will not check size +* @param type type of vehicle +* @param owner PlayerID of owner to generate a list for +* @param station index of station to generate a list for. INVALID_STATION when not used +* @param order index of oder to generate a list for. INVALID_ORDER when not used +* @param window_type tells what kind of window the list is for. Use the VLW flags in vehicle_gui.h +* @return the number of vehicles added to the list +*/ uint GenerateVehicleSortList(const Vehicle** sort_list, byte type, PlayerID owner, StationID station, OrderID order, uint16 window_type) { const uint subtype = (type != VEH_Aircraft) ? Train_Front : 2;