1
0
Fork 0

Compare commits

...

6 Commits

Author SHA1 Message Date
SamuXarick ad086f3c19
Merge 8031d68d01 into 6d95cea73a 2025-07-22 04:46:39 +00:00
translators 6d95cea73a Update: Translations from eints
english (au): 5 changes by krysclarke
korean: 6 changes by telk5093
greek: 5 changes by gh658804
russian: 5 changes by Ln-Wolf
finnish: 5 changes by hpiirai
portuguese: 5 changes by jcteotonio
portuguese (brazilian): 5 changes by pasantoro
2025-07-22 04:46:31 +00:00
Peter Nelson 921d83c324
Codechange: Unify structures with sprite sub-tile bounds and simplify bounding boxes. (#14424)
Lots of different structs contain variations on sub-tile bounds with different naming. Unify into a single struct that can be inherited and passed directly to AddSortableSpriteToDraw.

At the same time, offsets now work more logically: sub-tile bounds now specify the bounding box, and an offset can be applied to the sprite.
2025-07-22 00:02:00 +01:00
SamuXarick 8031d68d01 Change: Ships may reverse on find closest depot order
Ships may be allowed to reverse when:
- manually ordered to find the closest ship depot.
- executing an updated order to find the closest ship depot after departing from a station.
2025-05-24 18:08:46 +01:00
SamuXarick 7df9426000 Change: Find nearest depot to entry edge in intermediate region
When using an intermediate region, find the depot closest to the edge where it entered the region from.
2025-05-24 18:08:46 +01:00
Samu 0b44deedd0 Fix #5713: Use pathfinder to find closest ship depot
When ships are asked to find the closest depot, the depot that is provided is not always reachable. This patch provides the closest reachable ship depot, by utilizing the pathfinder.
2025-05-24 18:05:59 +01:00
54 changed files with 702 additions and 459 deletions

View File

@ -110,7 +110,7 @@ struct Aircraft final : public SpecializedVehicle<Aircraft, VEH_AIRCRAFT> {
uint Crash(bool flooded = false) override;
TileIndex GetOrderStationLocation(StationID station) override;
TileIndex GetCargoTile() const override { return this->First()->tile; }
ClosestDepot FindClosestDepot() override;
ClosestDepot FindClosestDepot(bool may_reverse = false) override;
/**
* Check if the aircraft type is a normal flying device; eg

View File

@ -48,10 +48,7 @@
void Aircraft::UpdateDeltaXY()
{
this->x_offs = -1;
this->y_offs = -1;
this->x_extent = 2;
this->y_extent = 2;
this->bounds = {{-1, -1, 0}, {2, 2, 0}, {}};
switch (this->subtype) {
default: NOT_REACHED();
@ -64,21 +61,21 @@ void Aircraft::UpdateDeltaXY()
case LANDING:
case HELILANDING:
case FLYING:
this->x_extent = 24;
this->y_extent = 24;
/* Bounds are not centred on the aircraft. */
this->bounds.extent.x = 24;
this->bounds.extent.y = 24;
break;
}
this->z_extent = 5;
this->bounds.extent.z = 5;
break;
case AIR_SHADOW:
this->z_extent = 1;
this->x_offs = 0;
this->y_offs = 0;
this->bounds.extent.z = 1;
this->bounds.origin = {};
break;
case AIR_ROTOR:
this->z_extent = 1;
this->bounds.extent.z = 1;
break;
}
}
@ -396,7 +393,7 @@ CommandCost CmdBuildAircraft(DoCommandFlags flags, TileIndex tile, const Engine
}
ClosestDepot Aircraft::FindClosestDepot()
ClosestDepot Aircraft::FindClosestDepot([[maybe_unused]] bool may_reverse)
{
const Station *st = GetTargetAirportIfValid(this);
/* If the station is not a valid airport or if it has no hangars */

View File

@ -69,36 +69,45 @@ static void DrawClearLandFence(const TileInfo *ti)
/* combine fences into one sprite object */
StartSpriteCombine();
int maxz = GetSlopeMaxPixelZ(ti->tileh);
SpriteBounds bounds{{}, {TILE_SIZE, TILE_SIZE, 4}, {}};
bounds.extent.z += GetSlopeMaxPixelZ(ti->tileh);
uint fence_nw = GetFence(ti->tile, DIAGDIR_NW);
if (fence_nw != 0) {
int z = GetSlopePixelZInCorner(ti->tileh, CORNER_W);
bounds.offset.x = 0;
bounds.offset.y = -static_cast<int>(TILE_SIZE);
bounds.offset.z = GetSlopePixelZInCorner(ti->tileh, CORNER_W);
SpriteID sprite = _clear_land_fence_sprites[fence_nw - 1] + _fence_mod_by_tileh_nw[ti->tileh];
AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x, ti->y - 16, 16, 32, maxz - z + 4, ti->z + z, false, 0, 16, -z);
AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false);
}
uint fence_ne = GetFence(ti->tile, DIAGDIR_NE);
if (fence_ne != 0) {
int z = GetSlopePixelZInCorner(ti->tileh, CORNER_E);
bounds.offset.x = -static_cast<int>(TILE_SIZE);
bounds.offset.y = 0;
bounds.offset.z = GetSlopePixelZInCorner(ti->tileh, CORNER_E);
SpriteID sprite = _clear_land_fence_sprites[fence_ne - 1] + _fence_mod_by_tileh_ne[ti->tileh];
AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x - 16, ti->y, 32, 16, maxz - z + 4, ti->z + z, false, 16, 0, -z);
AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false);
}
uint fence_sw = GetFence(ti->tile, DIAGDIR_SW);
uint fence_se = GetFence(ti->tile, DIAGDIR_SE);
if (fence_sw != 0 || fence_se != 0) {
int z = GetSlopePixelZInCorner(ti->tileh, CORNER_S);
bounds.offset.x = 0;
bounds.offset.y = 0;
bounds.offset.z = GetSlopePixelZInCorner(ti->tileh, CORNER_S);
if (fence_sw != 0) {
SpriteID sprite = _clear_land_fence_sprites[fence_sw - 1] + _fence_mod_by_tileh_sw[ti->tileh];
AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x, ti->y, 16, 16, maxz - z + 4, ti->z + z, false, 0, 0, -z);
AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false);
}
if (fence_se != 0) {
SpriteID sprite = _clear_land_fence_sprites[fence_se - 1] + _fence_mod_by_tileh_se[ti->tileh];
AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x, ti->y, 16, 16, maxz - z + 4, ti->z + z, false, 0, 0, -z);
AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false);
}
}
EndSpriteCombine();

View File

@ -28,15 +28,30 @@ inline int CentreBounds(int min, int max, int size)
return (min + max - size + 1) / 2;
}
/** Coordinates of a point in 2D */
struct Point {
int x;
int y;
/** A coordinate with two dimensons. */
template <typename T>
struct Coord2D {
T x = 0; ///< X coordinate.
T y = 0; ///< Y coordinate.
constexpr Point() : x(0), y(0) {}
constexpr Point(int x, int y) : x(x), y(y) {}
constexpr Coord2D() = default;
constexpr Coord2D(T x, T y) : x(x), y(y) {}
};
/** A coordinate with three dimensions. */
template <typename T>
struct Coord3D {
T x = 0; ///< X coordinate.
T y = 0; ///< Y coordinate.
T z = 0; ///< Z coordinate.
constexpr Coord3D() = default;
constexpr Coord3D(T x, T y, T z) : x(x), y(y), z(z) {}
};
/** Coordinates of a point in 2D */
using Point = Coord2D<int>;
/** Dimensions (a width and height) of a rectangle in 2D */
struct Dimension {
uint width;

View File

@ -992,9 +992,5 @@ void ReleaseDisasterVehicle(VehicleID vehicle)
void DisasterVehicle::UpdateDeltaXY()
{
this->x_offs = -1;
this->y_offs = -1;
this->x_extent = 2;
this->y_extent = 2;
this->z_extent = 5;
this->bounds = {{-1, -1, 0}, {2, 2, 5}, {}};
}

View File

@ -619,11 +619,7 @@ bool EffectVehicle::Tick()
void EffectVehicle::UpdateDeltaXY()
{
this->x_offs = 0;
this->y_offs = 0;
this->x_extent = 1;
this->y_extent = 1;
this->z_extent = 1;
this->bounds = {{}, {1, 1, 1}, {}};
}
/**

View File

@ -248,27 +248,12 @@ static int GetPCPElevation(TileIndex tile, DiagDirection pcp_pos)
*/
void DrawRailCatenaryOnTunnel(const TileInfo *ti)
{
/* xmin, ymin, xmax + 1, ymax + 1 of BB */
static const int tunnel_wire_bb[4][4] = {
{ 0, 1, 16, 15 }, // NE
{ 1, 0, 15, 16 }, // SE
{ 0, 1, 16, 15 }, // SW
{ 1, 0, 15, 16 }, // NW
};
DiagDirection dir = GetTunnelBridgeDirection(ti->tile);
SpriteID wire_base = GetWireBase(ti->tile);
const SortableSpriteStruct *sss = &_rail_catenary_sprite_data_tunnel[dir];
const int *bb_data = tunnel_wire_bb[dir];
AddSortableSpriteToDraw(
wire_base + sss->image_offset, PAL_NONE, ti->x + sss->x_offset, ti->y + sss->y_offset,
bb_data[2] - sss->x_offset, bb_data[3] - sss->y_offset, BB_Z_SEPARATOR - sss->z_offset + 1,
GetTilePixelZ(ti->tile) + sss->z_offset,
IsTransparencySet(TO_CATENARY),
bb_data[0] - sss->x_offset, bb_data[1] - sss->y_offset, BB_Z_SEPARATOR - sss->z_offset
);
const SortableSpriteStruct &sss = _rail_catenary_sprite_data_tunnel[dir];
AddSortableSpriteToDraw(wire_base + sss.image_offset, PAL_NONE, ti->x, ti->y, GetTilePixelZ(ti->tile), sss, IsTransparencySet(TO_CATENARY));
}
/**
@ -440,8 +425,8 @@ static void DrawRailCatenaryRailway(const TileInfo *ti)
continue; // No neighbour, go looking for a better position
}
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[temp], PAL_NONE, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE,
elevation, IsTransparencySet(TO_CATENARY), -1, -1);
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[temp], PAL_NONE, x, y, elevation,
{{-1, -1, 0}, {1, 1, BB_HEIGHT_UNDER_BRIDGE}, {1, 1, 0}}, IsTransparencySet(TO_CATENARY));
break; // We already have drawn a pylon, bail out
}
@ -482,7 +467,7 @@ static void DrawRailCatenaryRailway(const TileInfo *ti)
assert(pcp_config != 0); // We have a pylon on neither end of the wire, that doesn't work (since we have no sprites for that)
assert(!IsSteepSlope(tileh[TS_HOME]));
const SortableSpriteStruct *sss = &_rail_catenary_sprite_data[_rail_wires[tileh_selector][t][pcp_config]];
const SortableSpriteStruct &sss = _rail_catenary_sprite_data[_rail_wires[tileh_selector][t][pcp_config]];
/*
* The "wire"-sprite position is inside the tile, i.e. 0 <= sss->?_offset < TILE_SIZE.
@ -490,9 +475,8 @@ static void DrawRailCatenaryRailway(const TileInfo *ti)
* Also note that the result of GetSlopePixelZ() is very special for bridge-ramps, so we round the result up or
* down to the nearest full height change.
*/
AddSortableSpriteToDraw(wire_base + sss->image_offset, PAL_NONE, ti->x + sss->x_offset, ti->y + sss->y_offset,
sss->x_size, sss->y_size, sss->z_size, (GetSlopePixelZ(ti->x + sss->x_offset, ti->y + sss->y_offset, true) + 4) / 8 * 8 + sss->z_offset,
IsTransparencySet(TO_CATENARY));
int z = (GetSlopePixelZ(ti->x + sss.origin.x, ti->y + sss.origin.y, true) + 4) / 8 * 8;
AddSortableSpriteToDraw(wire_base + sss.image_offset, PAL_NONE, ti->x, ti->y, z, sss, IsTransparencySet(TO_CATENARY));
}
}
@ -530,13 +514,12 @@ void DrawRailCatenaryOnBridge(const TileInfo *ti)
SpriteID wire_base = GetWireBase(end, TCX_ON_BRIDGE);
AddSortableSpriteToDraw(wire_base + sss->image_offset, PAL_NONE, ti->x + sss->x_offset, ti->y + sss->y_offset,
sss->x_size, sss->y_size, sss->z_size, height + sss->z_offset,
IsTransparencySet(TO_CATENARY)
);
AddSortableSpriteToDraw(wire_base + sss->image_offset, PAL_NONE, ti->x, ti->y, height, *sss, IsTransparencySet(TO_CATENARY));
SpriteID pylon_base = GetPylonBase(end, TCX_ON_BRIDGE);
static constexpr SpriteBounds pylon_bounds{{-1, -1, 0}, {1, 1, BB_HEIGHT_UNDER_BRIDGE}, {1, 1, 0}};
/* Finished with wires, draw pylons
* every other tile needs a pylon on the northern end */
if (num % 2) {
@ -545,7 +528,7 @@ void DrawRailCatenaryOnBridge(const TileInfo *ti)
if (HasBit(tlg, (axis == AXIS_X ? 0 : 1))) ppp_pos = ReverseDir(ppp_pos);
uint x = ti->x + _x_pcp_offsets[pcp_pos] + _x_ppp_offsets[ppp_pos];
uint y = ti->y + _y_pcp_offsets[pcp_pos] + _y_ppp_offsets[ppp_pos];
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[ppp_pos], PAL_NONE, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, height, IsTransparencySet(TO_CATENARY), -1, -1);
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[ppp_pos], PAL_NONE, x, y, height, pylon_bounds, IsTransparencySet(TO_CATENARY));
}
/* need a pylon on the southern end of the bridge */
@ -555,7 +538,7 @@ void DrawRailCatenaryOnBridge(const TileInfo *ti)
if (HasBit(tlg, (axis == AXIS_X ? 0 : 1))) ppp_pos = ReverseDir(ppp_pos);
uint x = ti->x + _x_pcp_offsets[pcp_pos] + _x_ppp_offsets[ppp_pos];
uint y = ti->y + _y_pcp_offsets[pcp_pos] + _y_ppp_offsets[ppp_pos];
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[ppp_pos], PAL_NONE, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, height, IsTransparencySet(TO_CATENARY), -1, -1);
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[ppp_pos], PAL_NONE, x, y, height, pylon_bounds, IsTransparencySet(TO_CATENARY));
}
}
@ -569,17 +552,12 @@ void DrawRailCatenary(const TileInfo *ti)
switch (GetTileType(ti->tile)) {
case MP_RAILWAY:
if (IsRailDepot(ti->tile)) {
const SortableSpriteStruct *sss = &_rail_catenary_sprite_data_depot[GetRailDepotDirection(ti->tile)];
const SortableSpriteStruct &sss = _rail_catenary_sprite_data_depot[GetRailDepotDirection(ti->tile)];
SpriteID wire_base = GetWireBase(ti->tile);
/* This wire is not visible with the default depot sprites */
AddSortableSpriteToDraw(
wire_base + sss->image_offset, PAL_NONE, ti->x + sss->x_offset, ti->y + sss->y_offset,
sss->x_size, sss->y_size, sss->z_size,
GetTileMaxPixelZ(ti->tile) + sss->z_offset,
IsTransparencySet(TO_CATENARY)
);
AddSortableSpriteToDraw(wire_base + sss.image_offset, PAL_NONE, ti->x, ti->y, GetTileMaxPixelZ(ti->tile), sss, IsTransparencySet(TO_CATENARY));
return;
}
break;

View File

@ -374,13 +374,7 @@ static void DrawTile_Industry(TileInfo *ti)
image = dits->building.sprite;
if (image != 0) {
AddSortableSpriteToDraw(image, SpriteLayoutPaletteTransform(image, dits->building.pal, GetColourPalette(ind->random_colour)),
ti->x + dits->subtile_x,
ti->y + dits->subtile_y,
dits->width,
dits->height,
dits->dz,
ti->z,
IsTransparencySet(TO_INDUSTRIES));
*ti, *dits, IsTransparencySet(TO_INDUSTRIES));
if (IsTransparencySet(TO_INDUSTRIES)) return;
}

