diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 6b9b955a86..a7cad340c4 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -400,29 +400,30 @@ static bool CheckShipLeaveDepot(Ship *v) return false; } -static bool ShipAccelerate(Vehicle *v) +/** + * Accelerates the ship towards its target speed. + * @param v Ship to accelerate. + * @return Number of steps to move the ship. + */ +static uint ShipAccelerate(Vehicle *v) { - uint spd; - byte t; + uint speed; - spd = std::min(v->cur_speed + 1, v->vcache.cached_max_speed); - spd = std::min(spd, v->current_order.GetMaxSpeed() * 2); + speed = std::min(v->cur_speed + 1, v->vcache.cached_max_speed); + speed = std::min(speed, v->current_order.GetMaxSpeed() * 2); /* updates statusbar only if speed have changed to save CPU time */ - if (spd != v->cur_speed) { - v->cur_speed = spd; + if (speed != v->cur_speed) { + v->cur_speed = speed; SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); } - /* Convert direction-independent speed into direction-dependent speed. (old movement method) */ - spd = v->GetOldAdvanceSpeed(spd); - - if (spd == 0) return false; - if ((byte)++spd == 0) return true; - - v->progress = (t = v->progress) - (byte)spd; - - return (t < v->progress); + const uint advance_speed = v->GetAdvanceSpeed(speed); + const uint number_of_steps = (advance_speed + v->progress) / v->GetAdvanceDistance(); + const uint remainder = (advance_speed + v->progress) % v->GetAdvanceDistance(); + assert(remainder <= std::numeric_limits::max()); + v->progress = static_cast(remainder); + return number_of_steps; } /** @@ -633,13 +634,43 @@ bool IsShipDestinationTile(TileIndex tile, StationID station) return false; } +static void ReverseShipIntoTrackdir(Ship *v, Trackdir trackdir) +{ + static constexpr Direction _trackdir_to_direction[] = { + DIR_NE, DIR_SE, DIR_E, DIR_E, DIR_S, DIR_S, INVALID_DIR, INVALID_DIR, + DIR_SW, DIR_NW, DIR_W, DIR_W, DIR_N, DIR_N, INVALID_DIR, INVALID_DIR, + }; + + v->direction = _trackdir_to_direction[trackdir]; + assert(v->direction != INVALID_DIR); + v->state = TrackdirBitsToTrackBits(TrackdirToTrackdirBits(trackdir)); + + /* Remember our current location to avoid movement glitch */ + v->rotation_x_pos = v->x_pos; + v->rotation_y_pos = v->y_pos; + v->cur_speed = 0; + v->path.clear(); + + v->UpdatePosition(); + v->UpdateViewport(true, true); +} + +static void ReverseShip(Ship *v) +{ + v->direction = ReverseDir(v->direction); + + /* Remember our current location to avoid movement glitch */ + v->rotation_x_pos = v->x_pos; + v->rotation_y_pos = v->y_pos; + v->cur_speed = 0; + v->path.clear(); + + v->UpdatePosition(); + v->UpdateViewport(true, true); +} + static void ShipController(Ship *v) { - uint32 r; - Track track; - TrackBits tracks; - GetNewVehiclePosResult gp; - v->tick_counter++; v->current_order_time++; @@ -647,7 +678,7 @@ static void ShipController(Ship *v) if (v->vehstatus & VS_STOPPED) return; - if (ProcessOrders(v) && CheckReverseShip(v)) goto reverse_direction; + if (ProcessOrders(v) && CheckReverseShip(v)) return ReverseShip(v); v->HandleLoading(); @@ -671,159 +702,139 @@ static void ShipController(Ship *v) if (ShipMoveUpDownOnLock(v)) return; - if (!ShipAccelerate(v)) return; + const uint number_of_steps = ShipAccelerate(v); + for (uint i = 0; i < number_of_steps; ++i) { + GetNewVehiclePosResult gp = GetNewVehiclePos(v); + if (v->state != TRACK_BIT_WORMHOLE) { + /* Not on a bridge */ + if (gp.old_tile == gp.new_tile) { + /* Staying in tile */ + if (v->IsInDepot()) { + gp.x = v->x_pos; + gp.y = v->y_pos; + } else { + /* Not inside depot */ + const VehicleEnterTileStatus r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); + if (HasBit(r, VETS_CANNOT_ENTER)) return ReverseShip(v); - gp = GetNewVehiclePos(v); - if (v->state != TRACK_BIT_WORMHOLE) { - /* Not on a bridge */ - if (gp.old_tile == gp.new_tile) { - /* Staying in tile */ - if (v->IsInDepot()) { - gp.x = v->x_pos; - gp.y = v->y_pos; - } else { - /* Not inside depot */ - r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); - if (HasBit(r, VETS_CANNOT_ENTER)) goto reverse_direction; - - /* A leave station order only needs one tick to get processed, so we can - * always skip ahead. */ - if (v->current_order.IsType(OT_LEAVESTATION)) { - v->current_order.Free(); - SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); - /* Test if continuing forward would lead to a dead-end, moving into the dock. */ - DiagDirection exitdir = VehicleExitDir(v->direction, v->state); - TileIndex tile = TileAddByDiagDir(v->tile, exitdir); - if (TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0, exitdir)) == TRACK_BIT_NONE) goto reverse_direction; - } else if (v->dest_tile != 0) { - /* We have a target, let's see if we reached it... */ - if (v->current_order.IsType(OT_GOTO_WAYPOINT) && - DistanceManhattan(v->dest_tile, gp.new_tile) <= 3) { - /* We got within 3 tiles of our target buoy, so let's skip to our - * next order */ - UpdateVehicleTimetable(v, true); - v->IncrementRealOrderIndex(); - v->current_order.MakeDummy(); - } else if (v->current_order.IsType(OT_GOTO_DEPOT) && - v->dest_tile == gp.new_tile) { - /* Depot orders really need to reach the tile */ - if ((gp.x & 0xF) == 8 && (gp.y & 0xF) == 8) { - VehicleEnterDepot(v); - return; - } - } else if (v->current_order.IsType(OT_GOTO_STATION) && IsDockingTile(gp.new_tile)) { - /* Process station in the orderlist. */ - Station *st = Station::Get(v->current_order.GetDestination()); - if (st->docking_station.Contains(gp.new_tile) && IsShipDestinationTile(gp.new_tile, st->index)) { - v->last_station_visited = st->index; - if (st->facilities & FACIL_DOCK) { // ugly, ugly workaround for problem with ships able to drop off cargo at wrong stations - ShipArrivesAt(v, st); - v->BeginLoading(); - } else { // leave stations without docks right away - v->current_order.MakeLeaveStation(); - v->IncrementRealOrderIndex(); + /* A leave station order only needs one tick to get processed, so we can + * always skip ahead. */ + if (v->current_order.IsType(OT_LEAVESTATION)) { + v->current_order.Free(); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + /* Test if continuing forward would lead to a dead-end, moving into the dock. */ + const DiagDirection exitdir = VehicleExitDir(v->direction, v->state); + const TileIndex tile = TileAddByDiagDir(v->tile, exitdir); + if (TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0, exitdir)) == TRACK_BIT_NONE) return ReverseShip(v); + } else if (v->dest_tile != 0) { + /* We have a target, let's see if we reached it... */ + if (v->current_order.IsType(OT_GOTO_WAYPOINT) && + DistanceManhattan(v->dest_tile, gp.new_tile) <= 3) { + /* We got within 3 tiles of our target buoy, so let's skip to our + * next order */ + UpdateVehicleTimetable(v, true); + v->IncrementRealOrderIndex(); + v->current_order.MakeDummy(); + } else if (v->current_order.IsType(OT_GOTO_DEPOT) && + v->dest_tile == gp.new_tile) { + /* Depot orders really need to reach the tile */ + if ((gp.x & 0xF) == 8 && (gp.y & 0xF) == 8) { + VehicleEnterDepot(v); + return; + } + } else if (v->current_order.IsType(OT_GOTO_STATION) && IsDockingTile(gp.new_tile)) { + /* Process station in the orderlist. */ + Station *st = Station::Get(v->current_order.GetDestination()); + if (st->docking_station.Contains(gp.new_tile) && IsShipDestinationTile(gp.new_tile, st->index)) { + v->last_station_visited = st->index; + if (st->facilities & FACIL_DOCK) { // ugly, ugly workaround for problem with ships able to drop off cargo at wrong stations + ShipArrivesAt(v, st); + v->BeginLoading(); + } else { // leave stations without docks right away + v->current_order.MakeLeaveStation(); + v->IncrementRealOrderIndex(); + } } } } } + } else { + /* New tile */ + if (!IsValidTile(gp.new_tile)) return ReverseShip(v); + + const DiagDirection diagdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile); + assert(diagdir != INVALID_DIAGDIR); + const TrackBits tracks = GetAvailShipTracks(gp.new_tile, diagdir); + if (tracks == TRACK_BIT_NONE) { + Trackdir trackdir = INVALID_TRACKDIR; + CheckReverseShip(v, &trackdir); + if (trackdir == INVALID_TRACKDIR) return ReverseShip(v); + return ReverseShipIntoTrackdir(v, trackdir); + } + + /* Choose a direction, and continue if we find one */ + const Track track = ChooseShipTrack(v, gp.new_tile, diagdir, tracks); + if (track == INVALID_TRACK) return ReverseShip(v); + + const ShipSubcoordData &b = _ship_subcoord[diagdir][track]; + + gp.x = (gp.x & ~0xF) | b.x_subcoord; + gp.y = (gp.y & ~0xF) | b.y_subcoord; + + /* Call the landscape function and tell it that the vehicle entered the tile */ + const VehicleEnterTileStatus r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); + if (HasBit(r, VETS_CANNOT_ENTER)) return ReverseShip(v); + + if (!HasBit(r, VETS_ENTERED_WORMHOLE)) { + v->tile = gp.new_tile; + v->state = TrackToTrackBits(track); + + /* Update ship cache when the water class changes. Aqueducts are always canals. */ + if (GetEffectiveWaterClass(gp.old_tile) != GetEffectiveWaterClass(gp.new_tile)) v->UpdateCache(); + } + + const Direction new_direction = b.dir; + const DirDiff diff = DirDifference(new_direction, v->direction); + switch (diff) { + case DIRDIFF_SAME: + case DIRDIFF_45RIGHT: + case DIRDIFF_45LEFT: + /* Continue at speed */ + v->rotation = v->direction = new_direction; + break; + + default: + /* Stop for rotation */ + v->cur_speed = 0; + v->direction = new_direction; + /* Remember our current location to avoid movement glitch */ + v->rotation_x_pos = v->x_pos; + v->rotation_y_pos = v->y_pos; + break; + } } } else { - /* New tile */ - if (!IsValidTile(gp.new_tile)) goto reverse_direction; - - DiagDirection diagdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile); - assert(diagdir != INVALID_DIAGDIR); - tracks = GetAvailShipTracks(gp.new_tile, diagdir); - if (tracks == TRACK_BIT_NONE) { - Trackdir trackdir = INVALID_TRACKDIR; - CheckReverseShip(v, &trackdir); - if (trackdir == INVALID_TRACKDIR) goto reverse_direction; - static const Direction _trackdir_to_direction[] = { - DIR_NE, DIR_SE, DIR_E, DIR_E, DIR_S, DIR_S, INVALID_DIR, INVALID_DIR, - DIR_SW, DIR_NW, DIR_W, DIR_W, DIR_N, DIR_N, INVALID_DIR, INVALID_DIR, - }; - v->direction = _trackdir_to_direction[trackdir]; - assert(v->direction != INVALID_DIR); - v->state = TrackdirBitsToTrackBits(TrackdirToTrackdirBits(trackdir)); - goto direction_changed; + /* On a bridge */ + if (!IsTileType(gp.new_tile, MP_TUNNELBRIDGE) || !HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) { + v->x_pos = gp.x; + v->y_pos = gp.y; + v->UpdatePosition(); + if ((v->vehstatus & VS_HIDDEN) == 0) v->Vehicle::UpdateViewport(true); + return; } - /* Choose a direction, and continue if we find one */ - track = ChooseShipTrack(v, gp.new_tile, diagdir, tracks); - if (track == INVALID_TRACK) goto reverse_direction; - - const ShipSubcoordData &b = _ship_subcoord[diagdir][track]; - - gp.x = (gp.x & ~0xF) | b.x_subcoord; - gp.y = (gp.y & ~0xF) | b.y_subcoord; - - /* Call the landscape function and tell it that the vehicle entered the tile */ - r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); - if (HasBit(r, VETS_CANNOT_ENTER)) goto reverse_direction; - - if (!HasBit(r, VETS_ENTERED_WORMHOLE)) { - v->tile = gp.new_tile; - v->state = TrackToTrackBits(track); - - /* Update ship cache when the water class changes. Aqueducts are always canals. */ - WaterClass old_wc = GetEffectiveWaterClass(gp.old_tile); - WaterClass new_wc = GetEffectiveWaterClass(gp.new_tile); - if (old_wc != new_wc) v->UpdateCache(); - } - - Direction new_direction = b.dir; - DirDiff diff = DirDifference(new_direction, v->direction); - switch (diff) { - case DIRDIFF_SAME: - case DIRDIFF_45RIGHT: - case DIRDIFF_45LEFT: - /* Continue at speed */ - v->rotation = v->direction = new_direction; - break; - - default: - /* Stop for rotation */ - v->cur_speed = 0; - v->direction = new_direction; - /* Remember our current location to avoid movement glitch */ - v->rotation_x_pos = v->x_pos; - v->rotation_y_pos = v->y_pos; - break; - } - } - } else { - /* On a bridge */ - if (!IsTileType(gp.new_tile, MP_TUNNELBRIDGE) || !HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) { - v->x_pos = gp.x; - v->y_pos = gp.y; - v->UpdatePosition(); - if ((v->vehstatus & VS_HIDDEN) == 0) v->Vehicle::UpdateViewport(true); - return; + /* Ship is back on the bridge head, we need to consume its path + * cache entry here as we didn't have to choose a ship track. */ + if (!v->path.empty()) v->path.pop_front(); } - /* Ship is back on the bridge head, we need to consume its path - * cache entry here as we didn't have to choose a ship track. */ - if (!v->path.empty()) v->path.pop_front(); + /* update image of ship, as well as delta XY */ + v->x_pos = gp.x; + v->y_pos = gp.y; + + v->UpdatePosition(); + v->UpdateViewport(true, true); } - - /* update image of ship, as well as delta XY */ - v->x_pos = gp.x; - v->y_pos = gp.y; - -getout: - v->UpdatePosition(); - v->UpdateViewport(true, true); - return; - -reverse_direction: - v->direction = ReverseDir(v->direction); -direction_changed: - /* Remember our current location to avoid movement glitch */ - v->rotation_x_pos = v->x_pos; - v->rotation_y_pos = v->y_pos; - v->cur_speed = 0; - v->path.clear(); - goto getout; } bool Ship::Tick()