diff --git a/src/ground_vehicle.hpp b/src/ground_vehicle.hpp --- a/src/ground_vehicle.hpp +++ b/src/ground_vehicle.hpp @@ -165,25 +165,25 @@ struct GroundVehicle : public Specialize if (HasBit(this->gv_flags, GVF_GOINGUP_BIT)) { switch (this->direction) { case DIR_NE: - this->z_pos += (this->x_pos & 1); break; + this->z_pos += (this->x_pos & 1) ^ 1; break; case DIR_SW: - this->z_pos += (this->x_pos & 1) ^ 1; break; + this->z_pos += (this->x_pos & 1); break; case DIR_NW: - this->z_pos += (this->y_pos & 1); break; + this->z_pos += (this->y_pos & 1) ^ 1; break; case DIR_SE: - this->z_pos += (this->y_pos & 1) ^ 1; break; + this->z_pos += (this->y_pos & 1); break; default: break; } } else if (HasBit(this->gv_flags, GVF_GOINGDOWN_BIT)) { switch (this->direction) { case DIR_NE: - this->z_pos -= (this->x_pos & 1); break; + this->z_pos -= (this->x_pos & 1) ^ 1; break; case DIR_SW: - this->z_pos -= (this->x_pos & 1) ^ 1; break; + this->z_pos -= (this->x_pos & 1); break; case DIR_NW: - this->z_pos -= (this->y_pos & 1); break; + this->z_pos -= (this->y_pos & 1) ^ 1; break; case DIR_SE: - this->z_pos -= (this->y_pos & 1) ^ 1; break; + this->z_pos -= (this->y_pos & 1); break; default: break; } } @@ -212,8 +212,7 @@ struct GroundVehicle : public Specialize int8 d = DiagDirToAxis(dir) == AXIS_X ? x_pos : y_pos; /* We need only the least significant bit */ d &= 1; - /* Conditional "^ 1". Optimised to "(dir - 1) <= 1". */ - d ^= (int8)(dir == DIAGDIR_SW || dir == DIAGDIR_SE); + d ^= (int8)(dir == DIAGDIR_NW || dir == DIAGDIR_NE); /* Subtraction instead of addition because we are testing for GVF_GOINGUP_BIT. * GVF_GOINGUP_BIT is used because it's bit 0, so simple AND can be used, * without any shift */ diff --git a/src/landscape.cpp b/src/landscape.cpp --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -210,137 +210,79 @@ uint ApplyFoundationToSlope(Foundation f /** - * Determines height at given coordinate of a slope - * @param x x coordinate - * @param y y coordinate + * Determines height at given coordinate of a slope. + * + * At the northern corner (0, 0) the result is always a multiple of TILE_HEIGHT. + * When the height is a fractional Z, then the height is rounded down. For example, + * when at the height is 0 at x = 0 and the height is 8 at x = 16 (actually x = 0 + * of the next tile), then height is 0 at x = 1, 1 at x = 2, and 7 at x = 15. + * @param x x coordinate (value from 0 to 15) + * @param y y coordinate (value from 0 to 15) * @param corners slope to examine * @return height of given point of given slope */ uint GetPartialPixelZ(int x, int y, Slope corners) { if (IsHalftileSlope(corners)) { + /* A foundation is placed on half the tile at a specific corner. This means that, + * depending on the corner, that one half of the tile is at the maximum height. */ switch (GetHalftileSlopeCorner(corners)) { case CORNER_W: - if (x - y >= 0) return GetSlopeMaxPixelZ(corners); + if (x > y) return GetSlopeMaxPixelZ(corners); break; case CORNER_S: - if (x - (y ^ 0xF) >= 0) return GetSlopeMaxPixelZ(corners); + if (x + y >= (int)TILE_SIZE) return GetSlopeMaxPixelZ(corners); break; case CORNER_E: - if (y - x >= 0) return GetSlopeMaxPixelZ(corners); + if (x <= y) return GetSlopeMaxPixelZ(corners); break; case CORNER_N: - if ((y ^ 0xF) - x >= 0) return GetSlopeMaxPixelZ(corners); + if (x + y < (int)TILE_SIZE) return GetSlopeMaxPixelZ(corners); break; default: NOT_REACHED(); } } - int z = 0; - switch (RemoveHalftileSlope(corners)) { - case SLOPE_W: - if (x - y >= 0) { - z = (x - y) >> 1; - } - break; - - case SLOPE_S: - y ^= 0xF; - if ((x - y) >= 0) { - z = (x - y) >> 1; - } - break; - - case SLOPE_SW: - z = (x >> 1) + 1; - break; + case SLOPE_FLAT: return 0; - case SLOPE_E: - if (y - x >= 0) { - z = (y - x) >> 1; - } - break; - - case SLOPE_EW: - case SLOPE_NS: - case SLOPE_ELEVATED: - z = 4; - break; + /* One corner is up.*/ + case SLOPE_N: return x + y <= (int)TILE_SIZE ? (TILE_SIZE - x - y) >> 1 : 0; + case SLOPE_E: return y >= x ? (1 + y - x) >> 1 : 0; + case SLOPE_S: return x + y >= (int)TILE_SIZE ? (1 + x + y - TILE_SIZE) >> 1 : 0; + case SLOPE_W: return x >= y ? (x - y) >> 1 : 0; - case SLOPE_SE: - z = (y >> 1) + 1; - break; - - case SLOPE_WSE: - z = 8; - y ^= 0xF; - if (x - y < 0) { - z += (x - y) >> 1; - } - break; - - case SLOPE_N: - y ^= 0xF; - if (y - x >= 0) { - z = (y - x) >> 1; - } - break; + /* Two corners next to eachother are up. */ + case SLOPE_NE: return (TILE_SIZE - x) >> 1; + case SLOPE_SE: return (y + 1) >> 1; + case SLOPE_SW: return (x + 1) >> 1; + case SLOPE_NW: return (TILE_SIZE - y) >> 1; - case SLOPE_NW: - z = (y ^ 0xF) >> 1; - break; - - case SLOPE_NWS: - z = 8; - if (x - y < 0) { - z += (x - y) >> 1; - } - break; + /* Three corners are up on the same level. */ + case SLOPE_ENW: return x + y >= (int)TILE_SIZE ? TILE_HEIGHT - ((1 + x + y - TILE_SIZE) >> 1) : TILE_HEIGHT; + case SLOPE_SEN: return y < x ? TILE_HEIGHT - ((x - y) >> 1) : TILE_HEIGHT; + case SLOPE_WSE: return x + y <= (int)TILE_SIZE ? TILE_HEIGHT - ((TILE_SIZE - x - y) >> 1) : TILE_HEIGHT; + case SLOPE_NWS: return x < y ? TILE_HEIGHT - ((1 + y - x) >> 1) : TILE_HEIGHT; - case SLOPE_NE: - z = (x ^ 0xF) >> 1; - break; - - case SLOPE_ENW: - z = 8; - y ^= 0xF; - if (y - x < 0) { - z += (y - x) >> 1; - } - break; + /* Two corners at opposite sides are up. */ + case SLOPE_NS: return x + y < (int)TILE_SIZE ? (TILE_SIZE - x - y) >> 1 : (1 + x + y - TILE_SIZE) >> 1; + case SLOPE_EW: return x >= y ? (x - y) >> 1 : (1 + y - x) >> 1; - case SLOPE_SEN: - z = 8; - if (y - x < 0) { - z += (y - x) >> 1; - } - break; - - case SLOPE_STEEP_S: - z = 1 + ((x + y) >> 1); - break; + /* Very special cases. */ + case SLOPE_ELEVATED: return TILE_HEIGHT; - case SLOPE_STEEP_W: - z = 1 + ((x + (y ^ 0xF)) >> 1); - break; - - case SLOPE_STEEP_N: - z = 1 + (((x ^ 0xF) + (y ^ 0xF)) >> 1); - break; + /* Steep slopes. The top is at 2 * TILE_HEIGHT. */ + case SLOPE_STEEP_N: return (TILE_SIZE - x + TILE_SIZE - y) >> 1; + case SLOPE_STEEP_E: return (TILE_SIZE + 1 + y - x) >> 1; + case SLOPE_STEEP_S: return (1 + x + y) >> 1; + case SLOPE_STEEP_W: return (TILE_SIZE + x - y) >> 1; - case SLOPE_STEEP_E: - z = 1 + (((x ^ 0xF) + y) >> 1); - break; - - default: break; + default: NOT_REACHED(); } - - return z; } /** diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -528,6 +528,25 @@ static uint FixVehicleInclination(Vehicl } /** + * Check whether the ground vehicles are at the correct Z-coordinate. When they + * are not, this will cause all kinds of problems later on as the vehicle might + * not get onto bridges and so on. + */ +static void CheckGroundVehiclesAtCorrectZ() +{ + for (Vehicle *v : Vehicle::Iterate()) { + if (v->IsGroundVehicle()) { + /* + * Either the vehicle is not actually on the given tile, i.e. it is + * in the wormhole of a bridge or a tunnel, or the Z-coordinate must + * be the same as when it would be recalculated right now. + */ + assert(v->tile != TileVirtXY(v->x_pos, v->y_pos) || v->z_pos == GetSlopePixelZ(v->x_pos, v->y_pos, true)); + } + } +} + +/** * Checks for the possibility that a bridge may be on this tile * These are in fact all the tile types on which a bridge can be found * @param t The tile to analyze @@ -1249,7 +1268,7 @@ bool AfterLoadGame() case DIAGDIR_SW: if ((v->x_pos & 0xF) != TILE_SIZE - 1) continue; break; case DIAGDIR_NW: if ((v->y_pos & 0xF) != 0) continue; break; } - } else if (v->z_pos > GetSlopePixelZ(v->x_pos, v->y_pos, true)) { + } else if (v->z_pos > GetTileMaxPixelZ(TileVirtXY(v->x_pos, v->y_pos))) { v->tile = GetNorthernBridgeEnd(v->tile); v->UpdatePosition(); } else { @@ -2594,6 +2613,24 @@ bool AfterLoadGame() } } + + if (IsSavegameVersionBefore(SLV_CONSISTENT_PARTIAL_Z)) { + /* + * The logic of GetPartialPixelZ has been changed, so the resulting Zs on + * the map are consistent. This requires that the Z position of some + * vehicles is updated to reflect this new situation. + * + * This needs to be before SLV_158, because that performs asserts using + * GetSlopePixelZ which internally uses GetPartialPixelZ. + */ + for (Vehicle *v : Vehicle::Iterate()) { + if (v->IsGroundVehicle() && TileVirtXY(v->x_pos, v->y_pos) == v->tile) { + /* Vehicle is on the ground, and not in a wormhole. */ + v->z_pos = GetSlopePixelZ(v->x_pos, v->y_pos, true); + } + } + } + if (IsSavegameVersionBefore(SLV_158)) { for (Vehicle *v : Vehicle::Iterate()) { switch (v->type) { @@ -3228,6 +3265,8 @@ bool AfterLoadGame() AfterLoadLinkGraphs(); + CheckGroundVehiclesAtCorrectZ(); + /* Start the scripts. This MUST happen after everything else except * starting a new company. */ StartScripts(); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -348,6 +348,7 @@ enum SaveLoadVersion : uint16 { SLV_LINKGRAPH_EDGES, ///< 304 PR#10314 Explicitly store link graph edges destination, PR#10471 int64 instead of uint64 league rating SLV_VELOCITY_NAUTICAL, ///< 305 PR#10594 Separation of land and nautical velocity (knots!) + SLV_CONSISTENT_PARTIAL_Z, ///< 306 PR#10570 Conversion from an inconsistent partial Z calculation for slopes, to one that is (more) consistent. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -1680,18 +1680,15 @@ static int GetSlopePixelZ_TunnelBridge(T /* On the bridge ramp? */ if (ground_vehicle) { - int delta; - if (tileh != SLOPE_FLAT) return z + TILE_HEIGHT; switch (dir) { default: NOT_REACHED(); - case DIAGDIR_NE: delta = (TILE_SIZE - 1 - x) / 2; break; - case DIAGDIR_SE: delta = y / 2; break; - case DIAGDIR_SW: delta = x / 2; break; - case DIAGDIR_NW: delta = (TILE_SIZE - 1 - y) / 2; break; + case DIAGDIR_NE: tileh = SLOPE_NE; break; + case DIAGDIR_SE: tileh = SLOPE_SE; break; + case DIAGDIR_SW: tileh = SLOPE_SW; break; + case DIAGDIR_NW: tileh = SLOPE_NW; break; } - return z + 1 + delta; } } @@ -1859,6 +1856,29 @@ static void ChangeTileOwner_TunnelBridge } /** + * Helper to prepare the ground vehicle when entering a bridge. This get called + * when entering the bridge, at the last frame of travel on the bridge head. + * Our calling function gets called before UpdateInclination/UpdateZPosition, + * which normally controls the Z-coordinate. However, in the wormhole of the + * bridge the vehicle is in a strange state so UpdateInclination does not get + * called for the wormhole of the bridge and as such the going up/down bits + * would remain set. As such, this function clears those. In doing so, the call + * to UpdateInclination will not update the Z-coordinate, so that has to be + * done here as well. + * @param gv The ground vehicle entering the bridge. + */ +template +static void PrepareToEnterBridge(T *gv) +{ + if (HasBit(gv->gv_flags, GVF_GOINGUP_BIT)) { + gv->z_pos++; + ClrBit(gv->gv_flags, GVF_GOINGUP_BIT); + } else { + ClrBit(gv->gv_flags, GVF_GOINGDOWN_BIT); + } +} + +/** * Frame when the 'enter tunnel' sound should be played. This is the second * frame on a tile, so the sound is played shortly after entering the tunnel * tile, while the vehicle is still visible. @@ -1959,17 +1979,14 @@ static VehicleEnterTileStatus VehicleEnt case VEH_TRAIN: { Train *t = Train::From(v); t->track = TRACK_BIT_WORMHOLE; - ClrBit(t->gv_flags, GVF_GOINGUP_BIT); - ClrBit(t->gv_flags, GVF_GOINGDOWN_BIT); + PrepareToEnterBridge(t); break; } case VEH_ROAD: { RoadVehicle *rv = RoadVehicle::From(v); rv->state = RVSB_WORMHOLE; - /* There are no slopes inside bridges / tunnels. */ - ClrBit(rv->gv_flags, GVF_GOINGUP_BIT); - ClrBit(rv->gv_flags, GVF_GOINGDOWN_BIT); + PrepareToEnterBridge(rv); break; }