View File

@ -450,9 +450,8 @@ void DrawFoundation(TileInfo *ti, Foundation f)
if (IsSteepSlope(ti->tileh)) {
if (!IsNonContinuousFoundation(f)) {
/* Lower part of foundation */
AddSortableSpriteToDraw(
leveled_base + (ti->tileh & ~SLOPE_STEEP), PAL_NONE, ti->x, ti->y, TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1, ti->z
);
static constexpr SpriteBounds bounds{{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1}, {}};
AddSortableSpriteToDraw(leveled_base + (ti->tileh & ~SLOPE_STEEP), PAL_NONE, *ti, bounds);
}
Corner highest_corner = GetHighestSlopeCorner(ti->tileh);
@ -462,24 +461,25 @@ void DrawFoundation(TileInfo *ti, Foundation f)
/* inclined foundation */
uint8_t inclined = highest_corner * 2 + (f == FOUNDATION_INCLINED_Y ? 1 : 0);
AddSortableSpriteToDraw(inclined_base + inclined, PAL_NONE, ti->x, ti->y,
f == FOUNDATION_INCLINED_X ? TILE_SIZE : 1,
f == FOUNDATION_INCLINED_Y ? TILE_SIZE : 1,
TILE_HEIGHT, ti->z
);
SpriteBounds bounds{{}, {1, 1, TILE_HEIGHT}, {}};
if (f == FOUNDATION_INCLINED_X) bounds.extent.x = TILE_SIZE;
if (f == FOUNDATION_INCLINED_Y) bounds.extent.y = TILE_SIZE;
AddSortableSpriteToDraw(inclined_base + inclined, PAL_NONE, *ti, bounds);
OffsetGroundSprite(0, 0);
} else if (IsLeveledFoundation(f)) {
AddSortableSpriteToDraw(leveled_base + SlopeWithOneCornerRaised(highest_corner), PAL_NONE, ti->x, ti->y, TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1, ti->z - TILE_HEIGHT);
static constexpr SpriteBounds bounds{{0, 0, -(int)TILE_HEIGHT}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1}, {}};
AddSortableSpriteToDraw(leveled_base + SlopeWithOneCornerRaised(highest_corner), PAL_NONE, *ti, bounds);
OffsetGroundSprite(0, -(int)TILE_HEIGHT);
} else if (f == FOUNDATION_STEEP_LOWER) {
/* one corner raised */
OffsetGroundSprite(0, -(int)TILE_HEIGHT);
} else {
/* halftile foundation */
int x_bb = (((highest_corner == CORNER_W) || (highest_corner == CORNER_S)) ? TILE_SIZE / 2 : 0);
int y_bb = (((highest_corner == CORNER_S) || (highest_corner == CORNER_E)) ? TILE_SIZE / 2 : 0);
int8_t x_bb = (((highest_corner == CORNER_W) || (highest_corner == CORNER_S)) ? TILE_SIZE / 2 : 0);
int8_t y_bb = (((highest_corner == CORNER_S) || (highest_corner == CORNER_E)) ? TILE_SIZE / 2 : 0);
AddSortableSpriteToDraw(halftile_base + highest_corner, PAL_NONE, ti->x + x_bb, ti->y + y_bb, TILE_SIZE / 2, TILE_SIZE / 2, TILE_HEIGHT - 1, ti->z + TILE_HEIGHT);
SpriteBounds bounds{{x_bb, y_bb, TILE_HEIGHT}, {TILE_SIZE / 2, TILE_SIZE / 2, TILE_HEIGHT - 1}, {}};
AddSortableSpriteToDraw(halftile_base + highest_corner, PAL_NONE, *ti, bounds);
/* Reposition ground sprite back to original position after bounding box change above. This is similar to
* RemapCoords() but without zoom scaling. */
Point pt = {(y_bb - x_bb) * 2, y_bb + x_bb};
@ -488,15 +488,17 @@ void DrawFoundation(TileInfo *ti, Foundation f)
} else {
if (IsLeveledFoundation(f)) {
/* leveled foundation */
AddSortableSpriteToDraw(leveled_base + ti->tileh, PAL_NONE, ti->x, ti->y, TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1, ti->z);
static constexpr SpriteBounds bounds{{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1}, {}};
AddSortableSpriteToDraw(leveled_base + ti->tileh, PAL_NONE, *ti, bounds);
OffsetGroundSprite(0, -(int)TILE_HEIGHT);
} else if (IsNonContinuousFoundation(f)) {
/* halftile foundation */
Corner halftile_corner = GetHalftileFoundationCorner(f);
int x_bb = (((halftile_corner == CORNER_W) || (halftile_corner == CORNER_S)) ? TILE_SIZE / 2 : 0);
int y_bb = (((halftile_corner == CORNER_S) || (halftile_corner == CORNER_E)) ? TILE_SIZE / 2 : 0);
int8_t x_bb = (((halftile_corner == CORNER_W) || (halftile_corner == CORNER_S)) ? TILE_SIZE / 2 : 0);
int8_t y_bb = (((halftile_corner == CORNER_S) || (halftile_corner == CORNER_E)) ? TILE_SIZE / 2 : 0);
AddSortableSpriteToDraw(halftile_base + halftile_corner, PAL_NONE, ti->x + x_bb, ti->y + y_bb, TILE_SIZE / 2, TILE_SIZE / 2, TILE_HEIGHT - 1, ti->z);
SpriteBounds bounds{{x_bb, y_bb, 0}, {TILE_SIZE / 2, TILE_SIZE / 2, TILE_HEIGHT - 1}, {}};
AddSortableSpriteToDraw(halftile_base + halftile_corner, PAL_NONE, *ti, bounds);
/* Reposition ground sprite back to original position after bounding box change above. This is similar to
* RemapCoords() but without zoom scaling. */
Point pt = {(y_bb - x_bb) * 2, y_bb + x_bb};
@ -511,17 +513,17 @@ void DrawFoundation(TileInfo *ti, Foundation f)
/* tile-slope = sloped along X/Y, foundation-slope = three corners raised */
spr = inclined_base + 2 * GetRailFoundationCorner(f) + ((ti->tileh == SLOPE_SW || ti->tileh == SLOPE_NE) ? 1 : 0);
}
AddSortableSpriteToDraw(spr, PAL_NONE, ti->x, ti->y, TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1, ti->z);
static constexpr SpriteBounds bounds{{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1}, {}};
AddSortableSpriteToDraw(spr, PAL_NONE, *ti, bounds);
OffsetGroundSprite(0, 0);
} else {
/* inclined foundation */
uint8_t inclined = GetHighestSlopeCorner(ti->tileh) * 2 + (f == FOUNDATION_INCLINED_Y ? 1 : 0);
AddSortableSpriteToDraw(inclined_base + inclined, PAL_NONE, ti->x, ti->y,
f == FOUNDATION_INCLINED_X ? TILE_SIZE : 1,
f == FOUNDATION_INCLINED_Y ? TILE_SIZE : 1,
TILE_HEIGHT, ti->z
);
SpriteBounds bounds{{}, {1, 1, TILE_HEIGHT}, {}};
if (f == FOUNDATION_INCLINED_X) bounds.extent.x = TILE_SIZE;
if (f == FOUNDATION_INCLINED_Y) bounds.extent.y = TILE_SIZE;
AddSortableSpriteToDraw(inclined_base + inclined, PAL_NONE, *ti, bounds);
OffsetGroundSprite(0, 0);
}
ti->z += ApplyPixelFoundationToSlope(f, ti->tileh);

View File

@ -635,8 +635,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Não mos
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Mostrar/Ocultar gráfico para este tipo de carga
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Histórico de Carga
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Produzido
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Transportado
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Entregue
STR_GRAPH_INDUSTRY_RANGE_WAITING :Aguardando
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Mostrar avaliações detalhadas de desempenho
@ -4024,6 +4027,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Produç
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Produção no último minuto:
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% transportado)
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Centralizar visualização principal na localização da indústria. Ctrl+Clique para abrir uma nova visualização na localização da indústria
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Gráfico da Carga
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Mostrar o gráfico do histórico de carga da indústria
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Nível de produção: {YELLOW}{COMMA}%
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}A indústria anunciou fechamento iminente!

View File

@ -634,8 +634,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Display
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Toggle graph of this cargo type
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Cargo History
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Produced
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Transported
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Delivered
STR_GRAPH_INDUSTRY_RANGE_WAITING :Waiting
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Show detailed performance ratings
@ -4023,6 +4026,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Producti
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Production last minute:
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% transported)
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Centre the main view on industry location. Ctrl+Click to open a new viewport on industry location
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Cargo Graph
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Shows the graph of industry cargo history
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Production level: {YELLOW}{COMMA}%
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}The industry has announced imminent closure!

View File

@ -634,8 +634,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Älä n
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Valitse, näytetäänkö tämän rahdin kuvaaja
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} rahtihistoria
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Tuotettu
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Kuljetettu
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Toimitettu
STR_GRAPH_INDUSTRY_RANGE_WAITING :Odottava
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Näytä tarkat suorituskykyarviot
@ -4023,6 +4026,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Tuotanto
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Tuotanto viime minuutissa:
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}{NBSP}% kuljetettu)
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Keskitä päänäkymä tuotantolaitoksen sijaintiin. Ctrl+napsautus avataksesi uuden näkymäikkunan laitoksen sijaintiin
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Rahdin kuvaaja
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Näyttää tuotantolaitoksen rahtihistorian kuvaajana
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Tuotantotaso: {YELLOW}{COMMA}{NBSP}%
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}Teollisuuslaitos ilmoittaa välittömästä lakkautuksesta!

View File

@ -727,8 +727,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Εμφά
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Εναλλαγή γραφήματος αυτού του τύπου φορτίου
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Ιστορικό φορτίου
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Παράχθηκε/αν
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Μεταφέρθηκε/αν
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Παραδόθηκε
STR_GRAPH_INDUSTRY_RANGE_WAITING :Αναμονή
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Εμφάνιση λεπτομεριών αποδόσεων
@ -4117,6 +4120,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Παρα
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Παραγωγή τελευταίου λεπτού:
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% μεταφέρθηκαν)
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Κεντράρισμα εικόνας στην περιοχή της βιομηχανίας. Ctrl+Κλικ για άνοιγμα νέου παραθύρου προβολής στην περιοχή της βιομηχανίας
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Γράφημα φορτίου
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Εμφανίζει το γράφημα του ιστορικού του φορτίου της βιομηχανίας
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Επίπεδο παραγωγής: {YELLOW}{COMMA}%
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}Η βιομηχανία έχει ανακοινώσει άμεσο κλείσιμο!

View File

@ -635,8 +635,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}화물
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}이 화물의 그래프를 표시하거나 숨깁니다
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - 화물 그래프
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :생산량
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :수송량
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :수송됨
STR_GRAPH_INDUSTRY_RANGE_WAITING :대기 중
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}상세 성취도를 봅니다.
@ -4024,6 +4027,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}지난
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}지난 1분간 생산량:
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% 수송됨)
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}이 산업시설로 이동합니다. CTRL+클릭하면 이 산업시설을 기준으로 새로운 외부 화면을 엽니다
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}화물 그래프
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}산업시설 화물 이력 그래프를 보여줍니다
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}생산 수준: {YELLOW}{COMMA}%
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}산업시설이 곧 폐쇄됩니다!
@ -4999,6 +5004,7 @@ STR_ERROR_FLAT_LAND_REQUIRED :{WHITE}평지
STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION :{WHITE}잘못된 방향으로 땅이 기울어졌습니다
STR_ERROR_CAN_T_DO_THIS :{WHITE}그렇게 할 수 없습니다...
STR_ERROR_BUILDING_MUST_BE_DEMOLISHED :{WHITE}건물을 먼저 제거해야 합니다
STR_ERROR_BUILDING_IS_PROTECTED :{WHITE}... 건물이 보호되어 있습니다
STR_ERROR_CAN_T_CLEAR_THIS_AREA :{WHITE}이 지역을 파괴할 수 없습니다...
STR_ERROR_SITE_UNSUITABLE :{WHITE}... 알맞지 않은 장소입니다
STR_ERROR_ALREADY_BUILT :{WHITE}... 이미 지어져있습니다

View File

@ -635,8 +635,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Não mos
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Alternar o gráfico para este tipo de carga
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Histórico da Carga
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Produzido
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Transportado
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Entregue
STR_GRAPH_INDUSTRY_RANGE_WAITING :Em Espera
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Exibir classificações detalhadas de desempenho
@ -4024,6 +4027,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Produç
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Produção no último minuto:
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% transportado)
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Centrar visualização na localização da indústria. Ctrl+Clique para abrir um novo visualizador na localização da indústria
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Gráfico da Carga
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Mostra o gráfico do histórico da carga desta indústria
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Nível de produção: {YELLOW}{COMMA}%
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}A indústria anunciou encerramento iminente!

View File

@ -772,8 +772,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Скры
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Включить/отключить отображение груза на графике
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Продукция
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Произведено
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Перевезено
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Доставлено
STR_GRAPH_INDUSTRY_RANGE_WAITING :В ожидании
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Показать составляющие части рейтинга
@ -4198,6 +4201,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Прои
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Произведено за минуту:
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% перевезено)
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Показать предприятие в основном окне. Ctrl+щелчок{NBSP}- показать в дополнительном окне.
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}График доставки
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Показать график продукции предприятия
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Производительность: {YELLOW}{COMMA}%
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}Предприятие скоро закрывается!

View File

@ -90,12 +90,12 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR
/* no relative bounding box support */
DrawTileSeqStruct &dtss = tmp_layout.emplace_back();
dtss.delta_x = delta_x;
dtss.delta_y = buf.ReadByte();
dtss.delta_z = buf.ReadByte();
dtss.size_x = buf.ReadByte();
dtss.size_y = buf.ReadByte();
dtss.size_z = buf.ReadByte();
dtss.origin.x = delta_x;
dtss.origin.y = buf.ReadByte();
dtss.origin.z = buf.ReadByte();
dtss.extent.x = buf.ReadByte();
dtss.extent.y = buf.ReadByte();
dtss.extent.z = buf.ReadByte();
ReadSpriteLayoutSprite(buf, false, true, false, GSF_STATIONS, &dtss.image);
/* On error, bail out immediately. Temporary GRF data was already freed */

View File

@ -207,15 +207,15 @@ bool ReadSpriteLayout(ByteReader &buf, uint num_building_sprites, bool use_cur_s
return true;
}
seq->delta_x = buf.ReadByte();
seq->delta_y = buf.ReadByte();
seq->origin.x = buf.ReadByte();
seq->origin.y = buf.ReadByte();
if (!no_z_position) seq->delta_z = buf.ReadByte();
if (!no_z_position) seq->origin.z = buf.ReadByte();
if (seq->IsParentSprite()) {
seq->size_x = buf.ReadByte();
seq->size_y = buf.ReadByte();
seq->size_z = buf.ReadByte();
seq->extent.x = buf.ReadByte();
seq->extent.y = buf.ReadByte();
seq->extent.z = buf.ReadByte();
}
ReadSpriteLayoutRegisters(buf, flags, seq->IsParentSprite(), dts, i + 1);

View File

