Import Improved Breakdowns patch

Fix trailing whitespace

http://www.tt-forums.net/viewtopic.php?p=1146419#p1146419
pull/3/head
patch-import 9 years ago committed by Jonathan G Rennison
parent c8a727d3fc
commit 9f5164b403

@ -57,6 +57,7 @@ void UpdateAircraftCache(Aircraft *v, bool update_range = false);
void AircraftLeaveHangar(Aircraft *v, Direction exit_dir);
void AircraftNextAirportPos_and_Order(Aircraft *v);
void SetAircraftPosition(Aircraft *v, int x, int y, int z);
void FindBreakdownDestination(Aircraft *v);
void GetAircraftFlightLevelBounds(const Vehicle *v, int *min, int *max);
template <class T>

@ -130,6 +130,11 @@ static StationID FindNearestHangar(const Aircraft *v)
const AirportFTAClass *afc = st->airport.GetFTA();
if (!st->airport.HasHangar() || (
/* the airport needs to have facilities for this plane type */
(AircraftVehInfo(v->engine_type)->subtype & AIR_CTOL) ?
!(afc->flags & AirportFTAClass::AIRPLANES) :
!(afc->flags & AirportFTAClass::HELICOPTERS)
) || (
/* don't crash the plane if we know it can't land at the airport */
(afc->flags & AirportFTAClass::SHORT_STRIP) &&
(avi->subtype & AIR_FAST) &&
@ -301,6 +306,9 @@ CommandCost CmdBuildAircraft(TileIndex tile, DoCommandFlag flags, const Engine *
v->reliability = e->reliability;
v->reliability_spd_dec = e->reliability_spd_dec;
/* higher speed means higher breakdown chance */
/* to somewhat compensate for the fact that fast aircraft spend less time in the air */
v->breakdown_chance = Clamp(64 + (AircraftVehInfo(v->engine_type)->max_speed >> 3), 0, 255);
v->max_age = e->GetLifeLengthInDays();
_new_vehicle_id = v->index;
@ -642,7 +650,7 @@ static int UpdateAircraftSpeed(Aircraft *v, uint speed_limit = SPEED_LIMIT_NONE,
spd = min(v->cur_speed + (spd >> 8) + (v->subspeed < t), speed_limit);
/* adjust speed for broken vehicles */
if (v->vehstatus & VS_AIRCRAFT_BROKEN) spd = min(spd, SPEED_LIMIT_BROKEN);
if (v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_AIRCRAFT_SPEED) spd = min(v->breakdown_severity << 3, spd);
/* updates statusbar only if speed have changed to save CPU time */
if (spd != v->cur_speed) {
@ -1094,6 +1102,39 @@ static bool AircraftController(Aircraft *v)
return false;
}
/**
* Send a broken plane that needs to visit a depot to the correct location.
* @param v The airplane in question
*/
void FindBreakdownDestination(Aircraft *v)
{
assert(v->type == VEH_AIRCRAFT && v->breakdown_ctr == 1);
DestinationID destination = INVALID_STATION;
if (v->breakdown_type == BREAKDOWN_AIRCRAFT_DEPOT) {
/* Go to a hangar, if possible at our current destination */
v->FindClosestDepot(NULL, &destination, NULL);
} else if (v->breakdown_type == BREAKDOWN_AIRCRAFT_EM_LANDING) {
/* Go to the nearest airport with a hangar */
destination = FindNearestHangar(v);
} else {
NOT_REACHED();
}
if(destination != INVALID_STATION) {
if(destination != v->current_order.GetDestination()) {
v->current_order.MakeGoToDepot(destination, ODTFB_BREAKDOWN);
AircraftNextAirportPos_and_Order(v);
} else {
v->current_order.MakeGoToDepot(destination, ODTFB_BREAKDOWN);
}
} else {
/* If no hangar was found, crash */
v->targetairport = INVALID_STATION;
CrashAirplane(v);
}
}
/**
* Handle crashed aircraft \a v.
* @param v Crashed aircraft.
@ -1174,8 +1215,9 @@ static void HandleAircraftSmoke(Aircraft *v, bool mode)
if (!(v->vehstatus & VS_AIRCRAFT_BROKEN)) return;
/* breakdown-related speed limits are lifted when we are on the ground */
if(v->state != FLYING && v->state != LANDING && v->breakdown_type == BREAKDOWN_AIRCRAFT_SPEED) {
/* Stop smoking when landed */
if (v->cur_speed < 10) {
v->vehstatus &= ~VS_AIRCRAFT_BROKEN;
v->breakdown_ctr = 0;
return;
@ -1291,13 +1333,18 @@ static void MaybeCrashAirplane(Aircraft *v)
Station *st = Station::Get(v->targetairport);
/* FIXME -- MaybeCrashAirplane -> increase crashing chances of very modern airplanes on smaller than AT_METROPOLITAN airports */
uint32 prob = (0x4000 << _settings_game.vehicle.plane_crashes);
uint32 prob = (_settings_game.vehicle.improved_breakdowns && _settings_game.difficulty.vehicle_breakdowns) ?
0x10000 / 10000 : 0x4000 << _settings_game.vehicle.plane_crashes;
if ((st->airport.GetFTA()->flags & AirportFTAClass::SHORT_STRIP) &&
(AircraftVehInfo(v->engine_type)->subtype & AIR_FAST) &&
!_cheats.no_jetcrash.value) {
prob /= 20;
} else {
} else if (!_settings_game.vehicle.improved_breakdowns) {
prob /= 1500;
} else if (v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_AIRCRAFT_EM_LANDING) {
/* Airplanes that are attempting an emergency landing have a 2% chance to crash */
prob = 0x10000 / 50;
}
if (GB(Random(), 0, 22) > prob) return;

@ -362,6 +362,7 @@ static bool DisasterTick_Ufo(DisasterVehicle *v)
uint dist = Delta(v->x_pos, u->x_pos) + Delta(v->y_pos, u->y_pos);
if (dist < TILE_SIZE && !(u->vehstatus & VS_HIDDEN) && u->breakdown_ctr == 0) {
u->breakdown_type = BREAKDOWN_CRITICAL;
u->breakdown_ctr = 3;
u->breakdown_delay = 140;
}
@ -547,6 +548,7 @@ static bool DisasterTick_Big_Ufo(DisasterVehicle *v)
if (Delta(target->x_pos, v->x_pos) + Delta(target->y_pos, v->y_pos) <= 12 * (int)TILE_SIZE) {
target->breakdown_ctr = 5;
target->breakdown_delay = 0xF0;
target->breakdown_type = BREAKDOWN_CRITICAL;
}
}
}

@ -30,12 +30,9 @@ void GroundVehicle<T, Type>::PowerChanged()
uint32 number_of_parts = 0;
uint16 max_track_speed = v->GetDisplayMaxSpeed();
for (const T *u = v; u != NULL; u = u->Next()) {
uint32 current_power = u->GetPower() + u->GetPoweredPartPower(u);
total_power += current_power;
this->CalculatePower(total_power, max_te, false);
/* Only powered parts add tractive effort. */
if (current_power > 0) max_te += u->GetWeight() * u->GetTractiveEffort();
for (const T *u = v; u != NULL; u = u->Next()) {
number_of_parts++;
/* Get minimum max speed for this track. */
@ -58,8 +55,6 @@ void GroundVehicle<T, Type>::PowerChanged()
this->gcache.cached_air_drag = air_drag + 3 * air_drag * number_of_parts / 20;
max_te *= 10000; // Tractive effort in (tonnes * 1000 * 10 =) N.
max_te /= 256; // Tractive effort is a [0-255] coefficient.
if (this->gcache.cached_power != total_power || this->gcache.cached_max_te != max_te) {
/* Stop the vehicle if it has no power. */
if (total_power == 0) this->vehstatus |= VS_STOPPED;
@ -73,6 +68,30 @@ void GroundVehicle<T, Type>::PowerChanged()
this->gcache.cached_max_track_speed = max_track_speed;
}
template <class T, VehicleType Type>
void GroundVehicle<T, Type>::CalculatePower(uint32& total_power, uint32& max_te, bool breakdowns) const {
total_power = 0;
max_te = 0;
const T *v = T::From(this);
for (const T *u = v; u != NULL; u = u->Next()) {
uint32 current_power = u->GetPower() + u->GetPoweredPartPower(u);
total_power += current_power;
/* Only powered parts add tractive effort. */
if (current_power > 0) max_te += u->GetWeight() * u->GetTractiveEffort();
if (breakdowns && u->breakdown_ctr == 1 && u->breakdown_type == BREAKDOWN_LOW_POWER) {
total_power = total_power * u->breakdown_severity / 256;
}
}
max_te *= 10000; // Tractive effort in (tonnes * 1000 * 10 =) N.
max_te /= 256; // Tractive effort is a [0-255] coefficient.
}
/**
* Recalculates the cached weight of a vehicle and its parts. Should be called each time the cargo on
* the consist changes.
@ -104,7 +123,7 @@ void GroundVehicle<T, Type>::CargoChanged()
* @return Current acceleration of the vehicle.
*/
template <class T, VehicleType Type>
int GroundVehicle<T, Type>::GetAcceleration() const
int GroundVehicle<T, Type>::GetAcceleration()
{
/* Templated class used for function calls for performance reasons. */
const T *v = T::From(this);
@ -119,6 +138,7 @@ int GroundVehicle<T, Type>::GetAcceleration() const
* and km/h to m/s conversion below result in a maxium of
* about 1.1E11, way more than 4.3E9 of int32. */
int64 power = this->gcache.cached_power * 746ll;
uint32 max_te = this->gcache.cached_max_te; // [N]
/* This is constructed from:
* - axle resistance: U16 power * 10 for 128 vehicles.
@ -150,7 +170,16 @@ int GroundVehicle<T, Type>::GetAcceleration() const
/* This value allows to know if the vehicle is accelerating or braking. */
AccelStatus mode = v->GetAccelerationStatus();
const int max_te = this->gcache.cached_max_te; // [N]
/* handle breakdown power reduction */
//TODO
if( Type == VEH_TRAIN && mode == AS_ACCEL && HasBit(Train::From(this)->flags, VRF_BREAKDOWN_POWER)) {
/* We'd like to cache this, but changing cached_power has too many unwanted side-effects */
uint32 power_temp;
this->CalculatePower(power_temp, max_te, true);
power = power_temp * 74611;
}
/* Constructued from power, with need to multiply by 18 and assuming
* low speed, it needs to be a 64 bit integer too. */
int64 force;
@ -158,7 +187,7 @@ int GroundVehicle<T, Type>::GetAcceleration() const
if (!maglev) {
/* Conversion factor from km/h to m/s is 5/18 to get [N] in the end. */
force = power * 18 / (speed * 5);
if (mode == AS_ACCEL && force > max_te) force = max_te;
if (mode == AS_ACCEL && force > (int)max_te) force = max_te;
} else {
force = power / 25;
}
@ -168,6 +197,34 @@ int GroundVehicle<T, Type>::GetAcceleration() const
force = max(force, (mass * 8) + resistance);
}
/* If power is 0 because of a breakdown, we make the force 0 if accelerating */
if ( Type == VEH_TRAIN && mode == AS_ACCEL && HasBit(Train::From(this)->flags, VRF_BREAKDOWN_POWER) && power == 0) {
force = 0;
}
/* Calculate the breakdown chance */
if (_settings_game.vehicle.improved_breakdowns) {
assert(this->gcache.cached_max_track_speed > 0);
/** First, calculate (resistance / force * current speed / max speed) << 16.
* This yields a number x on a 0-1 scale, but shifted 16 bits to the left.
* We then calculate 64 + 128x, clamped to 0-255, but still shifted 16 bits to the left.
* Then we apply a correction for multiengine trains, and in the end we shift it 16 bits to the right to get a 0-255 number.
* @note A seperate correction for multiheaded engines is done in CheckVehicleBreakdown. We can't do that here because it would affect the whole consist.
*/
uint64 breakdown_factor = (uint64)abs(resistance) * (uint64)(this->cur_speed << 16);
breakdown_factor /= (max(force, (int64)100) * this->gcache.cached_max_track_speed);
breakdown_factor = min((64 << 16) + (breakdown_factor * 128), 255 << 16);
if ( Type == VEH_TRAIN && Train::From(this)->tcache.cached_num_engines > 1) {
/* For multiengine trains, breakdown chance is multiplied by 3 / (num_engines + 2) */
breakdown_factor *= 3;
breakdown_factor /= (Train::From(this)->tcache.cached_num_engines + 2);
}
/* breakdown_chance is at least 5 (5 / 128 = ~4% of the normal chance) */
this->breakdown_chance = max(breakdown_factor >> 16, (uint64)5);
} else {
this->breakdown_chance = 128;
}
if (mode == AS_ACCEL) {
/* Easy way out when there is no acceleration. */
if (force == resistance) return 0;
@ -178,7 +235,27 @@ int GroundVehicle<T, Type>::GetAcceleration() const
* a hill will never speed up enough to (eventually) get back to the
* same (maximum) speed. */
int accel = ClampToI32((force - resistance) / (mass * 4));
return force < resistance ? min(-1, accel) : max(1, accel);
accel = force < resistance ? min(-1, accel) : max(1, accel);
if (this->type == VEH_TRAIN ) {
if(_settings_game.vehicle.train_acceleration_model == AM_ORIGINAL &&
HasBit(Train::From(this)->flags, VRF_BREAKDOWN_POWER)) {
/* We need to apply the power reducation for non-realistic acceleration here */
uint32 power;
CalculatePower(power, max_te, true);
accel = accel * power / this->gcache.cached_power;
accel -= this->acceleration >> 1;
}
if ( this->IsFrontEngine() && !(this->current_order_time & 0x1FF) &&
!(this->current_order.IsType(OT_LOADING)) &&
!(Train::From(this)->flags & (VRF_IS_BROKEN | (1 << VRF_TRAIN_STUCK))) &&
this->cur_speed < 3 && accel < 5) {
SetBit(Train::From(this)->flags, VRF_TO_HEAVY);
}
}
return accel;
} else {
return ClampToI32(min(-force - resistance, -10000) / mass);
}

@ -91,9 +91,12 @@ struct GroundVehicle : public SpecializedVehicle<T, Type> {
void PowerChanged();
void CargoChanged();
int GetAcceleration() const;
bool IsChainInDepot() const;
void CalculatePower(uint32& power, uint32& max_te, bool breakdowns) const;
int GetAcceleration();
/**
* Common code executed for crashed ground vehicles
* @param flooded was this vehicle flooded?
@ -369,7 +372,22 @@ protected:
/* When we are going faster than the maximum speed, reduce the speed
* somewhat gradually. But never lower than the maximum speed. */
int tempmax = max_speed;
int tempmax = ((this->breakdown_ctr == 1) ? this->cur_speed : max_speed);
if (this->breakdown_ctr == 1) {
if (this->breakdown_type == BREAKDOWN_LOW_POWER) {
if((this->tick_counter & 0x7) == 0) {
if(this->cur_speed > (this->breakdown_severity * max_speed) >> 8) {
tempmax = this->cur_speed - (this->cur_speed / 10) - 1;
} else {
tempmax = (this->breakdown_severity * max_speed) >> 8;
}
}
}
if(this->breakdown_type == BREAKDOWN_LOW_SPEED)
tempmax = min(max_speed, this->breakdown_severity);
}
if (this->cur_speed > max_speed) {
tempmax = max(this->cur_speed - (this->cur_speed / 10) - 1, max_speed);
}

@ -1223,6 +1223,7 @@ STR_CONFIG_SETTING_STOP_LOCATION :New train order
STR_CONFIG_SETTING_STOP_LOCATION_HELPTEXT :Place where a train will stop at the platform by default. The 'near end' means close to the entry point, 'middle' means in the middle of the platform, and 'far end' means far away from the entry point. Note, that this setting only defines a default value for new orders. Individual orders can be set explicitly to either behaviour nevertheless
STR_CONFIG_SETTING_STOP_LOCATION_NEAR_END :near end
STR_CONFIG_SETTING_STOP_LOCATION_MIDDLE :middle
STR_CONFIG_SETTING_IMPROVED_BREAKDOWNS :{LTBLUE}Enable improved breakdowns: {ORANGE}{STRING}
STR_CONFIG_SETTING_STOP_LOCATION_FAR_END :far end
STR_CONFIG_SETTING_AUTOSCROLL :Pan window when mouse is at the edge: {STRING2}
STR_CONFIG_SETTING_AUTOSCROLL_HELPTEXT :When enabled, viewports will start to scroll when the mouse is near the edge of the window
@ -3624,11 +3625,20 @@ STR_VEHICLE_STATUS_LEAVING :{LTBLUE}Leaving
STR_VEHICLE_STATUS_CRASHED :{RED}Crashed!
STR_VEHICLE_STATUS_BROKEN_DOWN :{RED}Broken down
STR_VEHICLE_STATUS_STOPPED :{RED}Stopped
STR_VEHICLE_STATUS_BROKEN_DOWN_VEL :{RED}Broken down - {STRING1}, {LTBLUE} {VELOCITY}
STR_VEHICLE_STATUS_TRAIN_STOPPING_VEL :{RED}Stopping, {VELOCITY}
STR_VEHICLE_STATUS_TRAIN_NO_POWER :{RED}No power
STR_VEHICLE_STATUS_TRAIN_STUCK :{ORANGE}Waiting for free path
STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR :{ORANGE}Too far to next destination
STR_BREAKDOWN_TYPE_CRITICAL :Mechanical failure
STR_BREAKDOWN_TYPE_EM_STOP :Emergency stop
STR_BREAKDOWN_TYPE_LOW_SPEED :Limited to {VELOCITY}
STR_BREAKDOWN_TYPE_LOW_POWER :{COMMA}% Power
STR_BREAKDOWN_TYPE_DEPOT :Heading to {STATION} Hangar for repairs
STR_BREAKDOWN_TYPE_LANDING :Heading to {STATION} for emergency landing
STR_ERROR_TRAIN_TOO_HEAVY :{WHITE}{VEHICLE} is too heavy
STR_VEHICLE_STATUS_HEADING_FOR_STATION_VEL :{LTBLUE}Heading for {STATION}, {VELOCITY}
STR_VEHICLE_STATUS_NO_ORDERS_VEL :{LTBLUE}No orders, {VELOCITY}
STR_VEHICLE_STATUS_HEADING_FOR_WAYPOINT_VEL :{LTBLUE}Heading for {WAYPOINT}, {VELOCITY}
@ -3651,6 +3661,11 @@ STR_VEHICLE_DETAILS_SHIP_RENAME :{BLACK}Name shi
STR_VEHICLE_DETAILS_AIRCRAFT_RENAME :{BLACK}Name aircraft
STR_VEHICLE_INFO_AGE_RUNNING_COST_YR :{BLACK}Age: {LTBLUE}{STRING2}{BLACK} Running Cost: {LTBLUE}{CURRENCY_LONG}/yr
STR_RUNNING :{LTBLUE}Running
STR_NEED_REPAIR :{ORANGE}Train needs repair - max speed reduced to {VELOCITY}
STR_CURRENT_STATUS :{BLACK}Current status: {STRING2}
# The next two need to stay in this order
STR_VEHICLE_INFO_AGE :{COMMA} year{P "" s} ({COMMA})
STR_VEHICLE_INFO_AGE_RED :{RED}{COMMA} year{P "" s} ({COMMA})
@ -3688,7 +3703,9 @@ STR_QUERY_RENAME_AIRCRAFT_CAPTION :{WHITE}Name air
# Extra buttons for train details windows
STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE :{LTBLUE}{ENGINE}{BLACK} Built: {LTBLUE}{NUM}{BLACK} Value: {LTBLUE}{CURRENCY_LONG}
STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE_AND_SPEED :{LTBLUE}{ENGINE}{BLACK} Built: {LTBLUE}{NUM}{BLACK} Value: {LTBLUE}{CURRENCY_LONG} {BLACK}Max. speed: {LTBLUE}{VELOCITY}
STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE :{LTBLUE}{ENGINE}{BLACK} Value: {LTBLUE}{CURRENCY_LONG}
STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE_AND_SPEED :{LTBLUE}{ENGINE}{BLACK} Value: {LTBLUE}{CURRENCY_LONG} {BLACK}Max. speed: {LTBLUE}{VELOCITY}
STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_TEXT :{BLACK}Total cargo capacity of this train:
STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY :{LTBLUE}- {CARGO_LONG} ({CARGO_SHORT})

@ -100,6 +100,7 @@ enum OrderDepotTypeFlags {
ODTF_MANUAL = 0, ///< Manually initiated order.
ODTFB_SERVICE = 1 << 0, ///< This depot order is because of the servicing limit.
ODTFB_PART_OF_ORDERS = 1 << 1, ///< This depot order is because of a regular order.
ODTFB_BREAKDOWN = 1 << 2, ///< This depot order is because of a breakdown.
};
/**

@ -163,6 +163,8 @@ protected: // These functions should not be called outside acceleration code.
if (!this->IsArticulatedPart()) {
/* Road vehicle weight is in units of 1/4 t. */
weight += GetVehicleProperty(this, PROP_ROADVEH_WEIGHT, RoadVehInfo(this->engine_type)->weight) / 4;
//DIRTY HACK
if ( !weight ) weight = 1; //at least 1 for realistic accelerate
}
return weight;

@ -298,6 +298,7 @@ CommandCost CmdBuildRoadVehicle(TileIndex tile, DoCommandFlag flags, const Engin
v->reliability = e->reliability;
v->reliability_spd_dec = e->reliability_spd_dec;
v->breakdown_chance = 128;
v->max_age = e->GetLifeLengthInDays();
_new_vehicle_id = v->index;
@ -383,7 +384,6 @@ CommandCost CmdTurnRoadVeh(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
if ((v->vehstatus & VS_STOPPED) ||
(v->vehstatus & VS_CRASHED) ||
v->breakdown_ctr != 0 ||
v->overtaking != 0 ||
v->state == RVSB_WORMHOLE ||
v->IsInDepot() ||
@ -817,6 +817,9 @@ static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u)
/* For now, articulated road vehicles can't overtake anything. */
if (v->HasArticulatedPart()) return;
/* Don't overtake if the vehicle is broken or about to break down */
if (v->breakdown_ctr != 0) return;
/* Vehicles are not driving in same direction || direction is not a diagonal direction */
if (v->direction != u->direction || !(v->direction & 1)) return;

@ -2817,6 +2817,38 @@ bool AfterLoadGame()
}
}
/* Set some breakdown-related variables to the correct values. */
if (IsSavegameVersionBefore(SL_IB)) {
Vehicle *v;
FOR_ALL_VEHICLES(v) {
switch(v->type) {
case VEH_TRAIN: {
if (Train::From(v)->IsFrontEngine()) {
if (v->breakdown_ctr == 1) SetBit(Train::From(v)->flags, VRF_BREAKDOWN_STOPPED);
} else if (Train::From(v)->IsEngine() || Train::From(v)->IsMultiheaded()) {
/** Non-front engines could have a reliability of 0.
* Set it to the reliability of the front engine or the maximum, whichever is lower. */
const Engine *e = Engine::Get(v->engine_type);
v->reliability_spd_dec = e->reliability_spd_dec;
v->reliability = min(v->First()->reliability, e->reliability);
}
}
case VEH_ROAD:
v->breakdown_chance = 128;
break;
case VEH_SHIP:
v->breakdown_chance = 64;
break;
case VEH_AIRCRAFT:
v->breakdown_chance = Clamp(64 + (AircraftVehInfo(v->engine_type)->max_speed >> 3), 0, 255);
v->breakdown_severity = 40;
break;
default: break;
}
}
}
/* The road owner of standard road stops was not properly accounted for. */
if (IsSavegameVersionBefore(172)) {
for (TileIndex t = 0; t < map_size; t++) {

@ -262,8 +262,10 @@
* 192 26700
* 193 26802
* 194 26881 1.5.x
*
* 250 Improved Breakdowns
*/
extern const uint16 SAVEGAME_VERSION = 194; ///< Current savegame version of OpenTTD.
extern const uint16 SAVEGAME_VERSION = 250; ///< Current savegame version of OpenTTD.
SavegameType _savegame_type; ///< type of savegame we are loading

@ -89,6 +89,7 @@ enum SLRefType {
/** Highest possible savegame version. */
#define SL_MAX_VERSION UINT16_MAX
#define SL_IB 250
/** Flags of a chunk. */
enum ChunkType {

@ -671,6 +671,8 @@ const SaveLoad *GetVehicleDescription(VehicleType vt)
SLE_VAR(Vehicle, breakdown_delay, SLE_UINT8),
SLE_VAR(Vehicle, breakdowns_since_last_service, SLE_UINT8),
SLE_VAR(Vehicle, breakdown_chance, SLE_UINT8),
SLE_CONDVAR(Vehicle, breakdown_type, SLE_UINT8, SL_IB, SL_MAX_VERSION),
SLE_CONDVAR(Vehicle, breakdown_severity, SLE_UINT8, SL_IB, SL_MAX_VERSION),
SLE_CONDVAR(Vehicle, build_year, SLE_FILE_U8 | SLE_VAR_I32, 0, 30),
SLE_CONDVAR(Vehicle, build_year, SLE_INT32, 31, SL_MAX_VERSION),

@ -1656,6 +1656,7 @@ static SettingsContainer &GetSettingsTree()
disasters->Add(new SettingEntry("difficulty.disasters"));
disasters->Add(new SettingEntry("difficulty.economy"));
disasters->Add(new SettingEntry("difficulty.vehicle_breakdowns"));
disasters->Add(new SettingEntry("vehicle.improved_breakdowns"));
disasters->Add(new SettingEntry("vehicle.plane_crashes"));
}

@ -462,6 +462,7 @@ struct VehicleSettings {
byte extend_vehicle_life; ///< extend vehicle life by this many years
byte road_side; ///< the side of the road vehicles drive on
uint8 plane_crashes; ///< number of plane crashes, 0 = none, 1 = reduced, 2 = normal
bool improved_breakdowns; ///< different types, chances and serverities of breakdowns
};
/** Settings related to the economy. */

@ -382,6 +382,18 @@ static bool ShipAccelerate(Vehicle *v)
spd = min(v->cur_speed + 1, v->vcache.cached_max_speed);
spd = min(spd, v->current_order.GetMaxSpeed() * 2);
if(v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_LOW_POWER && v->cur_speed > (v->breakdown_severity * ShipVehInfo(v->engine_type)->max_speed) >> 8) {
if((v->tick_counter & 0x7) == 0 && v->cur_speed > 0) {
spd = v->cur_speed - 1;
} else {
spd = v->cur_speed;
}
}
if(v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_LOW_SPEED) {
spd = min(spd, v->breakdown_severity);
}
/* updates statusbar only if speed have changed to save CPU time */
if (spd != v->cur_speed) {
v->cur_speed = spd;
@ -702,6 +714,7 @@ CommandCost CmdBuildShip(TileIndex tile, DoCommandFlag flags, const Engine *e, u
v->reliability = e->reliability;
v->reliability_spd_dec = e->reliability_spd_dec;
v->breakdown_chance = 64; // ships have a 50% lower breakdown chance than normal
v->max_age = e->GetLifeLengthInDays();
_new_vehicle_id = v->index;

@ -1136,6 +1136,15 @@ strhelp = STR_CONFIG_SETTING_PLANE_CRASHES_HELPTEXT
strval = STR_CONFIG_SETTING_PLANE_CRASHES_NONE
cat = SC_BASIC
[SDT_BOOL]
base = GameSettings
var = vehicle.improved_breakdowns
from = SL_IB
guiflags = SGF_NO_NETWORK
def = false
str = STR_CONFIG_SETTING_IMPROVED_BREAKDOWNS
; station.join_stations
[SDT_NULL]
length = 1

@ -33,6 +33,14 @@ enum VehicleRailFlags {
VRF_TOGGLE_REVERSE = 7, ///< Used for vehicle var 0xFE bit 8 (toggled each time the train is reversed, accurate for first vehicle only).
VRF_TRAIN_STUCK = 8, ///< Train can't get a path reservation.
VRF_LEAVING_STATION = 9, ///< Train is just leaving a station.
VRF_BREAKDOWN_BRAKING = 10,///< used to mark a train that is braking because it is broken down
VRF_BREAKDOWN_POWER = 11,///< used to mark a train in which the power of one (or more) of the engines is reduced because of a breakdown
VRF_BREAKDOWN_SPEED = 12,///< used to mark a train that has a reduced maximum speed because of a breakdown
VRF_BREAKDOWN_STOPPED = 13,///< used to mark a train that is stopped because of a breakdown
/* Bitmask of all flags that indicate a broken train (braking is not included) */
VRF_IS_BROKEN = (1 << VRF_BREAKDOWN_POWER) | (1 << VRF_BREAKDOWN_SPEED) | (1 << VRF_BREAKDOWN_STOPPED),
VRF_NEED_REPAIR = 14,///< used to mark a train that has a reduced maximum speed because of a critical breakdown
VRF_TO_HEAVY = 15,
};
/** Modes for ignoring signals. */
@ -65,7 +73,7 @@ void FreeTrainTrackReservation(const Train *v, TileIndex origin = INVALID_TILE,
bool TryPathReserve(Train *v, bool mark_as_stuck = false, bool first_tile_okay = false);
int GetTrainStopLocation(StationID station_id, TileIndex tile, const Train *v, int *station_ahead, int *station_length);
void CheckBreakdownFlags(Train *v);
void GetTrainSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type);
/** Variables that are cached to improve performance and such */
@ -75,6 +83,7 @@ struct TrainCache {
/* cached values, recalculated on load and each time a vehicle is added to/removed from the consist. */
bool cached_tilt; ///< train can tilt; feature provides a bonus in curves
uint8 cached_num_engines; ///< total number of engines, including rear ends of multiheaded engines
byte user_def_data; ///< Cached property 0x25. Can be set by Callback 0x36.
@ -177,7 +186,25 @@ struct Train FINAL : public GroundVehicle<Train, VEH_TRAIN> {
}
protected: // These functions should not be called outside acceleration code.
/**
* Gets the speed a broken down train (low speed breakdown) is limited to.
* @note This value is not cached, because changing cached_max_speed would have unwanted consequences (e.g. in the GUI).
* @param v The front engine of the vehicle.
* @return The speed the train is limited to.
*/
inline uint16 GetBreakdownSpeed() const
{
assert(this->IsFrontEngine());
uint16 speed = UINT16_MAX;
for (const Train *w = this; w != NULL; w = w->Next()) {
if (w->breakdown_ctr == 1 && w->breakdown_type == BREAKDOWN_LOW_SPEED) {
speed = min(speed, w->breakdown_severity);
}
}
return speed;
}
/**
* Allows to know the power value that this vehicle will use.
* @return Power value from the engine in HP, or zero if the vehicle is not powered.
@ -264,7 +291,7 @@ protected: // These functions should not be called outside acceleration code.
*/
inline AccelStatus GetAccelerationStatus() const
{
return (this->vehstatus & VS_STOPPED) || HasBit(this->flags, VRF_REVERSING) || HasBit(this->flags, VRF_TRAIN_STUCK) ? AS_BRAKE : AS_ACCEL;
return (this->vehstatus & VS_STOPPED) || HasBit(this->flags, VRF_REVERSING) || HasBit(this->flags, VRF_TRAIN_STUCK ) || HasBit(this->flags, VRF_BREAKDOWN_BRAKING) ? AS_BRAKE : AS_ACCEL;
}
/**

@ -120,6 +120,34 @@ void CheckTrainsLengths()
}
}
/**
* Checks the breakdown flags (VehicleRailFlags 9-12) and sets the correct value in the first vehicle of the consist.
* This function is generally only called to check if a flag may be cleared.
* @param v the front engine
* @param flags bitmask of the flags to check.
*/
void CheckBreakdownFlags(Train *v)
{
assert(v->IsFrontEngine());
/* clear the flags we're gonna check first, we'll set them again later (if applicable ) */
CLRBITS(v->flags, (1 << VRF_BREAKDOWN_BRAKING) | VRF_IS_BROKEN);
for (const Train *w = v; w != NULL; w = w->Next()) {
if (v->IsEngine() || w->IsMultiheaded()) {
if (w->breakdown_ctr == 2) {
SetBit(v->flags, VRF_BREAKDOWN_BRAKING);
} else if (w->breakdown_ctr == 1) {
switch (w->breakdown_type) {
case BREAKDOWN_CRITICAL:
case BREAKDOWN_EM_STOP: SetBit(v->flags, VRF_BREAKDOWN_STOPPED); break;
case BREAKDOWN_LOW_SPEED: SetBit(v->flags, VRF_BREAKDOWN_SPEED); break;
case BREAKDOWN_LOW_POWER: SetBit(v->flags, VRF_BREAKDOWN_POWER); break;
}
}
}
}
}
/**
* Recalculates the cached stuff of a train. Should be called each time a vehicle is added
* to/removed from the chain, and when the game is loaded.
@ -136,6 +164,7 @@ void Train::ConsistChanged(ConsistChangeFlags allowed_changes)
EngineID first_engine = this->IsFrontEngine() ? this->engine_type : INVALID_ENGINE;
this->gcache.cached_total_length = 0;
this->compatible_railtypes = RAILTYPES_NONE;
this->tcache.cached_num_engines = 0;
bool train_can_tilt = true;
@ -204,8 +233,13 @@ void Train::ConsistChanged(ConsistChangeFlags allowed_changes)
/* max speed is the minimum of the speed limits of all vehicles in the consist */
if ((rvi_u->railveh_type != RAILVEH_WAGON || _settings_game.vehicle.wagon_speed_limits) && !UsesWagonOverride(u)) {
uint16 speed = GetVehicleProperty(u, PROP_TRAIN_SPEED, rvi_u->max_speed);
if (HasBit(u->flags, VRF_NEED_REPAIR)) speed = u->vcache.cached_max_speed;
if (speed != 0) max_speed = min(speed, max_speed);
}
if(u->IsEngine() || u-> IsMultiheaded()) {
this->tcache.cached_num_engines++;
}
}
uint16 new_cap = e_u->DetermineCapacity(u);
@ -433,6 +467,10 @@ int Train::GetCurrentMaxSpeed() const
}
max_speed = min(max_speed, this->current_order.GetMaxSpeed());
if ( HasBit(this->flags, VRF_BREAKDOWN_SPEED) ) {
max_speed = min(max_speed, this->GetBreakdownSpeed());
}
return min(max_speed, this->gcache.cached_max_track_speed);
}
@ -445,6 +483,9 @@ void Train::UpdateAcceleration()
uint weight = this->gcache.cached_weight;
assert(weight != 0);
this->acceleration = Clamp(power / weight * 4, 1, 255);
/* for non-realistic acceleration, breakdown chance is 128, corrected by the multiengine factor of 3/(n+2) */
this->breakdown_chance = min(128 * 3 / (this->tcache.cached_num_engines + 2), 5);
}
/**
@ -703,6 +744,8 @@ static void AddRearEngineToMultiheadedTrain(Train *v)
u->refit_cap = v->refit_cap;
u->railtype = v->railtype;
u->engine_type = v->engine_type;
u->reliability = v->reliability;
u->reliability_spd_dec = v->reliability_spd_dec;
u->build_year = v->build_year;
u->cur_image = SPR_IMG_QUERY;
u->random_bits = VehicleRandomBits();
@ -1931,7 +1974,7 @@ CommandCost CmdReverseTrainDirection(TileIndex tile, DoCommandFlag flags, uint32
}
} else {
/* turn the whole train around */
if ((v->vehstatus & VS_CRASHED) || v->breakdown_ctr != 0) return CMD_ERROR;
if ((v->vehstatus & VS_CRASHED) || HasBit(v->flags, VRF_BREAKDOWN_STOPPED)) return CMD_ERROR;
if (flags & DC_EXEC) {
/* Properly leave the station if we are loading and won't be loading anymore */
@ -2817,6 +2860,25 @@ int Train::UpdateSpeed()
return this->DoUpdateSpeed(this->GetAcceleration(), this->GetAccelerationStatus() == AS_BRAKE ? 0 : 2, this->GetCurrentMaxSpeed());
}
}
/**
* Handle all breakdown related stuff for a train consist.
* @param v The front engine.
*/
static bool HandlePossibleBreakdowns(Train *v)
{
assert(v->IsFrontEngine());
for (Train *u = v; u != NULL; u = u->Next()) {
if (u->breakdown_ctr != 0 && (u->IsEngine() || u->IsMultiheaded())) {
if (u->breakdown_ctr <= 2) {
if ( u->HandleBreakdown() ) return true;
/* We check the order of v (the first vehicle) instead of u here! */
} else if (!v->current_order.IsType(OT_LOADING)) {
u->breakdown_ctr--;
}
}
}
return false;
}
/**
* Trains enters a station, send out a news item if it is the first train, and start loading.
@ -3693,12 +3755,8 @@ static bool TrainCheckIfLineEnds(Train *v, bool reverse)
{
/* First, handle broken down train */
int t = v->breakdown_ctr;
if (t > 1) {
if(HasBit(v->flags, VRF_BREAKDOWN_BRAKING)) {
v->vehstatus |= VS_TRAIN_SLOWING;
uint16 break_speed = _breakdown_speeds[GB(~t, 4, 4)];
if (break_speed < v->cur_speed) v->cur_speed = break_speed;
} else {
v->vehstatus &= ~VS_TRAIN_SLOWING;
}
@ -3753,7 +3811,7 @@ static bool TrainLocoHandler(Train *v, bool mode)
}
/* train is broken down? */
if (v->HandleBreakdown()) return true;
if ( HandlePossibleBreakdowns(v) ) return true;
if (HasBit(v->flags, VRF_REVERSING) && v->cur_speed == 0) {
ReverseTrainDirection(v);
@ -3986,7 +4044,6 @@ void Train::OnNewDay()
if ((++this->day_counter & 7) == 0) DecreaseVehicleValue(this);
if (this->IsFrontEngine()) {
CheckVehicleBreakdown(this);
CheckIfTrainNeedsService(this);
@ -4011,6 +4068,9 @@ void Train::OnNewDay()
SetWindowClassesDirty(WC_TRAINS_LIST);
}
}
if(IsEngine() || IsMultiheaded()) {
CheckVehicleBreakdown(this);
}
}
/**

@ -214,17 +214,65 @@ static void TrainDetailsCargoTab(const CargoSummaryItem *item, int left, int rig
* @param right The right most coordinate to draw
* @param y The y coordinate
*/
static void TrainDetailsInfoTab(const Vehicle *v, int left, int right, int y)
static void TrainDetailsInfoTab(const Train *v, int left, int right, int y, byte line_number)
{
if (RailVehInfo(v->engine_type)->railveh_type == RAILVEH_WAGON) {
const RailVehicleInfo *rvi = RailVehInfo(v->engine_type);
bool show_speed = !UsesWagonOverride(v) && (_settings_game.vehicle.wagon_speed_limits || rvi->railveh_type != RAILVEH_WAGON);
uint16 speed;
if (rvi->railveh_type == RAILVEH_WAGON) {
SetDParam(0, v->engine_type);
SetDParam(1, v->value);
if ( show_speed && (speed = GetVehicleProperty(v, PROP_TRAIN_SPEED, rvi->max_speed))) {
SetDParam(2, speed); // StringID++
DrawString(left, right, y, STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE_AND_SPEED);
} else
DrawString(left, right, y, STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE);
} else {
switch ( line_number ) {
case 0:
SetDParam(0, v->engine_type);
SetDParam(1, v->build_year);
SetDParam(2, v->value);
if ( show_speed && (speed = GetVehicleProperty(v, PROP_TRAIN_SPEED, rvi->max_speed))) {
SetDParam(3, speed); // StringID++
DrawString( left, right, y, STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE_AND_SPEED, TC_FROMSTRING, SA_LEFT);
} else
DrawString(left, right, y, STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE);
break;
case 1:
SetDParam(0, v->reliability * 100 >> 16);
SetDParam(1, v->breakdowns_since_last_service);
DrawString(left, right, y, STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS, TC_FROMSTRING, SA_LEFT);
break;
case 2:
if ( v->breakdown_ctr == 1 ) {
if ( _settings_game.vehicle.improved_breakdowns ) {
SetDParam( 0, STR_VEHICLE_STATUS_BROKEN_DOWN_VEL );
SetDParam( 1, STR_BREAKDOWN_TYPE_CRITICAL + v->breakdown_type );
if ( v->breakdown_type == BREAKDOWN_LOW_SPEED ) {
SetDParam( 2, min( v->First( )->GetCurrentMaxSpeed( ), v->breakdown_severity ) );
} else if ( v->breakdown_type == BREAKDOWN_LOW_POWER ) {
SetDParam( 2, v->breakdown_severity * 100 / 256 );
}
} else
SetDParam( 0, STR_VEHICLE_STATUS_BROKEN_DOWN );
} else {
if ( HasBit( v->flags, VRF_NEED_REPAIR ) ) {
SetDParam( 0, STR_NEED_REPAIR );
SetDParam( 1, v->vcache.cached_max_speed );
} else
SetDParam( 0, STR_RUNNING );
}
DrawString( left, right, y, STR_CURRENT_STATUS);
break;
default: NOT_REACHED();
}
}
}
@ -334,6 +382,7 @@ int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab)
uint length = GetLengthOfArticulatedVehicle(v);
if (length > TRAIN_DETAILS_MAX_INDENT) num++;
}
if (det_tab == 1) num += 2 * Train::Get(veh_id)->tcache.cached_num_engines;
}
return num;
@ -365,6 +414,7 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po
bool rtl = _current_text_dir == TD_RTL;
Direction dir = rtl ? DIR_E : DIR_W;
int x = rtl ? right : left;
byte line_number = 0;
for (; v != NULL && vscroll_pos > -vscroll_cap; v = v->GetNextVehicle()) {
GetCargoSummaryOfArticulatedVehicle(v, &_cargo_summary);
@ -375,7 +425,7 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po
do {
Point offset;
int width = u->GetDisplayImageWidth(&offset);
if (vscroll_pos <= 0 && vscroll_pos > -vscroll_cap) {
if (vscroll_pos <= 0 && vscroll_pos > -vscroll_cap && line_number == 0) {
int pitch = 0;
const Engine *e = Engine::Get(v->engine_type);
if (e->GetGRF() != NULL) {
@ -396,7 +446,7 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po
}
uint num_lines = max(1u, _cargo_summary.Length());
for (uint i = 0; i < num_lines; i++) {
for (uint i = 0; i < num_lines;) {
int sprite_width = max<int>(dx, ScaleGUITrad(TRAIN_DETAILS_MIN_INDENT)) + 3;
int data_left = left + (rtl ? 0 : sprite_width);
int data_right = right - (rtl ? sprite_width : 0);
@ -415,7 +465,7 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po
break;
case TDW_TAB_INFO:
if (i == 0) TrainDetailsInfoTab(v, data_left, data_right, py);
if (i == 0) TrainDetailsInfoTab(v, data_left, data_right, py, line_number);
break;
case TDW_TAB_CAPACITY:
@ -430,6 +480,11 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po
default: NOT_REACHED();
}
}
if (det_tab != 1 || line_number >= (Train::From(v)->IsWagon() ? 0 : 2)) {
line_number = 0;
i++;
} else
line_number++;
vscroll_pos--;
}
}

@ -99,6 +99,25 @@ bool Vehicle::NeedsAutorenewing(const Company *c, bool use_renew_setting) const
void VehicleServiceInDepot(Vehicle *v)
{
assert(v != NULL);
const Engine *e = Engine::Get(v->engine_type);
if (v->type == VEH_TRAIN) {
if (v->Next() != NULL) VehicleServiceInDepot(v->Next());
if (!(Train::From(v)->IsEngine()) && !(Train::From(v)->IsRearDualheaded())) return;
ClrBit(Train::From(v)->flags,VRF_NEED_REPAIR);
const RailVehicleInfo *rvi = &e->u.rail;
v->vcache.cached_max_speed = rvi->max_speed;
if (Train::From(v)->IsFrontEngine()) {
Train::From(v)->ConsistChanged(CCF_REFIT);
CLRBITS(Train::From(v)->flags, (1 << VRF_BREAKDOWN_BRAKING) | VRF_IS_BROKEN );
}
}
v->date_of_last_service = _date;
v->breakdowns_since_last_service = 0;
v->reliability = e->reliability;
v->breakdown_ctr = 0;
v->vehstatus &= ~VS_AIRCRAFT_BROKEN;
/* Prevent vehicles from breaking down directly after exiting the depot. */
v->breakdown_chance /= 4;
SetWindowDirty(WC_VEHICLE_DETAILS, v->index); // ensure that last service date and reliability are updated
do {
@ -125,9 +144,10 @@ bool Vehicle::NeedsServicing() const
/* Are we ready for the next service cycle? */
const Company *c = Company::Get(this->owner);
if (this->ServiceIntervalIsPercent() ?
(this->reliability >= this->GetEngine()->reliability * (100 - this->GetServiceInterval()) / 100) :
(this->date_of_last_service + this->GetServiceInterval() >= _date)) {
if ((this->ServiceIntervalIsPercent() ?
(this->reliability >= this->GetEngine()->reliability * (100 - this->service_interval) / 100) :
(this->date_of_last_service + this->service_interval >= _date))
&& !(this->type == VEH_TRAIN && HasBit(Train::From(this)->flags ,VRF_NEED_REPAIR))) {
return false;
}
@ -897,6 +917,16 @@ void CallVehicleTicks()
default: break;
case VEH_TRAIN:
if (HasBit(Train::From(v)->flags, VRF_TO_HEAVY)) {
_current_company = v->owner;
if (IsLocalCompany()) {
SetDParam(0, v->index);
SetDParam(1, STR_ERROR_TRAIN_TOO_HEAVY);
AddVehicleNewsItem(STR_ERROR_TRAIN_TOO_HEAVY, NT_ADVICE, v->index);
ClrBit(Train::From(v)->flags,VRF_TO_HEAVY);
}
_current_company = OWNER_NONE;
}
case VEH_ROAD:
case VEH_AIRCRAFT:
case VEH_SHIP: {
@ -1128,50 +1158,152 @@ void DecreaseVehicleValue(Vehicle *v)
SetWindowDirty(WC_VEHICLE_DETAILS, v->index);
}
static const byte _breakdown_chance[64] = {
3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 5, 5, 6, 6, 7, 7,
8, 8, 9, 9, 10, 10, 11, 11,
12, 13, 13, 13, 13, 14, 15, 16,
17, 19, 21, 25, 28, 31, 34, 37,
40, 44, 48, 52, 56, 60, 64, 68,
72, 80, 90, 100, 110, 120, 130, 140,
150, 170, 190, 210, 230, 250, 250, 250,
/** The chances for the different types of vehicles to suffer from different types of breakdowns
* The chance for a given breakdown type n is _breakdown_chances[vehtype][n] - _breakdown_chances[vehtype][n-1] */
static const byte _breakdown_chances[4][4] = {
{ //Trains:
25, ///< 10% chance for BREAKDOWN_CRITICAL.
51, ///< 10% chance for BREAKDOWN_EM_STOP.
127, ///< 30% chance for BREAKDOWN_LOW_SPEED.
255, ///< 50% chance for BREAKDOWN_LOW_POWER.
},
{ //Road Vehicles:
51, ///< 20% chance for BREAKDOWN_CRITICAL.
76, ///< 10% chance for BREAKDOWN_EM_STOP.
153, ///< 30% chance for BREAKDOWN_LOW_SPEED.
255, ///< 40% chance for BREAKDOWN_LOW_POWER.
},
{ //Ships:
51, ///< 20% chance for BREAKDOWN_CRITICAL.
76, ///< 10% chance for BREAKDOWN_EM_STOP.
178, ///< 40% chance for BREAKDOWN_LOW_SPEED.
255, ///< 30% chance for BREAKDOWN_LOW_POWER.
},
{ //Aircraft:
178, ///< 70% chance for BREAKDOWN_AIRCRAFT_SPEED.
229, ///< 20% chance for BREAKDOWN_AIRCRAFT_DEPOT.
255, ///< 10% chance for BREAKDOWN_AIRCRAFT_EM_LANDING.
255, ///< Aircraft have only 3 breakdown types, so anything above 0% here will cause a crash.
},
};
/**
* Determine the type of breakdown a vehicle will have.
* Results are saved in breakdown_type and breakdown_severity.
* @param v the vehicle in question.
* @param r the random number to use. (Note that bits 0..6 are already used)
*/
void
DetermineBreakdownType( Vehicle *v, uint32 r ) {
/* if 'improved breakdowns' is off, just do the classic breakdown */
if ( !_settings_game.vehicle.improved_breakdowns ) {
v->breakdown_type = BREAKDOWN_CRITICAL;
v->breakdown_severity = 40; //only used by aircraft (321 km/h)
return;
}
byte rand = GB( r, 8, 8 );
const byte *breakdown_type_chance = _breakdown_chances[v->type];
if ( v->type == VEH_AIRCRAFT ) {
if ( rand <= breakdown_type_chance[BREAKDOWN_AIRCRAFT_SPEED] ) {
v->breakdown_type = BREAKDOWN_AIRCRAFT_SPEED;
/* all speed values here are 1/8th of the real max speed in km/h */
byte max_speed = min( AircraftVehInfo( v->engine_type )->max_speed >> 3, 255 );
byte min_speed = min( 15 + ( max_speed >> 2 ), AircraftVehInfo( v->engine_type )->max_speed >> 4 );
v->breakdown_severity = min_speed + ( ( ( v->reliability + GB( r, 16, 16 ) ) * ( max_speed - min_speed ) ) >> 17 );
} else if ( rand <= breakdown_type_chance[BREAKDOWN_AIRCRAFT_DEPOT] ) {
v->breakdown_type = BREAKDOWN_AIRCRAFT_DEPOT;
} else if ( rand <= breakdown_type_chance[BREAKDOWN_AIRCRAFT_EM_LANDING] ) {
/* emergency landings only happen when reliability < 87% */
if ( v->reliability < 0xDDDD ) {
v->breakdown_type = BREAKDOWN_AIRCRAFT_EM_LANDING;
} else {
/* try again */
DetermineBreakdownType( v, Random( ) );
}
} else {
NOT_REACHED( );
}
return;
}
if ( rand <= breakdown_type_chance[BREAKDOWN_CRITICAL] ) {
v->breakdown_type = BREAKDOWN_CRITICAL;
} else if ( rand <= breakdown_type_chance[BREAKDOWN_EM_STOP] ) {
/* Non-front engines cannot have emergency stops */
if ( v->type == VEH_TRAIN && !( Train::From( v )->IsFrontEngine( ) ) ) {
return DetermineBreakdownType( v, Random( ) );
}
v->breakdown_type = BREAKDOWN_EM_STOP;
v->breakdown_delay >>= 2; //emergency stops don't last long (1/4 of normal)
} else if ( rand <= breakdown_type_chance[BREAKDOWN_LOW_SPEED] ) {
v->breakdown_type = BREAKDOWN_LOW_SPEED;
/* average of random and reliability */
uint16 rand2 = ( GB( r, 16, 16 ) + v->reliability ) >> 1;
uint16 max_speed =
( v->type == VEH_TRAIN ) ?
GetVehicleProperty( v, PROP_TRAIN_SPEED, RailVehInfo( v->engine_type )->max_speed ) :
( v->type == VEH_ROAD ) ?
GetVehicleProperty( v, PROP_ROADVEH_SPEED, RoadVehInfo( v->engine_type )->max_speed ) :
( v->type == VEH_SHIP ) ?
GetVehicleProperty( v, PROP_SHIP_SPEED, ShipVehInfo( v->engine_type )->max_speed ) :
GetVehicleProperty( v, PROP_AIRCRAFT_SPEED, AircraftVehInfo( v->engine_type )->max_speed );
byte min_speed = min( 41, max_speed >> 2 );
/* we use the min() function here because we want to use the real value of max_speed for the min_speed calculation */
max_speed = min( max_speed, 255 );
v->breakdown_severity = Clamp( ( max_speed * rand2 ) >> 16, min_speed, max_speed );
} else if ( rand <= breakdown_type_chance[BREAKDOWN_LOW_POWER] ) {
v->breakdown_type = BREAKDOWN_LOW_POWER;
/** within this type there are two possibilities: (50/50)
* power reduction (10-90%), or no power at all */
if ( GB( r, 7, 1 ) ) {
v->breakdown_severity = Clamp( ( GB( r, 16, 16 ) + v->reliability ) >> 9, 26, 231 );
} else {
v->breakdown_severity = 0;
}
} else {
NOT_REACHED( );
}
}
void CheckVehicleBreakdown(Vehicle *v)
{
int rel, rel_old;
/* decrease reliability */
v->reliability = rel = max((rel_old = v->reliability) - v->reliability_spd_dec, 0);
if ((rel_old >> 8) != (rel >> 8)) SetWindowDirty(WC_VEHICLE_DETAILS, v->index);
if ((rel_old >> 8) != (rel >> 8)) SetWindowDirty(WC_VEHICLE_DETAILS, v->First()->index);
if (v->breakdown_ctr != 0 || (v->vehstatus & VS_STOPPED) ||
if (v->breakdown_ctr != 0 || (v->First()->vehstatus & VS_STOPPED) ||
_settings_game.difficulty.vehicle_breakdowns < 1 ||
v->cur_speed < 5 || _game_mode == GM_MENU) {
v->First()->cur_speed < 5 || _game_mode == GM_MENU ||
(v->type == VEH_AIRCRAFT && ((Aircraft*)v)->state != FLYING) ||
(v->type == VEH_TRAIN && !(Train::From(v)->IsFrontEngine()) && !_settings_game.vehicle.improved_breakdowns)) {
return;
}
uint32 r = Random();
/* increase chance of failure */
int chance = v->breakdown_chance + 1;
if (Chance16I(1, 25, r)) chance += 25;
v->breakdown_chance = min(255, chance);
uint32 r1 = Random();
uint32 r2 = Random();
/* calculate reliability value to use in comparison */
rel = v->reliability;
if (v->type == VEH_SHIP) rel += 0x6666;
/* reduced breakdowns? */
if (_settings_game.difficulty.vehicle_breakdowns == 1) rel += 0x6666;
/* check if to break down */
if (_breakdown_chance[(uint)min(rel, 0xffff) >> 10] <= v->breakdown_chance) {
v->breakdown_ctr = GB(r, 16, 6) + 0x3F;
v->breakdown_delay = GB(r, 24, 7) + 0x80;
v->breakdown_chance = 0;
byte chance = 128;
if (_settings_game.vehicle.improved_breakdowns) {
/* Dual engines have their breakdown chances reduced to 70% of the normal value */
chance = (v->type == VEH_TRAIN && Train::From(v)->IsMultiheaded()) ? v->First()->breakdown_chance * 7 / 10 : v->First()->breakdown_chance;
} else if(v->type == VEH_SHIP) {
chance = 64;
}
/**
* Chance is (1 - reliability) * breakdown_setting * breakdown_chance / 10.
* At 90% reliabilty, normal setting (2) and average breakdown_chance (128),
* a vehicle will break down (on average) every 100 days.
* This *should* mean that vehicles break down about as often as (or a little less than) they used to.
* However, because breakdowns are no longer by definition a complete stop,
* their impact will be significantly less.
*/
if ( (uint32) ( 0xffff - v->reliability ) * _settings_game.difficulty.vehicle_breakdowns * chance > GB( r1, 0, 24 ) * 10 ) {
v->breakdown_ctr = GB( r1, 24, 6 ) + 0xF;
v->breakdown_delay = GB( r2, 0, 7 ) + 0x80;
DetermineBreakdownType( v, r2 );
}
}
@ -1200,29 +1332,106 @@ bool Vehicle::HandleBreakdown()
}
if (this->type == VEH_AIRCRAFT) {
this->MarkDirty();
assert(this->breakdown_type <= BREAKDOWN_AIRCRAFT_EM_LANDING);
/* Aircraft just need this flag, the rest is handled elsewhere */
this->vehstatus |= VS_AIRCRAFT_BROKEN;
} else {
this->cur_speed = 0;
if(this->breakdown_type == BREAKDOWN_AIRCRAFT_SPEED ||
(this->current_order.IsType(OT_GOTO_DEPOT) &&
(this->current_order.GetDepotOrderType() & ODTFB_BREAKDOWN) &&
GetTargetAirportIfValid(Aircraft::From(this)) != NULL)) return false;
FindBreakdownDestination(Aircraft::From(this));
} else if ( this->type == VEH_TRAIN ) {
if ( this->breakdown_type == BREAKDOWN_LOW_POWER ||
this->First( )->cur_speed <= ( ( this->breakdown_type == BREAKDOWN_LOW_SPEED ) ? this->breakdown_severity : 0 ) ) {
switch ( this->breakdown_type ) {
case BREAKDOWN_CRITICAL:
if (!PlayVehicleSound(this, VSE_BREAKDOWN)) {
bool train_or_ship = this->type == VEH_TRAIN || this->type == VEH_SHIP;
SndPlayVehicleFx((_settings_game.game_creation.landscape != LT_TOYLAND) ?
(train_or_ship ? SND_10_TRAIN_BREAKDOWN : SND_0F_VEHICLE_BREAKDOWN) :
(train_or_ship ? SND_3A_COMEDY_BREAKDOWN_2 : SND_35_COMEDY_BREAKDOWN), this);
}
if (!(this->vehstatus & VS_HIDDEN) && !HasBit(EngInfo(this->engine_type)->misc_flags, EF_NO_BREAKDOWN_SMOKE)) {
if (!(this->vehstatus & VS_HIDDEN)) {
EffectVehicle *u = CreateEffectVehicleRel(this, 4, 4, 5, EV_BREAKDOWN_SMOKE);
if (u != NULL) u->animation_state = this->breakdown_delay * 2;
}
/* Max Speed reduction*/
if (_settings_game.vehicle.improved_breakdowns) {
if (!HasBit(Train::From(this)->flags,VRF_NEED_REPAIR)) {
const Engine *e = Engine::Get(this->engine_type);
const RailVehicleInfo *rvi = &e->u.rail;
if (rvi->max_speed > this->vcache.cached_max_speed)
this->vcache.cached_max_speed = rvi->max_speed;
}
this->vcache.cached_max_speed =
min(
this->vcache.cached_max_speed - (this->vcache.cached_max_speed >> 1) / Train::From(this->First())->tcache.cached_num_engines + 1,
this->vcache.cached_max_speed);
SetBit(Train::From(this)->flags, VRF_NEED_REPAIR);
Train::From(this->First())->ConsistChanged(CCF_TRACK);
}
/* FALL THROUGH */
case BREAKDOWN_EM_STOP:
CheckBreakdownFlags(Train::From(this->First()));
SetBit(Train::From(this->First())->flags, VRF_BREAKDOWN_STOPPED);
break;
case BREAKDOWN_LOW_SPEED:
CheckBreakdownFlags(Train::From(this->First()));
SetBit(Train::From(this->First())->flags, VRF_BREAKDOWN_SPEED);
break;
case BREAKDOWN_LOW_POWER:
SetBit(Train::From(this->First())->flags, VRF_BREAKDOWN_POWER);
break;
default: NOT_REACHED();
}
this->MarkDirty(); // Update graphics after speed is zeroed
SetWindowDirty(WC_VEHICLE_VIEW, this->index);
SetWindowDirty(WC_VEHICLE_DETAILS, this->index);
this->First()->MarkDirty();
SetWindowDirty(WC_VEHICLE_VIEW, this->index);
SetWindowDirty(WC_VEHICLE_DETAILS, this->index);
} else {
this->breakdown_ctr = 2; // wait until slowdown
this->breakdowns_since_last_service--;
SetBit( Train::From( this )->flags, VRF_BREAKDOWN_BRAKING );
return false;
}
if ( ( !( this->vehstatus & VS_HIDDEN ) ) && ( ( this->breakdown_type == BREAKDOWN_LOW_SPEED || this->breakdown_type == BREAKDOWN_LOW_POWER ) && ( this->tick_counter & 0x1F ) == 0 ) ) {
CreateEffectVehicleRel( this, 0, 0, 2, EV_BREAKDOWN_SMOKE ); //some grey clouds to indicate a broken engine
}
} else {
switch ( this->breakdown_type ) {
case BREAKDOWN_CRITICAL:
if ( !PlayVehicleSound( this, VSE_BREAKDOWN ) ) {
SndPlayVehicleFx( ( _settings_game.game_creation.landscape != LT_TOYLAND ) ? SND_0F_VEHICLE_BREAKDOWN : SND_35_COMEDY_BREAKDOWN, this );
}
if (!(this->vehstatus & VS_HIDDEN)) {
EffectVehicle *u = CreateEffectVehicleRel(this, 4, 4, 5, EV_BREAKDOWN_SMOKE);
if (u != NULL) u->animation_state = this->breakdown_delay * 2;
}
/* FALL THROUGH */
case BREAKDOWN_EM_STOP:
this->cur_speed = 0;
break;
case BREAKDOWN_LOW_SPEED:
case BREAKDOWN_LOW_POWER:
/* do nothing */
break;
default: NOT_REACHED( );
}
if ( ( !( this->vehstatus & VS_HIDDEN ) ) && (
( this->breakdown_type == BREAKDOWN_LOW_SPEED || this->breakdown_type == BREAKDOWN_LOW_POWER ) &&
( this->tick_counter & 0x1F ) == 0 ) ) {
/* Some gray clouds to indicate a broken RV */
CreateEffectVehicleRel( this, 0, 0, 2, EV_BREAKDOWN_SMOKE );
}
this->First()->MarkDirty();
SetWindowDirty(WC_VEHICLE_VIEW, this->index);
SetWindowDirty(WC_VEHICLE_DETAILS, this->index);
return (this->breakdown_type == BREAKDOWN_CRITICAL || this->breakdown_type == BREAKDOWN_EM_STOP );
}
/* FALL THROUGH */
/* FALL THROUGH */
case 1:
/* Aircraft breakdowns end only when arriving at the airport */
if (this->type == VEH_AIRCRAFT) return false;
@ -1231,11 +1440,17 @@ bool Vehicle::HandleBreakdown()
if ((this->tick_counter & (this->type == VEH_TRAIN ? 3 : 1)) == 0) {
if (--this->breakdown_delay == 0) {
this->breakdown_ctr = 0;
if( this->type == VEH_TRAIN ) {
CheckBreakdownFlags(Train::From(this->First()));
this->First()->MarkDirty();
SetWindowDirty(WC_VEHICLE_VIEW, this->First()->index);
} else {
this->MarkDirty();
SetWindowDirty(WC_VEHICLE_VIEW, this->index);
}
}
return true;
}
return (this->breakdown_type == BREAKDOWN_CRITICAL || this->breakdown_type == BREAKDOWN_EM_STOP);
default:
if (!this->current_order.IsType(OT_LOADING)) this->breakdown_ctr--;
@ -2211,7 +2426,7 @@ CommandCost Vehicle::SendToDepot(DoCommandFlag flags, DepotCommand command)
* Now we change the setting to apply the new one and let the vehicle head for the same depot.
* Note: the if is (true for requesting service == true for ordered to stop in depot) */
if (flags & DC_EXEC) {
this->current_order.SetDepotOrderType(ODTF_MANUAL);
if (!(this->current_order.GetDepotOrderType() & ODTFB_BREAKDOWN)) this->current_order.SetDepotOrderType(ODTF_MANUAL);
this->current_order.SetDepotActionType(halt_in_depot ? ODATF_SERVICE_ONLY : ODATFB_HALT);
SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP);
}
@ -2229,8 +2444,13 @@ CommandCost Vehicle::SendToDepot(DoCommandFlag flags, DepotCommand command)
SetBit(gv_flags, GVF_SUPPRESS_IMPLICIT_ORDERS);
}
this->current_order.MakeDummy();
SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP);
/* We don't cancel a breakdown-related goto depot order, we only change whether to halt or not */
if (this->current_order.GetDepotOrderType() & ODTFB_BREAKDOWN) {
this->current_order.SetDepotActionType(this->current_order.GetDepotActionType() == ODATFB_HALT ? ODATF_SERVICE_ONLY : ODATFB_HALT);
} else {
this->current_order.MakeDummy();
SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP);
}
}
return CommandCost();
}

@ -193,6 +193,8 @@ public:
Vehicle **hash_tile_prev; ///< NOSAVE: Previous vehicle in the tile location hash.
Vehicle **hash_tile_current; ///< NOSAVE: Cache of the current hash chain.
byte breakdown_severity; ///< severity of the breakdown. Note that lower means more severe
byte breakdown_type; ///< Type of breakdown
SpriteID colourmap; ///< NOSAVE: cached colour mapping
/* Related to age and service time */

@ -37,6 +37,7 @@
#include "engine_func.h"
#include "station_base.h"
#include "tilehighlight_func.h"
#include "train.h"
#include "zoom_func.h"
#include "safeguards.h"
@ -290,6 +291,31 @@ byte GetBestFittingSubType(Vehicle *v_from, Vehicle *v_for, CargoID dest_cargo_t
return ret_refit_cyc;
}
/**
* Get the engine that suffers from the most severe breakdown.
* This means the engine with the lowest breakdown_type.
* If the breakdown types of 2 engines are equal, the one with the lowest breakdown_severity (most severe) is picked.
* @param v The front engine of the train.
* @return The most severly broken engine.
*/
const Vehicle *GetMostSeverelyBrokenEngine(const Train *v)
{
assert(v->IsFrontEngine());
const Vehicle *w = v;
byte most_severe_type = 255;
for (const Vehicle *u = v; u != NULL; u = u->Next()) {
if (u->breakdown_ctr == 1) {
if (u->breakdown_type < most_severe_type) {
most_severe_type = u->breakdown_type;
w = u;
} else if (u->breakdown_type == most_severe_type && u->breakdown_severity < w->breakdown_severity) {
w = u;
}
}
}
return w;
}
/** Option to refit a vehicle chain */
struct RefitOption {
CargoID cargo; ///< Cargo to refit to
@ -2079,8 +2105,25 @@ struct VehicleDetailsWindow : Window {
y += FONT_HEIGHT_NORMAL;
/* Draw breakdown & reliability */
SetDParam(0, ToPercent16(v->reliability));
SetDParam(1, v->breakdowns_since_last_service);
byte total_engines = 0;
if (v->type == VEH_TRAIN) {
/* we want to draw the average reliability and total number of breakdowns */
uint32 total_reliability = 0;
uint16 total_breakdowns = 0;
for (const Vehicle *w = v; w != NULL; w = w->Next()) {
if (Train::From(w)->IsEngine() || Train::From(w)->IsMultiheaded()) {
total_reliability += w->reliability;
total_breakdowns += w->breakdowns_since_last_service;
}
}
total_engines = Train::From(v)->tcache.cached_num_engines;
assert(total_engines > 0);
SetDParam(0, ToPercent16(total_reliability / total_engines));
SetDParam(1, total_breakdowns);
} else {
SetDParam(0, ToPercent16(v->reliability));
SetDParam(1, v->breakdowns_since_last_service);
}
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS);
break;
}
@ -2394,6 +2437,13 @@ void StartStopVehicle(const Vehicle *v, bool texteffect)
DoCommandP(v->tile, v->index, 0, _vehicle_command_translation_table[VCT_CMD_START_STOP][v->type], texteffect ? CcStartStopVehicle : NULL);
}
/** Strings for aircraft breakdown types */
static const StringID _aircraft_breakdown_strings[] = {
STR_BREAKDOWN_TYPE_LOW_SPEED,
STR_BREAKDOWN_TYPE_DEPOT,
STR_BREAKDOWN_TYPE_LANDING,
};
/** Checks whether the vehicle may be refitted at the moment.*/
static bool IsVehicleRefitable(const Vehicle *v)
{
@ -2560,8 +2610,30 @@ public:
StringID str;
if (v->vehstatus & VS_CRASHED) {
str = STR_VEHICLE_STATUS_CRASHED;
} else if (v->type != VEH_AIRCRAFT && v->breakdown_ctr == 1) { // check for aircraft necessary?
str = STR_VEHICLE_STATUS_BROKEN_DOWN;
} else if ( v->breakdown_ctr == 1 || ( v->type == VEH_TRAIN && Train::From( v )->flags & VRF_IS_BROKEN ) ) {
if ( _settings_game.vehicle.improved_breakdowns ) {
str = STR_VEHICLE_STATUS_BROKEN_DOWN_VEL;
SetDParam( 2, v->GetDisplaySpeed( ) );
} else
str = STR_VEHICLE_STATUS_BROKEN_DOWN;
if ( v->type == VEH_AIRCRAFT ) {
SetDParam( 0, _aircraft_breakdown_strings[v->breakdown_type] );
if ( v->breakdown_type == BREAKDOWN_AIRCRAFT_SPEED ) {
SetDParam( 1, v->breakdown_severity << 3 );
} else {
SetDParam( 1, v->current_order.GetDestination( ) );
}
} else {
const Vehicle *w = ( v->type == VEH_TRAIN ) ? GetMostSeverelyBrokenEngine( Train::From( v ) ) : v;
SetDParam( 0, STR_BREAKDOWN_TYPE_CRITICAL + w->breakdown_type );
if ( w->breakdown_type == BREAKDOWN_LOW_SPEED ) {
SetDParam( 1, min( w->First()->GetDisplayMaxSpeed( ), w->breakdown_severity >> ( v->type == VEH_TRAIN ? 0 : 1 ) ) );
} else if ( w->breakdown_type == BREAKDOWN_LOW_POWER ) {
SetDParam( 1, w->breakdown_severity * 100 / 256 );
}
}
} else if (v->vehstatus & VS_STOPPED) {
if (v->type == VEH_TRAIN) {
if (v->cur_speed == 0) {

@ -77,6 +77,18 @@ static const uint MAX_LENGTH_VEHICLE_NAME_CHARS = 32; ///< The maximum length of
/** The length of a vehicle in tile units. */
static const uint VEHICLE_LENGTH = 8;
/* The different types of breakdowns */
enum BreakdownType {
BREAKDOWN_CRITICAL = 0, ///< Old style breakdown (black smoke)
BREAKDOWN_EM_STOP = 1, ///< Emergency stop
BREAKDOWN_LOW_SPEED = 2, ///< Lower max speed
BREAKDOWN_LOW_POWER = 3, ///< Power reduction
/* Aircraft have totally different breakdowns, so we use aliases to make things clearer */
BREAKDOWN_AIRCRAFT_SPEED = BREAKDOWN_CRITICAL, ///< Lower speed until the next airport
BREAKDOWN_AIRCRAFT_DEPOT = BREAKDOWN_EM_STOP, ///< We have to visit a depot at the next airport
BREAKDOWN_AIRCRAFT_EM_LANDING = BREAKDOWN_LOW_SPEED, ///< Emergency landing at the closest airport (with hangar!) we can find
};
/** Vehicle acceleration models. */
enum AccelerationModel {
AM_ORIGINAL,

Loading…
Cancel
Save