tron@2186: /* $Id$ */ tron@2186: rubidium@10429: /** @file train_cmd.cpp Handling of trains. */ belugas@6918: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@3234: #include "bridge_map.h" hackykid@1922: #include "debug.h" rubidium@8615: #include "tile_cmd.h" maedhros@6949: #include "landscape.h" tron@2561: #include "gui.h" tron@3315: #include "station_map.h" tron@3154: #include "tunnel_map.h" maedhros@7268: #include "articulated_vehicles.h" rubidium@8612: #include "command_func.h" truelight@0: #include "pathfind.h" matthijs@1247: #include "npf.h" rubidium@9281: #include "station_base.h" rubidium@9259: #include "news_func.h" rubidium@9282: #include "engine_func.h" peter1138@10382: #include "engine_base.h" rubidium@8750: #include "player_func.h" rubidium@8750: #include "player_base.h" rubidium@10222: #include "depot_base.h" rubidium@10222: #include "depot_func.h" truelight@1542: #include "waypoint.h" matthijs@1752: #include "vehicle_gui.h" bjarni@2676: #include "train.h" celestar@5573: #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@8596: #include "direction_func.h" KUDr@3900: #include "yapf/yapf.h" peter1138@6417: #include "cargotype.h" rubidium@7139: #include "group.h" glx@8298: #include "table/sprites.h" smatz@8579: #include "tunnelbridge_map.h" rubidium@8610: #include "strings_func.h" rubidium@8627: #include "functions.h" rubidium@8627: #include "window_func.h" rubidium@8636: #include "date_func.h" rubidium@8640: #include "vehicle_func.h" rubidium@8653: #include "sound_func.h" smatz@8734: #include "signal_func.h" rubidium@8707: #include "variables.h" rubidium@8708: #include "autoreplace_gui.h" rubidium@8720: #include "gfx_func.h" rubidium@8766: #include "settings_type.h" rubidium@9280: #include "order_func.h" peter1138@10266: #include "newgrf_station.h" rubidium@10272: #include "effectvehicle_func.h" smatz@8579: rubidium@8760: #include "table/strings.h" rubidium@8760: #include "table/train_cmd.h" truelight@0: truelight@742: static bool TrainCheckIfLineEnds(Vehicle *v); smatz@9206: static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image); smatz@8830: static TileIndex TrainApproachingCrossingTile(const 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@8744: smatz@8744: smatz@8744: /** smatz@8744: * Determine the side in which the train will leave the tile smatz@8744: * smatz@8744: * @param direction vehicle direction smatz@8744: * @param track vehicle track bits smatz@8744: * @return side of tile the train will leave smatz@8744: */ smatz@8744: static inline DiagDirection TrainExitDir(Direction direction, TrackBits track) smatz@8744: { smatz@8744: static const TrackBits state_dir_table[DIAGDIR_END] = { TRACK_BIT_RIGHT, TRACK_BIT_LOWER, TRACK_BIT_LEFT, TRACK_BIT_UPPER }; smatz@8744: smatz@8744: DiagDirection diagdir = DirToDiagDir(direction); smatz@8744: smatz@8744: /* Determine the diagonal direction in which we will exit this tile */ smatz@8744: if (!HasBit(direction, 0) && track != state_dir_table[diagdir]) { smatz@8744: diagdir = ChangeDiagDir(diagdir, DIAGDIRDIFF_90LEFT); smatz@8744: } smatz@8744: smatz@8744: return diagdir; smatz@8744: } 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@6440: if (!GetCargo(cargo)->is_freight) return 1; rubidium@10775: return _settings_game.vehicle.freight_trains; peter1138@5163: } peter1138@5163: peter1138@5163: hackykid@1905: /** peter1138@5588: * Recalculates the cached total power of a train. Should be called when the consist is changed peter1138@5588: * @param v First vehicle of the consist. peter1138@5588: */ smatz@9346: void TrainPowerChanged(Vehicle *v) peter1138@5588: { peter1138@6986: uint32 total_power = 0; peter1138@5588: uint32 max_te = 0; peter1138@5588: rubidium@7988: for (const Vehicle *u = v; u != NULL; u = u->Next()) { peter1138@5588: /* Power is not added for articulated parts */ peter1138@5588: if (IsArticulatedPart(u)) continue; peter1138@5588: rubidium@6498: RailType railtype = GetRailType(u->tile); tron@6020: bool engine_has_power = HasPowerOnRail(u->u.rail.railtype, railtype); tron@6020: bool wagon_has_power = HasPowerOnRail(v->u.rail.railtype, railtype); tron@6020: tron@6020: const RailVehicleInfo *rvi_u = RailVehInfo(u->engine_type); peter1138@5588: peter1138@6986: if (engine_has_power) { peter1138@6986: uint16 power = GetVehicleProperty(u, 0x0B, rvi_u->power); peter1138@6986: if (power != 0) { peter1138@10530: /* Halve power for multiheaded parts */ peter1138@10530: if (IsMultiheaded(u)) power /= 2; peter1138@10530: peter1138@6986: total_power += power; peter1138@6986: /* Tractive effort in (tonnes * 1000 * 10 =) N */ peter1138@6986: max_te += (u->u.rail.cached_veh_weight * 10000 * GetVehicleProperty(u, 0x1F, rvi_u->tractive_effort)) / 256; peter1138@6986: } peter1138@5588: } peter1138@5588: skidd13@8424: if (HasBit(u->u.rail.flags, VRF_POWEREDWAGON) && (wagon_has_power)) { peter1138@6986: total_power += RailVehInfo(u->u.rail.first_engine)->pow_wag_power; peter1138@5588: } peter1138@5588: } peter1138@5588: peter1138@6986: if (v->u.rail.cached_power != total_power || v->u.rail.cached_max_te != max_te) { rubidium@8035: /* If it has no power (no catenary), stop the train */ rubidium@8035: if (total_power == 0) v->vehstatus |= VS_STOPPED; rubidium@8035: peter1138@6986: v->u.rail.cached_power = total_power; peter1138@5588: v->u.rail.cached_max_te = max_te; peter1138@5588: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); smatz@8846: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); peter1138@5588: } peter1138@5588: } peter1138@5588: peter1138@5588: peter1138@5588: /** 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@9346: static void TrainCargoChanged(Vehicle *v) tron@2639: { peter1138@5162: uint32 weight = 0; hackykid@1905: rubidium@7988: for (Vehicle *u = v; u != NULL; u = u->Next()) { rubidium@7506: uint32 vweight = GetCargo(u->cargo_type)->weight * u->cargo.Count() * FreightWagonMult(u->cargo_type) / 16; peter1138@2602: belugas@6918: /* Vehicle weight is not added for articulated parts. */ bjarni@2676: if (!IsArticulatedPart(u)) { belugas@6918: /* vehicle weight is the sum of the weight of the vehicle and the weight of its cargo */ peter1138@7068: vweight += GetVehicleProperty(u, 0x16, RailVehInfo(u->engine_type)->weight); peter1138@2602: belugas@6918: /* powered wagons have extra weight added */ skidd13@8424: if (HasBit(u->u.rail.flags, VRF_POWEREDWAGON)) peter1138@4017: vweight += RailVehInfo(u->u.rail.first_engine)->pow_wag_weight; peter1138@2602: } hackykid@1905: belugas@6918: /* consist weight is the sum of the weight of all vehicles in the consist */ hackykid@1905: weight += vweight; hackykid@1905: belugas@6918: /* store vehicle weight in cache */ hackykid@1905: u->u.rail.cached_veh_weight = vweight; tron@6476: } hackykid@1905: belugas@6918: /* store consist weight in cache */ hackykid@1905: v->u.rail.cached_weight = weight; peter1138@5588: peter1138@5588: /* Now update train power (tractive effort is dependent on weight) */ peter1138@5588: TrainPowerChanged(v); hackykid@1905: } hackykid@1905: celestar@3355: 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. hackykid@1905: */ smatz@9346: void TrainConsistChanged(Vehicle *v) tron@2639: { smatz@9346: uint16 max_speed = UINT16_MAX; hackykid@1917: rubidium@6585: assert(v->type == VEH_TRAIN); bjarni@2676: assert(IsFrontEngine(v) || IsFreeWagon(v)); hackykid@1917: tron@6476: const RailVehicleInfo *rvi_v = RailVehInfo(v->engine_type); tron@6476: EngineID first_engine = IsFrontEngine(v) ? v->engine_type : INVALID_ENGINE; peter1138@2587: v->u.rail.cached_total_length = 0; rubidium@8732: v->u.rail.compatible_railtypes = RAILTYPES_NONE; hackykid@1905: peter1138@8674: bool train_can_tilt = true; peter1138@8674: rubidium@7988: for (Vehicle *u = v; u != NULL; u = u->Next()) { hackykid@1908: const RailVehicleInfo *rvi_u = RailVehInfo(u->engine_type); hackykid@1908: rubidium@7993: /* Check the v->first cache. */ rubidium@7993: assert(u->First() == v); peter1138@2993: belugas@6918: /* update the 'first engine' */ tron@6476: u->u.rail.first_engine = v == u ? INVALID_ENGINE : first_engine; tron@6074: u->u.rail.railtype = rvi_u->railtype; hackykid@1917: peter1138@10953: if (IsTrainEngine(u)) first_engine = u->engine_type; peter1138@10953: peter1138@10315: /* Set user defined data to its default value */ peter1138@10315: u->u.rail.user_def_data = rvi_u->user_def_data; peter1138@10315: } peter1138@10315: peter1138@10315: for (Vehicle *u = v; u != NULL; u = u->Next()) { peter1138@10315: /* Update user defined data (must be done before other properties) */ peter1138@10315: u->u.rail.user_def_data = GetVehicleProperty(u, 0x25, u->u.rail.user_def_data); peter1138@10315: } peter1138@10315: peter1138@10315: for (Vehicle *u = v; u != NULL; u = u->Next()) { peter1138@10315: const RailVehicleInfo *rvi_u = RailVehInfo(u->engine_type); peter1138@10315: peter1138@10315: if (!HasBit(EngInfo(u->engine_type)->misc_flags, EF_RAIL_TILTS)) train_can_tilt = false; peter1138@10315: peter1138@7099: /* Cache wagon override sprite group. NULL is returned if there is none */ peter1138@7099: u->u.rail.cached_override = GetWagonOverrideSpriteSet(u->engine_type, u->cargo_type, u->u.rail.first_engine); peter1138@7099: glx@8298: /* Reset color map */ glx@8298: u->colormap = PAL_NONE; glx@8298: 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@6918: /* 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@6918: /* Steam is offset by -4 units */ peter1138@2595: u->u.rail.cached_vis_effect = 4; peter1138@2595: } else { belugas@6918: /* Diesel fumes and sparks come from the centre */ peter1138@2595: u->u.rail.cached_vis_effect = 8; peter1138@2595: } peter1138@2595: } peter1138@2595: bjarni@2676: if (!IsArticulatedPart(u)) { peter1138@3926: /* Check powered wagon / visual effect callback */ skidd13@8424: if (HasBit(EngInfo(u->engine_type)->callbackmask, CBM_TRAIN_WAGON_POWER)) { peter1138@3926: uint16 callback = GetVehicleCallback(CBID_TRAIN_WAGON_POWER, 0, 0, u->engine_type, u); peter1138@3926: rubidium@10287: if (callback != CALLBACK_FAILED) u->u.rail.cached_vis_effect = GB(callback, 0, 8); peter1138@3926: } peter1138@3926: belugas@6119: if (rvi_v->pow_wag_power != 0 && rvi_u->railveh_type == RAILVEH_WAGON && skidd13@8424: UsesWagonOverride(u) && !HasBit(u->u.rail.cached_vis_effect, 7)) { belugas@6119: /* wagon is powered */ skidd13@8427: SetBit(u->u.rail.flags, VRF_POWEREDWAGON); // cache 'powered' status tron@6476: } else { skidd13@8425: ClrBit(u->u.rail.flags, VRF_POWEREDWAGON); peter1138@2595: } peter1138@2595: 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@8424: if (HasBit(u->u.rail.flags, VRF_EL_ENGINE_ALLOWED_NORMAL_RAIL)) { KUDr@5116: u->u.rail.railtype = RAILTYPE_RAIL; rubidium@8732: u->u.rail.compatible_railtypes |= RAILTYPES_RAIL; KUDr@5116: } KUDr@5116: belugas@6918: /* max speed is the minimum of the speed limits of all vehicles in the consist */ rubidium@10775: if ((rvi_u->railveh_type != RAILVEH_WAGON || _settings_game.vehicle.wagon_speed_limits) && !UsesWagonOverride(u)) { peter1138@6986: uint16 speed = GetVehicleProperty(u, 0x09, rvi_u->max_speed); peter1138@6986: if (speed != 0) max_speed = min(speed, max_speed); peter1138@6986: } hackykid@1908: } hackykid@1908: peter1138@7140: if (u->cargo_type == rvi_u->cargo_type && u->cargo_subtype == 0) { peter1138@7140: /* Set cargo capacity if we've not been refitted */ peter1138@7140: u->cargo_cap = GetVehicleProperty(u, 0x14, rvi_u->capacity); peter1138@7140: } peter1138@7104: belugas@6918: /* check the vehicle length (callback) */ tron@6476: uint16 veh_len = CALLBACK_FAILED; skidd13@8424: if (HasBit(EngInfo(u->engine_type)->callbackmask, CBM_VEHICLE_LENGTH)) { rubidium@7711: 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; skidd13@8418: veh_len = 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 hackykid@1922: u->u.rail.cached_veh_length = 8 - veh_len; peter1138@2587: v->u.rail.cached_total_length += u->u.rail.cached_veh_length; tron@6476: } hackykid@1905: belugas@6918: /* store consist weight/max speed in cache */ hackykid@1905: v->u.rail.cached_max_speed = max_speed; peter1138@8674: v->u.rail.cached_tilt = train_can_tilt; celestar@3355: belugas@6918: /* 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@9200: glx@9202: if (IsFrontEngine(v)) { glx@9202: UpdateTrainAcceleration(v); glx@9202: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); glx@9202: } hackykid@1905: } matthijs@1247: celestar@1179: enum AccelType { celestar@1179: AM_ACCEL, celestar@1179: AM_BRAKE celestar@1179: }; celestar@1179: belugas@6918: /** new acceleration*/ celestar@1179: static int GetTrainAcceleration(Vehicle *v, bool mode) celestar@1179: { smatz@9346: static const int absolute_max_speed = UINT16_MAX; peter1138@8665: int max_speed = absolute_max_speed; rubidium@8057: int speed = v->cur_speed * 10 / 16; // km-ish/h -> mp/h tron@6476: int curvecount[2] = {0, 0}; tron@6476: belugas@6918: /*first find the curve speed limit */ tron@6476: int numcurve = 0; tron@6476: int sum = 0; celestar@1179: int pos = 0; celestar@1179: int lastpos = -1; rubidium@7988: for (const Vehicle *u = v; u->Next() != NULL; u = u->Next(), pos++) { peter1138@8671: Direction this_dir = u->direction; peter1138@8671: Direction next_dir = u->Next()->direction; peter1138@8671: peter1138@8671: DirDiff dirdiff = DirDifference(this_dir, next_dir); peter1138@8671: if (dirdiff == DIRDIFF_SAME) continue; peter1138@8671: peter1138@8671: if (dirdiff == DIRDIFF_45LEFT) curvecount[0]++; peter1138@8671: if (dirdiff == DIRDIFF_45RIGHT) curvecount[1]++; peter1138@8671: if (dirdiff == DIRDIFF_45LEFT || dirdiff == DIRDIFF_45RIGHT) { peter1138@8671: if (lastpos != -1) { peter1138@8671: numcurve++; peter1138@8671: sum += pos - lastpos; peter1138@8671: if (pos - lastpos == 1) { peter1138@8671: max_speed = 88; celestar@1179: } celestar@1179: } peter1138@8671: lastpos = pos; celestar@1179: } celestar@1179: belugas@6918: /*if we have a 90 degree turn, fix the speed limit to 60 */ peter1138@8671: 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@8665: max_speed = absolute_max_speed; celestar@1179: } else if (total > 1) { peter1138@8671: if (numcurve > 0) sum /= numcurve; skidd13@8418: max_speed = 232 - (13 - Clamp(sum, 1, 12)) * (13 - Clamp(sum, 1, 12)); celestar@1179: } celestar@1179: } celestar@1179: peter1138@8665: if (max_speed != absolute_max_speed) { peter1138@8665: /* Apply the engine's rail type curve speed advantage, if it slowed by curves */ peter1138@8665: const RailtypeInfo *rti = GetRailTypeInfo(v->u.rail.railtype); peter1138@8665: max_speed += (max_speed / 2) * rti->curve_speed; peter1138@8674: peter1138@8674: if (v->u.rail.cached_tilt) { peter1138@8674: /* Apply max_speed bonus of 20% for a tilting train */ peter1138@8674: max_speed += max_speed / 5; peter1138@8674: } peter1138@8665: } celestar@1179: bjarni@2676: if (IsTileType(v->tile, MP_STATION) && IsFrontEngine(v)) { rubidium@9328: if (v->current_order.ShouldStopAtStation(v, GetStationIndex(v->tile))) { celestar@6324: int station_length = GetStationByTile(v->tile)->GetPlatformLength(v->tile, DirToDiagDir(v->direction)); peter1138@8666: peter1138@8666: int st_max_speed = 120; peter1138@8666: peter1138@8666: int delta_v = v->cur_speed / (station_length + 1); peter1138@8666: if (v->max_speed > (v->cur_speed - delta_v)) { peter1138@8666: st_max_speed = v->cur_speed - (delta_v / 10); peter1138@8666: } peter1138@8666: peter1138@8666: st_max_speed = max(st_max_speed, 25 * station_length); peter1138@8666: max_speed = min(max_speed, st_max_speed); celestar@1179: } celestar@1179: } celestar@1179: tron@6476: int mass = v->u.rail.cached_weight; tron@6476: int power = v->u.rail.cached_power * 746; hackykid@1905: max_speed = min(max_speed, v->u.rail.cached_max_speed); hackykid@1905: tron@6476: int num = 0; //number of vehicles, change this into the number of axles later tron@6476: int incl = 0; tron@6476: int drag_coeff = 20; //[1e-4] rubidium@7988: for (const Vehicle *u = v; u != NULL; u = u->Next()) { celestar@1179: num++; celestar@1179: drag_coeff += 3; celestar@1179: rubidium@6319: if (u->u.rail.track == TRACK_BIT_DEPOT) max_speed = min(max_speed, 61); celestar@1179: skidd13@8424: 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@8424: } 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@6476: const int area = 120; tron@6476: const int friction = 35; //[1e-3] tron@6476: 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@6476: const int max_te = v->u.rail.cached_max_te; // [N] tron@6476: 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@5588: if (mode == AM_ACCEL && force > max_te) force = max_te; tron@1472: break; tron@1472: tron@6476: default: NOT_REACHED(); tron@2519: case RAILTYPE_MAGLEV: celestar@1179: force = power / 25; tron@1472: break; celestar@1179: } tron@1472: } else { belugas@6918: /* "kickoff" acceleration */ peter1138@5588: force = (mode == AM_ACCEL && v->u.rail.railtype != RAILTYPE_MAGLEV) ? min(max_te, power) : power; peter1138@5588: 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@9346: 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@6476: uint power = v->u.rail.cached_power; tron@6476: uint weight = v->u.rail.cached_weight; truelight@0: assert(weight != 0); skidd13@8418: v->acceleration = Clamp(power / weight * 4, 1, 255); truelight@0: } truelight@0: peter1138@10293: SpriteID Train::GetImage(Direction direction) const truelight@0: { peter1138@10293: uint8 spritenum = this->spritenum; peter1138@10293: SpriteID sprite; truelight@0: skidd13@8424: if (HasBit(this->u.rail.flags, VRF_REVERSE_DIRECTION)) direction = ReverseDir(direction); bjarni@3256: peter1138@10293: if (is_custom_sprite(spritenum)) { peter1138@10293: sprite = GetCustomVehicleSprite(this, (Direction)(direction + 4 * IS_CUSTOM_SECONDHEAD_SPRITE(spritenum))); peter1138@10293: if (sprite != 0) return sprite; peter1138@10293: peter1138@10382: spritenum = GetEngine(this->engine_type)->image_index; truelight@0: } truelight@193: peter1138@10293: sprite = _engine_sprite_base[spritenum] + ((direction + _engine_sprite_add[spritenum]) & _engine_sprite_and[spritenum]); peter1138@10293: peter1138@10293: if (this->cargo.Count() >= this->cargo_cap / 2U) sprite += _wagon_full_adder[spritenum]; peter1138@10293: peter1138@10293: return sprite; peter1138@10293: } peter1138@10293: peter1138@10293: static SpriteID GetRailIcon(EngineID engine, bool rear_head, int &y) peter1138@10293: { peter1138@10293: Direction dir = rear_head ? DIR_E : DIR_W; peter1138@10293: uint8 spritenum = RailVehInfo(engine)->image_index; peter1138@10293: peter1138@10293: if (is_custom_sprite(spritenum)) { peter1138@10293: SpriteID sprite = GetCustomVehicleIcon(engine, dir); peter1138@10293: if (sprite != 0) { peter1138@10293: y += _traininfo_vehicle_pitch; // TODO Make this per-GRF peter1138@10293: return sprite; peter1138@10293: } peter1138@10293: peter1138@10382: spritenum = GetEngine(engine)->image_index; peter1138@10293: } peter1138@10293: peter1138@10293: if (rear_head) spritenum++; peter1138@10293: peter1138@10293: return ((6 + _engine_sprite_add[spritenum]) & _engine_sprite_and[spritenum]) + _engine_sprite_base[spritenum]; truelight@0: } truelight@0: peter1138@5919: void DrawTrainEngine(int x, int y, EngineID engine, SpriteID pal) truelight@0: { peter1138@10293: if (RailVehInfo(engine)->railveh_type == RAILVEH_MULTIHEAD) { peter1138@10293: int yf = y; peter1138@10293: int yr = y; peter1138@10293: peter1138@10293: SpriteID spritef = GetRailIcon(engine, false, yf); peter1138@10293: SpriteID spriter = GetRailIcon(engine, true, yr); peter1138@10293: DrawSprite(spritef, pal, x - 14, yf); peter1138@10293: DrawSprite(spriter, pal, x + 15, yr); peter1138@10293: } else { peter1138@10293: SpriteID sprite = GetRailIcon(engine, false, y); peter1138@10293: DrawSprite(sprite, pal, x, y); truelight@0: } truelight@0: } truelight@0: rubidium@7439: static CommandCost CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags) truelight@0: { tron@6476: const RailVehicleInfo *rvi = RailVehInfo(engine); rubidium@8726: CommandCost value(EXPENSES_NEW_VEHICLES, (GetEngineProperty(engine, 0x17, rvi->base_cost) * _price.build_railwagon) >> 8); tron@6476: rubidium@8091: uint num_vehicles = 1 + CountArticulatedParts(engine, false); peter1138@2602: truelight@0: if (!(flags & DC_QUERY_COST)) { bjarni@9277: /* Check that the wagon can drive on the track in question */ bjarni@9277: if (!IsCompatibleRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR; bjarni@9277: rubidium@8091: /* Allow for the wagon and the articulated parts, plus one to "terminate" the list. */ smatz@10905: Vehicle **vl = AllocaM(Vehicle*, num_vehicles + 1); rubidium@8091: memset(vl, 0, sizeof(*vl) * (num_vehicles + 1)); peter1138@4831: rubidium@7894: 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@6476: Vehicle *v = vl[0]; tron@2639: v->spritenum = rvi->image_index; truelight@0: tron@6476: Vehicle *u = NULL; tron@6476: tron@6476: Vehicle *w; truelight@919: FOR_ALL_VEHICLES(w) { rubidium@6585: if (w->type == VEH_TRAIN && w->tile == tile && rubidium@8192: IsFreeWagon(w) && w->engine_type == engine && rubidium@8192: !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@8278: v = new (v) Train(); truelight@0: v->engine_type = engine; truelight@0: tron@6476: DiagDirection dir = GetRailDepotDirection(tile); tron@2150: tron@3153: v->direction = DiagDirToDir(dir); tron@1986: v->tile = tile; truelight@193: tron@6476: int x = TileX(tile) * TILE_SIZE | _vehicle_initial_x_fract[dir]; tron@6476: 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@6476: v->z_pos = GetSlopeZ(x, y); truelight@0: v->owner = _current_player; rubidium@6319: v->u.rail.track = TRACK_BIT_DEPOT; truelight@0: v->vehstatus = VS_HIDDEN | VS_DEFPAL; truelight@0: smatz@9346: // v->subtype = 0; bjarni@2676: SetTrainWagon(v); maedhros@7267: truelight@0: if (u != NULL) { rubidium@7989: 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@9346: // v->cargo_subtype = 0; truelight@0: v->cargo_cap = rvi->capacity; rubidium@7446: v->value = value.GetCost(); truelight@0: // v->day_counter = 0; truelight@0: tron@6074: 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@7139: v->group_id = DEFAULT_GROUP; rubidium@7139: maedhros@7353: AddArticulatedParts(vl, VEH_TRAIN); peter1138@2602: bjarni@2564: _new_vehicle_id = v->index; truelight@0: truelight@0: VehiclePositionChanged(v); rubidium@7993: TrainConsistChanged(v->First()); rubidium@7993: UpdateTrainGroupID(v->First()); truelight@0: truelight@0: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); bjarni@2970: if (IsLocalPlayer()) { bjarni@7921: InvalidateAutoreplaceWindow(v->engine_type, v->group_id); // updates the replace Train window bjarni@2970: } bjarni@6195: GetPlayer(_current_player)->num_engines[engine]++; truelight@0: } truelight@0: } truelight@0: rubidium@8726: return value; truelight@0: } truelight@0: belugas@6918: /** Move all free vehicles in the depot to the train */ smatz@9346: static void NormalizeTrainVehInDepot(const Vehicle *u) truelight@0: { smatz@9346: const Vehicle *v; tron@1472: truelight@0: FOR_ALL_VEHICLES(v) { rubidium@6585: if (v->type == VEH_TRAIN && IsFreeWagon(v) && truelight@0: v->tile == u->tile && rubidium@6319: 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@9346: static CommandCost EstimateTrainCost(EngineID engine, const RailVehicleInfo *rvi) truelight@0: { rubidium@8726: return CommandCost(EXPENSES_NEW_VEHICLES, GetEngineProperty(engine, 0x17, rvi->base_cost) * (_price.build_railvehicle >> 3) >> 5); truelight@0: } truelight@0: smatz@9346: static void AddRearEngineToMultiheadedTrain(Vehicle *v, Vehicle *u, bool building) bjarni@1060: { rubidium@7993: 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@6319: u->u.rail.track = TRACK_BIT_DEPOT; bjarni@1060: u->vehstatus = v->vehstatus & ~VS_STOPPED; smatz@9346: // 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@7989: 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@6918: * @param flags type of operation Darkvater@1784: * @param p1 engine type id bjarni@3816: * @param p2 bit 0 when set, the train will get number 0, otherwise it will get a free number bjarni@3816: * bit 1 prevents any free cars from being added to the train truelight@0: */ rubidium@7439: CommandCost CmdBuildRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { Darkvater@1784: /* Check if the engine-type is valid (for the player) */ maedhros@6903: if (!IsEngineBuildable(p1, VEH_TRAIN, _current_player)) return_cmd_error(STR_RAIL_VEHICLE_NOT_AVAILABLE); bjarni@1196: Darkvater@1784: /* Check if the train is actually being built in a depot belonging Darkvater@1784: * to the player. Doesn't matter if only the cost is queried */ pasky@1443: if (!(flags & DC_QUERY_COST)) { smatz@10221: if (!IsRailDepotTile(tile)) return CMD_ERROR; tron@1901: if (!IsTileOwner(tile, _current_player)) return CMD_ERROR; pasky@1443: } bjarni@1221: tron@6476: const RailVehicleInfo *rvi = RailVehInfo(p1); bjarni@2244: belugas@6119: if (rvi->railveh_type == RAILVEH_WAGON) return CmdBuildRailWagon(p1, tile, flags); truelight@0: rubidium@7439: CommandCost value = EstimateTrainCost(p1, rvi); tron@6476: tron@6476: uint num_vehicles = tron@6476: (rvi->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1) + rubidium@8091: CountArticulatedParts(p1, false); truelight@0: truelight@0: if (!(flags & DC_QUERY_COST)) { bjarni@9277: /* Check if depot and new engine uses the same kind of tracks * bjarni@9277: * We need to see if the engine got power on the tile to avoid eletric engines in non-electric depots */ bjarni@9277: if (!HasPowerOnRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR; bjarni@9277: rubidium@8091: /* Allow for the dual-heads and the articulated parts, plus one to "terminate" the list. */ smatz@10905: Vehicle **vl = AllocaM(Vehicle*, num_vehicles + 1); rubidium@8091: memset(vl, 0, sizeof(*vl) * (num_vehicles + 1)); peter1138@4831: smatz@9346: if (!Vehicle::AllocateList(vl, num_vehicles)) { truelight@0: return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); smatz@9346: } truelight@0: tron@6476: Vehicle *v = vl[0]; tron@6476: skidd13@8424: UnitID unit_num = HasBit(p2, 0) ? 0 : GetFreeUnitNumber(VEH_TRAIN); rubidium@10775: 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@8279: v = new (v) Train(); truelight@0: v->unitnumber = unit_num; tron@3153: v->direction = DiagDirToDir(dir); tron@1986: v->tile = tile; truelight@0: v->owner = _current_player; tron@3491: v->x_pos = x; tron@3491: v->y_pos = y; tron@6476: v->z_pos = GetSlopeZ(x, y); smatz@9346: // v->running_ticks = 0; rubidium@6319: 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@9346: // v->cargo_subtype = 0; truelight@0: v->cargo_cap = rvi->capacity; truelight@0: v->max_speed = rvi->max_speed; rubidium@7446: v->value = value.GetCost(); truelight@1266: v->last_station_visited = INVALID_STATION; smatz@9346: // v->dest_tile = 0; truelight@193: tron@2477: v->engine_type = p1; truelight@0: tron@6074: 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@8754: v->name = NULL; tron@6074: v->u.rail.railtype = rvi->railtype; bjarni@2564: _new_vehicle_id = v->index; truelight@193: rubidium@10775: 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@9346: // v->vehicle_flags = 0; skidd13@8427: if (e->flags & ENGINE_EXCLUSIVE_PREVIEW) SetBit(v->vehicle_flags, VF_BUILT_AS_PROTOTYPE); maedhros@6502: rubidium@7139: v->group_id = DEFAULT_GROUP; rubidium@7139: smatz@9346: // v->subtype = 0; bjarni@2676: SetFrontEngine(v); bjarni@2676: SetTrainEngine(v); bjarni@2676: truelight@0: VehiclePositionChanged(v); truelight@0: belugas@6119: 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@7353: AddArticulatedParts(vl, VEH_TRAIN); bjarni@2244: } truelight@0: hackykid@1905: TrainConsistChanged(v); rubidium@7139: UpdateTrainGroupID(v); bjarni@2244: skidd13@8424: if (!HasBit(p2, 1)) { // 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@10621: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); truelight@0: InvalidateWindow(WC_COMPANY, v->owner); smatz@9346: if (IsLocalPlayer()) { bjarni@7921: InvalidateAutoreplaceWindow(v->engine_type, v->group_id); // updates the replace Train window smatz@9346: } bjarni@6195: bjarni@6195: GetPlayer(_current_player)->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@10221: if (!IsRailDepotTile(tile) || v->cur_speed != 0) return -1; truelight@0: tron@6476: int count = 0; rubidium@7988: 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@8022: if (!IsArticulatedPart(v) && !IsRearDualheaded(v)) count++; rubidium@6319: 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@5838: 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@6476: 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@6918: /* 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@6476: Vehicle *u; peter1138@2602: for (u = first; GetNextVehicle(u) != v; u = GetNextVehicle(u)) {} rubidium@7989: 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@8192: if (dst->type == VEH_TRAIN && IsFreeWagon(dst) && dst->tile == tile && !HASBITS(dst->vehstatus, VS_CRASHED)) { belugas@6918: /* 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@7988: 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@7993: UnlinkWagon(v, v->First()); bjarni@2676: if (dest == NULL) return; bjarni@2676: rubidium@7993: Vehicle *next = dest->Next(); glx@8503: v->SetNext(NULL); rubidium@7988: dest->SetNext(v); rubidium@7993: 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@6476: Vehicle *u; rubidium@7988: 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@6918: * @param flags type of operation 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@7439: 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@6476: Vehicle *src = GetVehicle(s); bjarni@1237: rubidium@6585: if (src->type != VEH_TRAIN || !CheckOwnership(src->owner)) return CMD_ERROR; truelight@0: rubidium@8192: /* Do not allow moving crashed vehicles inside the depot, it is likely to cause asserts later */ rubidium@8192: if (HASBITS(src->vehstatus, VS_CRASHED)) return CMD_ERROR; rubidium@8192: belugas@6918: /* if nothing is selected as destination, try and find a matching vehicle to drag to. */ tron@6476: Vehicle *dst; tron@2484: if (d == INVALID_VEHICLE) { Darkvater@4198: dst = IsTrainEngine(src) ? NULL : FindGoodVehiclePos(src); truelight@0: } else { tron@6462: if (!IsValidVehicleID(d)) return CMD_ERROR; tron@2484: dst = GetVehicle(d); rubidium@6585: if (dst->type != VEH_TRAIN || !CheckOwnership(dst->owner)) return CMD_ERROR; rubidium@8192: rubidium@8192: /* Do not allow appending to crashed vehicles, too */ rubidium@8192: if (HASBITS(dst->vehstatus, VS_CRASHED)) return CMD_ERROR; truelight@0: } truelight@0: belugas@6918: /* if an articulated part is being handled, deal with its parent vehicle */ rubidium@7993: while (IsArticulatedPart(src)) src = src->Previous(); peter1138@2602: if (dst != NULL) { rubidium@7993: while (IsArticulatedPart(dst)) dst = dst->Previous(); peter1138@2602: } peter1138@2602: belugas@6918: /* don't move the same vehicle.. */ rubidium@7446: if (src == dst) return CommandCost(); truelight@193: truelight@0: /* locate the head of the two chains */ rubidium@7993: Vehicle *src_head = src->First(); tron@6476: Vehicle *dst_head; peter1138@2602: if (dst != NULL) { rubidium@7993: dst_head = dst->First(); tron@6462: if (dst_head->tile != src_head->tile) return CMD_ERROR; belugas@6918: /* Now deal with articulated part of destination wagon */ peter1138@2602: dst = GetLastEnginePart(dst); tron@6462: } else { tron@6462: dst_head = NULL; peter1138@2602: } truelight@193: bjarni@8022: if (IsRearDualheaded(src)) return_cmd_error(STR_REAR_ENGINE_FOLLOW_FRONT_ERROR); bjarni@2676: belugas@6918: /* when moving all wagons, we can't have the same src_head and dst_head */ skidd13@8424: if (HasBit(p2, 0) && src_head == dst_head) return CommandCost(); truelight@0: peter1138@2883: { rubidium@10775: int max_len = _settings_game.vehicle.mammoth_trains ? 100 : 10; peter1138@2883: belugas@6918: /* check if all vehicles in the source train are stopped inside a depot. */ tron@6476: int src_len = CheckTrainStoppedInDepot(src_head); tron@3183: if (src_len < 0) return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED); peter1138@2883: belugas@6918: /* 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@6918: /* 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@6918: /* We are moving between rows, so only count the wagons from the source belugas@6918: * row that are being moved. */ skidd13@8424: 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@6918: /* 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@6918: /* 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@6918: /* 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@6918: /* 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@6918: /* moving a loco to a new line?, then we need to assign a unitnumber. */ bjarni@2676: if (dst == NULL && !IsFrontEngine(src) && IsTrainEngine(src)) { rubidium@6585: UnitID unit_num = GetFreeUnitNumber(VEH_TRAIN); rubidium@10775: 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: rubidium@8658: /* rubidium@8658: * Check whether the vehicles in the source chain are in the destination rubidium@8658: * chain. This can easily be done by checking whether the first vehicle rubidium@8658: * of the source chain is in the destination chain as the Next/Previous rubidium@8658: * pointers always make a doubly linked list of it where the assumption rubidium@8658: * v->Next()->Previous() == v holds (assuming v->Next() != NULL). rubidium@8658: */ rubidium@8658: bool src_in_dst = false; rubidium@8658: for (Vehicle *v = dst_head; !src_in_dst && v != NULL; v = v->Next()) src_in_dst = v == src; rubidium@8658: rubidium@8658: /* rubidium@8658: * If the source chain is in the destination chain then the user is rubidium@8658: * only reordering the vehicles, thus not attaching a new vehicle. rubidium@8658: * Therefor the 'allow wagon attach' callback does not need to be rubidium@8658: * called. If it would be called strange things would happen because rubidium@8658: * one 'attaches' an already 'attached' vehicle causing more trouble rubidium@8658: * than it actually solves (infinite loops and such). rubidium@8658: */ rubidium@8658: if (dst_head != NULL && !src_in_dst) { rubidium@8658: /* rubidium@8658: * When performing the 'allow wagon attach' callback, we have to check rubidium@8658: * that for each and every wagon, not only the first one. This means rubidium@8658: * that we have to test one wagon, attach it to the train and then test rubidium@8658: * the next wagon till we have reached the end. We have to restore it rubidium@8658: * to the state it was before we 'tried' attaching the train when the rubidium@8658: * attaching fails or succeeds because we are not 'only' doing this rubidium@8658: * in the DC_EXEC state. rubidium@8658: */ rubidium@8658: Vehicle *dst_tail = dst_head; rubidium@8658: while (dst_tail->Next() != NULL) dst_tail = dst_tail->Next(); rubidium@8658: rubidium@8658: Vehicle *orig_tail = dst_tail; rubidium@8658: Vehicle *next_to_attach = src; rubidium@8658: Vehicle *src_previous = src->Previous(); rubidium@8658: rubidium@8658: while (next_to_attach != NULL) { peter1138@8664: /* Back up and clear the first_engine data to avoid using wagon override group */ peter1138@8664: EngineID first_engine = next_to_attach->u.rail.first_engine; peter1138@8664: next_to_attach->u.rail.first_engine = INVALID_ENGINE; peter1138@8664: rubidium@8658: uint16 callback = GetVehicleCallbackParent(CBID_TRAIN_ALLOW_WAGON_ATTACH, 0, 0, dst_head->engine_type, next_to_attach, dst_head); peter1138@8664: peter1138@8664: /* Restore original first_engine data */ peter1138@8664: next_to_attach->u.rail.first_engine = first_engine; peter1138@8664: rubidium@8658: if (callback != CALLBACK_FAILED) { rubidium@8658: StringID error = STR_NULL; rubidium@8658: rubidium@8658: if (callback == 0xFD) error = STR_INCOMPATIBLE_RAIL_TYPES; rubidium@8658: if (callback < 0xFD) error = GetGRFStringID(GetEngineGRFID(dst_head->engine_type), 0xD000 + callback); rubidium@8658: rubidium@8658: if (error != STR_NULL) { rubidium@8658: /* rubidium@8658: * The attaching is not allowed. In this case 'next_to_attach' rubidium@8658: * can contain some vehicles of the 'source' and the destination rubidium@8658: * train can have some too. We 'just' add the to-be added wagons rubidium@8658: * to the chain and then split it where it was previously rubidium@8658: * separated, i.e. the tail of the original destination train. rubidium@8658: * Furthermore the 'previous' link of the original source vehicle needs rubidium@8658: * to be restored, otherwise the train goes missing in the depot. rubidium@8658: */ rubidium@8658: dst_tail->SetNext(next_to_attach); rubidium@8658: orig_tail->SetNext(NULL); rubidium@8658: if (src_previous != NULL) src_previous->SetNext(src); rubidium@8658: rubidium@8658: return_cmd_error(error); rubidium@8658: } peter1138@3727: } rubidium@8658: peter1138@8664: /* Only check further wagons if told to move the chain */ peter1138@8664: if (!HasBit(p2, 0)) break; peter1138@8664: rubidium@8658: /* rubidium@8658: * Adding a next wagon to the chain so we can test the other wagons. rubidium@8658: * First 'take' the first wagon from 'next_to_attach' and move it rubidium@8658: * to the next wagon. Then add that to the tail of the destination rubidium@8658: * train and update the tail with the new vehicle. rubidium@8658: */ rubidium@8658: Vehicle *to_add = next_to_attach; rubidium@8658: next_to_attach = next_to_attach->Next(); rubidium@8658: rubidium@8658: to_add->SetNext(NULL); rubidium@8658: dst_tail->SetNext(to_add); rubidium@8658: dst_tail = dst_tail->Next(); peter1138@3727: } rubidium@8658: rubidium@8658: /* rubidium@8658: * When we reach this the attaching is allowed. It also means that the rubidium@8658: * chain of vehicles to attach is empty, so we do not need to merge that. rubidium@8658: * This means only the splitting needs to be done. rubidium@8658: * Furthermore the 'previous' link of the original source vehicle needs rubidium@8658: * to be restored, otherwise the train goes missing in the depot. rubidium@8658: */ rubidium@8658: orig_tail->SetNext(NULL); rubidium@8658: if (src_previous != NULL) src_previous->SetNext(src); peter1138@3727: } truelight@0: truelight@0: /* do it? */ truelight@0: if (flags & DC_EXEC) { rubidium@7139: /* If we move the front Engine and if the second vehicle is not an engine rubidium@7139: add the whole vehicle to the DEFAULT_GROUP */ rubidium@7139: if (IsFrontEngine(src) && !IsDefaultGroupID(src->group_id)) { rubidium@8025: Vehicle *v = GetNextVehicle(src); rubidium@8025: rubidium@8025: if (v != NULL && IsTrainEngine(v)) { rubidium@8025: v->group_id = src->group_id; rubidium@8025: src->group_id = DEFAULT_GROUP; rubidium@7139: } rubidium@7139: } rubidium@7139: skidd13@8424: if (HasBit(p2, 0)) { belugas@6918: /* 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@7989: 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@6918: /* 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@6918: /* unlink single wagon from linked list */ hackykid@1917: src_head = UnlinkWagon(src, src_head); rubidium@7989: 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@6918: /* 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@6918: /* 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@7139: smatz@9346: /* Decrease the engines number of the src engine_type */ rubidium@7139: if (!IsDefaultGroupID(src->group_id) && IsValidGroupID(src->group_id)) { rubidium@7139: GetGroup(src->group_id)->num_engines[src->engine_type]--; rubidium@7139: } rubidium@7139: smatz@9346: /* If we move an engine to a new line affect it to the DEFAULT_GROUP */ rubidium@7139: 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@6918: /* 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@8025: 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@6918: /* link in the wagon(s) in the chain. */ truelight@0: { tron@1472: Vehicle *v; tron@1472: smatz@9191: for (v = src; GetNextVehicle(v) != NULL; v = GetNextVehicle(v)) {} rubidium@7988: GetLastEnginePart(v)->SetNext(dst->Next()); truelight@0: } rubidium@7988: dst->SetNext(src); truelight@0: } rubidium@7993: bjarni@2676: if (src->u.rail.other_multiheaded_part != NULL) { bjarni@2676: if (src->u.rail.other_multiheaded_part == src_head) { rubidium@7988: 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@7139: /* As in CmdMoveRailVehicle src_head->group_id will be equal to DEFAULT_GROUP rubidium@7139: * we need to save the group and reaffect it to src_head */ rubidium@7139: const GroupID tmp_g = src_head->group_id; tron@3491: CmdMoveRailVehicle(0, flags, src_head->index | (INVALID_VEHICLE << 16), 1); rubidium@7139: 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); hackykid@1905: TrainConsistChanged(src_head); rubidium@7139: 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@8846: 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@6476: } truelight@0: Darkvater@4198: if (dst_head != NULL) { bjarni@2676: NormaliseTrainConsist(dst_head); hackykid@1917: TrainConsistChanged(dst_head); rubidium@7139: UpdateTrainGroupID(dst_head); bjarni@2676: if (IsFrontEngine(dst_head)) { hackykid@1917: /* Update the refit button and window */ smatz@8846: 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@10621: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); truelight@0: } truelight@0: rubidium@7446: return CommandCost(); truelight@0: } truelight@0: Darkvater@1784: /** Start/Stop a train. tron@3491: * @param tile unused belugas@6918: * @param flags type of operation Darkvater@1784: * @param p1 train to start/stop Darkvater@1784: * @param p2 unused Darkvater@1784: */ rubidium@7439: CommandCost CmdStartStopTrain(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@4352: if (!IsValidVehicleID(p1)) return CMD_ERROR; bjarni@1237: tron@6476: Vehicle *v = GetVehicle(p1); truelight@0: rubidium@6585: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: peter1138@4244: /* Check if this train can be started/stopped. The callback will fail or peter1138@4244: * return 0xFF if it can. */ tron@6476: uint16 callback = GetVehicleCallback(CBID_VEHICLE_START_STOP_CHECK, 0, 0, v->engine_type, v); rubidium@10286: if (callback != CALLBACK_FAILED && GB(callback, 0, 8) != 0xFF) { peter1138@4244: StringID error = GetGRFStringID(GetEngineGRFID(v->engine_type), 0xD000 + callback); peter1138@4244: return_cmd_error(error); peter1138@4244: } peter1138@4244: bjarni@4251: if (v->vehstatus & VS_STOPPED && v->u.rail.cached_power == 0) return_cmd_error(STR_TRAIN_START_NO_CATENARY); bjarni@4251: truelight@0: if (flags & DC_EXEC) { rubidium@6319: if (v->vehstatus & VS_STOPPED && v->u.rail.track == TRACK_BIT_DEPOT) { tron@3139: DeleteVehicleNews(p1, STR_8814_TRAIN_IS_WAITING_IN_DEPOT); tron@3139: } tron@3139: truelight@0: v->vehstatus ^= VS_STOPPED; smatz@8846: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); truelight@0: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); truelight@0: } rubidium@7446: return CommandCost(); truelight@0: } truelight@0: Darkvater@1784: /** Sell a (single) train wagon/engine. tron@3491: * @param tile unused belugas@6918: * @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@10229: * 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@7439: CommandCost CmdSellRailWagon(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { peter1138@7285: /* Check if we deleted a vehicle window */ peter1138@7285: Window *w = NULL; peter1138@7285: truelight@4352: if (!IsValidVehicleID(p1) || p2 > 2) return CMD_ERROR; truelight@0: tron@6476: Vehicle *v = GetVehicle(p1); truelight@0: rubidium@6585: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: rubidium@8191: if (HASBITS(v->vehstatus, VS_CRASHED)) return_cmd_error(STR_CAN_T_SELL_DESTROYED_VEHICLE); rubidium@8191: rubidium@7993: while (IsArticulatedPart(v)) v = v->Previous(); rubidium@7993: Vehicle *first = v->First(); truelight@193: belugas@6918: /* 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@8022: 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@10596: DeleteWindowById(WC_VEHICLE_VIEW, first->index); bjarni@2618: } truelight@0: InvalidateWindow(WC_VEHICLE_DEPOT, first->tile); rubidium@10621: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); Darkvater@1766: } Darkvater@1766: rubidium@8726: 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@7486: cost.AddCost(-rear->value); Darkvater@1766: if (flags & DC_EXEC) { bjarni@2676: UnlinkWagon(rear, first); bjarni@5256: DeleteDepotHighlightOfVehicle(rear); rubidium@7894: delete rear; truelight@0: } truelight@0: } Darkvater@1766: Darkvater@1766: /* 2. We are selling the first engine, some special action might be required bjarni@2676: * here, so take attention */ Darkvater@1770: if ((flags & DC_EXEC) && v == first) { tron@6476: Vehicle *new_f = GetNextVehicle(first); Darkvater@1766: Darkvater@1766: /* 2.2 If there are wagons present after the deleted front engine, check rubidium@6648: * if the second wagon (which will be first) is an engine. If it is one, rubidium@6648: * promote it as a new train, retaining the unitnumber, orders */ rubidium@6648: if (new_f != NULL && IsTrainEngine(new_f)) { rubidium@6648: switch_engine = true; rubidium@6648: /* Copy important data from the front engine */ rubidium@6648: new_f->unitnumber = first->unitnumber; rubidium@6648: new_f->current_order = first->current_order; rubidium@6648: new_f->cur_order_index = first->cur_order_index; rubidium@6648: new_f->orders = first->orders; rubidium@6648: new_f->num_orders = first->num_orders; rubidium@8025: new_f->group_id = first->group_id; rubidium@6648: rubidium@6648: if (first->prev_shared != NULL) { rubidium@6648: first->prev_shared->next_shared = new_f; rubidium@6648: new_f->prev_shared = first->prev_shared; Darkvater@1766: } rubidium@6648: rubidium@6648: if (first->next_shared != NULL) { rubidium@6648: first->next_shared->prev_shared = new_f; rubidium@6648: new_f->next_shared = first->next_shared; rubidium@6648: } rubidium@6648: rubidium@6648: /* rubidium@6648: * Remove all order information from the front train, to rubidium@6648: * prevent the order and the shared order list to be rubidium@6648: * destroyed by Destroy/DeleteVehicle. rubidium@6648: */ rubidium@6648: first->orders = NULL; rubidium@6648: first->prev_shared = NULL; rubidium@6648: first->next_shared = NULL; rubidium@8025: first->group_id = DEFAULT_GROUP; rubidium@6648: peter1138@7285: /* If we deleted a window then open a new one for the 'new' train */ rubidium@7982: if (IsLocalPlayer() && w != NULL) ShowVehicleViewWindow(new_f); Darkvater@1766: } Darkvater@1766: } Darkvater@1766: Darkvater@1766: /* 3. Delete the requested wagon */ rubidium@7446: cost.AddCost(-v->value); Darkvater@1766: if (flags & DC_EXEC) { Darkvater@1766: first = UnlinkWagon(v, first); bjarni@5256: DeleteDepotHighlightOfVehicle(v); rubidium@7894: delete v; Darkvater@1766: Darkvater@1766: /* 4 If the second wagon was an engine, update it to front_engine smatz@9346: * 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); hackykid@1905: TrainConsistChanged(first); rubidium@7139: UpdateTrainGroupID(first); glx@9202: 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@9108: if (p2 == 2 && HasBit(ori_subtype, TS_FRONT)) { smatz@9346: for (v = first; v != NULL;) { smatz@9346: Vehicle *tmp = GetNextVehicle(v); tron@3491: DoCommand(v->tile, v->index | INVALID_VEHICLE << 16, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); smatz@9346: 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@9346: 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@7446: cost.AddCost(-rear->value); maedhros@7281: maedhros@7281: /* If this is a multiheaded vehicle with nothing maedhros@7281: * between the parts, tmp will be pointing to the maedhros@7281: * rear part, which is unlinked from the train and maedhros@7281: * deleted here. However, because tmp has already maedhros@7281: * been set it needs to be updated now so that the maedhros@7281: * loop never sees the rear part. */ maedhros@7281: if (tmp == rear) tmp = GetNextVehicle(tmp); maedhros@7281: bjarni@2676: if (flags & DC_EXEC) { bjarni@2676: first = UnlinkWagon(rear, first); bjarni@5256: DeleteDepotHighlightOfVehicle(rear); rubidium@7894: 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@7446: cost.AddCost(-v->value); Darkvater@1766: if (flags & DC_EXEC) { Darkvater@1766: first = UnlinkWagon(v, first); bjarni@5256: DeleteDepotHighlightOfVehicle(v); rubidium@7894: 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); hackykid@1905: TrainConsistChanged(first); rubidium@7139: 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@7054: void Train::UpdateDeltaXY(Direction direction) truelight@0: { rubidium@7054: #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@7054: this->x_offs = GB(x, 0, 8); rubidium@7054: this->y_offs = GB(x, 8, 8); frosch@9289: this->x_extent = GB(x, 16, 8); frosch@9289: this->y_extent = GB(x, 24, 8); frosch@9289: this->z_extent = 6; truelight@0: } truelight@0: truelight@0: static void UpdateVarsAfterSwap(Vehicle *v) truelight@0: { rubidium@7054: v->UpdateDeltaXY(v->direction); rubidium@7630: v->cur_image = v->GetImage(v->direction); truelight@0: BeginVehicleMove(v); truelight@0: VehiclePositionChanged(v); truelight@0: EndVehicleMove(v); truelight@0: } truelight@0: smatz@9346: 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@10775: if (_settings_client.gui.vehicle_speed || (old == 0) != (spd == 0)) { smatz@8846: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); smatz@9346: } truelight@0: } truelight@0: } truelight@0: truelight@954: static void SwapTrainFlags(byte *swap_flag1, byte *swap_flag2) truelight@954: { tron@6476: byte flag1 = *swap_flag1; tron@6476: byte flag2 = *swap_flag2; truelight@954: truelight@954: /* Clear the flags */ skidd13@8425: ClrBit(*swap_flag1, VRF_GOINGUP); skidd13@8425: ClrBit(*swap_flag1, VRF_GOINGDOWN); skidd13@8425: ClrBit(*swap_flag2, VRF_GOINGUP); skidd13@8425: ClrBit(*swap_flag2, VRF_GOINGDOWN); truelight@954: truelight@954: /* Reverse the rail-flags (if needed) */ skidd13@8424: if (HasBit(flag1, VRF_GOINGUP)) { skidd13@8427: SetBit(*swap_flag2, VRF_GOINGDOWN); skidd13@8424: } else if (HasBit(flag1, VRF_GOINGDOWN)) { skidd13@8427: SetBit(*swap_flag2, VRF_GOINGUP); truelight@954: } skidd13@8424: if (HasBit(flag2, VRF_GOINGUP)) { skidd13@8427: SetBit(*swap_flag1, VRF_GOINGDOWN); skidd13@8424: } else if (HasBit(flag2, VRF_GOINGDOWN)) { skidd13@8427: 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@7988: for (a = v; l != 0; l--) a = a->Next(); rubidium@7988: 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@5984: Swap(a->u.rail.track, b->u.rail.track); tron@5984: Swap(a->direction, b->direction); truelight@0: truelight@0: /* toggle direction */ rubidium@6319: if (a->u.rail.track != TRACK_BIT_DEPOT) a->direction = ReverseDir(a->direction); rubidium@6319: if (b->u.rail.track != TRACK_BIT_DEPOT) b->direction = ReverseDir(b->direction); truelight@193: tron@5984: Swap(a->x_pos, b->x_pos); tron@5984: Swap(a->y_pos, b->y_pos); tron@5984: Swap(a->tile, b->tile); tron@5984: 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@5573: /* call the proper EnterTile function unless we are in a wormhole */ rubidium@6319: if (a->u.rail.track != TRACK_BIT_WORMHOLE) VehicleEnterTile(a, a->tile, a->x_pos, a->y_pos); rubidium@6319: if (b->u.rail.track != TRACK_BIT_WORMHOLE) VehicleEnterTile(b, b->tile, b->x_pos, b->y_pos); truelight@0: } else { rubidium@6319: if (a->u.rail.track != TRACK_BIT_DEPOT) a->direction = ReverseDir(a->direction); truelight@193: UpdateVarsAfterSwap(a); truelight@1554: rubidium@6319: 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@8830: smatz@8830: /** smatz@8830: * Check if the vehicle is a train smatz@8830: * @param v vehicle on tile smatz@8830: * @return v if it is a train, NULL otherwise smatz@8830: */ smatz@8830: static void *TrainOnTileEnum(Vehicle *v, void *) truelight@744: { smatz@8830: return (v->type == VEH_TRAIN) ? v : NULL; smatz@8830: } smatz@8830: smatz@8830: smatz@8830: /** smatz@8830: * Checks if a train is approaching a rail-road crossing smatz@8830: * @param v vehicle on tile smatz@8830: * @param data tile with crossing we are testing smatz@8830: * @return v if it is approaching a crossing, NULL otherwise smatz@8830: */ smatz@8830: static void *TrainApproachingCrossingEnum(Vehicle *v, void *data) smatz@8830: { smatz@8830: /* not a train || not front engine || crashed */ smatz@8830: if (v->type != VEH_TRAIN || !IsFrontEngine(v) || v->vehstatus & VS_CRASHED) return NULL; smatz@8830: smatz@8830: TileIndex tile = *(TileIndex*)data; smatz@8830: smatz@8830: if (TrainApproachingCrossingTile(v) != tile) return NULL; smatz@8830: truelight@744: return v; truelight@744: } truelight@744: smatz@8830: smatz@8830: /** smatz@8830: * Finds a vehicle approaching rail-road crossing smatz@8830: * @param tile tile to test smatz@8830: * @return pointer to vehicle approaching the crossing smatz@8830: * @pre tile is a rail-road crossing smatz@8830: */ smatz@8830: static Vehicle *TrainApproachingCrossing(TileIndex tile) dominik@1103: { smatz@8830: assert(IsLevelCrossingTile(tile)); smatz@8830: smatz@9094: DiagDirection dir = AxisToDiagDir(GetCrossingRailAxis(tile)); smatz@8830: TileIndex tile_from = tile + TileOffsByDiagDir(dir); smatz@8830: smatz@9346: Vehicle *v = (Vehicle*)VehicleFromPos(tile_from, &tile, &TrainApproachingCrossingEnum); smatz@8830: smatz@8830: if (v != NULL) return v; smatz@8830: smatz@8830: dir = ReverseDiagDir(dir); smatz@8830: tile_from = tile + TileOffsByDiagDir(dir); smatz@8830: smatz@9346: return (Vehicle*)VehicleFromPos(tile_from, &tile, &TrainApproachingCrossingEnum); smatz@8830: } smatz@8830: smatz@8830: smatz@8830: /** smatz@8830: * Sets correct crossing state smatz@8830: * @param tile tile to update smatz@8840: * @param sound should we play sound? smatz@8830: * @pre tile is a rail-road crossing smatz@8830: */ smatz@8840: void UpdateLevelCrossing(TileIndex tile, bool sound) smatz@8830: { smatz@8830: assert(IsLevelCrossingTile(tile)); smatz@8830: smatz@8830: /* train on crossing || train approaching crossing */ smatz@8840: bool new_state = VehicleFromPos(tile, NULL, &TrainOnTileEnum) || TrainApproachingCrossing(tile); smatz@8840: smatz@8840: if (new_state != IsCrossingBarred(tile)) { smatz@8840: if (new_state && sound) { smatz@8840: SndPlayTileFx(SND_0E_LEVEL_CROSSING, tile); smatz@8840: } smatz@8840: SetCrossingBarred(tile, new_state); smatz@8840: MarkTileDirtyByTile(tile); dominik@1103: } dominik@1103: } dominik@1103: smatz@8830: hackykid@1922: /** smatz@8852: * Bars crossing and plays ding-ding sound if not barred already smatz@8852: * @param tile tile with crossing smatz@8852: * @pre tile is a rail-road crossing smatz@8852: */ smatz@8852: static inline void MaybeBarCrossingWithSound(TileIndex tile) smatz@8852: { smatz@8852: if (!IsCrossingBarred(tile)) { smatz@8852: BarCrossing(tile); smatz@8852: SndPlayTileFx(SND_0E_LEVEL_CROSSING, tile); smatz@8852: MarkTileDirtyByTile(tile); smatz@8852: } smatz@8852: } smatz@8852: smatz@8852: smatz@8852: /** hackykid@1922: * Advances wagons for train reversing, needed for variable length wagons. smatz@9208: * This one is called before the train is reversed. hackykid@1922: * @param v First vehicle in chain hackykid@1922: */ smatz@9208: static void AdvanceWagonsBeforeSwap(Vehicle *v) hackykid@1922: { tron@6476: Vehicle *base = v; smatz@9208: Vehicle *first = base; // first vehicle to move smatz@9208: Vehicle *last = GetLastVehicleInChain(v); // last vehicle to move tron@6476: uint length = CountVehiclesInChain(v); hackykid@1922: hackykid@1922: while (length > 2) { smatz@9208: last = last->Previous(); smatz@9208: first = first->Next(); smatz@9208: smatz@9208: int differential = base->u.rail.cached_veh_length - last->u.rail.cached_veh_length; smatz@9208: smatz@9208: /* do not update images now smatz@9208: * negative differential will be handled in AdvanceWagonsAfterSwap() */ smatz@9208: for (int i = 0; i < differential; i++) TrainController(first, last->Next(), false); smatz@9208: smatz@9208: base = first; // == base->Next() smatz@9208: length -= 2; smatz@9208: } smatz@9208: } smatz@9208: smatz@9208: smatz@9208: /** smatz@9208: * Advances wagons for train reversing, needed for variable length wagons. smatz@9208: * This one is called after the train is reversed. smatz@9208: * @param v First vehicle in chain smatz@9208: */ smatz@9208: static void AdvanceWagonsAfterSwap(Vehicle *v) smatz@9208: { smatz@9208: /* first of all, fix the situation when the train was entering a depot */ smatz@9208: Vehicle *dep = v; // last vehicle in front of just left depot smatz@9208: while (dep->Next() != NULL && (dep->u.rail.track == TRACK_BIT_DEPOT || dep->Next()->u.rail.track != TRACK_BIT_DEPOT)) { smatz@9208: dep = dep->Next(); // find first vehicle outside of a depot, with next vehicle inside a depot smatz@9208: } smatz@9208: smatz@9208: Vehicle *leave = dep->Next(); // first vehicle in a depot we are leaving now smatz@9208: smatz@9208: if (leave != NULL) { smatz@9208: /* 'pull' next wagon out of the depot, so we won't miss it (it could stay in depot forever) */ smatz@9208: int d = TicksToLeaveDepot(dep); smatz@9208: smatz@9208: if (d <= 0) { smatz@9208: leave->vehstatus &= ~VS_HIDDEN; // move it out of the depot smatz@10546: leave->u.rail.track = TrackToTrackBits(GetRailDepotTrack(leave->tile)); smatz@9208: for (int i = 0; i >= d; i--) TrainController(leave, NULL, false); // maybe move it, and maybe let another wagon leave smatz@9208: } smatz@9208: } else { smatz@9208: dep = NULL; // no vehicle in a depot, so no vehicle leaving a depot smatz@9208: } smatz@9208: smatz@9208: Vehicle *base = v; smatz@9208: Vehicle *first = base; // first vehicle to move smatz@9208: Vehicle *last = GetLastVehicleInChain(v); // last vehicle to move smatz@9208: uint length = CountVehiclesInChain(v); smatz@9208: smatz@9208: /* we have to make sure all wagons that leave a depot because of train reversing are moved coorectly smatz@9208: * they have already correct spacing, so we have to make sure they are moved how they should */ smatz@9208: bool nomove = (dep == NULL); // if there is no vehicle leaving a depot, limit the number of wagons moved immediatelly smatz@9208: smatz@9208: while (length > 2) { smatz@9208: /* we reached vehicle (originally) in front of a depot, stop now smatz@9208: * (we would move wagons that are alredy moved with new wagon length) */ smatz@9208: if (base == dep) break; smatz@9208: smatz@9208: /* the last wagon was that one leaving a depot, so do not move it anymore */ smatz@9208: if (last == dep) nomove = true; smatz@9208: smatz@9208: last = last->Previous(); smatz@9208: first = first->Next(); tron@6476: tron@6476: int differential = last->u.rail.cached_veh_length - base->u.rail.cached_veh_length; smatz@9208: smatz@9208: /* do not update images now */ smatz@9208: for (int i = 0; i < differential; i++) TrainController(first, (nomove ? last->Next() : NULL), false); smatz@9208: smatz@9208: 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@10221: if (IsRailDepotTile(v->tile)) { bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); bjarni@4739: } truelight@0: truelight@743: /* Check if we were approaching a rail/road-crossing */ smatz@8830: TileIndex crossing = TrainApproachingCrossingTile(v); truelight@743: belugas@6918: /* count number of vehicles */ smatz@9346: int r = CountVehiclesInChain(v) - 1; // number of vehicles - 1 truelight@0: smatz@9208: AdvanceWagonsBeforeSwap(v); hackykid@1922: truelight@0: /* swap start<>end, start+1<>end-1, ... */ tron@6476: int l = 0; truelight@0: do { truelight@0: ReverseTrainSwapVeh(v, l++, r--); truelight@0: } while (l <= r); truelight@0: smatz@9208: AdvanceWagonsAfterSwap(v); hackykid@1922: smatz@10221: if (IsRailDepotTile(v->tile)) { bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); bjarni@4739: } truelight@0: glx@10180: ToggleBit(v->u.rail.flags, VRF_TOGGLE_REVERSE); smatz@8639: skidd13@8425: ClrBit(v->u.rail.flags, VRF_REVERSING); smatz@8830: peter1138@9163: /* recalculate cached data */ peter1138@9163: TrainConsistChanged(v); peter1138@9163: peter1138@9163: /* update all images */ peter1138@9163: for (Vehicle *u = v; u != NULL; u = u->Next()) u->cur_image = u->GetImage(u->direction); peter1138@9163: smatz@8830: /* update crossing we were approaching */ smatz@8838: if (crossing != INVALID_TILE) UpdateLevelCrossing(crossing); smatz@8830: smatz@8830: /* maybe we are approaching crossing now, after reversal */ smatz@8830: crossing = TrainApproachingCrossingTile(v); smatz@8852: if (crossing != INVALID_TILE) MaybeBarCrossingWithSound(crossing); truelight@0: } truelight@0: Darkvater@1784: /** Reverse train. tron@3491: * @param tile unused belugas@6918: * @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@7439: CommandCost CmdReverseTrainDirection(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@4352: if (!IsValidVehicleID(p1)) return CMD_ERROR; bjarni@1237: tron@6476: Vehicle *v = GetVehicle(p1); truelight@0: rubidium@6585: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: smatz@9346: if (p2 != 0) { belugas@6918: /* turn a single unit around */ bjarni@3256: skidd13@8424: 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@7993: Vehicle *front = v->First(); belugas@6918: /* 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@8428: 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@7972: /* turn the whole train around */ rubidium@7972: if (v->vehstatus & VS_CRASHED || v->breakdown_ctr != 0) return CMD_ERROR; bjarni@3257: bjarni@3257: if (flags & DC_EXEC) { rubidium@10775: if (_settings_game.vehicle.realistic_acceleration && v->cur_speed != 0) { skidd13@8428: ToggleBit(v->u.rail.flags, VRF_REVERSING); bjarni@3256: } else { bjarni@3256: v->cur_speed = 0; bjarni@3256: SetLastSpeed(v, 0); bjarni@3256: ReverseTrainDirection(v); bjarni@3256: } truelight@0: } truelight@0: } rubidium@7446: return CommandCost(); truelight@0: } truelight@0: Darkvater@1784: /** Force a train through a red signal tron@3491: * @param tile unused belugas@6918: * @param flags type of operation Darkvater@1784: * @param p1 train to ignore the red signal Darkvater@1784: * @param p2 unused Darkvater@1784: */ rubidium@7439: CommandCost CmdForceTrainProceed(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@4352: if (!IsValidVehicleID(p1)) return CMD_ERROR; bjarni@1237: tron@6476: Vehicle *v = GetVehicle(p1); truelight@0: rubidium@6585: 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@7446: return CommandCost(); truelight@0: } truelight@0: Darkvater@1802: /** Refits a train to the specified cargo type. tron@3491: * @param tile unused belugas@6918: * @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@7042: * - p2 = (bit 16) - refit only this vehicle maedhros@7042: * @return cost of refit or error Darkvater@1802: */ rubidium@7439: 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@8424: bool only_this = HasBit(p2, 16); bjarni@842: truelight@4352: if (!IsValidVehicleID(p1)) return CMD_ERROR; tron@915: tron@6476: Vehicle *v = GetVehicle(p1); bjarni@1237: rubidium@6585: 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@8641: if (v->vehstatus & VS_CRASHED) return_cmd_error(STR_CAN_T_REFIT_DESTROYED_VEHICLE); Darkvater@1802: Darkvater@1802: /* Check cargo */ peter1138@6642: if (new_cid >= NUM_CARGO) return CMD_ERROR; truelight@0: rubidium@8726: CommandCost cost(EXPENSES_TRAIN_RUN); tron@6476: 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@8424: 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@6476: 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@6476: } hackykid@1883: hackykid@1883: if (amount != 0) { peter1138@4242: if (new_cid != v->cargo_type) { rubidium@7446: cost.AddCost(GetRefitCost(v->engine_type)); peter1138@4242: } peter1138@4242: hackykid@1883: num += amount; hackykid@1883: if (flags & DC_EXEC) { rubidium@7506: v->cargo.Truncate((v->cargo_type == new_cid) ? amount : 0); hackykid@1883: v->cargo_type = new_cid; hackykid@1883: v->cargo_cap = amount; peter1138@3954: v->cargo_subtype = new_subtype; hackykid@1883: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); hackykid@1883: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); rubidium@10621: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); hackykid@1883: } truelight@0: } truelight@193: } rubidium@7988: } 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 */ rubidium@7993: if (flags & DC_EXEC) TrainConsistChanged(GetVehicle(p1)->First()); peter1138@4708: truelight@0: return cost; truelight@0: } truelight@0: rubidium@6574: struct TrainFindDepotData { truelight@0: uint best_length; tron@1977: TileIndex tile; tron@2475: PlayerID 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@6574: }; 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@6918: /** returns the tile of a depot to goto to. The given vehicle must not be belugas@6918: * crashed! */ KUDr@3900: static TrainFindDepotData FindClosestTrainDepot(Vehicle *v, int max_distance) truelight@0: { tron@6476: assert(!(v->vehstatus & VS_CRASHED)); tron@6476: truelight@0: TrainFindDepotData tfdd; darkvater@308: tfdd.owner = v->owner; smatz@9050: tfdd.best_length = UINT_MAX; matthijs@1777: tfdd.reverse = false; darkvater@308: tron@6476: TileIndex tile = v->tile; smatz@10221: if (IsRailDepotTile(tile)) { darkvater@308: tfdd.tile = tile; darkvater@308: tfdd.best_length = 0; darkvater@308: return tfdd; darkvater@308: } truelight@0: rubidium@10775: switch (_settings_game.pf.pathfinder_for_trains) { smatz@9050: case VPF_YAPF: { /* YAPF */ smatz@9050: bool found = YapfFindNearestRailDepotTwoWay(v, max_distance, NPF_INFINITE_PENALTY, &tfdd.tile, &tfdd.reverse); smatz@9050: tfdd.best_length = found ? max_distance / 2 : UINT_MAX; // some fake distance or NOT_FOUND smatz@9050: } break; smatz@9050: smatz@9050: case VPF_NPF: { /* NPF */ smatz@9346: const Vehicle *last = GetLastVehicleInChain(v); smatz@9050: Trackdir trackdir = GetVehicleTrackdir(v); smatz@9050: Trackdir trackdir_rev = ReverseTrackdir(GetVehicleTrackdir(last)); smatz@9050: smatz@9050: assert(trackdir != INVALID_TRACKDIR); smatz@9050: 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@9050: if (ftd.best_bird_dist == 0) { smatz@9050: /* Found target */ smatz@9050: tfdd.tile = ftd.node.tile; smatz@9050: /* Our caller expects a number of tiles, so we just approximate that smatz@9346: * number by this. It might not be completely what we want, but it will smatz@9346: * work for now :-) We can possibly change this when the old pathfinder smatz@9346: * is removed. */ smatz@9050: tfdd.best_length = ftd.best_path_dist / NPF_TILE_LENGTH; smatz@9050: if (NPFGetFlag(&ftd.node, NPF_FLAG_REVERSE)) tfdd.reverse = true; smatz@9050: } smatz@9050: } break; smatz@9050: smatz@9050: default: smatz@9050: case VPF_NTP: { /* NTP */ smatz@9050: /* search in the forward direction first. */ smatz@9050: DiagDirection i = TrainExitDir(v->direction, v->u.rail.track); celestar@3355: NewTrainPathfind(tile, 0, v->u.rail.compatible_railtypes, i, (NTPEnumProc*)NtpCallbFindDepot, &tfdd); smatz@9050: if (tfdd.best_length == UINT_MAX){ smatz@9050: tfdd.reverse = true; smatz@9050: /* search in backwards direction */ smatz@9050: i = TrainExitDir(ReverseDir(v->direction), v->u.rail.track); smatz@9050: NewTrainPathfind(tile, 0, v->u.rail.compatible_railtypes, i, (NTPEnumProc*)NtpCallbFindDepot, &tfdd); smatz@9050: } smatz@9050: } break; truelight@0: } truelight@193: darkvater@308: return tfdd; truelight@0: } truelight@0: rubidium@10126: bool Train::FindClosestDepot(TileIndex *location, DestinationID *destination, bool *reverse) rubidium@10126: { rubidium@10126: TrainFindDepotData tfdd = FindClosestTrainDepot(this, 0); rubidium@10126: if (tfdd.best_length == (uint)-1) return false; rubidium@10126: rubidium@10126: if (location != NULL) *location = tfdd.tile; rubidium@10126: if (destination != NULL) *destination = GetDepotByTile(tfdd.tile)->index; rubidium@10126: if (reverse != NULL) *reverse = tfdd.reverse; rubidium@10126: rubidium@10126: return true; rubidium@10126: } rubidium@10126: Darkvater@1784: /** Send a train to a depot tron@3491: * @param tile unused belugas@6918: * @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@7439: 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@6585: return SendAllVehiclesToDepot(VEH_TRAIN, flags, p2 & DEPOT_SERVICE, _current_player, (p2 & VLW_MASK), p1); bjarni@4463: } bjarni@4463: bjarni@4506: if (!IsValidVehicleID(p1)) return CMD_ERROR; bjarni@1237: tron@6476: Vehicle *v = GetVehicle(p1); bjarni@1237: rubidium@10127: if (v->type != VEH_TRAIN) return CMD_ERROR; rubidium@10127: rubidium@10127: return v->SendToDepot(flags, (DepotCommand)(p2 & DEPOT_COMMAND_MASK)); truelight@0: } truelight@0: truelight@0: rubidium@6573: 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@9346: static void HandleLocomotiveSmokeCloud(const Vehicle *v) truelight@0: { peter1138@4656: bool sound = false; truelight@0: smatz@9346: if (v->vehstatus & VS_TRAIN_SLOWING || v->load_unload_time_rem != 0 || v->cur_speed < 2) { truelight@0: return; smatz@9346: } smatz@9346: smatz@9346: const Vehicle *u = v; truelight@0: truelight@0: do { tron@6074: 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@8424: bool disable_effect = HasBit(v->u.rail.cached_vis_effect, 6); truelight@0: belugas@6918: /* no smoke? */ belugas@6119: 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@6918: /* No smoke in depots or tunnels */ smatz@10221: if (IsRailDepotTile(v->tile) || IsTunnelTile(v->tile)) continue; celestar@3590: belugas@6918: /* No sparks for electric vehicles on nonelectrified tracks */ tron@6480: if (!HasPowerOnRail(v->u.rail.railtype, GetTileRailType(v->tile))) continue; peter1138@2612: peter1138@2595: if (effect_type == 0) { belugas@6918: /* Use default effect type for engine class. */ tron@6074: effect_type = rvi->engclass; peter1138@2595: } else { peter1138@2595: effect_type--; peter1138@2595: } peter1138@2595: tron@6476: int x = _vehicle_smoke_pos[v->direction] * effect_offset; tron@6476: int y = _vehicle_smoke_pos[(v->direction + 2) % 8] * effect_offset; peter1138@2595: skidd13@8424: 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@9346: case 0: smatz@9346: /* steam smoke. */ smatz@9346: if (GB(v->tick_counter, 0, 4) == 0) { smatz@9346: CreateEffectVehicleRel(v, x, y, 10, EV_STEAM_SMOKE); smatz@9346: sound = true; smatz@9346: } smatz@9346: break; smatz@9346: smatz@9346: case 1: smatz@9346: /* diesel smoke */ smatz@9346: if (u->cur_speed <= 40 && Chance16(15, 128)) { smatz@9346: CreateEffectVehicleRel(v, 0, 0, 10, EV_DIESEL_SMOKE); smatz@9346: sound = true; smatz@9346: } smatz@9346: break; smatz@9346: smatz@9346: case 2: smatz@9346: /* blue spark */ smatz@9346: if (GB(v->tick_counter, 0, 2) == 0 && Chance16(1, 45)) { smatz@9346: CreateEffectVehicleRel(v, 0, 0, 10, EV_ELECTRIC_SPARK); smatz@9346: sound = true; smatz@9346: } smatz@9346: break; smatz@9346: smatz@9346: default: smatz@9346: break; truelight@0: } rubidium@7988: } while ((v = v->Next()) != NULL); peter1138@4656: peter1138@4656: if (sound) PlayVehicleSound(u, VSE_TRAIN_EFFECT); truelight@0: } truelight@0: peter1138@9046: void Train::PlayLeaveStationSound() const truelight@0: { tron@541: static const SoundFx sfx[] = { tron@541: SND_04_TRAIN, tron@541: SND_0A_TRAIN_HORN, rubidium@7082: SND_0A_TRAIN_HORN, rubidium@7082: SND_47_MAGLEV_2, rubidium@7082: SND_41_MAGLEV tron@541: }; tron@541: peter1138@9046: if (PlayVehicleSound(this, VSE_START)) return; peter1138@9046: peter1138@9046: EngineID engtype = this->engine_type; peter1138@9046: SndPlayVehicleFx(sfx[RailVehInfo(engtype)->engclass], this); rubidium@7089: } rubidium@7089: truelight@0: static bool CheckTrainStayInDepot(Vehicle *v) truelight@0: { belugas@6918: /* bail out if not all wagons are in the same depot or not in a depot at all */ rubidium@7988: for (const Vehicle *u = v; u != NULL; u = u->Next()) { rubidium@6319: if (u->u.rail.track != TRACK_BIT_DEPOT || u->tile != v->tile) return false; tron@2639: } truelight@0: belugas@6918: /* 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: truelight@0: if (v->u.rail.force_proceed == 0) { 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: smatz@10544: if (UpdateSignalsOnSegment(v->tile, INVALID_DIAGDIR, v->owner) == SIGSEG_FULL) { bjarni@1151: InvalidateWindowClasses(WC_TRAINS_LIST); truelight@0: return true; bjarni@1151: } truelight@0: } Darkvater@2916: bjarni@578: VehicleServiceInDepot(v); bjarni@1151: InvalidateWindowClasses(WC_TRAINS_LIST); peter1138@9046: v->PlayLeaveStationSound(); truelight@193: rubidium@5838: v->u.rail.track = TRACK_BIT_X; rubidium@5838: 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@7054: v->UpdateDeltaXY(v->direction); rubidium@7630: v->cur_image = v->GetImage(v->direction); truelight@0: VehiclePositionChanged(v); smatz@8796: 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: smatz@9346: /** Check for station tiles */ rubidium@6574: struct TrainTrackFollowerData { truelight@0: TileIndex dest_coords; smatz@9346: StationID station_index; ///< station index we're heading for truelight@0: uint best_bird_dist; truelight@0: uint best_track_dist; rubidium@5838: TrackdirByte best_track; rubidium@6574: }; truelight@0: rubidium@5838: static bool NtpCallbFindStation(TileIndex tile, TrainTrackFollowerData *ttfd, Trackdir track, uint length) tron@1977: { belugas@6918: /* heading for nowhere? */ tron@2951: if (ttfd->dest_coords == 0) return false; truelight@0: belugas@6918: /* 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@6918: belugas@6918: /* found station */ ludde@2125: ttfd->best_track = track; KUDr@6629: ttfd->best_bird_dist = 0; truelight@0: return true; truelight@0: } else { belugas@6918: /* didn't find station, keep track of the best path so far. */ tron@6476: 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@9346: static void FillWithStationData(TrainTrackFollowerData *fd, const Vehicle *v) truelight@0: { tron@2639: fd->dest_coords = v->dest_tile; rubidium@9336: 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@6918: { 0, 9, 2, 9 }, ///< track 1 belugas@6918: { 9, 1, 9, 3 }, ///< track 2 belugas@6918: { 9, 0, 3, 9 }, ///< track upper belugas@6918: { 1, 9, 9, 2 }, ///< track lower belugas@6918: { 3, 2, 9, 9 }, ///< track left belugas@6918: { 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: truelight@0: /* choose a track */ smatz@9346: static Track ChooseTrainTrack(Vehicle *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks) truelight@193: { rubidium@5838: Track best_track; belugas@6918: /* pathfinders are able to tell that route was only 'guessed' */ KUDr@4870: bool path_not_found = false; KUDr@4870: peter1138@2758: #ifdef PF_BENCHMARK Darkvater@3341: TIC() truelight@0: #endif truelight@0: truelight@8329: assert((tracks & ~TRACK_BIT_MASK) == 0); truelight@0: matthijs@1247: /* quick return in case only one possible track is available */ truelight@8329: if (KillFirstBit(tracks) == TRACK_BIT_NONE) return FindFirstTrack(tracks); truelight@0: rubidium@10775: switch (_settings_game.pf.pathfinder_for_trains) { smatz@9050: case VPF_YAPF: { /* YAPF */ smatz@9050: Trackdir trackdir = YapfChooseRailTrack(v, tile, enterdir, tracks, &path_not_found); smatz@9050: if (trackdir != INVALID_TRACKDIR) { smatz@9050: best_track = TrackdirToTrack(trackdir); smatz@9050: } else { smatz@9050: best_track = FindFirstTrack(tracks); smatz@9050: } smatz@9050: } break; smatz@9050: smatz@9050: case VPF_NPF: { /* NPF */ smatz@9050: void *perf = NpfBeginInterval(); smatz@9050: smatz@9050: NPFFindStationOrTileData fstd; smatz@9050: NPFFillWithOrderData(&fstd, v); smatz@9050: /* The enterdir for the new tile, is the exitdir for the old tile */ smatz@9050: Trackdir trackdir = GetVehicleTrackdir(v); smatz@9050: assert(trackdir != INVALID_TRACKDIR); smatz@9050: smatz@9050: NPFFoundTargetData ftd = NPFRouteToStationOrTile(tile - TileOffsByDiagDir(enterdir), trackdir, true, &fstd, TRANSPORT_RAIL, 0, v->owner, v->u.rail.compatible_railtypes); smatz@9050: smatz@9050: if (ftd.best_trackdir == INVALID_TRACKDIR) { smatz@9050: /* We are already at our target. Just do something smatz@9050: * @todo maybe display error? smatz@9050: * @todo: go straight ahead if possible? */ smatz@9050: best_track = FindFirstTrack(tracks); smatz@9050: } else { smatz@9050: /* If ftd.best_bird_dist is 0, we found our target and ftd.best_trackdir contains smatz@9050: * the direction we need to take to get there, if ftd.best_bird_dist is not 0, smatz@9050: * we did not find our target, but ftd.best_trackdir contains the direction leading smatz@9050: * to the tile closest to our target. */ smatz@9050: if (ftd.best_bird_dist != 0) path_not_found = true; smatz@9050: /* Discard enterdir information, making it a normal track */ smatz@9050: best_track = TrackdirToTrack(ftd.best_trackdir); smatz@9050: } smatz@9050: smatz@9050: int time = NpfEndInterval(perf); smatz@9050: DEBUG(yapf, 4, "[NPFT] %d us - %d rounds - %d open - %d closed -- ", time, 0, _aystar_stats_open_size, _aystar_stats_closed_size); smatz@9050: } break; smatz@9050: smatz@9050: default: smatz@9050: case VPF_NTP: { /* NTP */ smatz@9050: void *perf = NpfBeginInterval(); smatz@9050: smatz@9050: TrainTrackFollowerData fd; smatz@9050: FillWithStationData(&fd, v); smatz@9050: smatz@9050: /* New train pathfinding */ smatz@9050: fd.best_bird_dist = UINT_MAX; smatz@9050: fd.best_track_dist = UINT_MAX; smatz@9050: fd.best_track = INVALID_TRACKDIR; smatz@9050: smatz@9050: NewTrainPathfind(tile - TileOffsByDiagDir(enterdir), v->dest_tile, smatz@9050: v->u.rail.compatible_railtypes, enterdir, (NTPEnumProc*)NtpCallbFindStation, &fd); smatz@9050: smatz@9050: /* check whether the path was found or only 'guessed' */ smatz@9050: if (fd.best_bird_dist != 0) path_not_found = true; smatz@9050: smatz@9050: if (fd.best_track == INVALID_TRACKDIR) { smatz@9050: /* blaha */ smatz@9050: best_track = FindFirstTrack(tracks); smatz@9050: } else { smatz@9050: best_track = TrackdirToTrack(fd.best_track); smatz@9050: } smatz@9050: smatz@9050: int time = NpfEndInterval(perf); smatz@9050: DEBUG(yapf, 4, "[NTPT] %d us - %d rounds - %d open - %d closed -- ", time, 0, 0, 0); smatz@9050: } break; truelight@0: } smatz@9050: belugas@6918: /* handle "path not found" state */ KUDr@4870: if (path_not_found) { belugas@6918: /* PF didn't find the route */ skidd13@8424: if (!HasBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) { belugas@6918: /* it is first time the problem occurred, set the "path not found" flag */ skidd13@8427: SetBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION); belugas@6918: /* and notify user about the event */ rubidium@10775: if (_settings_client.gui.lost_train_warn && v->owner == _local_player) { KUDr@4870: SetDParam(0, v->unitnumber); KUDr@4870: AddNewsItem( KUDr@4870: STR_TRAIN_IS_LOST, rubidium@10556: NS_ADVICE, KUDr@4870: v->index, KUDr@4870: 0); KUDr@4870: } KUDr@4870: } KUDr@4870: } else { belugas@6918: /* route found, is the train marked with "path not found" flag? */ skidd13@8424: if (HasBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) { belugas@6918: /* clear the flag as the PF's problem was solved */ skidd13@8425: ClrBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION); belugas@6918: /* can we also delete the "News" item somehow? */ KUDr@4870: } KUDr@4870: } truelight@0: peter1138@2758: #ifdef PF_BENCHMARK Darkvater@3341: TOC("PF time = ", 1) truelight@0: #endif truelight@0: truelight@0: return best_track; truelight@0: } truelight@0: truelight@0: truelight@0: static bool CheckReverseTrain(Vehicle *v) truelight@0: { rubidium@10775: if (_settings_game.difficulty.line_reverse_mode != 0 || rubidium@6319: v->u.rail.track == TRACK_BIT_DEPOT || v->u.rail.track == TRACK_BIT_WORMHOLE || smatz@9346: !(v->direction & 1)) { truelight@0: return false; smatz@9346: } truelight@0: tron@6476: TrainTrackFollowerData fd; truelight@0: FillWithStationData(&fd, v); truelight@0: tron@6476: uint reverse_best = 0; truelight@0: truelight@0: assert(v->u.rail.track); truelight@0: rubidium@10775: switch (_settings_game.pf.pathfinder_for_trains) { smatz@9346: case VPF_YAPF: /* YAPF */ smatz@9050: reverse_best = YapfCheckReverseTrain(v); smatz@9346: break; smatz@9050: smatz@9050: case VPF_NPF: { /* NPF */ smatz@9050: NPFFindStationOrTileData fstd; smatz@9050: NPFFoundTargetData ftd; smatz@9346: Vehicle *last = GetLastVehicleInChain(v); smatz@9050: smatz@9050: NPFFillWithOrderData(&fstd, v); smatz@9050: smatz@9050: Trackdir trackdir = GetVehicleTrackdir(v); smatz@9050: Trackdir trackdir_rev = ReverseTrackdir(GetVehicleTrackdir(last)); smatz@9050: assert(trackdir != INVALID_TRACKDIR); smatz@9050: assert(trackdir_rev != INVALID_TRACKDIR); smatz@9050: smatz@9050: ftd = NPFRouteToStationOrTileTwoWay(v->tile, trackdir, false, last->tile, trackdir_rev, false, &fstd, TRANSPORT_RAIL, 0, v->owner, v->u.rail.compatible_railtypes); smatz@9050: if (ftd.best_bird_dist != 0) { smatz@9050: /* We didn't find anything, just keep on going straight ahead */ smatz@9050: reverse_best = false; tron@3017: } else { smatz@9050: if (NPFGetFlag(&ftd.node, NPF_FLAG_REVERSE)) { smatz@9050: reverse_best = true; smatz@9050: } else { smatz@9050: reverse_best = false; smatz@9050: } tron@3017: } smatz@9050: } break; smatz@9050: smatz@9050: default: smatz@9050: case VPF_NTP: { /* NTP */ frosch@9290: int i = _search_directions[FindFirstTrack(v->u.rail.track)][DirToDiagDir(v->direction)]; frosch@9290: smatz@9050: int best_track = -1; smatz@9050: uint reverse = 0; smatz@9050: uint best_bird_dist = 0; smatz@9050: uint best_track_dist = 0; smatz@9050: smatz@9050: for (;;) { smatz@9050: fd.best_bird_dist = UINT_MAX; smatz@9050: fd.best_track_dist = UINT_MAX; smatz@9050: smatz@9050: NewTrainPathfind(v->tile, v->dest_tile, v->u.rail.compatible_railtypes, (DiagDirection)(reverse ^ i), (NTPEnumProc*)NtpCallbFindStation, &fd); smatz@9050: smatz@9050: if (best_track != -1) { smatz@9050: if (best_bird_dist != 0) { smatz@9050: if (fd.best_bird_dist != 0) { smatz@9050: /* neither reached the destination, pick the one with the smallest bird dist */ smatz@9050: if (fd.best_bird_dist > best_bird_dist) goto bad; smatz@9050: if (fd.best_bird_dist < best_bird_dist) goto good; smatz@9050: } else { smatz@9050: /* we found the destination for the first time */ smatz@9050: goto good; smatz@9050: } matthijs@1247: } else { smatz@9050: if (fd.best_bird_dist != 0) { smatz@9050: /* didn't find destination, but we've found the destination previously */ smatz@9050: goto bad; smatz@9050: } else { smatz@9050: /* both old & new reached the destination, compare track length */ smatz@9050: if (fd.best_track_dist > best_track_dist) goto bad; smatz@9050: if (fd.best_track_dist < best_track_dist) goto good; smatz@9050: } matthijs@1247: } smatz@9050: smatz@9050: /* if we reach this position, there's two paths of equal value so far. smatz@9050: * pick one randomly. */ smatz@9050: int r = GB(Random(), 0, 8); smatz@9050: if (_pick_track_table[i] == (v->direction & 3)) r += 80; smatz@9050: if (_pick_track_table[best_track] == (v->direction & 3)) r -= 80; smatz@9050: if (r <= 127) goto bad; truelight@0: } smatz@9050: good:; smatz@9050: best_track = i; smatz@9050: best_bird_dist = fd.best_bird_dist; smatz@9050: best_track_dist = fd.best_track_dist; smatz@9050: reverse_best = reverse; smatz@9050: bad:; smatz@9050: if (reverse != 0) break; smatz@9050: reverse = 2; truelight@0: } smatz@9050: } break; truelight@0: } truelight@0: truelight@0: return reverse_best != 0; truelight@0: } truelight@0: rubidium@9323: TileIndex Train::GetOrderStationLocation(StationID station) truelight@0: { rubidium@9326: if (station == this->last_station_visited) this->last_station_visited = INVALID_STATION; rubidium@9326: rubidium@10167: const Station *st = GetStation(station); rubidium@10167: if (!(st->facilities & FACIL_TRAIN)) { rubidium@10167: /* The destination station has no trainstation tiles. */ rubidium@10167: this->cur_order_index++; rubidium@10167: return 0; rubidium@10167: } rubidium@10167: rubidium@10167: return st->xy; truelight@0: } truelight@0: rubidium@7049: void Train::MarkDirty() truelight@0: { rubidium@7049: Vehicle *v = this; truelight@0: do { rubidium@7630: v->cur_image = v->GetImage(v->direction); smatz@8813: MarkSingleVehicleDirty(v); rubidium@7988: } while ((v = v->Next()) != NULL); rubidium@7049: rubidium@7049: /* need to update acceleration and cached values since the goods on the train changed. */ rubidium@7049: TrainCargoChanged(this); rubidium@7049: UpdateTrainAcceleration(this); truelight@0: } truelight@0: truelight@0: static int UpdateTrainSpeed(Vehicle *v) truelight@0: { truelight@0: uint accel; truelight@0: skidd13@8424: if (v->vehstatus & VS_STOPPED || HasBit(v->u.rail.flags, VRF_REVERSING)) { rubidium@10775: 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@10775: 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@6476: 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@8418: v->cur_speed = spd = Clamp(v->cur_speed + ((int)spd >> 8), 0, tempmax); celestar@1179: } truelight@0: truelight@0: if (!(v->direction & 1)) spd = spd * 3 >> 2; truelight@0: truelight@0: spd += v->progress; truelight@0: v->progress = (byte)spd; truelight@0: return (spd >> 8); 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@6476: 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@10556: v->owner == _local_player ? NS_ARRIVAL_PLAYER : NS_ARRIVAL_OTHER, truelight@0: v->index, tron@3017: 0 tron@3017: ); truelight@0: } truelight@0: rubidium@7046: v->BeginLoading(); peter1138@10266: peter1138@10266: 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@6476: byte old_z = v->z_pos; peter1138@7575: v->z_pos = GetSlopeZ(v->x_pos, v->y_pos); truelight@0: truelight@954: if (new_tile) { skidd13@8425: ClrBit(v->u.rail.flags, VRF_GOINGUP); skidd13@8425: ClrBit(v->u.rail.flags, VRF_GOINGDOWN); truelight@954: peter1138@7575: if (v->u.rail.track == TRACK_BIT_X || v->u.rail.track == TRACK_BIT_Y) { peter1138@7575: /* Any track that isn't TRACK_BIT_X or TRACK_BIT_Y cannot be sloped. rubidium@7548: * To check whether the current tile is sloped, and in which rubidium@7548: * direction it is sloped, we get the 'z' at the center of rubidium@7548: * the tile (middle_z) and the edge of the tile (old_z), rubidium@7548: * which we then can compare. */ rubidium@7548: static const int HALF_TILE_SIZE = TILE_SIZE / 2; rubidium@7548: static const int INV_TILE_SIZE_MASK = ~(TILE_SIZE - 1); rubidium@7548: rubidium@7548: 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@7548: rubidium@7548: /* For some reason tunnel tiles are always given as sloped :( rubidium@7548: * But they are not sloped... */ peter1138@7575: if (middle_z != v->z_pos && !IsTunnelTile(TileVirtXY(v->x_pos, v->y_pos))) { skidd13@8427: 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@5838: DIR_N , DIR_NW, DIR_W , INVALID_DIR, rubidium@5838: DIR_NE, DIR_N , DIR_SW, INVALID_DIR, tron@3157: DIR_E , DIR_SE, DIR_S truelight@0: }; truelight@0: smatz@9103: 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@9103: 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@9103: 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@8424: HasBit(v->u.rail.compatible_railtypes, GetRailType(tile)) tron@2549: ); truelight@0: } truelight@0: rubidium@6574: 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@6574: }; truelight@0: celestar@3355: static const RailtypeSlowdownParams _railtype_slowdown[] = { truelight@0: // normal accel belugas@6918: {256 / 4, 256 / 2, 256 / 4, 2}, ///< normal belugas@6918: {256 / 4, 256 / 2, 256 / 4, 2}, ///< electrified belugas@6918: {256 / 4, 256 / 2, 256 / 4, 2}, ///< monorail belugas@6918: {0, 256 / 2, 256 / 4, 2}, ///< maglev truelight@0: }; truelight@0: belugas@6918: /** Modify the speed of the vehicle due to a turn */ smatz@9346: static inline void AffectSpeedByDirChange(Vehicle *v, Direction new_dir) truelight@0: { rubidium@10775: if (_settings_game.vehicle.realistic_acceleration) return; tron@3158: tron@6476: DirDiff diff = DirDifference(v->direction, new_dir); tron@3158: if (diff == DIRDIFF_SAME) return; truelight@0: tron@6476: 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@6918: /** Modify the speed of the vehicle due to a change in altitude */ smatz@9103: static inline void AffectSpeedByZChange(Vehicle *v, byte old_z) truelight@0: { rubidium@10775: if (old_z == v->z_pos || _settings_game.vehicle.realistic_acceleration) return; truelight@0: tron@6476: 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: tron@3153: static void TrainMovedChangeSignals(TileIndex tile, DiagDirection dir) truelight@0: { tron@3267: if (IsTileType(tile, MP_RAILWAY) && rubidium@3792: GetRailTileType(tile) == RAIL_TILE_SIGNALS) { frosch@9290: TrackdirBits tracks = TrackBitsToTrackdirBits(GetTrackBits(tile)) & DiagdirReachesTrackdirs(dir); frosch@9290: Trackdir trackdir = FindFirstTrackdir(tracks); frosch@9290: UpdateSignalsOnSegment(tile, TrackdirToExitdir(trackdir), GetTileOwner(tile)); truelight@0: } 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: smatz@8830: /* we may need to update crossing we were approaching */ smatz@8830: TileIndex crossing = TrainApproachingCrossingTile(v); smatz@8830: truelight@0: v->u.rail.crash_anim_pos++; truelight@193: smatz@8846: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); smatz@8767: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); smatz@8767: smatz@8767: if (v->u.rail.track == TRACK_BIT_DEPOT) { smatz@8767: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); smatz@8767: } smatz@8767: rubidium@10621: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); smatz@8767: peter1138@10660: for (; v != NULL; v = v->Next()) { truelight@0: v->vehstatus |= VS_CRASHED; smatz@8813: MarkSingleVehicleDirty(v); peter1138@10660: } smatz@8830: smatz@8830: /* must be updated after the train has been marked crashed */ smatz@8838: if (crossing != INVALID_TILE) UpdateLevelCrossing(crossing); truelight@0: } truelight@0: smatz@9346: static uint CountPassengersInTrain(const Vehicle *v) truelight@0: { tron@2549: uint num = 0; peter1138@10660: peter1138@10660: for (; v != NULL; v = v->Next()) { rubidium@7506: if (IsCargoInClass(v->cargo_type, CC_PASSENGERS)) num += v->cargo.Count(); peter1138@10660: } peter1138@10660: truelight@0: return num; truelight@0: } truelight@0: peter1138@7462: struct TrainCollideChecker { smatz@9346: Vehicle *v; ///< vehicle we are testing for collision smatz@9346: uint num; ///< number of dead if train collided peter1138@7462: }; peter1138@7462: peter1138@7462: static void *FindTrainCollideEnum(Vehicle *v, void *data) peter1138@7462: { smatz@9346: TrainCollideChecker *tcc = (TrainCollideChecker*)data; peter1138@7462: smatz@8777: if (v->type != VEH_TRAIN) return NULL; smatz@8777: smatz@8777: /* get first vehicle now to make most usual checks faster */ smatz@8777: Vehicle *coll = v->First(); smatz@8777: smatz@8808: /* can't collide with own wagons && can't crash in depot && the same height level */ smatz@8808: if (coll != tcc->v && v->u.rail.track != TRACK_BIT_DEPOT && abs(v->z_pos - tcc->v->z_pos) < 6) { smatz@8808: int x_diff = v->x_pos - tcc->v->x_pos; smatz@8808: int y_diff = v->y_pos - tcc->v->y_pos; smatz@8808: smatz@8808: /* needed to disable possible crash of competitor train in station by building diagonal track at its end */ smatz@8808: if (x_diff * x_diff + y_diff * y_diff > 25) return NULL; smatz@8808: peter1138@7462: if (!(tcc->v->vehstatus & VS_CRASHED)) { smatz@8808: /* two drivers + passengers killed in train tcc->v (if it was not crashed already) */ peter1138@7462: tcc->num += 2 + CountPassengersInTrain(tcc->v); peter1138@7462: SetVehicleCrashed(tcc->v); peter1138@7462: } peter1138@7462: peter1138@7462: if (!(coll->vehstatus & VS_CRASHED)) { peter1138@7462: /* two drivers + passengers killed in train coll (if it was not crashed already) */ peter1138@7462: tcc->num += 2 + CountPassengersInTrain(coll); peter1138@7462: SetVehicleCrashed(coll); peter1138@7462: } peter1138@7462: } peter1138@7462: peter1138@7462: return NULL; peter1138@7462: } peter1138@7462: belugas@6918: /** 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: */ truelight@0: static void CheckTrainCollision(Vehicle *v) truelight@0: { truelight@0: /* can't collide in depot */ rubidium@6319: if (v->u.rail.track == TRACK_BIT_DEPOT) return; rubidium@6319: rubidium@6319: assert(v->u.rail.track == TRACK_BIT_WORMHOLE || TileVirtXY(v->x_pos, v->y_pos) == v->tile); truelight@0: tron@6476: TrainCollideChecker tcc; truelight@0: tcc.v = v; peter1138@7462: tcc.num = 0; peter1138@7462: peter1138@7462: /* find colliding vehicles */ peter1138@7867: if (v->u.rail.track == TRACK_BIT_WORMHOLE) { peter1138@7867: VehicleFromPos(v->tile, &tcc, FindTrainCollideEnum); smatz@8693: VehicleFromPos(GetOtherTunnelBridgeEnd(v->tile), &tcc, FindTrainCollideEnum); peter1138@7867: } else { peter1138@7867: VehicleFromPosXY(v->x_pos, v->y_pos, &tcc, FindTrainCollideEnum); peter1138@7867: } peter1138@7462: peter1138@7462: /* any dead -> no crash */ peter1138@7462: if (tcc.num == 0) return; peter1138@7462: peter1138@7462: SetDParam(0, tcc.num); truelight@0: AddNewsItem(STR_8868_TRAIN_CRASH_DIE_IN_FIREBALL, rubidium@10556: 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); truelight@0: } truelight@0: truelight@0: static void *CheckVehicleAtSignal(Vehicle *v, void *data) truelight@0: { smatz@8577: Direction dir = *(Direction*)data; smatz@8577: smatz@8577: if (v->type == VEH_TRAIN && IsFrontEngine(v)) { smatz@8577: DirDiff diff = ChangeDirDiff(DirDifference(v->direction, dir), DIRDIFF_90RIGHT); tron@3158: tron@3158: if (diff == DIRDIFF_90RIGHT || (v->cur_speed <= 5 && diff <= DIRDIFF_REVERSE)) return v; truelight@0: } tron@1432: return NULL; truelight@0: } truelight@0: smatz@9206: 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@9206: for (prev = v->Previous(); v != nomove; prev = v, v = v->Next()) { peter1138@7367: DiagDirection enterdir = DIAGDIR_BEGIN; smatz@8830: bool update_signals_crossing = false; // will we update signals or crossing state? truelight@0: BeginVehicleMove(v); truelight@193: tron@6479: GetNewVehiclePosResult gp = GetNewVehiclePos(v); rubidium@6319: if (v->u.rail.track != TRACK_BIT_WORMHOLE) { darkvater@22: /* Not inside tunnel */ tron@6478: if (gp.old_tile == gp.new_tile) { darkvater@22: /* Staying in the old tile */ rubidium@6319: if (v->u.rail.track == TRACK_BIT_DEPOT) { rubidium@6320: /* Inside depot */ truelight@0: gp.x = v->x_pos; truelight@0: gp.y = v->y_pos; truelight@0: } else { rubidium@6320: /* Not inside depot */ truelight@742: peter1138@5919: if (IsFrontEngine(v) && !TrainCheckIfLineEnds(v)) return; truelight@742: tron@6476: uint32 r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); skidd13@8424: if (HasBit(r, VETS_CANNOT_ENTER)) { truelight@0: goto invalid_rail; matthijs@1247: } skidd13@8424: if (HasBit(r, VETS_ENTERED_STATION)) { rubidium@6317: TrainEnterStation(v, r >> VETS_STATION_ID_OFFSET); truelight@0: return; truelight@0: } truelight@0: rubidium@9332: if (v->current_order.IsType(OT_LEAVESTATION)) { bjarni@6589: v->current_order.Free(); smatz@8846: 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@6477: Direction dir = GetNewVehicleDirectionByTile(gp.new_tile, gp.old_tile); peter1138@7367: enterdir = DirToDiagDir(dir); rubidium@6320: 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@9290: TrackStatus ts = GetTileTrackStatus(gp.new_tile, TRANSPORT_RAIL, 0, ReverseDiagDir(enterdir)); frosch@9290: TrackdirBits reachable_trackdirs = DiagdirReachesTrackdirs(enterdir); frosch@9290: frosch@9290: TrackdirBits trackdirbits = TrackStatusToTrackdirBits(ts) & reachable_trackdirs; frosch@9290: TrackBits red_signals = TrackdirBitsToTrackBits(TrackStatusToRedSignals(ts) & reachable_trackdirs); frosch@9112: frosch@9112: TrackBits bits = TrackdirBitsToTrackBits(trackdirbits); rubidium@10775: 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@5838: bits &= ~TrackCrossesTracks(FindFirstTrack(v->u.rail.track)); tron@3017: } tron@3017: rubidium@6320: 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@6320: if (!CheckCompatibleRail(v, gp.new_tile)) goto invalid_rail; truelight@0: tron@6476: 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 */ tron@6481: chosen_track = TrackToTrackBits(ChooseTrainTrack(v, gp.new_tile, enterdir, bits)); frosch@9112: assert(chosen_track & bits); truelight@0: truelight@0: /* Check if it's a red signal and that force proceed is not clicked. */ frosch@9112: if (red_signals & chosen_track && v->u.rail.force_proceed == 0) { frosch@9112: /* In front of a red signal */ frosch@9112: Trackdir i = FindFirstTrackdir(trackdirbits); tron@6477: tron@6477: if (!HasSignalOnTrackdir(gp.new_tile, ReverseTrackdir(i))) { tron@6477: v->cur_speed = 0; tron@6477: v->subspeed = 0; tron@6477: v->progress = 255 - 100; rubidium@10775: if (++v->load_unload_time_rem < _settings_game.pf.wait_oneway_signal * 20) return; tron@6477: } else if (HasSignalOnTrackdir(gp.new_tile, i)) { tron@6477: v->cur_speed = 0; tron@6477: v->subspeed = 0; rubidium@6987: v->progress = 255 - 10; rubidium@10775: if (++v->load_unload_time_rem < _settings_game.pf.wait_twoway_signal * 73) { tron@6477: TileIndex o_tile = gp.new_tile + TileOffsByDiagDir(enterdir); smatz@8577: Direction rdir = ReverseDir(dir); tron@6477: tron@6477: /* check if a train is waiting on the other side */ smatz@8577: if (VehicleFromPos(o_tile, &rdir, &CheckVehicleAtSignal) == NULL) return; tron@6477: } tron@6477: } tron@6477: goto reverse_train_direction; tron@6477: } truelight@0: } else { rubidium@6320: static const TrackBits _matching_tracks[8] = { rubidium@6320: TRACK_BIT_LEFT | TRACK_BIT_RIGHT, TRACK_BIT_X, rubidium@6320: TRACK_BIT_UPPER | TRACK_BIT_LOWER, TRACK_BIT_Y, rubidium@6320: TRACK_BIT_LEFT | TRACK_BIT_RIGHT, TRACK_BIT_X, rubidium@6320: TRACK_BIT_UPPER | TRACK_BIT_LOWER, TRACK_BIT_Y rubidium@6320: }; truelight@193: truelight@0: /* The wagon is active, simply follow the prev vehicle. */ rubidium@5838: chosen_track = (TrackBits)(byte)(_matching_tracks[GetDirectionToVehicle(prev, gp.x, gp.y)] & bits); truelight@0: } truelight@0: rubidium@6320: /* Make sure chosen track is a valid track */ rubidium@6320: assert( rubidium@6320: chosen_track == TRACK_BIT_X || chosen_track == TRACK_BIT_Y || rubidium@6320: chosen_track == TRACK_BIT_UPPER || chosen_track == TRACK_BIT_LOWER || rubidium@6320: 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@6476: const byte *b = _initial_tile_subcoord[FIND_FIRST_BIT(chosen_track)][enterdir]; tron@6476: gp.x = (gp.x & ~0xF) | b[0]; tron@6476: gp.y = (gp.y & ~0xF) | b[1]; tron@6476: Direction chosen_dir = (Direction)b[2]; truelight@193: truelight@0: /* Call the landscape function and tell it that the vehicle entered the tile */ tron@6476: uint32 r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); skidd13@8424: if (HasBit(r, VETS_CANNOT_ENTER)) { truelight@0: goto invalid_rail; matthijs@1247: } truelight@0: bjarni@2676: if (IsFrontEngine(v)) v->load_unload_time_rem = 0; truelight@0: skidd13@8424: if (!HasBit(r, VETS_ENTERED_WORMHOLE)) { truelight@0: v->tile = gp.new_tile; celestar@3355: tron@6480: if (GetTileRailType(gp.new_tile) != GetTileRailType(gp.old_tile)) { rubidium@7993: 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@7367: /* We need to update signal status, but after the vehicle position hash peter1138@7367: * has been updated by AfterSetTrainPos() */ smatz@8830: update_signals_crossing = true; truelight@193: tron@3017: if (prev == NULL) AffectSpeedByDirChange(v, chosen_dir); truelight@0: truelight@0: v->direction = chosen_dir; truelight@0: } truelight@0: } else { smatz@8639: /* In a tunnel or on a bridge smatz@8639: * - for tunnels, only the part when the vehicle is not visible (part of enter/exit tile too) smatz@8639: * - for bridges, only the middle part - without the bridge heads */ tron@6467: if (!(v->vehstatus & VS_HIDDEN)) { tron@6467: v->cur_speed = belugas@8987: min(v->cur_speed, GetBridgeSpec(GetBridgeType(v->tile))->speed); tron@6467: } celestar@5573: smatz@8886: if (!IsTileType(gp.new_tile, MP_TUNNELBRIDGE) || !HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) { ludde@2125: v->x_pos = gp.x; ludde@2125: v->y_pos = gp.y; ludde@2125: VehiclePositionChanged(v); celestar@5573: 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@8639: v->UpdateDeltaXY(v->direction); smatz@8639: 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@6476: 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@7367: smatz@8830: if (update_signals_crossing) { peter1138@7367: if (IsFrontEngine(v)) TrainMovedChangeSignals(gp.new_tile, enterdir); peter1138@7367: peter1138@7367: /* Signals can only change when the first peter1138@7367: * (above) or the last vehicle moves. */ smatz@8830: if (v->Next() == NULL) { smatz@8830: TrainMovedChangeSignals(gp.old_tile, ReverseDiagDir(enterdir)); smatz@8838: if (IsLevelCrossingTile(gp.old_tile)) UpdateLevelCrossing(gp.old_tile); smatz@8830: } peter1138@7367: } truelight@0: } tron@1438: return; truelight@0: truelight@0: invalid_rail: darkvater@22: /* We've reached end of line?? */ glx@10839: 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: 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@8569: * or inside a tunnel/bridge, recalculate the signals as they might need updating belugas@6980: * @param v the Vehicle of which last wagon is to be removed Darkvater@1418: */ truelight@0: static void DeleteLastWagon(Vehicle *v) truelight@0: { smatz@8763: Vehicle *first = v->First(); smatz@8763: 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@6476: Vehicle *u = v; rubidium@7988: for (; v->Next() != NULL; v = v->Next()) u = v; rubidium@7988: u->SetNext(NULL); truelight@0: smatz@8763: if (first == v) { smatz@8763: /* Removing front vehicle (the last to go) */ smatz@8763: DeleteWindowById(WC_VEHICLE_VIEW, v->index); smatz@8763: InvalidateWindow(WC_COMPANY, v->owner); smatz@8763: } else { smatz@8763: /* Recalculate cached train properties */ smatz@8763: TrainConsistChanged(first); smatz@8763: /* Update the depot window if the first vehicle is in depot - smatz@8763: * if v == first, then it is updated in PreDestructor() */ smatz@8763: if (first->u.rail.track == TRACK_BIT_DEPOT) { smatz@8763: InvalidateWindow(WC_VEHICLE_DEPOT, first->tile); smatz@8763: } smatz@8763: } smatz@8763: rubidium@10621: InvalidateWindowClassesData(WC_TRAINS_LIST, 0); truelight@0: smatz@8813: MarkSingleVehicleDirty(v); rubidium@7139: smatz@8751: /* 'v' shouldn't be accessed after it has been deleted */ smatz@8751: TrackBits track = v->u.rail.track; smatz@8751: TileIndex tile = v->tile; smatz@8796: Owner owner = v->owner; smatz@8751: rubidium@7894: delete v; smatz@8796: v = NULL; // make sure nobody will won't try to read 'v' anymore truelight@0: smatz@8830: /* check if the wagon was on a road/rail-crossing */ smatz@8838: if (IsLevelCrossingTile(tile)) UpdateLevelCrossing(tile); smatz@8751: smatz@8753: /* Update signals */ smatz@10221: if (IsTileType(tile, MP_TUNNELBRIDGE) || IsRailDepotTile(tile)) { smatz@8796: UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, owner); smatz@8753: } else { smatz@8796: SetSignalsOnBothDir(tile, (Track)(FIND_FIRST_BIT(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@5573: /* We don't need to twist around vehicles if they're not visible */ celestar@5573: if (!(v->vehstatus & VS_HIDDEN)) { tron@3977: v->direction = ChangeDir(v->direction, delta[GB(Random(), 0, 2)]); truelight@0: BeginVehicleMove(v); rubidium@7054: v->UpdateDeltaXY(v->direction); rubidium@7630: v->cur_image = v->GetImage(v->direction); celestar@5573: /* Refrain from updating the z position of the vehicle when on smatz@9346: * a bridge, because AfterSetTrainPos will put the vehicle under smatz@9346: * the bridge in that case */ rubidium@6319: if (v->u.rail.track != TRACK_BIT_WORMHOLE) AfterSetTrainPos(v, false); truelight@0: } rubidium@7988: } 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@6319: if (state == 4 && !(v->vehstatus & VS_HIDDEN)) { tron@1359: CreateEffectVehicleRel(v, 4, 4, 8, EV_EXPLOSION_LARGE); truelight@0: } truelight@0: tron@6476: uint32 r; skidd13@8463: if (state <= 200 && Chance16R(1, 7, r)) { tron@3017: int index = (r * 10 >> 16); truelight@0: tron@6476: 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@7988: } while ((u = u->Next()) != NULL); truelight@0: } truelight@0: tron@3017: if (state <= 240 && !(v->tick_counter & 3)) ChangeTrainDirRandomly(v); truelight@0: rubidium@10229: if (state >= 4440 && !(v->tick_counter & 0x1F)) { truelight@0: DeleteLastWagon(v); rubidium@7139: 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@10775: 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@10271: 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@8779: /** Maximum speeds for train that is broken down or approaching line end */ smatz@8808: 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@8801: smatz@8779: /** smatz@8801: * Train is approaching line end, slow down and possibly reverse smatz@8779: * smatz@8801: * @param v front train engine smatz@8801: * @param signal not line end, just a red signal smatz@8801: * @return true iff we did NOT have to reverse smatz@8779: */ smatz@8801: static bool TrainApproachingLineEnd(Vehicle *v, bool signal) truelight@0: { smatz@8801: /* Calc position within the current tile */ tron@6476: uint x = v->x_pos & 0xF; tron@6476: uint y = v->y_pos & 0xF; truelight@193: smatz@8808: /* for diagonal directions, 'x' will be 0..15 - smatz@8808: * for other directions, it will be 1, 3, 5, ..., 15 */ tron@2639: switch (v->direction) { smatz@8808: 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@8808: case DIR_E : x = ~x + y + 9; break; tron@3186: case DIR_SE: x = y; break; smatz@8808: case DIR_S : x = x + y - 7; break; smatz@8808: case DIR_W : x = ~y + x + 9; break; rubidium@5838: default: break; truelight@0: } truelight@0: smatz@8801: /* do not reverse when approaching red signal */ smatz@8801: if (!signal && x + 4 >= TILE_SIZE) { smatz@8801: /* 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@6918: /* slow down */ truelight@0: v->vehstatus |= VS_TRAIN_SLOWING; tron@6476: 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@8801: smatz@8801: /** smatz@8830: * Determines whether train would like to leave the tile smatz@8830: * @param v train to test smatz@8830: * @return true iff vehicle is NOT entering or inside a depot or tunnel/bridge smatz@8830: */ smatz@8830: static bool TrainCanLeaveTile(const Vehicle *v) smatz@8830: { smatz@8830: /* Exit if inside a tunnel/bridge or a depot */ smatz@8830: if (v->u.rail.track == TRACK_BIT_WORMHOLE || v->u.rail.track == TRACK_BIT_DEPOT) return false; smatz@8830: smatz@8830: TileIndex tile = v->tile; smatz@8830: smatz@8830: /* entering a tunnel/bridge? */ smatz@8830: if (IsTileType(tile, MP_TUNNELBRIDGE)) { smatz@8830: DiagDirection dir = GetTunnelBridgeDirection(tile); smatz@8830: if (DiagDirToDir(dir) == v->direction) return false; smatz@8830: } smatz@8830: smatz@8830: /* entering a depot? */ smatz@10221: if (IsRailDepotTile(tile)) { smatz@8830: DiagDirection dir = ReverseDiagDir(GetRailDepotDirection(tile)); smatz@8830: if (DiagDirToDir(dir) == v->direction) return false; smatz@8830: } smatz@8830: smatz@8830: return true; smatz@8830: } smatz@8830: smatz@8830: smatz@8830: /** smatz@8830: * Determines whether train is approaching a rail-road crossing smatz@8830: * (thus making it barred) smatz@8830: * @param v front engine of train smatz@8830: * @return TileIndex of crossing the train is approaching, else INVALID_TILE smatz@8830: * @pre v in non-crashed front engine smatz@8830: */ smatz@8830: static TileIndex TrainApproachingCrossingTile(const Vehicle *v) smatz@8830: { smatz@8830: assert(IsFrontEngine(v)); smatz@8830: assert(!(v->vehstatus & VS_CRASHED)); smatz@8830: smatz@8830: if (!TrainCanLeaveTile(v)) return INVALID_TILE; smatz@8830: smatz@8830: DiagDirection dir = TrainExitDir(v->direction, v->u.rail.track); smatz@8830: TileIndex tile = v->tile + TileOffsByDiagDir(dir); smatz@8830: smatz@9102: /* not a crossing || wrong axis || unusable rail (wrong type or owner) */ smatz@8830: if (!IsLevelCrossingTile(tile) || DiagDirToAxis(dir) == GetCrossingRoadAxis(tile) || smatz@9102: !CheckCompatibleRail(v, tile)) { smatz@8830: return INVALID_TILE; smatz@8830: } smatz@8830: smatz@8830: return tile; smatz@8830: } smatz@8830: smatz@8830: smatz@8830: /** smatz@8801: * Checks for line end. Also, bars crossing at next tile if needed smatz@8801: * smatz@8801: * @param v vehicle we are checking smatz@8801: * @return true iff we did NOT have to reverse smatz@8801: */ smatz@8801: static bool TrainCheckIfLineEnds(Vehicle *v) smatz@8801: { smatz@8801: /* First, handle broken down train */ smatz@8801: smatz@8801: int t = v->breakdown_ctr; smatz@8801: if (t > 1) { smatz@8801: v->vehstatus |= VS_TRAIN_SLOWING; smatz@8801: smatz@8801: uint16 break_speed = _breakdown_speeds[GB(~t, 4, 4)]; smatz@8801: if (break_speed < v->cur_speed) v->cur_speed = break_speed; smatz@8801: } else { smatz@8801: v->vehstatus &= ~VS_TRAIN_SLOWING; smatz@8801: } smatz@8801: smatz@8830: if (!TrainCanLeaveTile(v)) return true; smatz@8801: smatz@8801: /* Determine the non-diagonal direction in which we will exit this tile */ smatz@8801: DiagDirection dir = TrainExitDir(v->direction, v->u.rail.track); smatz@8801: /* Calculate next tile */ smatz@8830: TileIndex tile = v->tile + TileOffsByDiagDir(dir); smatz@8801: smatz@8801: /* Determine the track status on the next tile */ frosch@9290: TrackStatus ts = GetTileTrackStatus(tile, TRANSPORT_RAIL, 0, ReverseDiagDir(dir)); frosch@9290: TrackdirBits reachable_trackdirs = DiagdirReachesTrackdirs(dir); frosch@9290: frosch@9290: TrackdirBits trackdirbits = TrackStatusToTrackdirBits(ts) & reachable_trackdirs; frosch@9290: TrackdirBits red_signals = TrackStatusToRedSignals(ts) & reachable_trackdirs; smatz@8801: smatz@8801: /* We are sure the train is not entering a depot, it is detected above */ smatz@8801: smatz@8978: /* mask unreachable track bits if we are forbidden to do 90deg turns */ frosch@9112: TrackBits bits = TrackdirBitsToTrackBits(trackdirbits); rubidium@10775: if (_settings_game.pf.pathfinder_for_trains != VPF_NTP && _settings_game.pf.forbid_90_deg) { smatz@8978: bits &= ~TrackCrossesTracks(FindFirstTrack(v->u.rail.track)); smatz@8978: } smatz@8978: smatz@9102: /* no suitable trackbits at all || unusable rail (wrong type or owner) */ smatz@9102: if (bits == TRACK_BIT_NONE || !CheckCompatibleRail(v, tile)) { smatz@8801: return TrainApproachingLineEnd(v, false); smatz@8801: } smatz@8801: smatz@8801: /* approaching red signal */ frosch@9112: if ((trackdirbits & red_signals) != 0) return TrainApproachingLineEnd(v, true); smatz@8801: smatz@8801: /* approaching a rail/road crossing? then make it red */ smatz@8852: if (IsLevelCrossingTile(tile)) MaybeBarCrossingWithSound(tile); smatz@8801: smatz@8801: return true; smatz@8801: } smatz@8801: smatz@8801: truelight@0: static void TrainLocoHandler(Vehicle *v, bool mode) truelight@0: { truelight@0: /* train has crashed? */ rubidium@7972: if (v->vehstatus & VS_CRASHED) { truelight@0: if (!mode) HandleCrashedTrain(v); truelight@0: return; truelight@0: } truelight@0: tron@3017: if (v->u.rail.force_proceed != 0) v->u.rail.force_proceed--; 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@10263: if (!v->current_order.IsType(OT_LOADING)) v->breakdown_ctr--; truelight@0: } truelight@0: skidd13@8424: 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@9323: 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@7090: v->HandleLoading(mode); truelight@0: rubidium@9332: 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: tron@6476: int j = UpdateTrainSpeed(v); smatz@8922: smatz@8922: /* we need to invalidate the widget if we are stopping from 'Stopping 0 km/h' to 'Stopped' */ smatz@8922: if (v->cur_speed == 0 && v->u.rail.last_speed == 0 && v->vehstatus & VS_STOPPED) { smatz@8922: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); smatz@8922: } smatz@8922: truelight@0: if (j == 0) { belugas@6918: /* if the vehicle has speed 0, update the last_speed field. */ tron@3017: if (v->cur_speed != 0) return; truelight@0: } else { truelight@0: TrainCheckIfLineEnds(v); truelight@0: truelight@0: do { smatz@9206: TrainController(v, NULL, true); hackykid@1922: CheckTrainCollision(v); truelight@0: if (v->cur_speed <= 0x100) truelight@0: break; truelight@0: } while (--j != 0); truelight@0: } truelight@0: truelight@0: SetLastSpeed(v, v->cur_speed); truelight@0: } truelight@0: truelight@0: rubidium@7984: rubidium@7984: Money Train::GetRunningCost() const rubidium@7984: { rubidium@7984: Money cost = 0; rubidium@7984: const Vehicle *v = this; rubidium@7984: rubidium@7984: do { rubidium@7984: const RailVehicleInfo *rvi = RailVehInfo(v->engine_type); rubidium@7984: peter1138@9118: byte cost_factor = GetVehicleProperty(v, 0x0D, rvi->running_cost); rubidium@7984: if (cost_factor == 0) continue; rubidium@7984: peter1138@10530: /* Halve running cost for multiheaded parts */ peter1138@10530: if (IsMultiheaded(v)) cost_factor /= 2; peter1138@10530: peter1138@9122: cost += cost_factor * GetPriceByIndex(rvi->running_cost_class); rubidium@7984: } while ((v = GetNextVehicle(v)) != NULL); rubidium@7984: rubidium@7984: return cost; rubidium@7984: } rubidium@7984: rubidium@7984: rubidium@7631: void Train::Tick() truelight@0: { rubidium@7631: if (_age_cargo_skip_counter == 0) this->cargo.AgeCargo(); rubidium@7631: rubidium@7631: this->tick_counter++; rubidium@7631: rubidium@7631: if (IsFrontEngine(this)) { smatz@9052: if (!(this->vehstatus & VS_STOPPED)) this->running_ticks++; rubidium@7631: this->current_order_time++; rubidium@7631: rubidium@7631: TrainLocoHandler(this, false); truelight@193: belugas@6918: /* make sure vehicle wasn't deleted. */ rubidium@7631: if (this->type == VEH_TRAIN && IsFrontEngine(this)) rubidium@7631: TrainLocoHandler(this, true); rubidium@7631: } else if (IsFreeWagon(this) && HASBITS(this->vehstatus, VS_CRASHED)) { rubidium@8192: /* Delete flooded standalone wagon chain */ rubidium@8192: 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@9346: static const uint MAX_ACCEPTABLE_DEPOT_DIST = 16; smatz@9346: rubidium@10775: if (_settings_game.vehicle.servint_trains == 0 || !v->NeedsAutomaticServicing()) return; rubidium@7998: if (v->IsInDepot()) { bjarni@4529: VehicleServiceInDepot(v); bjarni@4529: return; bjarni@4529: } bjarni@4529: tron@6476: 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. */ KUDr@3900: if (tfdd.best_length == (uint)-1 || tfdd.best_length > MAX_ACCEPTABLE_DEPOT_DIST) { rubidium@9332: 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@9332: v->current_order.MakeDummy(); smatz@8846: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); truelight@0: } truelight@0: return; truelight@0: } truelight@0: smatz@9346: const Depot *depot = GetDepotByTile(tfdd.tile); truelight@0: rubidium@9332: if (v->current_order.IsType(OT_GOTO_DEPOT) && rubidium@9336: v->current_order.GetDestination() != depot->index && skidd13@8463: !Chance16(3, 16)) { truelight@0: return; tron@3017: } truelight@0: rubidium@10079: v->current_order.MakeGoToDepot(depot->index, ODTFB_SERVICE); darkvater@308: v->dest_tile = tfdd.tile; smatz@8846: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); truelight@0: } truelight@0: glx@8963: void Train::OnNewDay() truelight@0: { glx@8963: if ((++this->day_counter & 7) == 0) DecreaseVehicleValue(this); glx@8963: glx@8963: if (IsFrontEngine(this)) { glx@8963: CheckVehicleBreakdown(this); glx@8963: AgeVehicle(this); glx@8963: glx@8963: CheckIfTrainNeedsService(this); glx@8963: glx@8963: CheckOrders(this); truelight@193: truelight@0: /* update destination */ rubidium@9332: if (this->current_order.IsType(OT_GOTO_STATION)) { rubidium@9336: TileIndex tile = GetStation(this->current_order.GetDestination())->train_tile; glx@8963: if (tile != 0) this->dest_tile = tile; tron@2639: } truelight@0: smatz@9052: if (this->running_ticks != 0) { truelight@0: /* running costs */ smatz@9052: CommandCost cost(EXPENSES_TRAIN_RUN, this->GetRunningCost() * this->running_ticks / (364 * DAY_TICKS)); smatz@9052: smatz@9052: this->profit_this_year -= cost.GetCost(); smatz@9052: this->running_ticks = 0; glx@8963: glx@8963: SubtractMoneyFromPlayerFract(this->owner, cost); glx@8963: glx@8963: InvalidateWindow(WC_VEHICLE_DETAILS, this->index); bjarni@1151: InvalidateWindowClasses(WC_TRAINS_LIST); truelight@0: } glx@8963: } else if (IsTrainEngine(this)) { truelight@7520: /* Also age engines that aren't front engines */ glx@8963: AgeVehicle(this); truelight@0: } truelight@0: } truelight@0: rubidium@6573: void TrainsYearlyLoop() truelight@0: { truelight@0: Vehicle *v; truelight@0: truelight@0: FOR_ALL_VEHICLES(v) { rubidium@6585: if (v->type == VEH_TRAIN && IsFrontEngine(v)) { belugas@6918: /* show warning if train is not generating enough income last 2 years (corresponds to a red icon in the vehicle list) */ rubidium@10775: if (_settings_client.gui.train_income_warn && v->owner == _local_player && v->age >= 730 && v->GetDisplayProfitThisYear() < 0) { smatz@9110: SetDParam(1, v->GetDisplayProfitThisYear()); tron@534: SetDParam(0, v->unitnumber); truelight@0: AddNewsItem( truelight@0: STR_TRAIN_IS_UNPROFITABLE, rubidium@10556: 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@6573: 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@6573: void ConnectMultiheadedTrains() bjarni@2855: { bjarni@2855: Vehicle *v; bjarni@2855: bjarni@2855: FOR_ALL_VEHICLES(v) { rubidium@6585: 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) { rubidium@6585: if (v->type == VEH_TRAIN && IsFrontEngine(v)) { peter1138@10660: for (Vehicle *u = v; u != NULL; u = u->Next()) { 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: tron@6476: Vehicle *w; smatz@9191: for (w = u->Next(); w != NULL && (w->engine_type != u->engine_type || w->u.rail.other_multiheaded_part != NULL); w = GetNextVehicle(w)) {} tron@6476: if (w != NULL) { tron@6476: /* we found a car to partner with this engine. Now we will make sure it face the right way */ tron@6476: if (IsTrainEngine(w)) { tron@6476: ClearTrainEngine(w); tron@6476: w->spritenum++; bjarni@2855: } tron@6476: w->u.rail.other_multiheaded_part = u; tron@6476: u->u.rail.other_multiheaded_part = w; tron@6476: } else { tron@6476: /* we got a front car and no rear cars. We will fake this one for forget that it should have been multiheaded */ tron@6476: ClearMultiheaded(u); bjarni@2855: } bjarni@2855: } peter1138@10660: } bjarni@2855: } bjarni@2855: } bjarni@2855: } bjarni@2855: belugas@6918: /** 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@6573: void ConvertOldMultiheadToNew() bjarni@2855: { bjarni@2855: Vehicle *v; bjarni@2855: FOR_ALL_VEHICLES(v) { rubidium@6585: if (v->type == VEH_TRAIN) { skidd13@8427: 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@6585: if (v->type == VEH_TRAIN) { skidd13@8424: if (HasBit(v->subtype, 7) && ((v->subtype & ~0x80) == 0 || (v->subtype & ~0x80) == 4)) { peter1138@10660: for (Vehicle *u = v; u != NULL; u = u->Next()) { bjarni@2855: const RailVehicleInfo *rvi = RailVehInfo(u->engine_type); tron@3017: skidd13@8425: ClrBit(u->subtype, 7); tron@3017: switch (u->subtype) { rubidium@4434: case 0: /* TS_Front_Engine */ belugas@6119: 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@6119: if (rvi->railveh_type == RAILVEH_WAGON) { tron@3017: // normal wagon tron@3017: SetTrainWagon(u); tron@3017: break; tron@3017: } belugas@6119: 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@6119: 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@10660: } bjarni@2855: } bjarni@2855: } bjarni@2855: } bjarni@2855: }