@ -609,7 +609,7 @@ SpriteLayoutProcessor::SpriteLayoutProcessor(const NewGRFSpriteLayout &raw_layou
* Also include the groundsprite into the sequence for easier processing. */
DrawTileSeqStruct &copy = this->result_seq.emplace_back();
copy.image = this->raw_layout->ground;
copy.delta_z = static_cast<int8_t>(0x80);
copy.origin.z = static_cast<int8_t>(0x80);
this->result_seq.insert(this->result_seq.end(), this->raw_layout->seq.begin(), this->raw_layout->seq.end());
@ -692,13 +692,13 @@ void SpriteLayoutProcessor::ProcessRegisters(const ResolverObject &object, uint8
if (result.IsParentSprite()) {
if (flags & TLF_BB_XY_OFFSET) {
result.delta_x += object.GetRegister(regs->delta.parent[0]);
result.delta_y += object.GetRegister(regs->delta.parent[1]);
result.origin.x += object.GetRegister(regs->delta.parent[0]);
result.origin.y += object.GetRegister(regs->delta.parent[1]);
}
if (flags & TLF_BB_Z_OFFSET) result.delta_z += object.GetRegister(regs->delta.parent[2]);
if (flags & TLF_BB_Z_OFFSET) result.origin.z += object.GetRegister(regs->delta.parent[2]);
} else {
if (flags & TLF_CHILD_X_OFFSET) result.delta_x += object.GetRegister(regs->delta.child[0]);
if (flags & TLF_CHILD_Y_OFFSET) result.delta_y += object.GetRegister(regs->delta.child[1]);
if (flags & TLF_CHILD_X_OFFSET) result.origin.x += object.GetRegister(regs->delta.child[0]);
if (flags & TLF_CHILD_Y_OFFSET) result.origin.y += object.GetRegister(regs->delta.child[1]);
}
}
}

View File

@ -476,13 +476,7 @@ static void DrawTile_Object(TileInfo *ti)
if (!IsInvisibilitySet(TO_STRUCTURES)) {
for (const DrawTileSeqStruct &dtss : dts->GetSequence()) {
AddSortableSpriteToDraw(
dtss.image.sprite, palette,
ti->x + dtss.delta_x, ti->y + dtss.delta_y,
dtss.size_x, dtss.size_y,
dtss.size_z, ti->z + dtss.delta_z,
IsTransparencySet(TO_STRUCTURES)
);
AddSortableSpriteToDraw(dtss.image.sprite, palette, *ti, dtss, IsTransparencySet(TO_STRUCTURES));
}
}
} else {

View File

@ -1896,10 +1896,11 @@ VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v)
* Update the vehicle's destination tile from an order.
* @param order the order the vehicle currently has
* @param v the vehicle to update
* @param may_reverse Whether the vehicle is allowed to reverse when executing the updated order.
* @param conditional_depth the depth (amount of steps) to go with conditional orders. This to prevent infinite loops.
* @param pbs_look_ahead Whether we are forecasting orders for pbs reservations in advance. If true, the order indices must not be modified.
*/
bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool pbs_look_ahead)
bool UpdateOrderDest(Vehicle *v, const Order *order, bool may_reverse, int conditional_depth, bool pbs_look_ahead)
{
if (conditional_depth > v->GetNumOrders()) {
v->current_order.Free();
@ -1926,7 +1927,7 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool
if (v->dest_tile == 0 && TimerGameEconomy::date_fract != (v->index % Ticks::DAY_TICKS)) break;
/* We need to search for the nearest depot (hangar). */
ClosestDepot closest_depot = v->FindClosestDepot();
ClosestDepot closest_depot = v->FindClosestDepot(may_reverse);
if (closest_depot.found) {
/* PBS reservations cannot reverse */
@ -2018,7 +2019,7 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool
}
v->current_order = *order;
return UpdateOrderDest(v, order, conditional_depth + 1, pbs_look_ahead);
return UpdateOrderDest(v, order, may_reverse, conditional_depth + 1, pbs_look_ahead);
}
/**
@ -2116,7 +2117,7 @@ bool ProcessOrders(Vehicle *v)
break;
}
return UpdateOrderDest(v, order) && may_reverse;
return UpdateOrderDest(v, order, may_reverse) && may_reverse;
}
/**

View File

@ -20,7 +20,7 @@ void InvalidateVehicleOrder(const Vehicle *v, int data);
void CheckOrders(const Vehicle*);
void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist = false, bool reset_order_indices = true);
bool ProcessOrders(Vehicle *v);
bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth = 0, bool pbs_look_ahead = false);
bool UpdateOrderDest(Vehicle *v, const Order *order, bool may_reverse = false, int conditional_depth = 0, bool pbs_look_ahead = false);
VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v);
uint GetOrderDistance(VehicleOrderID prev, VehicleOrderID cur, const Vehicle *v, int conditional_depth = 0);

View File

@ -430,3 +430,83 @@ void PrintWaterRegionDebugInfo(TileIndex tile)
{
GetUpdatedWaterRegion(tile).PrintDebugInfo();
}
/**
* Tests the provided callback function on all tiles of the water patch of the region
* and returns true on the first tile that passes the callback test.
* @param callback The test function that will be called for the water patch.
* @param water_region_patch Water patch within the water region to test the callback.
* @return true if it passes the callback test, or false if the callback failed.
*/
bool TestTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_patch, TestTileIndexCallBack &callback)
{
const WaterRegion region = GetUpdatedWaterRegion(water_region_patch.x, water_region_patch.y);
/* Check if the region has a tile which passes the callback test. */
for (const TileIndex tile : region) {
if (region.GetLabel(tile) != water_region_patch.label || !callback(tile)) continue;
return true;
}
return false;
}
/**
* Tests the provided callback function on all tiles of the current water patch of the region, collects the
* tiles which passed the callback and returns the tile closest to the edge from where the region is entered from.
* @param high_level_path A span containing at least current and parent water patches.
* @param callback The test function that will be called for each tile in the water patch.
* @return The tile closest to the edge from where it came from that passed the callback test, or INVALID_TILE if no tile passed.
*/
TileIndex FindClosestEnteringTile(const std::span<WaterRegionPatchDesc> high_level_path, TestTileIndexCallBack &callback)
{
assert(high_level_path.size() > 1);
const WaterRegionPatchDesc &current_water_region_patch = high_level_path.back();
const WaterRegion current_region = GetUpdatedWaterRegion(current_water_region_patch.x, current_water_region_patch.y);
/* Check if the current region has a tile which passes the callback test. */
std::vector<TileIndex> tile_list;
for (const TileIndex tile : current_region) {
if (current_region.GetLabel(tile) != current_water_region_patch.label || !callback(tile)) continue;
/* We collect the tiles when we know which region we came from for further evaluation. */
tile_list.push_back(tile);
}
/* If there aren't any tiles that passed the callback, return with an invalid tile. */
if (tile_list.empty()) return INVALID_TILE;
/* If there's only one, just return it. */
if (tile_list.size() == 1) return tile_list.front();
const TileIndex top_tile = current_region.begin();
const TileIndex bot_tile = TileAddXY(top_tile, WATER_REGION_EDGE_LENGTH - 1, WATER_REGION_EDGE_LENGTH - 1);
/* Get the side from which the current region is entered from. */
const WaterRegionPatchDesc &parent_water_region_patch = high_level_path[high_level_path.size() - 2];
const WaterRegion parent_region = GetUpdatedWaterRegion(parent_water_region_patch.x, parent_water_region_patch.y);
const DiagDirection side = DiagdirBetweenTiles(top_tile, parent_region.begin());
/* Depending on the side, determine which corner tile to use to extract their x or y coordinates. */
const bool is_at_top = side == DIAGDIR_NE || side == DIAGDIR_NW;
const TileIndex edge_tile = is_at_top ? top_tile : bot_tile;
const bool is_axis_x = DiagDirToAxis(side) == AXIS_X;
const int x_or_y_edge = is_axis_x ? TileX(edge_tile) : TileY(edge_tile);
/* With more than one tile passing the callback, calculate the tile that is closest to the edge from whence it came. */
TileIndex best_tile = INVALID_TILE;
int best_dist = WATER_REGION_EDGE_LENGTH;
for (const TileIndex &tile : tile_list) {
const int x_or_y_tile = is_axis_x ? TileX(tile) : TileY(tile);
const int dist_to_edge = std::abs(x_or_y_tile - x_or_y_edge);
assert(dist_to_edge < WATER_REGION_EDGE_LENGTH);
if (dist_to_edge >= best_dist) continue;
best_dist = dist_to_edge;
best_tile = tile;
}
return best_tile;
}

View File

@ -64,4 +64,8 @@ void AllocateWaterRegions();
void PrintWaterRegionDebugInfo(TileIndex tile);
using TestTileIndexCallBack = std::function<bool(const TileIndex)>;
bool TestTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_patch, TestTileIndexCallBack &callback);
TileIndex FindClosestEnteringTile(const std::span<WaterRegionPatchDesc> high_level_path, TestTileIndexCallBack &callback);
#endif /* WATER_REGIONS_H */

View File

@ -34,6 +34,17 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP
*/
bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir);
/**
* Used when user sends ship to the nearest depot or if ship needs servicing using YAPF.
* @param v ship that needs to go to some depot
* @param max_penalty max distance (in pathfinder penalty) from the current ship position
* (used also as optimization - the pathfinder can stop path finding if max_penalty
* was reached and no depot was seen)
* @param may_reverse whether the ship is allowed to reverse
* @return the data about the depot
*/
FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty, bool may_reverse);
/**
* Finds the best path for given road vehicle using YAPF.
* @param v the RV that needs to find a path

View File

@ -36,6 +36,7 @@ protected:
TileIndex dest_tile;
TrackdirBits dest_trackdirs;
StationID dest_station;
bool any_ship_depot = false;
bool has_intermediate_dest = false;
TileIndex intermediate_dest_tile;
@ -55,6 +56,11 @@ public:
}
}
void SetAnyShipDepotDestination()
{
this->any_ship_depot = true;
}
void SetIntermediateDestination(const WaterRegionPatchDesc &water_region_patch)
{
this->has_intermediate_dest = true;
@ -69,10 +75,16 @@ protected:
return *static_cast<Tpf*>(this);
}
TestTileIndexCallBack detect_ship_depot = [&](const TileIndex tile)
{
return IsShipDepotTile(tile) && GetShipDepotPart(tile) == DEPOT_PART_NORTH && IsTileOwner(tile, Yapf().GetVehicle()->owner);
};
public:
/** Called by YAPF to detect if node ends in the desired destination. */
inline bool PfDetectDestination(Node &n)
{
if (this->any_ship_depot) return this->detect_ship_depot(n.key.tile);
return this->PfDetectDestinationTile(n.segment_last_tile, n.segment_last_td);
}
@ -89,6 +101,11 @@ public:
return tile == this->dest_tile && ((this->dest_trackdirs & TrackdirToTrackdirBits(trackdir)) != TRACKDIR_BIT_NONE);
}
inline TileIndex GetShipDepotDestination(const std::span<WaterRegionPatchDesc> high_level_path)
{
return FindClosestEnteringTile(high_level_path, this->detect_ship_depot);
}
/**
* Called by YAPF to calculate cost estimate. Calculates distance to the destination
* adds it to the actual cost from origin and stores the sum to the Node::estimate.
@ -99,7 +116,7 @@ public:
static const int dg_dir_to_x_offs[] = { -1, 0, 1, 0 };
static const int dg_dir_to_y_offs[] = { 0, 1, 0, -1 };
if (this->PfDetectDestination(n)) {
if (this->any_ship_depot || this->PfDetectDestination(n)) {
n.estimate = n.cost;
return true;
}
@ -158,7 +175,7 @@ public:
}
/** Restricts the search by creating corridor or water regions through which the ship is allowed to travel. */
inline void RestrictSearch(const std::vector<WaterRegionPatchDesc> &path)
inline void RestrictSearch(const std::span<WaterRegionPatchDesc> &path)
{
this->water_region_corridor.clear();
for (const WaterRegionPatchDesc &path_entry : path) this->water_region_corridor.push_back(path_entry);
@ -211,16 +228,20 @@ public:
return result;
}
static Trackdir ChooseShipTrack(const Ship *v, TileIndex tile, TrackdirBits forward_dirs, TrackdirBits reverse_dirs,
static Trackdir ChooseShipTrack(const Ship *v, TileIndex &tile, TrackdirBits forward_dirs, TrackdirBits reverse_dirs, int max_penalty,
bool &path_found, ShipPathCache &path_cache, Trackdir &best_origin_dir)
{
const std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
if (high_level_path.empty()) {
path_found = false;
/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
return CreateRandomPath(v, path_cache, SHIP_LOST_PATH_LENGTH);
}
const bool find_closest_depot = tile == INVALID_TILE;
if (find_closest_depot) tile = v->tile;
const bool automatic_servicing = find_closest_depot && max_penalty != 0;
/* Try one time without restricting the search area, which generally results in better and more natural looking paths.
* However the pathfinder can hit the node limit in certain situations such as long aqueducts or maze-like terrain.
* If that happens we run the pathfinder again, but restricted only to the regions provided by the region pathfinder. */
@ -229,13 +250,28 @@ public:
/* Set origin and destination nodes */
pf.SetOrigin(v->tile, forward_dirs | reverse_dirs);
pf.SetDestination(v);
const bool is_intermediate_destination = static_cast<int>(high_level_path.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1;
if (is_intermediate_destination) pf.SetIntermediateDestination(high_level_path.back());
if (find_closest_depot) {
pf.SetAnyShipDepotDestination();
} else {
pf.SetDestination(v);
}
pf.SetMaxCost(max_penalty);
const std::span<WaterRegionPatchDesc> high_level_path_span(high_level_path.data(), std::min<size_t>(high_level_path.size(), NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1));
const bool is_intermediate_destination = static_cast<int>(high_level_path_span.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1;
if (is_intermediate_destination) {
if (automatic_servicing) {
/* Automatic servicing requires a valid path cost from start to end.
* However, when an intermediate destination is set, the resulting cost
* cannot be used to determine if it falls within the maximum allowed penalty. */
return INVALID_TRACKDIR;
}
pf.SetIntermediateDestination(high_level_path_span.back());
}
/* Restrict the search area to prevent the low level pathfinder from expanding too many nodes. This can happen
* when the terrain is very "maze-like" or when the high level path "teleports" via a very long aqueduct. */
if (attempt > 0) pf.RestrictSearch(high_level_path);
if (attempt > 0) pf.RestrictSearch(high_level_path_span);
/* Find best path. */
path_found = pf.FindPath(v);
@ -245,6 +281,12 @@ public:
/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
if (!path_found) return CreateRandomPath(v, path_cache, SHIP_LOST_PATH_LENGTH);
/* Return early when only searching for the closest depot tile. */
if (find_closest_depot) {
tile = is_intermediate_destination ? pf.GetShipDepotDestination(high_level_path) : node->GetTile();
return INVALID_TRACKDIR;
}
/* Return only the path within the current water region if an intermediate destination was returned. If not, cache the entire path
* to the final destination tile. The low-level pathfinder might actually prefer a different docking tile in a nearby region. Without
* caching the full path the ship can get stuck in a loop. */
@ -254,7 +296,7 @@ public:
while (node->parent) {
const WaterRegionPatchDesc node_water_patch = GetWaterRegionPatchInfo(node->GetTile());
const bool node_water_patch_on_high_level_path = std::ranges::find(high_level_path, node_water_patch) != high_level_path.end();
const bool node_water_patch_on_high_level_path = std::ranges::find(high_level_path_span, node_water_patch) != high_level_path_span.end();
const bool add_full_path = !is_intermediate_destination && node_water_patch != end_water_patch;
/* The cached path must always lead to a region patch that's on the high level path.
@ -303,6 +345,7 @@ public:
{
bool path_found = false;
ShipPathCache dummy_cache;
TileIndex tile = v->tile;
Trackdir best_origin_dir = INVALID_TRACKDIR;
if (trackdir == nullptr) {
@ -310,17 +353,45 @@ public:
const Trackdir reverse_dir = ReverseTrackdir(v->GetVehicleTrackdir());
const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
const TrackdirBits reverse_dirs = TrackdirToTrackdirBits(reverse_dir);
(void)ChooseShipTrack(v, v->tile, forward_dirs, reverse_dirs, path_found, dummy_cache, best_origin_dir);
(void)ChooseShipTrack(v, tile, forward_dirs, reverse_dirs, 0, path_found, dummy_cache, best_origin_dir);
return path_found && best_origin_dir == reverse_dir;
} else {
/* This gets called when a ship suddenly can't move forward, e.g. due to terraforming. */
const DiagDirection entry = ReverseDiagDir(VehicleExitDir(v->direction, v->state));
const TrackdirBits reverse_dirs = DiagdirReachesTrackdirs(entry) & TrackStatusToTrackdirBits(GetTileTrackStatus(v->tile, TRANSPORT_WATER, 0, entry));
(void)ChooseShipTrack(v, v->tile, TRACKDIR_BIT_NONE, reverse_dirs, path_found, dummy_cache, best_origin_dir);
(void)ChooseShipTrack(v, tile, TRACKDIR_BIT_NONE, reverse_dirs, 0, path_found, dummy_cache, best_origin_dir);
*trackdir = path_found && best_origin_dir != INVALID_TRACKDIR ? best_origin_dir : GetRandomTrackdir(reverse_dirs);
return true;
}
}
/**
* Find the best depot for a ship.
* @param v Ship
* @param max_penalty maximum pathfinder cost.
* @param may_reverse whether the ship is allowed to reverse.
* @return FindDepotData with the best depot tile, cost and whether to reverse.
*/
static inline FindDepotData FindNearestDepot(const Ship *v, int max_penalty, bool may_reverse)
{
FindDepotData depot;
bool path_found = false;
ShipPathCache dummy_cache;
TileIndex tile = INVALID_TILE;
Trackdir best_origin_dir = INVALID_TRACKDIR;
const bool search_both_ways = may_reverse && max_penalty == 0;
const Trackdir forward_dir = v->GetVehicleTrackdir();
const Trackdir reverse_dir = ReverseTrackdir(forward_dir);
const TrackdirBits forward_dirs = TrackdirToTrackdirBits(forward_dir);
const TrackdirBits reverse_dirs = search_both_ways ? TrackdirToTrackdirBits(reverse_dir) : TRACKDIR_BIT_NONE;
(void)ChooseShipTrack(v, tile, forward_dirs, reverse_dirs, max_penalty, path_found, dummy_cache, best_origin_dir);
if (path_found) {
assert(tile != INVALID_TILE);
depot.tile = tile;
}
return depot;
}
};
/** Cost Provider module of YAPF for ships. */
@ -333,6 +404,11 @@ public:
typedef typename Types::NodeList::Item Node; ///< this will be our node type.
typedef typename Node::Key Key; ///< key to hash tables.
protected:
int max_cost;
CYapfCostShipT() : max_cost(0) {}
/** to access inherited path finder */
Tpf &Yapf()
{
@ -340,6 +416,11 @@ public:
}
public:
inline void SetMaxCost(int cost)
{
this->max_cost = cost;
}
inline int CurveCost(Trackdir td1, Trackdir td2)
{
assert(IsValidTrackdir(td1));
@ -384,6 +465,10 @@ public:
uint8_t speed_frac = (GetEffectiveWaterClass(n.GetTile()) == WATER_CLASS_SEA) ? svi->ocean_speed_frac : svi->canal_speed_frac;
if (speed_frac > 0) c += YAPF_TILE_LENGTH * (1 + tf->tiles_skipped) * speed_frac / (256 - speed_frac);
/* Finish if we already exceeded the maximum path cost (i.e. when
* searching for the nearest depot). */
if (this->max_cost > 0 && (n.parent->cost + c) > this->max_cost) return false;
/* Apply it. */
n.cost = n.parent->cost + c;
return true;
@ -422,7 +507,7 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP
{
Trackdir best_origin_dir = INVALID_TRACKDIR;
const TrackdirBits origin_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
const Trackdir td_ret = CYapfShip::ChooseShipTrack(v, tile, origin_dirs, TRACKDIR_BIT_NONE, path_found, path_cache, best_origin_dir);
const Trackdir td_ret = CYapfShip::ChooseShipTrack(v, tile, origin_dirs, TRACKDIR_BIT_NONE, 0, path_found, path_cache, best_origin_dir);
return (td_ret != INVALID_TRACKDIR) ? TrackdirToTrack(td_ret) : INVALID_TRACK;
}
@ -430,3 +515,8 @@ bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir)
{
return CYapfShip::CheckShipReverse(v, trackdir);
}
FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty, bool may_reverse)
{
return CYapfShip::FindNearestDepot(v, max_penalty, may_reverse);
}

View File

@ -119,6 +119,7 @@ public:
protected:
Key dest;
bool any_ship_depot = false;
public:
void SetDestination(const WaterRegionPatchDesc &water_region_patch)
@ -126,18 +127,32 @@ public:
this->dest.Set(water_region_patch);
}
void SetAnyShipDepotDestination()
{
this->any_ship_depot = true;
}
protected:
TestTileIndexCallBack detect_ship_depot = [&](const TileIndex tile)
{
return IsShipDepotTile(tile) && GetShipDepotPart(tile) == DEPOT_PART_NORTH && IsTileOwner(tile, Yapf().GetVehicle()->owner);
};
Tpf &Yapf() { return *static_cast<Tpf*>(this); }
public:
inline bool PfDetectDestination(Node &n) const
inline bool PfDetectDestination(Node &n)
{
if (this->any_ship_depot) {
return TestTileInWaterRegionPatch(n.key.water_region_patch, this->detect_ship_depot);
}
return n.key == this->dest;
}
inline bool PfCalcEstimate(Node &n)
{
if (this->PfDetectDestination(n)) {
if (this->any_ship_depot || this->PfDetectDestination(n)) {
n.estimate = n.cost;
return true;
}
@ -218,6 +233,31 @@ public:
assert(!path.empty());
return path;
}
static std::vector<WaterRegionPatchDesc> FindShipDepotRegionPath(const Ship *v)
{
const WaterRegionPatchDesc start_water_region_patch = GetWaterRegionPatchInfo(v->tile);
/* We reserve 4 nodes (patches) per water region. The vast majority of water regions have 1 or 2 regions so this should be a pretty
* safe limit. We cap the limit at 65536 which is at a region size of 16x16 is equivalent to one node per region for a 4096x4096 map. */
Tpf pf(std::min(static_cast<int>(Map::Size() * NODES_PER_REGION) / WATER_REGION_NUMBER_OF_TILES, MAX_NUMBER_OF_NODES));
pf.AddOrigin(start_water_region_patch);
pf.SetAnyShipDepotDestination();
/* Find best path. */
if (!pf.FindPath(v)) return {}; // Path not found.
std::vector<WaterRegionPatchDesc> path;
Node *node = pf.GetBestNode();
while (node != nullptr) {
path.push_back(node->key.water_region_patch);
node = node->parent;
}
assert(!path.empty());
std::ranges::reverse(path);
return path;
}
};
/** Cost Provider of YAPF for water regions. */
@ -296,5 +336,8 @@ struct CYapfRegionWater : CYapfT<CYapfRegion_TypesT<CYapfRegionWater, CRegionNod
*/
std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
{
const bool find_closest_depot = start_tile == INVALID_TILE;
if (find_closest_depot) return CYapfRegionWater::FindShipDepotRegionPath(v);
return CYapfRegionWater::FindWaterRegionPath(v, start_tile, max_returned_path_length);
}

