Merge branch 'master' into jgrpp

# Conflicts:
#	.github/workflows/release-windows.yml
#	src/company_base.h
#	src/company_cmd.cpp
#	src/company_gui.cpp
#	src/console_cmds.cpp
#	src/economy.cpp
#	src/economy_cmd.h
#	src/fios.h
#	src/goal.cpp
#	src/group_gui.cpp
#	src/network/core/config.h
#	src/network/network_admin.cpp
#	src/newgrf_config.cpp
#	src/os/windows/win32.cpp
#	src/saveload/afterload.cpp
#	src/saveload/company_sl.cpp
#	src/saveload/saveload.cpp
#	src/saveload/saveload_error.hpp
#	src/settings_gui.cpp
#	src/ship_cmd.cpp
#	src/stdafx.h
#	src/story.cpp
#	src/story_base.h
#	src/string.cpp
#	src/table/settings/economy_settings.ini
#	src/tests/CMakeLists.txt
#	src/tests/math_func.cpp
This commit is contained in:
Jonathan G Rennison
2023-05-30 00:49:14 +01:00
156 changed files with 2979 additions and 4098 deletions

View File

@@ -485,37 +485,38 @@ static inline void UpdateShipSpeed(Vehicle *v, uint speed)
}
}
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, Ship::From(v)->GetEffectiveMaxSpeed());
spd = std::min<uint>(spd, v->current_order.GetMaxSpeed() * 2);
speed = std::min<uint>(v->cur_speed + 1, Ship::From(v)->GetEffectiveMaxSpeed());
speed = std::min<uint>(speed, v->current_order.GetMaxSpeed() * 2);
if (v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_LOW_POWER && v->cur_speed > (v->breakdown_severity * ShipVehInfo(v->engine_type)->max_speed) >> 8) {
if ((v->tick_counter & 0x7) == 0 && v->cur_speed > 0) {
spd = v->cur_speed - 1;
speed = v->cur_speed - 1;
} else {
spd = v->cur_speed;
speed = v->cur_speed;
}
}
if (v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_LOW_SPEED) {
spd = std::min<uint>(spd, v->breakdown_severity);
speed = std::min<uint>(speed, v->breakdown_severity);
}
UpdateShipSpeed(v, spd);
UpdateShipSpeed(v, speed);
/* 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();
dbg_assert(remainder <= std::numeric_limits<byte>::max());
v->progress = static_cast<byte>(remainder);
return number_of_steps;
}
/**
@@ -866,13 +867,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];
dbg_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;
UpdateShipSpeed(v, 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;
UpdateShipSpeed(v, 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++;
@@ -880,7 +911,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();
@@ -902,183 +933,164 @@ static void ShipController(Ship *v)
if (ShipMoveUpDownOnLock(v)) return;
if (!ShipAccelerate(v) && !v->current_order.IsType(OT_LEAVESTATION)) return;
uint number_of_steps = ShipAccelerate(v);
if (number_of_steps == 0 && v->current_order.IsType(OT_LEAVESTATION)) number_of_steps = 1;
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)) {
StationID station_id = v->current_order.GetDestination();
v->current_order.Free();
/* A leave station order only needs one tick to get processed, so we can
* always skip ahead. */
if (v->current_order.IsType(OT_LEAVESTATION)) {
StationID station_id = v->current_order.GetDestination();
v->current_order.Free();
bool may_reverse = ProcessOrders(v);
bool may_reverse = ProcessOrders(v);
if (v->current_order.IsType(OT_GOTO_STATION) && v->current_order.GetDestination() == station_id &&
IsDockingTile(gp.new_tile) && Company::Get(v->owner)->settings.remain_if_next_order_same_station) {
Station *st = Station::Get(station_id);
if (st->facilities & FACIL_DOCK && st->docking_station.Contains(gp.new_tile) && IsShipDestinationTile(gp.new_tile, station_id)) {
v->last_station_visited = station_id;
ShipArrivesAt(v, st);
v->BeginLoading();
return;
}
}
v->PlayLeaveStationSound();
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
if (may_reverse && CheckReverseShip(v)) goto reverse_direction;
/* 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 (TrackdirBitsToTrackBits(GetTileTrackdirBits(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
if (v->current_order.IsType(OT_GOTO_STATION) && v->current_order.GetDestination() == station_id &&
IsDockingTile(gp.new_tile) && Company::Get(v->owner)->settings.remain_if_next_order_same_station) {
Station *st = Station::Get(station_id);
if (st->facilities & FACIL_DOCK && st->docking_station.Contains(gp.new_tile) && IsShipDestinationTile(gp.new_tile, station_id)) {
v->last_station_visited = station_id;
ShipArrivesAt(v, st);
v->BeginLoading();
} else { // leave stations without docks right away
v->current_order.MakeLeaveStation();
v->IncrementRealOrderIndex();
return;
}
}
v->PlayLeaveStationSound();
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
if (may_reverse && CheckReverseShip(v)) return ReverseShip(v);
/* 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 (TrackdirBitsToTrackBits(GetTileTrackdirBits(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);
dbg_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 */
Track track = ChooseShipTrack(v, gp.new_tile, diagdir, tracks);
if (track == INVALID_TRACK) return ReverseShip(v);
/* Try to avoid collision and keep distance between ships. */
if (_settings_game.vehicle.ship_collision_avoidance) CheckDistanceBetweenShips(gp.new_tile, v, tracks, &track, diagdir);
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 */
UpdateShipSpeed(v, 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);
dbg_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];
dbg_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)) {
if (_settings_game.vehicle.ship_collision_avoidance && gp.new_tile != TileVirtXY(v->x_pos, v->y_pos)) HandleSpeedOnAqueduct(v, gp.new_tile, v->tile);
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;
/* Try to avoid collision and keep distance between ships. */
if (_settings_game.vehicle.ship_collision_avoidance) CheckDistanceBetweenShips(gp.new_tile, v, tracks, &track, diagdir);
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 */
UpdateShipSpeed(v, 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)) {
/* Bridge exit */
if (_settings_game.vehicle.ship_collision_avoidance && gp.new_tile != TileVirtXY(v->x_pos, v->y_pos)) HandleSpeedOnAqueduct(v, gp.new_tile, v->tile);
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();
}
/* Bridge exit */
if (_settings_game.vehicle.ship_collision_avoidance && gp.new_tile != TileVirtXY(v->x_pos, v->y_pos)) HandleSpeedOnAqueduct(v, gp.new_tile, v->tile);
/* 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;
UpdateShipSpeed(v, 0);
v->path.clear();
goto getout;
}
bool Ship::Tick()