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;
}
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<uint>(v->cur_speed + 1, v->vcache.cached_max_speed);
spd = std::min<uint>(spd, v->current_order.GetMaxSpeed() * 2);
speed = std::min<uint>(v->cur_speed + 1, v->vcache.cached_max_speed);
speed = std::min<uint>(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<byte>::max());
v->progress = static_cast<byte>(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()