View File

@ -16,5 +16,6 @@
struct Ship;
std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length);
std::vector<WaterRegionPatchDesc> YapfFindShipDepotRegionPath(const Ship *v);
#endif /* YAPF_SHIP_REGIONS_H */

View File

@ -1899,7 +1899,7 @@ static void DrawSingleSignal(TileIndex tile, const RailTypeInfo *rti, Track trac
sprite += type * 16 + variant * 64 + image * 2 + condition + (type > SIGTYPE_LAST_NOPBS ? 64 : 0);
}
AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, GetSaveSlopeZ(x, y, track));
AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, GetSaveSlopeZ(x, y, track), {{}, {1, 1, BB_HEIGHT_UNDER_BRIDGE}, {}});
}
static uint32_t _drawtile_track_palette;
@ -1907,12 +1907,11 @@ static uint32_t _drawtile_track_palette;
/** Offsets for drawing fences */
struct FenceOffset {
Corner height_ref; //!< Corner to use height offset from.
int x_offs; //!< Bounding box X offset.
int y_offs; //!< Bounding box Y offset.
int x_size; //!< Bounding box X size.
int y_size; //!< Bounding box Y size.
struct FenceOffset : SpriteBounds {
Corner height_ref; ///< Corner to use height offset from.
constexpr FenceOffset(Corner height_ref, int8_t origin_x, int8_t origin_y, uint8_t extent_x, uint8_t extent_y) :
SpriteBounds({origin_x, origin_y, 0}, {extent_x, extent_y, 4}, {}), height_ref(height_ref) {}
};
/** Offsets for drawing fences */
@ -1948,12 +1947,7 @@ static void DrawTrackFence(const TileInfo *ti, SpriteID base_image, uint num_spr
if (_fence_offsets[rfo].height_ref != CORNER_INVALID) {
z += GetSlopePixelZInCorner(RemoveHalftileSlope(ti->tileh), _fence_offsets[rfo].height_ref);
}
AddSortableSpriteToDraw(base_image + (rfo % num_sprites), _drawtile_track_palette,
ti->x + _fence_offsets[rfo].x_offs,
ti->y + _fence_offsets[rfo].y_offs,
_fence_offsets[rfo].x_size,
_fence_offsets[rfo].y_size,
4, z);
AddSortableSpriteToDraw(base_image + (rfo % num_sprites), _drawtile_track_palette, ti->x, ti->y, z, _fence_offsets[rfo]);
}
/**

View File

@ -1418,7 +1418,7 @@ void DrawRoadTypeCatenary(const TileInfo *ti, RoadType rt, RoadBits rb)
* For tiles with OWNER_TOWN or OWNER_NONE, recolour CC to grey as a neutral colour. */
Owner owner = GetRoadOwner(ti->tile, GetRoadTramType(rt));
PaletteID pal = (owner == OWNER_NONE || owner == OWNER_TOWN ? GetColourPalette(COLOUR_GREY) : GetCompanyPalette(owner));
int z_wires = (ti->tileh == SLOPE_FLAT ? 0 : TILE_HEIGHT) + BB_HEIGHT_UNDER_BRIDGE;
uint8_t z_wires = (ti->tileh == SLOPE_FLAT ? 0 : TILE_HEIGHT) + BB_HEIGHT_UNDER_BRIDGE;
if (back != 0) {
/* The "back" sprite contains the west, north and east pillars.
* We cut the sprite at 3/8 of the west/east edges to create 3 sprites.
@ -1427,13 +1427,16 @@ void DrawRoadTypeCatenary(const TileInfo *ti, RoadType rt, RoadBits rb)
static const SubSprite west = { -INF, -INF, -12, INF };
static const SubSprite north = { -12, -INF, 12, INF };
static const SubSprite east = { 12, -INF, INF, INF };
AddSortableSpriteToDraw(back, pal, ti->x, ti->y, 16, 1, z_wires, ti->z, IsTransparencySet(TO_CATENARY), 15, 0, GetSlopePixelZInCorner(ti->tileh, CORNER_W), &west);
AddSortableSpriteToDraw(back, pal, ti->x, ti->y, 1, 1, z_wires, ti->z, IsTransparencySet(TO_CATENARY), 0, 0, GetSlopePixelZInCorner(ti->tileh, CORNER_N), &north);
AddSortableSpriteToDraw(back, pal, ti->x, ti->y, 1, 16, z_wires, ti->z, IsTransparencySet(TO_CATENARY), 0, 15, GetSlopePixelZInCorner(ti->tileh, CORNER_E), &east);
int8_t west_z = GetSlopePixelZInCorner(ti->tileh, CORNER_W);
int8_t north_z = GetSlopePixelZInCorner(ti->tileh, CORNER_N);
int8_t east_z = GetSlopePixelZInCorner(ti->tileh, CORNER_E);
AddSortableSpriteToDraw(back, pal, *ti, {{15, 0, west_z}, {1, 1, z_wires}, {-15, 0, static_cast<int8_t>(-west_z)}}, IsTransparencySet(TO_CATENARY), &west);
AddSortableSpriteToDraw(back, pal, *ti, {{0, 0, north_z}, {1, 1, z_wires}, {0, 0, static_cast<int8_t>(-north_z)}}, IsTransparencySet(TO_CATENARY), &north);
AddSortableSpriteToDraw(back, pal, *ti, {{0, 15, east_z}, {1, 1, z_wires}, {0, -15, static_cast<int8_t>(-east_z)}}, IsTransparencySet(TO_CATENARY), &east);
}
if (front != 0) {
/* Draw the "front" sprite (containing south pillar and wires) at a Z height that is both above the vehicles and above the "back" pillars. */
AddSortableSpriteToDraw(front, pal, ti->x, ti->y, 16, 16, z_wires + 1, ti->z, IsTransparencySet(TO_CATENARY), 0, 0, z_wires);
AddSortableSpriteToDraw(front, pal, *ti, {{0, 0, static_cast<int8_t>(z_wires)}, {TILE_SIZE, TILE_SIZE, 1}, {0, 0, static_cast<int8_t>(-z_wires)}}, IsTransparencySet(TO_CATENARY));
}
}
@ -1487,13 +1490,13 @@ void DrawRoadCatenary(const TileInfo *ti)
* @param h the height of the sprite to draw
* @param transparent whether the sprite should be transparent (used for roadside trees)
*/
static void DrawRoadDetail(SpriteID img, const TileInfo *ti, int dx, int dy, int h, bool transparent)
static void DrawRoadDetail(SpriteID img, const TileInfo *ti, int8_t dx, int8_t dy, uint8_t h, bool transparent)
{
int x = ti->x | dx;
int y = ti->y | dy;
int z = ti->z;
if (ti->tileh != SLOPE_FLAT) z = GetSlopePixelZ(x, y);
AddSortableSpriteToDraw(img, PAL_NONE, x, y, 2, 2, h, z, transparent);
AddSortableSpriteToDraw(img, PAL_NONE, ti->x, ti->y, z, {{dx, dy, 0}, {2, 2, h}, {}}, transparent);
}
/**

View File

@ -132,7 +132,7 @@ struct RoadVehicle final : public GroundVehicle<RoadVehicle, VEH_ROAD> {
uint Crash(bool flooded = false) override;
Trackdir GetVehicleTrackdir() const override;
TileIndex GetOrderStationLocation(StationID station) override;
ClosestDepot FindClosestDepot() override;
ClosestDepot FindClosestDepot(bool may_reverse = false) override;
bool IsBus() const;

View File

@ -346,7 +346,7 @@ static FindDepotData FindClosestRoadDepot(const RoadVehicle *v, int max_distance
return YapfRoadVehicleFindNearestDepot(v, max_distance);
}
ClosestDepot RoadVehicle::FindClosestDepot()
ClosestDepot RoadVehicle::FindClosestDepot([[maybe_unused]] bool may_reverse)
{
FindDepotData rfdd = FindClosestRoadDepot(this, 0);
if (rfdd.best_length == UINT_MAX) return ClosestDepot();
@ -405,29 +405,56 @@ void RoadVehicle::MarkDirty()
void RoadVehicle::UpdateDeltaXY()
{
static const int8_t _delta_xy_table[8][10] = {
/* y_extent, x_extent, y_offs, x_offs, y_bb_offs, x_bb_offs, y_extent_shorten, x_extent_shorten, y_bb_offs_shorten, x_bb_offs_shorten */
{3, 3, -1, -1, 0, 0, -1, -1, -1, -1}, // N
{3, 7, -1, -3, 0, -1, 0, -1, 0, 0}, // NE
{3, 3, -1, -1, 0, 0, 1, -1, 1, -1}, // E
{7, 3, -3, -1, -1, 0, 0, 0, 1, 0}, // SE
{3, 3, -1, -1, 0, 0, 1, 1, 1, 1}, // S
{3, 7, -1, -3, 0, -1, 0, 0, 0, 1}, // SW
{3, 3, -1, -1, 0, 0, -1, 1, -1, 1}, // W
{7, 3, -3, -1, -1, 0, -1, 0, 0, 0}, // NW
};
/* Set common defaults. */
this->bounds = {{-1, -1, 0}, {3, 3, 6}, {}};
int shorten = VEHICLE_LENGTH - this->gcache.cached_veh_length;
if (!IsDiagonalDirection(this->direction)) shorten >>= 1;
if (!IsDiagonalDirection(this->direction)) {
static const Point _sign_table[] = {
/* x, y */
{-1, -1}, // DIR_N
{-1, 1}, // DIR_E
{ 1, 1}, // DIR_S
{ 1, -1}, // DIR_W
};
const int8_t *bb = _delta_xy_table[this->direction];
this->x_bb_offs = bb[5] + bb[9] * shorten;
this->y_bb_offs = bb[4] + bb[8] * shorten;;
this->x_offs = bb[3];
this->y_offs = bb[2];
this->x_extent = bb[1] + bb[7] * shorten;
this->y_extent = bb[0] + bb[6] * shorten;
this->z_extent = 6;
int half_shorten = (VEHICLE_LENGTH - this->gcache.cached_veh_length) / 2;
/* For all straight directions, move the bound box to the centre of the vehicle, but keep the size. */
this->bounds.offset.x -= half_shorten * _sign_table[DirToDiagDir(this->direction)].x;
this->bounds.offset.y -= half_shorten * _sign_table[DirToDiagDir(this->direction)].y;
} else {
/* Unlike trains, road vehicles do not have their offsets moved to the centre. */
switch (this->direction) {
/* Shorten southern corner of the bounding box according the vehicle length. */
case DIR_NE:
this->bounds.origin.x = -3;
this->bounds.extent.x = this->gcache.cached_veh_length;
this->bounds.offset.x = 1;
break;
case DIR_NW:
this->bounds.origin.y = -3;
this->bounds.extent.y = this->gcache.cached_veh_length;
this->bounds.offset.y = 1;
break;
/* Move northern corner of the bounding box down according to vehicle length. */
case DIR_SW:
this->bounds.origin.x = -3 + (VEHICLE_LENGTH - this->gcache.cached_veh_length);
this->bounds.extent.x = this->gcache.cached_veh_length;
this->bounds.offset.x = 1 - (VEHICLE_LENGTH - this->gcache.cached_veh_length);
break;
case DIR_SE:
this->bounds.origin.y = -3 + (VEHICLE_LENGTH - this->gcache.cached_veh_length);
this->bounds.extent.y = this->gcache.cached_veh_length;
this->bounds.offset.y = 1 - (VEHICLE_LENGTH - this->gcache.cached_veh_length);
break;
default:
NOT_REACHED();
}
}
}
/**

View File

@ -57,7 +57,7 @@ struct Ship final : public SpecializedVehicle<Ship, VEH_SHIP> {
void OnNewEconomyDay() override;
Trackdir GetVehicleTrackdir() const override;
TileIndex GetOrderStationLocation(StationID station) override;
ClosestDepot FindClosestDepot() override;
ClosestDepot FindClosestDepot(bool may_reverse = false) override;
void UpdateCache();
void SetDestTile(TileIndex tile) override;
};

View File

@ -17,7 +17,6 @@
#include "station_base.h"
#include "newgrf_engine.h"
#include "pathfinder/yapf/yapf.h"
#include "pathfinder/yapf/yapf_ship_regions.h"
#include "newgrf_sound.h"
#include "spritecache.h"
#include "strings_func.h"
@ -39,13 +38,8 @@
#include "table/strings.h"
#include <unordered_set>
#include "safeguards.h"
/** Max distance in tiles (as the crow flies) to search for depots when user clicks "go to depot". */
constexpr int MAX_SHIP_DEPOT_SEARCH_DISTANCE = 80;
/**
* Determine the effective #WaterClass for a ship travelling on a tile.
* @param tile Tile of interest
@ -148,57 +142,15 @@ void Ship::GetImage(Direction direction, EngineImageType image_type, VehicleSpri
result->Set(_ship_sprites[spritenum] + direction);
}
static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance)
static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance, bool may_reverse = false)
{
const int max_region_distance = (max_distance / WATER_REGION_EDGE_LENGTH) + 1;
const TileIndex tile = v->tile;
if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) return Depot::GetByTile(tile);
static std::unordered_set<int> visited_patch_hashes;
static std::deque<WaterRegionPatchDesc> patches_to_search;
visited_patch_hashes.clear();
patches_to_search.clear();
FindDepotData sfdd = YapfShipFindNearestDepot(Ship::From(v), max_distance, may_reverse);
/* Step 1: find a set of reachable Water Region Patches using BFS. */
const WaterRegionPatchDesc start_patch = GetWaterRegionPatchInfo(v->tile);
patches_to_search.push_back(start_patch);
visited_patch_hashes.insert(CalculateWaterRegionPatchHash(start_patch));
while (!patches_to_search.empty()) {
/* Remove first patch from the queue and make it the current patch. */
const WaterRegionPatchDesc current_node = patches_to_search.front();
patches_to_search.pop_front();
/* Add neighbours of the current patch to the search queue. */
VisitWaterRegionPatchCallback visit_func = [&](const WaterRegionPatchDesc &water_region_patch) {
/* Note that we check the max distance per axis, not the total distance. */
if (std::abs(water_region_patch.x - start_patch.x) > max_region_distance ||
std::abs(water_region_patch.y - start_patch.y) > max_region_distance) return;
const int hash = CalculateWaterRegionPatchHash(water_region_patch);
if (visited_patch_hashes.count(hash) == 0) {
visited_patch_hashes.insert(hash);
patches_to_search.push_back(water_region_patch);
}
};
VisitWaterRegionPatchNeighbours(current_node, visit_func);
}
/* Step 2: Find the closest depot within the reachable Water Region Patches. */
const Depot *best_depot = nullptr;
uint best_dist_sq = std::numeric_limits<uint>::max();
for (const Depot *depot : Depot::Iterate()) {
const TileIndex tile = depot->xy;
if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) {
const uint dist_sq = DistanceSquare(tile, v->tile);
if (dist_sq < best_dist_sq && dist_sq <= max_distance * max_distance &&
visited_patch_hashes.count(CalculateWaterRegionPatchHash(GetWaterRegionPatchInfo(tile))) > 0) {
best_dist_sq = dist_sq;
best_depot = depot;
}
}
}
return best_depot;
if (sfdd.tile == INVALID_TILE) return nullptr;
return Depot::GetByTile(sfdd.tile);
}
static void CheckIfShipNeedsService(Vehicle *v)
@ -209,7 +161,7 @@ static void CheckIfShipNeedsService(Vehicle *v)
return;
}
uint max_distance = _settings_game.pf.yapf.maximum_go_to_depot_penalty / YAPF_TILE_LENGTH;
uint max_distance = _settings_game.pf.yapf.maximum_go_to_depot_penalty;
const Depot *depot = FindClosestShipDepot(v, max_distance);
@ -330,32 +282,26 @@ TileIndex Ship::GetOrderStationLocation(StationID station)
void Ship::UpdateDeltaXY()
{
static const int8_t _delta_xy_table[8][4] = {
/* y_extent, x_extent, y_offs, x_offs */
{ 6, 6, -3, -3}, // N
{ 6, 32, -3, -16}, // NE
{ 6, 6, -3, -3}, // E
{32, 6, -16, -3}, // SE
{ 6, 6, -3, -3}, // S
{ 6, 32, -3, -16}, // SW
{ 6, 6, -3, -3}, // W
{32, 6, -16, -3}, // NW
static constexpr SpriteBounds ship_bounds[DIR_END] = {
{{ -3, -3, 0}, { 6, 6, 6}, {}}, // N
{{-16, -3, 0}, {32, 6, 6}, {}}, // NE
{{ -3, -3, 0}, { 6, 6, 6}, {}}, // E
{{ -3, -16, 0}, { 6, 32, 6}, {}}, // SE
{{ -3, -3, 0}, { 6, 6, 6}, {}}, // S
{{-16, -3, 0}, {32, 6, 6}, {}}, // SW
{{ -3, -3, 0}, { 6, 6, 6}, {}}, // W
{{ -3, -16, 0}, { 6, 32, 6}, {}}, // NW
};
const int8_t *bb = _delta_xy_table[this->rotation];
this->x_offs = bb[3];
this->y_offs = bb[2];
this->x_extent = bb[1];
this->y_extent = bb[0];
this->z_extent = 6;
this->bounds = ship_bounds[this->rotation];
if (this->direction != this->rotation) {
/* If we are rotating, then it is possible the ship was moved to its next position. In that
* case, because we are still showing the old direction, the ship will appear to glitch sideways
* slightly. We can work around this by applying an additional offset to make the ship appear
* where it was before it moved. */
this->x_offs -= this->x_pos - this->rotation_x_pos;
this->y_offs -= this->y_pos - this->rotation_y_pos;
this->bounds.origin.x -= this->x_pos - this->rotation_x_pos;
this->bounds.origin.y -= this->y_pos - this->rotation_y_pos;
}
}
@ -954,9 +900,9 @@ CommandCost CmdBuildShip(DoCommandFlags flags, TileIndex tile, const Engine *e,
return CommandCost();
}
ClosestDepot Ship::FindClosestDepot()
ClosestDepot Ship::FindClosestDepot(bool may_reverse)
{
const Depot *depot = FindClosestShipDepot(this, MAX_SHIP_DEPOT_SEARCH_DISTANCE);
const Depot *depot = FindClosestShipDepot(this, 0, may_reverse);
if (depot == nullptr) return ClosestDepot();
return ClosestDepot(depot->xy, depot->index);

View File

@ -54,16 +54,11 @@ void DrawCommonTileSeq(const TileInfo *ti, const DrawTileSprites *dts, Transpare
if (dtss.IsParentSprite()) {
parent_sprite_encountered = true;
AddSortableSpriteToDraw(
image, pal,
ti->x + dtss.delta_x, ti->y + dtss.delta_y,
dtss.size_x, dtss.size_y,
dtss.size_z, ti->z + dtss.delta_z,
!HasBit(image, SPRITE_MODIFIER_OPAQUE) && IsTransparencySet(to)
AddSortableSpriteToDraw(image, pal, *ti, dtss, !HasBit(image, SPRITE_MODIFIER_OPAQUE) && IsTransparencySet(to)
);
} else {
int offs_x = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.delta_x) : dtss.delta_x;
int offs_y = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.delta_y) : dtss.delta_y;
int offs_x = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.origin.x) : dtss.origin.x;
int offs_y = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.origin.y) : dtss.origin.y;
bool transparent = !HasBit(image, SPRITE_MODIFIER_OPAQUE) && IsTransparencySet(to);
if (parent_sprite_encountered) {
AddChildSpriteScreen(image, pal, offs_x, offs_y, transparent);
@ -114,15 +109,15 @@ void DrawCommonTileSeqInGUI(int x, int y, const DrawTileSprites *dts, int32_t or
pal = SpriteLayoutPaletteTransform(image, pal, default_palette);
if (dtss.IsParentSprite()) {
Point pt = RemapCoords(dtss.delta_x, dtss.delta_y, dtss.delta_z);
Point pt = RemapCoords(dtss.origin.x, dtss.origin.y, dtss.origin.z);
DrawSprite(image, pal, x + UnScaleGUI(pt.x), y + UnScaleGUI(pt.y));
const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
child_offset.x = UnScaleGUI(pt.x + spr->x_offs);
child_offset.y = UnScaleGUI(pt.y + spr->y_offs);
} else {
int offs_x = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.delta_x) : dtss.delta_x;
int offs_y = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.delta_y) : dtss.delta_y;
int offs_x = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.origin.x) : dtss.origin.x;
int offs_y = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.origin.y) : dtss.origin.y;
DrawSprite(image, pal, x + child_offset.x + ScaleSpriteTrad(offs_x), y + child_offset.y + ScaleSpriteTrad(offs_y));
}
}

