Fix #8177: Ships with max speed overflow to near-zero speed (#10695)

This commit is contained in:
Kuhnovic 2023-04-29 10:33:01 +02:00 committed by GitHub
parent 4dd5f994be
commit 3991e76c96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 174 additions and 163 deletions

View File

@ -400,29 +400,30 @@ static bool CheckShipLeaveDepot(Ship *v)
return false; 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; uint speed;
byte t;
spd = std::min<uint>(v->cur_speed + 1, v->vcache.cached_max_speed); speed = std::min<uint>(v->cur_speed + 1, v->vcache.cached_max_speed);
spd = std::min<uint>(spd, v->current_order.GetMaxSpeed() * 2); speed = std::min<uint>(speed, v->current_order.GetMaxSpeed() * 2);
/* updates statusbar only if speed have changed to save CPU time */ /* updates statusbar only if speed have changed to save CPU time */
if (spd != v->cur_speed) { if (speed != v->cur_speed) {
v->cur_speed = spd; v->cur_speed = speed;
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
} }
/* Convert direction-independent speed into direction-dependent speed. (old movement method) */ const uint advance_speed = v->GetAdvanceSpeed(speed);
spd = v->GetOldAdvanceSpeed(spd); const uint number_of_steps = (advance_speed + v->progress) / v->GetAdvanceDistance();
const uint remainder = (advance_speed + v->progress) % v->GetAdvanceDistance();
if (spd == 0) return false; assert(remainder <= std::numeric_limits<byte>::max());
if ((byte)++spd == 0) return true; v->progress = static_cast<byte>(remainder);
return number_of_steps;
v->progress = (t = v->progress) - (byte)spd;
return (t < v->progress);
} }
/** /**
@ -633,13 +634,43 @@ bool IsShipDestinationTile(TileIndex tile, StationID station)
return false; 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) static void ShipController(Ship *v)
{ {
uint32 r;
Track track;
TrackBits tracks;
GetNewVehiclePosResult gp;
v->tick_counter++; v->tick_counter++;
v->current_order_time++; v->current_order_time++;
@ -647,7 +678,7 @@ static void ShipController(Ship *v)
if (v->vehstatus & VS_STOPPED) return; if (v->vehstatus & VS_STOPPED) return;
if (ProcessOrders(v) && CheckReverseShip(v)) goto reverse_direction; if (ProcessOrders(v) && CheckReverseShip(v)) return ReverseShip(v);
v->HandleLoading(); v->HandleLoading();
@ -671,159 +702,139 @@ static void ShipController(Ship *v)
if (ShipMoveUpDownOnLock(v)) return; 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); /* A leave station order only needs one tick to get processed, so we can
if (v->state != TRACK_BIT_WORMHOLE) { * always skip ahead. */
/* Not on a bridge */ if (v->current_order.IsType(OT_LEAVESTATION)) {
if (gp.old_tile == gp.new_tile) { v->current_order.Free();
/* Staying in tile */ SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
if (v->IsInDepot()) { /* Test if continuing forward would lead to a dead-end, moving into the dock. */
gp.x = v->x_pos; const DiagDirection exitdir = VehicleExitDir(v->direction, v->state);
gp.y = v->y_pos; const TileIndex tile = TileAddByDiagDir(v->tile, exitdir);
} else { if (TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0, exitdir)) == TRACK_BIT_NONE) return ReverseShip(v);
/* Not inside depot */ } else if (v->dest_tile != 0) {
r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); /* We have a target, let's see if we reached it... */
if (HasBit(r, VETS_CANNOT_ENTER)) goto reverse_direction; if (v->current_order.IsType(OT_GOTO_WAYPOINT) &&
DistanceManhattan(v->dest_tile, gp.new_tile) <= 3) {
/* A leave station order only needs one tick to get processed, so we can /* We got within 3 tiles of our target buoy, so let's skip to our
* always skip ahead. */ * next order */
if (v->current_order.IsType(OT_LEAVESTATION)) { UpdateVehicleTimetable(v, true);
v->current_order.Free(); v->IncrementRealOrderIndex();
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); v->current_order.MakeDummy();
/* Test if continuing forward would lead to a dead-end, moving into the dock. */ } else if (v->current_order.IsType(OT_GOTO_DEPOT) &&
DiagDirection exitdir = VehicleExitDir(v->direction, v->state); v->dest_tile == gp.new_tile) {
TileIndex tile = TileAddByDiagDir(v->tile, exitdir); /* Depot orders really need to reach the tile */
if (TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0, exitdir)) == TRACK_BIT_NONE) goto reverse_direction; if ((gp.x & 0xF) == 8 && (gp.y & 0xF) == 8) {
} else if (v->dest_tile != 0) { VehicleEnterDepot(v);
/* We have a target, let's see if we reached it... */ return;
if (v->current_order.IsType(OT_GOTO_WAYPOINT) && }
DistanceManhattan(v->dest_tile, gp.new_tile) <= 3) { } else if (v->current_order.IsType(OT_GOTO_STATION) && IsDockingTile(gp.new_tile)) {
/* We got within 3 tiles of our target buoy, so let's skip to our /* Process station in the orderlist. */
* next order */ Station *st = Station::Get(v->current_order.GetDestination());
UpdateVehicleTimetable(v, true); if (st->docking_station.Contains(gp.new_tile) && IsShipDestinationTile(gp.new_tile, st->index)) {
v->IncrementRealOrderIndex(); v->last_station_visited = st->index;
v->current_order.MakeDummy(); if (st->facilities & FACIL_DOCK) { // ugly, ugly workaround for problem with ships able to drop off cargo at wrong stations
} else if (v->current_order.IsType(OT_GOTO_DEPOT) && ShipArrivesAt(v, st);
v->dest_tile == gp.new_tile) { v->BeginLoading();
/* Depot orders really need to reach the tile */ } else { // leave stations without docks right away
if ((gp.x & 0xF) == 8 && (gp.y & 0xF) == 8) { v->current_order.MakeLeaveStation();
VehicleEnterDepot(v); v->IncrementRealOrderIndex();
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 { } else {
/* New tile */ /* On a bridge */
if (!IsValidTile(gp.new_tile)) goto reverse_direction; 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;
DiagDirection diagdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile); v->y_pos = gp.y;
assert(diagdir != INVALID_DIAGDIR); v->UpdatePosition();
tracks = GetAvailShipTracks(gp.new_tile, diagdir); if ((v->vehstatus & VS_HIDDEN) == 0) v->Vehicle::UpdateViewport(true);
if (tracks == TRACK_BIT_NONE) { return;
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;
} }
/* Choose a direction, and continue if we find one */ /* Ship is back on the bridge head, we need to consume its path
track = ChooseShipTrack(v, gp.new_tile, diagdir, tracks); * cache entry here as we didn't have to choose a ship track. */
if (track == INVALID_TRACK) goto reverse_direction; if (!v->path.empty()) v->path.pop_front();
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 /* update image of ship, as well as delta XY */
* cache entry here as we didn't have to choose a ship track. */ v->x_pos = gp.x;
if (!v->path.empty()) v->path.pop_front(); 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() bool Ship::Tick()