tron@2186: /* $Id$ */ tron@2186: celestar@9908: /** @file train_cmd.cpp */ celestar@9908: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@3234: #include "bridge_map.h" hackykid@1922: #include "debug.h" tron@2163: #include "functions.h" celestar@9908: #include "landscape.h" tron@2561: #include "gui.h" tron@3315: #include "station_map.h" tron@507: #include "table/strings.h" tron@679: #include "map.h" tron@1209: #include "tile.h" tron@3154: #include "tunnel_map.h" truelight@0: #include "vehicle.h" truelight@0: #include "command.h" truelight@0: #include "pathfind.h" matthijs@1247: #include "npf.h" truelight@0: #include "station.h" truelight@0: #include "table/train_cmd.h" truelight@0: #include "news.h" truelight@0: #include "engine.h" truelight@0: #include "player.h" tron@337: #include "sound.h" truelight@1313: #include "depot.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" bjarni@3256: #include "direction.h" KUDr@3900: #include "yapf/yapf.h" rubidium@4261: #include "date.h" peter1138@6417: #include "cargotype.h" celestar@9911: #include "group.h" truelight@0: truelight@742: static bool TrainCheckIfLineEnds(Vehicle *v); peter1138@5252: static void TrainController(Vehicle *v, bool update_image); 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}; rubidium@6319: static const TrackBits _state_dir_table[4] = { TRACK_BIT_RIGHT, TRACK_BIT_LOWER, TRACK_BIT_LEFT, TRACK_BIT_UPPER }; 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; peter1138@5163: return _patches.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: */ peter1138@5588: void TrainPowerChanged(Vehicle* v) peter1138@5588: { celestar@9910: uint32 total_power = 0; peter1138@5588: uint32 max_te = 0; peter1138@5588: tron@6476: 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: celestar@9910: if (engine_has_power) { celestar@9910: uint16 power = GetVehicleProperty(u, 0x0B, rvi_u->power); celestar@9910: if (power != 0) { celestar@9910: total_power += power; celestar@9910: /* Tractive effort in (tonnes * 1000 * 10 =) N */ celestar@9910: max_te += (u->u.rail.cached_veh_weight * 10000 * GetVehicleProperty(u, 0x1F, rvi_u->tractive_effort)) / 256; celestar@9910: } peter1138@5588: } peter1138@5588: peter1138@5588: if (HASBIT(u->u.rail.flags, VRF_POWEREDWAGON) && (wagon_has_power)) { celestar@9910: total_power += RailVehInfo(u->u.rail.first_engine)->pow_wag_power; peter1138@5588: } peter1138@5588: } peter1138@5588: celestar@9910: if (v->u.rail.cached_power != total_power || v->u.rail.cached_max_te != max_te) { celestar@9910: 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); peter1138@5588: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); 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: */ tron@2752: static void TrainCargoChanged(Vehicle* v) tron@2639: { peter1138@5162: uint32 weight = 0; hackykid@1905: tron@6476: for (Vehicle *u = v; u != NULL; u = u->next) { tron@6476: uint32 vweight = GetCargo(u->cargo_type)->weight * u->cargo_count * FreightWagonMult(u->cargo_type) / 16; peter1138@2602: celestar@9908: /* Vehicle weight is not added for articulated parts. */ bjarni@2676: if (!IsArticulatedPart(u)) { celestar@9908: /* vehicle weight is the sum of the weight of the vehicle and the weight of its cargo */ celestar@9911: vweight += GetVehicleProperty(u, 0x16, RailVehInfo(u->engine_type)->weight); peter1138@2602: celestar@9908: /* powered wagons have extra weight added */ peter1138@2602: if (HASBIT(u->u.rail.flags, VRF_POWEREDWAGON)) peter1138@4017: vweight += RailVehInfo(u->u.rail.first_engine)->pow_wag_weight; peter1138@2602: } hackykid@1905: celestar@9908: /* consist weight is the sum of the weight of all vehicles in the consist */ hackykid@1905: weight += vweight; hackykid@1905: celestar@9908: /* store vehicle weight in cache */ hackykid@1905: u->u.rail.cached_veh_weight = vweight; tron@6476: } hackykid@1905: celestar@9908: /* 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: */ tron@2639: void TrainConsistChanged(Vehicle* v) tron@2639: { hackykid@1905: uint16 max_speed = 0xFFFF; hackykid@1917: celestar@9895: 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; celestar@3355: v->u.rail.compatible_railtypes = 0; hackykid@1905: tron@6476: for (Vehicle *u = v; u != NULL; u = u->next) { hackykid@1908: const RailVehicleInfo *rvi_u = RailVehInfo(u->engine_type); hackykid@1908: celestar@9908: /* Update the v->first cache. This is faster than having to brute force it later. */ peter1138@2993: if (u->first == NULL) u->first = v; peter1138@2993: celestar@9908: /* 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@4017: if (IsTrainEngine(u)) first_engine = u->engine_type; peter1138@4017: celestar@9911: /* Cache wagon override sprite group. NULL is returned if there is none */ celestar@9911: u->u.rail.cached_override = GetWagonOverrideSpriteSet(u->engine_type, u->cargo_type, u->u.rail.first_engine); celestar@9911: 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)) { celestar@9908: /* 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) { celestar@9908: /* Steam is offset by -4 units */ peter1138@2595: u->u.rail.cached_vis_effect = 4; peter1138@2595: } else { celestar@9908: /* 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 */ peter1138@3956: if (HASBIT(EngInfo(u->engine_type)->callbackmask, CBM_WAGON_POWER)) { peter1138@3926: uint16 callback = GetVehicleCallback(CBID_TRAIN_WAGON_POWER, 0, 0, u->engine_type, u); peter1138@3926: peter1138@3926: if (callback != CALLBACK_FAILED) u->u.rail.cached_vis_effect = callback; peter1138@3926: } peter1138@3926: belugas@6119: if (rvi_v->pow_wag_power != 0 && rvi_u->railveh_type == RAILVEH_WAGON && celestar@9911: UsesWagonOverride(u) && !HASBIT(u->u.rail.cached_vis_effect, 7)) { belugas@6119: /* wagon is powered */ belugas@6119: SETBIT(u->u.rail.flags, VRF_POWEREDWAGON); // cache 'powered' status tron@6476: } else { tron@6476: 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 */ KUDr@5116: if (HASBIT(u->u.rail.flags, VRF_EL_ENGINE_ALLOWED_NORMAL_RAIL)) { KUDr@5116: u->u.rail.railtype = RAILTYPE_RAIL; KUDr@5116: u->u.rail.compatible_railtypes |= (1 << RAILTYPE_RAIL); KUDr@5116: } KUDr@5116: celestar@9908: /* max speed is the minimum of the speed limits of all vehicles in the consist */ celestar@9910: if ((rvi_u->railveh_type != RAILVEH_WAGON || _patches.wagon_speed_limits) && !UsesWagonOverride(u)) { celestar@9910: uint16 speed = GetVehicleProperty(u, 0x09, rvi_u->max_speed); celestar@9910: if (speed != 0) max_speed = min(speed, max_speed); celestar@9910: } hackykid@1908: } hackykid@1908: celestar@9911: if (u->cargo_type == rvi_u->cargo_type && u->cargo_subtype == 0) { celestar@9911: /* Set cargo capacity if we've not been refitted */ celestar@9911: u->cargo_cap = GetVehicleProperty(u, 0x14, rvi_u->capacity); celestar@9911: } celestar@9911: celestar@9908: /* check the vehicle length (callback) */ tron@6476: uint16 veh_len = CALLBACK_FAILED; peter1138@3956: if (HASBIT(EngInfo(u->engine_type)->callbackmask, CBM_VEHICLE_LENGTH)) { peter1138@3390: veh_len = GetVehicleCallback(CBID_TRAIN_VEHICLE_LENGTH, 0, 0, u->engine_type, u); peter1138@3956: } peter1138@3956: if (veh_len == CALLBACK_FAILED) veh_len = rvi_u->shorten_factor; hackykid@1934: 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: celestar@9908: /* store consist weight/max speed in cache */ hackykid@1905: v->u.rail.cached_max_speed = max_speed; celestar@3355: celestar@9908: /* 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); hackykid@1905: } matthijs@1247: matthijs@1247: /* These two arrays are used for realistic acceleration. XXX: How should they matthijs@1247: * be interpreted? */ celestar@1179: static const byte _curve_neighbours45[8][2] = { celestar@1179: {7, 1}, celestar@1179: {0, 2}, celestar@1179: {1, 3}, celestar@1179: {2, 4}, celestar@1179: {3, 5}, celestar@1179: {4, 6}, celestar@1179: {5, 7}, celestar@1179: {6, 0}, celestar@1179: }; celestar@1179: celestar@1179: static const byte _curve_neighbours90[8][2] = { celestar@1179: {6, 2}, celestar@1179: {7, 3}, celestar@1179: {0, 4}, celestar@1179: {1, 5}, celestar@1179: {2, 6}, celestar@1179: {3, 7}, celestar@1179: {4, 0}, celestar@1179: {5, 1}, celestar@1179: }; celestar@1179: celestar@1179: enum AccelType { celestar@1179: AM_ACCEL, celestar@1179: AM_BRAKE celestar@1179: }; celestar@1179: tron@2630: static bool TrainShouldStop(const Vehicle* v, TileIndex tile) celestar@1236: { tron@2630: const Order* o = &v->current_order; tron@3315: StationID sid = GetStationIndex(tile); tron@2630: celestar@9895: assert(v->type == VEH_TRAIN); celestar@9908: /* When does a train drive through a station celestar@9908: * first we deal with the "new nonstop handling" */ tron@4527: if (_patches.new_nonstop && o->flags & OF_NON_STOP && sid == o->dest) { celestar@1236: return false; tron@3315: } tron@3315: tron@3315: if (v->last_station_visited == sid) return false; tron@3315: tron@4527: if (sid != o->dest && (o->flags & OF_NON_STOP || _patches.new_nonstop)) { celestar@1236: return false; tron@3315: } celestar@1236: celestar@1236: return true; celestar@1236: } celestar@1236: celestar@9908: /** new acceleration*/ celestar@1179: static int GetTrainAcceleration(Vehicle *v, bool mode) celestar@1179: { celestar@1179: int max_speed = 2000; tron@6476: int speed = v->cur_speed * 10 / 16; //[mph] tron@6476: int curvecount[2] = {0, 0}; tron@6476: celestar@9908: /*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; tron@6476: for (const Vehicle *u = v; u->next != NULL; u = u->next, pos++) { tron@3157: Direction dir = u->direction; tron@3157: Direction ndir = u->next->direction; tron@1531: int i; celestar@1179: celestar@1179: for (i = 0; i < 2; i++) { celestar@1179: if ( _curve_neighbours45[dir][i] == ndir) { celestar@1179: curvecount[i]++; celestar@1179: if (lastpos != -1) { tron@1531: numcurve++; tron@1531: sum += pos - lastpos; celestar@1179: if (pos - lastpos == 1) { celestar@1179: max_speed = 88; celestar@1179: } celestar@1179: } celestar@1179: lastpos = pos; celestar@1179: } celestar@1179: } celestar@1179: celestar@9908: /*if we have a 90 degree turn, fix the speed limit to 60 */ tron@1472: if (_curve_neighbours90[dir][0] == ndir || tron@1472: _curve_neighbours90[dir][1] == ndir) { celestar@1179: max_speed = 61; celestar@1179: } celestar@1179: } celestar@1179: tron@1472: if (numcurve > 0) sum /= numcurve; tron@1472: 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) { celestar@1179: max_speed = 0xFFFF; celestar@1179: } else if (total > 1) { celestar@1179: max_speed = 232 - (13 - clamp(sum, 1, 12)) * (13 - clamp(sum, 1, 12)); celestar@1179: } celestar@1179: } celestar@1179: celestar@1179: max_speed += (max_speed / 2) * v->u.rail.railtype; celestar@1179: bjarni@2676: if (IsTileType(v->tile, MP_STATION) && IsFrontEngine(v)) { celestar@1236: if (TrainShouldStop(v, v->tile)) { celestar@6324: int station_length = GetStationByTile(v->tile)->GetPlatformLength(v->tile, DirToDiagDir(v->direction)); celestar@1179: int delta_v; celestar@1179: celestar@1179: max_speed = 120; celestar@1179: celestar@1179: delta_v = v->cur_speed / (station_length + 1); celestar@1179: if (v->max_speed > (v->cur_speed - delta_v)) celestar@1179: max_speed = v->cur_speed - (delta_v / 10); celestar@1179: celestar@1179: max_speed = max(max_speed, 25 * station_length); 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] tron@6476: 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: tron@1683: if (HASBIT(u->u.rail.flags, VRF_GOINGUP)) { rubidium@4434: incl += u->u.rail.cached_veh_weight * 60; //3% slope, quite a bit actually tron@1683: } 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 { celestar@9908: /* "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: tron@2817: static 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); tron@1472: v->acceleration = clamp(power / weight * 4, 1, 255); truelight@0: } truelight@0: tron@3157: int GetTrainImage(const Vehicle* v, Direction direction) truelight@0: { truelight@0: int img = v->spritenum; truelight@0: int base; truelight@0: bjarni@3256: if (HASBIT(v->u.rail.flags, VRF_REVERSE_DIRECTION)) direction = ReverseDir(direction); bjarni@3256: truelight@0: if (is_custom_sprite(img)) { rubidium@5838: base = GetCustomVehicleSprite(v, (Direction)(direction + 4 * IS_CUSTOM_SECONDHEAD_SPRITE(img))); tron@1472: if (base != 0) return base; peter1138@2464: img = orig_rail_vehicle_info[v->engine_type].image_index; truelight@0: } truelight@193: truelight@0: base = _engine_sprite_base[img] + ((direction + _engine_sprite_add[img]) & _engine_sprite_and[img]); truelight@0: tron@2639: if (v->cargo_count >= v->cargo_cap / 2) base += _wagon_full_adder[img]; truelight@0: return base; truelight@0: } truelight@0: peter1138@5919: void DrawTrainEngine(int x, int y, EngineID engine, SpriteID pal) truelight@0: { tron@540: const RailVehicleInfo *rvi = RailVehInfo(engine); truelight@193: truelight@0: int img = rvi->image_index; peter1138@5919: SpriteID image = 0; truelight@0: truelight@0: if (is_custom_sprite(img)) { tron@3186: image = GetCustomVehicleIcon(engine, DIR_W); peter1138@2570: if (image == 0) { peter1138@2570: img = orig_rail_vehicle_info[engine].image_index; peter1138@2570: } else { peter1138@2570: y += _traininfo_vehicle_pitch; peter1138@2570: } truelight@0: } tron@1472: if (image == 0) { truelight@0: image = (6 & _engine_sprite_and[img]) + _engine_sprite_base[img]; truelight@0: } truelight@0: belugas@6119: if (rvi->railveh_type == RAILVEH_MULTIHEAD) { peter1138@5919: DrawSprite(image, pal, x - 14, y); truelight@0: x += 15; truelight@0: image = 0; truelight@0: if (is_custom_sprite(img)) { rubidium@5838: image = GetCustomVehicleIcon(engine, DIR_E); peter1138@2464: if (image == 0) img = orig_rail_vehicle_info[engine].image_index; truelight@0: } tron@1472: if (image == 0) { tron@1472: image = tron@1472: ((6 + _engine_sprite_add[img + 1]) & _engine_sprite_and[img + 1]) + tron@1472: _engine_sprite_base[img + 1]; truelight@0: } truelight@0: } peter1138@5919: DrawSprite(image, pal, x, y); truelight@0: } truelight@0: peter1138@4737: uint CountArticulatedParts(EngineID engine_type) peter1138@2602: { tron@6476: if (!HASBIT(EngInfo(engine_type)->callbackmask, CBM_ARTIC_ENGINE)) return 0; tron@6476: peter1138@2602: uint i; peter1138@2602: for (i = 1; i < 10; i++) { tron@6476: uint16 callback = GetVehicleCallback(CBID_TRAIN_ARTIC_ENGINE, i, 0, engine_type, NULL); tron@2639: if (callback == CALLBACK_FAILED || callback == 0xFF) break; peter1138@2602: } peter1138@2602: peter1138@2608: return i - 1; peter1138@2602: } peter1138@2602: peter1138@3956: static void AddArticulatedParts(Vehicle **vl) peter1138@2602: { tron@6476: const Vehicle *v = vl[0]; tron@6476: Vehicle *u = vl[0]; peter1138@2602: peter1138@3956: if (!HASBIT(EngInfo(v->engine_type)->callbackmask, CBM_ARTIC_ENGINE)) return; peter1138@2602: tron@6476: for (uint i = 1; i < 10; i++) { tron@6476: uint16 callback = GetVehicleCallback(CBID_TRAIN_ARTIC_ENGINE, i, 0, v->engine_type, v); tron@3017: if (callback == CALLBACK_FAILED || callback == 0xFF) return; peter1138@2602: peter1138@4831: /* Attempt to use pre-allocated vehicles until they run out. This can happen peter1138@4831: * if the callback returns different values depending on the cargo type. */ peter1138@2602: u->next = vl[i]; peter1138@4831: if (u->next == NULL) u->next = AllocateVehicle(); peter1138@4831: if (u->next == NULL) return; peter1138@4831: peter1138@2602: u = u->next; peter1138@2602: tron@6476: EngineID engine_type = GB(callback, 0, 7); tron@6476: bool flip_image = HASBIT(callback, 7); tron@6476: const RailVehicleInfo *rvi_artic = RailVehInfo(engine_type); peter1138@2602: celestar@9908: /* get common values from first engine */ peter1138@2602: u->direction = v->direction; peter1138@2602: u->owner = v->owner; peter1138@2602: u->tile = v->tile; peter1138@2602: u->x_pos = v->x_pos; peter1138@2602: u->y_pos = v->y_pos; peter1138@2602: u->z_pos = v->z_pos; peter1138@2602: u->u.rail.track = v->u.rail.track; peter1138@2602: u->u.rail.railtype = v->u.rail.railtype; peter1138@2602: u->build_year = v->build_year; peter1138@2602: u->vehstatus = v->vehstatus & ~VS_STOPPED; peter1138@2602: u->u.rail.first_engine = v->engine_type; peter1138@2602: celestar@9908: /* get more settings from rail vehicle info */ peter1138@2602: u->spritenum = rvi_artic->image_index; peter1138@2602: if (flip_image) u->spritenum++; peter1138@2602: u->cargo_type = rvi_artic->cargo_type; peter1138@3870: u->cargo_subtype = 0; peter1138@2602: u->cargo_cap = rvi_artic->capacity; peter1138@2602: u->max_speed = 0; peter1138@2602: u->max_age = 0; peter1138@2602: u->engine_type = engine_type; peter1138@2602: u->value = 0; celestar@9911: u = new (u) Train(); bjarni@2676: u->subtype = 0; bjarni@2676: SetArticulatedPart(u); peter1138@2602: u->cur_image = 0xAC2; peter1138@2804: u->random_bits = VehicleRandomBits(); peter1138@2602: peter1138@2602: VehiclePositionChanged(u); peter1138@2602: } peter1138@2602: } truelight@0: tron@2477: static int32 CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags) truelight@0: { darkvater@889: SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES); darkvater@889: tron@6476: const RailVehicleInfo *rvi = RailVehInfo(engine); celestar@9911: int32 value = (GetEngineProperty(engine, 0x17, rvi->base_cost) * _eco->GetPrice(CEconomy::BUILD_RAILWAGON)) >> 8; tron@6476: tron@6476: uint num_vehicles = 1 + CountArticulatedParts(engine); peter1138@2602: truelight@0: if (!(flags & DC_QUERY_COST)) { peter1138@2609: Vehicle *vl[11]; // Allow for wagon and upto 10 artic parts. peter1138@2602: peter1138@4831: memset(&vl, 0, sizeof(vl)); peter1138@4831: peter1138@2602: if (!AllocateVehicles(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) { celestar@9895: if (w->type == VEH_TRAIN && w->tile == tile && bjarni@2676: IsFreeWagon(w) && w->engine_type == engine) { truelight@919: u = GetLastVehicleInChain(w); truelight@0: break; truelight@0: } truelight@0: } truelight@0: 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: bjarni@2676: v->subtype = 0; bjarni@2676: SetTrainWagon(v); truelight@0: if (u != NULL) { truelight@0: u->next = 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; peter1138@3870: v->cargo_subtype = 0; truelight@0: v->cargo_cap = rvi->capacity; truelight@0: v->value = value; 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; celestar@9911: v = new (v) Train(); truelight@0: v->cur_image = 0xAC2; peter1138@2804: v->random_bits = VehicleRandomBits(); truelight@193: celestar@9911: v->group_id = DEFAULT_GROUP; celestar@9911: peter1138@3956: AddArticulatedParts(vl); peter1138@2602: bjarni@2564: _new_vehicle_id = v->index; truelight@0: truelight@0: VehiclePositionChanged(v); hackykid@1917: TrainConsistChanged(GetFirstVehicleInChain(v)); celestar@9911: UpdateTrainGroupID(GetFirstVehicleInChain(v)); truelight@0: truelight@0: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); bjarni@2970: if (IsLocalPlayer()) { celestar@9895: InvalidateAutoreplaceWindow(VEH_TRAIN); // updates the replace Train window bjarni@2970: } bjarni@6195: GetPlayer(_current_player)->num_engines[engine]++; truelight@0: } truelight@0: } truelight@0: truelight@0: return value; truelight@0: } truelight@0: celestar@9908: /** Move all free vehicles in the depot to the train */ tron@2630: static void NormalizeTrainVehInDepot(const Vehicle* u) truelight@0: { tron@2630: const Vehicle* v; tron@1472: truelight@0: FOR_ALL_VEHICLES(v) { celestar@9895: 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: celestar@9911: static int32 EstimateTrainCost(EngineID engine, const RailVehicleInfo* rvi) truelight@0: { celestar@9911: return GetEngineProperty(engine, 0x17, rvi->base_cost) * (_eco->GetPrice(CEconomy::BUILD_RAILVEHICLE) >> 3) >> 5; truelight@0: } truelight@0: tron@2817: static void AddRearEngineToMultiheadedTrain(Vehicle* v, Vehicle* u, bool building) bjarni@1060: { 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; bjarni@2676: 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; bjarni@1060: if (building) v->next = 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; celestar@9911: u = new (u) Train(); 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 celestar@9908: * @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: */ tron@3491: int32 CmdBuildRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { Darkvater@1784: /* Check if the engine-type is valid (for the player) */ celestar@9908: 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)) { pasky@1443: if (!IsTileDepotType(tile, TRANSPORT_RAIL)) return CMD_ERROR; tron@1901: if (!IsTileOwner(tile, _current_player)) return CMD_ERROR; pasky@1443: } bjarni@1221: truelight@0: SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES); truelight@0: tron@6476: const RailVehicleInfo *rvi = RailVehInfo(p1); bjarni@2244: bjarni@2244: /* Check if depot and new engine uses the same kind of tracks */ bjarni@3411: /* We need to see if the engine got power on the tile to avoid eletric engines in non-electric depots */ tron@6074: if (!HasPowerOnRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR; truelight@193: belugas@6119: if (rvi->railveh_type == RAILVEH_WAGON) return CmdBuildRailWagon(p1, tile, flags); truelight@0: celestar@9911: int32 value = EstimateTrainCost(p1, rvi); tron@6476: tron@6476: uint num_vehicles = tron@6476: (rvi->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1) + tron@6476: CountArticulatedParts(p1); truelight@0: truelight@0: if (!(flags & DC_QUERY_COST)) { peter1138@2609: Vehicle *vl[12]; // Allow for upto 10 artic parts and dual-heads peter1138@4831: peter1138@4831: memset(&vl, 0, sizeof(vl)); peter1138@4831: tron@6173: if (!AllocateVehicles(vl, num_vehicles)) truelight@0: return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); truelight@0: tron@6476: Vehicle *v = vl[0]; tron@6476: celestar@9895: UnitID unit_num = HASBIT(p2, 0) ? 0 : GetFreeUnitNumber(VEH_TRAIN); bjarni@3816: if (unit_num > _patches.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: 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); 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; peter1138@3870: v->cargo_subtype = 0; truelight@0: v->cargo_cap = rvi->capacity; truelight@0: v->max_speed = rvi->max_speed; truelight@0: v->value = value; truelight@1266: v->last_station_visited = INVALID_STATION; truelight@0: 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: truelight@0: v->string_id = STR_SV_TRAIN_NAME; tron@6074: v->u.rail.railtype = rvi->railtype; bjarni@2564: _new_vehicle_id = v->index; truelight@193: truelight@0: v->service_interval = _patches.servint_trains; truelight@0: v->date_of_last_service = _date; rubidium@4329: v->build_year = _cur_year; celestar@9911: v = new (v) Train(); truelight@0: v->cur_image = 0xAC2; peter1138@2804: v->random_bits = VehicleRandomBits(); truelight@0: maedhros@6502: v->vehicle_flags = 0; maedhros@6502: if (e->flags & ENGINE_EXCLUSIVE_PREVIEW) SETBIT(v->vehicle_flags, VF_BUILT_AS_PROTOTYPE); maedhros@6502: celestar@9911: v->group_id = DEFAULT_GROUP; celestar@9911: bjarni@2676: 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 { peter1138@3956: AddArticulatedParts(vl); bjarni@2244: } truelight@0: hackykid@1905: TrainConsistChanged(v); truelight@0: UpdateTrainAcceleration(v); celestar@9911: UpdateTrainGroupID(v); bjarni@2244: rubidium@4434: 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); tron@588: RebuildVehicleLists(); truelight@0: InvalidateWindow(WC_COMPANY, v->owner); bjarni@6195: if (IsLocalPlayer()) celestar@9895: InvalidateAutoreplaceWindow(VEH_TRAIN); // updates the replace Train window 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 */ tron@3183: if (!IsTileDepotType(tile, TRANSPORT_RAIL) || v->cur_speed != 0) return -1; truelight@0: tron@6476: int count = 0; tron@1472: 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@3985: if (!IsArticulatedPart(v) && !(!IsTrainEngine(v) && IsMultiheaded(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: { celestar@9908: /* 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)) {} peter1138@2602: GetLastEnginePart(u)->next = 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) { celestar@9895: if (dst->type == VEH_TRAIN && IsFreeWagon(dst) && dst->tile == tile) { celestar@9908: /* 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) { tron@1472: 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: { bjarni@2676: UnlinkWagon(v, GetFirstVehicleInChain(v)); bjarni@2676: if (dest == NULL) return; bjarni@2676: bjarni@2676: v->next = dest->next; bjarni@2676: dest->next = v; 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; tron@6476: 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 celestar@9908: * @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: */ tron@3491: int32 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: celestar@9895: if (src->type != VEH_TRAIN || !CheckOwnership(src->owner)) return CMD_ERROR; truelight@0: celestar@9908: /* 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); celestar@9895: if (dst->type != VEH_TRAIN || !CheckOwnership(dst->owner)) return CMD_ERROR; truelight@0: } truelight@0: celestar@9908: /* if an articulated part is being handled, deal with its parent vehicle */ bjarni@2676: while (IsArticulatedPart(src)) src = GetPrevVehicleInChain(src); peter1138@2602: if (dst != NULL) { bjarni@2676: while (IsArticulatedPart(dst)) dst = GetPrevVehicleInChain(dst); peter1138@2602: } peter1138@2602: celestar@9908: /* don't move the same vehicle.. */ Darkvater@1784: if (src == dst) return 0; truelight@193: truelight@0: /* locate the head of the two chains */ tron@6476: Vehicle *src_head = GetFirstVehicleInChain(src); tron@6476: Vehicle *dst_head; peter1138@2602: if (dst != NULL) { peter1138@2602: dst_head = GetFirstVehicleInChain(dst); tron@6462: if (dst_head->tile != src_head->tile) return CMD_ERROR; celestar@9908: /* 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@2676: if (dst != NULL && IsMultiheaded(dst) && !IsTrainEngine(dst) && IsTrainWagon(src)) { bjarni@2676: /* We are moving a wagon to the rear part of a multiheaded engine */ bjarni@2676: if (dst->next == NULL) { bjarni@2676: /* It's the last one, so we will add the wagon just before the rear engine */ bjarni@2676: dst = GetPrevVehicleInChain(dst); truelight@2825: /* Now if the vehicle we want to link to is the vehicle itself, drop out */ truelight@2825: if (dst == src) return CMD_ERROR; celestar@9908: /* if dst is NULL, it means that dst got a rear multiheaded engine as first engine. We can't use that */ bjarni@2676: if (dst == NULL) return CMD_ERROR; bjarni@2676: } else { bjarni@2676: /* there are more units on this train, so we will add the wagon after the next one*/ bjarni@2676: dst = dst->next; bjarni@2676: } celestar@1601: } celestar@1601: bjarni@2676: if (IsTrainEngine(src) && dst_head != NULL) { bjarni@2676: /* we need to make sure that we didn't place it between a pair of multiheaded engines */ tron@6476: Vehicle *engine = NULL; tron@6476: tron@6476: for (Vehicle *u = dst_head; u != NULL; u = u->next) { bjarni@2676: if (IsTrainEngine(u) && IsMultiheaded(u) && u->u.rail.other_multiheaded_part != NULL) { bjarni@2676: engine = u; bjarni@2676: } Darkvater@4198: if (engine != NULL && engine->u.rail.other_multiheaded_part == u) { Darkvater@4198: engine = NULL; Darkvater@4198: } Darkvater@4198: if (u == dst) { Darkvater@4198: if (engine != NULL) dst = engine->u.rail.other_multiheaded_part; Darkvater@4198: break; Darkvater@4198: } bjarni@2676: } bjarni@2676: } bjarni@2676: bjarni@2676: if (IsMultiheaded(src) && !IsTrainEngine(src)) return_cmd_error(STR_REAR_ENGINE_FOLLOW_FRONT_ERROR); bjarni@2676: celestar@9908: /* when moving all wagons, we can't have the same src_head and dst_head */ Darkvater@1784: if (HASBIT(p2, 0) && src_head == dst_head) return 0; truelight@0: peter1138@2883: { peter1138@2883: int max_len = _patches.mammoth_trains ? 100 : 9; peter1138@2883: celestar@9908: /* 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: celestar@9908: /* 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) { celestar@9908: /* 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: celestar@9908: /* We are moving between rows, so only count the wagons from the source celestar@9908: * row that are being moved. */ peter1138@2883: 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 { celestar@9908: /* 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) { celestar@9908: /* 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); celestar@9908: /* 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 { celestar@9908: /* 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: celestar@9908: /* moving a loco to a new line?, then we need to assign a unitnumber. */ bjarni@2676: if (dst == NULL && !IsFrontEngine(src) && IsTrainEngine(src)) { celestar@9895: UnitID unit_num = GetFreeUnitNumber(VEH_TRAIN); truelight@0: if (unit_num > _patches.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: peter1138@3727: if (dst_head != NULL) { peter1138@3727: /* Check NewGRF Callback 0x1D */ peter1138@3727: uint16 callback = GetVehicleCallbackParent(CBID_TRAIN_ALLOW_WAGON_ATTACH, 0, 0, dst_head->engine_type, src, dst_head); peter1138@3727: if (callback != CALLBACK_FAILED) { peter1138@3729: if (callback == 0xFD) return_cmd_error(STR_INCOMPATIBLE_RAIL_TYPES); peter1138@3727: if (callback < 0xFD) { peter1138@3727: StringID error = GetGRFStringID(GetEngineGRFID(dst_head->engine_type), 0xD000 + callback); peter1138@3727: return_cmd_error(error); peter1138@3727: } peter1138@3727: } peter1138@3727: } truelight@0: truelight@0: /* do it? */ truelight@0: if (flags & DC_EXEC) { bjarni@2676: /* clear the ->first cache */ tron@6476: for (Vehicle *u = src_head; u != NULL; u = u->next) u->first = NULL; tron@6476: for (Vehicle *u = dst_head; u != NULL; u = u->next) u->first = NULL; bjarni@2607: celestar@9911: /* If we move the front Engine and if the second vehicle is not an engine celestar@9911: add the whole vehicle to the DEFAULT_GROUP */ celestar@9911: if (IsFrontEngine(src) && !IsDefaultGroupID(src->group_id)) { celestar@9911: const Vehicle *v = GetNextVehicle(src); celestar@9911: celestar@9911: if (v != NULL && !IsTrainEngine(v)) { celestar@9911: DoCommand(tile, DEFAULT_GROUP, v->index, flags, CMD_ADD_VEHICLE_GROUP); celestar@9911: } celestar@9911: } celestar@9911: Darkvater@1784: if (HASBIT(p2, 0)) { celestar@9908: /* unlink ALL wagons */ truelight@0: if (src != src_head) { truelight@0: Vehicle *v = src_head; peter1138@2602: while (GetNextVehicle(v) != src) v = GetNextVehicle(v); peter1138@2602: GetLastEnginePart(v)->next = 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 { celestar@9908: /* 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; celestar@9908: /* unlink single wagon from linked list */ hackykid@1917: src_head = UnlinkWagon(src, src_head); peter1138@2602: GetLastEnginePart(src)->next = 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: celestar@9908: /* 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)) { celestar@9908: /* 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; celestar@9911: celestar@9911: // Decrease the engines number of the src engine_type celestar@9911: if (!IsDefaultGroupID(src->group_id) && IsValidGroupID(src->group_id)) { celestar@9911: GetGroup(src->group_id)->num_engines[src->engine_type]--; celestar@9911: } celestar@9911: celestar@9911: // If we move an engine to a new line affect it to the DEFAULT_GROUP celestar@9911: 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)) { celestar@9908: /* 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); 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: celestar@9908: /* link in the wagon(s) in the chain. */ truelight@0: { tron@1472: Vehicle *v; tron@1472: peter1138@2602: for (v = src; GetNextVehicle(v) != NULL; v = GetNextVehicle(v)); peter1138@2602: GetLastEnginePart(v)->next = dst->next; truelight@0: } truelight@0: dst->next = src; truelight@0: } bjarni@2676: if (src->u.rail.other_multiheaded_part != NULL) { bjarni@2676: if (src->u.rail.other_multiheaded_part == src_head) { bjarni@2676: src_head = src_head->next; bjarni@2676: } bjarni@2676: AddWagonToConsist(src->u.rail.other_multiheaded_part, src); celestar@9908: /* previous line set the front engine to the old front. We need to clear that */ bjarni@4197: src->u.rail.other_multiheaded_part->first = NULL; bjarni@2676: } bjarni@2676: bjarni@2676: if (HASBIT(p2, 0) && src_head != NULL && src_head != src) { bjarni@2676: /* if we stole a rear multiheaded engine, we better give it back to the front end */ bjarni@2676: Vehicle *engine = NULL, *u; bjarni@2676: for (u = src_head; u != NULL; u = u->next) { bjarni@2676: if (IsMultiheaded(u)) { bjarni@2676: if (IsTrainEngine(u)) { bjarni@2676: engine = u; bjarni@2676: continue; bjarni@2676: } bjarni@2676: /* we got the rear engine to match with the front one */ bjarni@2676: engine = NULL; bjarni@2676: } bjarni@2676: } bjarni@2676: if (engine != NULL && engine->u.rail.other_multiheaded_part != NULL) { bjarni@2676: AddWagonToConsist(engine->u.rail.other_multiheaded_part, engine); celestar@9908: /* previous line set the front engine to the old front. We need to clear that */ bjarni@2676: engine->u.rail.other_multiheaded_part->first = NULL; bjarni@2676: } bjarni@2676: } truelight@0: 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)) { celestar@9911: /* As in CmdMoveRailVehicle src_head->group_id will be equal to DEFAULT_GROUP celestar@9911: * we need to save the group and reaffect it to src_head */ celestar@9911: const GroupID tmp_g = src_head->group_id; tron@3491: CmdMoveRailVehicle(0, flags, src_head->index | (INVALID_VEHICLE << 16), 1); celestar@9911: 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); celestar@9911: UpdateTrainGroupID(src_head); bjarni@2676: if (IsFrontEngine(src_head)) { hackykid@1917: UpdateTrainAcceleration(src_head); hackykid@1917: InvalidateWindow(WC_VEHICLE_DETAILS, src_head->index); hackykid@1917: /* Update the refit button and window */ hackykid@1917: InvalidateWindow(WC_VEHICLE_REFIT, src_head->index); hackykid@1917: InvalidateWindowWidget(WC_VEHICLE_VIEW, src_head->index, 12); 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); celestar@9911: UpdateTrainGroupID(dst_head); bjarni@2676: if (IsFrontEngine(dst_head)) { truelight@0: UpdateTrainAcceleration(dst_head); hackykid@1917: InvalidateWindow(WC_VEHICLE_DETAILS, dst_head->index); hackykid@1917: /* Update the refit button and window */ hackykid@1917: InvalidateWindowWidget(WC_VEHICLE_VIEW, dst_head->index, 12); 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: tron@737: RebuildVehicleLists(); truelight@0: } truelight@0: truelight@0: return 0; truelight@0: } truelight@0: Darkvater@1784: /** Start/Stop a train. tron@3491: * @param tile unused celestar@9908: * @param flags type of operation Darkvater@1784: * @param p1 train to start/stop Darkvater@1784: * @param p2 unused Darkvater@1784: */ tron@3491: int32 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: celestar@9895: 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); peter1138@4244: if (callback != CALLBACK_FAILED && callback != 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; darkvater@755: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); truelight@0: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); truelight@0: } truelight@0: return 0; truelight@0: } truelight@0: Darkvater@1784: /** Sell a (single) train wagon/engine. tron@3491: * @param tile unused celestar@9908: * @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 Darkvater@1784: 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: */ tron@3491: int32 CmdSellRailWagon(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@4352: if (!IsValidVehicleID(p1) || p2 > 2) return CMD_ERROR; truelight@0: tron@6476: Vehicle *v = GetVehicle(p1); truelight@0: celestar@9895: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: bjarni@1237: SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES); bjarni@1237: bjarni@2676: while (IsArticulatedPart(v)) v = GetPrevVehicleInChain(v); tron@6476: Vehicle *first = GetFirstVehicleInChain(v); truelight@193: celestar@9908: /* 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@2676: if (IsMultiheaded(v) && !IsTrainEngine(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)) { Darkvater@1842: DeleteWindowById(WC_VEHICLE_VIEW, first->index); bjarni@2618: } truelight@0: InvalidateWindow(WC_VEHICLE_DEPOT, first->tile); Darkvater@1779: RebuildVehicleLists(); Darkvater@1766: } Darkvater@1766: tron@6476: int32 cost = 0; 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) { bjarni@2676: cost -= rear->value; Darkvater@1766: if (flags & DC_EXEC) { bjarni@2676: UnlinkWagon(rear, first); bjarni@5256: DeleteDepotHighlightOfVehicle(rear); Darkvater@1766: DeleteVehicle(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.1 If the first wagon is sold, update the first-> pointers to NULL */ tron@6476: for (Vehicle *tmp = first; tmp != NULL; tmp = tmp->next) tmp->first = NULL; Darkvater@1766: Darkvater@1766: /* 2.2 If there are wagons present after the deleted front engine, check celestar@9895: * if the second wagon (which will be first) is an engine. If it is one, celestar@9895: * promote it as a new train, retaining the unitnumber, orders */ celestar@9895: if (new_f != NULL && IsTrainEngine(new_f)) { celestar@9895: switch_engine = true; celestar@9895: /* Copy important data from the front engine */ celestar@9895: new_f->unitnumber = first->unitnumber; celestar@9895: new_f->current_order = first->current_order; celestar@9895: new_f->cur_order_index = first->cur_order_index; celestar@9895: new_f->orders = first->orders; celestar@9895: new_f->num_orders = first->num_orders; celestar@9895: celestar@9895: if (first->prev_shared != NULL) { celestar@9895: first->prev_shared->next_shared = new_f; celestar@9895: new_f->prev_shared = first->prev_shared; Darkvater@1766: } celestar@9895: celestar@9895: if (first->next_shared != NULL) { celestar@9895: first->next_shared->prev_shared = new_f; celestar@9895: new_f->next_shared = first->next_shared; celestar@9911: } else { celestar@9911: RemoveVehicleFromGroup(v); celestar@9895: } celestar@9895: celestar@9895: /* celestar@9895: * Remove all order information from the front train, to celestar@9895: * prevent the order and the shared order list to be celestar@9895: * destroyed by Destroy/DeleteVehicle. celestar@9895: */ celestar@9895: first->orders = NULL; celestar@9895: first->prev_shared = NULL; celestar@9895: first->next_shared = NULL; celestar@9895: celestar@9895: if (IsLocalPlayer()) ShowTrainViewWindow(new_f); Darkvater@1766: } Darkvater@1766: } Darkvater@1766: Darkvater@1766: /* 3. Delete the requested wagon */ tron@1472: cost -= v->value; Darkvater@1766: if (flags & DC_EXEC) { Darkvater@1766: first = UnlinkWagon(v, first); bjarni@5256: DeleteDepotHighlightOfVehicle(v); Darkvater@1766: DeleteVehicle(v); Darkvater@1766: Darkvater@1766: /* 4 If the second wagon was an engine, update it to front_engine Darkvater@1766: * 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); celestar@9911: UpdateTrainGroupID(first); bjarni@2676: if (IsFrontEngine(first)) { hackykid@1917: InvalidateWindow(WC_VEHICLE_DETAILS, first->index); hackykid@1917: InvalidateWindow(WC_VEHICLE_REFIT, first->index); hackykid@1917: UpdateTrainAcceleration(first); hackykid@1917: } 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 */ bjarni@2676: if (p2 == 2 && HASBIT(ori_subtype, Train_Front)) { tron@6476: Vehicle *tmp; Darkvater@1766: for (v = first; v != NULL; v = tmp) { peter1138@2602: tmp = GetNextVehicle(v); tron@3491: DoCommand(v->tile, v->index | INVALID_VEHICLE << 16, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); 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 */ tron@6476: Vehicle *tmp; Darkvater@1766: for (; 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) { bjarni@2676: cost -= rear->value; bjarni@2676: if (flags & DC_EXEC) { bjarni@2676: first = UnlinkWagon(rear, first); bjarni@5256: DeleteDepotHighlightOfVehicle(rear); bjarni@2676: DeleteVehicle(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: Darkvater@1766: cost -= v->value; Darkvater@1766: if (flags & DC_EXEC) { Darkvater@1766: first = UnlinkWagon(v, first); bjarni@5256: DeleteDepotHighlightOfVehicle(v); Darkvater@1766: DeleteVehicle(v); celestar@9911: RemoveVehicleFromGroup(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); celestar@9911: UpdateTrainGroupID(first); tron@3017: if (IsFrontEngine(first)) UpdateTrainAcceleration(first); bjarni@2676: InvalidateWindow(WC_VEHICLE_DETAILS, first->index); bjarni@2676: InvalidateWindow(WC_VEHICLE_REFIT, first->index); hackykid@1905: } Darkvater@1766: } break; truelight@0: } truelight@0: return cost; truelight@0: } truelight@0: celestar@9911: void Train::UpdateDeltaXY(Direction direction) truelight@0: { celestar@9911: #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]; celestar@9911: this->x_offs = GB(x, 0, 8); celestar@9911: this->y_offs = GB(x, 8, 8); celestar@9911: this->sprite_width = GB(x, 16, 8); celestar@9911: this->sprite_height = GB(x, 24, 8); celestar@9911: this->z_height = 6; truelight@0: } truelight@0: truelight@0: static void UpdateVarsAfterSwap(Vehicle *v) truelight@0: { celestar@9911: v->UpdateDeltaXY(v->direction); truelight@0: v->cur_image = GetTrainImage(v, v->direction); truelight@0: BeginVehicleMove(v); truelight@0: VehiclePositionChanged(v); truelight@0: EndVehicleMove(v); truelight@0: } truelight@0: tron@2639: static 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; tron@3017: if (_patches.vehicle_speed || (old == 0) != (spd == 0)) darkvater@755: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); 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 */ truelight@954: CLRBIT(*swap_flag1, VRF_GOINGUP); truelight@954: CLRBIT(*swap_flag1, VRF_GOINGDOWN); truelight@954: CLRBIT(*swap_flag2, VRF_GOINGUP); truelight@954: CLRBIT(*swap_flag2, VRF_GOINGDOWN); truelight@954: truelight@954: /* Reverse the rail-flags (if needed) */ truelight@954: if (HASBIT(flag1, VRF_GOINGUP)) { truelight@954: SETBIT(*swap_flag2, VRF_GOINGDOWN); truelight@954: } else if (HASBIT(flag1, VRF_GOINGDOWN)) { truelight@954: SETBIT(*swap_flag2, VRF_GOINGUP); truelight@954: } truelight@954: if (HASBIT(flag2, VRF_GOINGUP)) { truelight@954: SETBIT(*swap_flag1, VRF_GOINGDOWN); truelight@954: } else if (HASBIT(flag2, VRF_GOINGDOWN)) { truelight@954: 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 */ tron@2549: for (a = v; l != 0; l--) a = a->next; tron@2549: 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: truelight@744: /* Check if the vehicle is a train and is on the tile we are testing */ truelight@744: static void *TestTrainOnCrossing(Vehicle *v, void *data) truelight@744: { celestar@9895: if (v->tile != *(const TileIndex*)data || v->type != VEH_TRAIN) return NULL; truelight@744: return v; truelight@744: } truelight@744: dominik@1103: static void DisableTrainCrossing(TileIndex tile) dominik@1103: { Darkvater@3560: if (IsLevelCrossingTile(tile) && tron@2639: VehicleFromPos(tile, &tile, TestTrainOnCrossing) == NULL && // empty? celestar@3322: IsCrossingBarred(tile)) { celestar@3322: UnbarCrossing(tile); tron@2639: MarkTileDirtyByTile(tile); dominik@1103: } dominik@1103: } dominik@1103: hackykid@1922: /** hackykid@1922: * Advances wagons for train reversing, needed for variable length wagons. hackykid@1922: * Needs to be called once before the train is reversed, and once after it. hackykid@1922: * @param v First vehicle in chain hackykid@1922: * @param before Set to true for the call before reversing, false otherwise hackykid@1922: */ hackykid@1922: static void AdvanceWagons(Vehicle *v, bool before) hackykid@1922: { tron@6476: Vehicle *base = v; tron@6476: Vehicle *first = base->next; tron@6476: uint length = CountVehiclesInChain(v); hackykid@1922: hackykid@1922: while (length > 2) { celestar@9908: /* find pairwise matching wagon celestar@9908: * start<>end, start+1<>end-1, ... */ tron@6476: Vehicle *last = first; tron@6476: for (uint i = length - 3; i > 0; i--) last = last->next; tron@6476: tron@6476: int differential = last->u.rail.cached_veh_length - base->u.rail.cached_veh_length; tron@2639: if (before) differential *= -1; hackykid@1922: hackykid@1922: if (differential > 0) { celestar@9908: /* disconnect last car to make sure only this subset moves */ tron@6476: Vehicle *tempnext = last->next; hackykid@1922: last->next = NULL; hackykid@1922: tron@6476: for (int i = 0; i < differential; i++) TrainController(first, false); hackykid@1922: hackykid@1922: last->next = tempnext; hackykid@1922: } hackykid@1922: hackykid@1922: base = first; hackykid@1922: first = first->next; hackykid@1922: length -= 2; hackykid@1922: } hackykid@1922: } hackykid@1922: tron@2951: truelight@0: static void ReverseTrainDirection(Vehicle *v) truelight@0: { bjarni@4739: if (IsTileDepotType(v->tile, TRANSPORT_RAIL)) { bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); bjarni@4739: } truelight@0: truelight@743: /* Check if we were approaching a rail/road-crossing */ truelight@743: { truelight@743: TileIndex tile = v->tile; tron@3163: DiagDirection dir = DirToDiagDir(v->direction); tron@3163: matthijs@1459: /* Determine the diagonal direction in which we will exit this tile */ tron@3163: if (!(v->direction & 1) && v->u.rail.track != _state_dir_table[dir]) { tron@3163: dir = ChangeDiagDir(dir, DIAGDIRDIFF_90LEFT); truelight@743: } truelight@743: /* Calculate next tile */ Darkvater@4559: tile += TileOffsByDiagDir(dir); dominik@1103: dominik@1103: /* Check if the train left a rail/road-crossing */ dominik@1103: DisableTrainCrossing(tile); truelight@743: } truelight@743: celestar@9908: /* count number of vehicles */ tron@6476: int r = -1; tron@6476: const Vehicle *u = v; truelight@0: do r++; while ( (u = u->next) != NULL ); truelight@0: hackykid@1922: AdvanceWagons(v, true); 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: hackykid@1922: AdvanceWagons(v, false); hackykid@1922: bjarni@4739: if (IsTileDepotType(v->tile, TRANSPORT_RAIL)) { bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); bjarni@4739: } truelight@0: truelight@954: CLRBIT(v->u.rail.flags, VRF_REVERSING); truelight@0: } truelight@0: Darkvater@1784: /** Reverse train. tron@3491: * @param tile unused celestar@9908: * @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: */ tron@3491: int32 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: celestar@9895: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: bjarni@3256: if (p2) { celestar@9908: /* turn a single unit around */ bjarni@3256: peter1138@3956: if (IsMultiheaded(v) || HASBIT(EngInfo(v->engine_type)->callbackmask, CBM_ARTIC_ENGINE)) { bjarni@3256: return_cmd_error(STR_ONLY_TURN_SINGLE_UNIT); bjarni@3256: } bjarni@3256: tron@6476: Vehicle *front = GetFirstVehicleInChain(v); celestar@9908: /* 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) { bjarni@3259: 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 { celestar@9908: /*turn the whole train around */ bjarni@3257: if (v->u.rail.crash_anim_pos != 0 || v->breakdown_ctr != 0) return CMD_ERROR; bjarni@3257: bjarni@3257: if (flags & DC_EXEC) { bjarni@3256: if (_patches.realistic_acceleration && v->cur_speed != 0) { bjarni@3256: 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: } truelight@0: return 0; truelight@0: } truelight@0: Darkvater@1784: /** Force a train through a red signal tron@3491: * @param tile unused celestar@9908: * @param flags type of operation Darkvater@1784: * @param p1 train to ignore the red signal Darkvater@1784: * @param p2 unused Darkvater@1784: */ tron@3491: int32 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: celestar@9895: 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: truelight@0: return 0; truelight@0: } truelight@0: Darkvater@1802: /** Refits a train to the specified cargo type. tron@3491: * @param tile unused celestar@9908: * @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 celestar@9911: * - p2 = (bit 16) - refit only this vehicle celestar@9911: * @return cost of refit or error Darkvater@1802: */ tron@3491: int32 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); celestar@9911: 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: celestar@9895: 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); Darkvater@1802: Darkvater@1802: /* Check cargo */ celestar@9895: if (new_cid >= NUM_CARGO) return CMD_ERROR; truelight@0: bjarni@1237: SET_EXPENSES_TYPE(EXPENSES_TRAIN_RUN); bjarni@1237: tron@6476: int32 cost = 0; 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: peter1138@3956: if (HASBIT(EngInfo(v->engine_type)->callbackmask, CBM_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) { bjarni@4544: cost += GetRefitCost(v->engine_type); peter1138@4242: } peter1138@4242: hackykid@1883: num += amount; hackykid@1883: if (flags & DC_EXEC) { peter1138@3955: v->cargo_count = (v->cargo_type == new_cid) ? min(amount, v->cargo_count) : 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); peter1138@3954: RebuildVehicleLists(); hackykid@1883: } truelight@0: } truelight@193: } celestar@9911: } 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 */ glx@5303: if (flags & DC_EXEC) TrainConsistChanged(GetFirstVehicleInChain(GetVehicle(p1))); peter1138@4708: truelight@0: return cost; truelight@0: } truelight@0: celestar@9895: 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; celestar@9895: }; 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: celestar@9908: /** returns the tile of a depot to goto to. The given vehicle must not be celestar@9908: * 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; darkvater@308: tfdd.best_length = (uint)-1; matthijs@1777: tfdd.reverse = false; darkvater@308: tron@6476: TileIndex tile = v->tile; tron@6476: if (IsTileDepotType(tile, TRANSPORT_RAIL)) { darkvater@308: tfdd.tile = tile; darkvater@308: tfdd.best_length = 0; darkvater@308: return tfdd; darkvater@308: } truelight@0: KUDr@3900: if (_patches.yapf.rail_use_yapf) { KUDr@3900: bool found = YapfFindNearestRailDepotTwoWay(v, max_distance, NPF_INFINITE_PENALTY, &tfdd.tile, &tfdd.reverse); KUDr@3900: tfdd.best_length = found ? max_distance / 2 : -1; // some fake distance or NOT_FOUND KUDr@3900: } else if (_patches.new_pathfinding_all) { matthijs@1777: Vehicle* last = GetLastVehicleInChain(v); matthijs@1942: Trackdir trackdir = GetVehicleTrackdir(v); matthijs@1942: Trackdir trackdir_rev = ReverseTrackdir(GetVehicleTrackdir(last)); matthijs@1942: tron@6476: assert(trackdir != INVALID_TRACKDIR); tron@6476: NPFFoundTargetData ftd = NPFRouteToDepotBreadthFirstTwoWay(v->tile, trackdir, last->tile, trackdir_rev, TRANSPORT_RAIL, v->owner, v->u.rail.compatible_railtypes, NPF_INFINITE_PENALTY); matthijs@1247: if (ftd.best_bird_dist == 0) { matthijs@1247: /* Found target */ matthijs@1247: tfdd.tile = ftd.node.tile; matthijs@1675: /* Our caller expects a number of tiles, so we just approximate that rubidium@4549: * number by this. It might not be completely what we want, but it will rubidium@4549: * work for now :-) We can possibly change this when the old pathfinder rubidium@4549: * is removed. */ matthijs@1777: tfdd.best_length = ftd.best_path_dist / NPF_TILE_LENGTH; tron@3017: if (NPFGetFlag(&ftd.node, NPF_FLAG_REVERSE)) tfdd.reverse = true; matthijs@1247: } truelight@0: } else { celestar@9908: /* search in the forward direction first. */ tron@6476: DiagDirection i = DirToDiagDir(v->direction); tron@3163: if (!(v->direction & 1) && v->u.rail.track != _state_dir_table[i]) { tron@3163: i = ChangeDiagDir(i, DIAGDIRDIFF_90LEFT); tron@3163: } celestar@3355: NewTrainPathfind(tile, 0, v->u.rail.compatible_railtypes, i, (NTPEnumProc*)NtpCallbFindDepot, &tfdd); darkvater@308: if (tfdd.best_length == (uint)-1){ matthijs@1777: tfdd.reverse = true; celestar@9908: /* search in backwards direction */ tron@3153: i = ReverseDiagDir(DirToDiagDir(v->direction)); tron@3163: if (!(v->direction & 1) && v->u.rail.track != _state_dir_table[i]) { tron@3163: i = ChangeDiagDir(i, DIAGDIRDIFF_90LEFT); tron@3163: } celestar@3355: NewTrainPathfind(tile, 0, v->u.rail.compatible_railtypes, i, (NTPEnumProc*)NtpCallbFindDepot, &tfdd); darkvater@308: } truelight@0: } truelight@193: darkvater@308: return tfdd; truelight@0: } truelight@0: Darkvater@1784: /** Send a train to a depot tron@3491: * @param tile unused celestar@9908: * @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: */ tron@3491: int32 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; celestar@9895: 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: celestar@9895: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; bjarni@4506: bjarni@4506: if (v->vehstatus & VS_CRASHED) return CMD_ERROR; matthijs@1757: tron@555: if (v->current_order.type == OT_GOTO_DEPOT) { bjarni@4519: if (!!(p2 & DEPOT_SERVICE) == HASBIT(v->current_order.flags, OFB_HALT_IN_DEPOT)) { bjarni@4510: /* We called with a different DEPOT_SERVICE setting. rubidium@4549: * Now we change the setting to apply the new one and let the vehicle head for the same depot. rubidium@4549: * Note: the if is (true for requesting service == true for ordered to stop in depot) */ bjarni@4510: if (flags & DC_EXEC) { bjarni@4510: TOGGLEBIT(v->current_order.flags, OFB_HALT_IN_DEPOT); bjarni@4510: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); bjarni@4510: } bjarni@4510: return 0; bjarni@4510: } bjarni@4510: bjarni@4506: if (p2 & DEPOT_DONT_CANCEL) return CMD_ERROR; // Requested no cancelation of depot orders truelight@0: if (flags & DC_EXEC) { pasky@1615: if (HASBIT(v->current_order.flags, OFB_PART_OF_ORDERS)) { truelight@0: v->cur_order_index++; truelight@0: } truelight@193: tron@555: v->current_order.type = OT_DUMMY; tron@555: v->current_order.flags = 0; darkvater@755: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); truelight@0: } truelight@0: return 0; truelight@0: } truelight@0: bjarni@4526: /* check if at a standstill (not stopped only) in a depot bjarni@4526: * the check is down here to make it possible to alter stop/service for trains entering the depot */ bjarni@4526: if (IsTileDepotType(v->tile, TRANSPORT_RAIL) && v->cur_speed == 0) return CMD_ERROR; bjarni@4526: tron@6476: TrainFindDepotData tfdd = FindClosestTrainDepot(v, 0); bjarni@4506: if (tfdd.best_length == (uint)-1) return_cmd_error(STR_883A_UNABLE_TO_FIND_ROUTE_TO); truelight@0: truelight@0: if (flags & DC_EXEC) { celestar@9910: if (v->current_order.type == OT_LOADING) v->LeaveStation(); celestar@9910: darkvater@308: v->dest_tile = tfdd.tile; tron@555: v->current_order.type = OT_GOTO_DEPOT; bjarni@4412: v->current_order.flags = OF_NON_STOP; bjarni@4506: if (!(p2 & DEPOT_SERVICE)) SETBIT(v->current_order.flags, OFB_HALT_IN_DEPOT); tron@4527: v->current_order.dest = GetDepotByTile(tfdd.tile)->index; bjarni@5259: v->current_order.refit_cargo = CT_INVALID; darkvater@755: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); matthijs@1777: /* If there is no depot in front, reverse automatically */ celestar@9910: if (tfdd.reverse) DoCommand(v->tile, v->index, 0, DC_EXEC, CMD_REVERSE_TRAIN_DIRECTION); truelight@0: } truelight@193: truelight@0: return 0; truelight@0: } truelight@0: truelight@0: celestar@9895: 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: tron@2630: static void HandleLocomotiveSmokeCloud(const Vehicle* v) truelight@0: { peter1138@4656: bool sound = false; truelight@0: truelight@0: if (v->vehstatus & VS_TRAIN_SLOWING || v->load_unload_time_rem != 0 || v->cur_speed < 2) truelight@0: return; truelight@0: tron@6476: 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); peter1138@2595: bool disable_effect = HASBIT(v->u.rail.cached_vis_effect, 6); truelight@0: celestar@9908: /* 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: celestar@9908: /* No smoke in depots or tunnels */ celestar@3590: if (IsTileDepotType(v->tile, TRANSPORT_RAIL) || IsTunnelTile(v->tile)) continue; celestar@3590: celestar@9908: /* 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) { celestar@9908: /* 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: bjarni@3256: 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) { truelight@0: case 0: celestar@9908: /* steam smoke. */ peter1138@2612: if (GB(v->tick_counter, 0, 4) == 0) { peter1138@2595: CreateEffectVehicleRel(v, x, y, 10, EV_STEAM_SMOKE); peter1138@4656: sound = true; truelight@0: } truelight@0: break; truelight@0: truelight@0: case 1: celestar@9908: /* diesel smoke */ tron@2637: if (u->cur_speed <= 40 && CHANCE16(15, 128)) { tron@1359: CreateEffectVehicleRel(v, 0, 0, 10, EV_DIESEL_SMOKE); peter1138@4656: sound = true; truelight@0: } truelight@0: break; truelight@0: truelight@0: case 2: celestar@9908: /* blue spark */ tron@2637: if (GB(v->tick_counter, 0, 2) == 0 && CHANCE16(1, 45)) { tron@1359: CreateEffectVehicleRel(v, 0, 0, 10, EV_ELECTRIC_SPARK); peter1138@4656: sound = true; truelight@0: } truelight@0: break; truelight@0: } tron@2637: } while ((v = v->next) != NULL); peter1138@4656: peter1138@4656: if (sound) PlayVehicleSound(u, VSE_TRAIN_EFFECT); truelight@0: } truelight@0: tron@2549: static void TrainPlayLeaveStationSound(const Vehicle* v) truelight@0: { tron@541: static const SoundFx sfx[] = { tron@541: SND_04_TRAIN, tron@541: SND_0A_TRAIN_HORN, celestar@9911: SND_0A_TRAIN_HORN, celestar@9911: SND_47_MAGLEV_2, celestar@9911: SND_41_MAGLEV tron@541: }; tron@541: tron@6476: if (PlayVehicleSound(v, VSE_START)) return; tron@6476: tron@2477: EngineID engtype = v->engine_type; celestar@9911: SndPlayVehicleFx(sfx[RailVehInfo(engtype)->engclass], v); celestar@9911: } celestar@9911: celestar@9911: void Train::PlayLeaveStationSound() const celestar@9911: { celestar@9911: TrainPlayLeaveStationSound(this); truelight@0: } truelight@0: truelight@0: static bool CheckTrainStayInDepot(Vehicle *v) truelight@0: { celestar@9908: /* bail out if not all wagons are in the same depot or not in a depot at all */ tron@6476: 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: celestar@9908: /* 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: tron@3172: if (UpdateSignalsOnSegment(v->tile, DirToDiagDir(v->direction))) { 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); truelight@0: TrainPlayLeaveStationSound(v); 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: celestar@9911: v->UpdateDeltaXY(v->direction); truelight@0: v->cur_image = GetTrainImage(v, v->direction); truelight@0: VehiclePositionChanged(v); tron@3172: UpdateSignalsOnSegment(v->tile, DirToDiagDir(v->direction)); truelight@0: UpdateTrainAcceleration(v); bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); truelight@0: truelight@0: return false; truelight@0: } truelight@0: matthijs@1247: /* Check for station tiles */ celestar@9895: struct TrainTrackFollowerData { truelight@0: TileIndex dest_coords; celestar@1551: 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; celestar@9895: }; truelight@0: rubidium@5838: static bool NtpCallbFindStation(TileIndex tile, TrainTrackFollowerData *ttfd, Trackdir track, uint length) tron@1977: { celestar@9908: /* heading for nowhere? */ tron@2951: if (ttfd->dest_coords == 0) return false; truelight@0: celestar@9908: /* 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 */ celestar@9908: celestar@9908: /* found station */ ludde@2125: ttfd->best_track = track; celestar@9895: ttfd->best_bird_dist = 0; truelight@0: return true; truelight@0: } else { celestar@9908: /* 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: tron@2630: static void FillWithStationData(TrainTrackFollowerData* fd, const Vehicle* v) truelight@0: { tron@2639: fd->dest_coords = v->dest_tile; tron@2639: if (v->current_order.type == OT_GOTO_STATION) { tron@4527: fd->station_index = v->current_order.dest; tron@2639: } else { tron@2639: fd->station_index = INVALID_STATION; tron@2639: } 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 uint32 _reachable_tracks[4] = { truelight@0: 0x10091009, truelight@0: 0x00160016, truelight@0: 0x05200520, truelight@0: 0x2A002A00, truelight@0: }; truelight@0: truelight@0: static const byte _search_directions[6][4] = { celestar@9908: { 0, 9, 2, 9 }, ///< track 1 celestar@9908: { 9, 1, 9, 3 }, ///< track 2 celestar@9908: { 9, 0, 3, 9 }, ///< track upper celestar@9908: { 1, 9, 9, 2 }, ///< track lower celestar@9908: { 3, 2, 9, 9 }, ///< track left celestar@9908: { 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 */ rubidium@5838: static Track ChooseTrainTrack(Vehicle* v, TileIndex tile, DiagDirection enterdir, TrackBits tracks) truelight@193: { rubidium@5838: Track best_track; celestar@9908: /* 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: rubidium@5838: assert((tracks & ~0x3F) == 0); truelight@0: matthijs@1247: /* quick return in case only one possible track is available */ rubidium@5838: if (KILL_FIRST_BIT(tracks) == 0) return FindFirstTrack(tracks); truelight@0: KUDr@3900: if (_patches.yapf.rail_use_yapf) { rubidium@5838: Trackdir trackdir = YapfChooseRailTrack(v, tile, enterdir, tracks, &path_not_found); KUDr@3900: if (trackdir != INVALID_TRACKDIR) { KUDr@3900: best_track = TrackdirToTrack(trackdir); KUDr@3900: } else { rubidium@5838: best_track = FindFirstTrack(tracks); KUDr@3900: } KUDr@3900: } else if (_patches.new_pathfinding_all) { /* Use a new pathfinding for everything */ KUDr@3900: void* perf = NpfBeginInterval(); KUDr@3900: matthijs@1247: NPFFindStationOrTileData fstd; matthijs@1247: NPFFillWithOrderData(&fstd, v); matthijs@1247: /* The enterdir for the new tile, is the exitdir for the old tile */ tron@6476: Trackdir trackdir = GetVehicleTrackdir(v); matthijs@1247: assert(trackdir != 0xff); matthijs@1247: tron@6476: NPFFoundTargetData ftd = NPFRouteToStationOrTile(tile - TileOffsByDiagDir(enterdir), trackdir, &fstd, TRANSPORT_RAIL, v->owner, v->u.rail.compatible_railtypes); matthijs@1698: matthijs@1698: if (ftd.best_trackdir == 0xff) { celestar@9908: /* We are already at our target. Just do something celestar@9908: * @todo maybe display error? celestar@9908: * @todo: go straight ahead if possible? */ rubidium@5838: best_track = FindFirstTrack(tracks); truelight@0: } else { matthijs@1698: /* If ftd.best_bird_dist is 0, we found our target and ftd.best_trackdir contains matthijs@1698: the direction we need to take to get there, if ftd.best_bird_dist is not 0, matthijs@1698: we did not find our target, but ftd.best_trackdir contains the direction leading matthijs@1698: to the tile closest to our target. */ KUDr@4870: if (ftd.best_bird_dist != 0) path_not_found = true; matthijs@1247: /* Discard enterdir information, making it a normal track */ hackykid@2008: best_track = TrackdirToTrack(ftd.best_trackdir); truelight@0: } KUDr@3900: tron@6476: int time = NpfEndInterval(perf); Darkvater@5568: DEBUG(yapf, 4, "[NPFT] %d us - %d rounds - %d open - %d closed -- ", time, 0, _aystar_stats_open_size, _aystar_stats_closed_size); truelight@0: } else { KUDr@3900: void* perf = NpfBeginInterval(); tron@6476: tron@6476: TrainTrackFollowerData fd; matthijs@1247: FillWithStationData(&fd, v); matthijs@1247: ludde@2044: /* New train pathfinding */ ludde@2044: fd.best_bird_dist = (uint)-1; ludde@2044: fd.best_track_dist = (uint)-1; rubidium@5838: fd.best_track = INVALID_TRACKDIR; ludde@2044: Darkvater@4559: NewTrainPathfind(tile - TileOffsByDiagDir(enterdir), v->dest_tile, celestar@3355: v->u.rail.compatible_railtypes, enterdir, (NTPEnumProc*)NtpCallbFindStation, &fd); ludde@2044: celestar@9908: /* check whether the path was found or only 'guessed' */ KUDr@4870: if (fd.best_bird_dist != 0) path_not_found = true; KUDr@4870: ludde@2044: if (fd.best_track == 0xff) { celestar@9908: /* blaha */ rubidium@5838: best_track = FindFirstTrack(tracks); matthijs@1247: } else { rubidium@5838: best_track = TrackdirToTrack(fd.best_track); matthijs@1247: } KUDr@3900: tron@6476: int time = NpfEndInterval(perf); Darkvater@5568: DEBUG(yapf, 4, "[NTPT] %d us - %d rounds - %d open - %d closed -- ", time, 0, 0, 0); truelight@0: } celestar@9908: /* handle "path not found" state */ KUDr@4870: if (path_not_found) { celestar@9908: /* PF didn't find the route */ KUDr@4870: if (!HASBIT(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) { celestar@9908: /* it is first time the problem occurred, set the "path not found" flag */ KUDr@4870: SETBIT(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION); celestar@9908: /* and notify user about the event */ KUDr@4996: if (_patches.lost_train_warn && v->owner == _local_player) { KUDr@4870: SetDParam(0, v->unitnumber); KUDr@4870: AddNewsItem( KUDr@4870: STR_TRAIN_IS_LOST, KUDr@4870: NEWS_FLAGS(NM_SMALL, NF_VIEWPORT|NF_VEHICLE, NT_ADVICE, 0), KUDr@4870: v->index, KUDr@4870: 0); KUDr@4870: } KUDr@4870: } KUDr@4870: } else { celestar@9908: /* route found, is the train marked with "path not found" flag? */ KUDr@4870: if (HASBIT(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) { celestar@9908: /* clear the flag as the PF's problem was solved */ KUDr@4870: CLRBIT(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION); celestar@9908: /* 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: { truelight@0: if (_opt.diff.line_reverse_mode != 0 || rubidium@6319: v->u.rail.track == TRACK_BIT_DEPOT || v->u.rail.track == TRACK_BIT_WORMHOLE || truelight@0: !(v->direction & 1)) truelight@0: return false; 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: tron@6476: int i = _search_directions[FIND_FIRST_BIT(v->u.rail.track)][DirToDiagDir(v->direction)]; truelight@0: KUDr@3900: if (_patches.yapf.rail_use_yapf) { KUDr@3900: reverse_best = YapfCheckReverseTrain(v); KUDr@3900: } else if (_patches.new_pathfinding_all) { /* Use a new pathfinding for everything */ matthijs@1247: NPFFindStationOrTileData fstd; matthijs@1247: NPFFoundTargetData ftd; rubidium@5838: Trackdir trackdir, trackdir_rev; matthijs@1247: Vehicle* last = GetLastVehicleInChain(v); matthijs@1247: matthijs@1247: NPFFillWithOrderData(&fstd, v); matthijs@1247: matthijs@1752: trackdir = GetVehicleTrackdir(v); matthijs@1942: trackdir_rev = ReverseTrackdir(GetVehicleTrackdir(last)); matthijs@1247: assert(trackdir != 0xff); matthijs@1247: assert(trackdir_rev != 0xff); matthijs@1247: celestar@3355: ftd = NPFRouteToStationOrTileTwoWay(v->tile, trackdir, last->tile, trackdir_rev, &fstd, TRANSPORT_RAIL, v->owner, v->u.rail.compatible_railtypes); matthijs@1247: if (ftd.best_bird_dist != 0) { matthijs@1247: /* We didn't find anything, just keep on going straight ahead */ matthijs@1247: reverse_best = false; matthijs@1247: } else { tron@3017: if (NPFGetFlag(&ftd.node, NPF_FLAG_REVERSE)) { matthijs@1247: reverse_best = true; tron@3017: } else { matthijs@1247: reverse_best = false; tron@3017: } matthijs@1247: } matthijs@1247: } else { tron@6476: int best_track = -1; tron@6476: uint reverse = 0; tron@6476: uint best_bird_dist = 0; tron@6476: uint best_track_dist = 0; tron@6476: tron@2952: for (;;) { matthijs@1247: fd.best_bird_dist = (uint)-1; matthijs@1247: fd.best_track_dist = (uint)-1; matthijs@1247: rubidium@5838: NewTrainPathfind(v->tile, v->dest_tile, v->u.rail.compatible_railtypes, (DiagDirection)(reverse ^ i), (NTPEnumProc*)NtpCallbFindStation, &fd); matthijs@1247: matthijs@1247: if (best_track != -1) { matthijs@1247: if (best_bird_dist != 0) { matthijs@1247: if (fd.best_bird_dist != 0) { matthijs@1247: /* neither reached the destination, pick the one with the smallest bird dist */ matthijs@1247: if (fd.best_bird_dist > best_bird_dist) goto bad; matthijs@1247: if (fd.best_bird_dist < best_bird_dist) goto good; matthijs@1247: } else { matthijs@1247: /* we found the destination for the first time */ matthijs@1247: goto good; matthijs@1247: } truelight@0: } else { matthijs@1247: if (fd.best_bird_dist != 0) { matthijs@1247: /* didn't find destination, but we've found the destination previously */ matthijs@1247: goto bad; matthijs@1247: } else { matthijs@1247: /* both old & new reached the destination, compare track length */ matthijs@1247: if (fd.best_track_dist > best_track_dist) goto bad; matthijs@1247: if (fd.best_track_dist < best_track_dist) goto good; matthijs@1247: } truelight@0: } matthijs@1247: matthijs@1247: /* if we reach this position, there's two paths of equal value so far. matthijs@1247: * pick one randomly. */ tron@6476: int r = GB(Random(), 0, 8); matthijs@1247: if (_pick_track_table[i] == (v->direction & 3)) r += 80; matthijs@1247: if (_pick_track_table[best_track] == (v->direction & 3)) r -= 80; matthijs@1247: if (r <= 127) goto bad; truelight@0: } matthijs@1247: good:; matthijs@1247: best_track = i; matthijs@1247: best_bird_dist = fd.best_bird_dist; matthijs@1247: best_track_dist = fd.best_track_dist; matthijs@1247: reverse_best = reverse; matthijs@1247: bad:; tron@3017: if (reverse != 0) break; matthijs@1247: reverse = 2; truelight@0: } truelight@0: } truelight@0: truelight@0: return reverse_best != 0; truelight@0: } truelight@0: truelight@0: static bool ProcessTrainOrder(Vehicle *v) truelight@0: { tron@3005: switch (v->current_order.type) { tron@3005: case OT_GOTO_DEPOT: tron@3005: if (!(v->current_order.flags & OF_PART_OF_ORDERS)) return false; tron@3005: if ((v->current_order.flags & OF_SERVICE_IF_NEEDED) && tron@3005: !VehicleNeedsService(v)) { tron@3005: v->cur_order_index++; tron@3005: } tron@3005: break; tron@3005: tron@3005: case OT_LOADING: tron@3005: case OT_LEAVESTATION: truelight@0: return false; truelight@4351: truelight@4351: default: break; truelight@0: } truelight@0: celestar@9908: /* check if we've reached the waypoint? */ tron@6476: bool at_waypoint = false; tron@555: if (v->current_order.type == OT_GOTO_WAYPOINT && v->tile == v->dest_tile) { truelight@0: v->cur_order_index++; peter1138@3934: at_waypoint = true; truelight@0: } truelight@0: celestar@9908: /* check if we've reached a non-stop station while TTDPatch nonstop is enabled.. */ tron@2549: if (_patches.new_nonstop && tron@2549: v->current_order.flags & OF_NON_STOP && tron@2549: IsTileType(v->tile, MP_STATION) && tron@4527: v->current_order.dest == GetStationIndex(v->tile)) { truelight@193: v->cur_order_index++; truelight@0: } truelight@0: celestar@9908: /* Get the current order */ tron@2639: if (v->cur_order_index >= v->num_orders) v->cur_order_index = 0; truelight@1024: tron@6476: const Order *order = GetVehicleOrder(v, v->cur_order_index); truelight@0: celestar@9908: /* If no order, do nothing. */ truelight@1024: if (order == NULL) { celestar@9895: v->current_order.Free(); truelight@0: v->dest_tile = 0; truelight@0: return false; truelight@0: } truelight@0: celestar@9908: /* If it is unchanged, keep it. */ tron@4527: if (order->type == v->current_order.type && tron@4527: order->flags == v->current_order.flags && tron@4527: order->dest == v->current_order.dest) truelight@0: return false; truelight@0: celestar@9908: /* Otherwise set it, and determine the destination tile. */ truelight@1024: v->current_order = *order; truelight@0: truelight@0: v->dest_tile = 0; truelight@0: peter1138@3934: InvalidateVehicleOrder(v); peter1138@3934: truelight@1024: switch (order->type) { truelight@1024: case OT_GOTO_STATION: tron@4527: if (order->dest == v->last_station_visited) truelight@1266: v->last_station_visited = INVALID_STATION; tron@4527: v->dest_tile = GetStation(order->dest)->xy; truelight@1024: break; truelight@1024: truelight@1024: case OT_GOTO_DEPOT: tron@4527: v->dest_tile = GetDepot(order->dest)->xy; truelight@1024: break; truelight@1024: truelight@1024: case OT_GOTO_WAYPOINT: tron@4527: v->dest_tile = GetWaypoint(order->dest)->xy; truelight@1024: break; peter1138@3934: peter1138@3934: default: peter1138@3934: return false; truelight@0: } truelight@0: peter1138@3934: return !at_waypoint && CheckReverseTrain(v); truelight@0: } truelight@0: celestar@9911: void Train::MarkDirty() truelight@0: { celestar@9911: Vehicle *v = this; truelight@0: do { truelight@0: v->cur_image = GetTrainImage(v, v->direction); truelight@0: MarkAllViewportsDirty(v->left_coord, v->top_coord, v->right_coord + 1, v->bottom_coord + 1); tron@2639: } while ((v = v->next) != NULL); celestar@9911: celestar@9911: /* need to update acceleration and cached values since the goods on the train changed. */ celestar@9911: TrainCargoChanged(this); celestar@9911: UpdateTrainAcceleration(this); truelight@0: } truelight@0: truelight@0: static int UpdateTrainSpeed(Vehicle *v) truelight@0: { truelight@0: uint accel; truelight@0: truelight@954: if (v->vehstatus & VS_STOPPED || HASBIT(v->u.rail.flags, VRF_REVERSING)) { tron@2639: if (_patches.realistic_acceleration) { celestar@1179: accel = GetTrainAcceleration(v, AM_BRAKE) * 2; tron@2639: } else { celestar@1179: accel = v->acceleration * -2; tron@2639: } truelight@0: } else { tron@2639: if (_patches.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; celestar@1179: 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); tron@6476: uint32 flags = v->owner == _local_player ? tron@6476: NEWS_FLAGS(NM_THIN, NF_VIEWPORT | NF_VEHICLE, NT_ARRIVAL_PLAYER, 0) : tron@6476: NEWS_FLAGS(NM_THIN, NF_VIEWPORT | NF_VEHICLE, NT_ARRIVAL_OTHER, 0); truelight@0: AddNewsItem( truelight@0: STR_8801_CITIZENS_CELEBRATE_FIRST, truelight@0: flags, truelight@0: v->index, tron@3017: 0 tron@3017: ); truelight@0: } truelight@0: celestar@9911: v->BeginLoading(); tron@4527: v->current_order.dest = 0; truelight@0: } truelight@0: truelight@954: static byte AfterSetTrainPos(Vehicle *v, bool new_tile) truelight@0: { celestar@9908: /* need this hint so it returns the right z coordinate on bridges. */ tron@6476: byte new_z = GetSlopeZ(v->x_pos, v->y_pos); tron@6476: tron@6476: byte old_z = v->z_pos; truelight@0: v->z_pos = new_z; truelight@0: truelight@954: if (new_tile) { truelight@954: CLRBIT(v->u.rail.flags, VRF_GOINGUP); truelight@954: CLRBIT(v->u.rail.flags, VRF_GOINGDOWN); truelight@954: truelight@954: if (new_z != old_z) { tron@1980: TileIndex tile = TileVirtXY(v->x_pos, v->y_pos); tron@1683: celestar@9908: /* XXX workaround, whole UP/DOWN detection needs overhaul */ tron@3184: if (!IsTunnelTile(tile)) { tron@1683: SETBIT(v->u.rail.flags, (new_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: tron@3153: static 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: tron@3153: static Direction GetNewVehicleDirection(const Vehicle *v, int x, int y) truelight@0: { truelight@0: uint offs = (y - v->y_pos + 1) * 4 + (x - v->x_pos + 1); truelight@0: assert(offs < 11); truelight@0: return _new_vehicle_direction_table[offs]; truelight@0: } truelight@0: Darkvater@2436: static 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 */ tron@1048: static bool CheckCompatibleRail(const Vehicle *v, TileIndex tile) truelight@0: { tron@1048: return tron@2549: IsTileOwner(tile, v->owner) && ( bjarni@2676: !IsFrontEngine(v) || rubidium@6498: HASBIT(v->u.rail.compatible_railtypes, GetRailType(tile)) tron@2549: ); truelight@0: } truelight@0: celestar@9895: 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 celestar@9895: }; truelight@0: celestar@3355: static const RailtypeSlowdownParams _railtype_slowdown[] = { truelight@0: // normal accel celestar@9908: {256 / 4, 256 / 2, 256 / 4, 2}, ///< normal celestar@9908: {256 / 4, 256 / 2, 256 / 4, 2}, ///< electrified celestar@9908: {256 / 4, 256 / 2, 256 / 4, 2}, ///< monorail celestar@9908: {0, 256 / 2, 256 / 4, 2}, ///< maglev truelight@0: }; truelight@0: celestar@9908: /** Modify the speed of the vehicle due to a turn */ tron@3157: static void AffectSpeedByDirChange(Vehicle* v, Direction new_dir) truelight@0: { tron@3158: if (_patches.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: celestar@9908: /** Modify the speed of the vehicle due to a change in altitude */ truelight@0: static void AffectSpeedByZChange(Vehicle *v, byte old_z) truelight@0: { tron@2639: if (old_z == v->z_pos || _patches.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@3172: static const DiagDirection _otherside_signal_directions[] = { rubidium@5838: DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_SE, INVALID_DIAGDIR, INVALID_DIAGDIR, tron@3172: DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NE 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) { tron@3269: uint i = FindFirstBit2x64(GetTrackBits(tile) * 0x101 & _reachable_tracks[dir]); truelight@0: UpdateSignalsOnSegment(tile, _otherside_signal_directions[i]); truelight@0: } truelight@0: } truelight@0: truelight@0: celestar@9895: struct TrainCollideChecker { tron@1434: const Vehicle *v; tron@1434: const Vehicle *v_skip; celestar@9895: }; truelight@0: tron@1434: static void *FindTrainCollideEnum(Vehicle *v, void *data) truelight@0: { rubidium@5838: const TrainCollideChecker* tcc = (TrainCollideChecker*)data; tron@1434: tron@1434: if (v != tcc->v && tron@1434: v != tcc->v_skip && celestar@9895: v->type == VEH_TRAIN && rubidium@6319: v->u.rail.track != TRACK_BIT_DEPOT && tron@1434: myabs(v->z_pos - tcc->v->z_pos) <= 6 && tron@1434: myabs(v->x_pos - tcc->v->x_pos) < 6 && tron@1434: myabs(v->y_pos - tcc->v->y_pos) < 6) { tron@1434: return v; tron@1434: } else { tron@1434: return NULL; tron@1434: } 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: truelight@0: v->u.rail.crash_anim_pos++; truelight@193: tron@6476: Vehicle *u = v; truelight@0: BEGIN_ENUM_WAGONS(v) truelight@0: v->vehstatus |= VS_CRASHED; truelight@0: END_ENUM_WAGONS(v) truelight@0: darkvater@755: InvalidateWindowWidget(WC_VEHICLE_VIEW, u->index, STATUS_BAR); truelight@0: } truelight@0: tron@2549: static uint CountPassengersInTrain(const Vehicle* v) truelight@0: { tron@2549: uint num = 0; truelight@0: BEGIN_ENUM_WAGONS(v) celestar@9896: if (IsCargoInClass(v->cargo_type, CC_PASSENGERS)) num += v->cargo_count; truelight@0: END_ENUM_WAGONS(v) truelight@0: return num; truelight@0: } truelight@0: celestar@9908: /** 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; truelight@0: tcc.v_skip = v->next; truelight@0: truelight@0: /* find colliding vehicle */ tron@6476: Vehicle *realcoll = (Vehicle*)VehicleFromPos(TileVirtXY(v->x_pos, v->y_pos), &tcc, FindTrainCollideEnum); tron@2639: if (realcoll == NULL) return; truelight@193: tron@6476: Vehicle *coll = GetFirstVehicleInChain(realcoll); truelight@193: truelight@0: /* it can't collide with its own wagons */ tron@1434: if (v == coll || rubidium@6319: (v->u.rail.track == TRACK_BIT_WORMHOLE && (v->direction & 2) != (realcoll->direction & 2))) truelight@0: return; truelight@0: celestar@9908: /* two drivers + passangers killed in train v */ tron@6476: uint num = 2 + CountPassengersInTrain(v); tron@1434: if (!(coll->vehstatus & VS_CRASHED)) celestar@9908: /* two drivers + passangers killed in train coll (if it was not crashed already) */ truelight@0: num += 2 + CountPassengersInTrain(coll); truelight@0: truelight@0: SetVehicleCrashed(v); bjarni@2676: if (IsFrontEngine(coll)) SetVehicleCrashed(coll); truelight@193: tron@534: SetDParam(0, num); truelight@0: AddNewsItem(STR_8868_TRAIN_CRASH_DIE_IN_FIREBALL, tron@1434: NEWS_FLAGS(NM_THIN, NF_VIEWPORT | NF_VEHICLE, NT_ACCIDENT, 0), 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: celestar@9895: struct VehicleAtSignalData { tron@1432: TileIndex tile; tron@3157: Direction direction; celestar@9895: }; tron@1432: truelight@0: static void *CheckVehicleAtSignal(Vehicle *v, void *data) truelight@0: { rubidium@5838: const VehicleAtSignalData* vasd = (VehicleAtSignalData*)data; tron@1432: celestar@9895: if (v->type == VEH_TRAIN && IsFrontEngine(v) && v->tile == vasd->tile) { tron@3158: DirDiff diff = ChangeDirDiff(DirDifference(v->direction, vasd->direction), 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: peter1138@5252: static void TrainController(Vehicle *v, bool update_image) truelight@0: { hackykid@1961: Vehicle *prev; truelight@0: darkvater@22: /* For every vehicle after and including the given vehicle */ hackykid@1961: for (prev = GetPrevVehicleInChain(v); v != NULL; prev = v, v = v->next) { 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); rubidium@6318: if (HASBIT(r, VETS_CANNOT_ENTER)) { truelight@0: goto invalid_rail; matthijs@1247: } rubidium@6317: if (HASBIT(r, VETS_ENTERED_STATION)) { rubidium@6317: TrainEnterStation(v, r >> VETS_STATION_ID_OFFSET); truelight@0: return; truelight@0: } truelight@0: tron@555: if (v->current_order.type == OT_LEAVESTATION) { celestar@9895: v->current_order.Free(); darkvater@755: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); 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); tron@6477: DiagDirection 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. */ tron@6477: uint32 ts = GetTileTrackStatus(gp.new_tile, TRANSPORT_RAIL) & _reachable_tracks[enterdir]; truelight@0: truelight@0: /* Combine the from & to directions. truelight@0: * Now, the lower byte contains the track status, and the byte at bit 16 contains truelight@0: * the signal status. */ tron@6476: uint32 tracks = ts | (ts >> 8); tron@6477: TrackBits bits = (TrackBits)(tracks & TRACK_BIT_MASK); KUDr@3900: if ((_patches.new_pathfinding_all || _patches.yapf.rail_use_yapf) && _patches.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)); matthijs@1247: assert(chosen_track & tracks); truelight@0: truelight@0: /* Check if it's a red signal and that force proceed is not clicked. */ tron@6477: if ((tracks >> 16) & chosen_track && v->u.rail.force_proceed == 0) { celestar@9908: /* In front of a red signal celestar@9908: * find the first set bit in ts. need to do it in 2 steps, since tron@6477: * FIND_FIRST_BIT only handles 6 bits at a time. */ tron@6477: Trackdir i = FindFirstTrackdir((TrackdirBits)(uint16)ts); 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; tron@6477: if (++v->load_unload_time_rem < _patches.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; celestar@9910: v->progress = 255 - 10; tron@6477: if (++v->load_unload_time_rem < _patches.wait_twoway_signal * 73) { tron@6477: TileIndex o_tile = gp.new_tile + TileOffsByDiagDir(enterdir); tron@6477: VehicleAtSignalData vasd; tron@6477: vasd.tile = o_tile; tron@6477: vasd.direction = ReverseDir(dir); tron@6477: tron@6477: /* check if a train is waiting on the other side */ tron@6477: if (VehicleFromPos(o_tile, &vasd, 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); rubidium@6317: if (HASBIT(r, VETS_CANNOT_ENTER)) { truelight@0: goto invalid_rail; matthijs@1247: } truelight@0: tron@3657: if (IsLevelCrossingTile(v->tile) && v->next == NULL) { tron@3657: UnbarCrossing(v->tile); tron@3657: MarkTileDirtyByTile(v->tile); tron@3657: } tron@3657: bjarni@2676: if (IsFrontEngine(v)) v->load_unload_time_rem = 0; truelight@0: rubidium@6317: 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)) { celestar@3355: TrainPowerChanged(GetFirstVehicleInChain(v)); celestar@3355: } celestar@3355: truelight@0: v->u.rail.track = chosen_track; matthijs@1330: assert(v->u.rail.track); truelight@0: } truelight@0: tron@3017: if (IsFrontEngine(v)) TrainMovedChangeSignals(gp.new_tile, enterdir); truelight@0: darkvater@22: /* Signals can only change when the first darkvater@22: * (above) or the last vehicle moves. */ rubidium@6320: if (v->next == NULL) TrainMovedChangeSignals(gp.old_tile, ReverseDiagDir(enterdir)); truelight@193: tron@3017: if (prev == NULL) AffectSpeedByDirChange(v, chosen_dir); truelight@0: truelight@0: v->direction = chosen_dir; truelight@0: } truelight@0: } else { rubidium@6320: /* In tunnel or on a bridge */ tron@6467: if (!(v->vehstatus & VS_HIDDEN)) { tron@6467: v->cur_speed = tron@6467: min(v->cur_speed, GetBridge(GetBridgeType(v->tile))->speed); tron@6467: } celestar@5573: rubidium@6317: if (!(IsTunnelTile(gp.new_tile) || IsBridgeTile(gp.new_tile)) || !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 */ tron@6476: Direction newdir = GetNewVehicleDirection(v, gp.x, gp.y); celestar@9911: v->UpdateDeltaXY(newdir); peter1138@5252: if (update_image) v->cur_image = GetTrainImage(v, newdir); 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: } truelight@0: } tron@1438: return; truelight@0: truelight@0: invalid_rail: darkvater@22: /* We've reached end of line?? */ tron@2639: 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: tron@1430: extern TileIndex CheckTunnelBusy(TileIndex tile, uint *length); dominik@98: 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, Darkvater@1418: * or inside a tunnel, recalculate the signals as they might need updating celestar@9909: * @param v the Vehicle of which last wagon is to be removed Darkvater@1418: */ truelight@0: static void DeleteLastWagon(Vehicle *v) truelight@0: { 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; tron@1434: for (; v->next != NULL; v = v->next) u = v; truelight@0: u->next = NULL; truelight@0: truelight@0: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); truelight@0: DeleteWindowById(WC_VEHICLE_VIEW, v->index); tron@588: RebuildVehicleLists(); truelight@0: InvalidateWindow(WC_COMPANY, v->owner); truelight@0: truelight@0: BeginVehicleMove(v); truelight@0: EndVehicleMove(v); celestar@9911: celestar@9911: if (IsFrontEngine(v)) RemoveVehicleFromGroup(v); celestar@9911: truelight@0: DeleteVehicle(v); truelight@0: rubidium@6319: if (v->u.rail.track != TRACK_BIT_DEPOT && v->u.rail.track != TRACK_BIT_WORMHOLE) Darkvater@1418: SetSignalsOnBothDir(v->tile, FIND_FIRST_BIT(v->u.rail.track)); truelight@193: tron@1434: /* Check if the wagon was on a road/rail-crossing and disable it if no tron@1434: * others are on it */ dominik@1103: DisableTrainCrossing(v->tile); dominik@1103: rubidium@6319: if ((v->u.rail.track == TRACK_BIT_WORMHOLE && v->vehstatus & VS_HIDDEN)) { // inside a tunnel tron@1430: TileIndex endtile = CheckTunnelBusy(v->tile, NULL); tron@1430: tron@3017: if (endtile == INVALID_TILE) return; // tunnel is busy (error returned) Darkvater@1418: tron@1431: switch (v->direction) { tron@1431: case 1: tron@1431: case 5: tron@1431: SetSignalsOnBothDir(v->tile, 0); tron@1431: SetSignalsOnBothDir(endtile, 0); tron@1431: break; tron@1431: tron@1431: case 3: tron@1431: case 7: tron@1431: SetSignalsOnBothDir(v->tile, 1); tron@1431: SetSignalsOnBothDir(endtile, 1); tron@1431: break; tron@1431: tron@1431: default: tron@1431: break; tron@1431: } 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); celestar@9911: v->UpdateDeltaXY(v->direction); truelight@0: v->cur_image = GetTrainImage(v, v->direction); celestar@5573: /* Refrain from updating the z position of the vehicle when on celestar@5573: a bridge, because AfterSetTrainPos will put the vehicle under celestar@5573: the bridge in that case */ rubidium@6319: if (v->u.rail.track != TRACK_BIT_WORMHOLE) AfterSetTrainPos(v, false); truelight@0: } tron@3017: } 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; tron@2637: 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: } tron@3017: } while ((u = u->next) != NULL); truelight@0: } truelight@0: tron@3017: if (state <= 240 && !(v->tick_counter & 3)) ChangeTrainDirRandomly(v); truelight@0: bjarni@1128: if (state >= 4440 && !(v->tick_counter&0x1F)) { truelight@0: DeleteLastWagon(v); celestar@9911: 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)) { celestar@9906: SndPlayVehicleFx((_opt.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); tron@2549: if (u != NULL) u->u.special.unk0 = 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: truelight@0: static const byte _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: truelight@742: static bool TrainCheckIfLineEnds(Vehicle *v) truelight@0: { tron@6476: int t = v->breakdown_ctr; tron@2484: if (t > 1) { truelight@0: v->vehstatus |= VS_TRAIN_SLOWING; truelight@193: tron@6476: uint16 break_speed = _breakdown_speeds[GB(~t, 4, 4)]; tron@2484: if (break_speed < v->cur_speed) v->cur_speed = break_speed; truelight@0: } else { truelight@0: v->vehstatus &= ~VS_TRAIN_SLOWING; truelight@0: } truelight@0: rubidium@6319: if (v->u.rail.track == TRACK_BIT_WORMHOLE) return true; // exit if inside a tunnel rubidium@6319: if (v->u.rail.track == TRACK_BIT_DEPOT) return true; // exit if inside a depot hackykid@2008: tron@6476: TileIndex tile = v->tile; truelight@0: celestar@5573: if (IsTileType(tile, MP_TUNNELBRIDGE)) { tron@6476: DiagDirection dir = IsTunnel(tile) ? GetTunnelDirection(tile) : GetBridgeRampDirection(tile); celestar@5573: if (DiagDirToDir(dir) == v->direction) return true; tron@3154: } truelight@193: truelight@0: // depot? truelight@742: /* XXX -- When enabled, this makes it possible to crash trains of others truelight@742: (by building a depot right against a station) */ glx@4632: /* if (IsTileType(tile, MP_RAILWAY) && GetRailTileType(tile) == RAIL_TILE_DEPOT_WAYPOINT) truelight@742: return true;*/ truelight@0: darkvater@22: /* Determine the non-diagonal direction in which we will exit this tile */ tron@6476: DiagDirection dir = DirToDiagDir(v->direction); tron@3163: if (!(v->direction & 1) && v->u.rail.track != _state_dir_table[dir]) { tron@3163: dir = ChangeDiagDir(dir, DIAGDIRDIFF_90LEFT); truelight@0: } darkvater@22: /* Calculate next tile */ Darkvater@4559: tile += TileOffsByDiagDir(dir); darkvater@22: // determine the track status on the next tile. tron@6476: uint32 ts = GetTileTrackStatus(tile, TRANSPORT_RAIL) & _reachable_tracks[dir]; truelight@193: darkvater@22: /* Calc position within the current tile ?? */ tron@6476: uint x = v->x_pos & 0xF; tron@6476: uint y = v->y_pos & 0xF; truelight@193: tron@2639: switch (v->direction) { tron@3186: case DIR_N : x = ~x + ~y + 24; break; tron@3186: case DIR_NW: x = y; /* FALLTHROUGH */ tron@3186: case DIR_NE: x = ~x + 16; break; tron@3186: case DIR_E : x = ~x + y + 8; break; tron@3186: case DIR_SE: x = y; break; tron@3186: case DIR_S : x = x + y - 8; break; tron@3186: case DIR_W : x = ~y + x + 8; break; rubidium@5838: default: break; truelight@0: } truelight@0: tron@2484: if (GB(ts, 0, 16) != 0) { peter1138@2874: /* If we approach a rail-piece which we can't enter, or the back of a depot, don't enter it! */ tron@3645: if (x + 4 >= TILE_SIZE && peter1138@2874: (!CheckCompatibleRail(v, tile) || peter1138@2874: (IsTileDepotType(tile, TRANSPORT_RAIL) && tron@3185: GetRailDepotDirection(tile) == dir))) { truelight@742: v->cur_speed = 0; truelight@742: ReverseTrainDirection(v); truelight@742: return false; truelight@742: } truelight@0: if ((ts &= (ts >> 16)) == 0) { celestar@9908: /* make a rail/road crossing red */ Darkvater@3560: if (IsLevelCrossingTile(tile)) { celestar@3322: if (!IsCrossingBarred(tile)) { celestar@3322: BarCrossing(tile); tron@541: SndPlayVehicleFx(SND_0E_LEVEL_CROSSING, v); truelight@0: MarkTileDirtyByTile(tile); truelight@0: } truelight@0: } truelight@742: return true; truelight@0: } tron@3645: } else if (x + 4 >= TILE_SIZE) { truelight@0: v->cur_speed = 0; truelight@0: ReverseTrainDirection(v); truelight@742: return false; truelight@0: } truelight@0: celestar@9908: /* slow down */ truelight@0: v->vehstatus |= VS_TRAIN_SLOWING; tron@6476: uint16 break_speed = _breakdown_speeds[x & 0xF]; tron@3017: if (!(v->direction & 1)) break_speed >>= 1; tron@2484: if (break_speed < v->cur_speed) v->cur_speed = break_speed; truelight@742: truelight@742: return true; truelight@0: } truelight@0: truelight@0: static void TrainLocoHandler(Vehicle *v, bool mode) truelight@0: { truelight@0: /* train has crashed? */ truelight@0: if (v->u.rail.crash_anim_pos != 0) { 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: } truelight@0: v->breakdown_ctr--; truelight@0: } truelight@0: truelight@954: 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: truelight@0: if (ProcessTrainOrder(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: celestar@9911: v->HandleLoading(mode); truelight@0: tron@3017: if (v->current_order.type == 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); truelight@0: if (j == 0) { celestar@9908: /* 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 { peter1138@5252: TrainController(v, 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: truelight@0: void Train_Tick(Vehicle *v) truelight@0: { truelight@0: if (_age_cargo_skip_counter == 0 && v->cargo_days != 0xff) truelight@0: v->cargo_days++; truelight@0: truelight@0: v->tick_counter++; truelight@0: bjarni@2676: if (IsFrontEngine(v)) { truelight@0: TrainLocoHandler(v, false); truelight@193: celestar@9908: /* make sure vehicle wasn't deleted. */ celestar@9895: if (v->type == VEH_TRAIN && IsFrontEngine(v)) truelight@0: TrainLocoHandler(v, true); bjarni@2676: } else if (IsFreeWagon(v) && HASBITS(v->vehstatus, VS_CRASHED)) { celestar@9908: /* Delete flooded standalone wagon */ darkvater@1132: if (++v->u.rail.crash_anim_pos >= 4400) darkvater@1132: DeleteVehicle(v); truelight@0: } truelight@0: } truelight@0: KUDr@3900: #define MAX_ACCEPTABLE_DEPOT_DIST 16 KUDr@3900: truelight@0: static void CheckIfTrainNeedsService(Vehicle *v) truelight@0: { tron@2639: if (_patches.servint_trains == 0) return; tron@2639: if (!VehicleNeedsService(v)) return; tron@2639: if (v->vehstatus & VS_STOPPED) return; tron@2639: if (_patches.gotodepot && VehicleHasDepotOrders(v)) return; truelight@193: celestar@9908: /* Don't interfere with a depot visit scheduled by the user, or a celestar@9908: * depot visit by the order list. */ tron@555: if (v->current_order.type == OT_GOTO_DEPOT && celestar@1530: (v->current_order.flags & (OF_HALT_IN_DEPOT | OF_PART_OF_ORDERS)) != 0) truelight@0: return; truelight@0: bjarni@4529: if (CheckTrainIsInsideDepot(v)) { 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) { tron@555: if (v->current_order.type == 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? */ tron@555: v->current_order.type = OT_DUMMY; tron@555: v->current_order.flags = 0; darkvater@755: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); truelight@0: } truelight@0: return; truelight@0: } truelight@0: tron@6476: const Depot* depot = GetDepotByTile(tfdd.tile); truelight@0: tron@555: if (v->current_order.type == OT_GOTO_DEPOT && tron@4527: v->current_order.dest != depot->index && tron@3017: !CHANCE16(3, 16)) { truelight@0: return; tron@3017: } truelight@0: celestar@9911: if (v->current_order.type == OT_LOADING) v->LeaveStation(); celestar@9911: tron@555: v->current_order.type = OT_GOTO_DEPOT; tron@555: v->current_order.flags = OF_NON_STOP; tron@4527: v->current_order.dest = depot->index; darkvater@308: v->dest_tile = tfdd.tile; darkvater@755: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); truelight@0: } truelight@0: Darkvater@1790: int32 GetTrainRunningCost(const Vehicle *v) truelight@0: { truelight@0: int32 cost = 0; truelight@0: truelight@0: do { tron@540: const RailVehicleInfo *rvi = RailVehInfo(v->engine_type); celestar@9910: celestar@9910: byte cost_factor = GetVehicleProperty(v, 0x0D, rvi->running_cost_base); celestar@9910: if (cost_factor == 0) continue; celestar@9910: switch (rvi->running_cost_class) { celestar@9910: default: celestar@9910: case 0: cost_factor *= _eco->GetPrice(CEconomy::RUNNING_RAIL0); break; celestar@9910: case 1: cost_factor *= _eco->GetPrice(CEconomy::RUNNING_RAIL0); break; celestar@9910: case 2: cost_factor *= _eco->GetPrice(CEconomy::RUNNING_RAIL0); break; celestar@9903: } celestar@9910: cost += rvi->running_cost_base * cost_factor; celestar@9910: peter1138@4839: } while ((v = GetNextVehicle(v)) != NULL); truelight@0: truelight@0: return cost; truelight@0: } truelight@0: truelight@0: void OnNewDay_Train(Vehicle *v) truelight@0: { tron@3017: if ((++v->day_counter & 7) == 0) DecreaseVehicleValue(v); truelight@0: bjarni@2676: if (IsFrontEngine(v)) { truelight@0: CheckVehicleBreakdown(v); truelight@0: AgeVehicle(v); truelight@193: truelight@0: CheckIfTrainNeedsService(v); truelight@193: tron@3140: CheckOrders(v); truelight@193: truelight@0: /* update destination */ tron@6476: if (v->current_order.type == OT_GOTO_STATION) { tron@6476: TileIndex tile = GetStation(v->current_order.dest)->train_tile; tron@6476: if (tile != 0) v->dest_tile = tile; tron@2639: } truelight@0: truelight@0: if ((v->vehstatus & VS_STOPPED) == 0) { truelight@0: /* running costs */ truelight@0: int32 cost = GetTrainRunningCost(v) / 364; truelight@0: truelight@0: v->profit_this_year -= cost >> 8; truelight@0: truelight@0: SET_EXPENSES_TYPE(EXPENSES_TRAIN_RUN); truelight@0: SubtractMoneyFromPlayerFract(v->owner, cost); truelight@0: truelight@0: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); bjarni@1151: InvalidateWindowClasses(WC_TRAINS_LIST); truelight@0: } truelight@0: } truelight@0: } truelight@0: celestar@9895: void TrainsYearlyLoop() truelight@0: { truelight@0: Vehicle *v; truelight@0: truelight@0: FOR_ALL_VEHICLES(v) { celestar@9895: if (v->type == VEH_TRAIN && IsFrontEngine(v)) { celestar@9908: /* show warning if train is not generating enough income last 2 years (corresponds to a red icon in the vehicle list) */ truelight@0: if (_patches.train_income_warn && v->owner == _local_player && v->age >= 730 && v->profit_this_year < 0) { tron@534: SetDParam(1, v->profit_this_year); tron@534: SetDParam(0, v->unitnumber); truelight@0: AddNewsItem( truelight@0: STR_TRAIN_IS_UNPROFITABLE, truelight@0: NEWS_FLAGS(NM_SMALL, NF_VIEWPORT|NF_VEHICLE, NT_ADVICE, 0), 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: celestar@9895: 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: */ celestar@9895: void ConnectMultiheadedTrains() bjarni@2855: { bjarni@2855: Vehicle *v; bjarni@2855: bjarni@2855: FOR_ALL_VEHICLES(v) { celestar@9895: 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) { celestar@9895: if (v->type == VEH_TRAIN && IsFrontEngine(v)) { bjarni@2855: Vehicle *u = v; bjarni@2855: bjarni@2855: BEGIN_ENUM_WAGONS(u) { bjarni@2855: if (u->u.rail.other_multiheaded_part != NULL) continue; // we already linked this one bjarni@2855: bjarni@2855: if (IsMultiheaded(u)) { bjarni@2855: if (!IsTrainEngine(u)) { bjarni@2855: /* we got a rear car without a front car. We will convert it to a front one */ bjarni@2855: SetTrainEngine(u); bjarni@2855: u->spritenum--; bjarni@2855: } bjarni@2855: tron@6476: Vehicle *w; tron@6476: 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: } bjarni@2855: } END_ENUM_WAGONS(u) bjarni@2855: } bjarni@2855: } bjarni@2855: } bjarni@2855: celestar@9908: /** 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: */ celestar@9895: void ConvertOldMultiheadToNew() bjarni@2855: { bjarni@2855: Vehicle *v; bjarni@2855: FOR_ALL_VEHICLES(v) { celestar@9895: if (v->type == VEH_TRAIN) { rubidium@4434: 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) { celestar@9895: if (v->type == VEH_TRAIN) { bjarni@2855: if (HASBIT(v->subtype, 7) && ((v->subtype & ~0x80) == 0 || (v->subtype & ~0x80) == 4)) { bjarni@2855: Vehicle *u = v; bjarni@2855: tron@3017: BEGIN_ENUM_WAGONS(u) { bjarni@2855: const RailVehicleInfo *rvi = RailVehInfo(u->engine_type); tron@3017: tron@3017: 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: } tron@3017: } END_ENUM_WAGONS(u) bjarni@2855: } bjarni@2855: } bjarni@2855: } bjarni@2855: }