View File

@ -10,28 +10,37 @@
#ifndef SPRITE_H
#define SPRITE_H
#include "core/geometry_type.hpp"
#include "transparency.h"
#include "table/sprites.h"
struct SpriteBounds {
Coord3D<int8_t> origin; ///< Position of northern corner within tile.
Coord3D<uint8_t> extent; ///< Size of bounding box.
Coord3D<int8_t> offset; ///< Relative position of sprite from bounding box.
constexpr SpriteBounds() = default;
constexpr SpriteBounds(const Coord3D<int8_t> &origin, const Coord3D<uint8_t> &extent, const Coord3D<int8_t> &offset) :
origin(origin), extent(extent), offset(offset) {}
};
/* The following describes bunch of sprites to be drawn together in a single 3D
* bounding box. Used especially for various multi-sprite buildings (like
* depots or stations): */
/** A tile child sprite and palette to draw for stations etc, with 3D bounding box */
struct DrawTileSeqStruct {
int8_t delta_x = 0;
int8_t delta_y = 0;
int8_t delta_z = 0; ///< \c 0x80 identifies child sprites
uint8_t size_x = 0;
uint8_t size_y = 0;
uint8_t size_z = 0;
PalSpriteID image{};
struct DrawTileSeqStruct : SpriteBounds {
PalSpriteID image;
constexpr DrawTileSeqStruct() = default;
constexpr DrawTileSeqStruct(int8_t origin_x, int8_t origin_y, int8_t origin_z, uint8_t extent_x, uint8_t extent_y, uint8_t extent_z, PalSpriteID image) :
SpriteBounds({origin_x, origin_y, origin_z}, {extent_x, extent_y, extent_z}, {}), image(image) {}
/** Check whether this is a parent sprite with a boundingbox. */
bool IsParentSprite() const
inline bool IsParentSprite() const
{
return (uint8_t)this->delta_z != 0x80;
return static_cast<uint8_t>(this->origin.z) != 0x80;
}
};
@ -69,14 +78,9 @@ struct DrawTileSpriteSpan : DrawTileSprites {
* This structure is the same for both Industries and Houses.
* Buildings here reference a general type of construction
*/
struct DrawBuildingsTileStruct {
struct DrawBuildingsTileStruct : SpriteBounds {
PalSpriteID ground;
PalSpriteID building;
uint8_t subtile_x;
uint8_t subtile_y;
uint8_t width;
uint8_t height;
uint8_t dz;
uint8_t draw_proc; // this allows to specify a special drawing procedure.
};

View File

@ -3184,7 +3184,7 @@ static void DrawTile_Station(TileInfo *ti)
7, 8, 9 // SLOPE_NE, SLOPE_ENW, SLOPE_SEN
};
AddSortableSpriteToDraw(image + foundation_parts[ti->tileh], PAL_NONE, ti->x, ti->y, 16, 16, 7, ti->z);
AddSortableSpriteToDraw(image + foundation_parts[ti->tileh], PAL_NONE, *ti, {{}, {TILE_SIZE, TILE_SIZE, 7}, {}});
} else {
/* Draw simple foundations, built up from 8 possible foundation sprites. */
@ -3218,7 +3218,7 @@ static void DrawTile_Station(TileInfo *ti)
StartSpriteCombine();
for (int i = 0; i < 8; i++) {
if (HasBit(parts, i)) {
AddSortableSpriteToDraw(image + i, PAL_NONE, ti->x, ti->y, 16, 16, 7, ti->z);
AddSortableSpriteToDraw(image + i, PAL_NONE, *ti, {{}, {TILE_SIZE, TILE_SIZE, 7}, {}});
}
}
EndSpriteCombine();

View File

@ -313,14 +313,12 @@ enum WireSpriteOffset : uint8_t {
WSO_ENTRANCE_SE,
};
struct SortableSpriteStruct {
struct SortableSpriteStruct : SpriteBounds {
uint8_t image_offset;
int8_t x_offset;
int8_t y_offset;
int8_t x_size;
int8_t y_size;
int8_t z_size;
int8_t z_offset;
constexpr SortableSpriteStruct(uint8_t image_offset, const SpriteBounds &bounds) : SpriteBounds(bounds), image_offset(image_offset) {}
constexpr SortableSpriteStruct(uint8_t image_offset, int8_t x_offset, int8_t y_offset, uint8_t x_size, uint8_t y_size, uint8_t z_size, int8_t z_offset) :
SpriteBounds({x_offset, y_offset, z_offset}, {x_size, y_size, z_size}, {}), image_offset(image_offset) {}
};
/** Distance between wire and rail */
@ -398,11 +396,17 @@ static const SortableSpriteStruct _rail_catenary_sprite_data_depot[] = {
{ WSO_ENTRANCE_NW, 7, 0, 1, 15, 1, ELRAIL_ELEVATION } //! Wire for NW depot exit
};
/**
* In tunnelheads, the bounding box for wires covers nearly the full tile, and is lowered a bit.
* ELRAIL_TUNNEL_OFFSET is the difference between visual position and bounding box.
*/
static const int8_t ELRAIL_TUNNEL_OFFSET = ELRAIL_ELEVATION - BB_Z_SEPARATOR;
static const SortableSpriteStruct _rail_catenary_sprite_data_tunnel[] = {
{ WSO_ENTRANCE_SW, 0, 7, 15, 1, 1, ELRAIL_ELEVATION }, //! Wire for NE tunnel (SW facing exit)
{ WSO_ENTRANCE_NW, 7, 0, 1, 15, 1, ELRAIL_ELEVATION }, //! Wire for SE tunnel (NW facing exit)
{ WSO_ENTRANCE_NE, 0, 7, 15, 1, 1, ELRAIL_ELEVATION }, //! Wire for SW tunnel (NE facing exit)
{ WSO_ENTRANCE_SE, 7, 0, 1, 15, 1, ELRAIL_ELEVATION } //! Wire for NW tunnel (SE facing exit)
{ WSO_ENTRANCE_SW, {{0, 0, BB_Z_SEPARATOR}, {16, 15, 1}, {0, 7, ELRAIL_TUNNEL_OFFSET}} }, //! Wire for NE tunnel (SW facing exit)
{ WSO_ENTRANCE_NW, {{0, 0, BB_Z_SEPARATOR}, {15, 16, 1}, {7, 0, ELRAIL_TUNNEL_OFFSET}} }, //! Wire for SE tunnel (NW facing exit)
{ WSO_ENTRANCE_NE, {{0, 0, BB_Z_SEPARATOR}, {16, 15, 1}, {0, 7, ELRAIL_TUNNEL_OFFSET}} }, //! Wire for SW tunnel (NE facing exit)
{ WSO_ENTRANCE_SE, {{0, 0, BB_Z_SEPARATOR}, {15, 16, 1}, {7, 0, ELRAIL_TUNNEL_OFFSET}} } //! Wire for NW tunnel (SE facing exit)
};

View File

@ -37,15 +37,15 @@ struct DrawIndustryCoordinates {
* @param p1 palette ID of ground sprite
* @param s2 sprite ID of building sprite
* @param p2 palette ID of building sprite
* @param sx coordinate x of the sprite
* @param sy coordinate y of the sprite
* @param w width of the sprite
* @param h height of the sprite
* @param dz virtual height of the sprite
* @param dx The x-position of the sprite within the tile.
* @param dy the y-position of the sprite within the tile.
* @param sx the x-extent of the sprite.
* @param sy the y-extent of the sprite.
* @param sz the z-extent of the sprite.
* @param p this allows to specify a special drawing procedure.
* @see DrawBuildingsTileStruct
*/
#define M(s1, p1, s2, p2, sx, sy, w, h, dz, p) { { s1, p1 }, { s2, p2 }, sx, sy, w, h, dz, p }
#define M(s1, p1, s2, p2, dx, dy, sx, sy, sz, p) { {{dx, dy, 0}, {sx, sy, sz}, {}}, { s1, p1 }, { s2, p2 }, p}
/** Structure for industry tiles drawing */
static const DrawBuildingsTileStruct _industry_draw_tile_data[NEW_INDUSTRYTILEOFFSET * 4] = {

View File

@ -13,15 +13,15 @@
* @param p1 The first sprite's palette of the building, mostly the ground sprite
* @param s2 The second sprite of the building.
* @param p2 The second sprite's palette of the building.
* @param sx The x-position of the sprite within the tile
* @param sy the y-position of the sprite within the tile
* @param w the width of the sprite
* @param h the height of the sprite
* @param dz the virtual height of the sprite
* @param dx The x-position of the sprite within the tile.
* @param dy the y-position of the sprite within the tile.
* @param sx the x-extent of the sprite.
* @param sy the y-extent of the sprite.
* @param sz the z-extent of the sprite.
* @param p set to 1 if a lift is present ()
* @see DrawBuildingsTileStruct
*/
#define M(s1, p1, s2, p2, sx, sy, w, h, dz, p) { { s1, p1 }, { s2, p2 }, sx, sy, w, h, dz, p}
#define M(s1, p1, s2, p2, dx, dy, sx, sy, sz, p) { {{dx, dy, 0}, {sx, sy, sz}, {}}, { s1, p1 }, { s2, p2 }, p}
/** structure of houses graphics*/
static const DrawBuildingsTileStruct _town_draw_tile_data[] = {

View File

@ -10,6 +10,7 @@
#ifndef TILE_CMD_H
#define TILE_CMD_H
#include "core/geometry_type.hpp"
#include "command_type.h"
#include "vehicle_type.h"
#include "cargo_type.h"
@ -26,12 +27,9 @@ enum class VehicleEnterTileState : uint8_t {
using VehicleEnterTileStates = EnumBitSet<VehicleEnterTileState, uint8_t>;
/** Tile information, used while rendering the tile */
struct TileInfo {
int x; ///< X position of the tile in unit coordinates
int y; ///< Y position of the tile in unit coordinates
struct TileInfo : Coord3D<int> {
Slope tileh; ///< Slope of the tile
TileIndex tile; ///< Tile index
int z; ///< Height
};
/** Tile description for the 'land area information' tool */

View File

@ -12,29 +12,29 @@
#include "core/strong_typedef_type.hpp"
static const uint TILE_SIZE = 16; ///< Tile size in world coordinates.
static const uint TILE_UNIT_MASK = TILE_SIZE - 1; ///< For masking in/out the inner-tile world coordinate units.
static const uint TILE_PIXELS = 32; ///< Pixel distance between tile columns/rows in #ZOOM_BASE.
static const uint TILE_HEIGHT = 8; ///< Height of a height level in world coordinate AND in pixels in #ZOOM_BASE.
static constexpr uint TILE_SIZE = 16; ///< Tile size in world coordinates.
static constexpr uint TILE_UNIT_MASK = TILE_SIZE - 1; ///< For masking in/out the inner-tile world coordinate units.
static constexpr uint TILE_PIXELS = 32; ///< Pixel distance between tile columns/rows in #ZOOM_BASE.
static constexpr uint TILE_HEIGHT = 8; ///< Height of a height level in world coordinate AND in pixels in #ZOOM_BASE.
static const uint MAX_BUILDING_PIXELS = 200; ///< Maximum height of a building in pixels in #ZOOM_BASE. (Also applies to "bridge buildings" on the bridge floor.)
static const int MAX_VEHICLE_PIXEL_X = 192; ///< Maximum width of a vehicle in pixels in #ZOOM_BASE.
static const int MAX_VEHICLE_PIXEL_Y = 96; ///< Maximum height of a vehicle in pixels in #ZOOM_BASE.
static constexpr uint MAX_BUILDING_PIXELS = 200; ///< Maximum height of a building in pixels in #ZOOM_BASE. (Also applies to "bridge buildings" on the bridge floor.)
static constexpr int MAX_VEHICLE_PIXEL_X = 192; ///< Maximum width of a vehicle in pixels in #ZOOM_BASE.
static constexpr int MAX_VEHICLE_PIXEL_Y = 96; ///< Maximum height of a vehicle in pixels in #ZOOM_BASE.
static const uint MAX_TILE_HEIGHT = 255; ///< Maximum allowed tile height
static constexpr uint MAX_TILE_HEIGHT = 255; ///< Maximum allowed tile height
static const uint MIN_HEIGHTMAP_HEIGHT = 1; ///< Lowest possible peak value for heightmap creation
static const uint MIN_CUSTOM_TERRAIN_TYPE = 1; ///< Lowest possible peak value for world generation
static constexpr uint MIN_HEIGHTMAP_HEIGHT = 1; ///< Lowest possible peak value for heightmap creation
static constexpr uint MIN_CUSTOM_TERRAIN_TYPE = 1; ///< Lowest possible peak value for world generation
static const uint MIN_MAP_HEIGHT_LIMIT = 15; ///< Lower bound of maximum allowed heightlevel (in the construction settings)
static const uint MAX_MAP_HEIGHT_LIMIT = MAX_TILE_HEIGHT; ///< Upper bound of maximum allowed heightlevel (in the construction settings)
static constexpr uint MIN_MAP_HEIGHT_LIMIT = 15; ///< Lower bound of maximum allowed heightlevel (in the construction settings)
static constexpr uint MAX_MAP_HEIGHT_LIMIT = MAX_TILE_HEIGHT; ///< Upper bound of maximum allowed heightlevel (in the construction settings)
static const uint MIN_SNOWLINE_HEIGHT = 2; ///< Minimum snowline height
static const uint DEF_SNOWLINE_HEIGHT = 10; ///< Default snowline height
static const uint MAX_SNOWLINE_HEIGHT = (MAX_TILE_HEIGHT - 2); ///< Maximum allowed snowline height
static constexpr uint MIN_SNOWLINE_HEIGHT = 2; ///< Minimum snowline height
static constexpr uint DEF_SNOWLINE_HEIGHT = 10; ///< Default snowline height
static constexpr uint MAX_SNOWLINE_HEIGHT = (MAX_TILE_HEIGHT - 2); ///< Maximum allowed snowline height
static const uint DEF_SNOW_COVERAGE = 40; ///< Default snow coverage.
static const uint DEF_DESERT_COVERAGE = 50; ///< Default desert coverage.
static constexpr uint DEF_SNOW_COVERAGE = 40; ///< Default snow coverage.
static constexpr uint DEF_DESERT_COVERAGE = 50; ///< Default desert coverage.
/**

View File

@ -284,15 +284,7 @@ static void DrawTile_Town(TileInfo *ti)
/* Add a house on top of the ground? */
SpriteID image = dcts->building.sprite;
if (image != 0) {
AddSortableSpriteToDraw(image, dcts->building.pal,
ti->x + dcts->subtile_x,
ti->y + dcts->subtile_y,
dcts->width,
dcts->height,
dcts->dz,
ti->z,
IsTransparencySet(TO_HOUSES)
);
AddSortableSpriteToDraw(image, dcts->building.pal, *ti, *dcts, IsTransparencySet(TO_HOUSES));
if (IsTransparencySet(TO_HOUSES)) return;
}

View File

@ -1376,7 +1376,7 @@ void DrawHouseInGUI(int x, int y, HouseID house_id, int view)
/* Add a house on top of the ground? */
if (dcts.building.sprite != 0) {
Point pt = RemapCoords(dcts.subtile_x, dcts.subtile_y, 0);
Point pt = RemapCoords(dcts.origin.x, dcts.origin.y, dcts.origin.z);
DrawSprite(dcts.building.sprite, dcts.building.pal, x + UnScaleGUI(pt.x), y + UnScaleGUI(pt.y));
}
};

View File

@ -129,7 +129,7 @@ struct Train final : public GroundVehicle<Train, VEH_TRAIN> {
uint Crash(bool flooded = false) override;
Trackdir GetVehicleTrackdir() const override;
TileIndex GetOrderStationLocation(StationID station) override;
ClosestDepot FindClosestDepot() override;
ClosestDepot FindClosestDepot(bool may_reverse = false) override;
void ReserveTrackUnderConsist() const;

View File

@ -1491,13 +1491,7 @@ CommandCost CmdSellRailWagon(DoCommandFlags flags, Vehicle *t, bool sell_chain,
void Train::UpdateDeltaXY()
{
/* Set common defaults. */
this->x_offs = -1;
this->y_offs = -1;
this->x_extent = 3;
this->y_extent = 3;
this->z_extent = 6;
this->x_bb_offs = 0;
this->y_bb_offs = 0;
this->bounds = {{-1, -1, 0}, {3, 3, 6}, {}};
/* Set if flipped and engine is NOT flagged with custom flip handling. */
int flipped = this->flags.Test(VehicleRailFlag::Flipped) && !EngInfo(this->engine_type)->misc_flags.Test(EngineMiscFlag::RailFlips);
@ -1508,50 +1502,47 @@ void Train::UpdateDeltaXY()
if (flipped) dir = ReverseDir(dir);
if (!IsDiagonalDirection(dir)) {
static const int _sign_table[] =
{
static const Point _sign_table[] = {
/* x, y */
-1, -1, // DIR_N
-1, 1, // DIR_E
1, 1, // DIR_S
1, -1, // DIR_W
{-1, -1}, // DIR_N
{-1, 1}, // DIR_E
{ 1, 1}, // DIR_S
{ 1, -1}, // DIR_W
};
int half_shorten = (VEHICLE_LENGTH - this->gcache.cached_veh_length + flipped) / 2;
/* For all straight directions, move the bound box to the centre of the vehicle, but keep the size. */
this->x_offs -= half_shorten * _sign_table[dir];
this->y_offs -= half_shorten * _sign_table[dir + 1];
this->x_extent += this->x_bb_offs = half_shorten * _sign_table[dir];
this->y_extent += this->y_bb_offs = half_shorten * _sign_table[dir + 1];
this->bounds.offset.x -= half_shorten * _sign_table[DirToDiagDir(dir)].x;
this->bounds.offset.y -= half_shorten * _sign_table[DirToDiagDir(dir)].y;
} else {
switch (dir) {
/* Shorten southern corner of the bounding box according the vehicle length
* and center the bounding box on the vehicle. */
case DIR_NE:
this->x_offs = 1 - (this->gcache.cached_veh_length + 1) / 2 + flip_offs;
this->x_extent = this->gcache.cached_veh_length - 1;
this->x_bb_offs = -1;
this->bounds.origin.x = -(this->gcache.cached_veh_length + 1) / 2 + flip_offs;
this->bounds.extent.x = this->gcache.cached_veh_length;
this->bounds.offset.x = 1;
break;
case DIR_NW:
this->y_offs = 1 - (this->gcache.cached_veh_length + 1) / 2 + flip_offs;
this->y_extent = this->gcache.cached_veh_length - 1;
this->y_bb_offs = -1;
this->bounds.origin.y = -(this->gcache.cached_veh_length + 1) / 2 + flip_offs;
this->bounds.extent.y = this->gcache.cached_veh_length;
this->bounds.offset.y = 1;
break;
/* Move northern corner of the bounding box down according to vehicle length
* and center the bounding box on the vehicle. */
case DIR_SW:
this->x_offs = 1 + (this->gcache.cached_veh_length + 1) / 2 - VEHICLE_LENGTH - flip_offs;
this->x_extent = VEHICLE_LENGTH - 1;
this->x_bb_offs = VEHICLE_LENGTH - this->gcache.cached_veh_length - 1;
this->bounds.origin.x = -(this->gcache.cached_veh_length) / 2 - flip_offs;
this->bounds.extent.x = this->gcache.cached_veh_length;
this->bounds.offset.x = 1 - (VEHICLE_LENGTH - this->gcache.cached_veh_length);
break;
case DIR_SE:
this->y_offs = 1 + (this->gcache.cached_veh_length + 1) / 2 - VEHICLE_LENGTH - flip_offs;
this->y_extent = VEHICLE_LENGTH - 1;
this->y_bb_offs = VEHICLE_LENGTH - this->gcache.cached_veh_length - 1;
this->bounds.origin.y = -(this->gcache.cached_veh_length) / 2 - flip_offs;
this->bounds.extent.y = this->gcache.cached_veh_length;
this->bounds.offset.y = 1 - (VEHICLE_LENGTH - this->gcache.cached_veh_length);
break;
default:
@ -2194,7 +2185,7 @@ static FindDepotData FindClosestTrainDepot(Train *v, int max_distance)
return YapfTrainFindNearestDepot(v, max_distance);
}
ClosestDepot Train::FindClosestDepot()
ClosestDepot Train::FindClosestDepot([[maybe_unused]] bool may_reverse)
{
FindDepotData tfdd = FindClosestTrainDepot(this, 0);
if (tfdd.best_length == UINT_MAX) return ClosestDepot();

View File

@ -635,7 +635,7 @@ CommandCost CmdPlantTree(DoCommandFlags flags, TileIndex tile, TileIndex start_t
}
struct TreeListEnt : PalSpriteID {
uint8_t x, y;
int8_t x, y;
};
static void DrawTile_Trees(TileInfo *ti)
@ -699,7 +699,8 @@ static void DrawTile_Trees(TileInfo *ti)
}
}
AddSortableSpriteToDraw(te[mi].sprite, te[mi].pal, ti->x + te[mi].x, ti->y + te[mi].y, 16 - te[mi].x, 16 - te[mi].y, 0x30, z, IsTransparencySet(TO_TREES), -te[mi].x, -te[mi].y);
SpriteBounds bounds{{}, {TILE_SIZE, TILE_SIZE, 48}, {te[mi].x, te[mi].y, 0}};
AddSortableSpriteToDraw(te[mi].sprite, te[mi].pal, ti->x, ti->y, z, bounds, IsTransparencySet(TO_TREES));
/* replace the removed one with the last one */
te[mi] = te[trees - 1];

View File

@ -1017,10 +1017,10 @@ static CommandCost ClearTile_TunnelBridge(TileIndex tile, DoCommandFlags flags)
* @param h Bounding box size in Y direction
* @param subsprite Optional subsprite for drawing halfpillars
*/
static inline void DrawPillar(const PalSpriteID *psid, int x, int y, int z, int w, int h, const SubSprite *subsprite)
static inline void DrawPillar(const PalSpriteID *psid, int x, int y, int z, uint8_t w, uint8_t h, const SubSprite *subsprite)
{
static const int PILLAR_Z_OFFSET = TILE_HEIGHT - BRIDGE_Z_START; ///< Start offset of pillar wrt. bridge (downwards)
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, w, h, BB_HEIGHT_UNDER_BRIDGE - PILLAR_Z_OFFSET, z, IsTransparencySet(TO_BRIDGES), 0, 0, -PILLAR_Z_OFFSET, subsprite);
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, z, {{0, 0, -PILLAR_Z_OFFSET}, {w, h, BB_HEIGHT_UNDER_BRIDGE}, {0, 0, PILLAR_Z_OFFSET}}, IsTransparencySet(TO_BRIDGES), subsprite);
}
/**
@ -1194,18 +1194,20 @@ static void DrawBridgeRoadBits(TileIndex head_tile, int x, int y, int z, int off
}
}
static const uint size_x[6] = { 1, 16, 16, 1, 16, 1 };
static const uint size_y[6] = { 16, 1, 1, 16, 1, 16 };
static const uint front_bb_offset_x[6] = { 15, 0, 0, 15, 0, 15 };
static const uint front_bb_offset_y[6] = { 0, 15, 15, 0, 15, 0 };
static constexpr SpriteBounds back_bounds[6] = {
{{}, {0, TILE_SIZE, 40}, {}},
{{}, {TILE_SIZE, 0, 40}, {}},
{{}, {TILE_SIZE, 0, 40}, {}},
{{}, {0, TILE_SIZE, 40}, {}},
{{}, {TILE_SIZE, 0, 40}, {}},
{{}, {0, TILE_SIZE, 40}, {}},
};
/* The sprites under the vehicles are drawn as SpriteCombine. StartSpriteCombine() has already been called
* The bounding boxes here are the same as for bridge front/roof */
for (uint i = 0; i < lengthof(seq_back); ++i) {
if (seq_back[i] != 0) {
AddSortableSpriteToDraw(seq_back[i], PAL_NONE,
x, y, size_x[offset], size_y[offset], 0x28, z,
trans_back[i]);
AddSortableSpriteToDraw(seq_back[i], PAL_NONE, x, y, z, back_bounds[offset], trans_back[i]);
}
}
@ -1213,12 +1215,18 @@ static void DrawBridgeRoadBits(TileIndex head_tile, int x, int y, int z, int off
EndSpriteCombine();
StartSpriteCombine();
static constexpr SpriteBounds front_bounds[6] = {
{{15, 0, 0}, {0, TILE_SIZE, 40}, {-15, 0, 0}},
{{0, 15, 0}, {TILE_SIZE, 0, 40}, {0, -15, 0}},
{{0, 15, 0}, {TILE_SIZE, 0, 40}, {0, -15, 0}},
{{15, 0, 0}, {0, TILE_SIZE, 40}, {-15, 0, 0}},
{{0, 15, 0}, {TILE_SIZE, 0, 40}, {0, -15, 0}},
{{15, 0, 0}, {0, TILE_SIZE, 40}, {-15, 0, 0}},
};
for (uint i = 0; i < lengthof(seq_front); ++i) {
if (seq_front[i] != 0) {
AddSortableSpriteToDraw(seq_front[i], PAL_NONE,
x, y, size_x[offset] + front_bb_offset_x[offset], size_y[offset] + front_bb_offset_y[offset], 0x28, z,
trans_front[i],
front_bb_offset_x[offset], front_bb_offset_y[offset]);
AddSortableSpriteToDraw(seq_front[i], PAL_NONE, x, y, z, front_bounds[offset], trans_front[i]);
}
}
}
@ -1251,15 +1259,35 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
*
*/
static const int _tunnel_BB[4][12] = {
/* tunnnel-roof | Z-separator | tram-catenary
* w h bb_x bb_y| x y w h |bb_x bb_y w h */
{ 1, 0, -15, -14, 0, 15, 16, 1, 0, 1, 16, 15 }, // NE
{ 0, 1, -14, -15, 15, 0, 1, 16, 1, 0, 15, 16 }, // SE
{ 1, 0, -15, -14, 0, 15, 16, 1, 0, 1, 16, 15 }, // SW
{ 0, 1, -14, -15, 15, 0, 1, 16, 1, 0, 15, 16 }, // NW
/* Tunnel sprites are positioned at 15,15, but the bounding box covers most of the tile. */
static constexpr SpriteBounds roof_bounds[DIAGDIR_END] = {
{{0, 1, BB_Z_SEPARATOR}, {TILE_SIZE, TILE_SIZE - 1, 1}, {TILE_SIZE - 1, TILE_SIZE - 2, -BB_Z_SEPARATOR}}, // NE
{{1, 0, BB_Z_SEPARATOR}, {TILE_SIZE - 1, TILE_SIZE, 1}, {TILE_SIZE - 2, TILE_SIZE - 1, -BB_Z_SEPARATOR}}, // SE
{{0, 1, BB_Z_SEPARATOR}, {TILE_SIZE, TILE_SIZE - 1, 1}, {TILE_SIZE - 1, TILE_SIZE - 2, -BB_Z_SEPARATOR}}, // SW
{{1, 0, BB_Z_SEPARATOR}, {TILE_SIZE - 1, TILE_SIZE, 1}, {TILE_SIZE - 2, TILE_SIZE - 1, -BB_Z_SEPARATOR}}, // NW
};
/* Catenary sprites are positioned at 0,0, with the same bounding box as above. */
static constexpr SpriteBounds catenary_bounds[DIAGDIR_END] = {
{{0, 1, BB_Z_SEPARATOR}, {TILE_SIZE, TILE_SIZE - 1, 1}, {0, -1, -BB_Z_SEPARATOR}}, // NE
{{1, 0, BB_Z_SEPARATOR}, {TILE_SIZE - 1, TILE_SIZE, 1}, {-1, 0, -BB_Z_SEPARATOR}}, // SE
{{0, 1, BB_Z_SEPARATOR}, {TILE_SIZE, TILE_SIZE - 1, 1}, {0, -1, -BB_Z_SEPARATOR}}, // SW
{{1, 0, BB_Z_SEPARATOR}, {TILE_SIZE - 1, TILE_SIZE, 1}, {-1, 0, -BB_Z_SEPARATOR}}, // NW
};
static constexpr SpriteBounds rear_sep[DIAGDIR_END] = {
{{}, {TILE_SIZE, 1, TILE_HEIGHT}, {}}, // NE
{{}, {1, TILE_SIZE, TILE_HEIGHT}, {}}, // SE
{{}, {TILE_SIZE, 1, TILE_HEIGHT}, {}}, // SW
{{}, {1, TILE_SIZE, TILE_HEIGHT}, {}}, // NW
};
static constexpr SpriteBounds front_sep[DIAGDIR_END] = {
{{0, TILE_SIZE - 1, 0}, {TILE_SIZE, 1, TILE_HEIGHT}, {}}, // NE
{{TILE_SIZE - 1, 0, 0}, {1, TILE_SIZE, TILE_HEIGHT}, {}}, // SE
{{0, TILE_SIZE - 1, 0}, {TILE_SIZE, 1, TILE_HEIGHT}, {}}, // SW
{{TILE_SIZE - 1, 0, 0}, {1, TILE_SIZE, TILE_HEIGHT}, {}}, // NW
};
const int *BB_data = _tunnel_BB[tunnelbridge_direction];
bool catenary = false;
@ -1332,7 +1360,7 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
if (catenary_sprite_base != 0) {
catenary = true;
StartSpriteCombine();
AddSortableSpriteToDraw(catenary_sprite_base + tunnelbridge_direction, PAL_NONE, ti->x, ti->y, BB_data[10], BB_data[11], TILE_HEIGHT, ti->z, IsTransparencySet(TO_CATENARY), BB_data[8], BB_data[9], BB_Z_SEPARATOR);
AddSortableSpriteToDraw(catenary_sprite_base + tunnelbridge_direction, PAL_NONE, *ti, catenary_bounds[tunnelbridge_direction], IsTransparencySet(TO_CATENARY));
}
} else {
const RailTypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile));
@ -1364,15 +1392,15 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
if (railtype_overlay != 0 && !catenary) StartSpriteCombine();
AddSortableSpriteToDraw(image + 1, PAL_NONE, ti->x + TILE_SIZE - 1, ti->y + TILE_SIZE - 1, BB_data[0], BB_data[1], TILE_HEIGHT, ti->z, false, BB_data[2], BB_data[3], BB_Z_SEPARATOR);
AddSortableSpriteToDraw(image + 1, PAL_NONE, *ti, roof_bounds[tunnelbridge_direction], false);
/* Draw railtype tunnel portal overlay if defined. */
if (railtype_overlay != 0) AddSortableSpriteToDraw(railtype_overlay + tunnelbridge_direction, PAL_NONE, ti->x + TILE_SIZE - 1, ti->y + TILE_SIZE - 1, BB_data[0], BB_data[1], TILE_HEIGHT, ti->z, false, BB_data[2], BB_data[3], BB_Z_SEPARATOR);
if (railtype_overlay != 0) AddSortableSpriteToDraw(railtype_overlay + tunnelbridge_direction, PAL_NONE, *ti, roof_bounds[tunnelbridge_direction], false);
if (catenary || railtype_overlay != 0) EndSpriteCombine();
/* Add helper BB for sprite sorting that separates the tunnel from things beside of it. */
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, ti->x, ti->y, BB_data[6], BB_data[7], TILE_HEIGHT, ti->z);
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, ti->x + BB_data[4], ti->y + BB_data[5], BB_data[6], BB_data[7], TILE_HEIGHT, ti->z);
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, *ti, rear_sep[tunnelbridge_direction]);
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, *ti, front_sep[tunnelbridge_direction]);
DrawBridgeMiddle(ti);
} else { // IsBridge(ti->tile)
@ -1423,7 +1451,7 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
* it doesn't disappear behind it
*/
/* Bridge heads are drawn solid no matter how invisibility/transparency is set */
AddSortableSpriteToDraw(psid->sprite, psid->pal, ti->x, ti->y, 16, 16, ti->tileh == SLOPE_FLAT ? 0 : 8, ti->z);
AddSortableSpriteToDraw(psid->sprite, psid->pal, *ti, {{}, {TILE_SIZE, TILE_SIZE, static_cast<uint8_t>(ti->tileh == SLOPE_FLAT ? 0 : TILE_HEIGHT)}, {}});
if (transport_type == TRANSPORT_ROAD) {
uint offset = tunnelbridge_direction;
@ -1445,9 +1473,9 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
SpriteID surface = GetCustomRailSprite(rti, ti->tile, RTSG_BRIDGE);
if (surface != 0) {
if (HasBridgeFlatRamp(ti->tileh, DiagDirToAxis(tunnelbridge_direction))) {
AddSortableSpriteToDraw(surface + ((DiagDirToAxis(tunnelbridge_direction) == AXIS_X) ? RTBO_X : RTBO_Y), PAL_NONE, ti->x, ti->y, 16, 16, 0, ti->z + 8);
AddSortableSpriteToDraw(surface + ((DiagDirToAxis(tunnelbridge_direction) == AXIS_X) ? RTBO_X : RTBO_Y), PAL_NONE, *ti, {{0, 0, TILE_HEIGHT}, {TILE_SIZE, TILE_SIZE, 0}, {}});
} else {
AddSortableSpriteToDraw(surface + RTBO_SLOPE + tunnelbridge_direction, PAL_NONE, ti->x, ti->y, 16, 16, 8, ti->z);
AddSortableSpriteToDraw(surface + RTBO_SLOPE + tunnelbridge_direction, PAL_NONE, *ti, {{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT}, {}});
}
}
/* Don't fallback to non-overlay sprite -- the spec states that
@ -1460,15 +1488,15 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
if (rti->UsesOverlay()) {
SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY);
if (HasBridgeFlatRamp(ti->tileh, DiagDirToAxis(tunnelbridge_direction))) {
AddSortableSpriteToDraw(overlay + RTO_X + DiagDirToAxis(tunnelbridge_direction), PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, ti->z + 8);
AddSortableSpriteToDraw(overlay + RTO_X + DiagDirToAxis(tunnelbridge_direction), PALETTE_CRASH, *ti, {{0, 0, TILE_HEIGHT}, {TILE_SIZE, TILE_SIZE, 0}, {}});
} else {
AddSortableSpriteToDraw(overlay + RTO_SLOPE_NE + tunnelbridge_direction, PALETTE_CRASH, ti->x, ti->y, 16, 16, 8, ti->z);
AddSortableSpriteToDraw(overlay + RTO_SLOPE_NE + tunnelbridge_direction, PALETTE_CRASH, *ti, {{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT}, {}});
}
} else {
if (HasBridgeFlatRamp(ti->tileh, DiagDirToAxis(tunnelbridge_direction))) {
AddSortableSpriteToDraw(DiagDirToAxis(tunnelbridge_direction) == AXIS_X ? rti->base_sprites.single_x : rti->base_sprites.single_y, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, ti->z + 8);
AddSortableSpriteToDraw(DiagDirToAxis(tunnelbridge_direction) == AXIS_X ? rti->base_sprites.single_x : rti->base_sprites.single_y, PALETTE_CRASH, *ti, {{0, 0, TILE_HEIGHT}, {TILE_SIZE, TILE_SIZE, 0}, {}});
} else {
AddSortableSpriteToDraw(rti->base_sprites.single_sloped + tunnelbridge_direction, PALETTE_CRASH, ti->x, ti->y, 16, 16, 8, ti->z);
AddSortableSpriteToDraw(rti->base_sprites.single_sloped + tunnelbridge_direction, PALETTE_CRASH, *ti, {{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT}, {}});
}
}
}
@ -1578,7 +1606,7 @@ void DrawBridgeMiddle(const TileInfo *ti)
int z = bridge_z - BRIDGE_Z_START;
/* Add a bounding box that separates the bridge from things below it. */
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, x, y, 16, 16, 1, bridge_z - TILE_HEIGHT + BB_Z_SEPARATOR);
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, x, y, bridge_z - TILE_HEIGHT + BB_Z_SEPARATOR, {{}, {TILE_SIZE, TILE_SIZE, 1}, {}});
/* Draw Trambits as SpriteCombine */
if (transport_type == TRANSPORT_ROAD || transport_type == TRANSPORT_RAIL) StartSpriteCombine();
@ -1586,9 +1614,9 @@ void DrawBridgeMiddle(const TileInfo *ti)
/* Draw floor and far part of bridge*/
if (!IsInvisibilitySet(TO_BRIDGES)) {
if (axis == AXIS_X) {
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, 16, 1, 0x28, z, IsTransparencySet(TO_BRIDGES), 0, 0, BRIDGE_Z_START);
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, z, {{0, 0, BRIDGE_Z_START}, {TILE_SIZE, 1, 40}, {0, 0, -BRIDGE_Z_START}}, IsTransparencySet(TO_BRIDGES));
} else {
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, 1, 16, 0x28, z, IsTransparencySet(TO_BRIDGES), 0, 0, BRIDGE_Z_START);
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, z, {{0, 0, BRIDGE_Z_START}, {1, TILE_SIZE, 40}, {0, 0, -BRIDGE_Z_START}}, IsTransparencySet(TO_BRIDGES));
}
}
@ -1602,16 +1630,16 @@ void DrawBridgeMiddle(const TileInfo *ti)
if (rti->UsesOverlay() && !IsInvisibilitySet(TO_BRIDGES)) {
SpriteID surface = GetCustomRailSprite(rti, rampsouth, RTSG_BRIDGE, TCX_ON_BRIDGE);
if (surface != 0) {
AddSortableSpriteToDraw(surface + axis, PAL_NONE, x, y, 16, 16, 0, bridge_z, IsTransparencySet(TO_BRIDGES));
AddSortableSpriteToDraw(surface + axis, PAL_NONE, x, y, bridge_z, {{}, {TILE_SIZE, TILE_SIZE, 0}, {}}, IsTransparencySet(TO_BRIDGES));
}
}
if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && !IsInvisibilitySet(TO_BRIDGES) && HasTunnelBridgeReservation(rampnorth)) {
if (rti->UsesOverlay()) {
SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY);
AddSortableSpriteToDraw(overlay + RTO_X + axis, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, bridge_z, IsTransparencySet(TO_BRIDGES));
AddSortableSpriteToDraw(overlay + RTO_X + axis, PALETTE_CRASH, ti->x, ti->y, bridge_z, {{}, {TILE_SIZE, TILE_SIZE, 0}, {}}, IsTransparencySet(TO_BRIDGES));
} else {
AddSortableSpriteToDraw(axis == AXIS_X ? rti->base_sprites.single_x : rti->base_sprites.single_y, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, bridge_z, IsTransparencySet(TO_BRIDGES));
AddSortableSpriteToDraw(axis == AXIS_X ? rti->base_sprites.single_x : rti->base_sprites.single_y, PALETTE_CRASH, ti->x, ti->y, bridge_z, {{}, {TILE_SIZE, TILE_SIZE, 0}, {}}, IsTransparencySet(TO_BRIDGES));
}
}
@ -1626,10 +1654,10 @@ void DrawBridgeMiddle(const TileInfo *ti)
if (!IsInvisibilitySet(TO_BRIDGES)) {
if (axis == AXIS_X) {
y += 12;
if (psid->sprite & SPRITE_MASK) AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, 16, 4, 0x28, z, IsTransparencySet(TO_BRIDGES), 0, 3, BRIDGE_Z_START);
if (psid->sprite & SPRITE_MASK) AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, z, {{0, 3, BRIDGE_Z_START}, {TILE_SIZE, 1, 40}, {0, -3, -BRIDGE_Z_START}}, IsTransparencySet(TO_BRIDGES));
} else {
x += 12;
if (psid->sprite & SPRITE_MASK) AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, 4, 16, 0x28, z, IsTransparencySet(TO_BRIDGES), 3, 0, BRIDGE_Z_START);
if (psid->sprite & SPRITE_MASK) AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, z, {{3, 0, BRIDGE_Z_START}, {1, TILE_SIZE, 40}, {-3, 0, -BRIDGE_Z_START}}, IsTransparencySet(TO_BRIDGES));
}
}

