tron@2186: /* $Id$ */ tron@2186: rubidium@9111: /** @file train_cmd.cpp Handling of trains. */ belugas@6422: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@3234: #include "bridge_map.h" hackykid@1922: #include "debug.h" rubidium@8119: #include "tile_cmd.h" maedhros@6453: #include "landscape.h" tron@2561: #include "gui.h" tron@3315: #include "station_map.h" tron@3154: #include "tunnel_map.h" maedhros@6772: #include "articulated_vehicles.h" rubidium@8116: #include "command_func.h" truelight@0: #include "pathfind.h" matthijs@1247: #include "npf.h" rubidium@8785: #include "station_base.h" rubidium@8763: #include "news_func.h" rubidium@8786: #include "engine_func.h" peter1138@9070: #include "engine_base.h" rubidium@10208: #include "company_func.h" rubidium@10208: #include "company_base.h" rubidium@8962: #include "depot_base.h" rubidium@8962: #include "depot_func.h" truelight@1542: #include "waypoint.h" matthijs@1752: #include "vehicle_gui.h" bjarni@2676: #include "train.h" celestar@5385: #include "bridge.h" peter1138@2982: #include "newgrf_callbacks.h" peter1138@2962: #include "newgrf_engine.h" peter1138@4656: #include "newgrf_sound.h" peter1138@3727: #include "newgrf_text.h" rubidium@8100: #include "direction_func.h" KUDr@3900: #include "yapf/yapf.h" rubidium@9810: #include "yapf/follow_track.hpp" peter1138@6091: #include "cargotype.h" rubidium@6643: #include "group.h" glx@7802: #include "table/sprites.h" smatz@8083: #include "tunnelbridge_map.h" rubidium@8114: #include "strings_func.h" rubidium@8131: #include "functions.h" rubidium@8131: #include "window_func.h" rubidium@8140: #include "date_func.h" rubidium@8144: #include "vehicle_func.h" rubidium@8157: #include "sound_func.h" smatz@8238: #include "signal_func.h" rubidium@8211: #include "variables.h" rubidium@8212: #include "autoreplace_gui.h" rubidium@8224: #include "gfx_func.h" rubidium@8270: #include "settings_type.h" rubidium@8784: #include "order_func.h" peter1138@9003: #include "newgrf_station.h" rubidium@9009: #include "effectvehicle_func.h" smatz@9704: #include "gamelog.h" smatz@9704: #include "network/network.h" rubidium@9809: #include "pbs.h" smatz@8083: rubidium@8264: #include "table/strings.h" rubidium@8264: #include "table/train_cmd.h" truelight@0: rubidium@9810: static Track ChooseTrainTrack(Vehicle* v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool force_res, bool *got_reservation, bool mark_stuck); truelight@742: static bool TrainCheckIfLineEnds(Vehicle *v); smatz@8710: static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image); smatz@8334: static TileIndex TrainApproachingCrossingTile(const Vehicle *v); rubidium@9822: static void CheckIfTrainNeedsService(Vehicle *v); rubidium@9819: static void CheckNextTrainTile(Vehicle *v); truelight@0: rubidium@4344: static const byte _vehicle_initial_x_fract[4] = {10, 8, 4, 8}; rubidium@4344: static const byte _vehicle_initial_y_fract[4] = { 8, 4, 8, 10}; smatz@8248: smatz@8248: smatz@8248: /** smatz@8248: * Determine the side in which the train will leave the tile smatz@8248: * smatz@8248: * @param direction vehicle direction smatz@8248: * @param track vehicle track bits smatz@8248: * @return side of tile the train will leave smatz@8248: */ smatz@8248: static inline DiagDirection TrainExitDir(Direction direction, TrackBits track) smatz@8248: { smatz@8248: static const TrackBits state_dir_table[DIAGDIR_END] = { TRACK_BIT_RIGHT, TRACK_BIT_LOWER, TRACK_BIT_LEFT, TRACK_BIT_UPPER }; smatz@8248: smatz@8248: DiagDirection diagdir = DirToDiagDir(direction); smatz@8248: smatz@8248: /* Determine the diagonal direction in which we will exit this tile */ smatz@8248: if (!HasBit(direction, 0) && track != state_dir_table[diagdir]) { smatz@8248: diagdir = ChangeDiagDir(diagdir, DIAGDIRDIFF_90LEFT); smatz@8248: } smatz@8248: smatz@8248: return diagdir; smatz@8248: } truelight@0: peter1138@5163: peter1138@5163: /** Return the cargo weight multiplier to use for a rail vehicle peter1138@5316: * @param cargo Cargo type to get multiplier for peter1138@5163: * @return Cargo weight multiplier peter1138@5163: */ peter1138@5316: byte FreightWagonMult(CargoID cargo) peter1138@5163: { peter1138@6114: if (!GetCargo(cargo)->is_freight) return 1; rubidium@9413: return _settings_game.vehicle.freight_trains; peter1138@5163: } peter1138@5163: peter1138@5163: hackykid@1905: /** peter1138@5400: * Recalculates the cached total power of a train. Should be called when the consist is changed peter1138@5400: * @param v First vehicle of the consist. peter1138@5400: */ smatz@8850: void TrainPowerChanged(Vehicle *v) peter1138@5400: { peter1138@6490: uint32 total_power = 0; peter1138@5400: uint32 max_te = 0; peter1138@5400: rubidium@7492: for (const Vehicle *u = v; u != NULL; u = u->Next()) { rubidium@6172: RailType railtype = GetRailType(u->tile); peter1138@9738: peter1138@9738: /* Power is not added for articulated parts */ peter1138@9738: if (!IsArticulatedPart(u)) { peter1138@9738: bool engine_has_power = HasPowerOnRail(u->u.rail.railtype, railtype); peter1138@9738: peter1138@9738: const RailVehicleInfo *rvi_u = RailVehInfo(u->engine_type); peter1138@9738: peter1138@9738: if (engine_has_power) { peter1138@9738: uint16 power = GetVehicleProperty(u, 0x0B, rvi_u->power); peter1138@9738: if (power != 0) { peter1138@9738: /* Halve power for multiheaded parts */ peter1138@9738: if (IsMultiheaded(u)) power /= 2; peter1138@9738: peter1138@9738: total_power += power; peter1138@9738: /* Tractive effort in (tonnes * 1000 * 10 =) N */ peter1138@9738: max_te += (u->u.rail.cached_veh_weight * 10000 * GetVehicleProperty(u, 0x1F, rvi_u->tractive_effort)) / 256; peter1138@9738: } peter1138@6490: } peter1138@5400: } peter1138@5400: peter1138@9738: if (HasBit(u->u.rail.flags, VRF_POWEREDWAGON) && HasPowerOnRail(v->u.rail.railtype, railtype)) { peter1138@6490: total_power += RailVehInfo(u->u.rail.first_engine)->pow_wag_power; peter1138@5400: } peter1138@5400: } peter1138@5400: peter1138@6490: if (v->u.rail.cached_power != total_power || v->u.rail.cached_max_te != max_te) { rubidium@7539: /* If it has no power (no catenary), stop the train */ rubidium@7539: if (total_power == 0) v->vehstatus |= VS_STOPPED; rubidium@7539: peter1138@6490: v->u.rail.cached_power = total_power; peter1138@5400: v->u.rail.cached_max_te = max_te; peter1138@5400: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); smatz@8350: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); peter1138@5400: } peter1138@5400: } peter1138@5400: peter1138@5400: peter1138@5400: /** hackykid@1905: * Recalculates the cached weight of a train and its vehicles. Should be called each time the cargo on hackykid@1905: * the consist changes. hackykid@1905: * @param v First vehicle of the consist. hackykid@1905: */ smatz@8850: static void TrainCargoChanged(Vehicle *v) tron@2639: { peter1138@5162: uint32 weight = 0; hackykid@1905: rubidium@7492: for (Vehicle *u = v; u != NULL; u = u->Next()) { rubidium@7010: uint32 vweight = GetCargo(u->cargo_type)->weight * u->cargo.Count() * FreightWagonMult(u->cargo_type) / 16; peter1138@2602: belugas@6422: /* Vehicle weight is not added for articulated parts. */ bjarni@2676: if (!IsArticulatedPart(u)) { belugas@6422: /* vehicle weight is the sum of the weight of the vehicle and the weight of its cargo */ peter1138@6572: vweight += GetVehicleProperty(u, 0x16, RailVehInfo(u->engine_type)->weight); peter1138@9738: } peter1138@9738: peter1138@9738: /* powered wagons have extra weight added */ peter1138@9738: if (HasBit(u->u.rail.flags, VRF_POWEREDWAGON)) { peter1138@9738: vweight += RailVehInfo(u->u.rail.first_engine)->pow_wag_weight; peter1138@2602: } hackykid@1905: belugas@6422: /* consist weight is the sum of the weight of all vehicles in the consist */ hackykid@1905: weight += vweight; hackykid@1905: belugas@6422: /* store vehicle weight in cache */ hackykid@1905: u->u.rail.cached_veh_weight = vweight; tron@6150: } hackykid@1905: belugas@6422: /* store consist weight in cache */ hackykid@1905: v->u.rail.cached_weight = weight; peter1138@5400: peter1138@5400: /* Now update train power (tractive effort is dependent on weight) */ peter1138@5400: TrainPowerChanged(v); hackykid@1905: } hackykid@1905: celestar@3355: smatz@9704: /** Logs a bug in GRF and shows a warning message if this smatz@9704: * is for the first time this happened. smatz@9704: * @param u first vehicle of chain smatz@9704: */ smatz@9704: static void RailVehicleLengthChanged(const Vehicle *u) smatz@9704: { smatz@9704: /* show a warning once for each engine in whole game and once for each GRF after each game load */ smatz@9704: const Engine *engine = GetEngine(u->engine_type); smatz@9704: uint32 grfid = engine->grffile->grfid; smatz@9704: GRFConfig *grfconfig = GetGRFConfig(grfid); smatz@9704: if (GamelogGRFBugReverse(grfid, engine->internal_id) || !HasBit(grfconfig->grf_bugs, GBUG_VEH_LENGTH)) { smatz@9704: SetBit(grfconfig->grf_bugs, GBUG_VEH_LENGTH); smatz@9704: SetDParamStr(0, grfconfig->name); smatz@9704: SetDParam(1, u->engine_type); smatz@9704: ShowErrorMessage(STR_NEWGRF_BROKEN_VEHICLE_LENGTH, STR_NEWGRF_BROKEN, 0, 0); smatz@9704: smatz@9704: /* debug output */ smatz@9704: char buffer[512]; smatz@9704: smatz@9704: SetDParamStr(0, grfconfig->name); smatz@9704: GetString(buffer, STR_NEWGRF_BROKEN, lastof(buffer)); smatz@9704: DEBUG(grf, 0, "%s", buffer + 3); smatz@9704: smatz@9704: SetDParam(1, u->engine_type); smatz@9704: GetString(buffer, STR_NEWGRF_BROKEN_VEHICLE_LENGTH, lastof(buffer)); smatz@9704: DEBUG(grf, 0, "%s", buffer + 3); smatz@9704: smatz@9704: if (!_networking) _pause_game = -1; smatz@9704: } smatz@9704: } smatz@9704: smatz@9704: /** Checks if lengths of all rail vehicles are valid. If not, shows an error message. */ smatz@9704: void CheckTrainsLengths() smatz@9704: { smatz@9704: const Vehicle *v; smatz@9704: smatz@9704: FOR_ALL_VEHICLES(v) { smatz@9704: if (v->type == VEH_TRAIN && v->First() == v && !(v->vehstatus & VS_CRASHED)) { smatz@9704: for (const Vehicle *u = v, *w = v->Next(); w != NULL; u = w, w = w->Next()) { smatz@9704: if (u->u.rail.track != TRACK_BIT_DEPOT) { smatz@9704: if ((w->u.rail.track != TRACK_BIT_DEPOT && smatz@9704: max(abs(u->x_pos - w->x_pos), abs(u->y_pos - w->y_pos)) != u->u.rail.cached_veh_length) || smatz@9704: (w->u.rail.track == TRACK_BIT_DEPOT && TicksToLeaveDepot(u) <= 0)) { smatz@9704: SetDParam(0, v->index); smatz@9704: SetDParam(1, v->owner); smatz@9704: ShowErrorMessage(INVALID_STRING_ID, STR_BROKEN_VEHICLE_LENGTH, 0, 0); smatz@9705: smatz@9704: if (!_networking) _pause_game = -1; smatz@9704: } smatz@9704: } smatz@9704: } smatz@9704: } smatz@9704: } smatz@9704: } smatz@9704: celestar@3355: /** hackykid@1917: * Recalculates the cached stuff of a train. Should be called each time a vehicle is added hackykid@1917: * to/removed from the chain, and when the game is loaded. hackykid@1917: * Note: this needs to be called too for 'wagon chains' (in the depot, without an engine) hackykid@1917: * @param v First vehicle of the chain. smatz@9704: * @param same_length should length of vehicles stay the same? hackykid@1905: */ smatz@9704: void TrainConsistChanged(Vehicle *v, bool same_length) tron@2639: { smatz@8850: uint16 max_speed = UINT16_MAX; hackykid@1917: rubidium@6259: assert(v->type == VEH_TRAIN); bjarni@2676: assert(IsFrontEngine(v) || IsFreeWagon(v)); hackykid@1917: tron@6150: const RailVehicleInfo *rvi_v = RailVehInfo(v->engine_type); tron@6150: EngineID first_engine = IsFrontEngine(v) ? v->engine_type : INVALID_ENGINE; peter1138@2587: v->u.rail.cached_total_length = 0; rubidium@8236: v->u.rail.compatible_railtypes = RAILTYPES_NONE; hackykid@1905: peter1138@8178: bool train_can_tilt = true; peter1138@8178: rubidium@7492: for (Vehicle *u = v; u != NULL; u = u->Next()) { hackykid@1908: const RailVehicleInfo *rvi_u = RailVehInfo(u->engine_type); hackykid@1908: rubidium@7497: /* Check the v->first cache. */ rubidium@7497: assert(u->First() == v); peter1138@2993: belugas@6422: /* update the 'first engine' */ tron@6150: u->u.rail.first_engine = v == u ? INVALID_ENGINE : first_engine; tron@5823: u->u.rail.railtype = rvi_u->railtype; hackykid@1917: peter1138@9515: if (IsTrainEngine(u)) first_engine = u->engine_type; peter1138@9515: peter1138@9037: /* Set user defined data to its default value */ peter1138@9037: u->u.rail.user_def_data = rvi_u->user_def_data; peter1138@9037: } peter1138@9037: peter1138@9037: for (Vehicle *u = v; u != NULL; u = u->Next()) { peter1138@9037: /* Update user defined data (must be done before other properties) */ peter1138@9037: u->u.rail.user_def_data = GetVehicleProperty(u, 0x25, u->u.rail.user_def_data); peter1138@9037: } peter1138@9037: peter1138@9037: for (Vehicle *u = v; u != NULL; u = u->Next()) { peter1138@9037: const RailVehicleInfo *rvi_u = RailVehInfo(u->engine_type); peter1138@9037: peter1138@9037: if (!HasBit(EngInfo(u->engine_type)->misc_flags, EF_RAIL_TILTS)) train_can_tilt = false; peter1138@9037: peter1138@6603: /* Cache wagon override sprite group. NULL is returned if there is none */ peter1138@6603: u->u.rail.cached_override = GetWagonOverrideSpriteSet(u->engine_type, u->cargo_type, u->u.rail.first_engine); peter1138@6603: glx@7802: /* Reset color map */ glx@7802: u->colormap = PAL_NONE; glx@7802: peter1138@2595: if (rvi_u->visual_effect != 0) { peter1138@2595: u->u.rail.cached_vis_effect = rvi_u->visual_effect; peter1138@2595: } else { bjarni@2676: if (IsTrainWagon(u) || IsArticulatedPart(u)) { belugas@6422: /* Wagons and articulated parts have no effect by default */ peter1138@2595: u->u.rail.cached_vis_effect = 0x40; peter1138@2595: } else if (rvi_u->engclass == 0) { belugas@6422: /* Steam is offset by -4 units */ peter1138@2595: u->u.rail.cached_vis_effect = 4; peter1138@2595: } else { belugas@6422: /* Diesel fumes and sparks come from the centre */ peter1138@2595: u->u.rail.cached_vis_effect = 8; peter1138@2595: } peter1138@2595: } peter1138@2595: peter1138@9738: /* Check powered wagon / visual effect callback */ peter1138@9738: if (HasBit(EngInfo(u->engine_type)->callbackmask, CBM_TRAIN_WAGON_POWER)) { peter1138@9738: uint16 callback = GetVehicleCallback(CBID_TRAIN_WAGON_POWER, 0, 0, u->engine_type, u); peter1138@9738: peter1138@9738: if (callback != CALLBACK_FAILED) u->u.rail.cached_vis_effect = GB(callback, 0, 8); peter1138@9738: } peter1138@9738: peter1138@9738: if (rvi_v->pow_wag_power != 0 && rvi_u->railveh_type == RAILVEH_WAGON && peter1138@9738: UsesWagonOverride(u) && !HasBit(u->u.rail.cached_vis_effect, 7)) { peter1138@9738: /* wagon is powered */ peter1138@9738: SetBit(u->u.rail.flags, VRF_POWEREDWAGON); // cache 'powered' status peter1138@9738: } else { peter1138@9738: ClrBit(u->u.rail.flags, VRF_POWEREDWAGON); peter1138@9738: } peter1138@9738: bjarni@2676: if (!IsArticulatedPart(u)) { celestar@3355: /* Do not count powered wagons for the compatible railtypes, as wagons always celestar@3355: have railtype normal */ celestar@3355: if (rvi_u->power > 0) { celestar@3355: v->u.rail.compatible_railtypes |= GetRailTypeInfo(u->u.rail.railtype)->powered_railtypes; celestar@3355: } celestar@3355: KUDr@5116: /* Some electric engines can be allowed to run on normal rail. It happens to all KUDr@5116: * existing electric engines when elrails are disabled and then re-enabled */ skidd13@7928: if (HasBit(u->u.rail.flags, VRF_EL_ENGINE_ALLOWED_NORMAL_RAIL)) { KUDr@5116: u->u.rail.railtype = RAILTYPE_RAIL; rubidium@8236: u->u.rail.compatible_railtypes |= RAILTYPES_RAIL; KUDr@5116: } KUDr@5116: belugas@6422: /* max speed is the minimum of the speed limits of all vehicles in the consist */ rubidium@9413: if ((rvi_u->railveh_type != RAILVEH_WAGON || _settings_game.vehicle.wagon_speed_limits) && !UsesWagonOverride(u)) { peter1138@6490: uint16 speed = GetVehicleProperty(u, 0x09, rvi_u->max_speed); peter1138@6490: if (speed != 0) max_speed = min(speed, max_speed); peter1138@6490: } hackykid@1908: } hackykid@1908: peter1138@6644: if (u->cargo_type == rvi_u->cargo_type && u->cargo_subtype == 0) { peter1138@6644: /* Set cargo capacity if we've not been refitted */ peter1138@6644: u->cargo_cap = GetVehicleProperty(u, 0x14, rvi_u->capacity); peter1138@6644: } peter1138@6608: belugas@6422: /* check the vehicle length (callback) */ tron@6150: uint16 veh_len = CALLBACK_FAILED; skidd13@7928: if (HasBit(EngInfo(u->engine_type)->callbackmask, CBM_VEHICLE_LENGTH)) { rubidium@7215: veh_len = GetVehicleCallback(CBID_VEHICLE_LENGTH, 0, 0, u->engine_type, u); peter1138@3956: } peter1138@3956: if (veh_len == CALLBACK_FAILED) veh_len = rvi_u->shorten_factor; smatz@9704: veh_len = 8 - Clamp(veh_len, 0, u->Next() == NULL ? 7 : 5); // the clamp on vehicles not the last in chain is stricter, as too short wagons can break the 'follow next vehicle' code smatz@9704: smatz@9704: /* verify length hasn't changed */ smatz@9704: if (same_length && veh_len != u->u.rail.cached_veh_length) RailVehicleLengthChanged(u); smatz@9704: smatz@9704: /* update vehicle length? */ smatz@9704: if (!same_length) u->u.rail.cached_veh_length = veh_len; smatz@9704: peter1138@2587: v->u.rail.cached_total_length += u->u.rail.cached_veh_length; tron@6150: } hackykid@1905: belugas@6422: /* store consist weight/max speed in cache */ hackykid@1905: v->u.rail.cached_max_speed = max_speed; peter1138@8178: v->u.rail.cached_tilt = train_can_tilt; celestar@3355: belugas@6422: /* recalculate cached weights and power too (we do this *after* the rest, so it is known which wagons are powered and need extra weight added) */ hackykid@1908: TrainCargoChanged(v); glx@8704: glx@8706: if (IsFrontEngine(v)) { glx@8706: UpdateTrainAcceleration(v); glx@8706: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); glx@8706: } hackykid@1905: } matthijs@1247: celestar@1179: enum AccelType { celestar@1179: AM_ACCEL, celestar@1179: AM_BRAKE celestar@1179: }; celestar@1179: belugas@6422: /** new acceleration*/ celestar@1179: static int GetTrainAcceleration(Vehicle *v, bool mode) celestar@1179: { smatz@8850: static const int absolute_max_speed = UINT16_MAX; peter1138@8169: int max_speed = absolute_max_speed; rubidium@7561: int speed = v->cur_speed * 10 / 16; // km-ish/h -> mp/h tron@6150: int curvecount[2] = {0, 0}; tron@6150: belugas@6422: /*first find the curve speed limit */ tron@6150: int numcurve = 0; tron@6150: int sum = 0; celestar@1179: int pos = 0; celestar@1179: int lastpos = -1; rubidium@7492: for (const Vehicle *u = v; u->Next() != NULL; u = u->Next(), pos++) { peter1138@8175: Direction this_dir = u->direction; peter1138@8175: Direction next_dir = u->Next()->direction; peter1138@8175: peter1138@8175: DirDiff dirdiff = DirDifference(this_dir, next_dir); peter1138@8175: if (dirdiff == DIRDIFF_SAME) continue; peter1138@8175: peter1138@8175: if (dirdiff == DIRDIFF_45LEFT) curvecount[0]++; peter1138@8175: if (dirdiff == DIRDIFF_45RIGHT) curvecount[1]++; peter1138@8175: if (dirdiff == DIRDIFF_45LEFT || dirdiff == DIRDIFF_45RIGHT) { peter1138@8175: if (lastpos != -1) { peter1138@8175: numcurve++; peter1138@8175: sum += pos - lastpos; peter1138@8175: if (pos - lastpos == 1) { peter1138@8175: max_speed = 88; celestar@1179: } celestar@1179: } peter1138@8175: lastpos = pos; celestar@1179: } celestar@1179: belugas@6422: /*if we have a 90 degree turn, fix the speed limit to 60 */ peter1138@8175: if (dirdiff == DIRDIFF_90LEFT || dirdiff == DIRDIFF_90RIGHT) { celestar@1179: max_speed = 61; celestar@1179: } celestar@1179: } celestar@1179: tron@1472: if ((curvecount[0] != 0 || curvecount[1] != 0) && max_speed > 88) { celestar@1179: int total = curvecount[0] + curvecount[1]; tron@1472: celestar@1179: if (curvecount[0] == 1 && curvecount[1] == 1) { peter1138@8169: max_speed = absolute_max_speed; celestar@1179: } else if (total > 1) { peter1138@8175: if (numcurve > 0) sum /= numcurve; skidd13@7922: max_speed = 232 - (13 - Clamp(sum, 1, 12)) * (13 - Clamp(sum, 1, 12)); celestar@1179: } celestar@1179: } celestar@1179: peter1138@8169: if (max_speed != absolute_max_speed) { peter1138@8169: /* Apply the engine's rail type curve speed advantage, if it slowed by curves */ peter1138@8169: const RailtypeInfo *rti = GetRailTypeInfo(v->u.rail.railtype); peter1138@8169: max_speed += (max_speed / 2) * rti->curve_speed; peter1138@8178: peter1138@8178: if (v->u.rail.cached_tilt) { peter1138@8178: /* Apply max_speed bonus of 20% for a tilting train */ peter1138@8178: max_speed += max_speed / 5; peter1138@8178: } peter1138@8169: } celestar@1179: bjarni@2676: if (IsTileType(v->tile, MP_STATION) && IsFrontEngine(v)) { rubidium@8832: if (v->current_order.ShouldStopAtStation(v, GetStationIndex(v->tile))) { celestar@5998: int station_length = GetStationByTile(v->tile)->GetPlatformLength(v->tile, DirToDiagDir(v->direction)); peter1138@8170: peter1138@8170: int st_max_speed = 120; peter1138@8170: peter1138@8170: int delta_v = v->cur_speed / (station_length + 1); peter1138@8170: if (v->max_speed > (v->cur_speed - delta_v)) { peter1138@8170: st_max_speed = v->cur_speed - (delta_v / 10); peter1138@8170: } peter1138@8170: peter1138@8170: st_max_speed = max(st_max_speed, 25 * station_length); peter1138@8170: max_speed = min(max_speed, st_max_speed); celestar@1179: } celestar@1179: } celestar@1179: tron@6150: int mass = v->u.rail.cached_weight; tron@6150: int power = v->u.rail.cached_power * 746; hackykid@1905: max_speed = min(max_speed, v->u.rail.cached_max_speed); hackykid@1905: tron@6150: int num = 0; //number of vehicles, change this into the number of axles later tron@6150: int incl = 0; tron@6150: int drag_coeff = 20; //[1e-4] rubidium@7492: for (const Vehicle *u = v; u != NULL; u = u->Next()) { celestar@1179: num++; celestar@1179: drag_coeff += 3; celestar@1179: rubidium@5993: if (u->u.rail.track == TRACK_BIT_DEPOT) max_speed = min(max_speed, 61); celestar@1179: skidd13@7928: if (HasBit(u->u.rail.flags, VRF_GOINGUP)) { rubidium@4434: incl += u->u.rail.cached_veh_weight * 60; //3% slope, quite a bit actually skidd13@7928: } else if (HasBit(u->u.rail.flags, VRF_GOINGDOWN)) { hackykid@1905: incl -= u->u.rail.cached_veh_weight * 60; celestar@1179: } celestar@1179: } celestar@1179: celestar@1179: v->max_speed = max_speed; celestar@1179: tron@6150: const int area = 120; tron@6150: const int friction = 35; //[1e-3] tron@6150: int resistance; tron@2519: if (v->u.rail.railtype != RAILTYPE_MAGLEV) { celestar@1179: resistance = 13 * mass / 10; celestar@1179: resistance += 60 * num; celestar@1179: resistance += friction * mass * speed / 1000; celestar@1179: resistance += (area * drag_coeff * speed * speed) / 10000; tron@3017: } else { celestar@1179: resistance = (area * (drag_coeff / 2) * speed * speed) / 10000; tron@3017: } celestar@1179: resistance += incl; celestar@1179: resistance *= 4; //[N] celestar@1179: peter1138@5277: /* Due to the mph to m/s conversion below, at speeds below 3 mph the force is peter1138@5277: * actually double the train's power */ tron@6150: const int max_te = v->u.rail.cached_max_te; // [N] tron@6150: int force; peter1138@5277: if (speed > 2) { celestar@1179: switch (v->u.rail.railtype) { tron@2519: case RAILTYPE_RAIL: celestar@3355: case RAILTYPE_ELECTRIC: tron@2519: case RAILTYPE_MONO: celestar@1179: force = power / speed; //[N] celestar@1179: force *= 22; celestar@1179: force /= 10; peter1138@5400: if (mode == AM_ACCEL && force > max_te) force = max_te; tron@1472: break; tron@1472: tron@6150: default: NOT_REACHED(); tron@2519: case RAILTYPE_MAGLEV: celestar@1179: force = power / 25; tron@1472: break; celestar@1179: } tron@1472: } else { belugas@6422: /* "kickoff" acceleration */ peter1138@5400: force = (mode == AM_ACCEL && v->u.rail.railtype != RAILTYPE_MAGLEV) ? min(max_te, power) : power; peter1138@5400: force = max(force, (mass * 8) + resistance); tron@1472: } celestar@1179: celestar@1179: if (force <= 0) force = 10000; celestar@1179: tron@2519: if (v->u.rail.railtype != RAILTYPE_MAGLEV) force = min(force, mass * 10 * 200); celestar@1179: celestar@1179: if (mode == AM_ACCEL) { tron@1684: return (force - resistance) / (mass * 4); celestar@1179: } else { peter1138@5279: return min((-force - resistance) / (mass * 4), -10000 / (mass * 4)); celestar@1179: } celestar@1179: } celestar@1179: smatz@8850: void UpdateTrainAcceleration(Vehicle *v) truelight@0: { bjarni@2676: assert(IsFrontEngine(v)); truelight@0: hackykid@1905: v->max_speed = v->u.rail.cached_max_speed; truelight@0: tron@6150: uint power = v->u.rail.cached_power; tron@6150: uint weight = v->u.rail.cached_weight; truelight@0: assert(weight != 0); skidd13@7922: v->acceleration = Clamp(power / weight * 4, 1, 255); truelight@0: } truelight@0: peter1138@9022: SpriteID Train::GetImage(Direction direction) const truelight@0: { peter1138@9022: uint8 spritenum = this->spritenum; peter1138@9022: SpriteID sprite; truelight@0: skidd13@7928: if (HasBit(this->u.rail.flags, VRF_REVERSE_DIRECTION)) direction = ReverseDir(direction); bjarni@3256: peter1138@9022: if (is_custom_sprite(spritenum)) { peter1138@9022: sprite = GetCustomVehicleSprite(this, (Direction)(direction + 4 * IS_CUSTOM_SECONDHEAD_SPRITE(spritenum))); peter1138@9022: if (sprite != 0) return sprite; peter1138@9022: peter1138@9070: spritenum = GetEngine(this->engine_type)->image_index; truelight@0: } truelight@193: peter1138@9022: sprite = _engine_sprite_base[spritenum] + ((direction + _engine_sprite_add[spritenum]) & _engine_sprite_and[spritenum]); peter1138@9022: peter1138@9022: if (this->cargo.Count() >= this->cargo_cap / 2U) sprite += _wagon_full_adder[spritenum]; peter1138@9022: peter1138@9022: return sprite; peter1138@9022: } peter1138@9022: peter1138@9022: static SpriteID GetRailIcon(EngineID engine, bool rear_head, int &y) peter1138@9022: { peter1138@9022: Direction dir = rear_head ? DIR_E : DIR_W; peter1138@9022: uint8 spritenum = RailVehInfo(engine)->image_index; peter1138@9022: peter1138@9022: if (is_custom_sprite(spritenum)) { peter1138@9022: SpriteID sprite = GetCustomVehicleIcon(engine, dir); peter1138@9022: if (sprite != 0) { peter1138@9022: y += _traininfo_vehicle_pitch; // TODO Make this per-GRF peter1138@9022: return sprite; peter1138@9022: } peter1138@9022: peter1138@9070: spritenum = GetEngine(engine)->image_index; peter1138@9022: } peter1138@9022: peter1138@9022: if (rear_head) spritenum++; peter1138@9022: peter1138@9022: return ((6 + _engine_sprite_add[spritenum]) & _engine_sprite_and[spritenum]) + _engine_sprite_base[spritenum]; truelight@0: } truelight@0: peter1138@5668: void DrawTrainEngine(int x, int y, EngineID engine, SpriteID pal) truelight@0: { peter1138@9022: if (RailVehInfo(engine)->railveh_type == RAILVEH_MULTIHEAD) { peter1138@9022: int yf = y; peter1138@9022: int yr = y; peter1138@9022: peter1138@9022: SpriteID spritef = GetRailIcon(engine, false, yf); peter1138@9022: SpriteID spriter = GetRailIcon(engine, true, yr); peter1138@9022: DrawSprite(spritef, pal, x - 14, yf); peter1138@9022: DrawSprite(spriter, pal, x + 15, yr); peter1138@9022: } else { peter1138@9022: SpriteID sprite = GetRailIcon(engine, false, y); peter1138@9022: DrawSprite(sprite, pal, x, y); truelight@0: } truelight@0: } truelight@0: rubidium@6943: static CommandCost CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags) truelight@0: { tron@6150: const RailVehicleInfo *rvi = RailVehInfo(engine); peter1138@9923: CommandCost value(EXPENSES_NEW_VEHICLES, (GetEngineProperty(engine, 0x17, rvi->cost_factor) * _price.build_railwagon) >> 8); tron@6150: rubidium@7595: uint num_vehicles = 1 + CountArticulatedParts(engine, false); peter1138@2602: truelight@0: if (!(flags & DC_QUERY_COST)) { bjarni@8781: /* Check that the wagon can drive on the track in question */ bjarni@8781: if (!IsCompatibleRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR; bjarni@8781: rubidium@7595: /* Allow for the wagon and the articulated parts, plus one to "terminate" the list. */ smatz@9488: Vehicle **vl = AllocaM(Vehicle*, num_vehicles + 1); rubidium@7595: memset(vl, 0, sizeof(*vl) * (num_vehicles + 1)); peter1138@4831: rubidium@7398: if (!Vehicle::AllocateList(vl, num_vehicles)) peter1138@2602: return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); truelight@0: truelight@0: if (flags & DC_EXEC) { tron@6150: Vehicle *v = vl[0]; tron@2639: v->spritenum = rvi->image_index; truelight@0: tron@6150: Vehicle *u = NULL; tron@6150: tron@6150: Vehicle *w; truelight@919: FOR_ALL_VEHICLES(w) { rubidium@6259: if (w->type == VEH_TRAIN && w->tile == tile && rubidium@7696: IsFreeWagon(w) && w->engine_type == engine && rubidium@7696: !HASBITS(w->vehstatus, VS_CRASHED)) { /// do not connect new wagon with crashed/flooded consists truelight@919: u = GetLastVehicleInChain(w); truelight@0: break; truelight@0: } truelight@0: } truelight@0: rubidium@7782: v = new (v) Train(); truelight@0: v->engine_type = engine; truelight@0: tron@6150: DiagDirection dir = GetRailDepotDirection(tile); tron@2150: tron@3153: v->direction = DiagDirToDir(dir); tron@1986: v->tile = tile; truelight@193: tron@6150: int x = TileX(tile) * TILE_SIZE | _vehicle_initial_x_fract[dir]; tron@6150: int y = TileY(tile) * TILE_SIZE | _vehicle_initial_y_fract[dir]; truelight@0: truelight@0: v->x_pos = x; truelight@0: v->y_pos = y; tron@6150: v->z_pos = GetSlopeZ(x, y); rubidium@10207: v->owner = _current_company; rubidium@5993: v->u.rail.track = TRACK_BIT_DEPOT; truelight@0: v->vehstatus = VS_HIDDEN | VS_DEFPAL; truelight@0: smatz@8850: // v->subtype = 0; bjarni@2676: SetTrainWagon(v); maedhros@6771: truelight@0: if (u != NULL) { rubidium@7493: u->SetNext(v); bjarni@2676: } else { bjarni@2676: SetFreeWagon(v); bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); truelight@0: } truelight@0: truelight@0: v->cargo_type = rvi->cargo_type; smatz@8850: // v->cargo_subtype = 0; truelight@0: v->cargo_cap = rvi->capacity; rubidium@6950: v->value = value.GetCost(); truelight@0: // v->day_counter = 0; truelight@0: tron@5823: v->u.rail.railtype = rvi->railtype; truelight@193: rubidium@4329: v->build_year = _cur_year; truelight@0: v->cur_image = 0xAC2; peter1138@2804: v->random_bits = VehicleRandomBits(); truelight@193: rubidium@6643: v->group_id = DEFAULT_GROUP; rubidium@6643: maedhros@6857: AddArticulatedParts(vl, VEH_TRAIN); peter1138@2602: bjarni@2564: _new_vehicle_id = v->index; truelight@0: truelight@0: VehiclePositionChanged(v); smatz@9704: TrainConsistChanged(v->First(), false); rubidium@7497: UpdateTrainGroupID(v->First()); truelight@0: truelight@0: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); rubidium@10207: if (IsLocalCompany()) { bjarni@7425: InvalidateAutoreplaceWindow(v->engine_type, v->group_id); // updates the replace Train window bjarni@2970: } rubidium@10207: GetCompany(_current_company)->num_engines[engine]++; truelight@0: } truelight@0: } truelight@0: rubidium@8230: return value; truelight@0: } truelight@0: belugas@6422: /** Move all free vehicles in the depot to the train */ smatz@8850: static void NormalizeTrainVehInDepot(const Vehicle *u) truelight@0: { smatz@8850: const Vehicle *v; tron@1472: truelight@0: FOR_ALL_VEHICLES(v) { rubidium@6259: if (v->type == VEH_TRAIN && IsFreeWagon(v) && truelight@0: v->tile == u->tile && rubidium@5993: v->u.rail.track == TRACK_BIT_DEPOT) { tron@3491: if (CmdFailed(DoCommand(0, v->index | (u->index << 16), 1, DC_EXEC, bjarni@2676: CMD_MOVE_RAIL_VEHICLE))) truelight@0: break; truelight@0: } truelight@0: } truelight@0: } truelight@0: smatz@8850: static CommandCost EstimateTrainCost(EngineID engine, const RailVehicleInfo *rvi) truelight@0: { peter1138@9923: return CommandCost(EXPENSES_NEW_VEHICLES, GetEngineProperty(engine, 0x17, rvi->cost_factor) * (_price.build_railvehicle >> 3) >> 5); truelight@0: } truelight@0: smatz@8850: static void AddRearEngineToMultiheadedTrain(Vehicle *v, Vehicle *u, bool building) bjarni@1060: { rubidium@7497: u = new (u) Train(); bjarni@1060: u->direction = v->direction; bjarni@1060: u->owner = v->owner; bjarni@1060: u->tile = v->tile; bjarni@1060: u->x_pos = v->x_pos; bjarni@1060: u->y_pos = v->y_pos; bjarni@1060: u->z_pos = v->z_pos; rubidium@5993: u->u.rail.track = TRACK_BIT_DEPOT; bjarni@1060: u->vehstatus = v->vehstatus & ~VS_STOPPED; smatz@8850: // u->subtype = 0; bjarni@2676: SetMultiheaded(u); bjarni@1060: u->spritenum = v->spritenum + 1; bjarni@1060: u->cargo_type = v->cargo_type; peter1138@3870: u->cargo_subtype = v->cargo_subtype; bjarni@1060: u->cargo_cap = v->cargo_cap; bjarni@1060: u->u.rail.railtype = v->u.rail.railtype; rubidium@7493: if (building) v->SetNext(u); bjarni@1060: u->engine_type = v->engine_type; bjarni@1060: u->build_year = v->build_year; tron@1472: if (building) v->value >>= 1; tron@1472: u->value = v->value; bjarni@1060: u->cur_image = 0xAC2; peter1138@2804: u->random_bits = VehicleRandomBits(); bjarni@1060: VehiclePositionChanged(u); bjarni@1060: } bjarni@1060: Darkvater@1784: /** Build a railroad vehicle. tron@3491: * @param tile tile of the depot where rail-vehicle is built belugas@6422: * @param flags type of operation Darkvater@1784: * @param p1 engine type id smatz@9628: * @param p2 bit 1 prevents any free cars from being added to the train truelight@0: */ rubidium@6943: CommandCost CmdBuildRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { rubidium@10207: /* Check if the engine-type is valid (for the company) */ rubidium@10207: if (!IsEngineBuildable(p1, VEH_TRAIN, _current_company)) return_cmd_error(STR_RAIL_VEHICLE_NOT_AVAILABLE); bjarni@1196: Darkvater@1784: /* Check if the train is actually being built in a depot belonging rubidium@10207: * to the company. Doesn't matter if only the cost is queried */ pasky@1443: if (!(flags & DC_QUERY_COST)) { smatz@8961: if (!IsRailDepotTile(tile)) return CMD_ERROR; rubidium@10207: if (!IsTileOwner(tile, _current_company)) return CMD_ERROR; pasky@1443: } bjarni@1221: tron@6150: const RailVehicleInfo *rvi = RailVehInfo(p1); bjarni@2244: belugas@5868: if (rvi->railveh_type == RAILVEH_WAGON) return CmdBuildRailWagon(p1, tile, flags); truelight@0: rubidium@6943: CommandCost value = EstimateTrainCost(p1, rvi); tron@6150: tron@6150: uint num_vehicles = tron@6150: (rvi->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1) + rubidium@7595: CountArticulatedParts(p1, false); truelight@0: truelight@0: if (!(flags & DC_QUERY_COST)) { bjarni@8781: /* Check if depot and new engine uses the same kind of tracks * bjarni@8781: * We need to see if the engine got power on the tile to avoid eletric engines in non-electric depots */ bjarni@8781: if (!HasPowerOnRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR; bjarni@8781: rubidium@7595: /* Allow for the dual-heads and the articulated parts, plus one to "terminate" the list. */ smatz@9488: Vehicle **vl = AllocaM(Vehicle*, num_vehicles + 1); rubidium@7595: memset(vl, 0, sizeof(*vl) * (num_vehicles + 1)); peter1138@4831: smatz@8850: if (!Vehicle::AllocateList(vl, num_vehicles)) { truelight@0: return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); smatz@8850: } truelight@0: tron@6150: Vehicle *v = vl[0]; tron@6150: smatz@9628: UnitID unit_num = (flags & DC_AUTOREPLACE) ? 0 : GetFreeUnitNumber(VEH_TRAIN); rubidium@9413: if (unit_num > _settings_game.vehicle.max_trains) bjarni@3816: return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); bjarni@3816: truelight@0: if (flags & DC_EXEC) { tron@3491: DiagDirection dir = GetRailDepotDirection(tile); tron@3491: int x = TileX(tile) * TILE_SIZE + _vehicle_initial_x_fract[dir]; tron@3491: int y = TileY(tile) * TILE_SIZE + _vehicle_initial_y_fract[dir]; tron@2150: maedhros@7783: v = new (v) Train(); truelight@0: v->unitnumber = unit_num; tron@3153: v->direction = DiagDirToDir(dir); tron@1986: v->tile = tile; rubidium@10207: v->owner = _current_company; tron@3491: v->x_pos = x; tron@3491: v->y_pos = y; tron@6150: v->z_pos = GetSlopeZ(x, y); smatz@8850: // v->running_ticks = 0; rubidium@5993: v->u.rail.track = TRACK_BIT_DEPOT; truelight@0: v->vehstatus = VS_HIDDEN | VS_STOPPED | VS_DEFPAL; truelight@0: v->spritenum = rvi->image_index; truelight@0: v->cargo_type = rvi->cargo_type; smatz@8850: // v->cargo_subtype = 0; truelight@0: v->cargo_cap = rvi->capacity; truelight@0: v->max_speed = rvi->max_speed; rubidium@6950: v->value = value.GetCost(); truelight@1266: v->last_station_visited = INVALID_STATION; smatz@8850: // v->dest_tile = 0; truelight@193: tron@2477: v->engine_type = p1; truelight@0: tron@5823: const Engine *e = GetEngine(p1); truelight@0: v->reliability = e->reliability; truelight@0: v->reliability_spd_dec = e->reliability_spd_dec; truelight@0: v->max_age = e->lifelength * 366; truelight@193: peter1138@8258: v->name = NULL; tron@5823: v->u.rail.railtype = rvi->railtype; bjarni@2564: _new_vehicle_id = v->index; truelight@193: rubidium@9413: v->service_interval = _settings_game.vehicle.servint_trains; truelight@0: v->date_of_last_service = _date; rubidium@4329: v->build_year = _cur_year; truelight@0: v->cur_image = 0xAC2; peter1138@2804: v->random_bits = VehicleRandomBits(); truelight@0: smatz@8850: // v->vehicle_flags = 0; skidd13@7931: if (e->flags & ENGINE_EXCLUSIVE_PREVIEW) SetBit(v->vehicle_flags, VF_BUILT_AS_PROTOTYPE); maedhros@6176: rubidium@6643: v->group_id = DEFAULT_GROUP; rubidium@6643: smatz@8850: // v->subtype = 0; bjarni@2676: SetFrontEngine(v); bjarni@2676: SetTrainEngine(v); bjarni@2676: truelight@0: VehiclePositionChanged(v); truelight@0: belugas@5868: if (rvi->railveh_type == RAILVEH_MULTIHEAD) { bjarni@2676: SetMultiheaded(v); peter1138@2602: AddRearEngineToMultiheadedTrain(vl[0], vl[1], true); bjarni@2676: /* Now we need to link the front and rear engines together bjarni@2676: * other_multiheaded_part is the pointer that links to the other half of the engine bjarni@2676: * vl[0] is the front and vl[1] is the rear bjarni@2676: */ bjarni@2676: vl[0]->u.rail.other_multiheaded_part = vl[1]; bjarni@2676: vl[1]->u.rail.other_multiheaded_part = vl[0]; peter1138@2602: } else { maedhros@6857: AddArticulatedParts(vl, VEH_TRAIN); bjarni@2244: } truelight@0: smatz@9704: TrainConsistChanged(v, false); rubidium@6643: UpdateTrainGroupID(v); bjarni@2244: smatz@9628: if (!HasBit(p2, 1) && !(flags & DC_AUTOREPLACE)) { // check if the cars should be added to the new vehicle bjarni@2244: NormalizeTrainVehInDepot(v); bjarni@2244: } truelight@0: bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); rubidium@9297: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); truelight@0: InvalidateWindow(WC_COMPANY, v->owner); rubidium@10207: if (IsLocalCompany()) { bjarni@7425: InvalidateAutoreplaceWindow(v->engine_type, v->group_id); // updates the replace Train window smatz@8850: } bjarni@5944: rubidium@10207: GetCompany(_current_company)->num_engines[p1]++; truelight@0: } truelight@0: } bjarni@1128: truelight@193: return value; truelight@0: } truelight@0: truelight@0: matthijs@1942: /* Check if all the wagons of the given train are in a depot, returns the tron@3183: * number of cars (including loco) then. If not it returns -1 */ bjarni@4648: int CheckTrainInDepot(const Vehicle *v, bool needs_to_be_stopped) truelight@0: { truelight@0: TileIndex tile = v->tile; truelight@193: truelight@0: /* check if stopped in a depot */ smatz@8961: if (!IsRailDepotTile(tile) || v->cur_speed != 0) return -1; truelight@0: tron@6150: int count = 0; rubidium@7492: for (; v != NULL; v = v->Next()) { peter1138@2844: /* This count is used by the depot code to determine the number of engines peter1138@2844: * in the consist. Exclude articulated parts so that autoreplacing to bjarni@3986: * engines with more articulated parts than before works correctly. bjarni@3985: * bjarni@3985: * Also skip counting rear ends of multiheaded engines */ bjarni@7526: if (!IsArticulatedPart(v) && !IsRearDualheaded(v)) count++; rubidium@5993: if (v->u.rail.track != TRACK_BIT_DEPOT || v->tile != tile || bjarni@4529: (IsFrontEngine(v) && needs_to_be_stopped && !(v->vehstatus & VS_STOPPED))) { tron@1472: return -1; tron@1472: } tron@1472: } truelight@0: truelight@0: return count; truelight@0: } truelight@0: bjarni@4529: /* Used to check if the train is inside the depot and verifying that the VS_STOPPED flag is set */ rubidium@5587: int CheckTrainStoppedInDepot(const Vehicle *v) bjarni@4529: { bjarni@4529: return CheckTrainInDepot(v, true); bjarni@4529: } bjarni@4529: bjarni@4529: /* Used to check if the train is inside the depot, but not checking the VS_STOPPED flag */ bjarni@4529: inline bool CheckTrainIsInsideDepot(const Vehicle *v) bjarni@4529: { tron@6150: return CheckTrainInDepot(v, false) > 0; bjarni@4529: } bjarni@4529: peter1138@2602: /** peter1138@2602: * Unlink a rail wagon from the consist. peter1138@2602: * @param v Vehicle to remove. peter1138@2602: * @param first The first vehicle of the consist. peter1138@2602: * @return The first vehicle of the consist. peter1138@2602: */ truelight@0: static Vehicle *UnlinkWagon(Vehicle *v, Vehicle *first) truelight@0: { belugas@6422: /* unlinking the first vehicle of the chain? */ truelight@0: if (v == first) { peter1138@2602: v = GetNextVehicle(v); tron@1472: if (v == NULL) return NULL; Darkvater@1766: bjarni@2676: if (IsTrainWagon(v)) SetFreeWagon(v); bjarni@2676: truelight@0: return v; truelight@0: } Darkvater@1766: tron@6150: Vehicle *u; peter1138@2602: for (u = first; GetNextVehicle(u) != v; u = GetNextVehicle(u)) {} rubidium@7493: GetLastEnginePart(u)->SetNext(GetNextVehicle(v)); Darkvater@1766: return first; truelight@0: } truelight@0: tron@1472: static Vehicle *FindGoodVehiclePos(const Vehicle *src) truelight@0: { truelight@0: Vehicle *dst; tron@2477: EngineID eng = src->engine_type; truelight@0: TileIndex tile = src->tile; truelight@0: truelight@0: FOR_ALL_VEHICLES(dst) { rubidium@7696: if (dst->type == VEH_TRAIN && IsFreeWagon(dst) && dst->tile == tile && !HASBITS(dst->vehstatus, VS_CRASHED)) { belugas@6422: /* check so all vehicles in the line have the same engine. */ truelight@0: Vehicle *v = dst; tron@1472: truelight@0: while (v->engine_type == eng) { rubidium@7492: v = v->Next(); tron@1472: if (v == NULL) return dst; truelight@0: } truelight@0: } truelight@0: } truelight@0: truelight@0: return NULL; truelight@0: } truelight@0: bjarni@2676: /* bjarni@2676: * add a vehicle v behind vehicle dest bjarni@2676: * use this function since it sets flags as needed bjarni@2676: */ bjarni@2676: static void AddWagonToConsist(Vehicle *v, Vehicle *dest) bjarni@2676: { rubidium@7497: UnlinkWagon(v, v->First()); bjarni@2676: if (dest == NULL) return; bjarni@2676: rubidium@7497: Vehicle *next = dest->Next(); glx@8007: v->SetNext(NULL); rubidium@7492: dest->SetNext(v); rubidium@7497: v->SetNext(next); bjarni@2676: ClearFreeWagon(v); bjarni@2676: ClearFrontEngine(v); bjarni@2676: } bjarni@2676: bjarni@2676: /* bjarni@2676: * move around on the train so rear engines are placed correctly according to the other engines bjarni@2676: * always call with the front engine bjarni@2676: */ bjarni@2676: static void NormaliseTrainConsist(Vehicle *v) bjarni@2676: { bjarni@2676: if (IsFreeWagon(v)) return; bjarni@2676: bjarni@2676: assert(IsFrontEngine(v)); bjarni@2676: tron@2952: for (; v != NULL; v = GetNextVehicle(v)) { bjarni@2676: if (!IsMultiheaded(v) || !IsTrainEngine(v)) continue; bjarni@2676: bjarni@2676: /* make sure that there are no free cars before next engine */ tron@6150: Vehicle *u; rubidium@7492: for (u = v; u->Next() != NULL && !IsTrainEngine(u->Next()); u = u->Next()) {} bjarni@2676: bjarni@2676: if (u == v->u.rail.other_multiheaded_part) continue; bjarni@2676: AddWagonToConsist(v->u.rail.other_multiheaded_part, u); bjarni@2676: } bjarni@2676: } bjarni@2676: Darkvater@1784: /** Move a rail vehicle around inside the depot. tron@3491: * @param tile unused belugas@6422: * @param flags type of operation frosch@9928: * Note: DC_AUTOREPLACE is set when autoreplace tries to undo its modifications or moves vehicles to temporary locations inside the depot. Darkvater@1784: * @param p1 various bitstuffed elements tron@2639: * - p1 (bit 0 - 15) source vehicle index tron@2639: * - p1 (bit 16 - 31) what wagon to put the source wagon AFTER, XXX - INVALID_VEHICLE to make a new line Darkvater@1784: * @param p2 (bit 0) move all vehicles following the source vehicle truelight@0: */ rubidium@6943: CommandCost CmdMoveRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { tron@2484: VehicleID s = GB(p1, 0, 16); tron@2484: VehicleID d = GB(p1, 16, 16); truelight@0: truelight@4352: if (!IsValidVehicleID(s)) return CMD_ERROR; tron@2484: tron@6150: Vehicle *src = GetVehicle(s); bjarni@1237: rubidium@6259: if (src->type != VEH_TRAIN || !CheckOwnership(src->owner)) return CMD_ERROR; truelight@0: rubidium@7696: /* Do not allow moving crashed vehicles inside the depot, it is likely to cause asserts later */ rubidium@7696: if (HASBITS(src->vehstatus, VS_CRASHED)) return CMD_ERROR; rubidium@7696: belugas@6422: /* if nothing is selected as destination, try and find a matching vehicle to drag to. */ tron@6150: Vehicle *dst; tron@2484: if (d == INVALID_VEHICLE) { Darkvater@4198: dst = IsTrainEngine(src) ? NULL : FindGoodVehiclePos(src); truelight@0: } else { tron@6136: if (!IsValidVehicleID(d)) return CMD_ERROR; tron@2484: dst = GetVehicle(d); rubidium@6259: if (dst->type != VEH_TRAIN || !CheckOwnership(dst->owner)) return CMD_ERROR; rubidium@7696: rubidium@7696: /* Do not allow appending to crashed vehicles, too */ rubidium@7696: if (HASBITS(dst->vehstatus, VS_CRASHED)) return CMD_ERROR; truelight@0: } truelight@0: belugas@6422: /* if an articulated part is being handled, deal with its parent vehicle */ rubidium@7497: while (IsArticulatedPart(src)) src = src->Previous(); peter1138@2602: if (dst != NULL) { rubidium@7497: while (IsArticulatedPart(dst)) dst = dst->Previous(); peter1138@2602: } peter1138@2602: belugas@6422: /* don't move the same vehicle.. */ rubidium@6950: if (src == dst) return CommandCost(); truelight@193: truelight@0: /* locate the head of the two chains */ rubidium@7497: Vehicle *src_head = src->First(); tron@6150: Vehicle *dst_head; peter1138@2602: if (dst != NULL) { rubidium@7497: dst_head = dst->First(); tron@6136: if (dst_head->tile != src_head->tile) return CMD_ERROR; belugas@6422: /* Now deal with articulated part of destination wagon */ peter1138@2602: dst = GetLastEnginePart(dst); tron@6136: } else { tron@6136: dst_head = NULL; peter1138@2602: } truelight@193: bjarni@7526: if (IsRearDualheaded(src)) return_cmd_error(STR_REAR_ENGINE_FOLLOW_FRONT_ERROR); bjarni@2676: belugas@6422: /* when moving all wagons, we can't have the same src_head and dst_head */ skidd13@7928: if (HasBit(p2, 0) && src_head == dst_head) return CommandCost(); truelight@0: frosch@9928: /* check if all vehicles in the source train are stopped inside a depot. */ frosch@9928: int src_len = CheckTrainStoppedInDepot(src_head); frosch@9928: if (src_len < 0) return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED); frosch@9928: frosch@9928: if ((flags & DC_AUTOREPLACE) == 0) { frosch@9928: /* Check whether there are more than 'max_len' train units (articulated parts and rear heads do not count) in the new chain */ rubidium@9413: int max_len = _settings_game.vehicle.mammoth_trains ? 100 : 10; peter1138@2883: belugas@6422: /* check the destination row if the source and destination aren't the same. */ peter1138@2883: if (src_head != dst_head) { peter1138@2883: int dst_len = 0; peter1138@2883: peter1138@2883: if (dst_head != NULL) { belugas@6422: /* check if all vehicles in the dest train are stopped. */ peter1138@2883: dst_len = CheckTrainStoppedInDepot(dst_head); tron@3183: if (dst_len < 0) return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED); peter1138@2883: } peter1138@2883: belugas@6422: /* We are moving between rows, so only count the wagons from the source belugas@6422: * row that are being moved. */ skidd13@7928: if (HasBit(p2, 0)) { peter1138@2883: const Vehicle *u; peter1138@2883: for (u = src_head; u != src && u != NULL; u = GetNextVehicle(u)) peter1138@2883: src_len--; peter1138@2883: } else { belugas@6422: /* If moving only one vehicle, just count that. */ peter1138@2883: src_len = 1; peter1138@2883: } peter1138@2883: peter1138@2883: if (src_len + dst_len > max_len) { belugas@6422: /* Abort if we're adding too many wagons to a train. */ peter1138@2883: if (dst_head != NULL && IsFrontEngine(dst_head)) return_cmd_error(STR_8819_TRAIN_TOO_LONG); belugas@6422: /* Abort if we're making a train on a new row. */ peter1138@2883: if (dst_head == NULL && IsTrainEngine(src)) return_cmd_error(STR_8819_TRAIN_TOO_LONG); peter1138@2883: } peter1138@2883: } else { belugas@6422: /* Abort if we're creating a new train on an existing row. */ peter1138@2883: if (src_len > max_len && src == src_head && IsTrainEngine(GetNextVehicle(src_head))) peter1138@2883: return_cmd_error(STR_8819_TRAIN_TOO_LONG); peter1138@2883: } peter1138@2883: } peter1138@2883: belugas@6422: /* moving a loco to a new line?, then we need to assign a unitnumber. */ bjarni@2676: if (dst == NULL && !IsFrontEngine(src) && IsTrainEngine(src)) { frosch@9928: UnitID unit_num = ((flags & DC_AUTOREPLACE) != 0 ? 0 : GetFreeUnitNumber(VEH_TRAIN)); rubidium@9413: if (unit_num > _settings_game.vehicle.max_trains) truelight@0: return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); truelight@0: tron@3017: if (flags & DC_EXEC) src->unitnumber = unit_num; truelight@0: } truelight@0: frosch@10178: /* When we move the front vehicle, the second vehicle might need a unitnumber */ frosch@10190: if (!HasBit(p2, 0) && (IsFreeWagon(src) || (IsFrontEngine(src) && dst == NULL)) && (flags & DC_AUTOREPLACE) == 0) { frosch@10178: Vehicle *second = GetNextUnit(src); frosch@10178: if (second != NULL && IsTrainEngine(second) && GetFreeUnitNumber(VEH_TRAIN) > _settings_game.vehicle.max_trains) { frosch@10178: return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); frosch@10178: } frosch@10178: } frosch@10178: rubidium@8162: /* rubidium@8162: * Check whether the vehicles in the source chain are in the destination rubidium@8162: * chain. This can easily be done by checking whether the first vehicle rubidium@8162: * of the source chain is in the destination chain as the Next/Previous rubidium@8162: * pointers always make a doubly linked list of it where the assumption rubidium@8162: * v->Next()->Previous() == v holds (assuming v->Next() != NULL). rubidium@8162: */ rubidium@8162: bool src_in_dst = false; rubidium@8162: for (Vehicle *v = dst_head; !src_in_dst && v != NULL; v = v->Next()) src_in_dst = v == src; rubidium@8162: rubidium@8162: /* rubidium@8162: * If the source chain is in the destination chain then the user is rubidium@8162: * only reordering the vehicles, thus not attaching a new vehicle. rubidium@8162: * Therefor the 'allow wagon attach' callback does not need to be rubidium@8162: * called. If it would be called strange things would happen because rubidium@8162: * one 'attaches' an already 'attached' vehicle causing more trouble rubidium@8162: * than it actually solves (infinite loops and such). rubidium@8162: */ frosch@9928: if (dst_head != NULL && !src_in_dst && (flags & DC_AUTOREPLACE) == 0) { rubidium@8162: /* rubidium@8162: * When performing the 'allow wagon attach' callback, we have to check rubidium@8162: * that for each and every wagon, not only the first one. This means rubidium@8162: * that we have to test one wagon, attach it to the train and then test rubidium@8162: * the next wagon till we have reached the end. We have to restore it rubidium@8162: * to the state it was before we 'tried' attaching the train when the rubidium@8162: * attaching fails or succeeds because we are not 'only' doing this rubidium@8162: * in the DC_EXEC state. rubidium@8162: */ rubidium@8162: Vehicle *dst_tail = dst_head; rubidium@8162: while (dst_tail->Next() != NULL) dst_tail = dst_tail->Next(); rubidium@8162: rubidium@8162: Vehicle *orig_tail = dst_tail; rubidium@8162: Vehicle *next_to_attach = src; rubidium@8162: Vehicle *src_previous = src->Previous(); rubidium@8162: rubidium@8162: while (next_to_attach != NULL) { peter1138@10188: /* Don't check callback for articulated or rear dual headed parts */ peter1138@10188: if (!IsArticulatedPart(next_to_attach) && !IsRearDualheaded(next_to_attach)) { peter1138@10188: /* Back up and clear the first_engine data to avoid using wagon override group */ peter1138@10188: EngineID first_engine = next_to_attach->u.rail.first_engine; peter1138@10188: next_to_attach->u.rail.first_engine = INVALID_ENGINE; peter1138@10188: peter1138@10188: uint16 callback = GetVehicleCallbackParent(CBID_TRAIN_ALLOW_WAGON_ATTACH, 0, 0, dst_head->engine_type, next_to_attach, dst_head); peter1138@10188: peter1138@10188: /* Restore original first_engine data */ peter1138@10188: next_to_attach->u.rail.first_engine = first_engine; peter1138@10188: peter1138@10188: if (callback != CALLBACK_FAILED) { peter1138@10188: StringID error = STR_NULL; peter1138@10188: peter1138@10188: if (callback == 0xFD) error = STR_INCOMPATIBLE_RAIL_TYPES; peter1138@10188: if (callback < 0xFD) error = GetGRFStringID(GetEngineGRFID(dst_head->engine_type), 0xD000 + callback); peter1138@10188: peter1138@10188: if (error != STR_NULL) { peter1138@10188: /* peter1138@10188: * The attaching is not allowed. In this case 'next_to_attach' peter1138@10188: * can contain some vehicles of the 'source' and the destination peter1138@10188: * train can have some too. We 'just' add the to-be added wagons peter1138@10188: * to the chain and then split it where it was previously peter1138@10188: * separated, i.e. the tail of the original destination train. peter1138@10188: * Furthermore the 'previous' link of the original source vehicle needs peter1138@10188: * to be restored, otherwise the train goes missing in the depot. peter1138@10188: */ peter1138@10188: dst_tail->SetNext(next_to_attach); peter1138@10188: orig_tail->SetNext(NULL); peter1138@10188: if (src_previous != NULL) src_previous->SetNext(src); peter1138@10188: peter1138@10188: return_cmd_error(error); peter1138@10188: } rubidium@8162: } peter1138@3727: } rubidium@8162: peter1138@8168: /* Only check further wagons if told to move the chain */ peter1138@8168: if (!HasBit(p2, 0)) break; peter1138@8168: rubidium@8162: /* rubidium@8162: * Adding a next wagon to the chain so we can test the other wagons. rubidium@8162: * First 'take' the first wagon from 'next_to_attach' and move it rubidium@8162: * to the next wagon. Then add that to the tail of the destination rubidium@8162: * train and update the tail with the new vehicle. rubidium@8162: */ rubidium@8162: Vehicle *to_add = next_to_attach; rubidium@8162: next_to_attach = next_to_attach->Next(); rubidium@8162: rubidium@8162: to_add->SetNext(NULL); rubidium@8162: dst_tail->SetNext(to_add); rubidium@8162: dst_tail = dst_tail->Next(); peter1138@3727: } rubidium@8162: rubidium@8162: /* rubidium@8162: * When we reach this the attaching is allowed. It also means that the rubidium@8162: * chain of vehicles to attach is empty, so we do not need to merge that. rubidium@8162: * This means only the splitting needs to be done. rubidium@8162: * Furthermore the 'previous' link of the original source vehicle needs rubidium@8162: * to be restored, otherwise the train goes missing in the depot. rubidium@8162: */ rubidium@8162: orig_tail->SetNext(NULL); rubidium@8162: if (src_previous != NULL) src_previous->SetNext(src); peter1138@3727: } truelight@0: truelight@0: /* do it? */ truelight@0: if (flags & DC_EXEC) { rubidium@6643: /* If we move the front Engine and if the second vehicle is not an engine rubidium@6643: add the whole vehicle to the DEFAULT_GROUP */ rubidium@6643: if (IsFrontEngine(src) && !IsDefaultGroupID(src->group_id)) { rubidium@7529: Vehicle *v = GetNextVehicle(src); rubidium@7529: rubidium@7529: if (v != NULL && IsTrainEngine(v)) { rubidium@7529: v->group_id = src->group_id; rubidium@7529: src->group_id = DEFAULT_GROUP; rubidium@6643: } rubidium@6643: } rubidium@6643: skidd13@7928: if (HasBit(p2, 0)) { belugas@6422: /* unlink ALL wagons */ truelight@0: if (src != src_head) { truelight@0: Vehicle *v = src_head; peter1138@2602: while (GetNextVehicle(v) != src) v = GetNextVehicle(v); rubidium@7493: GetLastEnginePart(v)->SetNext(NULL); hackykid@1917: } else { bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, src_head->tile); // We removed a line hackykid@1917: src_head = NULL; truelight@0: } truelight@0: } else { belugas@6422: /* if moving within the same chain, dont use dst_head as it may get invalidated */ Darkvater@4198: if (src_head == dst_head) dst_head = NULL; belugas@6422: /* unlink single wagon from linked list */ hackykid@1917: src_head = UnlinkWagon(src, src_head); rubidium@7493: GetLastEnginePart(src)->SetNext(NULL); truelight@0: } truelight@0: truelight@0: if (dst == NULL) { bjarni@4739: /* We make a new line in the depot, so we know already that we invalidate the window data */ bjarni@4806: InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile); bjarni@4739: belugas@6422: /* move the train to an empty line. for locomotives, we set the type to TS_Front. for wagons, 4. */ bjarni@2676: if (IsTrainEngine(src)) { bjarni@2676: if (!IsFrontEngine(src)) { belugas@6422: /* setting the type to 0 also involves setting up the orders field. */ bjarni@2676: SetFrontEngine(src); truelight@1024: assert(src->orders == NULL); truelight@0: src->num_orders = 0; rubidium@6643: smatz@8850: /* Decrease the engines number of the src engine_type */ rubidium@6643: if (!IsDefaultGroupID(src->group_id) && IsValidGroupID(src->group_id)) { rubidium@6643: GetGroup(src->group_id)->num_engines[src->engine_type]--; rubidium@6643: } rubidium@6643: smatz@8850: /* If we move an engine to a new line affect it to the DEFAULT_GROUP */ rubidium@6643: src->group_id = DEFAULT_GROUP; truelight@0: } truelight@0: } else { bjarni@2676: SetFreeWagon(src); truelight@0: } hackykid@1917: dst_head = src; truelight@0: } else { bjarni@2676: if (IsFrontEngine(src)) { belugas@6422: /* the vehicle was previously a loco. need to free the order list and delete vehicle windows etc. */ truelight@0: DeleteWindowById(WC_VEHICLE_VIEW, src->index); truelight@1024: DeleteVehicleOrders(src); rubidium@7529: RemoveVehicleFromGroup(src); truelight@0: } truelight@193: bjarni@4739: if (IsFrontEngine(src) || IsFreeWagon(src)) { bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile); bjarni@4739: ClearFrontEngine(src); bjarni@4739: ClearFreeWagon(src); bjarni@4739: src->unitnumber = 0; // doesn't occupy a unitnumber anymore. bjarni@4739: } truelight@0: belugas@6422: /* link in the wagon(s) in the chain. */ truelight@0: { tron@1472: Vehicle *v; tron@1472: smatz@8695: for (v = src; GetNextVehicle(v) != NULL; v = GetNextVehicle(v)) {} rubidium@7492: GetLastEnginePart(v)->SetNext(dst->Next()); truelight@0: } rubidium@7492: dst->SetNext(src); truelight@0: } rubidium@7497: bjarni@2676: if (src->u.rail.other_multiheaded_part != NULL) { bjarni@2676: if (src->u.rail.other_multiheaded_part == src_head) { rubidium@7492: src_head = src_head->Next(); bjarni@2676: } bjarni@2676: AddWagonToConsist(src->u.rail.other_multiheaded_part, src); bjarni@2676: } bjarni@2676: bjarni@2607: /* If there is an engine behind first_engine we moved away, it should become new first_engine bjarni@2607: * To do this, CmdMoveRailVehicle must be called once more bjarni@2676: * we can't loop forever here because next time we reach this line we will have a front engine */ bjarni@2676: if (src_head != NULL && !IsFrontEngine(src_head) && IsTrainEngine(src_head)) { rubidium@6643: /* As in CmdMoveRailVehicle src_head->group_id will be equal to DEFAULT_GROUP rubidium@6643: * we need to save the group and reaffect it to src_head */ rubidium@6643: const GroupID tmp_g = src_head->group_id; tron@3491: CmdMoveRailVehicle(0, flags, src_head->index | (INVALID_VEHICLE << 16), 1); rubidium@6643: SetTrainGroupID(src_head, tmp_g); rubidium@4434: src_head = NULL; // don't do anything more to this train since the new call will do it bjarni@2607: } bjarni@2607: Darkvater@4198: if (src_head != NULL) { bjarni@2676: NormaliseTrainConsist(src_head); smatz@9704: TrainConsistChanged(src_head, false); rubidium@6643: UpdateTrainGroupID(src_head); bjarni@2676: if (IsFrontEngine(src_head)) { hackykid@1917: /* Update the refit button and window */ hackykid@1917: InvalidateWindow(WC_VEHICLE_REFIT, src_head->index); smatz@8350: InvalidateWindowWidget(WC_VEHICLE_VIEW, src_head->index, VVW_WIDGET_REFIT_VEH); hackykid@1917: } hackykid@1917: /* Update the depot window */ hackykid@1917: InvalidateWindow(WC_VEHICLE_DEPOT, src_head->tile); tron@6150: } truelight@0: Darkvater@4198: if (dst_head != NULL) { bjarni@2676: NormaliseTrainConsist(dst_head); smatz@9704: TrainConsistChanged(dst_head, false); rubidium@6643: UpdateTrainGroupID(dst_head); bjarni@2676: if (IsFrontEngine(dst_head)) { hackykid@1917: /* Update the refit button and window */ smatz@8350: InvalidateWindowWidget(WC_VEHICLE_VIEW, dst_head->index, VVW_WIDGET_REFIT_VEH); hackykid@1917: InvalidateWindow(WC_VEHICLE_REFIT, dst_head->index); hackykid@1905: } hackykid@1917: /* Update the depot window */ hackykid@1917: InvalidateWindow(WC_VEHICLE_DEPOT, dst_head->tile); truelight@0: } truelight@0: rubidium@9297: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); truelight@0: } truelight@0: rubidium@6950: return CommandCost(); truelight@0: } truelight@0: Darkvater@1784: /** Sell a (single) train wagon/engine. tron@3491: * @param tile unused belugas@6422: * @param flags type of operation Darkvater@1766: * @param p1 the wagon/engine index Darkvater@1766: * @param p2 the selling mode Darkvater@1784: * - p2 = 0: only sell the single dragged wagon/engine (and any belonging rear-engines) Darkvater@1784: * - p2 = 1: sell the vehicle and all vehicles following it in the chain rubidium@8969: * if the wagon is dragged, don't delete the possibly belonging rear-engine to some front Darkvater@1784: * - p2 = 2: when selling attached locos, rearrange all vehicles after it to separate lines; Darkvater@1784: * all wagons of the same type will go on the same line. Used by the AI currently Darkvater@1766: */ rubidium@6943: CommandCost CmdSellRailWagon(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { peter1138@6789: /* Check if we deleted a vehicle window */ peter1138@6789: Window *w = NULL; peter1138@6789: truelight@4352: if (!IsValidVehicleID(p1) || p2 > 2) return CMD_ERROR; truelight@0: tron@6150: Vehicle *v = GetVehicle(p1); truelight@0: rubidium@6259: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: rubidium@7695: if (HASBITS(v->vehstatus, VS_CRASHED)) return_cmd_error(STR_CAN_T_SELL_DESTROYED_VEHICLE); rubidium@7695: rubidium@7497: while (IsArticulatedPart(v)) v = v->Previous(); rubidium@7497: Vehicle *first = v->First(); truelight@193: belugas@6422: /* make sure the vehicle is stopped in the depot */ tron@3183: if (CheckTrainStoppedInDepot(first) < 0) { tron@3183: return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED); tron@3183: } truelight@0: bjarni@7526: if (IsRearDualheaded(v)) return_cmd_error(STR_REAR_ENGINE_FOLLOW_FRONT_ERROR); bjarni@2676: Darkvater@1842: if (flags & DC_EXEC) { bjarni@2676: if (v == first && IsFrontEngine(first)) { rubidium@9274: DeleteWindowById(WC_VEHICLE_VIEW, first->index); bjarni@2618: } truelight@0: InvalidateWindow(WC_VEHICLE_DEPOT, first->tile); rubidium@9297: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); Darkvater@1766: } Darkvater@1766: rubidium@8230: CommandCost cost(EXPENSES_NEW_VEHICLES); Darkvater@1766: switch (p2) { Darkvater@1766: case 0: case 2: { /* Delete given wagon */ Darkvater@1766: bool switch_engine = false; // update second wagon to engine? Darkvater@1766: byte ori_subtype = v->subtype; // backup subtype of deleted wagon in case DeleteVehicle() changes Darkvater@1766: Darkvater@1766: /* 1. Delete the engine, if it is dualheaded also delete the matching bjarni@2676: * rear engine of the loco (from the point of deletion onwards) */ bjarni@2676: Vehicle *rear = (IsMultiheaded(v) && bjarni@2676: IsTrainEngine(v)) ? v->u.rail.other_multiheaded_part : NULL; bjarni@2676: Darkvater@1766: if (rear != NULL) { rubidium@6990: cost.AddCost(-rear->value); Darkvater@1766: if (flags & DC_EXEC) { bjarni@2676: UnlinkWagon(rear, first); bjarni@5256: DeleteDepotHighlightOfVehicle(rear); rubidium@7398: delete rear; truelight@0: } truelight@0: } Darkvater@1766: frosch@10192: /* 2. We are selling the front vehicle, some special action might be required bjarni@2676: * here, so take attention */ frosch@10192: if (v == first) { tron@6150: Vehicle *new_f = GetNextVehicle(first); Darkvater@1766: Darkvater@1766: /* 2.2 If there are wagons present after the deleted front engine, check rubidium@6322: * if the second wagon (which will be first) is an engine. If it is one, rubidium@6322: * promote it as a new train, retaining the unitnumber, orders */ rubidium@6322: if (new_f != NULL && IsTrainEngine(new_f)) { frosch@10192: if (IsTrainEngine(first)) { frosch@10192: /* Let the new front engine take over the setup of the old engine */ frosch@10192: switch_engine = true; frosch@10192: frosch@10192: if (flags & DC_EXEC) { frosch@10192: /* Make sure the group counts stay correct. */ frosch@10192: new_f->group_id = first->group_id; frosch@10192: first->group_id = DEFAULT_GROUP; frosch@10192: frosch@10192: /* Copy orders (by sharing) */ frosch@10192: new_f->orders = first->orders; frosch@10192: new_f->num_orders = first->num_orders; frosch@10192: new_f->AddToShared(first); frosch@10192: DeleteVehicleOrders(first); frosch@10192: frosch@10192: /* Copy other important data from the front engine */ frosch@10192: new_f->CopyVehicleConfigAndStatistics(first); frosch@10192: frosch@10192: /* If we deleted a window then open a new one for the 'new' train */ rubidium@10207: if (IsLocalCompany() && w != NULL) ShowVehicleViewWindow(new_f); frosch@10192: } frosch@10192: } else { frosch@10192: /* We are selling a free wagon, and construct a new train at the same time. frosch@10192: * This needs lots of extra checks (e.g. train limit), which are done by first moving frosch@10192: * the remaining vehicles to a new row */ frosch@10192: cost.AddCost(DoCommand(0, new_f->index | INVALID_VEHICLE << 16, 1, flags, CMD_MOVE_RAIL_VEHICLE)); frosch@10192: if (cost.Failed()) return cost; frosch@10192: } Darkvater@1766: } Darkvater@1766: } Darkvater@1766: Darkvater@1766: /* 3. Delete the requested wagon */ rubidium@6950: cost.AddCost(-v->value); Darkvater@1766: if (flags & DC_EXEC) { Darkvater@1766: first = UnlinkWagon(v, first); bjarni@5256: DeleteDepotHighlightOfVehicle(v); rubidium@7398: delete v; Darkvater@1766: Darkvater@1766: /* 4 If the second wagon was an engine, update it to front_engine smatz@8850: * which UnlinkWagon() has changed to TS_Free_Car */ bjarni@2676: if (switch_engine) SetFrontEngine(first); Darkvater@1766: Darkvater@1766: /* 5. If the train still exists, update its acceleration, window, etc. */ hackykid@1917: if (first != NULL) { bjarni@2676: NormaliseTrainConsist(first); smatz@9704: TrainConsistChanged(first, false); rubidium@6643: UpdateTrainGroupID(first); glx@8706: if (IsFrontEngine(first)) InvalidateWindow(WC_VEHICLE_REFIT, first->index); Darkvater@1766: } Darkvater@1766: Darkvater@1766: Darkvater@1766: /* (6.) Borked AI. If it sells an engine it expects all wagons lined bjarni@2676: * up on a new line to be added to the newly built loco. Replace it is. bjarni@2676: * Totally braindead cause building a new engine adds all loco-less bjarni@2676: * engines to its train anyways */ smatz@8612: if (p2 == 2 && HasBit(ori_subtype, TS_FRONT)) { smatz@8850: for (v = first; v != NULL;) { smatz@8850: Vehicle *tmp = GetNextVehicle(v); tron@3491: DoCommand(v->tile, v->index | INVALID_VEHICLE << 16, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); smatz@8850: v = tmp; Darkvater@1766: } Darkvater@1766: } Darkvater@1766: } Darkvater@1766: } break; Darkvater@1766: case 1: { /* Delete wagon and all wagons after it given certain criteria */ bjarni@2676: /* Start deleting every vehicle after the selected one bjarni@2676: * If we encounter a matching rear-engine to a front-engine bjarni@2676: * earlier in the chain (before deletion), leave it alone */ smatz@8850: for (Vehicle *tmp; v != NULL; v = tmp) { peter1138@2602: tmp = GetNextVehicle(v); Darkvater@1766: bjarni@2676: if (IsMultiheaded(v)) { bjarni@2676: if (IsTrainEngine(v)) { bjarni@2676: /* We got a front engine of a multiheaded set. Now we will sell the rear end too */ bjarni@2676: Vehicle *rear = v->u.rail.other_multiheaded_part; bjarni@2676: bjarni@2676: if (rear != NULL) { rubidium@6950: cost.AddCost(-rear->value); maedhros@6785: maedhros@6785: /* If this is a multiheaded vehicle with nothing maedhros@6785: * between the parts, tmp will be pointing to the maedhros@6785: * rear part, which is unlinked from the train and maedhros@6785: * deleted here. However, because tmp has already maedhros@6785: * been set it needs to be updated now so that the maedhros@6785: * loop never sees the rear part. */ maedhros@6785: if (tmp == rear) tmp = GetNextVehicle(tmp); maedhros@6785: bjarni@2676: if (flags & DC_EXEC) { bjarni@2676: first = UnlinkWagon(rear, first); bjarni@5256: DeleteDepotHighlightOfVehicle(rear); rubidium@7398: delete rear; bjarni@2676: } bjarni@2676: } bjarni@2676: } else if (v->u.rail.other_multiheaded_part != NULL) { bjarni@2676: /* The front to this engine is earlier in this train. Do nothing */ tron@2549: continue; tron@2549: } Darkvater@1766: } Darkvater@1766: rubidium@6950: cost.AddCost(-v->value); Darkvater@1766: if (flags & DC_EXEC) { Darkvater@1766: first = UnlinkWagon(v, first); bjarni@5256: DeleteDepotHighlightOfVehicle(v); rubidium@7398: delete v; Darkvater@1766: } Darkvater@1766: } Darkvater@1766: hackykid@1905: /* 3. If it is still a valid train after selling, update its acceleration and cached values */ tron@3017: if (flags & DC_EXEC && first != NULL) { bjarni@2676: NormaliseTrainConsist(first); smatz@9704: TrainConsistChanged(first, false); rubidium@6643: UpdateTrainGroupID(first); bjarni@2676: InvalidateWindow(WC_VEHICLE_REFIT, first->index); hackykid@1905: } Darkvater@1766: } break; truelight@0: } truelight@0: return cost; truelight@0: } truelight@0: rubidium@6558: void Train::UpdateDeltaXY(Direction direction) truelight@0: { rubidium@6558: #define MKIT(a, b, c, d) ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) | ((d & 0xFF) << 0) truelight@0: static const uint32 _delta_xy_table[8] = { truelight@0: MKIT(3, 3, -1, -1), truelight@0: MKIT(3, 7, -1, -3), truelight@0: MKIT(3, 3, -1, -1), truelight@0: MKIT(7, 3, -3, -1), truelight@0: MKIT(3, 3, -1, -1), truelight@0: MKIT(3, 7, -1, -3), truelight@0: MKIT(3, 3, -1, -1), truelight@0: MKIT(7, 3, -3, -1), truelight@0: }; truelight@0: #undef MKIT truelight@0: truelight@0: uint32 x = _delta_xy_table[direction]; rubidium@6558: this->x_offs = GB(x, 0, 8); rubidium@6558: this->y_offs = GB(x, 8, 8); frosch@8793: this->x_extent = GB(x, 16, 8); frosch@8793: this->y_extent = GB(x, 24, 8); frosch@8793: this->z_extent = 6; truelight@0: } truelight@0: truelight@0: static void UpdateVarsAfterSwap(Vehicle *v) truelight@0: { rubidium@6558: v->UpdateDeltaXY(v->direction); rubidium@7134: v->cur_image = v->GetImage(v->direction); truelight@0: BeginVehicleMove(v); truelight@0: VehiclePositionChanged(v); truelight@0: EndVehicleMove(v); truelight@0: } truelight@0: smatz@8850: static inline void SetLastSpeed(Vehicle *v, int spd) tron@2639: { truelight@0: int old = v->u.rail.last_speed; truelight@0: if (spd != old) { truelight@0: v->u.rail.last_speed = spd; rubidium@9413: if (_settings_client.gui.vehicle_speed || (old == 0) != (spd == 0)) { smatz@8350: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); smatz@8850: } truelight@0: } truelight@0: } truelight@0: rubidium@9808: /** Mark a train as stuck and stop it if it isn't stopped right now. */ rubidium@9808: static void MarkTrainAsStuck(Vehicle *v) truelight@954: { rubidium@9808: if (!HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) { rubidium@9808: /* It is the first time the problem occured, set the "train stuck" flag. */ rubidium@9808: SetBit(v->u.rail.flags, VRF_TRAIN_STUCK); rubidium@9808: v->load_unload_time_rem = 0; rubidium@9808: rubidium@9808: /* Stop train */ rubidium@9808: v->cur_speed = 0; rubidium@9808: v->subspeed = 0; rubidium@9808: SetLastSpeed(v, 0); rubidium@9808: rubidium@9808: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); rubidium@9808: } rubidium@9808: } rubidium@9808: rubidium@9808: static void SwapTrainFlags(uint16 *swap_flag1, uint16 *swap_flag2) rubidium@9808: { rubidium@9808: uint16 flag1 = *swap_flag1; rubidium@9808: uint16 flag2 = *swap_flag2; truelight@954: truelight@954: /* Clear the flags */ skidd13@7929: ClrBit(*swap_flag1, VRF_GOINGUP); skidd13@7929: ClrBit(*swap_flag1, VRF_GOINGDOWN); skidd13@7929: ClrBit(*swap_flag2, VRF_GOINGUP); skidd13@7929: ClrBit(*swap_flag2, VRF_GOINGDOWN); truelight@954: truelight@954: /* Reverse the rail-flags (if needed) */ skidd13@7928: if (HasBit(flag1, VRF_GOINGUP)) { skidd13@7931: SetBit(*swap_flag2, VRF_GOINGDOWN); skidd13@7928: } else if (HasBit(flag1, VRF_GOINGDOWN)) { skidd13@7931: SetBit(*swap_flag2, VRF_GOINGUP); truelight@954: } skidd13@7928: if (HasBit(flag2, VRF_GOINGUP)) { skidd13@7931: SetBit(*swap_flag1, VRF_GOINGDOWN); skidd13@7928: } else if (HasBit(flag2, VRF_GOINGDOWN)) { skidd13@7931: SetBit(*swap_flag1, VRF_GOINGUP); truelight@954: } truelight@954: } truelight@954: truelight@0: static void ReverseTrainSwapVeh(Vehicle *v, int l, int r) truelight@0: { truelight@0: Vehicle *a, *b; truelight@0: truelight@0: /* locate vehicles to swap */ rubidium@7492: for (a = v; l != 0; l--) a = a->Next(); rubidium@7492: for (b = v; r != 0; r--) b = b->Next(); truelight@0: truelight@0: if (a != b) { truelight@0: /* swap the hidden bits */ truelight@0: { truelight@0: uint16 tmp = (a->vehstatus & ~VS_HIDDEN) | (b->vehstatus&VS_HIDDEN); truelight@0: b->vehstatus = (b->vehstatus & ~VS_HIDDEN) | (a->vehstatus&VS_HIDDEN); truelight@0: a->vehstatus = tmp; truelight@0: } truelight@193: tron@5733: Swap(a->u.rail.track, b->u.rail.track); tron@5733: Swap(a->direction, b->direction); truelight@0: truelight@0: /* toggle direction */ rubidium@5993: if (a->u.rail.track != TRACK_BIT_DEPOT) a->direction = ReverseDir(a->direction); rubidium@5993: if (b->u.rail.track != TRACK_BIT_DEPOT) b->direction = ReverseDir(b->direction); truelight@193: tron@5733: Swap(a->x_pos, b->x_pos); tron@5733: Swap(a->y_pos, b->y_pos); tron@5733: Swap(a->tile, b->tile); tron@5733: Swap(a->z_pos, b->z_pos); truelight@0: truelight@954: SwapTrainFlags(&a->u.rail.flags, &b->u.rail.flags); truelight@954: truelight@0: /* update other vars */ truelight@0: UpdateVarsAfterSwap(a); truelight@0: UpdateVarsAfterSwap(b); truelight@1554: celestar@5385: /* call the proper EnterTile function unless we are in a wormhole */ rubidium@5993: if (a->u.rail.track != TRACK_BIT_WORMHOLE) VehicleEnterTile(a, a->tile, a->x_pos, a->y_pos); rubidium@5993: if (b->u.rail.track != TRACK_BIT_WORMHOLE) VehicleEnterTile(b, b->tile, b->x_pos, b->y_pos); truelight@0: } else { rubidium@5993: if (a->u.rail.track != TRACK_BIT_DEPOT) a->direction = ReverseDir(a->direction); truelight@193: UpdateVarsAfterSwap(a); truelight@1554: rubidium@5993: if (a->u.rail.track != TRACK_BIT_WORMHOLE) VehicleEnterTile(a, a->tile, a->x_pos, a->y_pos); truelight@0: } celestar@3355: celestar@3355: /* Update train's power incase tiles were different rail type */ celestar@3355: TrainPowerChanged(v); truelight@0: } truelight@0: smatz@8334: smatz@8334: /** smatz@8334: * Check if the vehicle is a train smatz@8334: * @param v vehicle on tile smatz@8334: * @return v if it is a train, NULL otherwise smatz@8334: */ rubidium@9775: static Vehicle *TrainOnTileEnum(Vehicle *v, void *) truelight@744: { smatz@8334: return (v->type == VEH_TRAIN) ? v : NULL; smatz@8334: } smatz@8334: smatz@8334: smatz@8334: /** smatz@8334: * Checks if a train is approaching a rail-road crossing smatz@8334: * @param v vehicle on tile smatz@8334: * @param data tile with crossing we are testing smatz@8334: * @return v if it is approaching a crossing, NULL otherwise smatz@8334: */ rubidium@9775: static Vehicle *TrainApproachingCrossingEnum(Vehicle *v, void *data) smatz@8334: { smatz@8334: /* not a train || not front engine || crashed */ smatz@8334: if (v->type != VEH_TRAIN || !IsFrontEngine(v) || v->vehstatus & VS_CRASHED) return NULL; smatz@8334: smatz@8334: TileIndex tile = *(TileIndex*)data; smatz@8334: smatz@8334: if (TrainApproachingCrossingTile(v) != tile) return NULL; smatz@8334: truelight@744: return v; truelight@744: } truelight@744: smatz@8334: smatz@8334: /** smatz@8334: * Finds a vehicle approaching rail-road crossing smatz@8334: * @param tile tile to test rubidium@10083: * @return true if a vehicle is approaching the crossing smatz@8334: * @pre tile is a rail-road crossing smatz@8334: */ rubidium@10083: static bool TrainApproachingCrossing(TileIndex tile) dominik@1103: { smatz@8334: assert(IsLevelCrossingTile(tile)); smatz@8334: smatz@8598: DiagDirection dir = AxisToDiagDir(GetCrossingRailAxis(tile)); smatz@8334: TileIndex tile_from = tile + TileOffsByDiagDir(dir); smatz@8334: rubidium@10083: if (HasVehicleOnPos(tile_from, &tile, &TrainApproachingCrossingEnum)) return true; smatz@8334: smatz@8334: dir = ReverseDiagDir(dir); smatz@8334: tile_from = tile + TileOffsByDiagDir(dir); smatz@8334: rubidium@10083: return HasVehicleOnPos(tile_from, &tile, &TrainApproachingCrossingEnum); smatz@8334: } smatz@8334: smatz@8334: smatz@8334: /** smatz@8334: * Sets correct crossing state smatz@8334: * @param tile tile to update smatz@8344: * @param sound should we play sound? smatz@8334: * @pre tile is a rail-road crossing smatz@8334: */ smatz@8344: void UpdateLevelCrossing(TileIndex tile, bool sound) smatz@8334: { smatz@8334: assert(IsLevelCrossingTile(tile)); smatz@8334: rubidium@9824: /* train on crossing || train approaching crossing || reserved */ rubidium@10083: bool new_state = HasVehicleOnPos(tile, NULL, &TrainOnTileEnum) || TrainApproachingCrossing(tile) || GetCrossingReservation(tile); smatz@8344: smatz@8344: if (new_state != IsCrossingBarred(tile)) { smatz@8344: if (new_state && sound) { smatz@8344: SndPlayTileFx(SND_0E_LEVEL_CROSSING, tile); smatz@8344: } smatz@8344: SetCrossingBarred(tile, new_state); smatz@8344: MarkTileDirtyByTile(tile); dominik@1103: } dominik@1103: } dominik@1103: smatz@8334: hackykid@1922: /** smatz@8356: * Bars crossing and plays ding-ding sound if not barred already smatz@8356: * @param tile tile with crossing smatz@8356: * @pre tile is a rail-road crossing smatz@8356: */ smatz@8356: static inline void MaybeBarCrossingWithSound(TileIndex tile) smatz@8356: { smatz@8356: if (!IsCrossingBarred(tile)) { smatz@8356: BarCrossing(tile); smatz@8356: SndPlayTileFx(SND_0E_LEVEL_CROSSING, tile); smatz@8356: MarkTileDirtyByTile(tile); smatz@8356: } smatz@8356: } smatz@8356: smatz@8356: smatz@8356: /** hackykid@1922: * Advances wagons for train reversing, needed for variable length wagons. smatz@8712: * This one is called before the train is reversed. hackykid@1922: * @param v First vehicle in chain hackykid@1922: */ smatz@8712: static void AdvanceWagonsBeforeSwap(Vehicle *v) hackykid@1922: { tron@6150: Vehicle *base = v; smatz@8712: Vehicle *first = base; // first vehicle to move smatz@8712: Vehicle *last = GetLastVehicleInChain(v); // last vehicle to move tron@6150: uint length = CountVehiclesInChain(v); hackykid@1922: hackykid@1922: while (length > 2) { smatz@8712: last = last->Previous(); smatz@8712: first = first->Next(); smatz@8712: smatz@8712: int differential = base->u.rail.cached_veh_length - last->u.rail.cached_veh_length; smatz@8712: smatz@8712: /* do not update images now smatz@8712: * negative differential will be handled in AdvanceWagonsAfterSwap() */ smatz@8712: for (int i = 0; i < differential; i++) TrainController(first, last->Next(), false); smatz@8712: smatz@8712: base = first; // == base->Next() smatz@8712: length -= 2; smatz@8712: } smatz@8712: } smatz@8712: smatz@8712: smatz@8712: /** smatz@8712: * Advances wagons for train reversing, needed for variable length wagons. smatz@8712: * This one is called after the train is reversed. smatz@8712: * @param v First vehicle in chain smatz@8712: */ smatz@8712: static void AdvanceWagonsAfterSwap(Vehicle *v) smatz@8712: { smatz@8712: /* first of all, fix the situation when the train was entering a depot */ smatz@8712: Vehicle *dep = v; // last vehicle in front of just left depot smatz@8712: while (dep->Next() != NULL && (dep->u.rail.track == TRACK_BIT_DEPOT || dep->Next()->u.rail.track != TRACK_BIT_DEPOT)) { smatz@8712: dep = dep->Next(); // find first vehicle outside of a depot, with next vehicle inside a depot smatz@8712: } smatz@8712: smatz@8712: Vehicle *leave = dep->Next(); // first vehicle in a depot we are leaving now smatz@8712: smatz@8712: if (leave != NULL) { smatz@8712: /* 'pull' next wagon out of the depot, so we won't miss it (it could stay in depot forever) */ smatz@8712: int d = TicksToLeaveDepot(dep); smatz@8712: smatz@8712: if (d <= 0) { smatz@8712: leave->vehstatus &= ~VS_HIDDEN; // move it out of the depot smatz@9224: leave->u.rail.track = TrackToTrackBits(GetRailDepotTrack(leave->tile)); smatz@8712: for (int i = 0; i >= d; i--) TrainController(leave, NULL, false); // maybe move it, and maybe let another wagon leave smatz@8712: } smatz@8712: } else { smatz@8712: dep = NULL; // no vehicle in a depot, so no vehicle leaving a depot smatz@8712: } smatz@8712: smatz@8712: Vehicle *base = v; smatz@8712: Vehicle *first = base; // first vehicle to move smatz@8712: Vehicle *last = GetLastVehicleInChain(v); // last vehicle to move smatz@8712: uint length = CountVehiclesInChain(v); smatz@8712: smatz@8712: /* we have to make sure all wagons that leave a depot because of train reversing are moved coorectly smatz@8712: * they have already correct spacing, so we have to make sure they are moved how they should */ smatz@8712: bool nomove = (dep == NULL); // if there is no vehicle leaving a depot, limit the number of wagons moved immediatelly smatz@8712: smatz@8712: while (length > 2) { smatz@8712: /* we reached vehicle (originally) in front of a depot, stop now smatz@8712: * (we would move wagons that are alredy moved with new wagon length) */ smatz@8712: if (base == dep) break; smatz@8712: smatz@8712: /* the last wagon was that one leaving a depot, so do not move it anymore */ smatz@8712: if (last == dep) nomove = true; smatz@8712: smatz@8712: last = last->Previous(); smatz@8712: first = first->Next(); tron@6150: tron@6150: int differential = last->u.rail.cached_veh_length - base->u.rail.cached_veh_length; smatz@8712: smatz@8712: /* do not update images now */ smatz@8712: for (int i = 0; i < differential; i++) TrainController(first, (nomove ? last->Next() : NULL), false); smatz@8712: smatz@8712: base = first; // == base->Next() hackykid@1922: length -= 2; hackykid@1922: } hackykid@1922: } hackykid@1922: tron@2951: truelight@0: static void ReverseTrainDirection(Vehicle *v) truelight@0: { smatz@8961: if (IsRailDepotTile(v->tile)) { bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); bjarni@4739: } truelight@0: rubidium@9819: /* Clear path reservation in front. */ rubidium@9819: FreeTrainTrackReservation(v); rubidium@9819: truelight@743: /* Check if we were approaching a rail/road-crossing */ smatz@8334: TileIndex crossing = TrainApproachingCrossingTile(v); truelight@743: belugas@6422: /* count number of vehicles */ smatz@8850: int r = CountVehiclesInChain(v) - 1; // number of vehicles - 1 truelight@0: smatz@8712: AdvanceWagonsBeforeSwap(v); hackykid@1922: truelight@0: /* swap start<>end, start+1<>end-1, ... */ tron@6150: int l = 0; truelight@0: do { truelight@0: ReverseTrainSwapVeh(v, l++, r--); truelight@0: } while (l <= r); truelight@0: smatz@8712: AdvanceWagonsAfterSwap(v); hackykid@1922: smatz@8961: if (IsRailDepotTile(v->tile)) { bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); bjarni@4739: } truelight@0: glx@8938: ToggleBit(v->u.rail.flags, VRF_TOGGLE_REVERSE); smatz@8143: skidd13@7929: ClrBit(v->u.rail.flags, VRF_REVERSING); smatz@8334: peter1138@8667: /* recalculate cached data */ smatz@9704: TrainConsistChanged(v, true); peter1138@8667: peter1138@8667: /* update all images */ peter1138@8667: for (Vehicle *u = v; u != NULL; u = u->Next()) u->cur_image = u->GetImage(u->direction); peter1138@8667: smatz@8334: /* update crossing we were approaching */ smatz@8342: if (crossing != INVALID_TILE) UpdateLevelCrossing(crossing); smatz@8334: smatz@8334: /* maybe we are approaching crossing now, after reversal */ smatz@8334: crossing = TrainApproachingCrossingTile(v); smatz@8356: if (crossing != INVALID_TILE) MaybeBarCrossingWithSound(crossing); rubidium@9819: rubidium@9819: /* If we are inside a depot after reversing, don't bother with path reserving. */ frosch@9973: if (v->u.rail.track & TRACK_BIT_DEPOT) { frosch@9973: /* Can't be stuck here as inside a depot is always a safe tile. */ frosch@9973: if (HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); frosch@9973: ClrBit(v->u.rail.flags, VRF_TRAIN_STUCK); frosch@9973: return; frosch@9973: } rubidium@9819: rubidium@9819: /* TrainExitDir does not always produce the desired dir for depots and rubidium@9819: * tunnels/bridges that is needed for UpdateSignalsOnSegment. */ rubidium@9819: DiagDirection dir = TrainExitDir(v->direction, v->u.rail.track); rubidium@9819: if (IsRailDepotTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)) dir = INVALID_DIAGDIR; rubidium@9819: rubidium@9819: if (UpdateSignalsOnSegment(v->tile, dir, v->owner) == SIGSEG_PBS || _settings_game.pf.reserve_paths) { rubidium@9819: /* If we are currently on a tile with conventional signals, we can't treat the rubidium@9819: * current tile as a safe tile or we would enter a PBS block without a reservation. */ rubidium@9819: bool first_tile_okay = !(IsTileType(v->tile, MP_RAILWAY) && rubidium@9819: HasSignalOnTrackdir(v->tile, GetVehicleTrackdir(v)) && rubidium@9819: !IsPbsSignal(GetSignalType(v->tile, FindFirstTrack(v->u.rail.track)))); rubidium@9819: smatz@9874: if (IsRailwayStationTile(v->tile)) SetRailwayStationPlatformReservation(v->tile, TrackdirToExitdir(GetVehicleTrackdir(v)), true); rubidium@9891: if (TryPathReserve(v, false, first_tile_okay)) { rubidium@9819: /* Do a look-ahead now in case our current tile was already a safe tile. */ rubidium@9819: CheckNextTrainTile(v); rubidium@9891: } else if (v->current_order.GetType() != OT_LOADING) { rubidium@9891: /* Do not wait for a way out when we're still loading */ rubidium@9891: MarkTrainAsStuck(v); rubidium@9819: } rubidium@9819: } truelight@0: } truelight@0: Darkvater@1784: /** Reverse train. tron@3491: * @param tile unused belugas@6422: * @param flags type of operation Darkvater@1784: * @param p1 train to reverse bjarni@3256: * @param p2 if true, reverse a unit in a train (needs to be in a depot) Darkvater@1784: */ rubidium@6943: CommandCost CmdReverseTrainDirection(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@4352: if (!IsValidVehicleID(p1)) return CMD_ERROR; bjarni@1237: tron@6150: Vehicle *v = GetVehicle(p1); truelight@0: rubidium@6259: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: smatz@8850: if (p2 != 0) { belugas@6422: /* turn a single unit around */ bjarni@3256: skidd13@7928: if (IsMultiheaded(v) || HasBit(EngInfo(v->engine_type)->callbackmask, CBM_VEHICLE_ARTIC_ENGINE)) { bjarni@3256: return_cmd_error(STR_ONLY_TURN_SINGLE_UNIT); bjarni@3256: } bjarni@3256: rubidium@7497: Vehicle *front = v->First(); belugas@6422: /* make sure the vehicle is stopped in the depot */ bjarni@3256: if (CheckTrainStoppedInDepot(front) < 0) { bjarni@3256: return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED); bjarni@3256: } bjarni@3257: bjarni@3257: if (flags & DC_EXEC) { skidd13@7932: ToggleBit(v->u.rail.flags, VRF_REVERSE_DIRECTION); peter1138@4856: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); peter1138@4856: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); bjarni@3257: } bjarni@3257: } else { rubidium@7476: /* turn the whole train around */ rubidium@7476: if (v->vehstatus & VS_CRASHED || v->breakdown_ctr != 0) return CMD_ERROR; bjarni@3257: bjarni@3257: if (flags & DC_EXEC) { rubidium@9413: if (_settings_game.vehicle.realistic_acceleration && v->cur_speed != 0) { skidd13@7932: ToggleBit(v->u.rail.flags, VRF_REVERSING); bjarni@3256: } else { bjarni@3256: v->cur_speed = 0; bjarni@3256: SetLastSpeed(v, 0); rubidium@10176: HideFillingPercent(&v->fill_percent_te_id); bjarni@3256: ReverseTrainDirection(v); bjarni@3256: } truelight@0: } truelight@0: } rubidium@6950: return CommandCost(); truelight@0: } truelight@0: Darkvater@1784: /** Force a train through a red signal tron@3491: * @param tile unused belugas@6422: * @param flags type of operation Darkvater@1784: * @param p1 train to ignore the red signal Darkvater@1784: * @param p2 unused Darkvater@1784: */ rubidium@6943: CommandCost CmdForceTrainProceed(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@4352: if (!IsValidVehicleID(p1)) return CMD_ERROR; bjarni@1237: tron@6150: Vehicle *v = GetVehicle(p1); truelight@0: rubidium@6259: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: tron@2639: if (flags & DC_EXEC) v->u.rail.force_proceed = 0x50; truelight@193: rubidium@6950: return CommandCost(); truelight@0: } truelight@0: Darkvater@1802: /** Refits a train to the specified cargo type. tron@3491: * @param tile unused belugas@6422: * @param flags type of operation Darkvater@1802: * @param p1 vehicle ID of the train to refit peter1138@3954: * param p2 various bitstuffed elements peter1138@3954: * - p2 = (bit 0-7) - the new cargo type to refit to peter1138@3954: * - p2 = (bit 8-15) - the new cargo subtype to refit to maedhros@6546: * - p2 = (bit 16) - refit only this vehicle maedhros@6546: * @return cost of refit or error Darkvater@1802: */ rubidium@6943: CommandCost CmdRefitRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { tron@2635: CargoID new_cid = GB(p2, 0, 8); peter1138@3954: byte new_subtype = GB(p2, 8, 8); skidd13@7928: bool only_this = HasBit(p2, 16); bjarni@842: truelight@4352: if (!IsValidVehicleID(p1)) return CMD_ERROR; tron@915: tron@6150: Vehicle *v = GetVehicle(p1); bjarni@1237: rubidium@6259: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; bjarni@2244: if (CheckTrainStoppedInDepot(v) < 0) return_cmd_error(STR_TRAIN_MUST_BE_STOPPED); smatz@8145: if (v->vehstatus & VS_CRASHED) return_cmd_error(STR_CAN_T_REFIT_DESTROYED_VEHICLE); Darkvater@1802: Darkvater@1802: /* Check cargo */ peter1138@6316: if (new_cid >= NUM_CARGO) return CMD_ERROR; truelight@0: rubidium@8230: CommandCost cost(EXPENSES_TRAIN_RUN); tron@6150: uint num = 0; tron@915: truelight@0: do { pasky@491: /* XXX: We also refit all the attached wagons en-masse if they pasky@491: * can be refitted. This is how TTDPatch does it. TODO: Have pasky@491: * some nice [Refit] button near each wagon. --pasky */ peter1138@2704: if (!CanRefitTo(v->engine_type, new_cid)) continue; Darkvater@1802: hackykid@1859: if (v->cargo_cap != 0) { hackykid@1895: uint16 amount = CALLBACK_FAILED; hackykid@1895: skidd13@7928: if (HasBit(EngInfo(v->engine_type)->callbackmask, CBM_VEHICLE_REFIT_CAPACITY)) { peter1138@3988: /* Back up the vehicle's cargo type */ hackykid@1895: CargoID temp_cid = v->cargo_type; peter1138@3988: byte temp_subtype = v->cargo_subtype; hackykid@1895: v->cargo_type = new_cid; peter1138@3988: v->cargo_subtype = new_subtype; peter1138@3988: /* Check the refit capacity callback */ peter1138@3390: amount = GetVehicleCallback(CBID_VEHICLE_REFIT_CAPACITY, 0, 0, v->engine_type, v); peter1138@3988: /* Restore the original cargo type */ hackykid@1895: v->cargo_type = temp_cid; peter1138@3988: v->cargo_subtype = temp_subtype; hackykid@1895: } hackykid@1895: hackykid@1895: if (amount == CALLBACK_FAILED) { // callback failed or not used, use default tron@6150: const RailVehicleInfo *rvi = RailVehInfo(v->engine_type); hackykid@1883: CargoID old_cid = rvi->cargo_type; tron@3017: /* normally, the capacity depends on the cargo type, a rail vehicle can tron@3017: * carry twice as much mail/goods as normal cargo, and four times as tron@3017: * many passengers tron@3017: */ hackykid@1883: amount = rvi->capacity; tron@3017: switch (old_cid) { tron@3017: case CT_PASSENGERS: break; tron@3017: case CT_MAIL: tron@3017: case CT_GOODS: amount *= 2; break; tron@3017: default: amount *= 4; break; tron@3017: } tron@3017: switch (new_cid) { tron@3017: case CT_PASSENGERS: break; tron@3017: case CT_MAIL: tron@3017: case CT_GOODS: amount /= 2; break; tron@3017: default: amount /= 4; break; tron@3017: } tron@6150: } hackykid@1883: frosch@10328: if (new_cid != v->cargo_type) { frosch@10328: cost.AddCost(GetRefitCost(v->engine_type)); frosch@10328: } frosch@10328: frosch@10328: num += amount; frosch@10328: if (flags & DC_EXEC) { frosch@10328: v->cargo.Truncate((v->cargo_type == new_cid) ? amount : 0); frosch@10328: v->cargo_type = new_cid; frosch@10328: v->cargo_cap = amount; frosch@10328: v->cargo_subtype = new_subtype; frosch@10328: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); frosch@10328: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); frosch@10328: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); truelight@0: } truelight@193: } rubidium@7492: } while ((v = v->Next()) != NULL && !only_this); truelight@0: peter1138@3008: _returned_refit_capacity = num; truelight@0: peter1138@4708: /* Update the train's cached variables */ smatz@9704: if (flags & DC_EXEC) TrainConsistChanged(GetVehicle(p1)->First(), false); peter1138@4708: truelight@0: return cost; truelight@0: } truelight@0: rubidium@6248: struct TrainFindDepotData { truelight@0: uint best_length; tron@1977: TileIndex tile; rubidium@10207: Owner owner; matthijs@1777: /** tron@2639: * true if reversing is necessary for the train to get to this depot tron@2639: * This value is unused when new depot finding and NPF are both disabled matthijs@1777: */ matthijs@1777: bool reverse; rubidium@6248: }; truelight@0: ludde@2125: static bool NtpCallbFindDepot(TileIndex tile, TrainFindDepotData *tfdd, int track, uint length) truelight@0: { tron@3269: if (IsTileType(tile, MP_RAILWAY) && tron@3269: IsTileOwner(tile, tfdd->owner) && tron@4182: IsRailDepot(tile)) { Darkvater@4406: /* approximate number of tiles by dividing by DIAG_FACTOR */ Darkvater@4406: tfdd->best_length = length / DIAG_FACTOR; tron@3269: tfdd->tile = tile; tron@3269: return true; truelight@0: } truelight@0: ludde@2125: return false; truelight@0: } truelight@0: belugas@6422: /** returns the tile of a depot to goto to. The given vehicle must not be belugas@6422: * crashed! */ KUDr@3900: static TrainFindDepotData FindClosestTrainDepot(Vehicle *v, int max_distance) truelight@0: { tron@6150: assert(!(v->vehstatus & VS_CRASHED)); tron@6150: truelight@0: TrainFindDepotData tfdd; darkvater@308: tfdd.owner = v->owner; matthijs@1777: tfdd.reverse = false; darkvater@308: rubidium@9934: if (IsRailDepotTile(v->tile)) { rubidium@9934: tfdd.tile = v->tile; rubidium@9934: tfdd.best_length = 0; rubidium@9934: return tfdd; rubidium@9934: } rubidium@9934: rubidium@9810: PBSTileInfo origin = FollowTrainReservation(v); rubidium@9810: if (IsRailDepotTile(origin.tile)) { rubidium@9810: tfdd.tile = origin.tile; darkvater@308: tfdd.best_length = 0; darkvater@308: return tfdd; darkvater@308: } truelight@0: rubidium@9934: tfdd.best_length = UINT_MAX; rubidium@9934: rubidium@9810: uint8 pathfinder = _settings_game.pf.pathfinder_for_trains; rubidium@9810: if ((_settings_game.pf.reserve_paths || HasReservedTracks(v->tile, v->u.rail.track)) && pathfinder == VPF_NTP) pathfinder = VPF_NPF; rubidium@9810: rubidium@9810: switch (pathfinder) { smatz@8554: case VPF_YAPF: { /* YAPF */ smatz@8554: bool found = YapfFindNearestRailDepotTwoWay(v, max_distance, NPF_INFINITE_PENALTY, &tfdd.tile, &tfdd.reverse); smatz@8554: tfdd.best_length = found ? max_distance / 2 : UINT_MAX; // some fake distance or NOT_FOUND smatz@8554: } break; smatz@8554: smatz@8554: case VPF_NPF: { /* NPF */ smatz@8850: const Vehicle *last = GetLastVehicleInChain(v); smatz@8554: Trackdir trackdir = GetVehicleTrackdir(v); smatz@8554: Trackdir trackdir_rev = ReverseTrackdir(GetVehicleTrackdir(last)); smatz@8554: smatz@8554: assert(trackdir != INVALID_TRACKDIR); smatz@8554: NPFFoundTargetData ftd = NPFRouteToDepotBreadthFirstTwoWay(v->tile, trackdir, false, last->tile, trackdir_rev, false, TRANSPORT_RAIL, 0, v->owner, v->u.rail.compatible_railtypes, NPF_INFINITE_PENALTY); smatz@8554: if (ftd.best_bird_dist == 0) { smatz@8554: /* Found target */ smatz@8554: tfdd.tile = ftd.node.tile; smatz@8554: /* Our caller expects a number of tiles, so we just approximate that smatz@8850: * number by this. It might not be completely what we want, but it will smatz@8850: * work for now :-) We can possibly change this when the old pathfinder smatz@8850: * is removed. */ smatz@8554: tfdd.best_length = ftd.best_path_dist / NPF_TILE_LENGTH; smatz@8554: if (NPFGetFlag(&ftd.node, NPF_FLAG_REVERSE)) tfdd.reverse = true; smatz@8554: } smatz@8554: } break; smatz@8554: smatz@8554: default: smatz@8554: case VPF_NTP: { /* NTP */ smatz@8554: /* search in the forward direction first. */ smatz@8554: DiagDirection i = TrainExitDir(v->direction, v->u.rail.track); rubidium@9810: NewTrainPathfind(v->tile, 0, v->u.rail.compatible_railtypes, i, (NTPEnumProc*)NtpCallbFindDepot, &tfdd); smatz@8554: if (tfdd.best_length == UINT_MAX){ smatz@8554: tfdd.reverse = true; smatz@8554: /* search in backwards direction */ smatz@8554: i = TrainExitDir(ReverseDir(v->direction), v->u.rail.track); rubidium@9810: NewTrainPathfind(v->tile, 0, v->u.rail.compatible_railtypes, i, (NTPEnumProc*)NtpCallbFindDepot, &tfdd); smatz@8554: } smatz@8554: } break; truelight@0: } truelight@193: darkvater@308: return tfdd; truelight@0: } truelight@0: rubidium@8890: bool Train::FindClosestDepot(TileIndex *location, DestinationID *destination, bool *reverse) rubidium@8890: { rubidium@8890: TrainFindDepotData tfdd = FindClosestTrainDepot(this, 0); rubidium@10236: if (tfdd.best_length == UINT_MAX) return false; rubidium@8890: rubidium@8890: if (location != NULL) *location = tfdd.tile; rubidium@8890: if (destination != NULL) *destination = GetDepotByTile(tfdd.tile)->index; rubidium@8890: if (reverse != NULL) *reverse = tfdd.reverse; rubidium@8890: rubidium@8890: return true; rubidium@8890: } rubidium@8890: Darkvater@1784: /** Send a train to a depot tron@3491: * @param tile unused belugas@6422: * @param flags type of operation Darkvater@1784: * @param p1 train to send to the depot bjarni@4451: * @param p2 various bitmasked elements bjarni@4506: * - p2 bit 0-3 - DEPOT_ flags (see vehicle.h) bjarni@4506: * - p2 bit 8-10 - VLW flag (for mass goto depot) Darkvater@1784: */ rubidium@6943: CommandCost CmdSendTrainToDepot(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { bjarni@4506: if (p2 & DEPOT_MASS_SEND) { bjarni@4506: /* Mass goto depot requested */ Darkvater@4546: if (!ValidVLWFlags(p2 & VLW_MASK)) return CMD_ERROR; rubidium@10207: return SendAllVehiclesToDepot(VEH_TRAIN, flags, p2 & DEPOT_SERVICE, _current_company, (p2 & VLW_MASK), p1); bjarni@4463: } bjarni@4463: bjarni@4506: if (!IsValidVehicleID(p1)) return CMD_ERROR; bjarni@1237: tron@6150: Vehicle *v = GetVehicle(p1); bjarni@1237: rubidium@8891: if (v->type != VEH_TRAIN) return CMD_ERROR; rubidium@8891: rubidium@8891: return v->SendToDepot(flags, (DepotCommand)(p2 & DEPOT_COMMAND_MASK)); truelight@0: } truelight@0: truelight@0: rubidium@6247: void OnTick_Train() truelight@0: { truelight@0: _age_cargo_skip_counter = (_age_cargo_skip_counter == 0) ? 184 : (_age_cargo_skip_counter - 1); truelight@0: } truelight@0: peter1138@2595: static const int8 _vehicle_smoke_pos[8] = { peter1138@2595: 1, 1, 1, 0, -1, -1, -1, 0 truelight@0: }; truelight@0: smatz@8850: static void HandleLocomotiveSmokeCloud(const Vehicle *v) truelight@0: { peter1138@4656: bool sound = false; truelight@0: smatz@8850: if (v->vehstatus & VS_TRAIN_SLOWING || v->load_unload_time_rem != 0 || v->cur_speed < 2) { truelight@0: return; smatz@8850: } smatz@8850: smatz@8850: const Vehicle *u = v; truelight@0: truelight@0: do { tron@5823: const RailVehicleInfo *rvi = RailVehInfo(v->engine_type); peter1138@2595: int effect_offset = GB(v->u.rail.cached_vis_effect, 0, 4) - 8; peter1138@2595: byte effect_type = GB(v->u.rail.cached_vis_effect, 4, 2); skidd13@7928: bool disable_effect = HasBit(v->u.rail.cached_vis_effect, 6); truelight@0: belugas@6422: /* no smoke? */ belugas@5868: if ((rvi->railveh_type == RAILVEH_WAGON && effect_type == 0) || peter1138@2595: disable_effect || tron@3546: v->vehstatus & VS_HIDDEN) { truelight@0: continue; tron@2639: } truelight@0: belugas@6422: /* No smoke in depots or tunnels */ smatz@8961: if (IsRailDepotTile(v->tile) || IsTunnelTile(v->tile)) continue; celestar@3590: belugas@6422: /* No sparks for electric vehicles on nonelectrified tracks */ tron@6154: if (!HasPowerOnRail(v->u.rail.railtype, GetTileRailType(v->tile))) continue; peter1138@2612: peter1138@2595: if (effect_type == 0) { belugas@6422: /* Use default effect type for engine class. */ tron@5823: effect_type = rvi->engclass; peter1138@2595: } else { peter1138@2595: effect_type--; peter1138@2595: } peter1138@2595: tron@6150: int x = _vehicle_smoke_pos[v->direction] * effect_offset; tron@6150: int y = _vehicle_smoke_pos[(v->direction + 2) % 8] * effect_offset; peter1138@2595: skidd13@7928: if (HasBit(v->u.rail.flags, VRF_REVERSE_DIRECTION)) { bjarni@3256: x = -x; bjarni@3256: y = -y; bjarni@3256: } bjarni@3256: peter1138@2595: switch (effect_type) { smatz@8850: case 0: smatz@8850: /* steam smoke. */ smatz@8850: if (GB(v->tick_counter, 0, 4) == 0) { smatz@8850: CreateEffectVehicleRel(v, x, y, 10, EV_STEAM_SMOKE); smatz@8850: sound = true; smatz@8850: } smatz@8850: break; smatz@8850: smatz@8850: case 1: smatz@8850: /* diesel smoke */ smatz@8850: if (u->cur_speed <= 40 && Chance16(15, 128)) { smatz@8850: CreateEffectVehicleRel(v, 0, 0, 10, EV_DIESEL_SMOKE); smatz@8850: sound = true; smatz@8850: } smatz@8850: break; smatz@8850: smatz@8850: case 2: smatz@8850: /* blue spark */ smatz@8850: if (GB(v->tick_counter, 0, 2) == 0 && Chance16(1, 45)) { smatz@8850: CreateEffectVehicleRel(v, 0, 0, 10, EV_ELECTRIC_SPARK); smatz@8850: sound = true; smatz@8850: } smatz@8850: break; smatz@8850: smatz@8850: default: smatz@8850: break; truelight@0: } rubidium@7492: } while ((v = v->Next()) != NULL); peter1138@4656: peter1138@4656: if (sound) PlayVehicleSound(u, VSE_TRAIN_EFFECT); truelight@0: } truelight@0: peter1138@8550: void Train::PlayLeaveStationSound() const truelight@0: { tron@541: static const SoundFx sfx[] = { tron@541: SND_04_TRAIN, tron@541: SND_0A_TRAIN_HORN, rubidium@6586: SND_0A_TRAIN_HORN, rubidium@6586: SND_47_MAGLEV_2, rubidium@6586: SND_41_MAGLEV tron@541: }; tron@541: peter1138@8550: if (PlayVehicleSound(this, VSE_START)) return; peter1138@8550: peter1138@8550: EngineID engtype = this->engine_type; peter1138@8550: SndPlayVehicleFx(sfx[RailVehInfo(engtype)->engclass], this); rubidium@6593: } rubidium@6593: rubidium@9816: /** Check if the train is on the last reserved tile and try to extend the path then. */ rubidium@9816: static void CheckNextTrainTile(Vehicle *v) rubidium@9816: { rubidium@9816: /* Don't do any look-ahead if path_backoff_interval is 255. */ rubidium@9816: if (_settings_game.pf.path_backoff_interval == 255) return; rubidium@9816: michi_cc@10102: /* Exit if we reached our destination depot or are inside a depot. */ michi_cc@10102: if ((v->tile == v->dest_tile && v->current_order.IsType(OT_GOTO_DEPOT)) || v->u.rail.track & TRACK_BIT_DEPOT) return; rubidium@9816: /* Exit if we are on a station tile and are going to stop. */ rubidium@9816: if (IsRailwayStationTile(v->tile) && v->current_order.ShouldStopAtStation(v, GetStationIndex(v->tile))) return; rubidium@9816: /* Exit if the current order doesn't have a destination, but the train has orders. */ rubidium@9816: if ((v->current_order.IsType(OT_NOTHING) || v->current_order.IsType(OT_LEAVESTATION)) && v->num_orders > 0) return; rubidium@9816: rubidium@9816: Trackdir td = GetVehicleTrackdir(v); rubidium@9816: rubidium@9816: /* On a tile with a red non-pbs signal, don't look ahead. */ rubidium@9816: if (IsTileType(v->tile, MP_RAILWAY) && HasSignalOnTrackdir(v->tile, td) && rubidium@9816: !IsPbsSignal(GetSignalType(v->tile, TrackdirToTrack(td))) && rubidium@9816: GetSignalStateByTrackdir(v->tile, td) == SIGNAL_STATE_RED) return; rubidium@9816: rubidium@9816: CFollowTrackRail ft(v); rubidium@9816: if (!ft.Follow(v->tile, td)) return; rubidium@9816: rubidium@9816: if (!HasReservedTracks(ft.m_new_tile, TrackdirBitsToTrackBits(ft.m_new_td_bits))) { rubidium@9816: /* Next tile is not reserved. */ rubidium@9816: if (KillFirstBit(ft.m_new_td_bits) == TRACKDIR_BIT_NONE) { rubidium@9816: if (HasPbsSignalOnTrackdir(ft.m_new_tile, FindFirstTrackdir(ft.m_new_td_bits))) { rubidium@9816: /* If the next tile is a PBS signal, try to make a reservation. */ rubidium@9816: TrackBits tracks = TrackdirBitsToTrackBits(ft.m_new_td_bits); rubidium@9816: if (_settings_game.pf.pathfinder_for_trains != VPF_NTP && _settings_game.pf.forbid_90_deg) { rubidium@9816: tracks &= ~TrackCrossesTracks(TrackdirToTrack(ft.m_old_td)); rubidium@9816: } rubidium@9816: ChooseTrainTrack(v, ft.m_new_tile, ft.m_exitdir, tracks, false, NULL, false); rubidium@9816: } rubidium@9816: } rubidium@9816: } rubidium@9816: } rubidium@9816: truelight@0: static bool CheckTrainStayInDepot(Vehicle *v) truelight@0: { belugas@6422: /* bail out if not all wagons are in the same depot or not in a depot at all */ rubidium@7492: for (const Vehicle *u = v; u != NULL; u = u->Next()) { rubidium@5993: if (u->u.rail.track != TRACK_BIT_DEPOT || u->tile != v->tile) return false; tron@2639: } truelight@0: belugas@6422: /* if the train got no power, then keep it in the depot */ bjarni@4252: if (v->u.rail.cached_power == 0) { bjarni@4252: v->vehstatus |= VS_STOPPED; bjarni@4252: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); bjarni@4252: return true; bjarni@4252: } bjarni@4252: rubidium@9818: SigSegState seg_state; rubidium@9818: truelight@0: if (v->u.rail.force_proceed == 0) { rubidium@9818: /* force proceed was not pressed */ bjarni@1151: if (++v->load_unload_time_rem < 37) { bjarni@1151: InvalidateWindowClasses(WC_TRAINS_LIST); truelight@0: return true; bjarni@1151: } bjarni@1151: truelight@0: v->load_unload_time_rem = 0; truelight@0: rubidium@9818: seg_state = _settings_game.pf.reserve_paths ? SIGSEG_PBS : UpdateSignalsOnSegment(v->tile, INVALID_DIAGDIR, v->owner); rubidium@9818: if (seg_state == SIGSEG_FULL || GetDepotWaypointReservation(v->tile)) { rubidium@9818: /* Full and no PBS signal in block or depot reserved, can't exit. */ bjarni@1151: InvalidateWindowClasses(WC_TRAINS_LIST); truelight@0: return true; bjarni@1151: } rubidium@9818: } else { rubidium@9818: seg_state = _settings_game.pf.reserve_paths ? SIGSEG_PBS : UpdateSignalsOnSegment(v->tile, INVALID_DIAGDIR, v->owner); truelight@0: } Darkvater@2916: rubidium@9818: /* Only leave when we can reserve a path to our destination. */ rubidium@9818: if (seg_state == SIGSEG_PBS && !TryPathReserve(v) && v->u.rail.force_proceed == 0) { rubidium@9818: /* No path and no force proceed. */ rubidium@9818: InvalidateWindowClasses(WC_TRAINS_LIST); rubidium@9818: MarkTrainAsStuck(v); rubidium@9818: return true; rubidium@9818: } rubidium@9818: rubidium@9818: SetDepotWaypointReservation(v->tile, true); rubidium@9818: if (_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(v->tile); rubidium@9818: bjarni@578: VehicleServiceInDepot(v); bjarni@1151: InvalidateWindowClasses(WC_TRAINS_LIST); peter1138@8550: v->PlayLeaveStationSound(); truelight@193: rubidium@5587: v->u.rail.track = TRACK_BIT_X; rubidium@5587: if (v->direction & 2) v->u.rail.track = TRACK_BIT_Y; truelight@193: truelight@0: v->vehstatus &= ~VS_HIDDEN; truelight@0: v->cur_speed = 0; truelight@193: rubidium@6558: v->UpdateDeltaXY(v->direction); rubidium@7134: v->cur_image = v->GetImage(v->direction); truelight@0: VehiclePositionChanged(v); smatz@8300: UpdateSignalsOnSegment(v->tile, INVALID_DIAGDIR, v->owner); truelight@0: UpdateTrainAcceleration(v); bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); truelight@0: truelight@0: return false; truelight@0: } truelight@0: rubidium@9809: /** Clear the reservation of a tile that was just left by a wagon on track_dir. */ michi_cc@10103: static void ClearPathReservation(const Vehicle *v, TileIndex tile, Trackdir track_dir) rubidium@9809: { rubidium@9809: DiagDirection dir = TrackdirToExitdir(track_dir); rubidium@9809: rubidium@9809: if (IsTileType(tile, MP_TUNNELBRIDGE)) { rubidium@9809: /* Are we just leaving a tunnel/bridge? */ rubidium@9809: if (GetTunnelBridgeDirection(tile) == ReverseDiagDir(dir)) { rubidium@9809: TileIndex end = GetOtherTunnelBridgeEnd(tile); rubidium@9809: michi_cc@10103: if (!HasVehicleOnTunnelBridge(tile, end, v)) { michi_cc@10103: /* Free the reservation only if no other train is on the tiles. */ michi_cc@10103: SetTunnelBridgeReservation(tile, false); michi_cc@10103: SetTunnelBridgeReservation(end, false); michi_cc@10103: michi_cc@10103: if (_settings_client.gui.show_track_reservation) { michi_cc@10103: MarkTileDirtyByTile(tile); michi_cc@10103: MarkTileDirtyByTile(end); michi_cc@10103: } rubidium@9809: } rubidium@9809: } rubidium@9809: } else if (IsRailwayStationTile(tile)) { rubidium@9809: TileIndex new_tile = TileAddByDiagDir(tile, dir); rubidium@9809: /* If the new tile is not a further tile of the same station, we rubidium@9809: * clear the reservation for the whole platform. */ rubidium@9809: if (!IsCompatibleTrainStationTile(new_tile, tile)) { rubidium@9809: SetRailwayStationPlatformReservation(tile, ReverseDiagDir(dir), false); rubidium@9809: } rubidium@9809: } else { rubidium@9809: /* Any other tile */ rubidium@9809: UnreserveRailTrack(tile, TrackdirToTrack(track_dir)); rubidium@9809: } rubidium@9809: } rubidium@9809: rubidium@9810: /** Free the reserved path in front of a vehicle. */ rubidium@9810: void FreeTrainTrackReservation(const Vehicle *v, TileIndex origin, Trackdir orig_td) rubidium@9810: { rubidium@9810: assert(IsFrontEngine(v)); rubidium@9810: rubidium@9810: TileIndex tile = origin != INVALID_TILE ? origin : v->tile; rubidium@9810: Trackdir td = orig_td != INVALID_TRACKDIR ? orig_td : GetVehicleTrackdir(v); rubidium@9810: bool free_tile = tile != v->tile || !(IsRailwayStationTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)); frosch@9974: StationID station_id = IsRailwayStationTile(v->tile) ? GetStationIndex(v->tile) : INVALID_STATION; rubidium@9810: rubidium@9810: /* Don't free reservation if it's not ours. */ rubidium@9810: if (TracksOverlap(GetReservedTrackbits(tile) | TrackToTrackBits(TrackdirToTrack(td)))) return; rubidium@9810: rubidium@9810: CFollowTrackRail ft(v, GetRailTypeInfo(v->u.rail.railtype)->compatible_railtypes); rubidium@9810: while (ft.Follow(tile, td)) { rubidium@9810: tile = ft.m_new_tile; rubidium@9810: TrackdirBits bits = (TrackdirBits)(ft.m_new_td_bits & (GetReservedTrackbits(tile) * 0x101)); rubidium@9810: td = RemoveFirstTrackdir(&bits); rubidium@9810: assert(bits == TRACKDIR_BIT_NONE); rubidium@9810: rubidium@9810: if (!IsValidTrackdir(td)) break; rubidium@9810: rubidium@9810: if (IsTileType(tile, MP_RAILWAY)) { rubidium@9810: if (HasSignalOnTrackdir(tile, td) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(td)))) { rubidium@9810: /* Conventional signal along trackdir: remove reservation and stop. */ rubidium@9810: UnreserveRailTrack(tile, TrackdirToTrack(td)); rubidium@9810: break; rubidium@9810: } rubidium@9810: if (HasPbsSignalOnTrackdir(tile, td)) { rubidium@9810: if (GetSignalStateByTrackdir(tile, td) == SIGNAL_STATE_RED) { rubidium@9810: /* Red PBS signal? Can't be our reservation, would be green then. */ rubidium@9810: break; rubidium@9810: } else { rubidium@9810: /* Turn the signal back to red. */ rubidium@9810: SetSignalStateByTrackdir(tile, td, SIGNAL_STATE_RED); rubidium@9810: MarkTileDirtyByTile(tile); rubidium@9810: } rubidium@9810: } else if (HasSignalOnTrackdir(tile, ReverseTrackdir(td)) && IsOnewaySignal(tile, TrackdirToTrack(td))) { rubidium@9810: break; rubidium@9810: } rubidium@9810: } rubidium@9810: rubidium@9810: /* Don't free first station/bridge/tunnel if we are on it. */ michi_cc@10103: if (free_tile || (!(ft.m_is_station && GetStationIndex(ft.m_new_tile) == station_id) && !ft.m_is_tunnel && !ft.m_is_bridge)) ClearPathReservation(v, tile, td); rubidium@9810: rubidium@9810: free_tile = true; rubidium@9810: } rubidium@9810: } rubidium@9810: smatz@8850: /** Check for station tiles */ rubidium@6248: struct TrainTrackFollowerData { truelight@0: TileIndex dest_coords; smatz@8850: StationID station_index; ///< station index we're heading for truelight@0: uint best_bird_dist; truelight@0: uint best_track_dist; rubidium@5587: TrackdirByte best_track; rubidium@6248: }; truelight@0: rubidium@5587: static bool NtpCallbFindStation(TileIndex tile, TrainTrackFollowerData *ttfd, Trackdir track, uint length) tron@1977: { belugas@6422: /* heading for nowhere? */ tron@2951: if (ttfd->dest_coords == 0) return false; truelight@0: belugas@6422: /* did we reach the final station? */ tron@3315: if ((ttfd->station_index == INVALID_STATION && tile == ttfd->dest_coords) || ( tron@3315: IsTileType(tile, MP_STATION) && celestar@3334: IsRailwayStation(tile) && tron@3315: GetStationIndex(tile) == ttfd->station_index tron@3315: )) { ludde@2044: /* We do not check for dest_coords if we have a station_index, ludde@2044: * because in that case the dest_coords are just an ludde@2044: * approximation of where the station is */ belugas@6422: belugas@6422: /* found station */ ludde@2125: ttfd->best_track = track; KUDr@6303: ttfd->best_bird_dist = 0; truelight@0: return true; truelight@0: } else { belugas@6422: /* didn't find station, keep track of the best path so far. */ tron@6150: uint dist = DistanceManhattan(tile, ttfd->dest_coords); truelight@0: if (dist < ttfd->best_bird_dist) { truelight@0: ttfd->best_bird_dist = dist; ludde@2125: ttfd->best_track = track; truelight@0: } truelight@0: return false; truelight@0: } truelight@0: } truelight@0: smatz@8850: static void FillWithStationData(TrainTrackFollowerData *fd, const Vehicle *v) truelight@0: { tron@2639: fd->dest_coords = v->dest_tile; rubidium@8840: fd->station_index = v->current_order.IsType(OT_GOTO_STATION) ? v->current_order.GetDestination() : INVALID_STATION; truelight@0: } truelight@0: truelight@0: static const byte _initial_tile_subcoord[6][4][3] = { rubidium@4344: {{ 15, 8, 1 }, { 0, 0, 0 }, { 0, 8, 5 }, { 0, 0, 0 }}, rubidium@4344: {{ 0, 0, 0 }, { 8, 0, 3 }, { 0, 0, 0 }, { 8, 15, 7 }}, rubidium@4344: {{ 0, 0, 0 }, { 7, 0, 2 }, { 0, 7, 6 }, { 0, 0, 0 }}, rubidium@4344: {{ 15, 8, 2 }, { 0, 0, 0 }, { 0, 0, 0 }, { 8, 15, 6 }}, rubidium@4344: {{ 15, 7, 0 }, { 8, 0, 4 }, { 0, 0, 0 }, { 0, 0, 0 }}, rubidium@4344: {{ 0, 0, 0 }, { 0, 0, 0 }, { 0, 8, 4 }, { 7, 15, 0 }}, truelight@0: }; truelight@0: truelight@0: static const byte _search_directions[6][4] = { belugas@6422: { 0, 9, 2, 9 }, ///< track 1 belugas@6422: { 9, 1, 9, 3 }, ///< track 2 belugas@6422: { 9, 0, 3, 9 }, ///< track upper belugas@6422: { 1, 9, 9, 2 }, ///< track lower belugas@6422: { 3, 2, 9, 9 }, ///< track left belugas@6422: { 9, 9, 1, 0 }, ///< track right truelight@0: }; truelight@0: truelight@0: static const byte _pick_track_table[6] = {1, 3, 2, 2, 0, 0}; truelight@0: rubidium@9810: /** rubidium@9810: * Perform pathfinding for a train. rubidium@9810: * rubidium@9810: * @param v The train rubidium@9810: * @param tile The tile the train is about to enter rubidium@9810: * @param enterdir Diagonal direction the train is coming from rubidium@9810: * @param tracks Usable tracks on the new tile rubidium@9810: * @param path_not_found [out] Set to false if the pathfinder couldn't find a way to the destination rubidium@9810: * @param do_track_reservation rubidium@9810: * @param dest [out] rubidium@9810: * @return The best track the train should follow rubidium@9810: */ rubidium@9810: static Track DoTrainPathfind(Vehicle* v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool *path_not_found, bool do_track_reservation, PBSTileInfo *dest) truelight@193: { rubidium@5587: Track best_track; KUDr@4870: peter1138@2758: #ifdef PF_BENCHMARK Darkvater@3341: TIC() truelight@0: #endif truelight@0: rubidium@9810: if (path_not_found) *path_not_found = false; rubidium@9810: rubidium@9810: uint8 pathfinder = _settings_game.pf.pathfinder_for_trains; rubidium@9810: if (do_track_reservation && pathfinder == VPF_NTP) pathfinder = VPF_NPF; rubidium@9810: rubidium@9810: switch (pathfinder) { smatz@8554: case VPF_YAPF: { /* YAPF */ rubidium@9810: Trackdir trackdir = YapfChooseRailTrack(v, tile, enterdir, tracks, path_not_found, do_track_reservation, dest); smatz@8554: if (trackdir != INVALID_TRACKDIR) { smatz@8554: best_track = TrackdirToTrack(trackdir); smatz@8554: } else { smatz@8554: best_track = FindFirstTrack(tracks); smatz@8554: } smatz@8554: } break; smatz@8554: smatz@8554: case VPF_NPF: { /* NPF */ smatz@8554: void *perf = NpfBeginInterval(); smatz@8554: smatz@8554: NPFFindStationOrTileData fstd; rubidium@9810: NPFFillWithOrderData(&fstd, v, do_track_reservation); rubidium@9810: rubidium@9810: PBSTileInfo origin = FollowTrainReservation(v); rubidium@9810: assert(IsValidTrackdir(origin.trackdir)); rubidium@9810: rubidium@9810: NPFFoundTargetData ftd = NPFRouteToStationOrTile(origin.tile, origin.trackdir, true, &fstd, TRANSPORT_RAIL, 0, v->owner, v->u.rail.compatible_railtypes); rubidium@9810: rubidium@9810: if (dest != NULL) { rubidium@9810: dest->tile = ftd.node.tile; rubidium@9810: dest->trackdir = (Trackdir)ftd.node.direction; rubidium@9810: dest->okay = ftd.res_okay; rubidium@9810: } smatz@8554: smatz@8554: if (ftd.best_trackdir == INVALID_TRACKDIR) { smatz@8554: /* We are already at our target. Just do something smatz@8554: * @todo maybe display error? smatz@8554: * @todo: go straight ahead if possible? */ smatz@8554: best_track = FindFirstTrack(tracks); smatz@8554: } else { smatz@8554: /* If ftd.best_bird_dist is 0, we found our target and ftd.best_trackdir contains smatz@8554: * the direction we need to take to get there, if ftd.best_bird_dist is not 0, smatz@8554: * we did not find our target, but ftd.best_trackdir contains the direction leading smatz@8554: * to the tile closest to our target. */ rubidium@9810: if (ftd.best_bird_dist != 0 && path_not_found != NULL) *path_not_found = true; smatz@8554: /* Discard enterdir information, making it a normal track */ smatz@8554: best_track = TrackdirToTrack(ftd.best_trackdir); smatz@8554: } smatz@8554: smatz@8554: int time = NpfEndInterval(perf); smatz@8554: DEBUG(yapf, 4, "[NPFT] %d us - %d rounds - %d open - %d closed -- ", time, 0, _aystar_stats_open_size, _aystar_stats_closed_size); smatz@8554: } break; smatz@8554: smatz@8554: default: smatz@8554: case VPF_NTP: { /* NTP */ smatz@8554: void *perf = NpfBeginInterval(); smatz@8554: smatz@8554: TrainTrackFollowerData fd; smatz@8554: FillWithStationData(&fd, v); smatz@8554: smatz@8554: /* New train pathfinding */ smatz@8554: fd.best_bird_dist = UINT_MAX; smatz@8554: fd.best_track_dist = UINT_MAX; smatz@8554: fd.best_track = INVALID_TRACKDIR; smatz@8554: smatz@8554: NewTrainPathfind(tile - TileOffsByDiagDir(enterdir), v->dest_tile, smatz@8554: v->u.rail.compatible_railtypes, enterdir, (NTPEnumProc*)NtpCallbFindStation, &fd); smatz@8554: smatz@8554: /* check whether the path was found or only 'guessed' */ rubidium@9810: if (fd.best_bird_dist != 0 && path_not_found != NULL) *path_not_found = true; smatz@8554: smatz@8554: if (fd.best_track == INVALID_TRACKDIR) { smatz@8554: /* blaha */ smatz@8554: best_track = FindFirstTrack(tracks); smatz@8554: } else { smatz@8554: best_track = TrackdirToTrack(fd.best_track); smatz@8554: } smatz@8554: smatz@8554: int time = NpfEndInterval(perf); smatz@8554: DEBUG(yapf, 4, "[NTPT] %d us - %d rounds - %d open - %d closed -- ", time, 0, 0, 0); smatz@8554: } break; truelight@0: } smatz@8554: rubidium@9810: #ifdef PF_BENCHMARK rubidium@9810: TOC("PF time = ", 1) rubidium@9810: #endif rubidium@9810: rubidium@9810: return best_track; rubidium@9810: } rubidium@9810: rubidium@9810: /** rubidium@9817: * Extend a train path as far as possible. Stops on encountering a safe tile, rubidium@9817: * another reservation or a track choice. rubidium@9817: * @return INVALID_TILE indicates that the reservation failed. rubidium@9817: */ rubidium@9817: static PBSTileInfo ExtendTrainReservation(const Vehicle *v, TrackBits *new_tracks, DiagDirection *enterdir) rubidium@9817: { rubidium@9817: bool no_90deg_turns = _settings_game.pf.pathfinder_for_trains != VPF_NTP && _settings_game.pf.forbid_90_deg; rubidium@9817: PBSTileInfo origin = FollowTrainReservation(v); rubidium@9817: rubidium@9817: CFollowTrackRail ft(v); rubidium@9817: rubidium@9817: TileIndex tile = origin.tile; rubidium@9817: Trackdir cur_td = origin.trackdir; rubidium@9817: while (ft.Follow(tile, cur_td)) { rubidium@9817: if (KillFirstBit(ft.m_new_td_bits) == TRACKDIR_BIT_NONE) { rubidium@9817: /* Possible signal tile. */ rubidium@9817: if (HasOnewaySignalBlockingTrackdir(ft.m_new_tile, FindFirstTrackdir(ft.m_new_td_bits))) break; rubidium@9817: } rubidium@9817: rubidium@9817: if (no_90deg_turns) { rubidium@9817: ft.m_new_td_bits &= ~TrackdirCrossesTrackdirs(ft.m_old_td); rubidium@9817: if (ft.m_new_td_bits == TRACKDIR_BIT_NONE) break; rubidium@9817: } rubidium@9817: rubidium@9817: /* Station, depot or waypoint are a possible target. */ rubidium@9817: bool target_seen = ft.m_is_station || (IsTileType(ft.m_new_tile, MP_RAILWAY) && !IsPlainRailTile(ft.m_new_tile)); rubidium@9817: if (target_seen || KillFirstBit(ft.m_new_td_bits) != TRACKDIR_BIT_NONE) { rubidium@9817: /* Choice found or possible target encountered. rubidium@9817: * On finding a possible target, we need to stop and let the pathfinder handle the rubidium@9817: * remaining path. This is because we don't know if this target is in one of our rubidium@9817: * orders, so we might cause pathfinding to fail later on if we find a choice. rubidium@9817: * This failure would cause a bogous call to TryReserveSafePath which might reserve rubidium@9817: * a wrong path not leading to our next destination. */ rubidium@9817: if (HasReservedTracks(ft.m_new_tile, TrackdirBitsToTrackBits(TrackdirReachesTrackdirs(ft.m_old_td)))) break; rubidium@9817: rubidium@9817: /* If we did skip some tiles, backtrack to the first skipped tile so the pathfinder rubidium@9817: * actually starts its search at the first unreserved tile. */ rubidium@9817: if (ft.m_tiles_skipped != 0) ft.m_new_tile -= TileOffsByDiagDir(ft.m_exitdir) * ft.m_tiles_skipped; rubidium@9817: rubidium@9817: /* Choice found, path valid but not okay. Save info about the choice tile as well. */ rubidium@9817: if (new_tracks) *new_tracks = TrackdirBitsToTrackBits(ft.m_new_td_bits); rubidium@9817: if (enterdir) *enterdir = ft.m_exitdir; rubidium@9817: return PBSTileInfo(ft.m_new_tile, ft.m_old_td, false); rubidium@9817: } rubidium@9817: rubidium@9817: tile = ft.m_new_tile; rubidium@9817: cur_td = FindFirstTrackdir(ft.m_new_td_bits); rubidium@9817: rubidium@9817: if (IsSafeWaitingPosition(v, tile, cur_td, true, no_90deg_turns)) { rubidium@9817: bool wp_free = IsWaitingPositionFree(v, tile, cur_td, no_90deg_turns); rubidium@9817: if (!(wp_free && TryReserveRailTrack(tile, TrackdirToTrack(cur_td)))) break; rubidium@9817: /* Safe position is all good, path valid and okay. */ rubidium@9817: return PBSTileInfo(tile, cur_td, true); rubidium@9817: } rubidium@9817: rubidium@9817: if (!TryReserveRailTrack(tile, TrackdirToTrack(cur_td))) break; rubidium@9817: } rubidium@9817: rubidium@10186: if (ft.m_err == CFollowTrackRail::EC_OWNER || ft.m_err == CFollowTrackRail::EC_NO_WAY) { rubidium@9817: /* End of line, path valid and okay. */ rubidium@9817: return PBSTileInfo(ft.m_old_tile, ft.m_old_td, true); rubidium@9817: } rubidium@9817: rubidium@9817: /* Sorry, can't reserve path, back out. */ rubidium@9817: tile = origin.tile; rubidium@9817: cur_td = origin.trackdir; rubidium@9817: TileIndex stopped = ft.m_old_tile; rubidium@9817: Trackdir stopped_td = ft.m_old_td; rubidium@9817: while (tile != stopped || cur_td != stopped_td) { rubidium@9817: if (!ft.Follow(tile, cur_td)) break; rubidium@9817: rubidium@9817: if (no_90deg_turns) { rubidium@9817: ft.m_new_td_bits &= ~TrackdirCrossesTrackdirs(ft.m_old_td); rubidium@9817: assert(ft.m_new_td_bits != TRACKDIR_BIT_NONE); rubidium@9817: } rubidium@9817: assert(KillFirstBit(ft.m_new_td_bits) == TRACKDIR_BIT_NONE); rubidium@9817: rubidium@9817: tile = ft.m_new_tile; rubidium@9817: cur_td = FindFirstTrackdir(ft.m_new_td_bits); rubidium@9817: rubidium@9817: UnreserveRailTrack(tile, TrackdirToTrack(cur_td)); rubidium@9817: } rubidium@9817: rubidium@9817: /* Path invalid. */ rubidium@9817: return PBSTileInfo(); rubidium@9817: } rubidium@9817: rubidium@9817: /** rubidium@9810: * Try to reserve any path to a safe tile, ignoring the vehicle's destination. rubidium@9810: * Safe tiles are tiles in front of a signal, depots and station tiles at end of line. rubidium@9810: * rubidium@9810: * @param v The vehicle. rubidium@9810: * @param tile The tile the search should start from. rubidium@9810: * @param td The trackdir the search should start from. rubidium@9810: * @param override_tailtype Whether all physically compatible railtypes should be followed. rubidium@9810: * @return True if a path to a safe stopping tile could be reserved. rubidium@9810: */ rubidium@9810: static bool TryReserveSafeTrack(const Vehicle* v, TileIndex tile, Trackdir td, bool override_tailtype) rubidium@9810: { rubidium@9810: if (_settings_game.pf.pathfinder_for_trains == VPF_YAPF) { rubidium@9810: return YapfRailFindNearestSafeTile(v, tile, td, override_tailtype); rubidium@9810: } else { rubidium@9810: return NPFRouteToSafeTile(v, tile, td, override_tailtype).res_okay; rubidium@9810: } rubidium@9810: } rubidium@9810: michi_cc@10251: /** This class will save the current order of a vehicle and restore it on destruction. */ michi_cc@10251: class VehicleOrderSaver rubidium@9812: { michi_cc@10251: private: michi_cc@10251: Vehicle *v; michi_cc@10251: Order old_order; michi_cc@10251: TileIndex old_dest_tile; rubidium@10343: StationID old_last_station_visited; michi_cc@10251: VehicleOrderID index; michi_cc@10251: michi_cc@10251: public: rubidium@10343: VehicleOrderSaver(Vehicle *_v) : rubidium@10343: v(_v), rubidium@10343: old_order(_v->current_order), rubidium@10343: old_dest_tile(_v->dest_tile), rubidium@10343: old_last_station_visited(_v->last_station_visited), rubidium@10343: index(_v->cur_order_index) michi_cc@10251: { michi_cc@10251: } michi_cc@10251: michi_cc@10251: ~VehicleOrderSaver() michi_cc@10251: { michi_cc@10251: this->v->current_order = this->old_order; michi_cc@10251: this->v->dest_tile = this->old_dest_tile; rubidium@10343: this->v->last_station_visited = this->old_last_station_visited; michi_cc@10251: } michi_cc@10251: michi_cc@10251: /** michi_cc@10251: * Set the current vehicle order to the next order in the order list. michi_cc@10251: * @return True if a suitable next order could be found. michi_cc@10251: */ michi_cc@10251: bool SwitchToNextOrder() michi_cc@10251: { michi_cc@10251: ++this->index; michi_cc@10251: michi_cc@10251: do { michi_cc@10251: /* Wrap around. */ michi_cc@10251: if (this->index >= this->v->num_orders) this->index = 0; michi_cc@10251: michi_cc@10251: Order *order = GetVehicleOrder(this->v, this->index); michi_cc@10251: assert(order != NULL); michi_cc@10251: michi_cc@10251: switch (order->GetType()) { michi_cc@10251: case OT_GOTO_DEPOT: michi_cc@10251: /* Skip service in depot orders when the train doesn't need service. */ michi_cc@10251: if ((order->GetDepotOrderType() & ODTFB_SERVICE) && !this->v->NeedsServicing()) break; michi_cc@10251: case OT_GOTO_STATION: michi_cc@10251: case OT_GOTO_WAYPOINT: michi_cc@10251: this->v->current_order = *order; michi_cc@10251: UpdateOrderDest(this->v, order); michi_cc@10251: return true; michi_cc@10251: case OT_CONDITIONAL: { michi_cc@10251: VehicleOrderID next = ProcessConditionalOrder(order, this->v); michi_cc@10251: if (next != INVALID_VEH_ORDER_ID) { michi_cc@10251: this->index = next; michi_cc@10261: /* Don't increment next, so no break here. */ michi_cc@10251: continue; michi_cc@10251: } michi_cc@10251: break; rubidium@9812: } michi_cc@10251: default: michi_cc@10251: break; rubidium@9812: } michi_cc@10261: /* Don't increment inside the while because otherwise conditional michi_cc@10261: * orders can lead to an infinite loop. */ michi_cc@10261: ++this->index; michi_cc@10261: } while (this->index != this->v->cur_order_index); michi_cc@10251: michi_cc@10251: return false; michi_cc@10251: } michi_cc@10251: }; rubidium@9812: rubidium@9810: /* choose a track */ rubidium@9810: static Track ChooseTrainTrack(Vehicle* v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool force_res, bool *got_reservation, bool mark_stuck) rubidium@9810: { rubidium@9810: Track best_track = INVALID_TRACK; rubidium@9810: bool do_track_reservation = _settings_game.pf.reserve_paths || force_res; rubidium@9810: bool changed_signal = false; rubidium@9810: rubidium@9810: assert((tracks & ~TRACK_BIT_MASK) == 0); rubidium@9810: rubidium@9810: if (got_reservation != NULL) *got_reservation = false; rubidium@9810: rubidium@9810: /* Don't use tracks here as the setting to forbid 90 deg turns might have been switched between reservation and now. */ rubidium@9810: TrackBits res_tracks = (TrackBits)(GetReservedTrackbits(tile) & DiagdirReachesTracks(enterdir)); rubidium@9810: /* Do we have a suitable reserved track? */ rubidium@9810: if (res_tracks != TRACK_BIT_NONE) return FindFirstTrack(res_tracks); rubidium@9810: rubidium@9810: /* Quick return in case only one possible track is available */ rubidium@9810: if (KillFirstBit(tracks) == TRACK_BIT_NONE) { rubidium@9810: Track track = FindFirstTrack(tracks); rubidium@9810: /* We need to check for signals only here, as a junction tile can't have signals. */ rubidium@9810: if (track != INVALID_TRACK && HasPbsSignalOnTrackdir(tile, TrackEnterdirToTrackdir(track, enterdir))) { rubidium@9810: do_track_reservation = true; rubidium@9810: changed_signal = true; rubidium@9810: SetSignalStateByTrackdir(tile, TrackEnterdirToTrackdir(track, enterdir), SIGNAL_STATE_GREEN); rubidium@9810: } else if (!do_track_reservation) { rubidium@9810: return track; rubidium@9810: } rubidium@9810: best_track = track; rubidium@9810: } rubidium@9810: rubidium@9817: PBSTileInfo res_dest(tile, INVALID_TRACKDIR, false); rubidium@9817: DiagDirection dest_enterdir = enterdir; rubidium@9810: if (do_track_reservation) { rubidium@9822: /* Check if the train needs service here, so it has a chance to always find a depot. rubidium@9822: * Also check if the current order is a service order so we don't reserve a path to rubidium@9822: * the destination but instead to the next one if service isn't needed. */ rubidium@9822: CheckIfTrainNeedsService(v); rubidium@9822: if (v->current_order.IsType(OT_DUMMY) || v->current_order.IsType(OT_CONDITIONAL) || v->current_order.IsType(OT_GOTO_DEPOT)) ProcessOrders(v); rubidium@9817: rubidium@9817: res_dest = ExtendTrainReservation(v, &tracks, &dest_enterdir); rubidium@9817: if (res_dest.tile == INVALID_TILE) { rubidium@9817: /* Reservation failed? */ rubidium@9817: if (mark_stuck) MarkTrainAsStuck(v); rubidium@9817: if (changed_signal) SetSignalStateByTrackdir(tile, TrackEnterdirToTrackdir(best_track, enterdir), SIGNAL_STATE_RED); rubidium@9817: return FindFirstTrack(tracks); rubidium@9817: } rubidium@9810: } rubidium@9810: michi_cc@10252: /* Save the current train order. The destructor will restore the old order on function exit. */ michi_cc@10252: VehicleOrderSaver orders(v); michi_cc@10252: michi_cc@10252: /* If the current tile is the destination of the current order and michi_cc@10307: * a reservation was requested, advance to the next order. michi_cc@10307: * Don't advance on a depot order as depots are always safe end points michi_cc@10307: * for a path and no look-ahead is necessary. This also avoids a michi_cc@10307: * problem with depot orders not part of the order list when the michi_cc@10307: * order list itself is empty. */ michi_cc@10307: if (!v->current_order.IsType(OT_GOTO_DEPOT) && (v->tile == v->dest_tile || (v->current_order.IsType(OT_GOTO_STATION) && IsRailwayStationTile(v->tile) && v->current_order.GetDestination() == GetStationIndex(v->tile)))) { michi_cc@10252: orders.SwitchToNextOrder(); michi_cc@10252: } michi_cc@10252: rubidium@9817: if (res_dest.tile != INVALID_TILE && !res_dest.okay) { rubidium@9817: /* Pathfinders are able to tell that route was only 'guessed'. */ rubidium@9817: bool path_not_found = false; rubidium@9817: TileIndex new_tile = res_dest.tile; rubidium@9817: rubidium@9817: Track next_track = DoTrainPathfind(v, new_tile, dest_enterdir, tracks, &path_not_found, do_track_reservation, &res_dest); rubidium@9817: if (new_tile == tile) best_track = next_track; rubidium@9817: rubidium@9817: /* handle "path not found" state */ rubidium@9817: if (path_not_found) { rubidium@9817: /* PF didn't find the route */ rubidium@9817: if (!HasBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) { rubidium@9817: /* it is first time the problem occurred, set the "path not found" flag */ rubidium@9817: SetBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION); rubidium@9817: /* and notify user about the event */ rubidium@10207: if (_settings_client.gui.lost_train_warn && v->owner == _local_company) { rubidium@9817: SetDParam(0, v->unitnumber); rubidium@9817: AddNewsItem( rubidium@9817: STR_TRAIN_IS_LOST, rubidium@9817: NS_ADVICE, rubidium@9817: v->index, rubidium@9817: 0); rubidium@9817: } KUDr@4870: } rubidium@9817: } else { rubidium@9817: /* route found, is the train marked with "path not found" flag? */ rubidium@9817: if (HasBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) { rubidium@9817: /* clear the flag as the PF's problem was solved */ rubidium@9817: ClrBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION); rubidium@9817: /* can we also delete the "News" item somehow? */ rubidium@9817: } KUDr@4870: } KUDr@4870: } truelight@0: rubidium@9810: /* No track reservation requested -> finished. */ rubidium@9810: if (!do_track_reservation) return best_track; rubidium@9810: rubidium@9810: /* A path was found, but could not be reserved. */ rubidium@9810: if (res_dest.tile != INVALID_TILE && !res_dest.okay) { rubidium@9810: if (mark_stuck) MarkTrainAsStuck(v); rubidium@9810: FreeTrainTrackReservation(v); rubidium@9810: return best_track; rubidium@9810: } rubidium@9810: rubidium@9810: /* No possible reservation target found, we are probably lost. */ rubidium@9810: if (res_dest.tile == INVALID_TILE) { rubidium@9810: /* Try to find any safe destination. */ rubidium@9810: PBSTileInfo origin = FollowTrainReservation(v); rubidium@9810: if (TryReserveSafeTrack(v, origin.tile, origin.trackdir, false)) { rubidium@9810: TrackBits res = GetReservedTrackbits(tile) & DiagdirReachesTracks(enterdir); rubidium@9810: best_track = FindFirstTrack(res); rubidium@9810: TryReserveRailTrack(v->tile, TrackdirToTrack(GetVehicleTrackdir(v))); rubidium@9810: if (got_reservation != NULL) *got_reservation = true; rubidium@9810: if (changed_signal) MarkTileDirtyByTile(tile); rubidium@9810: } else { rubidium@9810: FreeTrainTrackReservation(v); rubidium@9810: if (mark_stuck) MarkTrainAsStuck(v); rubidium@9810: } rubidium@9810: return best_track; rubidium@9810: } rubidium@9810: rubidium@9810: if (got_reservation != NULL) *got_reservation = true; rubidium@9810: rubidium@9812: /* Reservation target found and free, check if it is safe. */ rubidium@9812: while (!IsSafeWaitingPosition(v, res_dest.tile, res_dest.trackdir, true, _settings_game.pf.forbid_90_deg)) { rubidium@9812: /* Extend reservation until we have found a safe position. */ rubidium@9812: DiagDirection exitdir = TrackdirToExitdir(res_dest.trackdir); rubidium@9812: TileIndex next_tile = TileAddByDiagDir(res_dest.tile, exitdir); rubidium@9812: TrackBits reachable = TrackdirBitsToTrackBits((TrackdirBits)(GetTileTrackStatus(next_tile, TRANSPORT_RAIL, 0))) & DiagdirReachesTracks(exitdir); rubidium@9812: if (_settings_game.pf.pathfinder_for_trains != VPF_NTP && _settings_game.pf.forbid_90_deg) { rubidium@9812: reachable &= ~TrackCrossesTracks(TrackdirToTrack(res_dest.trackdir)); rubidium@9812: } rubidium@9812: rubidium@9812: /* Get next order with destination. */ michi_cc@10251: if (orders.SwitchToNextOrder()) { rubidium@9812: PBSTileInfo cur_dest; rubidium@9812: DoTrainPathfind(v, next_tile, exitdir, reachable, NULL, true, &cur_dest); rubidium@9812: if (cur_dest.tile != INVALID_TILE) { rubidium@9812: res_dest = cur_dest; rubidium@9812: if (res_dest.okay) continue; rubidium@9812: /* Path found, but could not be reserved. */ rubidium@9812: FreeTrainTrackReservation(v); rubidium@9812: if (mark_stuck) MarkTrainAsStuck(v); rubidium@9812: if (got_reservation != NULL) *got_reservation = false; rubidium@9812: changed_signal = false; rubidium@9812: break; rubidium@9812: } rubidium@9812: } rubidium@9812: /* No order or no safe position found, try any position. */ rubidium@9812: if (!TryReserveSafeTrack(v, res_dest.tile, res_dest.trackdir, true)) { rubidium@9812: FreeTrainTrackReservation(v); rubidium@9812: if (mark_stuck) MarkTrainAsStuck(v); rubidium@9812: if (got_reservation != NULL) *got_reservation = false; rubidium@9812: changed_signal = false; rubidium@9812: } rubidium@9812: break; rubidium@9812: } rubidium@9812: rubidium@9810: TryReserveRailTrack(v->tile, TrackdirToTrack(GetVehicleTrackdir(v))); rubidium@9810: rubidium@9810: if (changed_signal) MarkTileDirtyByTile(tile); truelight@0: truelight@0: return best_track; truelight@0: } truelight@0: rubidium@9813: /** rubidium@9813: * Try to reserve a path to a safe position. rubidium@9813: * rubidium@9813: * @param v The vehicle rubidium@9813: * @return True if a path could be reserved rubidium@9813: */ rubidium@9828: bool TryPathReserve(Vehicle *v, bool mark_as_stuck, bool first_tile_okay) rubidium@9813: { rubidium@9813: assert(v->type == VEH_TRAIN && IsFrontEngine(v)); rubidium@9813: rubidium@9813: /* We have to handle depots specially as the track follower won't look rubidium@9813: * at the depot tile itself but starts from the next tile. If we are still rubidium@9813: * inside the depot, a depot reservation can never be ours. */ rubidium@9813: if (v->u.rail.track & TRACK_BIT_DEPOT) { rubidium@9813: if (GetDepotWaypointReservation(v->tile)) { rubidium@9813: if (mark_as_stuck) MarkTrainAsStuck(v); rubidium@9813: return false; rubidium@9813: } else { rubidium@9813: /* Depot not reserved, but the next tile might be. */ rubidium@9813: TileIndex next_tile = TileAddByDiagDir(v->tile, GetRailDepotDirection(v->tile)); rubidium@9813: if (HasReservedTracks(next_tile, DiagdirReachesTracks(GetRailDepotDirection(v->tile)))) return false; rubidium@9813: } rubidium@9813: } rubidium@9813: rubidium@9813: /* Special check if we are in front of a two-sided conventional signal. */ rubidium@9813: DiagDirection dir = TrainExitDir(v->direction, v->u.rail.track); rubidium@9813: TileIndex next_tile = TileAddByDiagDir(v->tile, dir); rubidium@9813: if (IsTileType(next_tile, MP_RAILWAY) && HasReservedTracks(next_tile, DiagdirReachesTracks(dir))) { rubidium@9813: /* Can have only one reserved trackdir. */ rubidium@9813: Trackdir td = FindFirstTrackdir((TrackdirBits)(GetReservedTrackbits(next_tile) * 0x101 & DiagdirReachesTrackdirs(dir))); rubidium@9813: if (HasSignalOnTrackdir(next_tile, td) && HasSignalOnTrackdir(next_tile, ReverseTrackdir(td)) && rubidium@9813: !IsPbsSignal(GetSignalType(next_tile, TrackdirToTrack(td)))) { rubidium@9813: /* Signal already reserved, is not ours. */ rubidium@9813: if (mark_as_stuck) MarkTrainAsStuck(v); rubidium@9813: return false; rubidium@9813: } rubidium@9813: } rubidium@9813: rubidium@10083: bool other_train = false; frosch@9831: PBSTileInfo origin = FollowTrainReservation(v, &other_train); rubidium@9813: /* If we have a reserved path and the path ends at a safe tile, we are finished already. */ rubidium@9813: if (origin.okay && (v->tile != origin.tile || first_tile_okay)) { rubidium@9813: /* Can't be stuck then. */ rubidium@9813: if (HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); rubidium@9813: ClrBit(v->u.rail.flags, VRF_TRAIN_STUCK); rubidium@9813: return true; rubidium@9813: } frosch@9831: /* The path we are driving on is alread blocked by some other train. frosch@9831: * This can only happen when tracks and signals are changed. A crash frosch@9831: * is probably imminent, don't do any further reservation because frosch@9831: * it might cause stale reservations. */ rubidium@10083: if (other_train && v->tile != origin.tile) { frosch@9831: if (mark_as_stuck) MarkTrainAsStuck(v); frosch@9831: return false; frosch@9831: } rubidium@9813: rubidium@9813: /* If we are in a depot, tentativly reserve the depot. */ rubidium@9813: if (v->u.rail.track & TRACK_BIT_DEPOT) { rubidium@9813: SetDepotWaypointReservation(v->tile, true); rubidium@9813: if (_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(v->tile); rubidium@9813: } rubidium@9813: rubidium@9813: DiagDirection exitdir = TrackdirToExitdir(origin.trackdir); rubidium@9813: TileIndex new_tile = TileAddByDiagDir(origin.tile, exitdir); rubidium@9813: TrackBits reachable = TrackdirBitsToTrackBits((TrackdirBits)GetTileTrackStatus(new_tile, TRANSPORT_RAIL, 0) & DiagdirReachesTrackdirs(exitdir)); rubidium@9813: rubidium@9813: if (_settings_game.pf.pathfinder_for_trains != VPF_NTP && _settings_game.pf.forbid_90_deg) reachable &= ~TrackCrossesTracks(TrackdirToTrack(origin.trackdir)); rubidium@9813: rubidium@9813: bool res_made = false; rubidium@9813: ChooseTrainTrack(v, new_tile, exitdir, reachable, true, &res_made, mark_as_stuck); rubidium@9813: rubidium@9813: if (!res_made) { rubidium@9813: /* Free the depot reservation as well. */ rubidium@9813: if (v->u.rail.track & TRACK_BIT_DEPOT) SetDepotWaypointReservation(v->tile, false); rubidium@9813: return false; rubidium@9813: } rubidium@9813: rubidium@9813: if (HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); rubidium@9813: ClrBit(v->u.rail.flags, VRF_TRAIN_STUCK); rubidium@9813: return true; rubidium@9813: } rubidium@9813: truelight@0: truelight@0: static bool CheckReverseTrain(Vehicle *v) truelight@0: { rubidium@9413: if (_settings_game.difficulty.line_reverse_mode != 0 || rubidium@5993: v->u.rail.track == TRACK_BIT_DEPOT || v->u.rail.track == TRACK_BIT_WORMHOLE || smatz@8850: !(v->direction & 1)) { truelight@0: return false; smatz@8850: } truelight@0: tron@6150: TrainTrackFollowerData fd; truelight@0: FillWithStationData(&fd, v); truelight@0: tron@6150: uint reverse_best = 0; truelight@0: truelight@0: assert(v->u.rail.track); truelight@0: rubidium@9413: switch (_settings_game.pf.pathfinder_for_trains) { smatz@8850: case VPF_YAPF: /* YAPF */ smatz@8554: reverse_best = YapfCheckReverseTrain(v); smatz@8850: break; smatz@8554: smatz@8554: case VPF_NPF: { /* NPF */ smatz@8554: NPFFindStationOrTileData fstd; smatz@8554: NPFFoundTargetData ftd; smatz@8850: Vehicle *last = GetLastVehicleInChain(v); smatz@8554: smatz@8554: NPFFillWithOrderData(&fstd, v); smatz@8554: smatz@8554: Trackdir trackdir = GetVehicleTrackdir(v); smatz@8554: Trackdir trackdir_rev = ReverseTrackdir(GetVehicleTrackdir(last)); smatz@8554: assert(trackdir != INVALID_TRACKDIR); smatz@8554: assert(trackdir_rev != INVALID_TRACKDIR); smatz@8554: smatz@8554: ftd = NPFRouteToStationOrTileTwoWay(v->tile, trackdir, false, last->tile, trackdir_rev, false, &fstd, TRANSPORT_RAIL, 0, v->owner, v->u.rail.compatible_railtypes); smatz@8554: if (ftd.best_bird_dist != 0) { smatz@8554: /* We didn't find anything, just keep on going straight ahead */ smatz@8554: reverse_best = false; tron@3017: } else { smatz@8554: if (NPFGetFlag(&ftd.node, NPF_FLAG_REVERSE)) { smatz@8554: reverse_best = true; smatz@8554: } else { smatz@8554: reverse_best = false; smatz@8554: } tron@3017: } smatz@8554: } break; smatz@8554: smatz@8554: default: smatz@8554: case VPF_NTP: { /* NTP */ frosch@8794: int i = _search_directions[FindFirstTrack(v->u.rail.track)][DirToDiagDir(v->direction)]; frosch@8794: smatz@8554: int best_track = -1; smatz@8554: uint reverse = 0; smatz@8554: uint best_bird_dist = 0; smatz@8554: uint best_track_dist = 0; smatz@8554: smatz@8554: for (;;) { smatz@8554: fd.best_bird_dist = UINT_MAX; smatz@8554: fd.best_track_dist = UINT_MAX; smatz@8554: smatz@8554: NewTrainPathfind(v->tile, v->dest_tile, v->u.rail.compatible_railtypes, (DiagDirection)(reverse ^ i), (NTPEnumProc*)NtpCallbFindStation, &fd); smatz@8554: smatz@8554: if (best_track != -1) { smatz@8554: if (best_bird_dist != 0) { smatz@8554: if (fd.best_bird_dist != 0) { smatz@8554: /* neither reached the destination, pick the one with the smallest bird dist */ smatz@8554: if (fd.best_bird_dist > best_bird_dist) goto bad; smatz@8554: if (fd.best_bird_dist < best_bird_dist) goto good; smatz@8554: } else { smatz@8554: /* we found the destination for the first time */ smatz@8554: goto good; smatz@8554: } matthijs@1247: } else { smatz@8554: if (fd.best_bird_dist != 0) { smatz@8554: /* didn't find destination, but we've found the destination previously */ smatz@8554: goto bad; smatz@8554: } else { smatz@8554: /* both old & new reached the destination, compare track length */ smatz@8554: if (fd.best_track_dist > best_track_dist) goto bad; smatz@8554: if (fd.best_track_dist < best_track_dist) goto good; smatz@8554: } matthijs@1247: } smatz@8554: smatz@8554: /* if we reach this position, there's two paths of equal value so far. smatz@8554: * pick one randomly. */ smatz@8554: int r = GB(Random(), 0, 8); smatz@8554: if (_pick_track_table[i] == (v->direction & 3)) r += 80; smatz@8554: if (_pick_track_table[best_track] == (v->direction & 3)) r -= 80; smatz@8554: if (r <= 127) goto bad; truelight@0: } smatz@8554: good:; smatz@8554: best_track = i; smatz@8554: best_bird_dist = fd.best_bird_dist; smatz@8554: best_track_dist = fd.best_track_dist; smatz@8554: reverse_best = reverse; smatz@8554: bad:; smatz@8554: if (reverse != 0) break; smatz@8554: reverse = 2; truelight@0: } smatz@8554: } break; truelight@0: } truelight@0: truelight@0: return reverse_best != 0; truelight@0: } truelight@0: rubidium@8827: TileIndex Train::GetOrderStationLocation(StationID station) truelight@0: { rubidium@8830: if (station == this->last_station_visited) this->last_station_visited = INVALID_STATION; rubidium@8830: rubidium@8928: const Station *st = GetStation(station); rubidium@8928: if (!(st->facilities & FACIL_TRAIN)) { rubidium@8928: /* The destination station has no trainstation tiles. */ rubidium@8928: this->cur_order_index++; rubidium@8928: return 0; rubidium@8928: } rubidium@8928: rubidium@8928: return st->xy; truelight@0: } truelight@0: rubidium@6553: void Train::MarkDirty() truelight@0: { rubidium@6553: Vehicle *v = this; truelight@0: do { rubidium@7134: v->cur_image = v->GetImage(v->direction); smatz@8317: MarkSingleVehicleDirty(v); rubidium@7492: } while ((v = v->Next()) != NULL); rubidium@6553: rubidium@6553: /* need to update acceleration and cached values since the goods on the train changed. */ rubidium@6553: TrainCargoChanged(this); rubidium@6553: UpdateTrainAcceleration(this); truelight@0: } truelight@0: rubidium@10214: /** rubidium@10214: * This function looks at the vehicle and updates it's speed (cur_speed rubidium@10214: * and subspeed) variables. Furthermore, it returns the distance that rubidium@10214: * the train can drive this tick. This distance is expressed as 256 * n, rubidium@10214: * where n is the number of straight (long) tracks the train can rubidium@10214: * traverse. This means that moving along a straight track costs 256 rubidium@10214: * "speed" and a diagonal track costs 192 "speed". rubidium@10214: * @param v The vehicle to update the speed of. rubidium@10214: * @return distance to drive. rubidium@10214: */ truelight@0: static int UpdateTrainSpeed(Vehicle *v) truelight@0: { truelight@0: uint accel; truelight@0: rubidium@9808: if (v->vehstatus & VS_STOPPED || HasBit(v->u.rail.flags, VRF_REVERSING) || HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) { rubidium@9413: if (_settings_game.vehicle.realistic_acceleration) { celestar@1179: accel = GetTrainAcceleration(v, AM_BRAKE) * 2; tron@2639: } else { celestar@1179: accel = v->acceleration * -2; tron@2639: } truelight@0: } else { rubidium@9413: if (_settings_game.vehicle.realistic_acceleration) { celestar@1179: accel = GetTrainAcceleration(v, AM_ACCEL); tron@2639: } else { celestar@1179: accel = v->acceleration; tron@2639: } truelight@0: } truelight@0: tron@6150: uint spd = v->subspeed + accel * 2; truelight@0: v->subspeed = (byte)spd; celestar@1179: { celestar@1179: int tempmax = v->max_speed; celestar@1179: if (v->cur_speed > v->max_speed) celestar@1179: tempmax = v->cur_speed - (v->cur_speed / 10) - 1; skidd13@7922: v->cur_speed = spd = Clamp(v->cur_speed + ((int)spd >> 8), 0, tempmax); celestar@1179: } truelight@0: rubidium@10214: /* Scale speed by 3/4. Previously this was only done when the train was rubidium@10214: * facing diagonally and would apply to however many moves the train made rubidium@10214: * regardless the of direction actually moved in. Now it is always scaled, rubidium@10214: * 256 spd is used to go straight and 192 is used to go diagonally rubidium@10214: * (3/4 of 256). This results in the same effect, but without the error the rubidium@10214: * previous method caused. rubidium@10214: * rubidium@10214: * The scaling is done in this direction and not by multiplying the amount rubidium@10214: * to be subtracted by 4/3 so that the leftover speed can be saved in a rubidium@10214: * byte in v->progress. rubidium@10214: */ rubidium@10214: int scaled_spd = spd * 3 >> 2; rubidium@10214: rubidium@10214: scaled_spd += v->progress; rubidium@10214: v->progress = 0; // set later in TrainLocoHandler or TrainController rubidium@10214: return scaled_spd; truelight@0: } truelight@0: celestar@1551: static void TrainEnterStation(Vehicle *v, StationID station) truelight@0: { truelight@0: v->last_station_visited = station; truelight@0: truelight@0: /* check if a train ever visited this station before */ tron@6150: Station *st = GetStation(station); truelight@0: if (!(st->had_vehicle_of_type & HVOT_TRAIN)) { truelight@0: st->had_vehicle_of_type |= HVOT_TRAIN; tron@534: SetDParam(0, st->index); truelight@0: AddNewsItem( truelight@0: STR_8801_CITIZENS_CELEBRATE_FIRST, rubidium@10207: v->owner == _local_company ? NS_ARRIVAL_COMPANY : NS_ARRIVAL_OTHER, truelight@0: v->index, smatz@10123: st->index tron@3017: ); truelight@0: } truelight@0: rubidium@6550: v->BeginLoading(); peter1138@9003: peter1138@9003: StationAnimationTrigger(st, v->tile, STAT_ANIM_TRAIN_ARRIVES); truelight@0: } truelight@0: truelight@954: static byte AfterSetTrainPos(Vehicle *v, bool new_tile) truelight@0: { tron@6150: byte old_z = v->z_pos; peter1138@7079: v->z_pos = GetSlopeZ(v->x_pos, v->y_pos); truelight@0: truelight@954: if (new_tile) { skidd13@7929: ClrBit(v->u.rail.flags, VRF_GOINGUP); skidd13@7929: ClrBit(v->u.rail.flags, VRF_GOINGDOWN); truelight@954: peter1138@7079: if (v->u.rail.track == TRACK_BIT_X || v->u.rail.track == TRACK_BIT_Y) { peter1138@7079: /* Any track that isn't TRACK_BIT_X or TRACK_BIT_Y cannot be sloped. rubidium@7052: * To check whether the current tile is sloped, and in which rubidium@7052: * direction it is sloped, we get the 'z' at the center of rubidium@7052: * the tile (middle_z) and the edge of the tile (old_z), rubidium@7052: * which we then can compare. */ rubidium@7052: static const int HALF_TILE_SIZE = TILE_SIZE / 2; rubidium@7052: static const int INV_TILE_SIZE_MASK = ~(TILE_SIZE - 1); rubidium@7052: rubidium@7052: byte middle_z = GetSlopeZ((v->x_pos & INV_TILE_SIZE_MASK) | HALF_TILE_SIZE, (v->y_pos & INV_TILE_SIZE_MASK) | HALF_TILE_SIZE); rubidium@7052: rubidium@7052: /* For some reason tunnel tiles are always given as sloped :( rubidium@7052: * But they are not sloped... */ peter1138@7079: if (middle_z != v->z_pos && !IsTunnelTile(TileVirtXY(v->x_pos, v->y_pos))) { skidd13@7931: SetBit(v->u.rail.flags, (middle_z > old_z) ? VRF_GOINGUP : VRF_GOINGDOWN); tron@3184: } truelight@954: } truelight@0: } truelight@0: truelight@0: VehiclePositionChanged(v); truelight@0: EndVehicleMove(v); truelight@0: return old_z; truelight@0: } truelight@0: tron@3157: static const Direction _new_vehicle_direction_table[11] = { rubidium@5587: DIR_N , DIR_NW, DIR_W , INVALID_DIR, rubidium@5587: DIR_NE, DIR_N , DIR_SW, INVALID_DIR, tron@3157: DIR_E , DIR_SE, DIR_S truelight@0: }; truelight@0: smatz@8607: static inline Direction GetNewVehicleDirectionByTile(TileIndex new_tile, TileIndex old_tile) truelight@0: { tron@926: uint offs = (TileY(new_tile) - TileY(old_tile) + 1) * 4 + tron@926: TileX(new_tile) - TileX(old_tile) + 1; truelight@0: assert(offs < 11); truelight@0: return _new_vehicle_direction_table[offs]; truelight@0: } truelight@0: smatz@8607: static inline int GetDirectionToVehicle(const Vehicle *v, int x, int y) truelight@0: { truelight@0: byte offs; truelight@0: truelight@0: x -= v->x_pos; truelight@0: if (x >= 0) { truelight@0: offs = (x > 2) ? 0 : 1; truelight@0: } else { truelight@0: offs = (x < -2) ? 2 : 1; truelight@0: } truelight@0: truelight@0: y -= v->y_pos; truelight@0: if (y >= 0) { truelight@0: offs += ((y > 2) ? 0 : 1) * 4; truelight@0: } else { truelight@0: offs += ((y < -2) ? 2 : 1) * 4; truelight@0: } truelight@0: truelight@0: assert(offs < 11); truelight@0: return _new_vehicle_direction_table[offs]; truelight@0: } truelight@0: truelight@0: /* Check if the vehicle is compatible with the specified tile */ smatz@8607: static inline bool CheckCompatibleRail(const Vehicle *v, TileIndex tile) truelight@0: { tron@1048: return tron@2549: IsTileOwner(tile, v->owner) && ( bjarni@2676: !IsFrontEngine(v) || skidd13@7928: HasBit(v->u.rail.compatible_railtypes, GetRailType(tile)) tron@2549: ); truelight@0: } truelight@0: rubidium@6248: struct RailtypeSlowdownParams { truelight@0: byte small_turn, large_turn; truelight@0: byte z_up; // fraction to remove when moving up truelight@0: byte z_down; // fraction to remove when moving down rubidium@6248: }; truelight@0: celestar@3355: static const RailtypeSlowdownParams _railtype_slowdown[] = { truelight@0: // normal accel belugas@6422: {256 / 4, 256 / 2, 256 / 4, 2}, ///< normal belugas@6422: {256 / 4, 256 / 2, 256 / 4, 2}, ///< electrified belugas@6422: {256 / 4, 256 / 2, 256 / 4, 2}, ///< monorail belugas@6422: {0, 256 / 2, 256 / 4, 2}, ///< maglev truelight@0: }; truelight@0: belugas@6422: /** Modify the speed of the vehicle due to a turn */ smatz@8850: static inline void AffectSpeedByDirChange(Vehicle *v, Direction new_dir) truelight@0: { rubidium@9413: if (_settings_game.vehicle.realistic_acceleration) return; tron@3158: tron@6150: DirDiff diff = DirDifference(v->direction, new_dir); tron@3158: if (diff == DIRDIFF_SAME) return; truelight@0: tron@6150: const RailtypeSlowdownParams *rsp = &_railtype_slowdown[v->u.rail.railtype]; tron@3158: v->cur_speed -= (diff == DIRDIFF_45RIGHT || diff == DIRDIFF_45LEFT ? rsp->small_turn : rsp->large_turn) * v->cur_speed >> 8; truelight@0: } truelight@0: belugas@6422: /** Modify the speed of the vehicle due to a change in altitude */ smatz@8607: static inline void AffectSpeedByZChange(Vehicle *v, byte old_z) truelight@0: { rubidium@9413: if (old_z == v->z_pos || _settings_game.vehicle.realistic_acceleration) return; truelight@0: tron@6150: const RailtypeSlowdownParams *rsp = &_railtype_slowdown[v->u.rail.railtype]; truelight@0: truelight@0: if (old_z < v->z_pos) { truelight@0: v->cur_speed -= (v->cur_speed * rsp->z_up >> 8); truelight@0: } else { truelight@0: uint16 spd = v->cur_speed + rsp->z_down; tron@2639: if (spd <= v->max_speed) v->cur_speed = spd; truelight@0: } truelight@0: } truelight@0: rubidium@9821: static bool TrainMovedChangeSignals(TileIndex tile, DiagDirection dir) truelight@0: { tron@3267: if (IsTileType(tile, MP_RAILWAY) && rubidium@3792: GetRailTileType(tile) == RAIL_TILE_SIGNALS) { frosch@8794: TrackdirBits tracks = TrackBitsToTrackdirBits(GetTrackBits(tile)) & DiagdirReachesTrackdirs(dir); frosch@8794: Trackdir trackdir = FindFirstTrackdir(tracks); rubidium@9821: if (UpdateSignalsOnSegment(tile, TrackdirToExitdir(trackdir), GetTileOwner(tile)) == SIGSEG_PBS && HasSignalOnTrackdir(tile, trackdir)) { rubidium@9821: /* A PBS block with a non-PBS signal facing us? */ rubidium@9821: if (!IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) return true; rubidium@9821: } truelight@0: } rubidium@9821: return false; truelight@0: } truelight@0: truelight@0: truelight@0: static void SetVehicleCrashed(Vehicle *v) truelight@0: { tron@3017: if (v->u.rail.crash_anim_pos != 0) return; truelight@0: rubidium@9811: /* Free a possible path reservation and try to mark all tiles occupied by the train reserved. */ rubidium@9811: if (IsFrontEngine(v)) { rubidium@9811: /* Remove all reservations, also the ones currently under the train rubidium@9811: * and any railway station paltform reservation. */ rubidium@9811: FreeTrainTrackReservation(v); rubidium@9811: for (const Vehicle *u = v; u != NULL; u = u->Next()) { michi_cc@10103: ClearPathReservation(u, u->tile, GetVehicleTrackdir(u)); rubidium@9845: if (IsTileType(u->tile, MP_TUNNELBRIDGE)) { rubidium@9845: /* ClearPathReservation will not free the wormhole exit rubidium@9845: * if the train has just entered the wormhole. */ rubidium@9845: SetTunnelBridgeReservation(GetOtherTunnelBridgeEnd(u->tile), false); rubidium@9845: } rubidium@9811: } rubidium@9811: } rubidium@9811: smatz@8334: /* we may need to update crossing we were approaching */ smatz@8334: TileIndex crossing = TrainApproachingCrossingTile(v); smatz@8334: truelight@0: v->u.rail.crash_anim_pos++; truelight@193: smatz@8350: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); smatz@8271: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); smatz@8271: smatz@8271: if (v->u.rail.track == TRACK_BIT_DEPOT) { smatz@8271: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); smatz@8271: } smatz@8271: rubidium@9297: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); smatz@8271: peter1138@9321: for (; v != NULL; v = v->Next()) { truelight@0: v->vehstatus |= VS_CRASHED; smatz@8317: MarkSingleVehicleDirty(v); peter1138@9321: } smatz@8334: smatz@8334: /* must be updated after the train has been marked crashed */ smatz@8342: if (crossing != INVALID_TILE) UpdateLevelCrossing(crossing); truelight@0: } truelight@0: smatz@8850: static uint CountPassengersInTrain(const Vehicle *v) truelight@0: { tron@2549: uint num = 0; peter1138@9321: peter1138@9321: for (; v != NULL; v = v->Next()) { rubidium@7010: if (IsCargoInClass(v->cargo_type, CC_PASSENGERS)) num += v->cargo.Count(); peter1138@9321: } peter1138@9321: truelight@0: return num; truelight@0: } truelight@0: peter1138@6966: struct TrainCollideChecker { smatz@8850: Vehicle *v; ///< vehicle we are testing for collision smatz@8850: uint num; ///< number of dead if train collided peter1138@6966: }; peter1138@6966: rubidium@9775: static Vehicle *FindTrainCollideEnum(Vehicle *v, void *data) peter1138@6966: { smatz@8850: TrainCollideChecker *tcc = (TrainCollideChecker*)data; peter1138@6966: smatz@8281: if (v->type != VEH_TRAIN) return NULL; smatz@8281: smatz@8281: /* get first vehicle now to make most usual checks faster */ smatz@8281: Vehicle *coll = v->First(); smatz@8281: smatz@8312: /* can't collide with own wagons && can't crash in depot && the same height level */ smatz@8312: if (coll != tcc->v && v->u.rail.track != TRACK_BIT_DEPOT && abs(v->z_pos - tcc->v->z_pos) < 6) { smatz@8312: int x_diff = v->x_pos - tcc->v->x_pos; smatz@8312: int y_diff = v->y_pos - tcc->v->y_pos; smatz@8312: smatz@8312: /* needed to disable possible crash of competitor train in station by building diagonal track at its end */ smatz@8312: if (x_diff * x_diff + y_diff * y_diff > 25) return NULL; smatz@8312: peter1138@6966: if (!(tcc->v->vehstatus & VS_CRASHED)) { smatz@8312: /* two drivers + passengers killed in train tcc->v (if it was not crashed already) */ peter1138@6966: tcc->num += 2 + CountPassengersInTrain(tcc->v); peter1138@6966: SetVehicleCrashed(tcc->v); peter1138@6966: } peter1138@6966: peter1138@6966: if (!(coll->vehstatus & VS_CRASHED)) { peter1138@6966: /* two drivers + passengers killed in train coll (if it was not crashed already) */ peter1138@6966: tcc->num += 2 + CountPassengersInTrain(coll); peter1138@6966: SetVehicleCrashed(coll); peter1138@6966: } frosch@9833: frosch@9833: /* Try to reserve all tiles directly under the crashed trains. frosch@9833: * As there might be more than two trains involved, we have to do that for all vehicles */ frosch@9833: const Vehicle *u; frosch@9833: FOR_ALL_VEHICLES(u) { rubidium@9845: if (u->type == VEH_TRAIN && HASBITS(u->vehstatus, VS_CRASHED) && (u->u.rail.track & TRACK_BIT_DEPOT) == TRACK_BIT_NONE) { rubidium@9845: TrackBits trackbits = u->u.rail.track; rubidium@9845: if ((trackbits & TRACK_BIT_WORMHOLE) == TRACK_BIT_WORMHOLE) { rubidium@9845: /* Vehicle is inside a wormhole, v->u.rail.track contains no useful value then. */ rubidium@9845: trackbits |= DiagDirToDiagTrackBits(GetTunnelBridgeDirection(u->tile)); rubidium@9845: } smatz@9857: TryReserveRailTrack(u->tile, TrackBitsToTrack(trackbits)); frosch@9833: } frosch@9833: } peter1138@6966: } peter1138@6966: peter1138@6966: return NULL; peter1138@6966: } peter1138@6966: belugas@6422: /** tron@1434: * Checks whether the specified train has a collision with another vehicle. If bjarni@2676: * so, destroys this vehicle, and the other vehicle if its subtype has TS_Front. darkvater@22: * Reports the incident in a flashy news item, modifies station ratings and darkvater@22: * plays a sound. darkvater@22: */ glx@10282: static bool CheckTrainCollision(Vehicle *v) truelight@0: { truelight@0: /* can't collide in depot */ glx@10282: if (v->u.rail.track == TRACK_BIT_DEPOT) return false; rubidium@5993: rubidium@5993: assert(v->u.rail.track == TRACK_BIT_WORMHOLE || TileVirtXY(v->x_pos, v->y_pos) == v->tile); truelight@0: tron@6150: TrainCollideChecker tcc; truelight@0: tcc.v = v; peter1138@6966: tcc.num = 0; peter1138@6966: peter1138@6966: /* find colliding vehicles */ peter1138@7371: if (v->u.rail.track == TRACK_BIT_WORMHOLE) { rubidium@10083: FindVehicleOnPos(v->tile, &tcc, FindTrainCollideEnum); rubidium@10083: FindVehicleOnPos(GetOtherTunnelBridgeEnd(v->tile), &tcc, FindTrainCollideEnum); peter1138@7371: } else { rubidium@10083: FindVehicleOnPosXY(v->x_pos, v->y_pos, &tcc, FindTrainCollideEnum); peter1138@7371: } peter1138@6966: peter1138@6966: /* any dead -> no crash */ glx@10282: if (tcc.num == 0) return false; peter1138@6966: peter1138@6966: SetDParam(0, tcc.num); truelight@0: AddNewsItem(STR_8868_TRAIN_CRASH_DIE_IN_FIREBALL, rubidium@9234: NS_ACCIDENT_VEHICLE, truelight@0: v->index, tron@1434: 0 tron@1434: ); truelight@0: truelight@0: ModifyStationRatingAround(v->tile, v->owner, -160, 30); tron@541: SndPlayVehicleFx(SND_13_BIG_CRASH, v); glx@10282: return true; truelight@0: } truelight@0: rubidium@9775: static Vehicle *CheckVehicleAtSignal(Vehicle *v, void *data) truelight@0: { smatz@9699: DiagDirection exitdir = *(DiagDirection *)data; smatz@9699: smatz@9774: /* front engine of a train, not inside wormhole or depot, not crashed */ smatz@9774: if (v->type == VEH_TRAIN && IsFrontEngine(v) && (v->u.rail.track & TRACK_BIT_MASK) != 0 && !(v->vehstatus & VS_CRASHED)) { smatz@9699: if (v->cur_speed <= 5 && TrainExitDir(v->direction, v->u.rail.track) == exitdir) return v; truelight@0: } smatz@9699: tron@1432: return NULL; truelight@0: } truelight@0: smatz@8710: static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image) truelight@0: { hackykid@1961: Vehicle *prev; truelight@0: darkvater@22: /* For every vehicle after and including the given vehicle */ smatz@8710: for (prev = v->Previous(); v != nomove; prev = v, v = v->Next()) { peter1138@6871: DiagDirection enterdir = DIAGDIR_BEGIN; smatz@8334: bool update_signals_crossing = false; // will we update signals or crossing state? truelight@0: BeginVehicleMove(v); truelight@193: tron@6153: GetNewVehiclePosResult gp = GetNewVehiclePos(v); rubidium@5993: if (v->u.rail.track != TRACK_BIT_WORMHOLE) { darkvater@22: /* Not inside tunnel */ tron@6152: if (gp.old_tile == gp.new_tile) { darkvater@22: /* Staying in the old tile */ rubidium@5993: if (v->u.rail.track == TRACK_BIT_DEPOT) { rubidium@5994: /* Inside depot */ truelight@0: gp.x = v->x_pos; truelight@0: gp.y = v->y_pos; truelight@0: } else { rubidium@5994: /* Not inside depot */ truelight@742: peter1138@5668: if (IsFrontEngine(v) && !TrainCheckIfLineEnds(v)) return; truelight@742: tron@6150: uint32 r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); skidd13@7928: if (HasBit(r, VETS_CANNOT_ENTER)) { truelight@0: goto invalid_rail; matthijs@1247: } skidd13@7928: if (HasBit(r, VETS_ENTERED_STATION)) { rubidium@5991: TrainEnterStation(v, r >> VETS_STATION_ID_OFFSET); truelight@0: return; truelight@0: } truelight@0: rubidium@8836: if (v->current_order.IsType(OT_LEAVESTATION)) { bjarni@6263: v->current_order.Free(); smatz@8350: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); truelight@0: } truelight@0: } truelight@0: } else { truelight@0: /* A new tile is about to be entered. */ truelight@0: truelight@0: /* Determine what direction we're entering the new tile from */ tron@6151: Direction dir = GetNewVehicleDirectionByTile(gp.new_tile, gp.old_tile); peter1138@6871: enterdir = DirToDiagDir(dir); rubidium@5994: assert(IsValidDiagDirection(enterdir)); truelight@193: truelight@0: /* Get the status of the tracks in the new tile and mask truelight@0: * away the bits that aren't reachable. */ frosch@8794: TrackStatus ts = GetTileTrackStatus(gp.new_tile, TRANSPORT_RAIL, 0, ReverseDiagDir(enterdir)); frosch@8794: TrackdirBits reachable_trackdirs = DiagdirReachesTrackdirs(enterdir); frosch@8794: frosch@8794: TrackdirBits trackdirbits = TrackStatusToTrackdirBits(ts) & reachable_trackdirs; frosch@8794: TrackBits red_signals = TrackdirBitsToTrackBits(TrackStatusToRedSignals(ts) & reachable_trackdirs); frosch@8616: frosch@8616: TrackBits bits = TrackdirBitsToTrackBits(trackdirbits); rubidium@9413: if (_settings_game.pf.pathfinder_for_trains != VPF_NTP && _settings_game.pf.forbid_90_deg && prev == NULL) { matthijs@1247: /* We allow wagons to make 90 deg turns, because forbid_90_deg matthijs@1247: * can be switched on halfway a turn */ rubidium@5587: bits &= ~TrackCrossesTracks(FindFirstTrack(v->u.rail.track)); tron@3017: } tron@3017: rubidium@5994: if (bits == TRACK_BIT_NONE) goto invalid_rail; truelight@0: truelight@0: /* Check if the new tile contrains tracks that are compatible truelight@0: * with the current train, if not, bail out. */ rubidium@5994: if (!CheckCompatibleRail(v, gp.new_tile)) goto invalid_rail; truelight@0: tron@6150: TrackBits chosen_track; truelight@0: if (prev == NULL) { truelight@0: /* Currently the locomotive is active. Determine which one of the truelight@0: * available tracks to choose */ rubidium@9810: chosen_track = TrackToTrackBits(ChooseTrainTrack(v, gp.new_tile, enterdir, bits, false, NULL, true)); rubidium@9810: assert(chosen_track & (bits | GetReservedTrackbits(gp.new_tile))); truelight@0: truelight@0: /* Check if it's a red signal and that force proceed is not clicked. */ frosch@8616: if (red_signals & chosen_track && v->u.rail.force_proceed == 0) { frosch@8616: /* In front of a red signal */ frosch@8616: Trackdir i = FindFirstTrackdir(trackdirbits); tron@6151: rubidium@9808: /* Don't handle stuck trains here. */ rubidium@9808: if (HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) return; rubidium@9808: tron@6151: if (!HasSignalOnTrackdir(gp.new_tile, ReverseTrackdir(i))) { tron@6151: v->cur_speed = 0; tron@6151: v->subspeed = 0; tron@6151: v->progress = 255 - 100; rubidium@9413: if (++v->load_unload_time_rem < _settings_game.pf.wait_oneway_signal * 20) return; tron@6151: } else if (HasSignalOnTrackdir(gp.new_tile, i)) { tron@6151: v->cur_speed = 0; tron@6151: v->subspeed = 0; rubidium@6491: v->progress = 255 - 10; rubidium@9413: if (++v->load_unload_time_rem < _settings_game.pf.wait_twoway_signal * 73) { smatz@9699: DiagDirection exitdir = TrackdirToExitdir(i); smatz@9699: TileIndex o_tile = TileAddByDiagDir(gp.new_tile, exitdir); smatz@9699: smatz@9699: exitdir = ReverseDiagDir(exitdir); tron@6151: tron@6151: /* check if a train is waiting on the other side */ rubidium@10083: if (!HasVehicleOnPos(o_tile, &exitdir, &CheckVehicleAtSignal)) return; tron@6151: } tron@6151: } rubidium@9820: rubidium@9820: /* If we would reverse but are currently in a PBS block and rubidium@9820: * reversing of stuck trains is disabled, don't reverse. */ rubidium@9820: if (_settings_game.pf.wait_for_pbs_path == 255 && UpdateSignalsOnSegment(v->tile, enterdir, v->owner) == SIGSEG_PBS) { rubidium@9820: v->load_unload_time_rem = 0; rubidium@9820: return; rubidium@9820: } tron@6151: goto reverse_train_direction; rubidium@9888: } else { rubidium@9888: TryReserveRailTrack(gp.new_tile, TrackBitsToTrack(chosen_track)); tron@6151: } truelight@0: } else { rubidium@5994: static const TrackBits _matching_tracks[8] = { rubidium@5994: TRACK_BIT_LEFT | TRACK_BIT_RIGHT, TRACK_BIT_X, rubidium@5994: TRACK_BIT_UPPER | TRACK_BIT_LOWER, TRACK_BIT_Y, rubidium@5994: TRACK_BIT_LEFT | TRACK_BIT_RIGHT, TRACK_BIT_X, rubidium@5994: TRACK_BIT_UPPER | TRACK_BIT_LOWER, TRACK_BIT_Y rubidium@5994: }; truelight@193: truelight@0: /* The wagon is active, simply follow the prev vehicle. */ rubidium@5587: chosen_track = (TrackBits)(byte)(_matching_tracks[GetDirectionToVehicle(prev, gp.x, gp.y)] & bits); truelight@0: } truelight@0: rubidium@5994: /* Make sure chosen track is a valid track */ rubidium@5994: assert( rubidium@5994: chosen_track == TRACK_BIT_X || chosen_track == TRACK_BIT_Y || rubidium@5994: chosen_track == TRACK_BIT_UPPER || chosen_track == TRACK_BIT_LOWER || rubidium@5994: chosen_track == TRACK_BIT_LEFT || chosen_track == TRACK_BIT_RIGHT); truelight@0: truelight@0: /* Update XY to reflect the entrance to the new tile, and select the direction to use */ tron@6150: const byte *b = _initial_tile_subcoord[FIND_FIRST_BIT(chosen_track)][enterdir]; tron@6150: gp.x = (gp.x & ~0xF) | b[0]; tron@6150: gp.y = (gp.y & ~0xF) | b[1]; tron@6150: Direction chosen_dir = (Direction)b[2]; truelight@193: truelight@0: /* Call the landscape function and tell it that the vehicle entered the tile */ tron@6150: uint32 r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); skidd13@7928: if (HasBit(r, VETS_CANNOT_ENTER)) { truelight@0: goto invalid_rail; matthijs@1247: } truelight@0: skidd13@7928: if (!HasBit(r, VETS_ENTERED_WORMHOLE)) { rubidium@9809: Track track = FindFirstTrack(chosen_track); rubidium@9809: Trackdir tdir = TrackDirectionToTrackdir(track, chosen_dir); rubidium@9809: if (IsFrontEngine(v) && HasPbsSignalOnTrackdir(gp.new_tile, tdir)) { rubidium@9809: SetSignalStateByTrackdir(gp.new_tile, tdir, SIGNAL_STATE_RED); rubidium@9809: MarkTileDirtyByTile(gp.new_tile); rubidium@9809: } rubidium@9809: rubidium@9809: /* Clear any track reservation when the last vehicle leaves the tile */ michi_cc@10103: if (v->Next() == NULL) ClearPathReservation(v, v->tile, GetVehicleTrackdir(v)); rubidium@9809: truelight@0: v->tile = gp.new_tile; celestar@3355: tron@6154: if (GetTileRailType(gp.new_tile) != GetTileRailType(gp.old_tile)) { rubidium@7497: TrainPowerChanged(v->First()); celestar@3355: } celestar@3355: truelight@0: v->u.rail.track = chosen_track; matthijs@1330: assert(v->u.rail.track); truelight@0: } truelight@0: peter1138@6871: /* We need to update signal status, but after the vehicle position hash peter1138@6871: * has been updated by AfterSetTrainPos() */ smatz@8334: update_signals_crossing = true; truelight@193: tron@3017: if (prev == NULL) AffectSpeedByDirChange(v, chosen_dir); truelight@0: truelight@0: v->direction = chosen_dir; rubidium@9816: rubidium@9816: if (IsFrontEngine(v)) { rubidium@9816: v->load_unload_time_rem = 0; rubidium@9816: rubidium@9824: /* If we are approching a crossing that is reserved, play the sound now. */ rubidium@9824: TileIndex crossing = TrainApproachingCrossingTile(v); rubidium@9824: if (crossing != INVALID_TILE && GetCrossingReservation(crossing)) SndPlayTileFx(SND_0E_LEVEL_CROSSING, crossing); rubidium@9824: rubidium@9816: /* Always try to extend the reservation when entering a tile. */ rubidium@9816: CheckNextTrainTile(v); rubidium@9816: } truelight@0: } truelight@0: } else { smatz@8143: /* In a tunnel or on a bridge smatz@8143: * - for tunnels, only the part when the vehicle is not visible (part of enter/exit tile too) smatz@8143: * - for bridges, only the middle part - without the bridge heads */ tron@6141: if (!(v->vehstatus & VS_HIDDEN)) { tron@6141: v->cur_speed = belugas@8491: min(v->cur_speed, GetBridgeSpec(GetBridgeType(v->tile))->speed); tron@6141: } celestar@5385: rubidium@9816: if (IsTileType(gp.new_tile, MP_TUNNELBRIDGE) && HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) { rubidium@9816: /* Perform look-ahead on tunnel exit. */ rubidium@9888: if (IsFrontEngine(v)) { rubidium@9888: TryReserveRailTrack(gp.new_tile, DiagDirToDiagTrack(GetTunnelBridgeDirection(gp.new_tile))); rubidium@9888: CheckNextTrainTile(v); rubidium@9888: } rubidium@9816: } else { ludde@2125: v->x_pos = gp.x; ludde@2125: v->y_pos = gp.y; ludde@2125: VehiclePositionChanged(v); celestar@5385: if (!(v->vehstatus & VS_HIDDEN)) EndVehicleMove(v); ludde@2125: continue; truelight@0: } truelight@0: } truelight@0: truelight@0: /* update image of train, as well as delta XY */ smatz@8143: v->UpdateDeltaXY(v->direction); smatz@8143: if (update_image) v->cur_image = v->GetImage(v->direction); truelight@0: truelight@0: v->x_pos = gp.x; truelight@0: v->y_pos = gp.y; truelight@0: truelight@0: /* update the Z position of the vehicle */ tron@6150: byte old_z = AfterSetTrainPos(v, (gp.new_tile != gp.old_tile)); truelight@193: truelight@0: if (prev == NULL) { darkvater@22: /* This is the first vehicle in the train */ truelight@0: AffectSpeedByZChange(v, old_z); truelight@0: } peter1138@6871: smatz@8334: if (update_signals_crossing) { rubidium@9821: if (IsFrontEngine(v)) { rubidium@9821: if (TrainMovedChangeSignals(gp.new_tile, enterdir)) { rubidium@9821: /* We are entering a block with PBS signals right now, but rubidium@9821: * not through a PBS signal. This means we don't have a rubidium@9821: * reservation right now. As a conventional signal will only rubidium@9821: * ever be green if no other train is in the block, getting rubidium@9821: * a path should always be possible. If the player built rubidium@9821: * such a strange network that it is not possible, the train rubidium@9821: * will be marked as stuck and the player has to deal with rubidium@9821: * the problem. */ rubidium@9821: if ((!HasReservedTracks(gp.new_tile, v->u.rail.track) && rubidium@9821: !TryReserveRailTrack(gp.new_tile, FindFirstTrack(v->u.rail.track))) || rubidium@9821: !TryPathReserve(v)) { rubidium@9821: MarkTrainAsStuck(v); rubidium@9821: } rubidium@9821: } rubidium@9821: } peter1138@6871: peter1138@6871: /* Signals can only change when the first peter1138@6871: * (above) or the last vehicle moves. */ smatz@8334: if (v->Next() == NULL) { smatz@8334: TrainMovedChangeSignals(gp.old_tile, ReverseDiagDir(enterdir)); smatz@8342: if (IsLevelCrossingTile(gp.old_tile)) UpdateLevelCrossing(gp.old_tile); smatz@8334: } peter1138@6871: } rubidium@9816: rubidium@9816: /* Do not check on every tick to save some computing time. */ rubidium@9816: if (IsFrontEngine(v) && v->tick_counter % _settings_game.pf.path_backoff_interval == 0) CheckNextTrainTile(v); truelight@0: } tron@1438: return; truelight@0: truelight@0: invalid_rail: darkvater@22: /* We've reached end of line?? */ glx@9470: if (prev != NULL) error("Disconnecting train"); truelight@193: truelight@0: reverse_train_direction: truelight@0: v->load_unload_time_rem = 0; truelight@0: v->cur_speed = 0; truelight@0: v->subspeed = 0; truelight@0: ReverseTrainDirection(v); truelight@0: } truelight@0: frosch@9840: /** Collect trackbits of all crashed train vehicles on a tile rubidium@10083: * @param v Vehicle passed from Find/HasVehicleOnPos() frosch@9840: * @param data trackdirbits for the result rubidium@10083: * @return NULL to iterate over all vehicles on the tile. frosch@9840: */ frosch@9840: static Vehicle *CollectTrackbitsFromCrashedVehiclesEnum(Vehicle *v, void *data) frosch@9840: { frosch@9840: TrackBits *trackbits = (TrackBits *)data; frosch@9840: rubidium@9845: if (v->type == VEH_TRAIN && (v->vehstatus & VS_CRASHED) != 0) { rubidium@9845: if ((v->u.rail.track & TRACK_BIT_WORMHOLE) == TRACK_BIT_WORMHOLE) { rubidium@9845: /* Vehicle is inside a wormhole, v->u.rail.track contains no useful value then. */ rubidium@9845: *trackbits |= DiagDirToDiagTrackBits(GetTunnelBridgeDirection(v->tile)); rubidium@9845: } else { rubidium@9845: *trackbits |= v->u.rail.track; rubidium@9845: } rubidium@9845: } frosch@9840: frosch@9840: return NULL; frosch@9840: } frosch@9840: Darkvater@1418: /** Darkvater@1418: * Deletes/Clears the last wagon of a crashed train. It takes the engine of the Darkvater@1418: * train, then goes to the last wagon and deletes that. Each call to this function Darkvater@1418: * will remove the last wagon of a crashed train. If this wagon was on a crossing, smatz@8073: * or inside a tunnel/bridge, recalculate the signals as they might need updating belugas@6484: * @param v the Vehicle of which last wagon is to be removed Darkvater@1418: */ truelight@0: static void DeleteLastWagon(Vehicle *v) truelight@0: { smatz@8267: Vehicle *first = v->First(); smatz@8267: Darkvater@1418: /* Go to the last wagon and delete the link pointing there Darkvater@1418: * *u is then the one-before-last wagon, and *v the last Darkvater@1418: * one which will physicially be removed */ tron@6150: Vehicle *u = v; rubidium@7492: for (; v->Next() != NULL; v = v->Next()) u = v; rubidium@7492: u->SetNext(NULL); truelight@0: smatz@8267: if (first == v) { smatz@8267: /* Removing front vehicle (the last to go) */ smatz@8267: DeleteWindowById(WC_VEHICLE_VIEW, v->index); smatz@8267: InvalidateWindow(WC_COMPANY, v->owner); smatz@8267: } else { smatz@8267: /* Recalculate cached train properties */ smatz@9704: TrainConsistChanged(first, false); smatz@8267: /* Update the depot window if the first vehicle is in depot - smatz@8267: * if v == first, then it is updated in PreDestructor() */ smatz@8267: if (first->u.rail.track == TRACK_BIT_DEPOT) { smatz@8267: InvalidateWindow(WC_VEHICLE_DEPOT, first->tile); smatz@8267: } smatz@8267: } smatz@8267: rubidium@9297: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); truelight@0: smatz@8317: MarkSingleVehicleDirty(v); rubidium@6643: smatz@8255: /* 'v' shouldn't be accessed after it has been deleted */ frosch@9840: TrackBits trackbits = v->u.rail.track; smatz@8255: TileIndex tile = v->tile; smatz@8300: Owner owner = v->owner; smatz@8255: rubidium@7398: delete v; frosch@9840: v = NULL; // make sure nobody will try to read 'v' anymore frosch@9840: rubidium@9845: if ((trackbits & TRACK_BIT_WORMHOLE) == TRACK_BIT_WORMHOLE) { rubidium@9845: /* Vehicle is inside a wormhole, v->u.rail.track contains no useful value then. */ rubidium@9845: trackbits |= DiagDirToDiagTrackBits(GetTunnelBridgeDirection(tile)); rubidium@9845: } rubidium@9845: rubidium@9845: Track track = TrackBitsToTrack(trackbits); frosch@9840: if (HasReservedTracks(tile, trackbits)) { frosch@9840: UnreserveRailTrack(tile, track); frosch@9840: frosch@9840: /* If there are still crashed vehicles on the tile, give the track reservation to them */ frosch@9840: TrackBits remaining_trackbits = TRACK_BIT_NONE; rubidium@10083: FindVehicleOnPos(tile, &remaining_trackbits, CollectTrackbitsFromCrashedVehiclesEnum); frosch@9840: frosch@9840: /* It is important that these two are the first in the loop, as reservation cannot deal with every trackbit combination */ frosch@9840: assert(TRACK_BEGIN == TRACK_X && TRACK_Y == TRACK_BEGIN + 1); frosch@9840: for (Track t = TRACK_BEGIN; t < TRACK_END; t++) { frosch@9840: if (HasBit(remaining_trackbits, t)) { frosch@9840: TryReserveRailTrack(tile, t); frosch@9840: } frosch@9840: } frosch@9840: } truelight@0: smatz@8334: /* check if the wagon was on a road/rail-crossing */ smatz@8342: if (IsLevelCrossingTile(tile)) UpdateLevelCrossing(tile); smatz@8255: smatz@8257: /* Update signals */ smatz@8961: if (IsTileType(tile, MP_TUNNELBRIDGE) || IsRailDepotTile(tile)) { smatz@8300: UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, owner); smatz@8257: } else { frosch@9840: SetSignalsOnBothDir(tile, track, owner); dominik@98: } truelight@0: } truelight@0: truelight@0: static void ChangeTrainDirRandomly(Vehicle *v) truelight@0: { tron@3160: static const DirDiff delta[] = { tron@3160: DIRDIFF_45LEFT, DIRDIFF_SAME, DIRDIFF_SAME, DIRDIFF_45RIGHT tron@3160: }; truelight@193: truelight@0: do { celestar@5385: /* We don't need to twist around vehicles if they're not visible */ celestar@5385: if (!(v->vehstatus & VS_HIDDEN)) { tron@3977: v->direction = ChangeDir(v->direction, delta[GB(Random(), 0, 2)]); truelight@0: BeginVehicleMove(v); rubidium@6558: v->UpdateDeltaXY(v->direction); rubidium@7134: v->cur_image = v->GetImage(v->direction); celestar@5385: /* Refrain from updating the z position of the vehicle when on smatz@8850: * a bridge, because AfterSetTrainPos will put the vehicle under smatz@8850: * the bridge in that case */ rubidium@5993: if (v->u.rail.track != TRACK_BIT_WORMHOLE) AfterSetTrainPos(v, false); truelight@0: } rubidium@7492: } while ((v = v->Next()) != NULL); truelight@0: } truelight@0: truelight@0: static void HandleCrashedTrain(Vehicle *v) truelight@0: { tron@3017: int state = ++v->u.rail.crash_anim_pos; truelight@193: rubidium@5993: if (state == 4 && !(v->vehstatus & VS_HIDDEN)) { tron@1359: CreateEffectVehicleRel(v, 4, 4, 8, EV_EXPLOSION_LARGE); truelight@0: } truelight@0: tron@6150: uint32 r; skidd13@7967: if (state <= 200 && Chance16R(1, 7, r)) { tron@3017: int index = (r * 10 >> 16); truelight@0: tron@6150: Vehicle *u = v; truelight@0: do { truelight@0: if (--index < 0) { celestar@1137: r = Random(); truelight@0: truelight@0: CreateEffectVehicleRel(u, tron@2140: GB(r, 8, 3) + 2, tron@2140: GB(r, 16, 3) + 2, tron@2140: GB(r, 0, 3) + 5, tron@1359: EV_EXPLOSION_SMALL); truelight@0: break; truelight@0: } rubidium@7492: } while ((u = u->Next()) != NULL); truelight@0: } truelight@0: tron@3017: if (state <= 240 && !(v->tick_counter & 3)) ChangeTrainDirRandomly(v); truelight@0: rubidium@8969: if (state >= 4440 && !(v->tick_counter & 0x1F)) { truelight@0: DeleteLastWagon(v); rubidium@6643: InvalidateWindow(WC_REPLACE_VEHICLE, (v->group_id << 16) | VEH_TRAIN); bjarni@1128: } truelight@0: } truelight@0: truelight@0: static void HandleBrokenTrain(Vehicle *v) truelight@0: { truelight@0: if (v->breakdown_ctr != 1) { truelight@0: v->breakdown_ctr = 1; truelight@0: v->cur_speed = 0; truelight@0: truelight@0: if (v->breakdowns_since_last_service != 255) truelight@0: v->breakdowns_since_last_service++; truelight@193: truelight@0: InvalidateWindow(WC_VEHICLE_VIEW, v->index); truelight@0: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); truelight@193: peter1138@4656: if (!PlayVehicleSound(v, VSE_BREAKDOWN)) { rubidium@9413: SndPlayVehicleFx((_settings_game.game_creation.landscape != LT_TOYLAND) ? peter1138@4656: SND_10_TRAIN_BREAKDOWN : SND_3A_COMEDY_BREAKDOWN_2, v); peter1138@4656: } truelight@0: truelight@0: if (!(v->vehstatus & VS_HIDDEN)) { truelight@0: Vehicle *u = CreateEffectVehicleRel(v, 4, 4, 5, EV_BREAKDOWN_SMOKE); rubidium@9008: if (u != NULL) u->u.effect.animation_state = v->breakdown_delay * 2; truelight@0: } truelight@0: } truelight@0: truelight@0: if (!(v->tick_counter & 3)) { truelight@0: if (!--v->breakdown_delay) { truelight@0: v->breakdown_ctr = 0; truelight@0: InvalidateWindow(WC_VEHICLE_VIEW, v->index); truelight@0: } truelight@0: } truelight@0: } truelight@0: smatz@8283: /** Maximum speeds for train that is broken down or approaching line end */ smatz@8312: static const uint16 _breakdown_speeds[16] = { truelight@0: 225, 210, 195, 180, 165, 150, 135, 120, 105, 90, 75, 60, 45, 30, 15, 15 truelight@0: }; truelight@0: smatz@8305: smatz@8283: /** smatz@8305: * Train is approaching line end, slow down and possibly reverse smatz@8283: * smatz@8305: * @param v front train engine smatz@8305: * @param signal not line end, just a red signal smatz@8305: * @return true iff we did NOT have to reverse smatz@8283: */ smatz@8305: static bool TrainApproachingLineEnd(Vehicle *v, bool signal) truelight@0: { smatz@8305: /* Calc position within the current tile */ tron@6150: uint x = v->x_pos & 0xF; tron@6150: uint y = v->y_pos & 0xF; truelight@193: smatz@8312: /* for diagonal directions, 'x' will be 0..15 - smatz@8312: * for other directions, it will be 1, 3, 5, ..., 15 */ tron@2639: switch (v->direction) { smatz@8312: case DIR_N : x = ~x + ~y + 25; break; tron@3186: case DIR_NW: x = y; /* FALLTHROUGH */ tron@3186: case DIR_NE: x = ~x + 16; break; smatz@8312: case DIR_E : x = ~x + y + 9; break; tron@3186: case DIR_SE: x = y; break; smatz@8312: case DIR_S : x = x + y - 7; break; smatz@8312: case DIR_W : x = ~y + x + 9; break; rubidium@5587: default: break; truelight@0: } truelight@0: smatz@8305: /* do not reverse when approaching red signal */ rubidium@10287: if (!signal && x + (v->u.rail.cached_veh_length + 1) / 2 >= TILE_SIZE) { smatz@8305: /* we are too near the tile end, reverse now */ truelight@0: v->cur_speed = 0; truelight@0: ReverseTrainDirection(v); truelight@742: return false; truelight@0: } truelight@0: belugas@6422: /* slow down */ truelight@0: v->vehstatus |= VS_TRAIN_SLOWING; tron@6150: uint16 break_speed = _breakdown_speeds[x & 0xF]; tron@2484: if (break_speed < v->cur_speed) v->cur_speed = break_speed; truelight@742: truelight@742: return true; truelight@0: } truelight@0: smatz@8305: smatz@8305: /** smatz@8334: * Determines whether train would like to leave the tile smatz@8334: * @param v train to test smatz@8334: * @return true iff vehicle is NOT entering or inside a depot or tunnel/bridge smatz@8334: */ smatz@8334: static bool TrainCanLeaveTile(const Vehicle *v) smatz@8334: { smatz@8334: /* Exit if inside a tunnel/bridge or a depot */ smatz@8334: if (v->u.rail.track == TRACK_BIT_WORMHOLE || v->u.rail.track == TRACK_BIT_DEPOT) return false; smatz@8334: smatz@8334: TileIndex tile = v->tile; smatz@8334: smatz@8334: /* entering a tunnel/bridge? */ smatz@8334: if (IsTileType(tile, MP_TUNNELBRIDGE)) { smatz@8334: DiagDirection dir = GetTunnelBridgeDirection(tile); smatz@8334: if (DiagDirToDir(dir) == v->direction) return false; smatz@8334: } smatz@8334: smatz@8334: /* entering a depot? */ smatz@8961: if (IsRailDepotTile(tile)) { smatz@8334: DiagDirection dir = ReverseDiagDir(GetRailDepotDirection(tile)); smatz@8334: if (DiagDirToDir(dir) == v->direction) return false; smatz@8334: } smatz@8334: smatz@8334: return true; smatz@8334: } smatz@8334: smatz@8334: smatz@8334: /** smatz@8334: * Determines whether train is approaching a rail-road crossing smatz@8334: * (thus making it barred) smatz@8334: * @param v front engine of train smatz@8334: * @return TileIndex of crossing the train is approaching, else INVALID_TILE smatz@8334: * @pre v in non-crashed front engine smatz@8334: */ smatz@8334: static TileIndex TrainApproachingCrossingTile(const Vehicle *v) smatz@8334: { smatz@8334: assert(IsFrontEngine(v)); smatz@8334: assert(!(v->vehstatus & VS_CRASHED)); smatz@8334: smatz@8334: if (!TrainCanLeaveTile(v)) return INVALID_TILE; smatz@8334: smatz@8334: DiagDirection dir = TrainExitDir(v->direction, v->u.rail.track); smatz@8334: TileIndex tile = v->tile + TileOffsByDiagDir(dir); smatz@8334: smatz@8606: /* not a crossing || wrong axis || unusable rail (wrong type or owner) */ smatz@8334: if (!IsLevelCrossingTile(tile) || DiagDirToAxis(dir) == GetCrossingRoadAxis(tile) || smatz@8606: !CheckCompatibleRail(v, tile)) { smatz@8334: return INVALID_TILE; smatz@8334: } smatz@8334: smatz@8334: return tile; smatz@8334: } smatz@8334: smatz@8334: smatz@8334: /** smatz@8305: * Checks for line end. Also, bars crossing at next tile if needed smatz@8305: * smatz@8305: * @param v vehicle we are checking smatz@8305: * @return true iff we did NOT have to reverse smatz@8305: */ smatz@8305: static bool TrainCheckIfLineEnds(Vehicle *v) smatz@8305: { smatz@8305: /* First, handle broken down train */ smatz@8305: smatz@8305: int t = v->breakdown_ctr; smatz@8305: if (t > 1) { smatz@8305: v->vehstatus |= VS_TRAIN_SLOWING; smatz@8305: smatz@8305: uint16 break_speed = _breakdown_speeds[GB(~t, 4, 4)]; smatz@8305: if (break_speed < v->cur_speed) v->cur_speed = break_speed; smatz@8305: } else { smatz@8305: v->vehstatus &= ~VS_TRAIN_SLOWING; smatz@8305: } smatz@8305: smatz@8334: if (!TrainCanLeaveTile(v)) return true; smatz@8305: smatz@8305: /* Determine the non-diagonal direction in which we will exit this tile */ smatz@8305: DiagDirection dir = TrainExitDir(v->direction, v->u.rail.track); smatz@8305: /* Calculate next tile */ smatz@8334: TileIndex tile = v->tile + TileOffsByDiagDir(dir); smatz@8305: smatz@8305: /* Determine the track status on the next tile */ frosch@8794: TrackStatus ts = GetTileTrackStatus(tile, TRANSPORT_RAIL, 0, ReverseDiagDir(dir)); frosch@8794: TrackdirBits reachable_trackdirs = DiagdirReachesTrackdirs(dir); frosch@8794: frosch@8794: TrackdirBits trackdirbits = TrackStatusToTrackdirBits(ts) & reachable_trackdirs; frosch@8794: TrackdirBits red_signals = TrackStatusToRedSignals(ts) & reachable_trackdirs; smatz@8305: smatz@8305: /* We are sure the train is not entering a depot, it is detected above */ smatz@8305: smatz@8482: /* mask unreachable track bits if we are forbidden to do 90deg turns */ frosch@8616: TrackBits bits = TrackdirBitsToTrackBits(trackdirbits); rubidium@9413: if (_settings_game.pf.pathfinder_for_trains != VPF_NTP && _settings_game.pf.forbid_90_deg) { smatz@8482: bits &= ~TrackCrossesTracks(FindFirstTrack(v->u.rail.track)); smatz@8482: } smatz@8482: smatz@8606: /* no suitable trackbits at all || unusable rail (wrong type or owner) */ smatz@8606: if (bits == TRACK_BIT_NONE || !CheckCompatibleRail(v, tile)) { smatz@8305: return TrainApproachingLineEnd(v, false); smatz@8305: } smatz@8305: smatz@8305: /* approaching red signal */ frosch@8616: if ((trackdirbits & red_signals) != 0) return TrainApproachingLineEnd(v, true); smatz@8305: smatz@8305: /* approaching a rail/road crossing? then make it red */ smatz@8356: if (IsLevelCrossingTile(tile)) MaybeBarCrossingWithSound(tile); smatz@8305: smatz@8305: return true; smatz@8305: } smatz@8305: smatz@8305: truelight@0: static void TrainLocoHandler(Vehicle *v, bool mode) truelight@0: { truelight@0: /* train has crashed? */ rubidium@7476: if (v->vehstatus & VS_CRASHED) { truelight@0: if (!mode) HandleCrashedTrain(v); truelight@0: return; truelight@0: } truelight@0: rubidium@9808: if (v->u.rail.force_proceed != 0) { rubidium@9808: v->u.rail.force_proceed--; rubidium@9808: ClrBit(v->u.rail.flags, VRF_TRAIN_STUCK); rubidium@9808: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); rubidium@9808: } truelight@0: truelight@0: /* train is broken down? */ truelight@0: if (v->breakdown_ctr != 0) { truelight@0: if (v->breakdown_ctr <= 2) { truelight@0: HandleBrokenTrain(v); truelight@0: return; truelight@0: } rubidium@9000: if (!v->current_order.IsType(OT_LOADING)) v->breakdown_ctr--; truelight@0: } truelight@0: skidd13@7928: if (HasBit(v->u.rail.flags, VRF_REVERSING) && v->cur_speed == 0) { truelight@0: ReverseTrainDirection(v); truelight@0: } truelight@0: truelight@0: /* exit if train is stopped */ tron@3017: if (v->vehstatus & VS_STOPPED && v->cur_speed == 0) return; truelight@0: rubidium@9816: bool valid_order = v->current_order.IsValid() && v->current_order.GetType() != OT_CONDITIONAL; rubidium@8827: if (ProcessOrders(v) && CheckReverseTrain(v)) { truelight@0: v->load_unload_time_rem = 0; truelight@0: v->cur_speed = 0; truelight@0: v->subspeed = 0; truelight@0: ReverseTrainDirection(v); truelight@0: return; truelight@0: } truelight@0: rubidium@6594: v->HandleLoading(mode); truelight@0: rubidium@8836: if (v->current_order.IsType(OT_LOADING)) return; tron@3017: tron@3017: if (CheckTrainStayInDepot(v)) return; truelight@0: truelight@0: if (!mode) HandleLocomotiveSmokeCloud(v); truelight@0: rubidium@9816: /* We had no order but have an order now, do look ahead. */ rubidium@9816: if (!valid_order && v->current_order.IsValid()) { rubidium@9816: CheckNextTrainTile(v); rubidium@9816: } rubidium@9816: rubidium@9813: /* Handle stuck trains. */ rubidium@9813: if (!mode && HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) { rubidium@9813: ++v->load_unload_time_rem; rubidium@9813: rubidium@9813: /* Should we try reversing this tick if still stuck? */ rubidium@9813: bool turn_around = v->load_unload_time_rem % (_settings_game.pf.wait_for_pbs_path * DAY_TICKS) == 0 && _settings_game.pf.wait_for_pbs_path < 255; rubidium@9813: rubidium@9816: if (!turn_around && v->load_unload_time_rem % _settings_game.pf.path_backoff_interval != 0 && v->u.rail.force_proceed == 0) return; rubidium@9813: if (!TryPathReserve(v)) { rubidium@9813: /* Still stuck. */ rubidium@9813: if (turn_around) ReverseTrainDirection(v); rubidium@9813: rubidium@9813: if (HasBit(v->u.rail.flags, VRF_TRAIN_STUCK) && v->load_unload_time_rem > 2 * _settings_game.pf.wait_for_pbs_path * DAY_TICKS) { rubidium@9813: /* Show message to player. */ rubidium@10207: if (_settings_client.gui.lost_train_warn && v->owner == _local_company) { rubidium@9813: SetDParam(0, v->unitnumber); rubidium@9813: AddNewsItem( rubidium@9813: STR_TRAIN_IS_STUCK, rubidium@9813: NS_ADVICE, rubidium@9813: v->index, rubidium@9813: 0); rubidium@9813: } rubidium@9813: v->load_unload_time_rem = 0; rubidium@9813: } rubidium@9813: /* Exit if force proceed not pressed, else reset stuck flag anyway. */ rubidium@9813: if (v->u.rail.force_proceed == 0) return; rubidium@9813: ClrBit(v->u.rail.flags, VRF_TRAIN_STUCK); rubidium@9813: v->load_unload_time_rem = 0; rubidium@9813: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); rubidium@9813: } rubidium@9813: } rubidium@9813: tron@6150: int j = UpdateTrainSpeed(v); smatz@8426: smatz@8426: /* we need to invalidate the widget if we are stopping from 'Stopping 0 km/h' to 'Stopped' */ smatz@8426: if (v->cur_speed == 0 && v->u.rail.last_speed == 0 && v->vehstatus & VS_STOPPED) { smatz@8426: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); smatz@8426: } smatz@8426: rubidium@10214: int adv_spd = (v->direction & 1) ? 192 : 256; rubidium@10214: if (j < adv_spd) { belugas@6422: /* if the vehicle has speed 0, update the last_speed field. */ rubidium@10214: if (v->cur_speed == 0) SetLastSpeed(v, v->cur_speed); truelight@0: } else { truelight@0: TrainCheckIfLineEnds(v); rubidium@10214: /* Loop until the train has finished moving. */ truelight@0: do { rubidium@10214: j -= adv_spd; smatz@8710: TrainController(v, NULL, true); glx@10282: /* Don't continue to move if the train crashed. */ glx@10282: if (CheckTrainCollision(v)) break; rubidium@10214: /* 192 spd used for going straight, 256 for going diagonally. */ rubidium@10214: adv_spd = (v->direction & 1) ? 192 : 256; rubidium@10214: } while (j >= adv_spd); rubidium@10214: SetLastSpeed(v, v->cur_speed); truelight@0: } truelight@0: rubidium@10214: if (v->progress == 0) v->progress = j; // Save unused spd for next time, if TrainController didn't set progress truelight@0: } truelight@0: truelight@0: rubidium@7488: rubidium@7488: Money Train::GetRunningCost() const rubidium@7488: { rubidium@7488: Money cost = 0; rubidium@7488: const Vehicle *v = this; rubidium@7488: rubidium@7488: do { rubidium@7488: const RailVehicleInfo *rvi = RailVehInfo(v->engine_type); rubidium@7488: peter1138@8622: byte cost_factor = GetVehicleProperty(v, 0x0D, rvi->running_cost); rubidium@7488: if (cost_factor == 0) continue; rubidium@7488: peter1138@9208: /* Halve running cost for multiheaded parts */ peter1138@9208: if (IsMultiheaded(v)) cost_factor /= 2; peter1138@9208: peter1138@8626: cost += cost_factor * GetPriceByIndex(rvi->running_cost_class); rubidium@7488: } while ((v = GetNextVehicle(v)) != NULL); rubidium@7488: rubidium@7488: return cost; rubidium@7488: } rubidium@7488: rubidium@7488: rubidium@7135: void Train::Tick() truelight@0: { rubidium@7135: if (_age_cargo_skip_counter == 0) this->cargo.AgeCargo(); rubidium@7135: rubidium@7135: this->tick_counter++; rubidium@7135: rubidium@7135: if (IsFrontEngine(this)) { smatz@8556: if (!(this->vehstatus & VS_STOPPED)) this->running_ticks++; rubidium@7135: this->current_order_time++; rubidium@7135: rubidium@7135: TrainLocoHandler(this, false); truelight@193: belugas@6422: /* make sure vehicle wasn't deleted. */ rubidium@7135: if (this->type == VEH_TRAIN && IsFrontEngine(this)) rubidium@7135: TrainLocoHandler(this, true); rubidium@7135: } else if (IsFreeWagon(this) && HASBITS(this->vehstatus, VS_CRASHED)) { rubidium@7696: /* Delete flooded standalone wagon chain */ rubidium@7696: if (++this->u.rail.crash_anim_pos >= 4400) DeleteVehicleChain(this); truelight@0: } truelight@0: } truelight@0: truelight@0: static void CheckIfTrainNeedsService(Vehicle *v) truelight@0: { smatz@8850: static const uint MAX_ACCEPTABLE_DEPOT_DIST = 16; smatz@8850: rubidium@9413: if (_settings_game.vehicle.servint_trains == 0 || !v->NeedsAutomaticServicing()) return; rubidium@7502: if (v->IsInDepot()) { bjarni@4529: VehicleServiceInDepot(v); bjarni@4529: return; bjarni@4529: } bjarni@4529: tron@6150: TrainFindDepotData tfdd = FindClosestTrainDepot(v, MAX_ACCEPTABLE_DEPOT_DIST); darkvater@308: /* Only go to the depot if it is not too far out of our way. */ rubidium@10236: if (tfdd.best_length == UINT_MAX || tfdd.best_length > MAX_ACCEPTABLE_DEPOT_DIST) { rubidium@8836: if (v->current_order.IsType(OT_GOTO_DEPOT)) { darkvater@308: /* If we were already heading for a depot but it has darkvater@308: * suddenly moved farther away, we continue our normal darkvater@308: * schedule? */ rubidium@8836: v->current_order.MakeDummy(); smatz@8350: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); truelight@0: } truelight@0: return; truelight@0: } truelight@0: smatz@8850: const Depot *depot = GetDepotByTile(tfdd.tile); truelight@0: rubidium@8836: if (v->current_order.IsType(OT_GOTO_DEPOT) && rubidium@8840: v->current_order.GetDestination() != depot->index && skidd13@7967: !Chance16(3, 16)) { truelight@0: return; tron@3017: } truelight@0: rubidium@8853: v->current_order.MakeGoToDepot(depot->index, ODTFB_SERVICE); darkvater@308: v->dest_tile = tfdd.tile; smatz@8350: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); truelight@0: } truelight@0: glx@8467: void Train::OnNewDay() truelight@0: { glx@8467: if ((++this->day_counter & 7) == 0) DecreaseVehicleValue(this); glx@8467: glx@8467: if (IsFrontEngine(this)) { glx@8467: CheckVehicleBreakdown(this); glx@8467: AgeVehicle(this); glx@8467: glx@8467: CheckIfTrainNeedsService(this); glx@8467: glx@8467: CheckOrders(this); truelight@193: truelight@0: /* update destination */ rubidium@8836: if (this->current_order.IsType(OT_GOTO_STATION)) { rubidium@8840: TileIndex tile = GetStation(this->current_order.GetDestination())->train_tile; glx@8467: if (tile != 0) this->dest_tile = tile; tron@2639: } truelight@0: smatz@8556: if (this->running_ticks != 0) { truelight@0: /* running costs */ smatz@8556: CommandCost cost(EXPENSES_TRAIN_RUN, this->GetRunningCost() * this->running_ticks / (364 * DAY_TICKS)); smatz@8556: smatz@8556: this->profit_this_year -= cost.GetCost(); smatz@8556: this->running_ticks = 0; glx@8467: rubidium@10207: SubtractMoneyFromCompanyFract(this->owner, cost); glx@8467: glx@8467: InvalidateWindow(WC_VEHICLE_DETAILS, this->index); bjarni@1151: InvalidateWindowClasses(WC_TRAINS_LIST); truelight@0: } glx@8467: } else if (IsTrainEngine(this)) { truelight@7024: /* Also age engines that aren't front engines */ glx@8467: AgeVehicle(this); truelight@0: } truelight@0: } truelight@0: rubidium@6247: void TrainsYearlyLoop() truelight@0: { truelight@0: Vehicle *v; truelight@0: truelight@0: FOR_ALL_VEHICLES(v) { rubidium@6259: if (v->type == VEH_TRAIN && IsFrontEngine(v)) { belugas@6422: /* show warning if train is not generating enough income last 2 years (corresponds to a red icon in the vehicle list) */ rubidium@10207: if (_settings_client.gui.train_income_warn && v->owner == _local_company && v->age >= 730 && v->GetDisplayProfitThisYear() < 0) { smatz@8614: SetDParam(1, v->GetDisplayProfitThisYear()); tron@534: SetDParam(0, v->unitnumber); truelight@0: AddNewsItem( truelight@0: STR_TRAIN_IS_UNPROFITABLE, rubidium@9234: NS_ADVICE, truelight@0: v->index, truelight@0: 0); truelight@0: } truelight@0: truelight@0: v->profit_last_year = v->profit_this_year; truelight@0: v->profit_this_year = 0; truelight@0: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); truelight@0: } truelight@0: } truelight@0: } truelight@0: truelight@0: rubidium@6247: void InitializeTrains() truelight@0: { truelight@0: _age_cargo_skip_counter = 1; truelight@0: } bjarni@2855: bjarni@2855: /* bjarni@2855: * Link front and rear multiheaded engines to each other bjarni@2855: * This is done when loading a savegame bjarni@2855: */ rubidium@6247: void ConnectMultiheadedTrains() bjarni@2855: { bjarni@2855: Vehicle *v; bjarni@2855: bjarni@2855: FOR_ALL_VEHICLES(v) { rubidium@6259: if (v->type == VEH_TRAIN) { bjarni@2855: v->u.rail.other_multiheaded_part = NULL; bjarni@2855: } bjarni@2855: } bjarni@2855: bjarni@2855: FOR_ALL_VEHICLES(v) { frosch@10173: if (v->type == VEH_TRAIN && (IsFrontEngine(v) || IsFreeWagon(v))) { frosch@10170: /* Two ways to associate multiheaded parts to each other: frosch@10170: * sequential-matching: Trains shall be arranged to look like <..>..<..>..<..>.. frosch@10170: * bracket-matching: Free vehicle chains shall be arranged to look like ..<..<..>..<..>..>.. frosch@10170: * frosch@10170: * Note: Old savegames might contain chains which do not comply with these rules, e.g. frosch@10170: * - the front and read parts have invalid orders frosch@10170: * - different engine types might be combined frosch@10170: * - there might be different amounts of front and rear parts. frosch@10170: * frosch@10170: * Note: The multiheaded parts need to be matched exactly like they are matched on the server, else desyncs will occur. frosch@10170: * This is why two matching strategies are needed. frosch@10170: */ frosch@10170: frosch@10170: bool sequential_matching = IsFrontEngine(v); frosch@10170: frosch@10170: for (Vehicle *u = v; u != NULL; u = GetNextVehicle(u)) { bjarni@2855: if (u->u.rail.other_multiheaded_part != NULL) continue; // we already linked this one bjarni@2855: bjarni@2855: if (IsMultiheaded(u)) { bjarni@2855: if (!IsTrainEngine(u)) { bjarni@2855: /* we got a rear car without a front car. We will convert it to a front one */ bjarni@2855: SetTrainEngine(u); bjarni@2855: u->spritenum--; bjarni@2855: } bjarni@2855: frosch@10170: /* Find a matching back part */ frosch@10170: EngineID eid = u->engine_type; tron@6150: Vehicle *w; frosch@10170: if (sequential_matching) { frosch@10170: for (w = GetNextVehicle(u); w != NULL; w = GetNextVehicle(w)) { frosch@10170: if (w->engine_type != eid || w->u.rail.other_multiheaded_part != NULL || !IsMultiheaded(w)) continue; frosch@10170: frosch@10170: /* we found a car to partner with this engine. Now we will make sure it face the right way */ frosch@10170: if (IsTrainEngine(w)) { frosch@10170: ClearTrainEngine(w); frosch@10170: w->spritenum++; frosch@10170: } frosch@10170: break; frosch@10170: } frosch@10170: } else { frosch@10170: uint stack_pos = 0; frosch@10170: for (w = GetNextVehicle(u); w != NULL; w = GetNextVehicle(w)) { frosch@10170: if (w->engine_type != eid || w->u.rail.other_multiheaded_part != NULL || !IsMultiheaded(w)) continue; frosch@10170: frosch@10170: if (IsTrainEngine(w)) { frosch@10170: stack_pos++; frosch@10170: } else { frosch@10170: if (stack_pos == 0) break; frosch@10170: stack_pos--; frosch@10170: } frosch@10170: } frosch@10170: } frosch@10170: tron@6150: if (w != NULL) { tron@6150: w->u.rail.other_multiheaded_part = u; tron@6150: u->u.rail.other_multiheaded_part = w; tron@6150: } else { tron@6150: /* we got a front car and no rear cars. We will fake this one for forget that it should have been multiheaded */ tron@6150: ClearMultiheaded(u); bjarni@2855: } bjarni@2855: } peter1138@9321: } bjarni@2855: } bjarni@2855: } bjarni@2855: } bjarni@2855: belugas@6422: /** bjarni@2855: * Converts all trains to the new subtype format introduced in savegame 16.2 bjarni@2855: * It also links multiheaded engines or make them forget they are multiheaded if no suitable partner is found bjarni@2855: */ rubidium@6247: void ConvertOldMultiheadToNew() bjarni@2855: { bjarni@2855: Vehicle *v; bjarni@2855: FOR_ALL_VEHICLES(v) { rubidium@6259: if (v->type == VEH_TRAIN) { skidd13@7931: SetBit(v->subtype, 7); // indicates that it's the old format and needs to be converted in the next loop bjarni@2855: } bjarni@2855: } bjarni@2855: bjarni@2855: FOR_ALL_VEHICLES(v) { rubidium@6259: if (v->type == VEH_TRAIN) { skidd13@7928: if (HasBit(v->subtype, 7) && ((v->subtype & ~0x80) == 0 || (v->subtype & ~0x80) == 4)) { peter1138@9321: for (Vehicle *u = v; u != NULL; u = u->Next()) { bjarni@2855: const RailVehicleInfo *rvi = RailVehInfo(u->engine_type); tron@3017: skidd13@7929: ClrBit(u->subtype, 7); tron@3017: switch (u->subtype) { rubidium@4434: case 0: /* TS_Front_Engine */ belugas@5868: if (rvi->railveh_type == RAILVEH_MULTIHEAD) SetMultiheaded(u); tron@3017: SetFrontEngine(u); tron@3017: SetTrainEngine(u); bjarni@2855: break; tron@3017: rubidium@4434: case 1: /* TS_Artic_Part */ tron@3017: u->subtype = 0; tron@3017: SetArticulatedPart(u); tron@3017: break; tron@3017: rubidium@4434: case 2: /* TS_Not_First */ tron@3017: u->subtype = 0; belugas@5868: if (rvi->railveh_type == RAILVEH_WAGON) { tron@3017: // normal wagon tron@3017: SetTrainWagon(u); tron@3017: break; tron@3017: } belugas@5868: if (rvi->railveh_type == RAILVEH_MULTIHEAD && rvi->image_index == u->spritenum - 1) { bjarni@2855: // rear end of a multiheaded engine bjarni@2855: SetMultiheaded(u); bjarni@2855: break; bjarni@2855: } belugas@5868: if (rvi->railveh_type == RAILVEH_MULTIHEAD) SetMultiheaded(u); bjarni@2855: SetTrainEngine(u); tron@3017: break; tron@3017: rubidium@4434: case 4: /* TS_Free_Car */ tron@3017: u->subtype = 0; tron@3017: SetTrainWagon(u); tron@3017: SetFreeWagon(u); tron@3017: break; tron@3017: default: NOT_REACHED(); break; tron@3017: } peter1138@9321: } bjarni@2855: } bjarni@2855: } bjarni@2855: } bjarni@2855: }