OpenTTD-patches/tree_cmd.c

640 lines
14 KiB
C

/* $Id$ */
#include "stdafx.h"
#include "openttd.h"
#include "clear.h"
#include "table/strings.h"
#include "table/sprites.h"
#include "table/tree_land.h"
#include "functions.h"
#include "map.h"
#include "tile.h"
#include "viewport.h"
#include "command.h"
#include "town.h"
#include "sound.h"
#include "variables.h"
static int GetRandomTreeType(TileIndex tile, uint seed)
{
switch (_opt.landscape) {
case LT_NORMAL:
return seed * 12 >> 8;
case LT_HILLY:
return (seed >> 5) + 12;
case LT_DESERT:
switch (GetMapExtraBits(tile)) {
case 0: return (seed >> 6) + 28;
case 1: return (seed > 12) ? -1 : 27;
default: return (seed * 7 >> 8) + 20;
}
default:
return (seed * 9 >> 8) + 32;
}
}
static void PlaceTree(TileIndex tile, uint32 r)
{
int tree = GetRandomTreeType(tile, GB(r, 24, 8));
byte m5;
if (tree >= 0) {
SetTileType(tile, MP_TREES);
m5 = GB(r, 16, 8);
if (GB(m5, 0, 3) == 7) m5--; // there is no growth state 7
_m[tile].m5 = m5 & 0x07; // growth state;
_m[tile].m5 |= m5 & 0xC0; // amount of trees
_m[tile].m3 = tree; // set type of tree
_m[tile].m4 = 0; // no hedge
// above snowline?
if (_opt.landscape == LT_HILLY && GetTileZ(tile) > _opt.snow_line) {
_m[tile].m2 = 0xE0; // set land type to snow
_m[tile].m2 |= GB(r, 24, 3); // randomize counter
} else {
_m[tile].m2 = GB(r, 24, 5); // randomize counter and ground
}
}
}
static void DoPlaceMoreTrees(TileIndex tile)
{
uint i;
for (i = 0; i < 1000; i++) {
uint32 r = Random();
int x = GB(r, 0, 5) - 16;
int y = GB(r, 8, 5) - 16;
uint dist = myabs(x) + myabs(y);
TileIndex cur_tile = TILE_MASK(tile + TileDiffXY(x, y));
if (dist <= 13 &&
IsTileType(cur_tile, MP_CLEAR) &&
!IsClearGround(cur_tile, CL_FIELDS) &&
!IsClearGround(cur_tile, CL_ROCKS)) {
PlaceTree(cur_tile, r);
}
}
}
static void PlaceMoreTrees(void)
{
uint i = ScaleByMapSize(GB(Random(), 0, 5) + 25);
do {
DoPlaceMoreTrees(RandomTile());
} while (--i);
}
void PlaceTreesRandomly(void)
{
uint i;
i = ScaleByMapSize(1000);
do {
uint32 r = Random();
TileIndex tile = RandomTileSeed(r);
if (IsTileType(tile, MP_CLEAR) &&
!IsClearGround(tile, CL_FIELDS) &&
!IsClearGround(tile, CL_ROCKS)) {
PlaceTree(tile, r);
}
} while (--i);
/* place extra trees at rainforest area */
if (_opt.landscape == LT_DESERT) {
i = ScaleByMapSize(15000);
do {
uint32 r = Random();
TileIndex tile = RandomTileSeed(r);
if (IsTileType(tile, MP_CLEAR) && GetMapExtraBits(tile) == 2) {
PlaceTree(tile, r);
}
} while (--i);
}
}
void GenerateTrees(void)
{
uint i;
if (_opt.landscape != LT_CANDY) PlaceMoreTrees();
for (i = _opt.landscape == LT_HILLY ? 15 : 6; i != 0; i--) {
PlaceTreesRandomly();
}
}
/** Plant a tree.
* @param x,y start tile of area-drag of tree plantation
* @param p1 tree type, -1 means random.
* @param p2 end tile of area-drag
*/
int32 CmdPlantTree(int ex, int ey, uint32 flags, uint32 p1, uint32 p2)
{
int32 cost;
int sx, sy, x, y;
if (p2 >= MapSize()) return CMD_ERROR;
/* Check the tree type. It can be random or some valid value within the current climate */
if (p1 != (uint)-1 && p1 - _tree_base_by_landscape[_opt.landscape] >= _tree_count_by_landscape[_opt.landscape]) return CMD_ERROR;
SET_EXPENSES_TYPE(EXPENSES_OTHER);
// make sure sx,sy are smaller than ex,ey
sx = TileX(p2);
sy = TileY(p2);
ex /= 16; ey /= 16;
if (ex < sx) intswap(ex, sx);
if (ey < sy) intswap(ey, sy);
cost = 0; // total cost
for (x = sx; x <= ex; x++) {
for (y = sy; y <= ey; y++) {
TileIndex tile = TileXY(x, y);
if (!EnsureNoVehicle(tile)) continue;
switch (GetTileType(tile)) {
case MP_TREES:
// no more space for trees?
if (_game_mode != GM_EDITOR && (_m[tile].m5 & 0xC0) == 0xC0) {
_error_message = STR_2803_TREE_ALREADY_HERE;
continue;
}
if (flags & DC_EXEC) {
_m[tile].m5 += 0x40;
MarkTileDirtyByTile(tile);
}
// 2x as expensive to add more trees to an existing tile
cost += _price.build_trees * 2;
break;
case MP_CLEAR:
if (!IsTileOwner(tile, OWNER_NONE)) {
_error_message = STR_2804_SITE_UNSUITABLE;
continue;
}
switch (GetClearGround(tile)) {
case CL_FIELDS: cost += _price.clear_3; break;
case CL_ROCKS: cost += _price.clear_2; break;
default: break;
}
if (flags & DC_EXEC) {
int treetype;
int m2;
if (_game_mode != GM_EDITOR && _current_player < MAX_PLAYERS) {
Town *t = ClosestTownFromTile(tile, _patches.dist_local_authority);
if (t != NULL)
ChangeTownRating(t, RATING_TREE_UP_STEP, RATING_TREE_MAXIMUM);
}
switch (GetClearGround(tile)) {
case CL_ROUGH: m2 = 16; break;
case CL_SNOW: m2 = GetClearDensity(tile) << 6 | 0x20; break;
default: m2 = 0; break;
}
treetype = p1;
if (treetype == -1) {
treetype = GetRandomTreeType(tile, GB(Random(), 24, 8));
if (treetype == -1) treetype = 27;
}
ModifyTile(tile,
MP_SETTYPE(MP_TREES) |
MP_MAP2 | MP_MAP3LO | MP_MAP3HI_CLEAR | MP_MAP5,
m2, /* map2 */
treetype, /* map3lo */
_game_mode == GM_EDITOR ? 3 : 0 /* map5 */
);
if (_game_mode == GM_EDITOR && IS_BYTE_INSIDE(treetype, 0x14, 0x1B))
SetMapExtraBits(tile, 2);
}
cost += _price.build_trees;
break;
default:
_error_message = STR_2804_SITE_UNSUITABLE;
break;
}
}
}
return (cost == 0) ? CMD_ERROR : cost;
}
typedef struct TreeListEnt {
uint32 image;
byte x,y;
} TreeListEnt;
static void DrawTile_Trees(TileInfo *ti)
{
uint16 m2;
const uint32 *s;
const TreePos* d;
byte z;
m2 = _m[ti->tile].m2;
if ((m2 & 0x30) == 0) {
DrawClearLandTile(ti, 3);
} else if ((m2 & 0x30) == 0x20) {
DrawGroundSprite(_tree_sprites_1[m2 >> 6] + _tileh_to_sprite[ti->tileh]);
} else {
DrawHillyLandTile(ti);
}
DrawClearLandFence(ti);
z = ti->z;
if (ti->tileh != 0) {
z += 4;
if (IsSteepTileh(ti->tileh))
z += 4;
}
{
uint16 tmp = ti->x;
uint index;
tmp = ROR(tmp, 2);
tmp -= ti->y;
tmp = ROR(tmp, 3);
tmp -= ti->x;
tmp = ROR(tmp, 1);
tmp += ti->y;
d = _tree_layout_xy[GB(tmp, 4, 2)];
index = GB(tmp, 6, 2) + (_m[ti->tile].m3 << 2);
/* different tree styles above one of the grounds */
if ((m2 & 0xB0) == 0xA0 && index >= 48 && index < 80)
index += 164 - 48;
assert(index < lengthof(_tree_layout_sprite));
s = _tree_layout_sprite[index];
}
StartSpriteCombine();
if (!(_display_opt & DO_TRANS_BUILDINGS) || !_patches.invisible_trees) {
TreeListEnt te[4];
uint i;
/* put the trees to draw in a list */
i = (ti->map5 >> 6) + 1;
do {
uint32 image = s[0] + (--i == 0 ? GB(ti->map5, 0, 3) : 3);
if (_display_opt & DO_TRANS_BUILDINGS) MAKE_TRANSPARENT(image);
te[i].image = image;
te[i].x = d->x;
te[i].y = d->y;
s++;
d++;
} while (i);
/* draw them in a sorted way */
for (;;) {
byte min = 0xFF;
TreeListEnt *tep = NULL;
i = (ti->map5 >> 6) + 1;
do {
if (te[--i].image != 0 && te[i].x + te[i].y < min) {
min = te[i].x + te[i].y;
tep = &te[i];
}
} while (i);
if (tep == NULL) break;
AddSortableSpriteToDraw(tep->image, ti->x + tep->x, ti->y + tep->y, 5, 5, 0x10, z);
tep->image = 0;
}
}
EndSpriteCombine();
}
static uint GetSlopeZ_Trees(const TileInfo* ti)
{
return GetPartialZ(ti->x & 0xF, ti->y & 0xF, ti->tileh) + ti->z;
}
static uint GetSlopeTileh_Trees(const TileInfo* ti)
{
return ti->tileh;
}
static int32 ClearTile_Trees(TileIndex tile, byte flags)
{
uint num;
if (flags & DC_EXEC && _current_player < MAX_PLAYERS) {
Town *t = ClosestTownFromTile(tile, _patches.dist_local_authority);
if (t != NULL)
ChangeTownRating(t, RATING_TREE_DOWN_STEP, RATING_TREE_MINIMUM);
}
num = GB(_m[tile].m5, 6, 2) + 1;
if (IS_INT_INSIDE(_m[tile].m3, 20, 26 + 1)) num *= 4;
if (flags & DC_EXEC) DoClearSquare(tile);
return num * _price.remove_trees;
}
static void GetAcceptedCargo_Trees(TileIndex tile, AcceptedCargo ac)
{
/* not used */
}
static void GetTileDesc_Trees(TileIndex tile, TileDesc *td)
{
byte b;
StringID str;
td->owner = GetTileOwner(tile);
b = _m[tile].m3;
(str=STR_2810_CACTUS_PLANTS, b==0x1B) ||
(str=STR_280F_RAINFOREST, IS_BYTE_INSIDE(b, 0x14, 0x1A+1)) ||
(str=STR_280E_TREES, true);
td->str = str;
}
static void AnimateTile_Trees(TileIndex tile)
{
/* not used */
}
static void TileLoopTreesDesert(TileIndex tile)
{
static const SoundFx forest_sounds[] = {
SND_42_LOON_BIRD,
SND_43_LION,
SND_44_MONKEYS,
SND_48_DISTANT_BIRD
};
byte b = GetMapExtraBits(tile);
if (b == 2) {
uint32 r = Random();
if (CHANCE16I(1, 200, r)) SndPlayTileFx(forest_sounds[GB(r, 16, 2)], tile);
} else if (b == 1) {
if (GB(_m[tile].m2, 4, 2) != 2) {
SB(_m[tile].m2, 4, 2, 2);
SB(_m[tile].m2, 6, 2, 3);
MarkTileDirtyByTile(tile);
}
}
}
static void TileLoopTreesAlps(TileIndex tile)
{
byte tmp, m2;
int k;
/* distance from snow line, in steps of 8 */
k = GetTileZ(tile) - _opt.snow_line;
tmp = _m[tile].m2 & 0xF0;
if (k < -8) {
if ((tmp & 0x30) != 0x20) return;
m2 = 0; // no snow
} else if (k == -8) {
m2 = 0x20; // 1/4 snow
if (tmp == m2) return;
} else if (k == 0) {
m2 = 0x60;// 1/2 snow
if (tmp == m2) return;
} else if (k == 8) {
m2 = 0xA0; // 3/4 snow
if (tmp == m2) return;
} else {
if (tmp == 0xE0) {
uint32 r = Random();
if (CHANCE16I(1, 200, r)) {
SndPlayTileFx((r & 0x80000000) ? SND_39_HEAVY_WIND : SND_34_WIND, tile);
}
return;
} else {
m2 = 0xE0; // full snow
}
}
_m[tile].m2 &= 0xF;
_m[tile].m2 |= m2;
MarkTileDirtyByTile(tile);
}
static void TileLoop_Trees(TileIndex tile)
{
byte m5;
static const TileIndexDiffC _tileloop_trees_dir[] = {
{-1, -1},
{ 0, -1},
{ 1, -1},
{-1, 0},
{ 1, 0},
{-1, 1},
{ 0, 1},
{ 1, 1}
};
if (_opt.landscape == LT_DESERT) {
TileLoopTreesDesert(tile);
} else if (_opt.landscape == LT_HILLY) {
TileLoopTreesAlps(tile);
}
TileLoopClearHelper(tile);
/* increase counter */
AB(_m[tile].m2, 0, 4, 1);
if (GB(_m[tile].m2, 0, 4) != 0) return;
m5 = _m[tile].m5;
if (GB(m5, 0, 3) == 3) {
/* regular sized tree */
if (_opt.landscape == LT_DESERT && _m[tile].m3 != 0x1B && GetMapExtraBits(tile) == 1) {
m5++; /* start destructing */
} else {
switch (GB(Random(), 0, 3)) {
case 0: /* start destructing */
m5++;
break;
case 1: /* add a tree */
if (m5 < 0xC0) {
m5 = (m5 + 0x40) & ~7;
break;
}
/* fall through */
case 2: { /* add a neighbouring tree */
byte m3 = _m[tile].m3;
tile += ToTileIndexDiff(_tileloop_trees_dir[Random() & 7]);
if (!IsTileType(tile, MP_CLEAR)) return;
switch (GetClearGround(tile)) {
case CL_GRASS:
if (GetClearDensity(tile) != 3) return;
_m[tile].m2 = 0;
break;
case CL_ROUGH: _m[tile].m2 = 0x10; break;
case CL_SNOW: _m[tile].m2 = GetClearDensity(tile) << 6 | 0x20; break;
default: return;
}
_m[tile].m3 = m3;
_m[tile].m4 = 0;
SetTileType(tile, MP_TREES);
m5 = 0;
break;
}
default:
return;
}
}
} else if (GB(m5, 0, 3) == 6) {
/* final stage of tree destruction */
if (GB(m5, 6, 2) != 0) {
/* more than one tree, delete it? */
m5 = ((m5 - 6) - 0x40) + 3;
} else {
/* just one tree, change type into MP_CLEAR */
SetTileType(tile, MP_CLEAR);
SetTileOwner(tile, OWNER_NONE);
switch (_m[tile].m2 & 0x30) {
case 0x00: SetClearGroundDensity(tile, CL_GRASS, 3); break;
case 0x10: SetClearGroundDensity(tile, CL_ROUGH, 3); break;
default: SetClearGroundDensity(tile, CL_SNOW, GB(_m[tile].m2, 6, 2)); break;
}
MarkTileDirtyByTile(tile);
return;
}
} else {
/* in the middle of a transition, change to next */
m5++;
}
_m[tile].m5 = m5;
MarkTileDirtyByTile(tile);
}
void OnTick_Trees(void)
{
uint32 r;
TileIndex tile;
ClearGround ct;
int tree;
/* place a tree at a random rainforest spot */
if (_opt.landscape == LT_DESERT &&
(r = Random(), tile = RandomTileSeed(r), GetMapExtraBits(tile) == 2) &&
IsTileType(tile, MP_CLEAR) &&
(ct = GetClearGround(tile), ct == CL_GRASS || ct == CL_ROUGH) &&
(tree = GetRandomTreeType(tile, GB(r, 24, 8))) >= 0) {
ModifyTile(tile,
MP_SETTYPE(MP_TREES) |
MP_MAP2 | MP_MAP3LO | MP_MAP3HI | MP_MAP5,
(ct == CL_ROUGH ? 0x10 : 0),
tree,
_m[tile].m4 & ~3,
0
);
}
// byte underflow
if (--_trees_tick_ctr != 0) return;
/* place a tree at a random spot */
r = Random();
tile = TILE_MASK(r);
if (IsTileType(tile, MP_CLEAR) &&
(ct = GetClearGround(tile), ct == CL_GRASS || ct == CL_ROUGH || ct == CL_SNOW) &&
(tree = GetRandomTreeType(tile, GB(r, 24, 8))) >= 0) {
int m2;
switch (ct) {
case CL_GRASS: m2 = 0; break;
case CL_ROUGH: m2 = 0x10; break;
default: m2 = (GetClearDensity(tile) << 6) | 0x20; break;
}
ModifyTile(tile,
MP_SETTYPE(MP_TREES) |
MP_MAP2 | MP_MAP3LO | MP_MAP3HI | MP_MAP5,
m2,
tree,
_m[tile].m4 & ~3,
0
);
}
}
static void ClickTile_Trees(TileIndex tile)
{
/* not used */
}
static uint32 GetTileTrackStatus_Trees(TileIndex tile, TransportType mode)
{
return 0;
}
static void ChangeTileOwner_Trees(TileIndex tile, PlayerID old_player, PlayerID new_player)
{
/* not used */
}
void InitializeTrees(void)
{
_trees_tick_ctr = 0;
}
const TileTypeProcs _tile_type_trees_procs = {
DrawTile_Trees, /* draw_tile_proc */
GetSlopeZ_Trees, /* get_slope_z_proc */
ClearTile_Trees, /* clear_tile_proc */
GetAcceptedCargo_Trees, /* get_accepted_cargo_proc */
GetTileDesc_Trees, /* get_tile_desc_proc */
GetTileTrackStatus_Trees, /* get_tile_track_status_proc */
ClickTile_Trees, /* click_tile_proc */
AnimateTile_Trees, /* animate_tile_proc */
TileLoop_Trees, /* tile_loop_clear */
ChangeTileOwner_Trees, /* change_tile_owner_clear */
NULL, /* get_produced_cargo_proc */
NULL, /* vehicle_enter_tile_proc */
NULL, /* vehicle_leave_tile_proc */
GetSlopeTileh_Trees, /* get_slope_tileh_proc */
};