View File

@ -1109,8 +1109,7 @@ static void DoDrawVehicle(const Vehicle *v)
for (uint i = 0; i < v->sprite_cache.sprite_seq.count; ++i) {
PaletteID pal2 = v->sprite_cache.sprite_seq.seq[i].pal;
if (!pal2 || v->vehstatus.Test(VehState::Crashed)) pal2 = pal;
AddSortableSpriteToDraw(v->sprite_cache.sprite_seq.seq[i].sprite, pal2, v->x_pos + v->x_offs, v->y_pos + v->y_offs,
v->x_extent, v->y_extent, v->z_extent, v->z_pos, shadowed, v->x_bb_offs, v->y_bb_offs);
AddSortableSpriteToDraw(v->sprite_cache.sprite_seq.seq[i].sprite, pal2, v->x_pos, v->y_pos, v->z_pos, v->bounds, shadowed);
}
EndSpriteCombine();
}
@ -1674,12 +1673,24 @@ void Vehicle::UpdateBoundingBoxCoordinates(bool update_cache) const
Rect new_coord;
this->sprite_cache.sprite_seq.GetBounds(&new_coord);
Point pt = RemapCoords(this->x_pos + this->x_offs, this->y_pos + this->y_offs, this->z_pos);
/* z-bounds are not used. */
Point pt = RemapCoords(this->x_pos + this->bounds.origin.x + this->bounds.offset.x, this->y_pos + this->bounds.origin.y + this->bounds.offset.y, this->z_pos);
new_coord.left += pt.x;
new_coord.top += pt.y;
new_coord.right += pt.x + 2 * ZOOM_BASE;
new_coord.bottom += pt.y + 2 * ZOOM_BASE;
extern bool _draw_bounding_boxes;
if (_draw_bounding_boxes) {
int x = this->x_pos + this->bounds.origin.x;
int y = this->y_pos + this->bounds.origin.y;
int z = this->z_pos + this->bounds.origin.z;
new_coord.left = std::min(new_coord.left, RemapCoords(x + bounds.extent.x, y, z).x);
new_coord.right = std::max(new_coord.right, RemapCoords(x, y + bounds.extent.y, z).x + 1);
new_coord.top = std::min(new_coord.top, RemapCoords(x, y, z + bounds.extent.z).y);
new_coord.bottom = std::max(new_coord.bottom, RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1);
}
if (update_cache) {
/*
* If the old coordinates are invalid, set the cache to the new coordinates for correct
@ -2583,7 +2594,7 @@ CommandCost Vehicle::SendToDepot(DoCommandFlags flags, DepotCommandFlags command
return CommandCost();
}
ClosestDepot closest_depot = this->FindClosestDepot();
ClosestDepot closest_depot = this->FindClosestDepot(true);
static const StringID no_depot[] = {STR_ERROR_UNABLE_TO_FIND_ROUTE_TO, STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT, STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT, STR_ERROR_CAN_T_SEND_AIRCRAFT_TO_HANGAR};
if (!closest_depot.found) return CommandCost(no_depot[this->type]);

View File

@ -10,6 +10,7 @@
#ifndef VEHICLE_BASE_H
#define VEHICLE_BASE_H
#include "sprite.h"
#include "track_type.h"
#include "command_type.h"
#include "order_base.h"
@ -293,13 +294,7 @@ public:
* 0xff == reserved for another custom sprite
*/
uint8_t spritenum = 0;
uint8_t x_extent = 0; ///< x-extent of vehicle bounding box
uint8_t y_extent = 0; ///< y-extent of vehicle bounding box
uint8_t z_extent = 0; ///< z-extent of vehicle bounding box
int8_t x_bb_offs = 0; ///< x offset of vehicle bounding box
int8_t y_bb_offs = 0; ///< y offset of vehicle bounding box
int8_t x_offs = 0; ///< x offset for vehicle sprite
int8_t y_offs = 0; ///< y offset for vehicle sprite
SpriteBounds bounds{}; ///< Bounding box of vehicle.
EngineID engine_type = EngineID::Invalid(); ///< The type of engine used for this vehicle.
TextEffectID fill_percent_te_id = INVALID_TE_ID; ///< a text-effect id to a loading indicator object
@ -792,9 +787,10 @@ public:
/**
* Find the closest depot for this vehicle and tell us the location,
* DestinationID and whether we should reverse.
* @param may_reverse Whether the vehicle is allowed to reverse.
* @return A structure with information about the closest depot, if found.
*/
virtual ClosestDepot FindClosestDepot() { return {}; }
virtual ClosestDepot FindClosestDepot([[maybe_unused]] bool may_reverse = false) { return {}; }
virtual void SetDestTile(TileIndex tile) { this->dest_tile = tile; }

View File

@ -660,12 +660,17 @@ static void AddCombinedSprite(SpriteID image, PaletteID pal, int x, int y, int z
* @param bb_offset_z bounding box extent towards negative Z (world)
* @param sub Only draw a part of the sprite.
*/
void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w, int h, int dz, int z, bool transparent, int bb_offset_x, int bb_offset_y, int bb_offset_z, const SubSprite *sub)
void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int z, const SpriteBounds &bounds, bool transparent, const SubSprite *sub)
{
int32_t left, right, top, bottom;
assert((image & SPRITE_MASK) < MAX_SPRITES);
/* Move to bounding box. */
x += bounds.origin.x;
y += bounds.origin.y;
z += bounds.origin.z;
/* make the sprites transparent with the right palette */
if (transparent) {
SetBit(image, PALETTE_MODIFIER_TRANSPARENT);
@ -673,21 +678,21 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w,
}
if (_vd.combine_sprites == SPRITE_COMBINE_ACTIVE) {
AddCombinedSprite(image, pal, x, y, z, sub);
AddCombinedSprite(image, pal, x + bounds.offset.x, y + bounds.offset.y, z + bounds.offset.z, sub);
return;
}
_vd.last_child = LAST_CHILD_NONE;
Point pt = RemapCoords(x, y, z);
Point pt = RemapCoords(x + bounds.offset.x, y + bounds.offset.y, z + bounds.offset.z);
int tmp_left, tmp_top, tmp_x = pt.x, tmp_y = pt.y;
/* Compute screen extents of sprite */
if (image == SPR_EMPTY_BOUNDING_BOX) {
left = tmp_left = RemapCoords(x + w , y + bb_offset_y, z + bb_offset_z).x;
right = RemapCoords(x + bb_offset_x, y + h , z + bb_offset_z).x + 1;
top = tmp_top = RemapCoords(x + bb_offset_x, y + bb_offset_y, z + dz ).y;
bottom = RemapCoords(x + w , y + h , z + bb_offset_z).y + 1;
left = tmp_left = RemapCoords(x + bounds.extent.x, y, z).x;
right = RemapCoords(x, y + bounds.extent.y, z).x + 1;
top = tmp_top = RemapCoords(x, y, z + bounds.extent.z).y;
bottom = RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1;
} else {
const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
left = tmp_left = (pt.x += spr->x_offs);
@ -698,10 +703,10 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w,
if (_draw_bounding_boxes && (image != SPR_EMPTY_BOUNDING_BOX)) {
/* Compute maximal extents of sprite and its bounding box */
left = std::min(left , RemapCoords(x + w , y + bb_offset_y, z + bb_offset_z).x);
right = std::max(right , RemapCoords(x + bb_offset_x, y + h , z + bb_offset_z).x + 1);
top = std::min(top , RemapCoords(x + bb_offset_x, y + bb_offset_y, z + dz ).y);
bottom = std::max(bottom, RemapCoords(x + w , y + h , z + bb_offset_z).y + 1);
left = std::min(left , RemapCoords(x + bounds.extent.x, y, z).x);
right = std::max(right , RemapCoords(x, y + bounds.extent.y, z).x + 1);
top = std::min(top , RemapCoords(x, y, z + bounds.extent.z).y);
bottom = std::max(bottom, RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1);
}
/* Do not add the sprite to the viewport, if it is outside */
@ -722,14 +727,14 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w,
ps.image = image;
ps.pal = pal;
ps.sub = sub;
ps.xmin = x + bb_offset_x;
ps.xmax = x + std::max(bb_offset_x, w) - 1;
ps.xmin = x;
ps.xmax = x + bounds.extent.x - 1;
ps.ymin = y + bb_offset_y;
ps.ymax = y + std::max(bb_offset_y, h) - 1;
ps.ymin = y;
ps.ymax = y + bounds.extent.y - 1;
ps.zmin = z + bb_offset_z;
ps.zmax = z + std::max(bb_offset_z, dz) - 1;
ps.zmin = z;
ps.zmax = z + bounds.extent.z - 1;
ps.first_child = LAST_CHILD_NONE;

View File

@ -11,6 +11,7 @@
#define VIEWPORT_FUNC_H
#include "gfx_type.h"
#include "sprite.h"
#include "viewport_type.h"
#include "window_type.h"
#include "tile_map.h"
@ -51,10 +52,14 @@ void OffsetGroundSprite(int x, int y);
void DrawGroundSprite(SpriteID image, PaletteID pal, const SubSprite *sub = nullptr, int extra_offs_x = 0, int extra_offs_y = 0);
void DrawGroundSpriteAt(SpriteID image, PaletteID pal, int32_t x, int32_t y, int z, const SubSprite *sub = nullptr, int extra_offs_x = 0, int extra_offs_y = 0);
void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w, int h, int dz, int z, bool transparent = false, int bb_offset_x = 0, int bb_offset_y = 0, int bb_offset_z = 0, const SubSprite *sub = nullptr);
void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int z, const SpriteBounds &bounds, bool transparent = false, const SubSprite *sub = nullptr);
void AddChildSpriteScreen(SpriteID image, PaletteID pal, int x, int y, bool transparent = false, const SubSprite *sub = nullptr, bool scale = true, bool relative = true);
std::string *ViewportAddString(const DrawPixelInfo *dpi, const ViewportSign *sign, ViewportStringFlags flags, Colours colour);
inline void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, const Coord3D<int32_t> &world, const SpriteBounds &bounds, bool transparent = false, const SubSprite *sub = nullptr)
{
AddSortableSpriteToDraw(image, pal, world.x, world.y, world.z, bounds, transparent, sub);
}
void StartSpriteCombine();
void EndSpriteCombine();

