summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--openttd.h1
-rw-r--r--vehicle.c136
2 files changed, 89 insertions, 48 deletions
diff --git a/openttd.h b/openttd.h
index 14ecc6dcb..6d53aa7b8 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 1753181cd..76d78168b 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;