Index: src/aircraft_cmd.cpp =================================================================== --- src/aircraft_cmd.cpp (revision 14286) +++ src/aircraft_cmd.cpp (working copy) @@ -379,6 +379,7 @@ const Engine *e = GetEngine(p1); v->reliability = e->reliability; v->reliability_spd_dec = e->reliability_spd_dec; + v->breakdown_chance = 128; v->max_age = e->lifelength * 366; _new_vehicle_id = v->index; Index: src/lang/english.txt =================================================================== --- src/lang/english.txt (revision 14286) +++ src/lang/english.txt (working copy) @@ -1063,6 +1063,7 @@ STR_CONFIG_PATCHES_STOP_ON_TOWN_ROAD :{LTBLUE}Allow drive-through road stops on town owned roads: {ORANGE}{STRING} STR_CONFIG_PATCHES_ADJACENT_STATIONS :{LTBLUE}Allow building adjacent stations: {ORANGE}{STRING} STR_CONFIG_PATCHES_DYNAMIC_ENGINES :{LTBLUE}Enable multiple NewGRF engine sets: {ORANGE}{STRING} +STR_CONFIG_PATCHES_IMPROVED_BREAKDOWNS :{LTBLUE}Enable improved breakdowns (requires realistic acceleration): {ORANGE}{STRING} STR_CONFIG_PATCHES_SMALL_AIRPORTS :{LTBLUE}Always allow small airports: {ORANGE}{STRING1} @@ -2885,7 +2886,10 @@ STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Reset the lateness counter, so the vehicle will be on time STR_SERVICE_HINT :{BLACK}Skip this order unless a service is needed STR_VEHICLE_INFO_COST_WEIGHT_SPEED_POWER :{BLACK}Cost: {CURRENCY} Weight: {WEIGHT_S}{}Speed: {VELOCITY} Power: {POWER}{}Running Cost: {CURRENCY}/yr{}Capacity: {CARGO} -STR_885C_BROKEN_DOWN :{RED}Broken down +STR_885C_BROKEN_DOWN :{RED}Broken - {STRING1} +STR_BROKEN_DOWN_VEL :{RED}Broken - {STRING1} / {LTBLUE}{VELOCITY} +STR_CURRENT_STATUS :{BLACK}Current status: {STRING2} +STR_RUNNING :{LTBLUE}Running STR_885D_AGE_RUNNING_COST_YR :{BLACK}Age: {LTBLUE}{STRING2}{BLACK} Running Cost: {LTBLUE}{CURRENCY}/yr STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED :{BLACK}Weight: {LTBLUE}{WEIGHT_S} {BLACK}Power: {LTBLUE}{POWER}{BLACK} Max. speed: {LTBLUE}{VELOCITY} STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE :{BLACK}Weight: {LTBLUE}{WEIGHT_S} {BLACK}Power: {LTBLUE}{POWER}{BLACK} Max. speed: {LTBLUE}{VELOCITY} {BLACK}Max. T.E.: {LTBLUE}{FORCE} @@ -2907,6 +2911,11 @@ STR_CHANGE_WAYPOINT_NAME :{BLACK}Change waypoint name STR_WAYPOINT_NAME :{WHITE}{WAYPOINT} +STR_BREAKDOWN_TYPE_CRITICAL :Mechanical failure {SKIP} +STR_BREAKDOWN_TYPE_EM_STOP :Emergency stop {SKIP} +STR_BREAKDOWN_TYPE_LOW_SPEED :Limited to {VELOCITY} +STR_BREAKDOWN_TYPE_LOW_POWER :{COMMA}% Power + STR_TRAIN_STOPPING :{RED}Stopping STR_TRAIN_STOPPING_VEL :{RED}Stopping, {VELOCITY} STR_INCOMPATIBLE_RAIL_TYPES :Incompatible rail types Index: src/roadveh_cmd.cpp =================================================================== --- src/roadveh_cmd.cpp (revision 14286) +++ src/roadveh_cmd.cpp (working copy) @@ -252,6 +252,7 @@ e = GetEngine(p1); v->reliability = e->reliability; v->reliability_spd_dec = e->reliability_spd_dec; + v->breakdown_chance = 128; v->max_age = e->lifelength * 366; _new_vehicle_id = v->index; Index: src/saveload.cpp =================================================================== --- src/saveload.cpp (revision 14286) +++ src/saveload.cpp (working copy) @@ -37,7 +37,7 @@ #include "table/strings.h" -extern const uint16 SAVEGAME_VERSION = 101; +extern const uint16 SAVEGAME_VERSION = 102; SavegameType _savegame_type; ///< type of savegame we are loading Index: src/settings.cpp =================================================================== --- src/settings.cpp (revision 14286) +++ src/settings.cpp (working copy) @@ -1297,6 +1297,7 @@ SDT_CONDBOOL(GameSettings, order.timetabling, 67, SL_MAX_VERSION, 0, 0, true, STR_CONFIG_PATCHES_TIMETABLE_ALLOW, NULL), SDT_CONDVAR(GameSettings, vehicle.plane_speed, SLE_UINT8, 90, SL_MAX_VERSION, 0, 0, 4, 1, 4, 0, STR_CONFIG_PATCHES_PLANE_SPEED, NULL), SDT_CONDBOOL(GameSettings, vehicle.dynamic_engines, 95, SL_MAX_VERSION, 0,NN, false, STR_CONFIG_PATCHES_DYNAMIC_ENGINES, NULL), + SDT_CONDBOOL(GameSettings, vehicle.improved_breakdowns, 102, SL_MAX_VERSION, 0, 0, false, STR_CONFIG_PATCHES_IMPROVED_BREAKDOWNS, NULL), SDT_BOOL(GameSettings, station.join_stations, 0, 0, true, STR_CONFIG_PATCHES_JOINSTATIONS, NULL), SDTC_CONDBOOL( gui.sg_full_load_any, 0, 92, 0, 0 , true, STR_NULL, NULL), Index: src/settings_gui.cpp =================================================================== --- src/settings_gui.cpp (revision 14286) +++ src/settings_gui.cpp (working copy) @@ -706,6 +706,7 @@ "vehicle.plane_speed", "order.timetabling", "vehicle.dynamic_engines", + "vehicle.improved_breakdowns", }; struct PatchEntry { Index: src/settings_type.h =================================================================== --- src/settings_type.h (revision 14286) +++ src/settings_type.h (working copy) @@ -292,6 +292,7 @@ bool never_expire_vehicles; ///< never expire vehicles byte extend_vehicle_life; ///< extend vehicle life by this many years byte road_side; ///< the side of the road vehicles drive on + bool improved_breakdowns; ///< improved breakdowns, currently train-only }; /** Settings related to the economy. */ Index: src/ship_cmd.cpp =================================================================== --- src/ship_cmd.cpp (revision 14286) +++ src/ship_cmd.cpp (working copy) @@ -806,6 +806,7 @@ e = GetEngine(p1); v->reliability = e->reliability; v->reliability_spd_dec = e->reliability_spd_dec; + v->breakdown_chance = 64; //ships have 50% a lower breakdown chance than other vehicles v->max_age = e->lifelength * 366; _new_vehicle_id = v->index; Index: src/train_cmd.cpp =================================================================== --- src/train_cmd.cpp (revision 14286) +++ src/train_cmd.cpp (working copy) @@ -102,16 +102,18 @@ return _settings_game.vehicle.freight_trains; } - /** - * Recalculates the cached total power of a train. Should be called when the consist is changed + * Calculate the power and max TE of a train. + * @note We use pointers because we can't return more than one value easily. * @param v First vehicle of the consist. + * @param total_power A pointer to the variable to store the total_power in. + * @param max_te A pointer to the variable to store the max TE in. + * @param breakdowns Whether to account for breakdowns when calculating. */ -void TrainPowerChanged(Vehicle *v) -{ - uint32 total_power = 0; - uint32 max_te = 0; - +static void CalculateTrainPower(const Vehicle *v, uint32 *total_power, uint32 *max_te, bool breakdowns) { + *total_power = 0; + *max_te = 0; + for (const Vehicle *u = v; u != NULL; u = u->Next()) { RailType railtype = GetRailType(u->tile); @@ -126,19 +128,33 @@ if (power != 0) { /* Halve power for multiheaded parts */ if (IsMultiheaded(u)) power /= 2; - - total_power += power; + if (breakdowns && u->breakdown_ctr == 1 && u->breakdown_type == BREAKDOWN_LOW_POWER) { + power = power * u->breakdown_severity / 256; + } + *total_power += power; /* Tractive effort in (tonnes * 1000 * 10 =) N */ - max_te += (u->u.rail.cached_veh_weight * 10000 * GetVehicleProperty(u, 0x1F, rvi_u->tractive_effort)) / 256; + *max_te += (u->u.rail.cached_veh_weight * 10000 * GetVehicleProperty(u, 0x1F, rvi_u->tractive_effort)) / 256; } } } if (HasBit(u->u.rail.flags, VRF_POWEREDWAGON) && HasPowerOnRail(v->u.rail.railtype, railtype)) { - total_power += RailVehInfo(u->u.rail.first_engine)->pow_wag_power; + *total_power += RailVehInfo(u->u.rail.first_engine)->pow_wag_power; } } +} +/** + * Recalculates the cached total power of a train. Should be called when the consist is changed + * @param v First vehicle of the consist. + */ +void TrainPowerChanged(Vehicle *v) +{ + uint32 total_power = 0; + uint32 max_te = 0; + + CalculateTrainPower(v, &total_power, &max_te, false); + if (v->u.rail.cached_power != total_power || v->u.rail.cached_max_te != max_te) { /* If it has no power (no catenary), stop the train */ if (total_power == 0) v->vehstatus |= VS_STOPPED; @@ -150,8 +166,49 @@ } } +/** + * 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. + */ +static void CheckBreakdownFlags(Vehicle *v) +{ + assert(IsFrontEngine(v)); + /* clear the flags we're gonna check first */ + CLRBITS(v->u.rail.flags, (1 << VRF_BREAKDOWN_BRAKING) | (1 << VRF_BREAKDOWN_POWER) | + (1 << VRF_BREAKDOWN_SPEED) | (1 << VRF_BREAKDOWN_STOPPED)); + for (const Vehicle *w = v; w != NULL; w = w->Next()) { + if (IsTrainEngine(w) || IsMultiheaded(w)) { + if (w->breakdown_ctr == 2) SetBit(v->u.rail.flags, VRF_BREAKDOWN_BRAKING); + if (w->breakdown_ctr == 1 && w->breakdown_type == BREAKDOWN_LOW_POWER) SetBit(v->u.rail.flags, VRF_BREAKDOWN_POWER); + if (w->breakdown_ctr == 1 && w->breakdown_type == BREAKDOWN_LOW_SPEED) SetBit(v->u.rail.flags, VRF_BREAKDOWN_SPEED); + if (w->breakdown_ctr == 1 && (w->breakdown_type == BREAKDOWN_CRITICAL || w->breakdown_type == BREAKDOWN_EM_STOP)) SetBit(v->u.rail.flags, VRF_BREAKDOWN_STOPPED); + } + } +} + /** + * Gets the speed a broken down train (low speed breakdown) is limited to. + * @param v The front engine of the vehicle. + * @return The speed the train is limited to. + */ + +static uint16 GetBreakdownSpeed(const Vehicle *v) +{ + assert(IsFrontEngine(v)); + uint16 speed = UINT16_MAX; + + for (const Vehicle *w = v; w != NULL; w = w->Next()) { + if (w->breakdown_ctr == 1 && w->breakdown_type == BREAKDOWN_LOW_SPEED) { + speed = min(speed, w->breakdown_severity); + } + } + return speed; +} + +/** * Recalculates the cached weight of a train and its vehicles. Should be called each time the cargo on * the consist changes. * @param v First vehicle of the consist. @@ -466,9 +523,17 @@ } int mass = v->u.rail.cached_weight; - int power = v->u.rail.cached_power * 746; + uint32 power = v->u.rail.cached_power * 746; max_speed = min(max_speed, v->u.rail.cached_max_speed); + uint32 max_te = v->u.rail.cached_max_te; // [N] + /* handle breakdown speed/power reduction */ + if (HasBit(v->u.rail.flags, VRF_BREAKDOWN_SPEED)) max_speed = min(v->max_speed, GetBreakdownSpeed(v)); + if (mode == AM_ACCEL && HasBit(v->u.rail.flags, VRF_BREAKDOWN_POWER)) { + CalculateTrainPower(v, &power, &max_te, true); + power *= 746; + } + int num = 0; //number of vehicles, change this into the number of axles later int incl = 0; int drag_coeff = 20; //[1e-4] @@ -503,7 +568,6 @@ /* Due to the mph to m/s conversion below, at speeds below 3 mph the force is * actually double the train's power */ - const int max_te = v->u.rail.cached_max_te; // [N] int force; if (speed > 2) { switch (v->u.rail.railtype) { @@ -513,7 +577,7 @@ force = power / speed; //[N] force *= 22; force /= 10; - if (mode == AM_ACCEL && force > max_te) force = max_te; + if (mode == AM_ACCEL && force > (int)max_te) force = max_te; break; default: NOT_REACHED(); @@ -526,11 +590,25 @@ force = (mode == AM_ACCEL && v->u.rail.railtype != RAILTYPE_MAGLEV) ? min(max_te, power) : power; force = max(force, (mass * 8) + resistance); } - + if (force <= 0) force = 10000; if (v->u.rail.railtype != RAILTYPE_MAGLEV) force = min(force, mass * 10 * 200); + /* If power is 0 because of a breakdown, we make the force 0 if accelerating */ + if (mode == AM_ACCEL && HasBit(v->u.rail.flags, VRF_BREAKDOWN_POWER) && power == 0) { + force = 0; + } + + /* calculate the breakdown chance based on resistance/force and speed/max_speed */ + if (_settings_game.vehicle.realistic_acceleration && _settings_game.vehicle.improved_breakdowns) { + uint64 breakdown_factor = 128 * resistance * v->cur_speed; + breakdown_factor /= (max(force, 1000) * v->u.rail.cached_max_speed); + v->breakdown_chance = Clamp(breakdown_factor + 64, 0, 255); + } else { + v->breakdown_chance = 128; + } + if (mode == AM_ACCEL) { return (force - resistance) / (mass * 4); } else { @@ -548,6 +626,9 @@ uint weight = v->u.rail.cached_weight; assert(weight != 0); v->acceleration = Clamp(power / weight * 4, 1, 255); + + /* for non-realistic acceleration, breakdown chance is always 128 (average) */ + v->breakdown_chance = 128; } SpriteID Train::GetImage(Direction direction) const @@ -743,6 +824,8 @@ u->u.rail.railtype = v->u.rail.railtype; if (building) v->SetNext(u); u->engine_type = v->engine_type; + u->reliability = v->reliability; + u->reliability_spd_dec = v->reliability_spd_dec; u->build_year = v->build_year; if (building) v->value >>= 1; u->value = v->value; @@ -1949,7 +2032,7 @@ } } 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->u.rail.flags, VRF_BREAKDOWN_STOPPED)) return CMD_ERROR; if (flags & DC_EXEC) { if (_settings_game.vehicle.realistic_acceleration && v->cur_speed != 0) { @@ -3208,7 +3291,7 @@ { uint accel; - if (v->vehstatus & VS_STOPPED || HasBit(v->u.rail.flags, VRF_REVERSING) || HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) { + if (v->vehstatus & VS_STOPPED || HasBit(v->u.rail.flags, VRF_REVERSING) || HasBit(v->u.rail.flags, VRF_TRAIN_STUCK) || HasBit(v->u.rail.flags, VRF_BREAKDOWN_BRAKING)) { if (_settings_game.vehicle.realistic_acceleration) { accel = GetTrainAcceleration(v, AM_BRAKE) * 2; } else { @@ -3973,36 +4056,128 @@ } } -static void HandleBrokenTrain(Vehicle *v) +/** + * Handle the start of a breakdown + * @param v the vehicle in question + */ +static void HandleBreakdownStart(Vehicle *v) { - if (v->breakdown_ctr != 1) { - v->breakdown_ctr = 1; - v->cur_speed = 0; + v->breakdown_ctr = 1; - if (v->breakdowns_since_last_service != 255) - v->breakdowns_since_last_service++; + if (v->breakdowns_since_last_service != 255) + v->breakdowns_since_last_service++; - InvalidateWindow(WC_VEHICLE_VIEW, v->index); - InvalidateWindow(WC_VEHICLE_DETAILS, v->index); + switch (v->breakdown_type) { + case BREAKDOWN_CRITICAL: + if (!PlayVehicleSound(v, VSE_BREAKDOWN)) { + SndPlayVehicleFx((_settings_game.game_creation.landscape != LT_TOYLAND) ? + SND_10_TRAIN_BREAKDOWN : SND_3A_COMEDY_BREAKDOWN_2, v); + } + if (!(v->vehstatus & VS_HIDDEN)) { + Vehicle *u = CreateEffectVehicleRel(v, 4, 4, 5, EV_BREAKDOWN_SMOKE); + if (u != NULL) u->u.effect.animation_state = v->breakdown_delay * 2; + } + CheckBreakdownFlags(v->First()); + SetBit(v->First()->u.rail.flags, VRF_BREAKDOWN_STOPPED); + break; + case BREAKDOWN_EM_STOP: + CheckBreakdownFlags(v->First()); + SetBit(v->First()->u.rail.flags, VRF_BREAKDOWN_STOPPED); + break; + case BREAKDOWN_LOW_SPEED: + CheckBreakdownFlags(v->First()); + SetBit(v->First()->u.rail.flags, VRF_BREAKDOWN_SPEED); + break; + case BREAKDOWN_LOW_POWER: + SetBit(v->First()->u.rail.flags, VRF_BREAKDOWN_POWER); + break; + default: NOT_REACHED(); + } - if (!PlayVehicleSound(v, VSE_BREAKDOWN)) { - SndPlayVehicleFx((_settings_game.game_creation.landscape != LT_TOYLAND) ? - SND_10_TRAIN_BREAKDOWN : SND_3A_COMEDY_BREAKDOWN_2, v); - } + InvalidateWindow(WC_VEHICLE_VIEW, v->First()->index); + InvalidateWindow(WC_VEHICLE_DETAILS, v->First()->index); +} - if (!(v->vehstatus & VS_HIDDEN)) { - Vehicle *u = CreateEffectVehicleRel(v, 4, 4, 5, EV_BREAKDOWN_SMOKE); - if (u != NULL) u->u.effect.animation_state = v->breakdown_delay * 2; - } +/** + * Make preparations for the oncoming breakdown (slow down etc). + * Also check if those preparations (aka braking) are finished. + * If they are, start the breakdown. + * @param v the vehicle in question + */ +static void PrepareBreakdown(Vehicle *v) +{ + switch (v->breakdown_type) { + case BREAKDOWN_CRITICAL: + case BREAKDOWN_EM_STOP: + if (v->First()->cur_speed == 0) { + HandleBreakdownStart(v); + } else { + SetBit(v->First()->u.rail.flags, VRF_BREAKDOWN_BRAKING); + } + break; + case BREAKDOWN_LOW_SPEED: + if (v->First()->cur_speed <= v->breakdown_severity) { + HandleBreakdownStart(v); + } else { + SetBit(v->First()->u.rail.flags, VRF_BREAKDOWN_BRAKING); + } + break; + case BREAKDOWN_LOW_POWER: + HandleBreakdownStart(v); + break; + default: NOT_REACHED(); } +} - if (!(v->tick_counter & 3)) { - if (!--v->breakdown_delay) { - v->breakdown_ctr = 0; - InvalidateWindow(WC_VEHICLE_VIEW, v->index); +/** + * Handle a train that is broken down or going to break down. + * @param v the vehicle in question + * @return whether the vehicle is completely stopped by the breakdown. This to avoid unneccessary code execution. + */ +static void HandleBrokenTrain(Vehicle *v) +{ + /** + * when v->breakdown_ctr hits 2, we prepare for the breakdown (->brake) + * PrepareBreakdown(v) is called every tick until the it determines that enough preparation (braking) has been done. + * It then calls HandleBreakdownStart(v), which sets the counter to 1. + * If the counter is 1 we are officially 'broken down'. + */ + + if (v->breakdown_ctr == 2) { + PrepareBreakdown(v); + } + if (v->breakdown_ctr == 1) { + if (!(v->tick_counter & 3)) { + if (!--v->breakdown_delay) { + v->breakdown_ctr = 0; + CheckBreakdownFlags(v->First()); + InvalidateWindow(WC_VEHICLE_VIEW, v->First()->index); + InvalidateWindow(WC_VEHICLE_DETAILS, v->First()->index); + } } + if ((v->breakdown_type == BREAKDOWN_LOW_SPEED || v->breakdown_type == BREAKDOWN_LOW_POWER) && (v->tick_counter & 0x1F) == 0) { + CreateEffectVehicleRel(v, 0, 0, 2, EV_SMOKE); //some grey clouds to indicate a broken engine + } } } +/** + * Handle all breakdown related stuff for a train + * @param v the front engine + */ +static void HandlePossibleBreakdowns(Vehicle *v) +{ + assert(IsFrontEngine(v)); + for (Vehicle *u = v; u != NULL; u = u->Next()) { + if (u->breakdown_ctr != 0 && (IsTrainEngine(u) || IsMultiheaded(u))) { + if (u->breakdown_ctr <= 2) { + HandleBrokenTrain(u); + /* We check the order of v (the first vehicle) instead of u here! */ + } else if (!v->current_order.IsType(OT_LOADING)) { + u->breakdown_ctr--; + } + } + } +} /** Maximum speeds for train that is broken down or approaching line end */ static const uint16 _breakdown_speeds[16] = { @@ -4117,13 +4292,8 @@ static bool TrainCheckIfLineEnds(Vehicle *v) { /* First, handle broken down train */ - - int t = v->breakdown_ctr; - if (t > 1) { + if (HasBit(v->u.rail.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; } @@ -4179,21 +4349,14 @@ InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); } - /* train is broken down? */ - if (v->breakdown_ctr != 0) { - if (v->breakdown_ctr <= 2) { - HandleBrokenTrain(v); - return; - } - if (!v->current_order.IsType(OT_LOADING)) v->breakdown_ctr--; - } + HandlePossibleBreakdowns(v); if (HasBit(v->u.rail.flags, VRF_REVERSING) && v->cur_speed == 0) { ReverseTrainDirection(v); } /* exit if train is stopped */ - if (v->vehstatus & VS_STOPPED && v->cur_speed == 0) return; + if ((v->vehstatus & VS_STOPPED || HasBit(v->u.rail.flags, VRF_BREAKDOWN_STOPPED)) && v->cur_speed == 0) return; bool valid_order = v->current_order.IsValid() && v->current_order.GetType() != OT_CONDITIONAL; if (ProcessOrders(v) && CheckReverseTrain(v)) { @@ -4358,7 +4521,6 @@ if ((++this->day_counter & 7) == 0) DecreaseVehicleValue(this); if (IsFrontEngine(this)) { - CheckVehicleBreakdown(this); AgeVehicle(this); CheckIfTrainNeedsService(this); @@ -4387,6 +4549,12 @@ /* Also age engines that aren't front engines */ AgeVehicle(this); } + + if (IsTrainEngine(this) || IsMultiheaded(this)) { + if (IsFrontEngine(this) || (_settings_game.vehicle.improved_breakdowns && _settings_game.vehicle.realistic_acceleration)) { + CheckVehicleBreakdown(this); + } + } } void TrainsYearlyLoop() Index: src/train_gui.cpp =================================================================== --- src/train_gui.cpp (revision 14286) +++ src/train_gui.cpp (working copy) @@ -131,7 +131,7 @@ } } -static void TrainDetailsInfoTab(const Vehicle *v, int x, int y) +static void TrainDetailsInfoTab(const Vehicle *v, int x, int y, byte extra_lines) { if (RailVehInfo(v->engine_type)->railveh_type == RAILVEH_WAGON) { SetDParam(0, v->engine_type); @@ -142,6 +142,26 @@ SetDParam(1, v->build_year); SetDParam(2, v->value); DrawString(x, y, STR_882C_BUILT_VALUE, TC_BLACK); + + if (extra_lines == 0) return; + SetDParam(0, v->reliability * 100 >> 16); + SetDParam(1, v->breakdowns_since_last_service); + DrawString(x, y + 14, STR_8860_RELIABILITY_BREAKDOWNS, TC_BLACK); + + if (extra_lines == 1) return; + if (v->breakdown_ctr == 1) { + SetDParam(0, STR_885C_BROKEN_DOWN); + SetDParam(1, STR_BREAKDOWN_TYPE_CRITICAL + v->breakdown_type); + if (v->breakdown_type == BREAKDOWN_LOW_SPEED) { + SetDParam(2, v->breakdown_severity * 10 / 16); + } else if (v->breakdown_type == BREAKDOWN_LOW_POWER) { + SetDParam(2, v->breakdown_severity * 100 / 256); + } + } else { + SetDParam(0, STR_RUNNING); + } + DrawString(x, y + 28, STR_CURRENT_STATUS, TC_BLACK); + return; } } @@ -212,7 +232,13 @@ case 1: /* Only show name and value for the 'real' part */ if (!IsArticulatedPart(v)) { - TrainDetailsInfoTab(v, px, py); + /* number of extra text lines we have room for in the window, with a max of 2 */ + byte extra_lines = Clamp(vscroll_cap + vscroll_pos, 0, 2); + if (RailVehInfo(v->engine_type)->railveh_type != RAILVEH_WAGON) { + y += 14 * extra_lines; + vscroll_pos -= extra_lines; + } + TrainDetailsInfoTab(v, px, py, extra_lines); } break; case 2: TrainDetailsCapacityTab(v, px, py); break; Index: src/vehicle.cpp =================================================================== --- src/vehicle.cpp (revision 14286) +++ src/vehicle.cpp (working copy) @@ -118,9 +118,19 @@ void VehicleServiceInDepot(Vehicle *v) { + if (v->type == VEH_TRAIN) { + if (v->Next() != NULL) VehicleServiceInDepot(v->Next()); + if (!IsTrainEngine(v) && !IsRearDualheaded(v)) return; + } v->date_of_last_service = _date; v->breakdowns_since_last_service = 0; + v->breakdown_ctr = 0; + v->breakdown_delay = 0; v->reliability = GetEngine(v->engine_type)->reliability; + if (v->type == VEH_TRAIN && IsFrontEngine(v)) { + CLRBITS(v->u.rail.flags, (1 << VRF_BREAKDOWN_BRAKING) | (1 << VRF_BREAKDOWN_POWER) | + (1 << VRF_BREAKDOWN_SPEED) | (1 << VRF_BREAKDOWN_STOPPED)); + } InvalidateWindow(WC_VEHICLE_DETAILS, v->index); // ensure that last service date and reliability are updated } @@ -1002,50 +1012,113 @@ InvalidateWindow(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 */ +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: + 255, ///< 100% chance for BREAKDOWN_CRITICAL. + 255, ///< 0% chance for BREAKDOWN_EM_STOP. + 255, ///< 0% chance for BREAKDOWN_LOW_SPEED. + 255, ///< 0% chance for BREAKDOWN_LOW_POWER. + }, + { //Ships: + 255, ///< 100% chance for BREAKDOWN_CRITICAL. + 255, ///< 0% chance for BREAKDOWN_EM_STOP. + 255, ///< 0% chance for BREAKDOWN_LOW_SPEED. + 255, ///< 0% chance for BREAKDOWN_LOW_POWER. + }, + { //Aircraft: + 255, ///< 100% chance for BREAKDOWN_CRITICAL. + 255, ///< 0% chance for BREAKDOWN_EM_STOP. + 255, ///< 0% chance for BREAKDOWN_LOW_SPEED. + 255, ///< 0% chance for BREAKDOWN_LOW_POWER. + }, }; +/* the minimum speed when suffering from a breakdown of type BREAKDOWN_LOW_SPEED */ +/* for steam/diesel/electric/mono/maglev */ +static byte _min_breakdown_speed[5] = {21, 41, 41, 61, 101}; + +/** + * 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) +{ + /* we require both 'improved breakdowns' and 'realistic acceleration', else just do the classic breakdown */ + if (!_settings_game.vehicle.realistic_acceleration || !_settings_game.vehicle.improved_breakdowns) { + v->breakdown_type = BREAKDOWN_CRITICAL; + return; + } + byte rand = GB(r, 8, 8); + const byte *breakdown_type_chance = _breakdown_chances[v->type]; + + if (rand <= breakdown_type_chance[BREAKDOWN_CRITICAL]) { + v->breakdown_type = BREAKDOWN_CRITICAL; + v->breakdown_severity = 0; + } else if (rand <= breakdown_type_chance[BREAKDOWN_EM_STOP]) { + v->breakdown_type = BREAKDOWN_EM_STOP; + v->breakdown_severity = 0; + 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; + const RailVehicleInfo *rvi = RailVehInfo(v->engine_type); + uint16 max_speed = GetVehicleProperty(v, 0x09, rvi->max_speed); + uint16 min_speed = min(_min_breakdown_speed[rvi->engclass], 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)) InvalidateWindow(WC_VEHICLE_DETAILS, v->index); - - if (v->breakdown_ctr != 0 || v->vehstatus & VS_STOPPED || + if ((rel_old >> 8) != (rel >> 8)) InvalidateWindow(WC_VEHICLE_DETAILS, v->First()->index); + 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) { return; } + uint32 r1 = Random(); + uint32 r2 = Random(); - uint32 r = Random(); + /** + * 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 upon a train network will be significantly less. + */ - /* increase chance of failure */ - int chance = v->breakdown_chance + 1; - if (Chance16I(1, 25, r)) chance += 25; - v->breakdown_chance = min(255, chance); - - /* 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; + if ((uint32)(0xffff - v->reliability) * _settings_game.difficulty.vehicle_breakdowns * v->First()->breakdown_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); } } @@ -2202,6 +2275,8 @@ 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, 102, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, breakdown_severity, SLE_UINT8, 102, 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), Index: src/vehicle_base.h =================================================================== --- src/vehicle_base.h (revision 14286) +++ src/vehicle_base.h (working copy) @@ -146,6 +146,18 @@ /* used to mark a train that can't get a path reservation */ VRF_TRAIN_STUCK = 8, + + /* used to mark a train that is braking because it is broken down */ + VRF_BREAKDOWN_BRAKING = 9, + + /* used to mark a train in which the power of one (or more) of the engines is reduced because of a breakdown */ + VRF_BREAKDOWN_POWER = 10, + + /* used to mark a train that has a reduced maximum speed because of a breakdown */ + VRF_BREAKDOWN_SPEED = 11, + + /* used to mark a train that is stopped because of a breakdown */ + VRF_BREAKDOWN_STOPPED = 12, }; struct VehicleAir { @@ -249,7 +261,9 @@ byte breakdown_ctr; byte breakdown_delay; byte breakdowns_since_last_service; - byte breakdown_chance; + byte breakdown_chance; //situation-specific chance to break down (more heavily loaded -> higher chance) + byte breakdown_severity; //Severity of the breakdown. Note that lower means more severe. + BreakdownType breakdown_type; // type of breakdown int32 x_pos; // coordinates int32 y_pos; Index: src/vehicle_gui.cpp =================================================================== --- src/vehicle_gui.cpp (revision 14286) +++ src/vehicle_gui.cpp (working copy) @@ -132,6 +132,31 @@ DrawSprite(SPR_BLOT, pal, x, y); } +/** + * Get the engine that suffers from the most severe breakdown. + * This means the engine with the lowest breakdown_type. + * If the breakdown_type of 2 engine is 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 Vehicle *v) +{ + assert(IsFrontEngine(v)); + 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; +} + struct RefitOption { CargoID cargo; byte subtype; @@ -1460,8 +1485,25 @@ DrawString(2, 35, _vehicle_translation_table[VST_VEHICLE_PROFIT_THIS_YEAR_LAST_YEAR][v->type], TC_FROMSTRING); /* Draw breakdown & reliability */ - SetDParam(0, v->reliability * 100 >> 16); - SetDParam(1, v->breakdowns_since_last_service); + if (v->type == VEH_TRAIN) { + /* we want to draw the average reliability and total # of breakdowns */ + uint32 total_reliability = 0; + uint16 total_breakdowns = 0; + byte total_engines = 0; + for (const Vehicle *w = v; w != NULL; w = w->Next()) { + if (IsTrainEngine(w) || IsMultiheaded(w)) { + total_reliability += w->reliability; + total_breakdowns += w->breakdowns_since_last_service; + total_engines++; + } + } + assert(total_engines > 0); + SetDParam(0, (total_reliability * 100 / total_engines) >> 16); + SetDParam(1, total_breakdowns); + } else { + SetDParam(0, v->reliability * 100 >> 16); + SetDParam(1, v->breakdowns_since_last_service); + } DrawString(2, 45, _vehicle_translation_table[VST_VEHICLE_RELIABILITY_BREAKDOWNS][v->type], TC_FROMSTRING); /* Draw service interval text */ @@ -1894,8 +1936,26 @@ if (v->vehstatus & VS_CRASHED) { str = STR_8863_CRASHED; - } else if (v->type != VEH_AIRCRAFT && v->breakdown_ctr == 1) { // check for aircraft necessary? + } else if (v->breakdown_ctr == 1 && (v->type == VEH_ROAD || v->type == VEH_SHIP)) { //no aircraft; is that neccessary? str = STR_885C_BROKEN_DOWN; + SetDParam(0, STR_BREAKDOWN_TYPE_CRITICAL); + } else if (v->type == VEH_TRAIN && HASBITS(v->u.rail.flags, + (1 << VRF_BREAKDOWN_POWER) | (1 << VRF_BREAKDOWN_SPEED) | (1 << VRF_BREAKDOWN_STOPPED))) { + /* we want to display the most severe breakdown */ + if (v->cur_speed == 0 || !_settings_client.gui.vehicle_speed) { + str = STR_885C_BROKEN_DOWN; + } else { + str = STR_BROKEN_DOWN_VEL; + SetDParam(2, v->GetDisplaySpeed()); + } + + const Vehicle *w = GetMostSeverelyBrokenEngine(v); + SetDParam(0, STR_BREAKDOWN_TYPE_CRITICAL + w->breakdown_type); + if (w->breakdown_type == BREAKDOWN_LOW_SPEED) { + SetDParam(1, w->breakdown_severity * 10 / 16); + } 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) { Index: src/vehicle_type.h =================================================================== --- src/vehicle_type.h (revision 14286) +++ src/vehicle_type.h (working copy) @@ -61,4 +61,12 @@ MAX_LENGTH_VEHICLE_NAME_PIXELS = 150, ///< The maximum length of a vehicle name in pixels }; +/* The different types of breakdowns */ +enum BreakdownType { + BREAKDOWN_CRITICAL, ///< Old style breakdown (black smoke) + BREAKDOWN_EM_STOP, ///< Emergency stop + BREAKDOWN_LOW_SPEED, ///< Lower max speed + BREAKDOWN_LOW_POWER, ///< Power reduction +}; + #endif /* VEHICLE_TYPE_H */