Add NewGRF object property/flag to use land as object ground sprite

This handles variable ground densities, snow/desert, etc.
pull/341/head
Jonathan G Rennison 3 years ago
parent 906fde15c7
commit 924ffb013f

@ -1853,6 +1853,52 @@
<li>m3: random bits</li>
<li>m5: index into the array of objects, bits 16 to 23 (lower bits in m2)</li>
<li>m7: animation counter</li>
<li style="color: blue">m4 bits 7..5: update counter (for objects using land ground sprites), incremented on every periodic processing.<BR>
For snow and desert, these bits are not used, tile is updated on every periodic processing.</li>
<li style="color: blue">m4 bits 3..2: ground type (for objects using land ground sprites):
<table style="color: blue">
<tr>
<td nowrap valign=top><tt>0</tt>&nbsp; </td>
<td align=left>bare land / grass</td>
</tr>
<tr>
<td nowrap valign=top><tt>1</tt>&nbsp; </td>
<td align=left>snow or desert</td>
</tr>
</table>
</li>
<li style="color: blue">m4 bits 1..0: density (for objects using land ground sprites):
<table style="color: blue">
<tr>
<td nowrap valign=top><tt>0</tt>&nbsp; </td>
<td>bare land</td>
<td>1/4 snow</td>
<td></td>
</tr>
<tr>
<td nowrap valign=top><tt>1</tt>&nbsp; </td>
<td>1/3 grass</td>
<td>2/4 snow;&nbsp;</td>
<td>1/2 desert</td>
</tr>
<tr>
<td nowrap valign=top><tt>2</tt>&nbsp; </td>
<td>2/3 grass</td>
<td>3/4 snow</td>
<td></td>
</tr>
<tr>
<td nowrap valign=top><tt>3</tt>&nbsp; </td>
<td>full grass;&nbsp;</td>
<td>full snow;&nbsp;</td>
<td>full desert</td>
</tr>
</table>
</li>
</ul>
</td>
</tr>

@ -324,7 +324,7 @@ the array so you can quickly see what is used and what is not.
<td class="bits"><span class="free">O</span><span class="used" title="Water class">XX</span> <span class="used" title="Owner">XXXXX</span></td>
<td class="bits"><span class="pool" title="Object index on pool (m2 + m5)">XXXX XXXX XXXX XXXX</span></td>
<td class="bits"><span class="used" title="Random bits">XXXX XXXX</span></td>
<td class="bits"><span class="free">OOOO OOOO</span></td>
<td class="bits"><span class="patch" title="Ground update counter">PPP</span><span class="free">O</span> <span class="patch" title="Ground type: grass/bare, snow/desert">PP</span> <span class="patch" title="Ground density">PP</span></td>
<td class="bits"><span class="pool" title="Object index on pool (m2 + m5)">XXXX XXXX</span></td>
<td class="bits"><span class="free">OOOO OOOO</span></td>
<td class="bits"><span class="used" title="Animation counter">XXXX XXXX</span></td>

