From 9f5164b4037ee045a70929381bcca0b48645defc Mon Sep 17 00:00:00 2001 From: patch-import Date: Sun, 2 Aug 2015 22:58:41 +0100 Subject: [PATCH] Import Improved Breakdowns patch Fix trailing whitespace http://www.tt-forums.net/viewtopic.php?p=1146419#p1146419 --- src/aircraft.h | 1 + src/aircraft_cmd.cpp | 55 ++++++- src/disaster_vehicle.cpp | 2 + src/ground_vehicle.cpp | 99 ++++++++++-- src/ground_vehicle.hpp | 22 ++- src/lang/english.txt | 17 ++ src/order_type.h | 1 + src/roadveh.h | 2 + src/roadveh_cmd.cpp | 5 +- src/saveload/afterload.cpp | 32 ++++ src/saveload/saveload.cpp | 4 +- src/saveload/saveload.h | 1 + src/saveload/vehicle_sl.cpp | 2 + src/settings_gui.cpp | 1 + src/settings_type.h | 1 + src/ship_cmd.cpp | 13 ++ src/table/settings.ini | 9 ++ src/train.h | 31 +++- src/train_cmd.cpp | 76 ++++++++- src/train_gui.cpp | 65 +++++++- src/vehicle.cpp | 312 ++++++++++++++++++++++++++++++------ src/vehicle_base.h | 2 + src/vehicle_gui.cpp | 80 ++++++++- src/vehicle_type.h | 12 ++ 24 files changed, 761 insertions(+), 84 deletions(-) diff --git a/src/aircraft.h b/src/aircraft.h index 0805ae8ec6..cbb424423f 100644 --- a/src/aircraft.h +++ b/src/aircraft.h @@ -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 diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 18dcbea27b..26ea3a4c77 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -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; diff --git a/src/disaster_vehicle.cpp b/src/disaster_vehicle.cpp index d75e2d460e..08958d5bb8 100644 --- a/src/disaster_vehicle.cpp +++ b/src/disaster_vehicle.cpp @@ -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; } } } diff --git a/src/ground_vehicle.cpp b/src/ground_vehicle.cpp index fcdab77b03..3d8f7541ac 100644 --- a/src/ground_vehicle.cpp +++ b/src/ground_vehicle.cpp @@ -30,12 +30,9 @@ void GroundVehicle::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::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::PowerChanged() this->gcache.cached_max_track_speed = max_track_speed; } +template +void GroundVehicle::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::CargoChanged() * @return Current acceleration of the vehicle. */ template -int GroundVehicle::GetAcceleration() const +int GroundVehicle::GetAcceleration() { /* Templated class used for function calls for performance reasons. */ const T *v = T::From(this); @@ -119,6 +138,7 @@ int GroundVehicle::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::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::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::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::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); } diff --git a/src/ground_vehicle.hpp b/src/ground_vehicle.hpp index 56b97875fc..00dcfca721 100644 --- a/src/ground_vehicle.hpp +++ b/src/ground_vehicle.hpp @@ -91,9 +91,12 @@ struct GroundVehicle : public SpecializedVehicle { 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); } diff --git a/src/lang/english.txt b/src/lang/english.txt index 11ae31c4c3..6ce5de1467 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -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}) diff --git a/src/order_type.h b/src/order_type.h index d3a771ce8b..aba2616a10 100644 --- a/src/order_type.h +++ b/src/order_type.h @@ -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. }; /** diff --git a/src/roadveh.h b/src/roadveh.h index 5b265f0470..301ec82e11 100644 --- a/src/roadveh.h +++ b/src/roadveh.h @@ -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; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 35c671d355..3747ad8854 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -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; diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 2046d004b1..c2a84ba6ce 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -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++) { diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index b7c04fa742..7336115dbd 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -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 diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 72c51fa69d..106b12494d 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -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 { diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index ebc5fc4215..d0674571fb 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -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), diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index f1af4804e4..62c81741be 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -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")); } diff --git a/src/settings_type.h b/src/settings_type.h index 41366a7719..035bef31a8 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -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. */ diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index a24fb35104..e7e2961bdd 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -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; diff --git a/src/table/settings.ini b/src/table/settings.ini index 24ad486323..72015af6c4 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -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 diff --git a/src/train.h b/src/train.h index 280d59ebdd..9df852fc37 100644 --- a/src/train.h +++ b/src/train.h @@ -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 { } 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; } /** diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 8f20973b35..82680bf3ec 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -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); + } } /** diff --git a/src/train_gui.cpp b/src/train_gui.cpp index d9da097017..7927f8dc0d 100644 --- a/src/train_gui.cpp +++ b/src/train_gui.cpp @@ -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(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--; } } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index d54c627a05..bd7e9b835c 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -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(); } diff --git a/src/vehicle_base.h b/src/vehicle_base.h index 59584da788..86b1eef8dd 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -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 */ diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 4ace090dda..39aa203089 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -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) { diff --git a/src/vehicle_type.h b/src/vehicle_type.h index 0921b39e36..3b3e0911da 100644 --- a/src/vehicle_type.h +++ b/src/vehicle_type.h @@ -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,