mirror of
https://github.com/JGRennison/OpenTTD-patches.git
synced 2024-11-06 03:20:28 +00:00
b618b75c9b
-Feature: Bridges can now be placed above: Any railway track combination (excluding depots and waypoints) Any road combination (excluding depots) Clear tiles (duh), including fields Tunnel entrances Bridge heads Thanks to Tron for idea and implementation, KUDr for the yapf synchronization and many others for hours of testing There are still a number of visual problems remaining, especially when electric railways are on or under the bridge. DO NOT REPORT THOSE BUGS FOR THE TIME BEING please.
651 lines
14 KiB
C
651 lines
14 KiB
C
/* $Id$ */
|
|
|
|
#include "stdafx.h"
|
|
#include "openttd.h"
|
|
#include "bridge_map.h"
|
|
#include "clear_map.h"
|
|
#include "functions.h"
|
|
#include "map.h"
|
|
#include "player.h"
|
|
#include "spritecache.h"
|
|
#include "table/sprites.h"
|
|
#include "tile.h"
|
|
#include <stdarg.h>
|
|
#include "viewport.h"
|
|
#include "command.h"
|
|
#include "vehicle.h"
|
|
#include "variables.h"
|
|
#include "void_map.h"
|
|
#include "water_map.h"
|
|
|
|
extern const TileTypeProcs
|
|
_tile_type_clear_procs,
|
|
_tile_type_rail_procs,
|
|
_tile_type_road_procs,
|
|
_tile_type_town_procs,
|
|
_tile_type_trees_procs,
|
|
_tile_type_station_procs,
|
|
_tile_type_water_procs,
|
|
_tile_type_dummy_procs,
|
|
_tile_type_industry_procs,
|
|
_tile_type_tunnelbridge_procs,
|
|
_tile_type_unmovable_procs;
|
|
|
|
const TileTypeProcs * const _tile_type_procs[16] = {
|
|
&_tile_type_clear_procs,
|
|
&_tile_type_rail_procs,
|
|
&_tile_type_road_procs,
|
|
&_tile_type_town_procs,
|
|
&_tile_type_trees_procs,
|
|
&_tile_type_station_procs,
|
|
&_tile_type_water_procs,
|
|
&_tile_type_dummy_procs,
|
|
&_tile_type_industry_procs,
|
|
&_tile_type_tunnelbridge_procs,
|
|
&_tile_type_unmovable_procs,
|
|
};
|
|
|
|
/* landscape slope => sprite */
|
|
const byte _tileh_to_sprite[32] = {
|
|
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,0,
|
|
0,0,0,0,0,0,0,16,0,0,0,17,0,15,18,0,
|
|
};
|
|
|
|
const byte _inclined_tileh[] = {
|
|
SLOPE_SW, SLOPE_NW, SLOPE_SW, SLOPE_SE, SLOPE_NE, SLOPE_SE, SLOPE_NE, SLOPE_NW
|
|
};
|
|
|
|
|
|
/* find the landscape height for the coordinates x y */
|
|
void FindLandscapeHeight(TileInfo *ti, uint x, uint y)
|
|
{
|
|
ti->x = x;
|
|
ti->y = y;
|
|
|
|
if (x >= MapMaxX() * TILE_SIZE - 1 || y >= MapMaxY() * TILE_SIZE - 1) {
|
|
ti->tileh = SLOPE_FLAT;
|
|
ti->type = MP_VOID;
|
|
ti->tile = 0;
|
|
ti->z = 0;
|
|
} else {
|
|
TileIndex tile = TileVirtXY(x, y);
|
|
|
|
ti->tile = tile;
|
|
ti->type = GetTileType(tile);
|
|
ti->tileh = GetTileSlope(tile, &ti->z);
|
|
}
|
|
}
|
|
|
|
uint GetPartialZ(int x, int y, Slope corners)
|
|
{
|
|
int z = 0;
|
|
|
|
switch (corners) {
|
|
case SLOPE_W:
|
|
if (x - y >= 0)
|
|
z = (x - y) >> 1;
|
|
break;
|
|
|
|
case SLOPE_S:
|
|
y^=0xF;
|
|
if ( (x - y) >= 0)
|
|
z = (x - y) >> 1;
|
|
break;
|
|
|
|
case SLOPE_SW:
|
|
z = (x>>1) + 1;
|
|
break;
|
|
|
|
case SLOPE_E:
|
|
if (y - x >= 0)
|
|
z = (y - x) >> 1;
|
|
break;
|
|
|
|
case SLOPE_EW:
|
|
case SLOPE_NS:
|
|
case SLOPE_ELEVATED:
|
|
z = 4;
|
|
break;
|
|
|
|
case SLOPE_SE:
|
|
z = (y>>1) + 1;
|
|
break;
|
|
|
|
case SLOPE_WSE:
|
|
z = 8;
|
|
y^=0xF;
|
|
if (x - y < 0)
|
|
z += (x - y) >> 1;
|
|
break;
|
|
|
|
case SLOPE_N:
|
|
y ^= 0xF;
|
|
if (y - x >= 0)
|
|
z = (y - x) >> 1;
|
|
break;
|
|
|
|
case SLOPE_NW:
|
|
z = (y^0xF)>>1;
|
|
break;
|
|
|
|
case SLOPE_NWS:
|
|
z = 8;
|
|
if (x - y < 0)
|
|
z += (x - y) >> 1;
|
|
break;
|
|
|
|
case SLOPE_NE:
|
|
z = (x^0xF)>>1;
|
|
break;
|
|
|
|
case SLOPE_ENW:
|
|
z = 8;
|
|
y ^= 0xF;
|
|
if (y - x < 0)
|
|
z += (y - x) >> 1;
|
|
break;
|
|
|
|
case SLOPE_SEN:
|
|
z = 8;
|
|
if (y - x < 0)
|
|
z += (y - x) >> 1;
|
|
break;
|
|
|
|
case SLOPE_STEEP_S:
|
|
z = 1 + ((x+y)>>1);
|
|
break;
|
|
|
|
case SLOPE_STEEP_W:
|
|
z = 1 + ((x+(y^0xF))>>1);
|
|
break;
|
|
|
|
case SLOPE_STEEP_N:
|
|
z = 1 + (((x^0xF)+(y^0xF))>>1);
|
|
break;
|
|
|
|
case SLOPE_STEEP_E:
|
|
z = 1 + (((x^0xF)+(y^0xF))>>1);
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
return z;
|
|
}
|
|
|
|
uint GetSlopeZ(int x, int y)
|
|
{
|
|
TileInfo ti;
|
|
|
|
FindLandscapeHeight(&ti, x, y);
|
|
|
|
return _tile_type_procs[ti.type]->get_slope_z_proc(&ti);
|
|
}
|
|
|
|
// direction=true: check for foundation in east and south corner
|
|
// direction=false: check for foundation in west and south corner
|
|
static bool HasFoundation(TileIndex tile, bool direction)
|
|
{
|
|
bool south, other; // southern corner and east/west corner
|
|
Slope tileh = GetTileSlope(tile, NULL);
|
|
Slope slope = _tile_type_procs[GetTileType(tile)]->get_slope_tileh_proc(tile, tileh);
|
|
|
|
if (slope == SLOPE_FLAT && slope != tileh) tileh = SLOPE_ELEVATED;
|
|
south = (tileh & SLOPE_S) != (slope & SLOPE_S);
|
|
|
|
if (direction) {
|
|
other = (tileh & SLOPE_E) != (slope & SLOPE_E);
|
|
} else {
|
|
other = (tileh & SLOPE_W) != (slope & SLOPE_W);
|
|
}
|
|
return south || other;
|
|
}
|
|
|
|
void DrawFoundation(TileInfo *ti, uint f)
|
|
{
|
|
uint32 sprite_base = SPR_SLOPES_BASE-14;
|
|
|
|
if (HasFoundation(TILE_ADDXY(ti->tile, 0, -1), true)) sprite_base += 22; // foundation in NW direction
|
|
if (HasFoundation(TILE_ADDXY(ti->tile, -1, 0), false)) sprite_base += 44; // foundation in NE direction
|
|
|
|
if (f < 15) {
|
|
// leveled foundation
|
|
if (sprite_base < SPR_SLOPES_BASE) sprite_base = SPR_FOUNDATION_BASE + 1; // use original slope sprites
|
|
|
|
AddSortableSpriteToDraw(f - 1 + sprite_base, ti->x, ti->y, 16, 16, 7, ti->z);
|
|
ti->z += TILE_HEIGHT;
|
|
ti->tileh = SLOPE_FLAT;
|
|
OffsetGroundSprite(31, 1);
|
|
} else {
|
|
// inclined foundation
|
|
sprite_base += 14;
|
|
|
|
#define M(x) (1 << (x))
|
|
AddSortableSpriteToDraw(
|
|
HASBIT(M(SLOPE_W) | M(SLOPE_S) | M(SLOPE_E) | M(SLOPE_N), ti->tileh) ?
|
|
sprite_base + (f - 15) : SPR_FOUNDATION_BASE + ti->tileh,
|
|
ti->x, ti->y, 1, 1, 1, ti->z
|
|
);
|
|
#undef M
|
|
|
|
ti->tileh = _inclined_tileh[f - 15];
|
|
OffsetGroundSprite(31, 9);
|
|
}
|
|
}
|
|
|
|
void DoClearSquare(TileIndex tile)
|
|
{
|
|
MakeClear(tile, CLEAR_GRASS, _generating_world ? 3 : 0);
|
|
MarkTileDirtyByTile(tile);
|
|
}
|
|
|
|
uint32 GetTileTrackStatus(TileIndex tile, TransportType mode)
|
|
{
|
|
return _tile_type_procs[GetTileType(tile)]->get_tile_track_status_proc(tile, mode);
|
|
}
|
|
|
|
void ChangeTileOwner(TileIndex tile, byte old_player, byte new_player)
|
|
{
|
|
_tile_type_procs[GetTileType(tile)]->change_tile_owner_proc(tile, old_player, new_player);
|
|
}
|
|
|
|
void GetAcceptedCargo(TileIndex tile, AcceptedCargo ac)
|
|
{
|
|
memset(ac, 0, sizeof(AcceptedCargo));
|
|
_tile_type_procs[GetTileType(tile)]->get_accepted_cargo_proc(tile, ac);
|
|
}
|
|
|
|
void AnimateTile(TileIndex tile)
|
|
{
|
|
_tile_type_procs[GetTileType(tile)]->animate_tile_proc(tile);
|
|
}
|
|
|
|
void ClickTile(TileIndex tile)
|
|
{
|
|
_tile_type_procs[GetTileType(tile)]->click_tile_proc(tile);
|
|
}
|
|
|
|
void DrawTile(TileInfo *ti)
|
|
{
|
|
_tile_type_procs[ti->type]->draw_tile_proc(ti);
|
|
}
|
|
|
|
void GetTileDesc(TileIndex tile, TileDesc *td)
|
|
{
|
|
_tile_type_procs[GetTileType(tile)]->get_tile_desc_proc(tile, td);
|
|
}
|
|
|
|
/** Clear a piece of landscape
|
|
* @param tile tile to clear
|
|
* @param p1 unused
|
|
* @param p2 unused
|
|
*/
|
|
int32 CmdLandscapeClear(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
|
|
|
|
return _tile_type_procs[GetTileType(tile)]->clear_tile_proc(tile, flags);
|
|
}
|
|
|
|
/** Clear a big piece of landscape
|
|
* @param tile end tile of area dragging
|
|
* @param p1 start tile of area dragging
|
|
* @param p2 unused
|
|
*/
|
|
int32 CmdClearArea(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
int32 cost, ret, money;
|
|
int ex;
|
|
int ey;
|
|
int sx,sy;
|
|
int x,y;
|
|
bool success = false;
|
|
|
|
if (p1 >= MapSize()) return CMD_ERROR;
|
|
|
|
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
|
|
|
|
// make sure sx,sy are smaller than ex,ey
|
|
ex = TileX(tile);
|
|
ey = TileY(tile);
|
|
sx = TileX(p1);
|
|
sy = TileY(p1);
|
|
if (ex < sx) intswap(ex, sx);
|
|
if (ey < sy) intswap(ey, sy);
|
|
|
|
money = GetAvailableMoneyForCommand();
|
|
cost = 0;
|
|
|
|
for (x = sx; x <= ex; ++x) {
|
|
for (y = sy; y <= ey; ++y) {
|
|
ret = DoCommand(TileXY(x, y), 0, 0, flags & ~DC_EXEC, CMD_LANDSCAPE_CLEAR);
|
|
if (CmdFailed(ret)) continue;
|
|
cost += ret;
|
|
success = true;
|
|
|
|
if (flags & DC_EXEC) {
|
|
if (ret > 0 && (money -= ret) < 0) {
|
|
_additional_cash_required = ret;
|
|
return cost - ret;
|
|
}
|
|
DoCommand(TileXY(x, y), 0, 0, flags, CMD_LANDSCAPE_CLEAR);
|
|
|
|
// draw explosion animation...
|
|
if ((x == sx || x == ex) && (y == sy || y == ey)) {
|
|
// big explosion in each corner, or small explosion for single tiles
|
|
CreateEffectVehicleAbove(x * TILE_SIZE + TILE_SIZE / 2, y * TILE_SIZE + TILE_SIZE / 2, 2,
|
|
sy == ey && sx == ex ? EV_EXPLOSION_SMALL : EV_EXPLOSION_LARGE
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return (success) ? cost : CMD_ERROR;
|
|
}
|
|
|
|
|
|
#define TILELOOP_BITS 4
|
|
#define TILELOOP_SIZE (1 << TILELOOP_BITS)
|
|
#define TILELOOP_ASSERTMASK ((TILELOOP_SIZE-1) + ((TILELOOP_SIZE-1) << MapLogX()))
|
|
#define TILELOOP_CHKMASK (((1 << (MapLogX() - TILELOOP_BITS))-1) << TILELOOP_BITS)
|
|
|
|
void RunTileLoop(void)
|
|
{
|
|
TileIndex tile;
|
|
uint count;
|
|
|
|
tile = _cur_tileloop_tile;
|
|
|
|
assert( (tile & ~TILELOOP_ASSERTMASK) == 0);
|
|
count = (MapSizeX() / TILELOOP_SIZE) * (MapSizeY() / TILELOOP_SIZE);
|
|
do {
|
|
_tile_type_procs[GetTileType(tile)]->tile_loop_proc(tile);
|
|
|
|
if (TileX(tile) < MapSizeX() - TILELOOP_SIZE) {
|
|
tile += TILELOOP_SIZE; /* no overflow */
|
|
} else {
|
|
tile = TILE_MASK(tile - TILELOOP_SIZE * (MapSizeX() / TILELOOP_SIZE - 1) + TileDiffXY(0, TILELOOP_SIZE)); /* x would overflow, also increase y */
|
|
}
|
|
} while (--count);
|
|
assert( (tile & ~TILELOOP_ASSERTMASK) == 0);
|
|
|
|
tile += 9;
|
|
if (tile & TILELOOP_CHKMASK)
|
|
tile = (tile + MapSizeX()) & TILELOOP_ASSERTMASK;
|
|
_cur_tileloop_tile = tile;
|
|
}
|
|
|
|
void InitializeLandscape(void)
|
|
{
|
|
uint maxx = MapMaxX();
|
|
uint maxy = MapMaxY();
|
|
uint sizex = MapSizeX();
|
|
uint x;
|
|
uint y;
|
|
|
|
for (y = 0; y < maxy; y++) {
|
|
for (x = 0; x < maxx; x++) {
|
|
MakeClear(sizex * y + x, CLEAR_GRASS, 3);
|
|
SetTileHeight(sizex * y + x, 0);
|
|
_m[sizex * y + x].extra = 0;
|
|
ClearBridgeMiddle(sizex * y + x);
|
|
}
|
|
MakeVoid(sizex * y + x);
|
|
}
|
|
for (x = 0; x < sizex; x++) MakeVoid(sizex * y + x);
|
|
}
|
|
|
|
void ConvertGroundTilesIntoWaterTiles(void)
|
|
{
|
|
TileIndex tile = 0;
|
|
|
|
for (tile = 0; tile < MapSize(); ++tile) {
|
|
if (IsTileType(tile, MP_CLEAR) && GetTileMaxZ(tile) == 0) {
|
|
MakeWater(tile);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const byte _genterrain_tbl_1[5] = { 10, 22, 33, 37, 4 };
|
|
static const byte _genterrain_tbl_2[5] = { 0, 0, 0, 0, 33 };
|
|
|
|
static void GenerateTerrain(int type, int flag)
|
|
{
|
|
uint32 r;
|
|
uint x;
|
|
uint y;
|
|
uint w;
|
|
uint h;
|
|
const Sprite* template;
|
|
const byte *p;
|
|
Tile* tile;
|
|
byte direction;
|
|
|
|
r = Random();
|
|
template = GetSprite((((r >> 24) * _genterrain_tbl_1[type]) >> 8) + _genterrain_tbl_2[type] + 4845);
|
|
|
|
x = r & MapMaxX();
|
|
y = (r >> MapLogX()) & MapMaxY();
|
|
|
|
|
|
if (x < 2 || y < 2) return;
|
|
|
|
direction = GB(r, 22, 2);
|
|
if (direction & 1) {
|
|
w = template->height;
|
|
h = template->width;
|
|
} else {
|
|
w = template->width;
|
|
h = template->height;
|
|
}
|
|
p = template->data;
|
|
|
|
if (flag & 4) {
|
|
uint xw = x * MapSizeY();
|
|
uint yw = y * MapSizeX();
|
|
uint bias = (MapSizeX() + MapSizeY()) * 16;
|
|
|
|
switch (flag & 3) {
|
|
case 0:
|
|
if (xw + yw > MapSize() - bias) return;
|
|
break;
|
|
|
|
case 1:
|
|
if (yw < xw + bias) return;
|
|
break;
|
|
|
|
case 2:
|
|
if (xw + yw < MapSize() + bias) return;
|
|
break;
|
|
|
|
case 3:
|
|
if (xw < yw + bias) return;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (x + w >= MapMaxX() - 1) return;
|
|
if (y + h >= MapMaxY() - 1) return;
|
|
|
|
tile = &_m[TileXY(x, y)];
|
|
|
|
switch (direction) {
|
|
case 0:
|
|
do {
|
|
Tile* tile_cur = tile;
|
|
uint w_cur;
|
|
|
|
for (w_cur = w; w_cur != 0; --w_cur) {
|
|
if (*p >= tile_cur->type_height) tile_cur->type_height = *p;
|
|
p++;
|
|
tile_cur++;
|
|
}
|
|
tile += TileDiffXY(0, 1);
|
|
} while (--h != 0);
|
|
break;
|
|
|
|
case 1:
|
|
do {
|
|
Tile* tile_cur = tile;
|
|
uint h_cur;
|
|
|
|
for (h_cur = h; h_cur != 0; --h_cur) {
|
|
if (*p >= tile_cur->type_height) tile_cur->type_height = *p;
|
|
p++;
|
|
tile_cur += TileDiffXY(0, 1);
|
|
}
|
|
tile++;
|
|
} while (--w != 0);
|
|
break;
|
|
|
|
case 2:
|
|
tile += TileDiffXY(w - 1, 0);
|
|
do {
|
|
Tile* tile_cur = tile;
|
|
uint w_cur;
|
|
|
|
for (w_cur = w; w_cur != 0; --w_cur) {
|
|
if (*p >= tile_cur->type_height) tile_cur->type_height = *p;
|
|
p++;
|
|
tile_cur--;
|
|
}
|
|
tile += TileDiffXY(0, 1);
|
|
} while (--h != 0);
|
|
break;
|
|
|
|
case 3:
|
|
tile += TileDiffXY(0, h - 1);
|
|
do {
|
|
Tile* tile_cur = tile;
|
|
uint h_cur;
|
|
|
|
for (h_cur = h; h_cur != 0; --h_cur) {
|
|
if (*p >= tile_cur->type_height) tile_cur->type_height = *p;
|
|
p++;
|
|
tile_cur -= TileDiffXY(0, 1);
|
|
}
|
|
tile++;
|
|
} while (--w != 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
#include "table/genland.h"
|
|
|
|
static void CreateDesertOrRainForest(void)
|
|
{
|
|
TileIndex tile;
|
|
const TileIndexDiffC *data;
|
|
uint i;
|
|
|
|
for (tile = 0; tile != MapSize(); ++tile) {
|
|
for (data = _make_desert_or_rainforest_data;
|
|
data != endof(_make_desert_or_rainforest_data); ++data) {
|
|
TileIndex t = TILE_MASK(tile + ToTileIndexDiff(*data));
|
|
if (TileHeight(t) >= 4 || IsTileType(t, MP_WATER)) break;
|
|
}
|
|
if (data == endof(_make_desert_or_rainforest_data))
|
|
SetTropicZone(tile, TROPICZONE_DESERT);
|
|
}
|
|
|
|
for (i = 0; i != 256; i++)
|
|
RunTileLoop();
|
|
|
|
for (tile = 0; tile != MapSize(); ++tile) {
|
|
for (data = _make_desert_or_rainforest_data;
|
|
data != endof(_make_desert_or_rainforest_data); ++data) {
|
|
TileIndex t = TILE_MASK(tile + ToTileIndexDiff(*data));
|
|
if (IsTileType(t, MP_CLEAR) && IsClearGround(t, CLEAR_DESERT)) break;
|
|
}
|
|
if (data == endof(_make_desert_or_rainforest_data))
|
|
SetTropicZone(tile, TROPICZONE_RAINFOREST);
|
|
}
|
|
}
|
|
|
|
void GenerateLandscape(void)
|
|
{
|
|
uint i;
|
|
uint flag;
|
|
uint32 r;
|
|
|
|
switch (_opt.landscape) {
|
|
case LT_HILLY:
|
|
for (i = ScaleByMapSize((Random() & 0x7F) + 950); i != 0; --i) {
|
|
GenerateTerrain(2, 0);
|
|
}
|
|
|
|
r = Random();
|
|
flag = GB(r, 0, 2) | 4;
|
|
for (i = ScaleByMapSize(GB(r, 16, 7) + 450); i != 0; --i) {
|
|
GenerateTerrain(4, flag);
|
|
}
|
|
break;
|
|
|
|
case LT_DESERT:
|
|
for (i = ScaleByMapSize((Random() & 0x7F) + 170); i != 0; --i) {
|
|
GenerateTerrain(0, 0);
|
|
}
|
|
|
|
r = Random();
|
|
flag = GB(r, 0, 2) | 4;
|
|
for (i = ScaleByMapSize(GB(r, 16, 8) + 1700); i != 0; --i) {
|
|
GenerateTerrain(0, flag);
|
|
}
|
|
|
|
flag ^= 2;
|
|
|
|
for (i = ScaleByMapSize((Random() & 0x7F) + 410); i != 0; --i) {
|
|
GenerateTerrain(3, flag);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
i = ScaleByMapSize((Random() & 0x7F) + (3 - _opt.diff.quantity_sea_lakes) * 256 + 100);
|
|
for (; i != 0; --i) {
|
|
GenerateTerrain(_opt.diff.terrain_type, 0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
ConvertGroundTilesIntoWaterTiles();
|
|
|
|
if (_opt.landscape == LT_DESERT) CreateDesertOrRainForest();
|
|
}
|
|
|
|
void OnTick_Town(void);
|
|
void OnTick_Trees(void);
|
|
void OnTick_Station(void);
|
|
void OnTick_Industry(void);
|
|
|
|
void OnTick_Players(void);
|
|
void OnTick_Train(void);
|
|
|
|
void CallLandscapeTick(void)
|
|
{
|
|
OnTick_Town();
|
|
OnTick_Trees();
|
|
OnTick_Station();
|
|
OnTick_Industry();
|
|
|
|
OnTick_Players();
|
|
OnTick_Train();
|
|
}
|
|
|
|
TileIndex AdjustTileCoordRandomly(TileIndex a, byte rng)
|
|
{
|
|
int rn = rng;
|
|
uint32 r = Random();
|
|
|
|
return TILE_MASK(TileXY(
|
|
TileX(a) + (GB(r, 0, 8) * rn * 2 >> 8) - rn,
|
|
TileY(a) + (GB(r, 8, 8) * rn * 2 >> 8) - rn
|
|
));
|
|
}
|
|
|
|
bool IsValidTile(TileIndex tile)
|
|
{
|
|
return (tile < MapSizeX() * MapMaxY() && TileX(tile) != MapMaxX());
|
|
}
|