tron@2186: /* $Id$ */ tron@2186: belugas@6422: /** @file train_cmd.cpp */ belugas@6422: 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" maedhros@6453: #include "landscape.h" tron@2561: #include "gui.h" tron@3315: #include "station_map.h" tron@507: #include "table/strings.h" rubidium@7266: #include "strings.h" tron@679: #include "map.h" tron@1209: #include "tile.h" tron@3154: #include "tunnel_map.h" truelight@0: #include "vehicle.h" maedhros@6980: #include "timetable.h" maedhros@6772: #include "articulated_vehicles.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@5385: #include "bridge.h" peter1138@2982: #include "newgrf_callbacks.h" peter1138@2962: #include "newgrf_engine.h" peter1138@4656: #include "newgrf_sound.h" peter1138@3727: #include "newgrf_text.h" bjarni@3256: #include "direction.h" KUDr@3900: #include "yapf/yapf.h" rubidium@4261: #include "date.h" peter1138@6091: #include "cargotype.h" rubidium@6643: #include "group.h" glx@7802: #include "table/sprites.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@5993: 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@6114: if (!GetCargo(cargo)->is_freight) return 1; peter1138@5163: return _patches.freight_trains; peter1138@5163: } peter1138@5163: peter1138@5163: hackykid@1905: /** peter1138@5400: * Recalculates the cached total power of a train. Should be called when the consist is changed peter1138@5400: * @param v First vehicle of the consist. peter1138@5400: */ peter1138@5400: void TrainPowerChanged(Vehicle* v) peter1138@5400: { peter1138@6490: uint32 total_power = 0; peter1138@5400: uint32 max_te = 0; peter1138@5400: rubidium@7492: for (const Vehicle *u = v; u != NULL; u = u->Next()) { peter1138@5400: /* Power is not added for articulated parts */ peter1138@5400: if (IsArticulatedPart(u)) continue; peter1138@5400: rubidium@6172: RailType railtype = GetRailType(u->tile); tron@5769: bool engine_has_power = HasPowerOnRail(u->u.rail.railtype, railtype); tron@5769: bool wagon_has_power = HasPowerOnRail(v->u.rail.railtype, railtype); tron@5769: tron@5769: const RailVehicleInfo *rvi_u = RailVehInfo(u->engine_type); peter1138@5400: peter1138@6490: if (engine_has_power) { peter1138@6490: uint16 power = GetVehicleProperty(u, 0x0B, rvi_u->power); peter1138@6490: if (power != 0) { peter1138@6490: total_power += power; peter1138@6490: /* Tractive effort in (tonnes * 1000 * 10 =) N */ peter1138@6490: max_te += (u->u.rail.cached_veh_weight * 10000 * GetVehicleProperty(u, 0x1F, rvi_u->tractive_effort)) / 256; peter1138@6490: } peter1138@5400: } peter1138@5400: skidd13@7928: if (HasBit(u->u.rail.flags, VRF_POWEREDWAGON) && (wagon_has_power)) { peter1138@6490: total_power += RailVehInfo(u->u.rail.first_engine)->pow_wag_power; peter1138@5400: } peter1138@5400: } peter1138@5400: peter1138@6490: if (v->u.rail.cached_power != total_power || v->u.rail.cached_max_te != max_te) { rubidium@7539: /* If it has no power (no catenary), stop the train */ rubidium@7539: if (total_power == 0) v->vehstatus |= VS_STOPPED; rubidium@7539: peter1138@6490: v->u.rail.cached_power = total_power; peter1138@5400: v->u.rail.cached_max_te = max_te; peter1138@5400: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); peter1138@5400: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); peter1138@5400: } peter1138@5400: } peter1138@5400: peter1138@5400: peter1138@5400: /** hackykid@1905: * Recalculates the cached weight of a train and its vehicles. Should be called each time the cargo on hackykid@1905: * the consist changes. hackykid@1905: * @param v First vehicle of the consist. hackykid@1905: */ tron@2752: static void TrainCargoChanged(Vehicle* v) tron@2639: { peter1138@5162: uint32 weight = 0; hackykid@1905: rubidium@7492: for (Vehicle *u = v; u != NULL; u = u->Next()) { rubidium@7010: uint32 vweight = GetCargo(u->cargo_type)->weight * u->cargo.Count() * FreightWagonMult(u->cargo_type) / 16; peter1138@2602: belugas@6422: /* Vehicle weight is not added for articulated parts. */ bjarni@2676: if (!IsArticulatedPart(u)) { belugas@6422: /* vehicle weight is the sum of the weight of the vehicle and the weight of its cargo */ peter1138@6572: vweight += GetVehicleProperty(u, 0x16, RailVehInfo(u->engine_type)->weight); peter1138@2602: belugas@6422: /* powered wagons have extra weight added */ skidd13@7928: if (HasBit(u->u.rail.flags, VRF_POWEREDWAGON)) peter1138@4017: vweight += RailVehInfo(u->u.rail.first_engine)->pow_wag_weight; peter1138@2602: } hackykid@1905: belugas@6422: /* consist weight is the sum of the weight of all vehicles in the consist */ hackykid@1905: weight += vweight; hackykid@1905: belugas@6422: /* store vehicle weight in cache */ hackykid@1905: u->u.rail.cached_veh_weight = vweight; tron@6150: } hackykid@1905: belugas@6422: /* store consist weight in cache */ hackykid@1905: v->u.rail.cached_weight = weight; peter1138@5400: peter1138@5400: /* Now update train power (tractive effort is dependent on weight) */ peter1138@5400: TrainPowerChanged(v); hackykid@1905: } hackykid@1905: celestar@3355: 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: rubidium@6259: assert(v->type == VEH_TRAIN); bjarni@2676: assert(IsFrontEngine(v) || IsFreeWagon(v)); hackykid@1917: tron@6150: const RailVehicleInfo *rvi_v = RailVehInfo(v->engine_type); tron@6150: EngineID first_engine = IsFrontEngine(v) ? v->engine_type : INVALID_ENGINE; peter1138@2587: v->u.rail.cached_total_length = 0; celestar@3355: v->u.rail.compatible_railtypes = 0; hackykid@1905: rubidium@7492: for (Vehicle *u = v; u != NULL; u = u->Next()) { hackykid@1908: const RailVehicleInfo *rvi_u = RailVehInfo(u->engine_type); hackykid@1908: rubidium@7497: /* Check the v->first cache. */ rubidium@7497: assert(u->First() == v); peter1138@2993: belugas@6422: /* update the 'first engine' */ tron@6150: u->u.rail.first_engine = v == u ? INVALID_ENGINE : first_engine; tron@5823: u->u.rail.railtype = rvi_u->railtype; hackykid@1917: peter1138@4017: if (IsTrainEngine(u)) first_engine = u->engine_type; peter1138@4017: peter1138@6603: /* Cache wagon override sprite group. NULL is returned if there is none */ peter1138@6603: u->u.rail.cached_override = GetWagonOverrideSpriteSet(u->engine_type, u->cargo_type, u->u.rail.first_engine); peter1138@6603: glx@7802: /* Reset color map */ glx@7802: u->colormap = PAL_NONE; glx@7802: peter1138@2595: if (rvi_u->visual_effect != 0) { peter1138@2595: u->u.rail.cached_vis_effect = rvi_u->visual_effect; peter1138@2595: } else { bjarni@2676: if (IsTrainWagon(u) || IsArticulatedPart(u)) { belugas@6422: /* Wagons and articulated parts have no effect by default */ peter1138@2595: u->u.rail.cached_vis_effect = 0x40; peter1138@2595: } else if (rvi_u->engclass == 0) { belugas@6422: /* Steam is offset by -4 units */ peter1138@2595: u->u.rail.cached_vis_effect = 4; peter1138@2595: } else { belugas@6422: /* Diesel fumes and sparks come from the centre */ peter1138@2595: u->u.rail.cached_vis_effect = 8; peter1138@2595: } peter1138@2595: } peter1138@2595: bjarni@2676: if (!IsArticulatedPart(u)) { peter1138@3926: /* Check powered wagon / visual effect callback */ skidd13@7928: if (HasBit(EngInfo(u->engine_type)->callbackmask, CBM_TRAIN_WAGON_POWER)) { peter1138@3926: uint16 callback = GetVehicleCallback(CBID_TRAIN_WAGON_POWER, 0, 0, u->engine_type, u); peter1138@3926: peter1138@3926: if (callback != CALLBACK_FAILED) u->u.rail.cached_vis_effect = callback; peter1138@3926: } peter1138@3926: belugas@5868: if (rvi_v->pow_wag_power != 0 && rvi_u->railveh_type == RAILVEH_WAGON && skidd13@7928: UsesWagonOverride(u) && !HasBit(u->u.rail.cached_vis_effect, 7)) { belugas@5868: /* wagon is powered */ skidd13@7931: SetBit(u->u.rail.flags, VRF_POWEREDWAGON); // cache 'powered' status tron@6150: } else { skidd13@7929: ClrBit(u->u.rail.flags, VRF_POWEREDWAGON); peter1138@2595: } peter1138@2595: celestar@3355: /* Do not count powered wagons for the compatible railtypes, as wagons always celestar@3355: have railtype normal */ celestar@3355: if (rvi_u->power > 0) { celestar@3355: v->u.rail.compatible_railtypes |= GetRailTypeInfo(u->u.rail.railtype)->powered_railtypes; celestar@3355: } celestar@3355: KUDr@5116: /* Some electric engines can be allowed to run on normal rail. It happens to all KUDr@5116: * existing electric engines when elrails are disabled and then re-enabled */ skidd13@7928: 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: belugas@6422: /* max speed is the minimum of the speed limits of all vehicles in the consist */ peter1138@6490: if ((rvi_u->railveh_type != RAILVEH_WAGON || _patches.wagon_speed_limits) && !UsesWagonOverride(u)) { peter1138@6490: uint16 speed = GetVehicleProperty(u, 0x09, rvi_u->max_speed); peter1138@6490: if (speed != 0) max_speed = min(speed, max_speed); peter1138@6490: } hackykid@1908: } hackykid@1908: peter1138@6644: if (u->cargo_type == rvi_u->cargo_type && u->cargo_subtype == 0) { peter1138@6644: /* Set cargo capacity if we've not been refitted */ peter1138@6644: u->cargo_cap = GetVehicleProperty(u, 0x14, rvi_u->capacity); peter1138@6644: } peter1138@6608: glx@7881: u->u.rail.user_def_data = GetVehicleProperty(u, 0x25, rvi_u->user_def_data); glx@7881: belugas@6422: /* check the vehicle length (callback) */ tron@6150: uint16 veh_len = CALLBACK_FAILED; skidd13@7928: if (HasBit(EngInfo(u->engine_type)->callbackmask, CBM_VEHICLE_LENGTH)) { rubidium@7215: veh_len = GetVehicleCallback(CBID_VEHICLE_LENGTH, 0, 0, u->engine_type, u); peter1138@3956: } peter1138@3956: if (veh_len == CALLBACK_FAILED) veh_len = rvi_u->shorten_factor; skidd13@7922: 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@6150: } hackykid@1905: belugas@6422: /* store consist weight/max speed in cache */ hackykid@1905: v->u.rail.cached_max_speed = max_speed; celestar@3355: belugas@6422: /* recalculate cached weights and power too (we do this *after* the rest, so it is known which wagons are powered and need extra weight added) */ hackykid@1908: TrainCargoChanged(v); 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: rubidium@6259: assert(v->type == VEH_TRAIN); belugas@6422: /* When does a train drive through a station belugas@6422: * 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: belugas@6422: /** new acceleration*/ celestar@1179: static int GetTrainAcceleration(Vehicle *v, bool mode) celestar@1179: { celestar@1179: int max_speed = 2000; rubidium@7561: int speed = v->cur_speed * 10 / 16; // km-ish/h -> mp/h tron@6150: int curvecount[2] = {0, 0}; tron@6150: belugas@6422: /*first find the curve speed limit */ tron@6150: int numcurve = 0; tron@6150: int sum = 0; celestar@1179: int pos = 0; celestar@1179: int lastpos = -1; rubidium@7492: for (const Vehicle *u = v; u->Next() != NULL; u = u->Next(), pos++) { tron@3157: Direction dir = u->direction; rubidium@7492: 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: belugas@6422: /*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) { skidd13@7922: 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@5998: 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@6150: int mass = v->u.rail.cached_weight; tron@6150: int power = v->u.rail.cached_power * 746; hackykid@1905: max_speed = min(max_speed, v->u.rail.cached_max_speed); hackykid@1905: tron@6150: int num = 0; //number of vehicles, change this into the number of axles later tron@6150: int incl = 0; tron@6150: int drag_coeff = 20; //[1e-4] rubidium@7492: for (const Vehicle *u = v; u != NULL; u = u->Next()) { celestar@1179: num++; celestar@1179: drag_coeff += 3; celestar@1179: rubidium@5993: if (u->u.rail.track == TRACK_BIT_DEPOT) max_speed = min(max_speed, 61); celestar@1179: skidd13@7928: if (HasBit(u->u.rail.flags, VRF_GOINGUP)) { rubidium@4434: incl += u->u.rail.cached_veh_weight * 60; //3% slope, quite a bit actually skidd13@7928: } else if (HasBit(u->u.rail.flags, VRF_GOINGDOWN)) { hackykid@1905: incl -= u->u.rail.cached_veh_weight * 60; celestar@1179: } celestar@1179: } celestar@1179: celestar@1179: v->max_speed = max_speed; celestar@1179: tron@6150: const int area = 120; tron@6150: const int friction = 35; //[1e-3] tron@6150: int resistance; tron@2519: if (v->u.rail.railtype != RAILTYPE_MAGLEV) { celestar@1179: resistance = 13 * mass / 10; celestar@1179: resistance += 60 * num; celestar@1179: resistance += friction * mass * speed / 1000; celestar@1179: resistance += (area * drag_coeff * speed * speed) / 10000; tron@3017: } else { celestar@1179: resistance = (area * (drag_coeff / 2) * speed * speed) / 10000; tron@3017: } celestar@1179: resistance += incl; celestar@1179: resistance *= 4; //[N] celestar@1179: peter1138@5277: /* Due to the mph to m/s conversion below, at speeds below 3 mph the force is peter1138@5277: * actually double the train's power */ tron@6150: const int max_te = v->u.rail.cached_max_te; // [N] tron@6150: int force; peter1138@5277: if (speed > 2) { celestar@1179: switch (v->u.rail.railtype) { tron@2519: case RAILTYPE_RAIL: celestar@3355: case RAILTYPE_ELECTRIC: tron@2519: case RAILTYPE_MONO: celestar@1179: force = power / speed; //[N] celestar@1179: force *= 22; celestar@1179: force /= 10; peter1138@5400: if (mode == AM_ACCEL && force > max_te) force = max_te; tron@1472: break; tron@1472: tron@6150: default: NOT_REACHED(); tron@2519: case RAILTYPE_MAGLEV: celestar@1179: force = power / 25; tron@1472: break; celestar@1179: } tron@1472: } else { belugas@6422: /* "kickoff" acceleration */ peter1138@5400: force = (mode == AM_ACCEL && v->u.rail.railtype != RAILTYPE_MAGLEV) ? min(max_te, power) : power; peter1138@5400: force = max(force, (mass * 8) + resistance); tron@1472: } celestar@1179: celestar@1179: if (force <= 0) force = 10000; celestar@1179: tron@2519: if (v->u.rail.railtype != RAILTYPE_MAGLEV) force = min(force, mass * 10 * 200); celestar@1179: celestar@1179: if (mode == AM_ACCEL) { tron@1684: return (force - resistance) / (mass * 4); celestar@1179: } else { peter1138@5279: return min((-force - resistance) / (mass * 4), -10000 / (mass * 4)); celestar@1179: } celestar@1179: } celestar@1179: 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@6150: uint power = v->u.rail.cached_power; tron@6150: uint weight = v->u.rail.cached_weight; truelight@0: assert(weight != 0); skidd13@7922: v->acceleration = Clamp(power / weight * 4, 1, 255); truelight@0: } truelight@0: rubidium@7134: int Train::GetImage(Direction direction) const truelight@0: { rubidium@7134: int img = this->spritenum; truelight@0: int base; truelight@0: skidd13@7928: if (HasBit(this->u.rail.flags, VRF_REVERSE_DIRECTION)) direction = ReverseDir(direction); bjarni@3256: truelight@0: if (is_custom_sprite(img)) { rubidium@7134: base = GetCustomVehicleSprite(this, (Direction)(direction + 4 * IS_CUSTOM_SECONDHEAD_SPRITE(img))); tron@1472: if (base != 0) return base; rubidium@7134: img = orig_rail_vehicle_info[this->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: rubidium@7134: if (this->cargo.Count() >= this->cargo_cap / 2U) base += _wagon_full_adder[img]; truelight@0: return base; truelight@0: } truelight@0: peter1138@5668: 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@5668: 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@5868: if (rvi->railveh_type == RAILVEH_MULTIHEAD) { peter1138@5668: DrawSprite(image, pal, x - 14, y); truelight@0: x += 15; truelight@0: image = 0; truelight@0: if (is_custom_sprite(img)) { rubidium@5587: 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@5668: DrawSprite(image, pal, x, y); truelight@0: } truelight@0: rubidium@6943: static CommandCost CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags) truelight@0: { darkvater@889: SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES); darkvater@889: tron@6150: const RailVehicleInfo *rvi = RailVehInfo(engine); rubidium@6950: CommandCost value((GetEngineProperty(engine, 0x17, rvi->base_cost) * _price.build_railwagon) >> 8); tron@6150: rubidium@7595: uint num_vehicles = 1 + CountArticulatedParts(engine, false); peter1138@2602: truelight@0: if (!(flags & DC_QUERY_COST)) { rubidium@7595: /* Allow for the wagon and the articulated parts, plus one to "terminate" the list. */ rubidium@7595: Vehicle **vl = (Vehicle**)alloca(sizeof(*vl) * (num_vehicles + 1)); rubidium@7595: memset(vl, 0, sizeof(*vl) * (num_vehicles + 1)); peter1138@4831: rubidium@7398: if (!Vehicle::AllocateList(vl, num_vehicles)) peter1138@2602: return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); truelight@0: truelight@0: if (flags & DC_EXEC) { tron@6150: Vehicle *v = vl[0]; tron@2639: v->spritenum = rvi->image_index; truelight@0: tron@6150: Vehicle *u = NULL; tron@6150: tron@6150: Vehicle *w; truelight@919: FOR_ALL_VEHICLES(w) { rubidium@6259: if (w->type == VEH_TRAIN && w->tile == tile && rubidium@7696: IsFreeWagon(w) && w->engine_type == engine && rubidium@7696: !HASBITS(w->vehstatus, VS_CRASHED)) { /// do not connect new wagon with crashed/flooded consists truelight@919: u = GetLastVehicleInChain(w); truelight@0: break; truelight@0: } truelight@0: } truelight@0: rubidium@7782: v = new (v) Train(); truelight@0: v->engine_type = engine; truelight@0: tron@6150: DiagDirection dir = GetRailDepotDirection(tile); tron@2150: tron@3153: v->direction = DiagDirToDir(dir); tron@1986: v->tile = tile; truelight@193: tron@6150: int x = TileX(tile) * TILE_SIZE | _vehicle_initial_x_fract[dir]; tron@6150: int y = TileY(tile) * TILE_SIZE | _vehicle_initial_y_fract[dir]; truelight@0: truelight@0: v->x_pos = x; truelight@0: v->y_pos = y; tron@6150: v->z_pos = GetSlopeZ(x, y); truelight@0: v->owner = _current_player; rubidium@5993: 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); maedhros@6771: truelight@0: if (u != NULL) { rubidium@7493: u->SetNext(v); bjarni@2676: } else { bjarni@2676: SetFreeWagon(v); bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); truelight@0: } truelight@0: truelight@0: v->cargo_type = rvi->cargo_type; peter1138@3870: v->cargo_subtype = 0; truelight@0: v->cargo_cap = rvi->capacity; rubidium@6950: v->value = value.GetCost(); truelight@0: // v->day_counter = 0; truelight@0: tron@5823: v->u.rail.railtype = rvi->railtype; truelight@193: rubidium@4329: v->build_year = _cur_year; truelight@0: v->cur_image = 0xAC2; peter1138@2804: v->random_bits = VehicleRandomBits(); truelight@193: rubidium@6643: v->group_id = DEFAULT_GROUP; rubidium@6643: maedhros@6857: AddArticulatedParts(vl, VEH_TRAIN); peter1138@2602: bjarni@2564: _new_vehicle_id = v->index; truelight@0: truelight@0: VehiclePositionChanged(v); rubidium@7497: TrainConsistChanged(v->First()); rubidium@7497: UpdateTrainGroupID(v->First()); truelight@0: truelight@0: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); bjarni@2970: if (IsLocalPlayer()) { bjarni@7425: InvalidateAutoreplaceWindow(v->engine_type, v->group_id); // updates the replace Train window bjarni@2970: } bjarni@5944: GetPlayer(_current_player)->num_engines[engine]++; truelight@0: } truelight@0: } truelight@0: rubidium@6950: return CommandCost(value); truelight@0: } truelight@0: belugas@6422: /** 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) { rubidium@6259: if (v->type == VEH_TRAIN && IsFreeWagon(v) && truelight@0: v->tile == u->tile && rubidium@5993: v->u.rail.track == TRACK_BIT_DEPOT) { tron@3491: if (CmdFailed(DoCommand(0, v->index | (u->index << 16), 1, DC_EXEC, bjarni@2676: CMD_MOVE_RAIL_VEHICLE))) truelight@0: break; truelight@0: } truelight@0: } truelight@0: } truelight@0: rubidium@6943: static CommandCost EstimateTrainCost(EngineID engine, const RailVehicleInfo* rvi) truelight@0: { rubidium@6950: return CommandCost(GetEngineProperty(engine, 0x17, rvi->base_cost) * (_price.build_railvehicle >> 3) >> 5); truelight@0: } truelight@0: tron@2817: static void AddRearEngineToMultiheadedTrain(Vehicle* v, Vehicle* u, bool building) bjarni@1060: { rubidium@7497: u = new (u) Train(); bjarni@1060: u->direction = v->direction; bjarni@1060: u->owner = v->owner; bjarni@1060: u->tile = v->tile; bjarni@1060: u->x_pos = v->x_pos; bjarni@1060: u->y_pos = v->y_pos; bjarni@1060: u->z_pos = v->z_pos; rubidium@5993: u->u.rail.track = TRACK_BIT_DEPOT; bjarni@1060: u->vehstatus = v->vehstatus & ~VS_STOPPED; 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; rubidium@7493: if (building) v->SetNext(u); bjarni@1060: u->engine_type = v->engine_type; bjarni@1060: u->build_year = v->build_year; tron@1472: if (building) v->value >>= 1; tron@1472: u->value = v->value; bjarni@1060: u->cur_image = 0xAC2; peter1138@2804: u->random_bits = VehicleRandomBits(); bjarni@1060: VehiclePositionChanged(u); bjarni@1060: } bjarni@1060: Darkvater@1784: /** Build a railroad vehicle. tron@3491: * @param tile tile of the depot where rail-vehicle is built belugas@6422: * @param flags type of operation Darkvater@1784: * @param p1 engine type id bjarni@3816: * @param p2 bit 0 when set, the train will get number 0, otherwise it will get a free number bjarni@3816: * bit 1 prevents any free cars from being added to the train truelight@0: */ rubidium@6943: CommandCost CmdBuildRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { Darkvater@1784: /* Check if the engine-type is valid (for the player) */ maedhros@6407: 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@6150: 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@5823: if (!HasPowerOnRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR; truelight@193: belugas@5868: if (rvi->railveh_type == RAILVEH_WAGON) return CmdBuildRailWagon(p1, tile, flags); truelight@0: rubidium@6943: CommandCost value = EstimateTrainCost(p1, rvi); tron@6150: tron@6150: uint num_vehicles = tron@6150: (rvi->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1) + rubidium@7595: CountArticulatedParts(p1, false); truelight@0: truelight@0: if (!(flags & DC_QUERY_COST)) { rubidium@7595: /* Allow for the dual-heads and the articulated parts, plus one to "terminate" the list. */ rubidium@7595: Vehicle **vl = (Vehicle**)alloca(sizeof(*vl) * (num_vehicles + 1)); rubidium@7595: memset(vl, 0, sizeof(*vl) * (num_vehicles + 1)); peter1138@4831: rubidium@7398: if (!Vehicle::AllocateList(vl, num_vehicles)) truelight@0: return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); truelight@0: tron@6150: Vehicle *v = vl[0]; tron@6150: skidd13@7928: 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: maedhros@7783: v = new (v) Train(); truelight@0: v->unitnumber = unit_num; tron@3153: v->direction = DiagDirToDir(dir); tron@1986: v->tile = tile; truelight@0: v->owner = _current_player; tron@3491: v->x_pos = x; tron@3491: v->y_pos = y; tron@6150: v->z_pos = GetSlopeZ(x, y); rubidium@5993: v->u.rail.track = TRACK_BIT_DEPOT; truelight@0: v->vehstatus = VS_HIDDEN | VS_STOPPED | VS_DEFPAL; truelight@0: v->spritenum = rvi->image_index; truelight@0: v->cargo_type = rvi->cargo_type; peter1138@3870: v->cargo_subtype = 0; truelight@0: v->cargo_cap = rvi->capacity; truelight@0: v->max_speed = rvi->max_speed; rubidium@6950: v->value = value.GetCost(); truelight@1266: v->last_station_visited = INVALID_STATION; truelight@0: v->dest_tile = 0; truelight@193: tron@2477: v->engine_type = p1; truelight@0: tron@5823: const Engine *e = GetEngine(p1); truelight@0: v->reliability = e->reliability; truelight@0: v->reliability_spd_dec = e->reliability_spd_dec; truelight@0: v->max_age = e->lifelength * 366; truelight@193: truelight@0: v->string_id = STR_SV_TRAIN_NAME; tron@5823: 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; truelight@0: v->cur_image = 0xAC2; peter1138@2804: v->random_bits = VehicleRandomBits(); truelight@0: maedhros@6176: v->vehicle_flags = 0; skidd13@7931: if (e->flags & ENGINE_EXCLUSIVE_PREVIEW) SetBit(v->vehicle_flags, VF_BUILT_AS_PROTOTYPE); maedhros@6176: rubidium@6643: v->group_id = DEFAULT_GROUP; rubidium@6643: bjarni@2676: v->subtype = 0; bjarni@2676: SetFrontEngine(v); bjarni@2676: SetTrainEngine(v); bjarni@2676: truelight@0: VehiclePositionChanged(v); truelight@0: belugas@5868: if (rvi->railveh_type == RAILVEH_MULTIHEAD) { bjarni@2676: SetMultiheaded(v); peter1138@2602: AddRearEngineToMultiheadedTrain(vl[0], vl[1], true); bjarni@2676: /* Now we need to link the front and rear engines together bjarni@2676: * other_multiheaded_part is the pointer that links to the other half of the engine bjarni@2676: * vl[0] is the front and vl[1] is the rear bjarni@2676: */ bjarni@2676: vl[0]->u.rail.other_multiheaded_part = vl[1]; bjarni@2676: vl[1]->u.rail.other_multiheaded_part = vl[0]; peter1138@2602: } else { maedhros@6857: AddArticulatedParts(vl, VEH_TRAIN); bjarni@2244: } truelight@0: hackykid@1905: TrainConsistChanged(v); truelight@0: UpdateTrainAcceleration(v); rubidium@6643: UpdateTrainGroupID(v); bjarni@2244: skidd13@7928: 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@5944: if (IsLocalPlayer()) bjarni@7425: InvalidateAutoreplaceWindow(v->engine_type, v->group_id); // updates the replace Train window bjarni@5944: bjarni@5944: 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@6150: int count = 0; rubidium@7492: for (; v != NULL; v = v->Next()) { peter1138@2844: /* This count is used by the depot code to determine the number of engines peter1138@2844: * in the consist. Exclude articulated parts so that autoreplacing to bjarni@3986: * engines with more articulated parts than before works correctly. bjarni@3985: * bjarni@3985: * Also skip counting rear ends of multiheaded engines */ bjarni@7526: if (!IsArticulatedPart(v) && !IsRearDualheaded(v)) count++; rubidium@5993: if (v->u.rail.track != TRACK_BIT_DEPOT || v->tile != tile || bjarni@4529: (IsFrontEngine(v) && needs_to_be_stopped && !(v->vehstatus & VS_STOPPED))) { tron@1472: return -1; tron@1472: } tron@1472: } truelight@0: truelight@0: return count; truelight@0: } truelight@0: bjarni@4529: /* Used to check if the train is inside the depot and verifying that the VS_STOPPED flag is set */ rubidium@5587: int CheckTrainStoppedInDepot(const Vehicle *v) bjarni@4529: { bjarni@4529: return CheckTrainInDepot(v, true); bjarni@4529: } bjarni@4529: bjarni@4529: /* Used to check if the train is inside the depot, but not checking the VS_STOPPED flag */ bjarni@4529: inline bool CheckTrainIsInsideDepot(const Vehicle *v) bjarni@4529: { tron@6150: return CheckTrainInDepot(v, false) > 0; bjarni@4529: } bjarni@4529: peter1138@2602: /** peter1138@2602: * Unlink a rail wagon from the consist. peter1138@2602: * @param v Vehicle to remove. peter1138@2602: * @param first The first vehicle of the consist. peter1138@2602: * @return The first vehicle of the consist. peter1138@2602: */ truelight@0: static Vehicle *UnlinkWagon(Vehicle *v, Vehicle *first) truelight@0: { belugas@6422: /* unlinking the first vehicle of the chain? */ truelight@0: if (v == first) { peter1138@2602: v = GetNextVehicle(v); tron@1472: if (v == NULL) return NULL; Darkvater@1766: bjarni@2676: if (IsTrainWagon(v)) SetFreeWagon(v); bjarni@2676: truelight@0: return v; truelight@0: } Darkvater@1766: tron@6150: Vehicle *u; peter1138@2602: for (u = first; GetNextVehicle(u) != v; u = GetNextVehicle(u)) {} rubidium@7493: GetLastEnginePart(u)->SetNext(GetNextVehicle(v)); Darkvater@1766: return first; truelight@0: } truelight@0: tron@1472: static Vehicle *FindGoodVehiclePos(const Vehicle *src) truelight@0: { truelight@0: Vehicle *dst; tron@2477: EngineID eng = src->engine_type; truelight@0: TileIndex tile = src->tile; truelight@0: truelight@0: FOR_ALL_VEHICLES(dst) { rubidium@7696: if (dst->type == VEH_TRAIN && IsFreeWagon(dst) && dst->tile == tile && !HASBITS(dst->vehstatus, VS_CRASHED)) { belugas@6422: /* check so all vehicles in the line have the same engine. */ truelight@0: Vehicle *v = dst; tron@1472: truelight@0: while (v->engine_type == eng) { rubidium@7492: v = v->Next(); tron@1472: if (v == NULL) return dst; truelight@0: } truelight@0: } truelight@0: } truelight@0: truelight@0: return NULL; truelight@0: } truelight@0: bjarni@2676: /* bjarni@2676: * add a vehicle v behind vehicle dest bjarni@2676: * use this function since it sets flags as needed bjarni@2676: */ bjarni@2676: static void AddWagonToConsist(Vehicle *v, Vehicle *dest) bjarni@2676: { rubidium@7497: UnlinkWagon(v, v->First()); bjarni@2676: if (dest == NULL) return; bjarni@2676: rubidium@7497: Vehicle *next = dest->Next(); rubidium@7492: dest->SetNext(v); rubidium@7497: v->SetNext(next); bjarni@2676: ClearFreeWagon(v); bjarni@2676: ClearFrontEngine(v); bjarni@2676: } bjarni@2676: bjarni@2676: /* bjarni@2676: * move around on the train so rear engines are placed correctly according to the other engines bjarni@2676: * always call with the front engine bjarni@2676: */ bjarni@2676: static void NormaliseTrainConsist(Vehicle *v) bjarni@2676: { bjarni@2676: if (IsFreeWagon(v)) return; bjarni@2676: bjarni@2676: assert(IsFrontEngine(v)); bjarni@2676: tron@2952: for (; v != NULL; v = GetNextVehicle(v)) { bjarni@2676: if (!IsMultiheaded(v) || !IsTrainEngine(v)) continue; bjarni@2676: bjarni@2676: /* make sure that there are no free cars before next engine */ tron@6150: Vehicle *u; rubidium@7492: for (u = v; u->Next() != NULL && !IsTrainEngine(u->Next()); u = u->Next()) {} bjarni@2676: bjarni@2676: if (u == v->u.rail.other_multiheaded_part) continue; bjarni@2676: AddWagonToConsist(v->u.rail.other_multiheaded_part, u); bjarni@2676: } bjarni@2676: } bjarni@2676: Darkvater@1784: /** Move a rail vehicle around inside the depot. tron@3491: * @param tile unused belugas@6422: * @param flags type of operation Darkvater@1784: * @param p1 various bitstuffed elements tron@2639: * - p1 (bit 0 - 15) source vehicle index tron@2639: * - p1 (bit 16 - 31) what wagon to put the source wagon AFTER, XXX - INVALID_VEHICLE to make a new line Darkvater@1784: * @param p2 (bit 0) move all vehicles following the source vehicle truelight@0: */ rubidium@6943: CommandCost CmdMoveRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { tron@2484: VehicleID s = GB(p1, 0, 16); tron@2484: VehicleID d = GB(p1, 16, 16); truelight@0: truelight@4352: if (!IsValidVehicleID(s)) return CMD_ERROR; tron@2484: tron@6150: Vehicle *src = GetVehicle(s); bjarni@1237: rubidium@6259: if (src->type != VEH_TRAIN || !CheckOwnership(src->owner)) return CMD_ERROR; truelight@0: rubidium@7696: /* Do not allow moving crashed vehicles inside the depot, it is likely to cause asserts later */ rubidium@7696: if (HASBITS(src->vehstatus, VS_CRASHED)) return CMD_ERROR; rubidium@7696: belugas@6422: /* if nothing is selected as destination, try and find a matching vehicle to drag to. */ tron@6150: Vehicle *dst; tron@2484: if (d == INVALID_VEHICLE) { Darkvater@4198: dst = IsTrainEngine(src) ? NULL : FindGoodVehiclePos(src); truelight@0: } else { tron@6136: if (!IsValidVehicleID(d)) return CMD_ERROR; tron@2484: dst = GetVehicle(d); rubidium@6259: if (dst->type != VEH_TRAIN || !CheckOwnership(dst->owner)) return CMD_ERROR; rubidium@7696: rubidium@7696: /* Do not allow appending to crashed vehicles, too */ rubidium@7696: if (HASBITS(dst->vehstatus, VS_CRASHED)) return CMD_ERROR; truelight@0: } truelight@0: belugas@6422: /* if an articulated part is being handled, deal with its parent vehicle */ rubidium@7497: while (IsArticulatedPart(src)) src = src->Previous(); peter1138@2602: if (dst != NULL) { rubidium@7497: while (IsArticulatedPart(dst)) dst = dst->Previous(); peter1138@2602: } peter1138@2602: belugas@6422: /* don't move the same vehicle.. */ rubidium@6950: if (src == dst) return CommandCost(); truelight@193: truelight@0: /* locate the head of the two chains */ rubidium@7497: Vehicle *src_head = src->First(); tron@6150: Vehicle *dst_head; peter1138@2602: if (dst != NULL) { rubidium@7497: dst_head = dst->First(); tron@6136: if (dst_head->tile != src_head->tile) return CMD_ERROR; belugas@6422: /* Now deal with articulated part of destination wagon */ peter1138@2602: dst = GetLastEnginePart(dst); tron@6136: } else { tron@6136: dst_head = NULL; peter1138@2602: } truelight@193: bjarni@7526: if (IsRearDualheaded(src)) return_cmd_error(STR_REAR_ENGINE_FOLLOW_FRONT_ERROR); bjarni@2676: belugas@6422: /* when moving all wagons, we can't have the same src_head and dst_head */ skidd13@7928: if (HasBit(p2, 0) && src_head == dst_head) return CommandCost(); truelight@0: peter1138@2883: { peter1138@2883: int max_len = _patches.mammoth_trains ? 100 : 9; peter1138@2883: belugas@6422: /* check if all vehicles in the source train are stopped inside a depot. */ tron@6150: int src_len = CheckTrainStoppedInDepot(src_head); tron@3183: if (src_len < 0) return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED); peter1138@2883: belugas@6422: /* check the destination row if the source and destination aren't the same. */ peter1138@2883: if (src_head != dst_head) { peter1138@2883: int dst_len = 0; peter1138@2883: peter1138@2883: if (dst_head != NULL) { belugas@6422: /* check if all vehicles in the dest train are stopped. */ peter1138@2883: dst_len = CheckTrainStoppedInDepot(dst_head); tron@3183: if (dst_len < 0) return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED); peter1138@2883: } peter1138@2883: belugas@6422: /* We are moving between rows, so only count the wagons from the source belugas@6422: * row that are being moved. */ skidd13@7928: if (HasBit(p2, 0)) { peter1138@2883: const Vehicle *u; peter1138@2883: for (u = src_head; u != src && u != NULL; u = GetNextVehicle(u)) peter1138@2883: src_len--; peter1138@2883: } else { belugas@6422: /* If moving only one vehicle, just count that. */ peter1138@2883: src_len = 1; peter1138@2883: } peter1138@2883: peter1138@2883: if (src_len + dst_len > max_len) { belugas@6422: /* Abort if we're adding too many wagons to a train. */ peter1138@2883: if (dst_head != NULL && IsFrontEngine(dst_head)) return_cmd_error(STR_8819_TRAIN_TOO_LONG); belugas@6422: /* Abort if we're making a train on a new row. */ peter1138@2883: if (dst_head == NULL && IsTrainEngine(src)) return_cmd_error(STR_8819_TRAIN_TOO_LONG); peter1138@2883: } peter1138@2883: } else { belugas@6422: /* Abort if we're creating a new train on an existing row. */ peter1138@2883: if (src_len > max_len && src == src_head && IsTrainEngine(GetNextVehicle(src_head))) peter1138@2883: return_cmd_error(STR_8819_TRAIN_TOO_LONG); peter1138@2883: } peter1138@2883: } peter1138@2883: belugas@6422: /* moving a loco to a new line?, then we need to assign a unitnumber. */ bjarni@2676: if (dst == NULL && !IsFrontEngine(src) && IsTrainEngine(src)) { rubidium@6259: 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) { rubidium@6643: /* If we move the front Engine and if the second vehicle is not an engine rubidium@6643: add the whole vehicle to the DEFAULT_GROUP */ rubidium@6643: if (IsFrontEngine(src) && !IsDefaultGroupID(src->group_id)) { rubidium@7529: Vehicle *v = GetNextVehicle(src); rubidium@7529: rubidium@7529: if (v != NULL && IsTrainEngine(v)) { rubidium@7529: v->group_id = src->group_id; rubidium@7529: src->group_id = DEFAULT_GROUP; rubidium@6643: } rubidium@6643: } rubidium@6643: skidd13@7928: if (HasBit(p2, 0)) { belugas@6422: /* unlink ALL wagons */ truelight@0: if (src != src_head) { truelight@0: Vehicle *v = src_head; peter1138@2602: while (GetNextVehicle(v) != src) v = GetNextVehicle(v); rubidium@7493: GetLastEnginePart(v)->SetNext(NULL); hackykid@1917: } else { bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, src_head->tile); // We removed a line hackykid@1917: src_head = NULL; truelight@0: } truelight@0: } else { belugas@6422: /* if moving within the same chain, dont use dst_head as it may get invalidated */ Darkvater@4198: if (src_head == dst_head) dst_head = NULL; belugas@6422: /* unlink single wagon from linked list */ hackykid@1917: src_head = UnlinkWagon(src, src_head); rubidium@7493: GetLastEnginePart(src)->SetNext(NULL); truelight@0: } truelight@0: truelight@0: if (dst == NULL) { bjarni@4739: /* We make a new line in the depot, so we know already that we invalidate the window data */ bjarni@4806: InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile); bjarni@4739: belugas@6422: /* move the train to an empty line. for locomotives, we set the type to TS_Front. for wagons, 4. */ bjarni@2676: if (IsTrainEngine(src)) { bjarni@2676: if (!IsFrontEngine(src)) { belugas@6422: /* setting the type to 0 also involves setting up the orders field. */ bjarni@2676: SetFrontEngine(src); truelight@1024: assert(src->orders == NULL); truelight@0: src->num_orders = 0; rubidium@6643: rubidium@6643: // Decrease the engines number of the src engine_type rubidium@6643: if (!IsDefaultGroupID(src->group_id) && IsValidGroupID(src->group_id)) { rubidium@6643: GetGroup(src->group_id)->num_engines[src->engine_type]--; rubidium@6643: } rubidium@6643: rubidium@6643: // If we move an engine to a new line affect it to the DEFAULT_GROUP rubidium@6643: src->group_id = DEFAULT_GROUP; truelight@0: } truelight@0: } else { bjarni@2676: SetFreeWagon(src); truelight@0: } hackykid@1917: dst_head = src; truelight@0: } else { bjarni@2676: if (IsFrontEngine(src)) { belugas@6422: /* the vehicle was previously a loco. need to free the order list and delete vehicle windows etc. */ truelight@0: DeleteWindowById(WC_VEHICLE_VIEW, src->index); truelight@1024: DeleteVehicleOrders(src); rubidium@7529: RemoveVehicleFromGroup(src); truelight@0: } truelight@193: bjarni@4739: if (IsFrontEngine(src) || IsFreeWagon(src)) { bjarni@4739: InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile); bjarni@4739: ClearFrontEngine(src); bjarni@4739: ClearFreeWagon(src); bjarni@4739: src->unitnumber = 0; // doesn't occupy a unitnumber anymore. bjarni@4739: } truelight@0: belugas@6422: /* link in the wagon(s) in the chain. */ truelight@0: { tron@1472: Vehicle *v; tron@1472: peter1138@2602: for (v = src; GetNextVehicle(v) != NULL; v = GetNextVehicle(v)); rubidium@7492: GetLastEnginePart(v)->SetNext(dst->Next()); truelight@0: } rubidium@7492: dst->SetNext(src); truelight@0: } rubidium@7497: bjarni@2676: if (src->u.rail.other_multiheaded_part != NULL) { bjarni@2676: if (src->u.rail.other_multiheaded_part == src_head) { rubidium@7492: src_head = src_head->Next(); bjarni@2676: } bjarni@2676: AddWagonToConsist(src->u.rail.other_multiheaded_part, src); bjarni@2676: } bjarni@2676: bjarni@2607: /* If there is an engine behind first_engine we moved away, it should become new first_engine bjarni@2607: * To do this, CmdMoveRailVehicle must be called once more bjarni@2676: * we can't loop forever here because next time we reach this line we will have a front engine */ bjarni@2676: if (src_head != NULL && !IsFrontEngine(src_head) && IsTrainEngine(src_head)) { rubidium@6643: /* As in CmdMoveRailVehicle src_head->group_id will be equal to DEFAULT_GROUP rubidium@6643: * we need to save the group and reaffect it to src_head */ rubidium@6643: const GroupID tmp_g = src_head->group_id; tron@3491: CmdMoveRailVehicle(0, flags, src_head->index | (INVALID_VEHICLE << 16), 1); rubidium@6643: SetTrainGroupID(src_head, tmp_g); rubidium@4434: src_head = NULL; // don't do anything more to this train since the new call will do it bjarni@2607: } bjarni@2607: Darkvater@4198: if (src_head != NULL) { bjarni@2676: NormaliseTrainConsist(src_head); hackykid@1905: TrainConsistChanged(src_head); rubidium@6643: 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@6150: } truelight@0: Darkvater@4198: if (dst_head != NULL) { bjarni@2676: NormaliseTrainConsist(dst_head); hackykid@1917: TrainConsistChanged(dst_head); rubidium@6643: 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: rubidium@6950: return CommandCost(); truelight@0: } truelight@0: Darkvater@1784: /** Start/Stop a train. tron@3491: * @param tile unused belugas@6422: * @param flags type of operation Darkvater@1784: * @param p1 train to start/stop Darkvater@1784: * @param p2 unused Darkvater@1784: */ rubidium@6943: CommandCost CmdStartStopTrain(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@4352: if (!IsValidVehicleID(p1)) return CMD_ERROR; bjarni@1237: tron@6150: Vehicle *v = GetVehicle(p1); truelight@0: rubidium@6259: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: peter1138@4244: /* Check if this train can be started/stopped. The callback will fail or peter1138@4244: * return 0xFF if it can. */ tron@6150: 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@5993: 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: } rubidium@6950: return CommandCost(); truelight@0: } truelight@0: Darkvater@1784: /** Sell a (single) train wagon/engine. tron@3491: * @param tile unused belugas@6422: * @param flags type of operation Darkvater@1766: * @param p1 the wagon/engine index Darkvater@1766: * @param p2 the selling mode Darkvater@1784: * - p2 = 0: only sell the single dragged wagon/engine (and any belonging rear-engines) Darkvater@1784: * - p2 = 1: sell the vehicle and all vehicles following it in the chain 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: */ rubidium@6943: CommandCost CmdSellRailWagon(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { peter1138@6789: /* Check if we deleted a vehicle window */ peter1138@6789: Window *w = NULL; peter1138@6789: truelight@4352: if (!IsValidVehicleID(p1) || p2 > 2) return CMD_ERROR; truelight@0: tron@6150: Vehicle *v = GetVehicle(p1); truelight@0: rubidium@6259: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: rubidium@7695: if (HASBITS(v->vehstatus, VS_CRASHED)) return_cmd_error(STR_CAN_T_SELL_DESTROYED_VEHICLE); rubidium@7695: bjarni@1237: SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES); bjarni@1237: rubidium@7497: while (IsArticulatedPart(v)) v = v->Previous(); rubidium@7497: Vehicle *first = v->First(); truelight@193: belugas@6422: /* make sure the vehicle is stopped in the depot */ tron@3183: if (CheckTrainStoppedInDepot(first) < 0) { tron@3183: return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED); tron@3183: } truelight@0: bjarni@7526: if (IsRearDualheaded(v)) return_cmd_error(STR_REAR_ENGINE_FOLLOW_FRONT_ERROR); bjarni@2676: Darkvater@1842: if (flags & DC_EXEC) { bjarni@2676: if (v == first && IsFrontEngine(first)) { peter1138@6789: w = FindWindowById(WC_VEHICLE_VIEW, first->index); peter1138@6789: if (w != NULL) DeleteWindow(w); bjarni@2618: } truelight@0: InvalidateWindow(WC_VEHICLE_DEPOT, first->tile); Darkvater@1779: RebuildVehicleLists(); Darkvater@1766: } Darkvater@1766: rubidium@6950: CommandCost cost; Darkvater@1766: switch (p2) { Darkvater@1766: case 0: case 2: { /* Delete given wagon */ Darkvater@1766: bool switch_engine = false; // update second wagon to engine? Darkvater@1766: byte ori_subtype = v->subtype; // backup subtype of deleted wagon in case DeleteVehicle() changes Darkvater@1766: Darkvater@1766: /* 1. Delete the engine, if it is dualheaded also delete the matching bjarni@2676: * rear engine of the loco (from the point of deletion onwards) */ bjarni@2676: Vehicle *rear = (IsMultiheaded(v) && bjarni@2676: IsTrainEngine(v)) ? v->u.rail.other_multiheaded_part : NULL; bjarni@2676: Darkvater@1766: if (rear != NULL) { rubidium@6990: cost.AddCost(-rear->value); Darkvater@1766: if (flags & DC_EXEC) { bjarni@2676: UnlinkWagon(rear, first); bjarni@5256: DeleteDepotHighlightOfVehicle(rear); rubidium@7398: delete rear; truelight@0: } truelight@0: } Darkvater@1766: 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@6150: Vehicle *new_f = GetNextVehicle(first); Darkvater@1766: Darkvater@1766: /* 2.2 If there are wagons present after the deleted front engine, check rubidium@6322: * if the second wagon (which will be first) is an engine. If it is one, rubidium@6322: * promote it as a new train, retaining the unitnumber, orders */ rubidium@6322: if (new_f != NULL && IsTrainEngine(new_f)) { rubidium@6322: switch_engine = true; rubidium@6322: /* Copy important data from the front engine */ rubidium@6322: new_f->unitnumber = first->unitnumber; rubidium@6322: new_f->current_order = first->current_order; rubidium@6322: new_f->cur_order_index = first->cur_order_index; rubidium@6322: new_f->orders = first->orders; rubidium@6322: new_f->num_orders = first->num_orders; rubidium@7529: new_f->group_id = first->group_id; rubidium@6322: rubidium@6322: if (first->prev_shared != NULL) { rubidium@6322: first->prev_shared->next_shared = new_f; rubidium@6322: new_f->prev_shared = first->prev_shared; Darkvater@1766: } rubidium@6322: rubidium@6322: if (first->next_shared != NULL) { rubidium@6322: first->next_shared->prev_shared = new_f; rubidium@6322: new_f->next_shared = first->next_shared; rubidium@6322: } rubidium@6322: rubidium@6322: /* rubidium@6322: * Remove all order information from the front train, to rubidium@6322: * prevent the order and the shared order list to be rubidium@6322: * destroyed by Destroy/DeleteVehicle. rubidium@6322: */ rubidium@6322: first->orders = NULL; rubidium@6322: first->prev_shared = NULL; rubidium@6322: first->next_shared = NULL; rubidium@7529: first->group_id = DEFAULT_GROUP; rubidium@6322: peter1138@6789: /* If we deleted a window then open a new one for the 'new' train */ rubidium@7486: if (IsLocalPlayer() && w != NULL) ShowVehicleViewWindow(new_f); Darkvater@1766: } Darkvater@1766: } Darkvater@1766: Darkvater@1766: /* 3. Delete the requested wagon */ rubidium@6950: cost.AddCost(-v->value); Darkvater@1766: if (flags & DC_EXEC) { Darkvater@1766: first = UnlinkWagon(v, first); bjarni@5256: DeleteDepotHighlightOfVehicle(v); rubidium@7398: delete v; Darkvater@1766: Darkvater@1766: /* 4 If the second wagon was an engine, update it to front_engine 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); rubidium@6643: 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 */ skidd13@7928: if (p2 == 2 && HasBit(ori_subtype, Train_Front)) { tron@6150: 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@6150: 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) { rubidium@6950: cost.AddCost(-rear->value); maedhros@6785: maedhros@6785: /* If this is a multiheaded vehicle with nothing maedhros@6785: * between the parts, tmp will be pointing to the maedhros@6785: * rear part, which is unlinked from the train and maedhros@6785: * deleted here. However, because tmp has already maedhros@6785: * been set it needs to be updated now so that the maedhros@6785: * loop never sees the rear part. */ maedhros@6785: if (tmp == rear) tmp = GetNextVehicle(tmp); maedhros@6785: bjarni@2676: if (flags & DC_EXEC) { bjarni@2676: first = UnlinkWagon(rear, first); bjarni@5256: DeleteDepotHighlightOfVehicle(rear); rubidium@7398: delete rear; bjarni@2676: } bjarni@2676: } bjarni@2676: } else if (v->u.rail.other_multiheaded_part != NULL) { bjarni@2676: /* The front to this engine is earlier in this train. Do nothing */ tron@2549: continue; tron@2549: } Darkvater@1766: } Darkvater@1766: rubidium@6950: cost.AddCost(-v->value); Darkvater@1766: if (flags & DC_EXEC) { Darkvater@1766: first = UnlinkWagon(v, first); bjarni@5256: DeleteDepotHighlightOfVehicle(v); rubidium@7398: delete v; Darkvater@1766: } Darkvater@1766: } Darkvater@1766: hackykid@1905: /* 3. If it is still a valid train after selling, update its acceleration and cached values */ tron@3017: if (flags & DC_EXEC && first != NULL) { bjarni@2676: NormaliseTrainConsist(first); hackykid@1905: TrainConsistChanged(first); rubidium@6643: 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: rubidium@6558: void Train::UpdateDeltaXY(Direction direction) truelight@0: { rubidium@6558: #define MKIT(a, b, c, d) ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) | ((d & 0xFF) << 0) truelight@0: static const uint32 _delta_xy_table[8] = { truelight@0: MKIT(3, 3, -1, -1), truelight@0: MKIT(3, 7, -1, -3), truelight@0: MKIT(3, 3, -1, -1), truelight@0: MKIT(7, 3, -3, -1), truelight@0: MKIT(3, 3, -1, -1), truelight@0: MKIT(3, 7, -1, -3), truelight@0: MKIT(3, 3, -1, -1), truelight@0: MKIT(7, 3, -3, -1), truelight@0: }; truelight@0: #undef MKIT truelight@0: truelight@0: uint32 x = _delta_xy_table[direction]; rubidium@6558: this->x_offs = GB(x, 0, 8); rubidium@6558: this->y_offs = GB(x, 8, 8); rubidium@6558: this->sprite_width = GB(x, 16, 8); rubidium@6558: this->sprite_height = GB(x, 24, 8); rubidium@6558: this->z_height = 6; truelight@0: } truelight@0: truelight@0: static void UpdateVarsAfterSwap(Vehicle *v) truelight@0: { rubidium@6558: v->UpdateDeltaXY(v->direction); rubidium@7134: v->cur_image = v->GetImage(v->direction); truelight@0: BeginVehicleMove(v); truelight@0: VehiclePositionChanged(v); truelight@0: EndVehicleMove(v); truelight@0: } truelight@0: 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@6150: byte flag1 = *swap_flag1; tron@6150: byte flag2 = *swap_flag2; truelight@954: truelight@954: /* Clear the flags */ skidd13@7929: ClrBit(*swap_flag1, VRF_GOINGUP); skidd13@7929: ClrBit(*swap_flag1, VRF_GOINGDOWN); skidd13@7929: ClrBit(*swap_flag2, VRF_GOINGUP); skidd13@7929: ClrBit(*swap_flag2, VRF_GOINGDOWN); truelight@954: truelight@954: /* Reverse the rail-flags (if needed) */ skidd13@7928: if (HasBit(flag1, VRF_GOINGUP)) { skidd13@7931: SetBit(*swap_flag2, VRF_GOINGDOWN); skidd13@7928: } else if (HasBit(flag1, VRF_GOINGDOWN)) { skidd13@7931: SetBit(*swap_flag2, VRF_GOINGUP); truelight@954: } skidd13@7928: if (HasBit(flag2, VRF_GOINGUP)) { skidd13@7931: SetBit(*swap_flag1, VRF_GOINGDOWN); skidd13@7928: } else if (HasBit(flag2, VRF_GOINGDOWN)) { skidd13@7931: SetBit(*swap_flag1, VRF_GOINGUP); truelight@954: } truelight@954: } truelight@954: truelight@0: static void ReverseTrainSwapVeh(Vehicle *v, int l, int r) truelight@0: { truelight@0: Vehicle *a, *b; truelight@0: truelight@0: /* locate vehicles to swap */ rubidium@7492: for (a = v; l != 0; l--) a = a->Next(); rubidium@7492: for (b = v; r != 0; r--) b = b->Next(); truelight@0: truelight@0: if (a != b) { truelight@0: /* swap the hidden bits */ truelight@0: { truelight@0: uint16 tmp = (a->vehstatus & ~VS_HIDDEN) | (b->vehstatus&VS_HIDDEN); truelight@0: b->vehstatus = (b->vehstatus & ~VS_HIDDEN) | (a->vehstatus&VS_HIDDEN); truelight@0: a->vehstatus = tmp; truelight@0: } truelight@193: tron@5733: Swap(a->u.rail.track, b->u.rail.track); tron@5733: Swap(a->direction, b->direction); truelight@0: truelight@0: /* toggle direction */ rubidium@5993: if (a->u.rail.track != TRACK_BIT_DEPOT) a->direction = ReverseDir(a->direction); rubidium@5993: if (b->u.rail.track != TRACK_BIT_DEPOT) b->direction = ReverseDir(b->direction); truelight@193: tron@5733: Swap(a->x_pos, b->x_pos); tron@5733: Swap(a->y_pos, b->y_pos); tron@5733: Swap(a->tile, b->tile); tron@5733: Swap(a->z_pos, b->z_pos); truelight@0: truelight@954: SwapTrainFlags(&a->u.rail.flags, &b->u.rail.flags); truelight@954: truelight@0: /* update other vars */ truelight@0: UpdateVarsAfterSwap(a); truelight@0: UpdateVarsAfterSwap(b); truelight@1554: celestar@5385: /* call the proper EnterTile function unless we are in a wormhole */ rubidium@5993: if (a->u.rail.track != TRACK_BIT_WORMHOLE) VehicleEnterTile(a, a->tile, a->x_pos, a->y_pos); rubidium@5993: if (b->u.rail.track != TRACK_BIT_WORMHOLE) VehicleEnterTile(b, b->tile, b->x_pos, b->y_pos); truelight@0: } else { rubidium@5993: if (a->u.rail.track != TRACK_BIT_DEPOT) a->direction = ReverseDir(a->direction); truelight@193: UpdateVarsAfterSwap(a); truelight@1554: rubidium@5993: if (a->u.rail.track != TRACK_BIT_WORMHOLE) VehicleEnterTile(a, a->tile, a->x_pos, a->y_pos); truelight@0: } celestar@3355: celestar@3355: /* Update train's power incase tiles were different rail type */ celestar@3355: TrainPowerChanged(v); truelight@0: } truelight@0: 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: { rubidium@6259: 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@6150: Vehicle *base = v; rubidium@7492: Vehicle *first = base->Next(); tron@6150: uint length = CountVehiclesInChain(v); hackykid@1922: hackykid@1922: while (length > 2) { belugas@6422: /* find pairwise matching wagon belugas@6422: * start<>end, start+1<>end-1, ... */ tron@6150: Vehicle *last = first; rubidium@7492: for (uint i = length - 3; i > 0; i--) last = last->Next(); tron@6150: tron@6150: 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) { belugas@6422: /* disconnect last car to make sure only this subset moves */ rubidium@7492: Vehicle *tempnext = last->Next(); rubidium@7492: last->SetNext(NULL); hackykid@1922: tron@6150: for (int i = 0; i < differential; i++) TrainController(first, false); hackykid@1922: rubidium@7492: last->SetNext(tempnext); hackykid@1922: } hackykid@1922: hackykid@1922: base = first; rubidium@7492: 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: belugas@6422: /* count number of vehicles */ tron@6150: int r = -1; tron@6150: const Vehicle *u = v; rubidium@7492: 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@6150: 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: skidd13@7929: ClrBit(v->u.rail.flags, VRF_REVERSING); truelight@0: } truelight@0: Darkvater@1784: /** Reverse train. tron@3491: * @param tile unused belugas@6422: * @param flags type of operation Darkvater@1784: * @param p1 train to reverse bjarni@3256: * @param p2 if true, reverse a unit in a train (needs to be in a depot) Darkvater@1784: */ rubidium@6943: CommandCost CmdReverseTrainDirection(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@4352: if (!IsValidVehicleID(p1)) return CMD_ERROR; bjarni@1237: tron@6150: Vehicle *v = GetVehicle(p1); truelight@0: rubidium@6259: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: bjarni@3256: if (p2) { belugas@6422: /* turn a single unit around */ bjarni@3256: skidd13@7928: if (IsMultiheaded(v) || HasBit(EngInfo(v->engine_type)->callbackmask, CBM_VEHICLE_ARTIC_ENGINE)) { bjarni@3256: return_cmd_error(STR_ONLY_TURN_SINGLE_UNIT); bjarni@3256: } bjarni@3256: rubidium@7497: Vehicle *front = v->First(); belugas@6422: /* make sure the vehicle is stopped in the depot */ bjarni@3256: if (CheckTrainStoppedInDepot(front) < 0) { bjarni@3256: return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED); bjarni@3256: } bjarni@3257: bjarni@3257: if (flags & DC_EXEC) { skidd13@7932: ToggleBit(v->u.rail.flags, VRF_REVERSE_DIRECTION); peter1138@4856: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); peter1138@4856: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); bjarni@3257: } bjarni@3257: } else { rubidium@7476: /* turn the whole train around */ rubidium@7476: if (v->vehstatus & VS_CRASHED || v->breakdown_ctr != 0) return CMD_ERROR; bjarni@3257: bjarni@3257: if (flags & DC_EXEC) { bjarni@3256: if (_patches.realistic_acceleration && v->cur_speed != 0) { skidd13@7932: ToggleBit(v->u.rail.flags, VRF_REVERSING); bjarni@3256: } else { bjarni@3256: v->cur_speed = 0; bjarni@3256: SetLastSpeed(v, 0); bjarni@3256: ReverseTrainDirection(v); bjarni@3256: } truelight@0: } truelight@0: } rubidium@6950: return CommandCost(); truelight@0: } truelight@0: Darkvater@1784: /** Force a train through a red signal tron@3491: * @param tile unused belugas@6422: * @param flags type of operation Darkvater@1784: * @param p1 train to ignore the red signal Darkvater@1784: * @param p2 unused Darkvater@1784: */ rubidium@6943: CommandCost CmdForceTrainProceed(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@4352: if (!IsValidVehicleID(p1)) return CMD_ERROR; bjarni@1237: tron@6150: Vehicle *v = GetVehicle(p1); truelight@0: rubidium@6259: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; truelight@0: tron@2639: if (flags & DC_EXEC) v->u.rail.force_proceed = 0x50; truelight@193: rubidium@6950: return CommandCost(); truelight@0: } truelight@0: Darkvater@1802: /** Refits a train to the specified cargo type. tron@3491: * @param tile unused belugas@6422: * @param flags type of operation Darkvater@1802: * @param p1 vehicle ID of the train to refit peter1138@3954: * param p2 various bitstuffed elements peter1138@3954: * - p2 = (bit 0-7) - the new cargo type to refit to peter1138@3954: * - p2 = (bit 8-15) - the new cargo subtype to refit to maedhros@6546: * - p2 = (bit 16) - refit only this vehicle maedhros@6546: * @return cost of refit or error Darkvater@1802: */ rubidium@6943: CommandCost CmdRefitRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { tron@2635: CargoID new_cid = GB(p2, 0, 8); peter1138@3954: byte new_subtype = GB(p2, 8, 8); skidd13@7928: bool only_this = HasBit(p2, 16); bjarni@842: truelight@4352: if (!IsValidVehicleID(p1)) return CMD_ERROR; tron@915: tron@6150: Vehicle *v = GetVehicle(p1); bjarni@1237: rubidium@6259: if (v->type != VEH_TRAIN || !CheckOwnership(v->owner)) return CMD_ERROR; bjarni@2244: if (CheckTrainStoppedInDepot(v) < 0) return_cmd_error(STR_TRAIN_MUST_BE_STOPPED); Darkvater@1802: Darkvater@1802: /* Check cargo */ peter1138@6316: if (new_cid >= NUM_CARGO) return CMD_ERROR; truelight@0: bjarni@1237: SET_EXPENSES_TYPE(EXPENSES_TRAIN_RUN); bjarni@1237: rubidium@6950: CommandCost cost; tron@6150: uint num = 0; tron@915: truelight@0: do { pasky@491: /* XXX: We also refit all the attached wagons en-masse if they pasky@491: * can be refitted. This is how TTDPatch does it. TODO: Have pasky@491: * some nice [Refit] button near each wagon. --pasky */ peter1138@2704: if (!CanRefitTo(v->engine_type, new_cid)) continue; Darkvater@1802: hackykid@1859: if (v->cargo_cap != 0) { hackykid@1895: uint16 amount = CALLBACK_FAILED; hackykid@1895: skidd13@7928: if (HasBit(EngInfo(v->engine_type)->callbackmask, CBM_VEHICLE_REFIT_CAPACITY)) { peter1138@3988: /* Back up the vehicle's cargo type */ hackykid@1895: CargoID temp_cid = v->cargo_type; peter1138@3988: byte temp_subtype = v->cargo_subtype; hackykid@1895: v->cargo_type = new_cid; peter1138@3988: v->cargo_subtype = new_subtype; peter1138@3988: /* Check the refit capacity callback */ peter1138@3390: amount = GetVehicleCallback(CBID_VEHICLE_REFIT_CAPACITY, 0, 0, v->engine_type, v); peter1138@3988: /* Restore the original cargo type */ hackykid@1895: v->cargo_type = temp_cid; peter1138@3988: v->cargo_subtype = temp_subtype; hackykid@1895: } hackykid@1895: hackykid@1895: if (amount == CALLBACK_FAILED) { // callback failed or not used, use default tron@6150: const RailVehicleInfo *rvi = RailVehInfo(v->engine_type); hackykid@1883: CargoID old_cid = rvi->cargo_type; tron@3017: /* normally, the capacity depends on the cargo type, a rail vehicle can tron@3017: * carry twice as much mail/goods as normal cargo, and four times as tron@3017: * many passengers tron@3017: */ hackykid@1883: amount = rvi->capacity; tron@3017: switch (old_cid) { tron@3017: case CT_PASSENGERS: break; tron@3017: case CT_MAIL: tron@3017: case CT_GOODS: amount *= 2; break; tron@3017: default: amount *= 4; break; tron@3017: } tron@3017: switch (new_cid) { tron@3017: case CT_PASSENGERS: break; tron@3017: case CT_MAIL: tron@3017: case CT_GOODS: amount /= 2; break; tron@3017: default: amount /= 4; break; tron@3017: } tron@6150: } hackykid@1883: hackykid@1883: if (amount != 0) { peter1138@4242: if (new_cid != v->cargo_type) { rubidium@6950: cost.AddCost(GetRefitCost(v->engine_type)); peter1138@4242: } peter1138@4242: hackykid@1883: num += amount; hackykid@1883: if (flags & DC_EXEC) { rubidium@7010: v->cargo.Truncate((v->cargo_type == new_cid) ? amount : 0); hackykid@1883: v->cargo_type = new_cid; hackykid@1883: v->cargo_cap = amount; peter1138@3954: v->cargo_subtype = new_subtype; hackykid@1883: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); hackykid@1883: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); peter1138@3954: RebuildVehicleLists(); hackykid@1883: } truelight@0: } truelight@193: } rubidium@7492: } while ((v = v->Next()) != NULL && !only_this); truelight@0: peter1138@3008: _returned_refit_capacity = num; truelight@0: peter1138@4708: /* Update the train's cached variables */ rubidium@7497: if (flags & DC_EXEC) TrainConsistChanged(GetVehicle(p1)->First()); peter1138@4708: truelight@0: return cost; truelight@0: } truelight@0: rubidium@6248: struct TrainFindDepotData { truelight@0: uint best_length; tron@1977: TileIndex tile; tron@2475: PlayerID owner; matthijs@1777: /** tron@2639: * true if reversing is necessary for the train to get to this depot tron@2639: * This value is unused when new depot finding and NPF are both disabled matthijs@1777: */ matthijs@1777: bool reverse; rubidium@6248: }; truelight@0: ludde@2125: static bool NtpCallbFindDepot(TileIndex tile, TrainFindDepotData *tfdd, int track, uint length) truelight@0: { tron@3269: if (IsTileType(tile, MP_RAILWAY) && tron@3269: IsTileOwner(tile, tfdd->owner) && tron@4182: IsRailDepot(tile)) { Darkvater@4406: /* approximate number of tiles by dividing by DIAG_FACTOR */ Darkvater@4406: tfdd->best_length = length / DIAG_FACTOR; tron@3269: tfdd->tile = tile; tron@3269: return true; truelight@0: } truelight@0: ludde@2125: return false; truelight@0: } truelight@0: belugas@6422: /** returns the tile of a depot to goto to. The given vehicle must not be belugas@6422: * crashed! */ KUDr@3900: static TrainFindDepotData FindClosestTrainDepot(Vehicle *v, int max_distance) truelight@0: { tron@6150: assert(!(v->vehstatus & VS_CRASHED)); tron@6150: truelight@0: TrainFindDepotData tfdd; darkvater@308: tfdd.owner = v->owner; darkvater@308: tfdd.best_length = (uint)-1; matthijs@1777: tfdd.reverse = false; darkvater@308: tron@6150: TileIndex tile = v->tile; tron@6150: 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@6150: assert(trackdir != INVALID_TRACKDIR); rubidium@6683: NPFFoundTargetData ftd = NPFRouteToDepotBreadthFirstTwoWay(v->tile, trackdir, last->tile, trackdir_rev, TRANSPORT_RAIL, 0, 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 { belugas@6422: /* search in the forward direction first. */ tron@6150: 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; belugas@6422: /* 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 belugas@6422: * @param flags type of operation Darkvater@1784: * @param p1 train to send to the depot bjarni@4451: * @param p2 various bitmasked elements bjarni@4506: * - p2 bit 0-3 - DEPOT_ flags (see vehicle.h) bjarni@4506: * - p2 bit 8-10 - VLW flag (for mass goto depot) Darkvater@1784: */ rubidium@6943: CommandCost CmdSendTrainToDepot(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { bjarni@4506: if (p2 & DEPOT_MASS_SEND) { bjarni@4506: /* Mass goto depot requested */ Darkvater@4546: if (!ValidVLWFlags(p2 & VLW_MASK)) return CMD_ERROR; rubidium@6259: 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@6150: Vehicle *v = GetVehicle(p1); bjarni@1237: rubidium@6259: 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) { skidd13@7928: 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) { skidd13@7929: ClrBit(v->current_order.flags, OFB_PART_OF_ORDERS); skidd13@7932: ToggleBit(v->current_order.flags, OFB_HALT_IN_DEPOT); bjarni@4510: InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); bjarni@4510: } rubidium@6950: return CommandCost(); 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) { skidd13@7928: 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: } rubidium@6950: return CommandCost(); 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@6150: 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) { maedhros@6502: if (v->current_order.type == OT_LOADING) v->LeaveStation(); maedhros@6502: 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; skidd13@7931: 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 */ maedhros@6502: if (tfdd.reverse) DoCommand(v->tile, v->index, 0, DC_EXEC, CMD_REVERSE_TRAIN_DIRECTION); truelight@0: } truelight@193: rubidium@6950: return CommandCost(); truelight@0: } truelight@0: truelight@0: rubidium@6247: void OnTick_Train() truelight@0: { truelight@0: _age_cargo_skip_counter = (_age_cargo_skip_counter == 0) ? 184 : (_age_cargo_skip_counter - 1); truelight@0: } truelight@0: peter1138@2595: static const int8 _vehicle_smoke_pos[8] = { peter1138@2595: 1, 1, 1, 0, -1, -1, -1, 0 truelight@0: }; truelight@0: 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@6150: const Vehicle* u = v; truelight@0: truelight@0: do { tron@5823: const RailVehicleInfo *rvi = RailVehInfo(v->engine_type); peter1138@2595: int effect_offset = GB(v->u.rail.cached_vis_effect, 0, 4) - 8; peter1138@2595: byte effect_type = GB(v->u.rail.cached_vis_effect, 4, 2); skidd13@7928: bool disable_effect = HasBit(v->u.rail.cached_vis_effect, 6); truelight@0: belugas@6422: /* no smoke? */ belugas@5868: if ((rvi->railveh_type == RAILVEH_WAGON && effect_type == 0) || peter1138@2595: disable_effect || tron@3546: v->vehstatus & VS_HIDDEN) { truelight@0: continue; tron@2639: } truelight@0: belugas@6422: /* No smoke in depots or tunnels */ celestar@3590: if (IsTileDepotType(v->tile, TRANSPORT_RAIL) || IsTunnelTile(v->tile)) continue; celestar@3590: belugas@6422: /* No sparks for electric vehicles on nonelectrified tracks */ tron@6154: if (!HasPowerOnRail(v->u.rail.railtype, GetTileRailType(v->tile))) continue; peter1138@2612: peter1138@2595: if (effect_type == 0) { belugas@6422: /* Use default effect type for engine class. */ tron@5823: effect_type = rvi->engclass; peter1138@2595: } else { peter1138@2595: effect_type--; peter1138@2595: } peter1138@2595: tron@6150: int x = _vehicle_smoke_pos[v->direction] * effect_offset; tron@6150: int y = _vehicle_smoke_pos[(v->direction + 2) % 8] * effect_offset; peter1138@2595: skidd13@7928: if (HasBit(v->u.rail.flags, VRF_REVERSE_DIRECTION)) { bjarni@3256: x = -x; bjarni@3256: y = -y; bjarni@3256: } bjarni@3256: peter1138@2595: switch (effect_type) { truelight@0: case 0: belugas@6422: /* 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: belugas@6422: /* 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: belugas@6422: /* 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: } rubidium@7492: } 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, rubidium@6586: SND_0A_TRAIN_HORN, rubidium@6586: SND_47_MAGLEV_2, rubidium@6586: SND_41_MAGLEV tron@541: }; tron@541: tron@6150: if (PlayVehicleSound(v, VSE_START)) return; tron@6150: tron@2477: EngineID engtype = v->engine_type; rubidium@6586: SndPlayVehicleFx(sfx[RailVehInfo(engtype)->engclass], v); truelight@0: } truelight@0: rubidium@6593: void Train::PlayLeaveStationSound() const rubidium@6593: { rubidium@6593: TrainPlayLeaveStationSound(this); rubidium@6593: } rubidium@6593: truelight@0: static bool CheckTrainStayInDepot(Vehicle *v) truelight@0: { belugas@6422: /* bail out if not all wagons are in the same depot or not in a depot at all */ rubidium@7492: for (const Vehicle *u = v; u != NULL; u = u->Next()) { rubidium@5993: if (u->u.rail.track != TRACK_BIT_DEPOT || u->tile != v->tile) return false; tron@2639: } truelight@0: belugas@6422: /* if the train got no power, then keep it in the depot */ bjarni@4252: if (v->u.rail.cached_power == 0) { bjarni@4252: v->vehstatus |= VS_STOPPED; bjarni@4252: InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); bjarni@4252: return true; bjarni@4252: } bjarni@4252: 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@5587: v->u.rail.track = TRACK_BIT_X; rubidium@5587: if (v->direction & 2) v->u.rail.track = TRACK_BIT_Y; truelight@193: truelight@0: v->vehstatus &= ~VS_HIDDEN; truelight@0: v->cur_speed = 0; truelight@193: rubidium@6558: v->UpdateDeltaXY(v->direction); rubidium@7134: v->cur_image = v->GetImage(v->direction); truelight@0: VehiclePositionChanged(v); 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 */ rubidium@6248: 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@5587: TrackdirByte best_track; rubidium@6248: }; truelight@0: rubidium@5587: static bool NtpCallbFindStation(TileIndex tile, TrainTrackFollowerData *ttfd, Trackdir track, uint length) tron@1977: { belugas@6422: /* heading for nowhere? */ tron@2951: if (ttfd->dest_coords == 0) return false; truelight@0: belugas@6422: /* did we reach the final station? */ tron@3315: if ((ttfd->station_index == INVALID_STATION && tile == ttfd->dest_coords) || ( tron@3315: IsTileType(tile, MP_STATION) && celestar@3334: IsRailwayStation(tile) && tron@3315: GetStationIndex(tile) == ttfd->station_index tron@3315: )) { ludde@2044: /* We do not check for dest_coords if we have a station_index, ludde@2044: * because in that case the dest_coords are just an ludde@2044: * approximation of where the station is */ belugas@6422: belugas@6422: /* found station */ ludde@2125: ttfd->best_track = track; KUDr@6303: ttfd->best_bird_dist = 0; truelight@0: return true; truelight@0: } else { belugas@6422: /* didn't find station, keep track of the best path so far. */ tron@6150: uint dist = DistanceManhattan(tile, ttfd->dest_coords); truelight@0: if (dist < ttfd->best_bird_dist) { truelight@0: ttfd->best_bird_dist = dist; ludde@2125: ttfd->best_track = track; truelight@0: } truelight@0: return false; truelight@0: } truelight@0: } truelight@0: 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] = { belugas@6422: { 0, 9, 2, 9 }, ///< track 1 belugas@6422: { 9, 1, 9, 3 }, ///< track 2 belugas@6422: { 9, 0, 3, 9 }, ///< track upper belugas@6422: { 1, 9, 9, 2 }, ///< track lower belugas@6422: { 3, 2, 9, 9 }, ///< track left belugas@6422: { 9, 9, 1, 0 }, ///< track right truelight@0: }; truelight@0: truelight@0: static const byte _pick_track_table[6] = {1, 3, 2, 2, 0, 0}; truelight@0: truelight@0: /* choose a track */ rubidium@5587: static Track ChooseTrainTrack(Vehicle* v, TileIndex tile, DiagDirection enterdir, TrackBits tracks) truelight@193: { rubidium@5587: Track best_track; belugas@6422: /* pathfinders are able to tell that route was only 'guessed' */ KUDr@4870: bool path_not_found = false; KUDr@4870: peter1138@2758: #ifdef PF_BENCHMARK Darkvater@3341: TIC() truelight@0: #endif truelight@0: truelight@7833: assert((tracks & ~TRACK_BIT_MASK) == 0); truelight@0: matthijs@1247: /* quick return in case only one possible track is available */ truelight@7833: if (KillFirstBit(tracks) == TRACK_BIT_NONE) return FindFirstTrack(tracks); truelight@0: KUDr@3900: if (_patches.yapf.rail_use_yapf) { rubidium@5587: 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@5587: 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@6150: Trackdir trackdir = GetVehicleTrackdir(v); matthijs@1247: assert(trackdir != 0xff); matthijs@1247: rubidium@6683: NPFFoundTargetData ftd = NPFRouteToStationOrTile(tile - TileOffsByDiagDir(enterdir), trackdir, &fstd, TRANSPORT_RAIL, 0, v->owner, v->u.rail.compatible_railtypes); matthijs@1698: matthijs@1698: if (ftd.best_trackdir == 0xff) { belugas@6422: /* We are already at our target. Just do something belugas@6422: * @todo maybe display error? belugas@6422: * @todo: go straight ahead if possible? */ rubidium@5587: 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@6150: int time = NpfEndInterval(perf); Darkvater@5380: 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@6150: tron@6150: 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@5587: 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: belugas@6422: /* 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) { belugas@6422: /* blaha */ rubidium@5587: best_track = FindFirstTrack(tracks); matthijs@1247: } else { rubidium@5587: best_track = TrackdirToTrack(fd.best_track); matthijs@1247: } KUDr@3900: tron@6150: int time = NpfEndInterval(perf); Darkvater@5380: DEBUG(yapf, 4, "[NTPT] %d us - %d rounds - %d open - %d closed -- ", time, 0, 0, 0); truelight@0: } belugas@6422: /* handle "path not found" state */ KUDr@4870: if (path_not_found) { belugas@6422: /* PF didn't find the route */ skidd13@7928: if (!HasBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) { belugas@6422: /* it is first time the problem occurred, set the "path not found" flag */ skidd13@7931: SetBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION); belugas@6422: /* 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 { belugas@6422: /* route found, is the train marked with "path not found" flag? */ skidd13@7928: if (HasBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) { belugas@6422: /* clear the flag as the PF's problem was solved */ skidd13@7929: ClrBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION); belugas@6422: /* 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@5993: 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@6150: TrainTrackFollowerData fd; truelight@0: FillWithStationData(&fd, v); truelight@0: tron@6150: uint reverse_best = 0; truelight@0: truelight@0: assert(v->u.rail.track); truelight@0: tron@6150: 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@5587: 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: rubidium@6683: ftd = NPFRouteToStationOrTileTwoWay(v->tile, trackdir, last->tile, trackdir_rev, &fstd, TRANSPORT_RAIL, 0, 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@6150: int best_track = -1; tron@6150: uint reverse = 0; tron@6150: uint best_bird_dist = 0; tron@6150: uint best_track_dist = 0; tron@6150: tron@2952: for (;;) { matthijs@1247: fd.best_bird_dist = (uint)-1; matthijs@1247: fd.best_track_dist = (uint)-1; matthijs@1247: rubidium@5587: 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@6150: 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)) { maedhros@7070: UpdateVehicleTimetable(v, true); 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: rubidium@7109: /** rubidium@7109: * Reversing because of order change is allowed only just after leaving a rubidium@7109: * station (and the difficulty setting to allowed, of course) rubidium@7109: * this can be detected because only after OT_LEAVESTATION, current_order rubidium@7109: * will be reset to nothing. (That also happens if no order, but in that case rubidium@7109: * it won't hit the point in code where may_reverse is checked) rubidium@7109: */ rubidium@7109: bool may_reverse = v->current_order.type == OT_NOTHING; rubidium@7109: belugas@6422: /* check if we've reached the waypoint? */ tron@555: if (v->current_order.type == OT_GOTO_WAYPOINT && v->tile == v->dest_tile) { maedhros@7070: UpdateVehicleTimetable(v, true); truelight@0: v->cur_order_index++; truelight@0: } truelight@0: belugas@6422: /* 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)) { maedhros@7070: UpdateVehicleTimetable(v, true); truelight@193: v->cur_order_index++; truelight@0: } truelight@0: belugas@6422: /* Get the current order */ tron@2639: if (v->cur_order_index >= v->num_orders) v->cur_order_index = 0; truelight@1024: tron@6150: const Order *order = GetVehicleOrder(v, v->cur_order_index); truelight@0: belugas@6422: /* If no order, do nothing. */ truelight@1024: if (order == NULL) { bjarni@6263: v->current_order.Free(); truelight@0: v->dest_tile = 0; truelight@0: return false; truelight@0: } truelight@0: belugas@6422: /* 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: belugas@6422: /* 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: rubidium@7109: return may_reverse && CheckReverseTrain(v); truelight@0: } truelight@0: rubidium@6553: void Train::MarkDirty() truelight@0: { rubidium@6553: Vehicle *v = this; truelight@0: do { rubidium@7134: v->cur_image = v->GetImage(v->direction); truelight@0: MarkAllViewportsDirty(v->left_coord, v->top_coord, v->right_coord + 1, v->bottom_coord + 1); rubidium@7492: } while ((v = v->Next()) != NULL); rubidium@6553: rubidium@6553: /* need to update acceleration and cached values since the goods on the train changed. */ rubidium@6553: TrainCargoChanged(this); rubidium@6553: UpdateTrainAcceleration(this); truelight@0: } truelight@0: truelight@0: static int UpdateTrainSpeed(Vehicle *v) truelight@0: { truelight@0: uint accel; truelight@0: skidd13@7928: 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@6150: uint spd = v->subspeed + accel * 2; truelight@0: v->subspeed = (byte)spd; celestar@1179: { celestar@1179: int tempmax = v->max_speed; celestar@1179: if (v->cur_speed > v->max_speed) celestar@1179: tempmax = v->cur_speed - (v->cur_speed / 10) - 1; skidd13@7922: v->cur_speed = spd = Clamp(v->cur_speed + ((int)spd >> 8), 0, tempmax); celestar@1179: } truelight@0: 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@6150: Station *st = GetStation(station); truelight@0: if (!(st->had_vehicle_of_type & HVOT_TRAIN)) { truelight@0: st->had_vehicle_of_type |= HVOT_TRAIN; tron@534: SetDParam(0, st->index); tron@6150: uint32 flags = v->owner == _local_player ? tron@6150: NEWS_FLAGS(NM_THIN, NF_VIEWPORT | NF_VEHICLE, NT_ARRIVAL_PLAYER, 0) : tron@6150: 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: rubidium@6550: 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: { tron@6150: byte old_z = v->z_pos; peter1138@7079: v->z_pos = GetSlopeZ(v->x_pos, v->y_pos); truelight@0: truelight@954: if (new_tile) { skidd13@7929: ClrBit(v->u.rail.flags, VRF_GOINGUP); skidd13@7929: ClrBit(v->u.rail.flags, VRF_GOINGDOWN); truelight@954: peter1138@7079: if (v->u.rail.track == TRACK_BIT_X || v->u.rail.track == TRACK_BIT_Y) { peter1138@7079: /* Any track that isn't TRACK_BIT_X or TRACK_BIT_Y cannot be sloped. rubidium@7052: * To check whether the current tile is sloped, and in which rubidium@7052: * direction it is sloped, we get the 'z' at the center of rubidium@7052: * the tile (middle_z) and the edge of the tile (old_z), rubidium@7052: * which we then can compare. */ rubidium@7052: static const int HALF_TILE_SIZE = TILE_SIZE / 2; rubidium@7052: static const int INV_TILE_SIZE_MASK = ~(TILE_SIZE - 1); rubidium@7052: rubidium@7052: byte middle_z = GetSlopeZ((v->x_pos & INV_TILE_SIZE_MASK) | HALF_TILE_SIZE, (v->y_pos & INV_TILE_SIZE_MASK) | HALF_TILE_SIZE); rubidium@7052: rubidium@7052: /* For some reason tunnel tiles are always given as sloped :( rubidium@7052: * But they are not sloped... */ peter1138@7079: if (middle_z != v->z_pos && !IsTunnelTile(TileVirtXY(v->x_pos, v->y_pos))) { skidd13@7931: SetBit(v->u.rail.flags, (middle_z > old_z) ? VRF_GOINGUP : VRF_GOINGDOWN); tron@3184: } truelight@954: } truelight@0: } truelight@0: truelight@0: VehiclePositionChanged(v); truelight@0: EndVehicleMove(v); truelight@0: return old_z; truelight@0: } truelight@0: tron@3157: static const Direction _new_vehicle_direction_table[11] = { rubidium@5587: DIR_N , DIR_NW, DIR_W , INVALID_DIR, rubidium@5587: DIR_NE, DIR_N , DIR_SW, INVALID_DIR, tron@3157: DIR_E , DIR_SE, DIR_S truelight@0: }; truelight@0: 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) || skidd13@7928: HasBit(v->u.rail.compatible_railtypes, GetRailType(tile)) tron@2549: ); truelight@0: } truelight@0: rubidium@6248: struct RailtypeSlowdownParams { truelight@0: byte small_turn, large_turn; truelight@0: byte z_up; // fraction to remove when moving up truelight@0: byte z_down; // fraction to remove when moving down rubidium@6248: }; truelight@0: celestar@3355: static const RailtypeSlowdownParams _railtype_slowdown[] = { truelight@0: // normal accel belugas@6422: {256 / 4, 256 / 2, 256 / 4, 2}, ///< normal belugas@6422: {256 / 4, 256 / 2, 256 / 4, 2}, ///< electrified belugas@6422: {256 / 4, 256 / 2, 256 / 4, 2}, ///< monorail belugas@6422: {0, 256 / 2, 256 / 4, 2}, ///< maglev truelight@0: }; truelight@0: belugas@6422: /** Modify the speed of the vehicle due to a turn */ tron@3157: static void AffectSpeedByDirChange(Vehicle* v, Direction new_dir) truelight@0: { tron@3158: if (_patches.realistic_acceleration) return; tron@3158: tron@6150: DirDiff diff = DirDifference(v->direction, new_dir); tron@3158: if (diff == DIRDIFF_SAME) return; truelight@0: tron@6150: const RailtypeSlowdownParams *rsp = &_railtype_slowdown[v->u.rail.railtype]; tron@3158: v->cur_speed -= (diff == DIRDIFF_45RIGHT || diff == DIRDIFF_45LEFT ? rsp->small_turn : rsp->large_turn) * v->cur_speed >> 8; truelight@0: } truelight@0: belugas@6422: /** Modify the speed of the vehicle due to a change in altitude */ 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@6150: const RailtypeSlowdownParams *rsp = &_railtype_slowdown[v->u.rail.railtype]; truelight@0: truelight@0: if (old_z < v->z_pos) { truelight@0: v->cur_speed -= (v->cur_speed * rsp->z_up >> 8); truelight@0: } else { truelight@0: uint16 spd = v->cur_speed + rsp->z_down; tron@2639: if (spd <= v->max_speed) v->cur_speed = spd; truelight@0: } truelight@0: } truelight@0: tron@3172: static const DiagDirection _otherside_signal_directions[] = { rubidium@5587: 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: 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@6150: 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) rubidium@7010: 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: peter1138@6966: struct TrainCollideChecker { peter1138@6966: Vehicle *v; peter1138@6966: const Vehicle *v_skip; peter1138@6966: uint num; peter1138@6966: }; peter1138@6966: peter1138@6966: static void *FindTrainCollideEnum(Vehicle *v, void *data) peter1138@6966: { peter1138@6966: TrainCollideChecker* tcc = (TrainCollideChecker*)data; peter1138@6966: peter1138@6966: if (v != tcc->v && peter1138@6966: v != tcc->v_skip && peter1138@6966: v->type == VEH_TRAIN && peter1138@6966: v->u.rail.track != TRACK_BIT_DEPOT && skidd13@7923: abs(v->z_pos - tcc->v->z_pos) < 6 && skidd13@7923: abs(v->x_pos - tcc->v->x_pos) < 6 && skidd13@7923: abs(v->y_pos - tcc->v->y_pos) < 6 ) { peter1138@6966: rubidium@7497: Vehicle *coll = v->First(); peter1138@6966: peter1138@6966: /* it can't collide with its own wagons */ peter1138@6966: if (tcc->v == coll || peter1138@6966: (tcc->v->u.rail.track == TRACK_BIT_WORMHOLE && (tcc->v->direction & 2) != (v->direction & 2))) peter1138@6966: return NULL; peter1138@6966: peter1138@6966: /* two drivers + passengers killed in train tcc->v (if it was not crashed already) */ peter1138@6966: if (!(tcc->v->vehstatus & VS_CRASHED)) { peter1138@6966: tcc->num += 2 + CountPassengersInTrain(tcc->v); peter1138@6966: SetVehicleCrashed(tcc->v); peter1138@6966: } peter1138@6966: peter1138@6966: if (!(coll->vehstatus & VS_CRASHED)) { peter1138@6966: /* two drivers + passengers killed in train coll (if it was not crashed already) */ peter1138@6966: tcc->num += 2 + CountPassengersInTrain(coll); peter1138@6966: SetVehicleCrashed(coll); peter1138@6966: } peter1138@6966: } peter1138@6966: peter1138@6966: return NULL; peter1138@6966: } peter1138@6966: belugas@6422: /** tron@1434: * Checks whether the specified train has a collision with another vehicle. If bjarni@2676: * so, destroys this vehicle, and the other vehicle if its subtype has TS_Front. darkvater@22: * Reports the incident in a flashy news item, modifies station ratings and darkvater@22: * plays a sound. darkvater@22: */ truelight@0: static void CheckTrainCollision(Vehicle *v) truelight@0: { truelight@0: /* can't collide in depot */ rubidium@5993: if (v->u.rail.track == TRACK_BIT_DEPOT) return; rubidium@5993: rubidium@5993: assert(v->u.rail.track == TRACK_BIT_WORMHOLE || TileVirtXY(v->x_pos, v->y_pos) == v->tile); truelight@0: tron@6150: TrainCollideChecker tcc; truelight@0: tcc.v = v; rubidium@7492: tcc.v_skip = v->Next(); peter1138@6966: tcc.num = 0; peter1138@6966: peter1138@6966: /* find colliding vehicles */ peter1138@7371: if (v->u.rail.track == TRACK_BIT_WORMHOLE) { peter1138@7371: VehicleFromPos(v->tile, &tcc, FindTrainCollideEnum); peter1138@7371: if (IsBridgeTile(v->tile)) { peter1138@7371: VehicleFromPos(GetOtherBridgeEnd(v->tile), &tcc, FindTrainCollideEnum); peter1138@7371: } else { peter1138@7371: VehicleFromPos(GetOtherTunnelEnd(v->tile), &tcc, FindTrainCollideEnum); peter1138@7371: } peter1138@7371: } else { peter1138@7371: VehicleFromPosXY(v->x_pos, v->y_pos, &tcc, FindTrainCollideEnum); peter1138@7371: } peter1138@6966: peter1138@6966: /* any dead -> no crash */ peter1138@6966: if (tcc.num == 0) return; peter1138@6966: peter1138@6966: SetDParam(0, tcc.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: rubidium@6248: struct VehicleAtSignalData { tron@1432: TileIndex tile; tron@3157: Direction direction; rubidium@6248: }; tron@1432: truelight@0: static void *CheckVehicleAtSignal(Vehicle *v, void *data) truelight@0: { rubidium@5587: const VehicleAtSignalData* vasd = (VehicleAtSignalData*)data; tron@1432: rubidium@6259: 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 */ rubidium@7497: for (prev = v->Previous(); v != NULL; prev = v, v = v->Next()) { peter1138@6871: DiagDirection enterdir = DIAGDIR_BEGIN; peter1138@6871: bool update_signals = false; truelight@0: BeginVehicleMove(v); truelight@193: tron@6153: GetNewVehiclePosResult gp = GetNewVehiclePos(v); rubidium@5993: if (v->u.rail.track != TRACK_BIT_WORMHOLE) { darkvater@22: /* Not inside tunnel */ tron@6152: if (gp.old_tile == gp.new_tile) { darkvater@22: /* Staying in the old tile */ rubidium@5993: if (v->u.rail.track == TRACK_BIT_DEPOT) { rubidium@5994: /* Inside depot */ truelight@0: gp.x = v->x_pos; truelight@0: gp.y = v->y_pos; truelight@0: } else { rubidium@5994: /* Not inside depot */ truelight@742: peter1138@5668: if (IsFrontEngine(v) && !TrainCheckIfLineEnds(v)) return; truelight@742: tron@6150: uint32 r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); skidd13@7928: if (HasBit(r, VETS_CANNOT_ENTER)) { truelight@0: goto invalid_rail; matthijs@1247: } skidd13@7928: if (HasBit(r, VETS_ENTERED_STATION)) { rubidium@5991: TrainEnterStation(v, r >> VETS_STATION_ID_OFFSET); truelight@0: return; truelight@0: } truelight@0: tron@555: if (v->current_order.type == OT_LEAVESTATION) { bjarni@6263: 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@6151: Direction dir = GetNewVehicleDirectionByTile(gp.new_tile, gp.old_tile); peter1138@6871: enterdir = DirToDiagDir(dir); rubidium@5994: assert(IsValidDiagDirection(enterdir)); truelight@193: truelight@0: /* Get the status of the tracks in the new tile and mask truelight@0: * away the bits that aren't reachable. */ rubidium@6683: uint32 ts = GetTileTrackStatus(gp.new_tile, TRANSPORT_RAIL, 0) & _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@6150: uint32 tracks = ts | (ts >> 8); tron@6151: 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@5587: bits &= ~TrackCrossesTracks(FindFirstTrack(v->u.rail.track)); tron@3017: } tron@3017: rubidium@5994: if (bits == TRACK_BIT_NONE) goto invalid_rail; truelight@0: truelight@0: /* Check if the new tile contrains tracks that are compatible truelight@0: * with the current train, if not, bail out. */ rubidium@5994: if (!CheckCompatibleRail(v, gp.new_tile)) goto invalid_rail; truelight@0: tron@6150: TrackBits chosen_track; truelight@0: if (prev == NULL) { truelight@0: /* Currently the locomotive is active. Determine which one of the truelight@0: * available tracks to choose */ tron@6155: 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@6151: if ((tracks >> 16) & chosen_track && v->u.rail.force_proceed == 0) { belugas@6422: /* In front of a red signal belugas@6422: * find the first set bit in ts. need to do it in 2 steps, since tron@6151: * FIND_FIRST_BIT only handles 6 bits at a time. */ tron@6151: Trackdir i = FindFirstTrackdir((TrackdirBits)(uint16)ts); tron@6151: tron@6151: if (!HasSignalOnTrackdir(gp.new_tile, ReverseTrackdir(i))) { tron@6151: v->cur_speed = 0; tron@6151: v->subspeed = 0; tron@6151: v->progress = 255 - 100; tron@6151: if (++v->load_unload_time_rem < _patches.wait_oneway_signal * 20) return; tron@6151: } else if (HasSignalOnTrackdir(gp.new_tile, i)) { tron@6151: v->cur_speed = 0; tron@6151: v->subspeed = 0; rubidium@6491: v->progress = 255 - 10; tron@6151: if (++v->load_unload_time_rem < _patches.wait_twoway_signal * 73) { tron@6151: TileIndex o_tile = gp.new_tile + TileOffsByDiagDir(enterdir); tron@6151: VehicleAtSignalData vasd; tron@6151: vasd.tile = o_tile; tron@6151: vasd.direction = ReverseDir(dir); tron@6151: tron@6151: /* check if a train is waiting on the other side */ tron@6151: if (VehicleFromPos(o_tile, &vasd, CheckVehicleAtSignal) == NULL) return; tron@6151: } tron@6151: } tron@6151: goto reverse_train_direction; tron@6151: } truelight@0: } else { rubidium@5994: static const TrackBits _matching_tracks[8] = { rubidium@5994: TRACK_BIT_LEFT | TRACK_BIT_RIGHT, TRACK_BIT_X, rubidium@5994: TRACK_BIT_UPPER | TRACK_BIT_LOWER, TRACK_BIT_Y, rubidium@5994: TRACK_BIT_LEFT | TRACK_BIT_RIGHT, TRACK_BIT_X, rubidium@5994: TRACK_BIT_UPPER | TRACK_BIT_LOWER, TRACK_BIT_Y rubidium@5994: }; truelight@193: truelight@0: /* The wagon is active, simply follow the prev vehicle. */ rubidium@5587: chosen_track = (TrackBits)(byte)(_matching_tracks[GetDirectionToVehicle(prev, gp.x, gp.y)] & bits); truelight@0: } truelight@0: rubidium@5994: /* Make sure chosen track is a valid track */ rubidium@5994: assert( rubidium@5994: chosen_track == TRACK_BIT_X || chosen_track == TRACK_BIT_Y || rubidium@5994: chosen_track == TRACK_BIT_UPPER || chosen_track == TRACK_BIT_LOWER || rubidium@5994: chosen_track == TRACK_BIT_LEFT || chosen_track == TRACK_BIT_RIGHT); truelight@0: truelight@0: /* Update XY to reflect the entrance to the new tile, and select the direction to use */ tron@6150: const byte *b = _initial_tile_subcoord[FIND_FIRST_BIT(chosen_track)][enterdir]; tron@6150: gp.x = (gp.x & ~0xF) | b[0]; tron@6150: gp.y = (gp.y & ~0xF) | b[1]; tron@6150: Direction chosen_dir = (Direction)b[2]; truelight@193: truelight@0: /* Call the landscape function and tell it that the vehicle entered the tile */ tron@6150: uint32 r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); skidd13@7928: if (HasBit(r, VETS_CANNOT_ENTER)) { truelight@0: goto invalid_rail; matthijs@1247: } truelight@0: rubidium@7492: 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: skidd13@7928: if (!HasBit(r, VETS_ENTERED_WORMHOLE)) { truelight@0: v->tile = gp.new_tile; celestar@3355: tron@6154: if (GetTileRailType(gp.new_tile) != GetTileRailType(gp.old_tile)) { rubidium@7497: TrainPowerChanged(v->First()); celestar@3355: } celestar@3355: truelight@0: v->u.rail.track = chosen_track; matthijs@1330: assert(v->u.rail.track); truelight@0: } truelight@0: peter1138@6871: /* We need to update signal status, but after the vehicle position hash peter1138@6871: * has been updated by AfterSetTrainPos() */ peter1138@6871: update_signals = true; truelight@193: tron@3017: if (prev == NULL) AffectSpeedByDirChange(v, chosen_dir); truelight@0: truelight@0: v->direction = chosen_dir; truelight@0: } truelight@0: } else { rubidium@5994: /* In tunnel or on a bridge */ tron@6141: if (!(v->vehstatus & VS_HIDDEN)) { tron@6141: v->cur_speed = tron@6141: min(v->cur_speed, GetBridge(GetBridgeType(v->tile))->speed); tron@6141: } celestar@5385: skidd13@7928: 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@5385: if (!(v->vehstatus & VS_HIDDEN)) EndVehicleMove(v); ludde@2125: continue; truelight@0: } truelight@0: } truelight@0: truelight@0: /* update image of train, as well as delta XY */ tron@6150: Direction newdir = GetNewVehicleDirection(v, gp.x, gp.y); rubidium@6558: v->UpdateDeltaXY(newdir); rubidium@7134: if (update_image) v->cur_image = v->GetImage(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@6150: byte old_z = AfterSetTrainPos(v, (gp.new_tile != gp.old_tile)); truelight@193: truelight@0: if (prev == NULL) { darkvater@22: /* This is the first vehicle in the train */ truelight@0: AffectSpeedByZChange(v, old_z); truelight@0: } peter1138@6871: peter1138@6871: if (update_signals) { peter1138@6871: if (IsFrontEngine(v)) TrainMovedChangeSignals(gp.new_tile, enterdir); peter1138@6871: peter1138@6871: /* Signals can only change when the first peter1138@6871: * (above) or the last vehicle moves. */ rubidium@7492: if (v->Next() == NULL) TrainMovedChangeSignals(gp.old_tile, ReverseDiagDir(enterdir)); peter1138@6871: } 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 belugas@6484: * @param v the Vehicle of which last wagon is to be removed Darkvater@1418: */ truelight@0: static void DeleteLastWagon(Vehicle *v) truelight@0: { Darkvater@1418: /* Go to the last wagon and delete the link pointing there Darkvater@1418: * *u is then the one-before-last wagon, and *v the last Darkvater@1418: * one which will physicially be removed */ tron@6150: Vehicle *u = v; rubidium@7492: for (; v->Next() != NULL; v = v->Next()) u = v; rubidium@7492: u->SetNext(NULL); truelight@0: 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); rubidium@6643: rubidium@7398: delete v; truelight@0: rubidium@5993: 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@5993: 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@5385: /* We don't need to twist around vehicles if they're not visible */ celestar@5385: if (!(v->vehstatus & VS_HIDDEN)) { tron@3977: v->direction = ChangeDir(v->direction, delta[GB(Random(), 0, 2)]); truelight@0: BeginVehicleMove(v); rubidium@6558: v->UpdateDeltaXY(v->direction); rubidium@7134: v->cur_image = v->GetImage(v->direction); celestar@5385: /* Refrain from updating the z position of the vehicle when on celestar@5385: a bridge, because AfterSetTrainPos will put the vehicle under celestar@5385: the bridge in that case */ rubidium@5993: if (v->u.rail.track != TRACK_BIT_WORMHOLE) AfterSetTrainPos(v, false); truelight@0: } rubidium@7492: } while ((v = v->Next()) != NULL); truelight@0: } truelight@0: truelight@0: static void HandleCrashedTrain(Vehicle *v) truelight@0: { tron@3017: int state = ++v->u.rail.crash_anim_pos; truelight@193: rubidium@5993: if (state == 4 && !(v->vehstatus & VS_HIDDEN)) { tron@1359: CreateEffectVehicleRel(v, 4, 4, 8, EV_EXPLOSION_LARGE); truelight@0: } truelight@0: tron@6150: uint32 r; tron@2637: if (state <= 200 && CHANCE16R(1, 7, r)) { tron@3017: int index = (r * 10 >> 16); truelight@0: tron@6150: Vehicle *u = v; truelight@0: do { truelight@0: if (--index < 0) { celestar@1137: r = Random(); truelight@0: truelight@0: CreateEffectVehicleRel(u, tron@2140: GB(r, 8, 3) + 2, tron@2140: GB(r, 16, 3) + 2, tron@2140: GB(r, 0, 3) + 5, tron@1359: EV_EXPLOSION_SMALL); truelight@0: break; truelight@0: } rubidium@7492: } while ((u = u->Next()) != NULL); truelight@0: } truelight@0: tron@3017: if (state <= 240 && !(v->tick_counter & 3)) ChangeTrainDirRandomly(v); truelight@0: bjarni@1128: if (state >= 4440 && !(v->tick_counter&0x1F)) { truelight@0: DeleteLastWagon(v); rubidium@6643: InvalidateWindow(WC_REPLACE_VEHICLE, (v->group_id << 16) | VEH_TRAIN); bjarni@1128: } truelight@0: } truelight@0: truelight@0: static void HandleBrokenTrain(Vehicle *v) truelight@0: { truelight@0: if (v->breakdown_ctr != 1) { truelight@0: v->breakdown_ctr = 1; truelight@0: v->cur_speed = 0; truelight@0: truelight@0: if (v->breakdowns_since_last_service != 255) truelight@0: v->breakdowns_since_last_service++; truelight@193: truelight@0: InvalidateWindow(WC_VEHICLE_VIEW, v->index); truelight@0: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); truelight@193: peter1138@4656: if (!PlayVehicleSound(v, VSE_BREAKDOWN)) { belugas@6357: 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); rubidium@7334: if (u != NULL) u->u.special.animation_state = v->breakdown_delay * 2; truelight@0: } truelight@0: } truelight@0: truelight@0: if (!(v->tick_counter & 3)) { truelight@0: if (!--v->breakdown_delay) { truelight@0: v->breakdown_ctr = 0; truelight@0: InvalidateWindow(WC_VEHICLE_VIEW, v->index); truelight@0: } truelight@0: } truelight@0: } truelight@0: 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@6150: int t = v->breakdown_ctr; tron@2484: if (t > 1) { truelight@0: v->vehstatus |= VS_TRAIN_SLOWING; truelight@193: tron@6150: 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@5993: if (v->u.rail.track == TRACK_BIT_WORMHOLE) return true; // exit if inside a tunnel rubidium@5993: if (v->u.rail.track == TRACK_BIT_DEPOT) return true; // exit if inside a depot hackykid@2008: tron@6150: TileIndex tile = v->tile; truelight@0: celestar@5385: if (IsTileType(tile, MP_TUNNELBRIDGE)) { tron@6150: DiagDirection dir = IsTunnel(tile) ? GetTunnelDirection(tile) : GetBridgeRampDirection(tile); celestar@5385: 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@6150: 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. rubidium@6683: uint32 ts = GetTileTrackStatus(tile, TRANSPORT_RAIL, 0) & _reachable_tracks[dir]; truelight@193: darkvater@22: /* Calc position within the current tile ?? */ tron@6150: uint x = v->x_pos & 0xF; tron@6150: 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@5587: 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) { belugas@6422: /* 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: belugas@6422: /* slow down */ truelight@0: v->vehstatus |= VS_TRAIN_SLOWING; tron@6150: 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? */ rubidium@7476: if (v->vehstatus & VS_CRASHED) { truelight@0: if (!mode) HandleCrashedTrain(v); truelight@0: return; truelight@0: } truelight@0: tron@3017: if (v->u.rail.force_proceed != 0) v->u.rail.force_proceed--; truelight@0: truelight@0: /* train is broken down? */ truelight@0: if (v->breakdown_ctr != 0) { truelight@0: if (v->breakdown_ctr <= 2) { truelight@0: HandleBrokenTrain(v); truelight@0: return; truelight@0: } truelight@0: v->breakdown_ctr--; truelight@0: } truelight@0: skidd13@7928: if (HasBit(v->u.rail.flags, VRF_REVERSING) && v->cur_speed == 0) { truelight@0: ReverseTrainDirection(v); truelight@0: } truelight@0: truelight@0: /* exit if train is stopped */ tron@3017: if (v->vehstatus & VS_STOPPED && v->cur_speed == 0) return; truelight@0: 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: rubidium@6594: 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@6150: int j = UpdateTrainSpeed(v); truelight@0: if (j == 0) { belugas@6422: /* 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: rubidium@7488: rubidium@7488: Money Train::GetRunningCost() const rubidium@7488: { rubidium@7488: Money cost = 0; rubidium@7488: const Vehicle *v = this; rubidium@7488: rubidium@7488: do { rubidium@7488: const RailVehicleInfo *rvi = RailVehInfo(v->engine_type); rubidium@7488: rubidium@7488: byte cost_factor = GetVehicleProperty(v, 0x0D, rvi->running_cost_base); rubidium@7488: if (cost_factor == 0) continue; rubidium@7488: rubidium@7488: cost += cost_factor * _price.running_rail[rvi->running_cost_class]; rubidium@7488: } while ((v = GetNextVehicle(v)) != NULL); rubidium@7488: rubidium@7488: return cost; rubidium@7488: } rubidium@7488: rubidium@7488: rubidium@7135: void Train::Tick() truelight@0: { rubidium@7135: if (_age_cargo_skip_counter == 0) this->cargo.AgeCargo(); rubidium@7135: rubidium@7135: this->tick_counter++; rubidium@7135: rubidium@7135: if (IsFrontEngine(this)) { rubidium@7135: this->current_order_time++; rubidium@7135: rubidium@7135: TrainLocoHandler(this, false); truelight@193: belugas@6422: /* make sure vehicle wasn't deleted. */ rubidium@7135: if (this->type == VEH_TRAIN && IsFrontEngine(this)) rubidium@7135: TrainLocoHandler(this, true); rubidium@7135: } else if (IsFreeWagon(this) && HASBITS(this->vehstatus, VS_CRASHED)) { rubidium@7696: /* Delete flooded standalone wagon chain */ rubidium@7696: if (++this->u.rail.crash_anim_pos >= 4400) DeleteVehicleChain(this); truelight@0: } truelight@0: } truelight@0: KUDr@3900: #define MAX_ACCEPTABLE_DEPOT_DIST 16 KUDr@3900: truelight@0: static void CheckIfTrainNeedsService(Vehicle *v) truelight@0: { rubidium@7502: if (_patches.servint_trains == 0 || !VehicleNeedsService(v)) return; rubidium@7502: if (v->IsInDepot()) { bjarni@4529: VehicleServiceInDepot(v); bjarni@4529: return; bjarni@4529: } bjarni@4529: tron@6150: TrainFindDepotData tfdd = FindClosestTrainDepot(v, MAX_ACCEPTABLE_DEPOT_DIST); darkvater@308: /* Only go to the depot if it is not too far out of our way. */ 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@6150: 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: 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: 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@6150: if (v->current_order.type == OT_GOTO_STATION) { tron@6150: TileIndex tile = GetStation(v->current_order.dest)->train_tile; tron@6150: if (tile != 0) v->dest_tile = tile; tron@2639: } truelight@0: truelight@0: if ((v->vehstatus & VS_STOPPED) == 0) { truelight@0: /* running costs */ rubidium@7488: CommandCost cost(v->GetRunningCost() / 364); rubidium@6950: rubidium@6950: v->profit_this_year -= cost.GetCost() >> 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@7024: } else if (IsTrainEngine(v)) { truelight@7024: /* Also age engines that aren't front engines */ truelight@7024: AgeVehicle(v); truelight@0: } truelight@0: } truelight@0: rubidium@6247: void TrainsYearlyLoop() truelight@0: { truelight@0: Vehicle *v; truelight@0: truelight@0: FOR_ALL_VEHICLES(v) { rubidium@6259: if (v->type == VEH_TRAIN && IsFrontEngine(v)) { belugas@6422: /* show warning if train is not generating enough income last 2 years (corresponds to a red icon in the vehicle list) */ truelight@0: if (_patches.train_income_warn && v->owner == _local_player && v->age >= 730 && v->profit_this_year < 0) { rubidium@7002: 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: rubidium@6247: void InitializeTrains() truelight@0: { truelight@0: _age_cargo_skip_counter = 1; truelight@0: } bjarni@2855: bjarni@2855: /* bjarni@2855: * Link front and rear multiheaded engines to each other bjarni@2855: * This is done when loading a savegame bjarni@2855: */ rubidium@6247: void ConnectMultiheadedTrains() bjarni@2855: { bjarni@2855: Vehicle *v; bjarni@2855: bjarni@2855: FOR_ALL_VEHICLES(v) { rubidium@6259: if (v->type == VEH_TRAIN) { bjarni@2855: v->u.rail.other_multiheaded_part = NULL; bjarni@2855: } bjarni@2855: } bjarni@2855: bjarni@2855: FOR_ALL_VEHICLES(v) { rubidium@6259: 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@6150: Vehicle *w; rubidium@7492: for (w = u->Next(); w != NULL && (w->engine_type != u->engine_type || w->u.rail.other_multiheaded_part != NULL); w = GetNextVehicle(w)); tron@6150: if (w != NULL) { tron@6150: /* we found a car to partner with this engine. Now we will make sure it face the right way */ tron@6150: if (IsTrainEngine(w)) { tron@6150: ClearTrainEngine(w); tron@6150: w->spritenum++; bjarni@2855: } tron@6150: w->u.rail.other_multiheaded_part = u; tron@6150: u->u.rail.other_multiheaded_part = w; tron@6150: } else { tron@6150: /* we got a front car and no rear cars. We will fake this one for forget that it should have been multiheaded */ tron@6150: ClearMultiheaded(u); bjarni@2855: } bjarni@2855: } bjarni@2855: } END_ENUM_WAGONS(u) bjarni@2855: } bjarni@2855: } bjarni@2855: } bjarni@2855: belugas@6422: /** bjarni@2855: * Converts all trains to the new subtype format introduced in savegame 16.2 bjarni@2855: * It also links multiheaded engines or make them forget they are multiheaded if no suitable partner is found bjarni@2855: */ rubidium@6247: void ConvertOldMultiheadToNew() bjarni@2855: { bjarni@2855: Vehicle *v; bjarni@2855: FOR_ALL_VEHICLES(v) { rubidium@6259: if (v->type == VEH_TRAIN) { skidd13@7931: SetBit(v->subtype, 7); // indicates that it's the old format and needs to be converted in the next loop bjarni@2855: } bjarni@2855: } bjarni@2855: bjarni@2855: FOR_ALL_VEHICLES(v) { rubidium@6259: if (v->type == VEH_TRAIN) { skidd13@7928: if (HasBit(v->subtype, 7) && ((v->subtype & ~0x80) == 0 || (v->subtype & ~0x80) == 4)) { bjarni@2855: Vehicle *u = v; bjarni@2855: tron@3017: BEGIN_ENUM_WAGONS(u) { bjarni@2855: const RailVehicleInfo *rvi = RailVehInfo(u->engine_type); tron@3017: skidd13@7929: ClrBit(u->subtype, 7); tron@3017: switch (u->subtype) { rubidium@4434: case 0: /* TS_Front_Engine */ belugas@5868: if (rvi->railveh_type == RAILVEH_MULTIHEAD) SetMultiheaded(u); tron@3017: SetFrontEngine(u); tron@3017: SetTrainEngine(u); bjarni@2855: break; tron@3017: rubidium@4434: case 1: /* TS_Artic_Part */ tron@3017: u->subtype = 0; tron@3017: SetArticulatedPart(u); tron@3017: break; tron@3017: rubidium@4434: case 2: /* TS_Not_First */ tron@3017: u->subtype = 0; belugas@5868: if (rvi->railveh_type == RAILVEH_WAGON) { tron@3017: // normal wagon tron@3017: SetTrainWagon(u); tron@3017: break; tron@3017: } belugas@5868: if (rvi->railveh_type == RAILVEH_MULTIHEAD && rvi->image_index == u->spritenum - 1) { bjarni@2855: // rear end of a multiheaded engine bjarni@2855: SetMultiheaded(u); bjarni@2855: break; bjarni@2855: } belugas@5868: if (rvi->railveh_type == RAILVEH_MULTIHEAD) SetMultiheaded(u); bjarni@2855: SetTrainEngine(u); tron@3017: break; tron@3017: rubidium@4434: case 4: /* TS_Free_Car */ tron@3017: u->subtype = 0; tron@3017: SetTrainWagon(u); tron@3017: SetFreeWagon(u); tron@3017: break; tron@3017: default: NOT_REACHED(); break; tron@3017: } tron@3017: } END_ENUM_WAGONS(u) bjarni@2855: } bjarni@2855: } bjarni@2855: } bjarni@2855: }