@ -4296,6 +4296,11 @@ static ChangeInfoResult ObjectChangeInfo(uint id, int numinfo, int prop, const G
spec->generate_amount = buf->ReadByte();
break;
case A0RPI_OBJECT_USE_LAND_GROUND:
if (MappedPropertyLengthMismatch(buf, 1, mapping_entry)) break;
SB(spec->ctrl_flags, OBJECT_CTRL_FLAG_USE_LAND_GROUND, 1, (buf->ReadByte() != 0 ? 1 : 0));
break;
default:
ret = HandleAction0PropertyDefault(buf, prop);
break;

@ -42,6 +42,7 @@ extern const GRFFeatureInfo _grf_feature_list[] = {
GRFFeatureInfo("action0_signals_recolour", 1),
GRFFeatureInfo("action0_signals_extra_aspects", 1),
GRFFeatureInfo("action3_signals_custom_signal_sprites", 1),
GRFFeatureInfo("action0_object_use_land_ground", 1),
GRFFeatureInfo(),
};
@ -66,6 +67,7 @@ extern const GRFPropertyMapDefinition _grf_action0_remappable_properties[] = {
GRFPropertyMapDefinition(GSF_SIGNALS, A0RPI_SIGNALS_ENABLE_RESTRICTED_SIGNALS, "signals_enable_restricted_signals"),
GRFPropertyMapDefinition(GSF_SIGNALS, A0RPI_SIGNALS_ENABLE_SIGNAL_RECOLOUR, "signals_enable_signal_recolour"),
GRFPropertyMapDefinition(GSF_SIGNALS, A0RPI_SIGNALS_EXTRA_ASPECTS, "signals_extra_aspects"),
GRFPropertyMapDefinition(GSF_OBJECTS, A0RPI_OBJECT_USE_LAND_GROUND, "object_use_land_ground"),
GRFPropertyMapDefinition(),
};

@ -32,6 +32,7 @@ enum Action0RemapPropertyIds {
A0RPI_SIGNALS_ENABLE_RESTRICTED_SIGNALS,
A0RPI_SIGNALS_ENABLE_SIGNAL_RECOLOUR,
A0RPI_SIGNALS_EXTRA_ASPECTS,
A0RPI_OBJECT_USE_LAND_GROUND,
};

@ -20,6 +20,7 @@
#include "tile_cmd.h"
#include "town.h"
#include "water.h"
#include "clear_func.h"
#include "newgrf_animation_base.h"
#include "safeguards.h"
@ -425,7 +426,26 @@ static void DrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *grou
SpriteID image = dts->ground.sprite;
PaletteID pal = dts->ground.pal;
if (GB(image, 0, SPRITE_WIDTH) != 0) {
if (spec->ctrl_flags & OBJECT_CTRL_FLAG_USE_LAND_GROUND) {
if (IsTileOnWater(ti->tile)) {
DrawWaterClassGround(ti);
} else {
switch (GetObjectGroundType(ti->tile)) {
case OBJECT_GROUND_GRASS:
DrawClearLandTile(ti, GetObjectGroundDensity(ti->tile));
break;
case OBJECT_GROUND_SNOW_DESERT:
DrawGroundSprite(GetSpriteIDForSnowDesert(ti->tileh, GetObjectGroundDensity(ti->tile)), PAL_NONE);
break;
default:
/* This should never be reached, just draw a black sprite to make the problem clear without being unnecessarily punitive */
DrawGroundSprite(SPR_FLAT_BARE_LAND + SlopeToSpriteOffset(ti->tileh), PALETTE_ALL_BLACK);
break;
}
}
} else if (GB(image, 0, SPRITE_WIDTH) != 0) {
/* If the ground sprite is the default flat water sprite, draw also canal/river borders
* Do not do this if the tile's WaterClass is 'land'. */
if ((image == SPR_FLAT_WATER_TILE || spec->flags & OBJECT_FLAG_DRAW_WATER) && IsTileOnWater(ti->tile)) {

@ -40,6 +40,12 @@ enum ObjectFlags {
};
DECLARE_ENUM_AS_BIT_SET(ObjectFlags)
enum ObjectCtrlFlags {
OBJECT_CTRL_FLAG_NONE = 0, ///< Just nothing.
OBJECT_CTRL_FLAG_USE_LAND_GROUND = 1 << 0, ///< Use land for ground sprite.
};
DECLARE_ENUM_AS_BIT_SET(ObjectCtrlFlags)
void ResetObjects();
/** Class IDs for objects. */
@ -68,6 +74,7 @@ struct ObjectSpec {
Date introduction_date; ///< From when can this object be built.
Date end_of_life_date; ///< When can't this object be built anymore.
ObjectFlags flags; ///< Flags/settings related to the object.
ObjectCtrlFlags ctrl_flags; ///< Extra control flags.
AnimationInfo animation; ///< Information about the animation.
uint16 callback_mask; ///< Bitmask of requested/allowed callbacks.
uint8 height; ///< The height of this structure, in heightlevels; max MAX_TILE_HEIGHT.

@ -126,6 +126,9 @@ void BuildObject(ObjectType type, TileIndex tile, CompanyID owner, Town *town, u
bool remove = IsDockingTile(t);
MakeObject(t, owner, o->index, wc, Random());
if (remove) RemoveDockingTile(t);
if ((spec->ctrl_flags & OBJECT_CTRL_FLAG_USE_LAND_GROUND) && wc == WATER_CLASS_INVALID) {
SetObjectGroundTypeDensity(t, OBJECT_GROUND_GRASS, 0);
}
MarkTileDirtyByTile(t, VMDF_NOT_MAP_MODE);
}
@ -740,6 +743,84 @@ static void GetTileDesc_Object(TileIndex tile, TileDesc *td)
}
}
/** Convert to or from snowy tiles. */
static void TileLoopObjectGroundAlps(TileIndex tile)
{
int k;
if ((int)TileHeight(tile) < GetSnowLine() - 1) {
/* Fast path to avoid needing to check all 4 corners */
k = -1;
} else {
k = GetTileZ(tile) - GetSnowLine() + 1;
}
if (k < 0) {
/* Below the snow line, do nothing if no snow. */
if (GetObjectGroundType(tile) != OBJECT_GROUND_SNOW_DESERT) return;
} else {
/* At or above the snow line, make snow tile if needed. */
if (GetObjectGroundType(tile) != OBJECT_GROUND_SNOW_DESERT) {
SetObjectGroundTypeDensity(tile, OBJECT_GROUND_SNOW_DESERT, 0);
MarkTileDirtyByTile(tile);
return;
}
}
/* Update snow density. */
uint current_density = GetObjectGroundDensity(tile);
uint req_density = (k < 0) ? 0u : std::min<uint>(k, 3u);
if (current_density < req_density) {
SetObjectGroundDensity(tile, current_density + 1);
} else if (current_density > req_density) {
SetObjectGroundDensity(tile, current_density - 1);
} else {
/* Density at the required level. */
if (k >= 0) return;
SetObjectGroundTypeDensity(tile, OBJECT_GROUND_GRASS, 3);
}
MarkTileDirtyByTile(tile);
}
/**
* Tests if at least one surrounding tile is non-desert
* @param tile tile to check
* @return does this tile have at least one non-desert tile around?
*/
static inline bool NeighbourIsNormal(TileIndex tile)
{
for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) {
TileIndex t = tile + TileOffsByDiagDir(dir);
if (!IsValidTile(t)) continue;
if (GetTropicZone(t) != TROPICZONE_DESERT) return true;
if (HasTileWaterClass(t) && GetWaterClass(t) == WATER_CLASS_SEA) return true;
}
return false;
}
static void TileLoopObjectGroundDesert(TileIndex tile)
{
/* Current desert level - 0 if it is not desert */
uint current = 0;
if (GetObjectGroundType(tile) == OBJECT_GROUND_SNOW_DESERT) current = GetObjectGroundDensity(tile);
/* Expected desert level - 0 if it shouldn't be desert */
uint expected = 0;
if (GetTropicZone(tile) == TROPICZONE_DESERT) {
expected = NeighbourIsNormal(tile) ? 1 : 3;
}
if (current == expected) return;
if (expected == 0) {
SetObjectGroundTypeDensity(tile, OBJECT_GROUND_GRASS, 3);
} else {
/* Transition from clear to desert is not smooth (after clearing desert tile) */
SetObjectGroundTypeDensity(tile, OBJECT_GROUND_SNOW_DESERT, expected);
}
MarkTileDirtyByTile(tile);
}
static void TileLoop_Object(TileIndex tile)
{
const ObjectSpec *spec = ObjectSpec::GetByTile(tile);
@ -749,7 +830,29 @@ static void TileLoop_Object(TileIndex tile)
if (o->location.tile == tile) TriggerObjectAnimation(o, OAT_256_TICKS, spec);
}
if (IsTileOnWater(tile)) TileLoop_Water(tile);
if (IsTileOnWater(tile)) {
TileLoop_Water(tile);
} else if (spec->ctrl_flags & OBJECT_CTRL_FLAG_USE_LAND_GROUND) {
switch (_settings_game.game_creation.landscape) {
case LT_TROPIC: TileLoopObjectGroundDesert(tile); break;
case LT_ARCTIC: TileLoopObjectGroundAlps(tile); break;
}
if (GetObjectGroundType(tile) == OBJECT_GROUND_GRASS && GetObjectGroundDensity(tile) != 3) {
if (_game_mode != GM_EDITOR) {
if (GetObjectGroundCounter(tile) < 7) {
AddObjectGroundCounter(tile, 1);
} else {
SetObjectGroundCounter(tile, 0);
SetObjectGroundDensity(tile, GetObjectGroundDensity(tile) + 1);
MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE);
}
} else {
SetObjectGroundTypeDensity(tile, OBJECT_GROUND_GRASS, 3);
MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE);
}
}
}
if (!IsObjectType(tile, OBJECT_HQ)) return;