View File

@ -89,8 +89,8 @@ enum ZoomStateChange : uint8_t {
* z=6 reserved, currently unused.
* z=7 Z separator between bridge/tunnel and the things under/above it.
*/
static const uint BB_HEIGHT_UNDER_BRIDGE = 6; ///< Everything that can be built under low bridges, must not exceed this Z height.
static const uint BB_Z_SEPARATOR = 7; ///< Separates the bridge/tunnel from the things under/above it.
static constexpr int BB_HEIGHT_UNDER_BRIDGE = 6; ///< Everything that can be built under low bridges, must not exceed this Z height.
static constexpr int BB_Z_SEPARATOR = 7; ///< Separates the bridge/tunnel from the things under/above it.
/** Viewport place method (type of highlighted area and placed objects) */
enum ViewportPlaceMethod : uint8_t {

View File

@ -806,11 +806,7 @@ static void DrawWaterTileStruct(const TileInfo *ti, std::span<const DrawTileSeqS
for (const DrawTileSeqStruct &dtss : seq) {
uint tile_offs = offset + dtss.image.sprite;
if (feature < CF_END) tile_offs = GetCanalSpriteOffset(feature, ti->tile, tile_offs);
AddSortableSpriteToDraw(base + tile_offs, palette,
ti->x + dtss.delta_x, ti->y + dtss.delta_y,
dtss.size_x, dtss.size_y,
dtss.size_z, ti->z + dtss.delta_z,
IsTransparencySet(TO_BUILDINGS));
AddSortableSpriteToDraw(base + tile_offs, palette, *ti, dtss, IsTransparencySet(TO_BUILDINGS));
}
}