Fix multiplayer desync when adding a track piece of a different railtype

Undoing a CMD_CONVERT_RAIL did not undo changes to the
infrastructure totals
Defer CMD_CONVERT_RAIL until all checks have succeeded instead

In the case where:
* The addition would have resulted in the tile changing railtype
* The addition fails because of slope restrictions, a train is present,
  or auto-removing signals fails
* The command test did not fail (e.g. because the train has moved)
This commit is contained in:
Jonathan G Rennison 2023-09-11 23:40:34 +01:00
parent 56ecbb5574
commit 627eb1effc

View File

@ -357,6 +357,12 @@ static CommandCost EnsureNoTrainOnTrack(TileIndex tile, Track track)
return EnsureNoTrainOnTrackBits(tile, rail_bits); return EnsureNoTrainOnTrackBits(tile, rail_bits);
} }
struct CheckTrackCombinationRailTypeChanges {
RailType convert_to = INVALID_RAILTYPE;
RailType primary = INVALID_RAILTYPE;
RailType secondary = INVALID_RAILTYPE;
};
/** /**
* Check that the new track bits may be built. * Check that the new track bits may be built.
* @param tile %Tile to build on. * @param tile %Tile to build on.
@ -366,7 +372,8 @@ static CommandCost EnsureNoTrainOnTrack(TileIndex tile, Track track)
* @param flags Flags of the operation. * @param flags Flags of the operation.
* @return Succeeded or failed command. * @return Succeeded or failed command.
*/ */
static CommandCost CheckTrackCombination(TileIndex tile, TrackBits to_build, RailType railtype, bool disable_dual_rail_type, DoCommandFlag flags, bool auto_remove_signals) static CommandCost CheckTrackCombination(TileIndex tile, TrackBits to_build, RailType railtype, bool disable_dual_rail_type,
DoCommandFlag flags, bool auto_remove_signals, CheckTrackCombinationRailTypeChanges &changes)
{ {
if (!IsPlainRail(tile)) return_cmd_error(STR_ERROR_IMPOSSIBLE_TRACK_COMBINATION); if (!IsPlainRail(tile)) return_cmd_error(STR_ERROR_IMPOSSIBLE_TRACK_COMBINATION);
@ -390,10 +397,10 @@ static CommandCost CheckTrackCombination(TileIndex tile, TrackBits to_build, Rai
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
if (to_build & TRACK_BIT_RT_1) { if (to_build & TRACK_BIT_RT_1) {
RailType current_rt = GetRailType(tile); RailType current_rt = GetRailType(tile);
SetRailType(tile, railtype); changes.primary = railtype;
SetSecondaryRailType(tile, current_rt); changes.secondary = current_rt;
} else { } else {
SetSecondaryRailType(tile, railtype); changes.secondary = railtype;
} }
} }
return CommandCost(); return CommandCost();
@ -460,13 +467,14 @@ static CommandCost CheckTrackCombination(TileIndex tile, TrackBits to_build, Rai
CommandCost ret; CommandCost ret;
if (rt != INVALID_RAILTYPE) { if (rt != INVALID_RAILTYPE) {
ret = DoCommand(tile, tile, rt, flags, CMD_CONVERT_RAIL); ret = DoCommand(tile, tile, rt, flags & ~DC_EXEC, CMD_CONVERT_RAIL);
if (ret.Failed()) return ret; if (ret.Failed()) return ret;
changes.convert_to = rt;
} }
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
SetRailType(tile, railtype); changes.primary = railtype;
SetSecondaryRailType(tile, railtype); changes.secondary = railtype;
} }
return ret; return ret;
@ -684,16 +692,8 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u
if (!IsPlainRail(tile)) return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); // just get appropriate error message if (!IsPlainRail(tile)) return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); // just get appropriate error message
const RailType old_rt = GetRailType(tile); CheckTrackCombinationRailTypeChanges changes;
const RailType old_secondary_rt = GetSecondaryRailType(tile); ret = CheckTrackCombination(tile, trackbit, railtype, disable_dual_rail_type, flags, auto_remove_signals, changes);
auto rt_guard = scope_guard([&]() {
if (flags & DC_EXEC) {
SetRailType(tile, old_rt);
SetSecondaryRailType(tile, old_secondary_rt);
}
});
ret = CheckTrackCombination(tile, trackbit, railtype, disable_dual_rail_type, flags, auto_remove_signals);
if (ret.Succeeded()) { if (ret.Succeeded()) {
cost.AddCost(ret); cost.AddCost(ret);
ret = EnsureNoTrainOnTrack(tile, track); ret = EnsureNoTrainOnTrack(tile, track);
@ -720,9 +720,15 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u
} }
} }
rt_guard.cancel();
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
if (changes.convert_to != INVALID_RAILTYPE) {
/* The cost is already accounted for and a test already done in CheckTrackCombination */
CommandCost ret = DoCommand(tile, tile, changes.convert_to, flags, CMD_CONVERT_RAIL);
assert(ret.Succeeded());
}
if (changes.primary != INVALID_RAILTYPE) SetRailType(tile, changes.primary);
if (changes.secondary != INVALID_RAILTYPE) SetSecondaryRailType(tile, changes.secondary);
SetRailGroundType(tile, RAIL_GROUND_BARREN); SetRailGroundType(tile, RAIL_GROUND_BARREN);
TrackBits bits = GetTrackBits(tile); TrackBits bits = GetTrackBits(tile);
TrackBits newbits = bits | trackbit; TrackBits newbits = bits | trackbit;