mirror of
https://github.com/JGRennison/OpenTTD-patches.git
synced 2024-11-08 01:10:28 +00:00
585 lines
14 KiB
C
585 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 "tree.h"
|
|
#include "viewport.h"
|
|
#include "command.h"
|
|
#include "town.h"
|
|
#include "sound.h"
|
|
#include "variables.h"
|
|
|
|
static TreeType GetRandomTreeType(TileIndex tile, uint seed)
|
|
{
|
|
switch (_opt.landscape) {
|
|
case LT_NORMAL:
|
|
return seed * TR_COUNT_TEMPERATE / 256 + TR_TEMPERATE;
|
|
|
|
case LT_HILLY:
|
|
return seed * TR_COUNT_SUB_ARCTIC / 256 + TR_SUB_ARCTIC;
|
|
|
|
case LT_DESERT:
|
|
switch (GetMapExtraBits(tile)) {
|
|
case 0: return seed * TR_COUNT_SUB_TROPICAL / 256 + TR_SUB_TROPICAL;
|
|
case 1: return (seed > 12) ? TR_INVALID : TR_CACTUS;
|
|
default: return seed * TR_COUNT_RAINFOREST / 256 + TR_RAINFOREST;
|
|
}
|
|
|
|
default:
|
|
return seed * TR_COUNT_TOYLAND / 256 + TR_TOYLAND;
|
|
}
|
|
}
|
|
|
|
static void PlaceTree(TileIndex tile, uint32 r)
|
|
{
|
|
TreeType tree = GetRandomTreeType(tile, GB(r, 24, 8));
|
|
|
|
if (tree != TR_INVALID) {
|
|
MakeTree(tile, tree, GB(r, 22, 2), min(GB(r, 16, 3), 6), TR_GRASS, 0);
|
|
|
|
// above snowline?
|
|
if (_opt.landscape == LT_HILLY && GetTileZ(tile) > _opt.snow_line) {
|
|
SetTreeGroundDensity(tile, TR_SNOW_DESERT, 3);
|
|
SetTreeCounter(tile, GB(r, 24, 3));
|
|
} else {
|
|
SetTreeGroundDensity(tile, GB(r, 28, 1), 0);
|
|
SetTreeCounter(tile, GB(r, 24, 4));
|
|
}
|
|
}
|
|
}
|
|
|
|
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 && GetTreeCount(tile) == 3) {
|
|
_error_message = STR_2803_TREE_ALREADY_HERE;
|
|
continue;
|
|
}
|
|
|
|
if (flags & DC_EXEC) {
|
|
AddTreeCount(tile, 1);
|
|
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) {
|
|
TreeType treetype;
|
|
uint growth;
|
|
|
|
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);
|
|
}
|
|
|
|
treetype = p1;
|
|
if (treetype == TR_INVALID) {
|
|
treetype = GetRandomTreeType(tile, GB(Random(), 24, 8));
|
|
if (treetype == TR_INVALID) treetype = TR_CACTUS;
|
|
}
|
|
|
|
growth = _game_mode == GM_EDITOR ? 3 : 0;
|
|
switch (GetClearGround(tile)) {
|
|
case CL_ROUGH: MakeTree(tile, treetype, 0, growth, TR_ROUGH, 0); break;
|
|
case CL_SNOW: MakeTree(tile, treetype, 0, growth, TR_SNOW_DESERT, GetClearDensity(tile)); break;
|
|
default: MakeTree(tile, treetype, 0, growth, TR_GRASS, 0); break;
|
|
}
|
|
MarkTileDirtyByTile(tile);
|
|
|
|
if (_game_mode == GM_EDITOR && IS_INT_INSIDE(treetype, TR_RAINFOREST, TR_CACTUS))
|
|
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)
|
|
{
|
|
const uint32 *s;
|
|
const TreePos* d;
|
|
byte z;
|
|
|
|
switch (GetTreeGround(ti->tile)) {
|
|
case TR_GRASS: DrawClearLandTile(ti, 3); break;
|
|
case TR_ROUGH: DrawHillyLandTile(ti); break;
|
|
default: DrawGroundSprite(_tree_sprites_1[GetTreeDensity(ti->tile)] + _tileh_to_sprite[ti->tileh]); break;
|
|
}
|
|
|
|
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) + (GetTreeType(ti->tile) << 2);
|
|
|
|
/* different tree styles above one of the grounds */
|
|
if (GetTreeGround(ti->tile) == TR_SNOW_DESERT &&
|
|
GetTreeDensity(ti->tile) >= 2 &&
|
|
IS_INT_INSIDE(index, TR_SUB_ARCTIC << 2, TR_RAINFOREST << 2)) {
|
|
index += 164 - (TR_SUB_ARCTIC << 2);
|
|
}
|
|
|
|
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 = GetTreeCount(tile) + 1;
|
|
if (IS_INT_INSIDE(GetTreeType(tile), TR_RAINFOREST, TR_CACTUS)) 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)
|
|
{
|
|
TreeType tt = GetTreeType(tile);
|
|
|
|
if (IS_INT_INSIDE(tt, TR_RAINFOREST, TR_CACTUS)) {
|
|
td->str = STR_280F_RAINFOREST;
|
|
} else if (tt == TR_CACTUS) {
|
|
td->str = STR_2810_CACTUS_PLANTS;
|
|
} else {
|
|
td->str = STR_280E_TREES;
|
|
}
|
|
|
|
td->owner = GetTileOwner(tile);
|
|
}
|
|
|
|
static void AnimateTile_Trees(TileIndex tile)
|
|
{
|
|
/* not used */
|
|
}
|
|
|
|
static void TileLoopTreesDesert(TileIndex tile)
|
|
{
|
|
switch (GetMapExtraBits(tile)) {
|
|
case 1:
|
|
if (GetTreeGround(tile) != TR_SNOW_DESERT) {
|
|
SetTreeGroundDensity(tile, TR_SNOW_DESERT, 3);
|
|
MarkTileDirtyByTile(tile);
|
|
}
|
|
break;
|
|
|
|
case 2: {
|
|
static const SoundFx forest_sounds[] = {
|
|
SND_42_LOON_BIRD,
|
|
SND_43_LION,
|
|
SND_44_MONKEYS,
|
|
SND_48_DISTANT_BIRD
|
|
};
|
|
uint32 r = Random();
|
|
|
|
if (CHANCE16I(1, 200, r)) SndPlayTileFx(forest_sounds[GB(r, 16, 2)], tile);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void TileLoopTreesAlps(TileIndex tile)
|
|
{
|
|
int k = GetTileZ(tile) - _opt.snow_line;
|
|
|
|
if (k < -8) {
|
|
if (GetTreeGround(tile) != TR_SNOW_DESERT) return;
|
|
SetTreeGroundDensity(tile, TR_GRASS, 0);
|
|
} else {
|
|
uint density = min((uint)(k + 8) / 8, 3);
|
|
|
|
if (GetTreeGround(tile) != TR_SNOW_DESERT || GetTreeDensity(tile) != density) {
|
|
SetTreeGroundDensity(tile, TR_SNOW_DESERT, density);
|
|
} else {
|
|
if (GetTreeDensity(tile) == 3) {
|
|
uint32 r = Random();
|
|
if (CHANCE16I(1, 200, r)) {
|
|
SndPlayTileFx((r & 0x80000000) ? SND_39_HEAVY_WIND : SND_34_WIND, tile);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
MarkTileDirtyByTile(tile);
|
|
}
|
|
|
|
static void TileLoop_Trees(TileIndex tile)
|
|
{
|
|
static const TileIndexDiffC _tileloop_trees_dir[] = {
|
|
{-1, -1},
|
|
{ 0, -1},
|
|
{ 1, -1},
|
|
{-1, 0},
|
|
{ 1, 0},
|
|
{-1, 1},
|
|
{ 0, 1},
|
|
{ 1, 1}
|
|
};
|
|
|
|
switch (_opt.landscape) {
|
|
case LT_DESERT: TileLoopTreesDesert(tile); break;
|
|
case LT_HILLY: TileLoopTreesAlps(tile); break;
|
|
}
|
|
|
|
TileLoopClearHelper(tile);
|
|
|
|
if (GetTreeCounter(tile) < 15) {
|
|
AddTreeCounter(tile, 1);
|
|
return;
|
|
}
|
|
SetTreeCounter(tile, 0);
|
|
|
|
switch (GetTreeGrowth(tile)) {
|
|
case 3: /* regular sized tree */
|
|
if (_opt.landscape == LT_DESERT && GetTreeType(tile) != TR_CACTUS && GetMapExtraBits(tile) == 1) {
|
|
AddTreeGrowth(tile, 1);
|
|
} else {
|
|
switch (GB(Random(), 0, 3)) {
|
|
case 0: /* start destructing */
|
|
AddTreeGrowth(tile, 1);
|
|
break;
|
|
|
|
case 1: /* add a tree */
|
|
if (GetTreeCount(tile) < 3) {
|
|
AddTreeCount(tile, 1);
|
|
SetTreeGrowth(tile, 0);
|
|
break;
|
|
}
|
|
/* FALL THROUGH */
|
|
|
|
case 2: { /* add a neighbouring tree */
|
|
TreeType treetype = GetTreeType(tile);
|
|
|
|
tile += ToTileIndexDiff(_tileloop_trees_dir[Random() & 7]);
|
|
|
|
if (!IsTileType(tile, MP_CLEAR)) return;
|
|
|
|
switch (GetClearGround(tile)) {
|
|
case CL_GRASS:
|
|
if (GetClearDensity(tile) != 3) return;
|
|
MakeTree(tile, treetype, 0, 0, TR_GRASS, 0);
|
|
break;
|
|
|
|
case CL_ROUGH: MakeTree(tile, treetype, 0, 0, TR_ROUGH, 0); break;
|
|
case CL_SNOW: MakeTree(tile, treetype, 0, 0, TR_SNOW_DESERT, GetClearDensity(tile)); break;
|
|
default: return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 6: /* final stage of tree destruction */
|
|
if (GetTreeCount(tile) > 0) {
|
|
/* more than one tree, delete it */
|
|
AddTreeCount(tile, -1);
|
|
SetTreeGrowth(tile, 3);
|
|
} else {
|
|
/* just one tree, change type into MP_CLEAR */
|
|
switch (GetTreeGround(tile)) {
|
|
case TR_GRASS: MakeClear(tile, CL_GRASS, 3); break;
|
|
case TR_ROUGH: MakeClear(tile, CL_ROUGH, 3); break;
|
|
default: MakeClear(tile, CL_SNOW, GetTreeDensity(tile)); break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
AddTreeGrowth(tile, 1);
|
|
break;
|
|
}
|
|
|
|
MarkTileDirtyByTile(tile);
|
|
}
|
|
|
|
void OnTick_Trees(void)
|
|
{
|
|
uint32 r;
|
|
TileIndex tile;
|
|
ClearGround ct;
|
|
TreeType 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))) != TR_INVALID) {
|
|
MakeTree(tile, tree, 0, 0, ct == CL_ROUGH ? TR_ROUGH : TR_GRASS, 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))) != TR_INVALID) {
|
|
switch (ct) {
|
|
case CL_GRASS: MakeTree(tile, tree, 0, 0, TR_GRASS, 0); break;
|
|
case CL_ROUGH: MakeTree(tile, tree, 0, 0, TR_ROUGH, 0); break;
|
|
default: MakeTree(tile, tree, 0, 0, TR_SNOW_DESERT, GetClearDensity(tile)); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 */
|
|
};
|