@ -13,6 +13,11 @@
#include "water_map.h"
#include "object_type.h"
enum ObjectGround {
OBJECT_GROUND_GRASS = 0, ///< Grass or bare
OBJECT_GROUND_SNOW_DESERT = 1, ///< Snow or desert
};
ObjectType GetObjectType(TileIndex t);
/**
@ -62,6 +67,93 @@ static inline byte GetObjectRandomBits(TileIndex t)
return _m[t].m3;
}
/**
* Get the ground type of ths tile.
* @param t The tile to get the ground type of.
* @pre IsTileType(t, MP_OBJECT)
* @return The ground type.
*/
static inline ObjectGround GetObjectGroundType(TileIndex t)
{
assert_tile(IsTileType(t, MP_OBJECT), t);
return (ObjectGround)GB(_m[t].m4, 2, 2);
}
/**
* Get the ground density of this tile.
* Only meaningful for some ground types.
* @param t The tile to get the density of.
* @pre IsTileType(t, MP_OBJECT)
* @return the density
*/
static inline uint GetObjectGroundDensity(TileIndex t)
{
assert_tile(IsTileType(t, MP_OBJECT), t);
return GB(_m[t].m4, 0, 2);
}
/**
* Set the ground density of this tile.
* Only meaningful for some ground types.
* @param t The tile to set the density of.
* @param d the new density
* @pre IsTileType(t, MP_OBJECT)
*/
static inline void SetObjectGroundDensity(TileIndex t, uint d)
{
assert_tile(IsTileType(t, MP_OBJECT), t);
SB(_m[t].m4, 0, 2, d);
}
/**
* Get the counter used to advance to the next ground density type.
* @param t The tile to get the counter of.
* @pre IsTileType(t, MP_OBJECT)
* @return The value of the counter
*/
static inline uint GetObjectGroundCounter(TileIndex t)
{
assert_tile(IsTileType(t, MP_OBJECT), t);
return GB(_m[t].m4, 5, 3);
}
/**
* Increments the counter used to advance to the next ground density type.
* @param t the tile to increment the counter of
* @param c the amount to increment the counter with
* @pre IsTileType(t, MP_OBJECT)
*/
static inline void AddObjectGroundCounter(TileIndex t, int c)
{
assert_tile(IsTileType(t, MP_OBJECT), t);
_m[t].m4 += c << 5;
}
/**
* Sets the counter used to advance to the next ground density type.
* @param t The tile to set the counter of.
* @param c The amount to set the counter to.
* @pre IsTileType(t, MP_OBJECT)
*/
static inline void SetObjectGroundCounter(TileIndex t, uint c)
{
assert_tile(IsTileType(t, MP_OBJECT), t);
SB(_m[t].m4, 5, 3, c);
}
/**
* Sets ground type and density in one go, also sets the counter to 0
* @param t the tile to set the ground type and density for
* @param type the new ground type of the tile
* @param density the density of the ground tile
* @pre IsTileType(t, MP_OBJECT)
*/
static inline void SetObjectGroundTypeDensity(TileIndex t, ObjectGround type, uint density)
{
assert_tile(IsTileType(t, MP_OBJECT), t);
_m[t].m4 = 0 << 5 | type << 2 | density;
}
/**
* Make an Object tile.

@ -868,11 +868,15 @@ class NIHObject : public NIHelper {
seprintf(buffer, lastof(buffer), " [%c] flags: 0x%X", output.flags & 1 ? '-' : '+', spec->flags);
output.print(buffer);
if (output.flags & 1) {
auto print = [&](const char *name) {
seprintf(buffer, lastof(buffer), " %s", name);
output.print(buffer);
};
auto check_flag = [&](ObjectFlags flag, const char *name) {
if (spec->flags & flag) {
seprintf(buffer, lastof(buffer), " %s", name);
output.print(buffer);
}
if (spec->flags & flag) print(name);
};
auto check_ctrl_flag = [&](ObjectCtrlFlags flag, const char *name) {
if (spec->ctrl_flags & flag) print(name);
};
check_flag(OBJECT_FLAG_ONLY_IN_SCENEDIT, "OBJECT_FLAG_ONLY_IN_SCENEDIT");
check_flag(OBJECT_FLAG_CANNOT_REMOVE, "OBJECT_FLAG_CANNOT_REMOVE");
@ -888,6 +892,7 @@ class NIHObject : public NIHelper {
check_flag(OBJECT_FLAG_ALLOW_UNDER_BRIDGE, "OBJECT_FLAG_ALLOW_UNDER_BRIDGE");
check_flag(OBJECT_FLAG_ANIM_RANDOM_BITS, "OBJECT_FLAG_ANIM_RANDOM_BITS");
check_flag(OBJECT_FLAG_SCALE_BY_WATER, "OBJECT_FLAG_SCALE_BY_WATER");
check_ctrl_flag(OBJECT_CTRL_FLAG_USE_LAND_GROUND, "OBJECT_CTRL_FLAG_USE_LAND_GROUND");
}
}
}

@ -121,7 +121,7 @@ static const DrawTileSprites _object_hq[] = {
#undef TILE_SPRITE_LINE
#define M(name, size, build_cost_multiplier, clear_cost_multiplier, height, climate, gen_amount, flags) { GRFFilePropsBase<2>(), INVALID_OBJECT_CLASS, name, climate, size, build_cost_multiplier, clear_cost_multiplier, 0, MAX_DAY + 1, flags, {0, 0, 0, 0}, 0, height, 1, gen_amount, true }
#define M(name, size, build_cost_multiplier, clear_cost_multiplier, height, climate, gen_amount, flags) { GRFFilePropsBase<2>(), INVALID_OBJECT_CLASS, name, climate, size, build_cost_multiplier, clear_cost_multiplier, 0, MAX_DAY + 1, flags, OBJECT_CTRL_FLAG_NONE, {0, 0, 0, 0}, 0, height, 1, gen_amount, true }
/* Climates
* T = Temperate

Loading…
Cancel
Save