diff --git a/docs/newgrf-additions.html b/docs/newgrf-additions.html index 62d28efc1a..620b461cd9 100644 --- a/docs/newgrf-additions.html +++ b/docs/newgrf-additions.html @@ -238,6 +238,7 @@ +
BitValueMeaning
01Scripts (AI/GS) may not build this road/tram type
12Towns may not modify tiles of this road/tram type in any way whatsoever

This is indicated by the feature name: action0_roadtype_extra_flags, version 1

diff --git a/src/command_type.h b/src/command_type.h index 7629412828..d4b324a36f 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -477,6 +477,7 @@ enum DoCommandFlag { DC_NO_MODIFY_TOWN_RATING = 0x400, ///< do not change town rating DC_FORCE_CLEAR_TILE = 0x800, ///< do not only remove the object on the tile, but also clear any water left on it DC_ALLOW_REMOVE_WATER = 0x1000,///< always allow removing water + DC_TOWN = 0x2000,///< town operation }; DECLARE_ENUM_AS_BIT_SET(DoCommandFlag) diff --git a/src/landscape.cpp b/src/landscape.cpp index b9b1abffb1..a77e7801ca 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -32,6 +32,7 @@ #include "pathfinder/npf/aystar.h" #include "saveload/saveload.h" #include "framerate_type.h" +#include "town.h" #include "3rdparty/cpp-btree/btree_set.h" #include "scope_info.h" #include @@ -713,6 +714,8 @@ CommandCost CmdLandscapeClear(TileIndex tile, DoCommandFlag flags, uint32 p1, ui return_cmd_error(STR_ERROR_CLEARING_LIMIT_REACHED); } + if ((flags & DC_TOWN) && !MayTownModifyRoad(tile)) return CMD_ERROR; + const ClearedObjectArea *coa = FindClearedObject(tile); /* If this tile was the first tile which caused object destruction, always diff --git a/src/road.h b/src/road.h index b1924ded75..430b4fe202 100644 --- a/src/road.h +++ b/src/road.h @@ -53,10 +53,12 @@ DECLARE_ENUM_AS_BIT_SET(RoadTypeFlags) /** Roadtype extra flags. */ enum RoadTypeExtraFlags { - RXTF_NOT_AVAILABLE_AI_GS = 0, ///< Bit number for unavailable for AI/GS + RXTF_NOT_AVAILABLE_AI_GS = 0, ///< Bit number for unavailable for AI/GS + RXTF_NO_TOWN_MODIFICATION, ///< Bit number for no town modification - RXTFB_NONE = 0, ///< All flags cleared. - RXTFB_NOT_AVAILABLE_AI_GS = 1 << RXTF_NOT_AVAILABLE_AI_GS, ///< Value for unavailable for AI/GS + RXTFB_NONE = 0, ///< All flags cleared. + RXTFB_NOT_AVAILABLE_AI_GS = 1 << RXTF_NOT_AVAILABLE_AI_GS, ///< Value for unavailable for AI/GS + RXTFB_NO_TOWN_MODIFICATION = 1 << RXTF_NO_TOWN_MODIFICATION, ///< Value for no town modification }; DECLARE_ENUM_AS_BIT_SET(RoadTypeExtraFlags) diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index afa477e998..68bf3bd7c8 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -721,6 +721,8 @@ CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 bool disable_custom_bridge_heads = HasBit(p1, 13); + if ((flags & DC_TOWN) && !MayTownModifyRoad(tile)) return CMD_ERROR; + Slope tileh = GetTileSlope(tile); RoadTramType rtt = GetRoadTramType(rt); diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index 3052813060..3beb589157 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -861,8 +861,9 @@ class NIHRoadType : public NIHelper { HasBit(rti->flags, ROTF_HIDDEN) ? 'h' : '-', HasBit(rti->flags, ROTF_TOWN_BUILD) ? 'T' : '-'); print(buffer); - seprintf(buffer, lastof(buffer), " Extra Flags: %c", - HasBit(rti->extra_flags, RXTF_NOT_AVAILABLE_AI_GS) ? 's' : '-'); + seprintf(buffer, lastof(buffer), " Extra Flags: %c%c", + HasBit(rti->extra_flags, RXTF_NOT_AVAILABLE_AI_GS) ? 's' : '-', + HasBit(rti->extra_flags, RXTF_NO_TOWN_MODIFICATION) ? 't' : '-'); print(buffer); seprintf(buffer, lastof(buffer), " Powered: 0x" OTTD_PRINTFHEX64, rti->powered_roadtypes); print(buffer); diff --git a/src/town.h b/src/town.h index 586b4e31cf..fccbdc0c9d 100644 --- a/src/town.h +++ b/src/town.h @@ -337,5 +337,6 @@ static inline uint16 TownTicksToGameTicks(uint16 ticks) { RoadType GetTownRoadType(const Town *t); +bool MayTownModifyRoad(TileIndex tile); #endif /* TOWN_H */ diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index aaaa7f17e9..6b146bdf66 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -976,6 +976,7 @@ void OnTick_Town() static RoadBits GetTownRoadBits(TileIndex tile) { if (IsRoadDepotTile(tile) || IsStandardRoadStopTile(tile)) return ROAD_NONE; + if (!MayTownModifyRoad(tile)) return ROAD_NONE; return GetAnyRoadBits(tile, RTT_ROAD, true); } @@ -996,6 +997,7 @@ RoadType GetTownRoadType(const Town *t) /* Can town build this road. */ if (!HasBit(rti->flags, ROTF_TOWN_BUILD)) continue; + if (HasBit(rti->extra_flags, RXTF_NO_TOWN_MODIFICATION)) continue; /* Not yet introduced at this date. */ if (IsInsideMM(rti->introduction_date, 0, MAX_DAY) && rti->introduction_date > _date) continue; @@ -1011,6 +1013,17 @@ RoadType GetTownRoadType(const Town *t) return best_rt; } +bool MayTownModifyRoad(TileIndex tile) +{ + if (MayHaveRoad(tile)) { + RoadType present_road = GetRoadTypeRoad(tile); + if (present_road != INVALID_ROADTYPE && HasBit(GetRoadTypeInfo(present_road)->extra_flags, RXTF_NO_TOWN_MODIFICATION)) return false; + RoadType present_tram = GetRoadTypeTram(tile); + if (present_tram != INVALID_ROADTYPE && HasBit(GetRoadTypeInfo(present_tram)->extra_flags, RXTF_NO_TOWN_MODIFICATION)) return false; + } + return true; +} + /** * Check for parallel road inside a given distance. * Assuming a road from (tile - TileOffsByDiagDir(dir)) to tile, @@ -1069,7 +1082,7 @@ static bool IsRoadAllowedHere(Town *t, TileIndex tile, DiagDirection dir) * This is to make sure that we can build a road here later. */ RoadType rt = GetTownRoadType(t); if (DoCommand(tile, ((dir == DIAGDIR_NW || dir == DIAGDIR_SE) ? ROAD_Y : ROAD_X) | (rt << 4), 0, DC_AUTO, CMD_BUILD_ROAD).Failed() && - DoCommand(tile, 0, 0, DC_AUTO, CMD_LANDSCAPE_CLEAR).Failed()) { + DoCommand(tile, 0, 0, DC_AUTO | DC_TOWN, CMD_LANDSCAPE_CLEAR).Failed()) { return false; } } @@ -1087,7 +1100,7 @@ static bool IsRoadAllowedHere(Town *t, TileIndex tile, DiagDirection dir) if (!_generating_world && Chance16(1, 10)) { /* Note: Do not replace "^ SLOPE_ELEVATED" with ComplementSlope(). The slope might be steep. */ res = DoCommand(tile, Chance16(1, 16) ? cur_slope : cur_slope ^ SLOPE_ELEVATED, 0, - DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_TERRAFORM_LAND); + DC_EXEC | DC_AUTO | DC_NO_WATER | DC_TOWN, CMD_TERRAFORM_LAND); } if (res.Failed() && Chance16(1, 3)) { /* We can consider building on the slope, though. */ @@ -1103,9 +1116,9 @@ static bool TerraformTownTile(TileIndex tile, int edges, int dir) { assert(tile < MapSize()); - CommandCost r = DoCommand(tile, edges, dir, DC_AUTO | DC_NO_WATER, CMD_TERRAFORM_LAND); + CommandCost r = DoCommand(tile, edges, dir, DC_AUTO | DC_NO_WATER | DC_TOWN, CMD_TERRAFORM_LAND); if (r.Failed() || r.GetCost() >= (_price[PR_TERRAFORM] + 2) * 8) return false; - DoCommand(tile, edges, dir, DC_AUTO | DC_NO_WATER | DC_EXEC, CMD_TERRAFORM_LAND); + DoCommand(tile, edges, dir, DC_AUTO | DC_NO_WATER | DC_TOWN | DC_EXEC, CMD_TERRAFORM_LAND); return true; } @@ -1237,7 +1250,7 @@ static bool GrowTownWithExtraHouse(Town *t, TileIndex tile) static bool GrowTownWithRoad(const Town *t, TileIndex tile, RoadBits rcmd) { RoadType rt = GetTownRoadType(t); - if (DoCommand(tile, rcmd | (rt << 4), t->index, DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD).Succeeded()) { + if (DoCommand(tile, rcmd | (rt << 4), t->index, DC_EXEC | DC_AUTO | DC_NO_WATER | DC_TOWN, CMD_BUILD_ROAD).Succeeded()) { _grow_town_result = GROWTH_SUCCEED; return true; } @@ -1296,6 +1309,9 @@ static bool GrowTownWithBridge(const Town *t, const TileIndex tile, const DiagDi /* no water tiles in between? */ if (bridge_length == 1) return false; + if (!MayTownModifyRoad(bridge_tile)) return false; + if (IsValidTile(bridge_tile + delta) && !MayTownModifyRoad(bridge_tile + delta)) return false; + std::bitset tried; uint n = MAX_BRIDGES; byte bridge_type = RandomRange (n); @@ -1303,8 +1319,8 @@ static bool GrowTownWithBridge(const Town *t, const TileIndex tile, const DiagDi for (;;) { /* Can we actually build the bridge? */ RoadType rt = GetTownRoadType(t); - if (MayTownBuildBridgeType(bridge_type) && DoCommand(tile, bridge_tile, bridge_type | rt << 8 | TRANSPORT_ROAD << 15, CommandFlagsToDCFlags(GetCommandFlags(CMD_BUILD_BRIDGE)), CMD_BUILD_BRIDGE).Succeeded()) { - DoCommand(tile, bridge_tile, bridge_type | rt << 8 | TRANSPORT_ROAD << 15, DC_EXEC | CommandFlagsToDCFlags(GetCommandFlags(CMD_BUILD_BRIDGE)), CMD_BUILD_BRIDGE); + if (MayTownBuildBridgeType(bridge_type) && DoCommand(tile, bridge_tile, bridge_type | rt << 8 | TRANSPORT_ROAD << 15, CommandFlagsToDCFlags(GetCommandFlags(CMD_BUILD_BRIDGE)) | DC_TOWN, CMD_BUILD_BRIDGE).Succeeded()) { + DoCommand(tile, bridge_tile, bridge_type | rt << 8 | TRANSPORT_ROAD << 15, DC_EXEC | CommandFlagsToDCFlags(GetCommandFlags(CMD_BUILD_BRIDGE)) | DC_TOWN, CMD_BUILD_BRIDGE); _grow_town_result = GROWTH_SUCCEED; return true; } @@ -1389,6 +1405,7 @@ static void GrowTownInTile(TileIndex *tile_ptr, RoadBits cur_rb, DiagDirection t if (!_settings_game.economy.allow_town_roads && !_generating_world) return; if (!_settings_game.economy.allow_town_level_crossings && IsTileType(tile, MP_RAILWAY)) return; + if (!MayTownModifyRoad(tile)) return; /* Remove hills etc */ if (!_settings_game.construction.build_on_slopes || Chance16(1, 6)) LevelTownLand(tile); @@ -1791,9 +1808,9 @@ static bool GrowTown(Town *t) for (ptr = _town_coord_mod; ptr != endof(_town_coord_mod); ++ptr) { /* Only work with plain land that not already has a house */ if (!IsTileType(tile, MP_HOUSE) && IsTileFlat(tile)) { - if (DoCommand(tile, 0, 0, DC_AUTO | DC_NO_WATER, CMD_LANDSCAPE_CLEAR).Succeeded()) { + if (DoCommand(tile, 0, 0, DC_AUTO | DC_NO_WATER | DC_TOWN, CMD_LANDSCAPE_CLEAR).Succeeded()) { RoadType rt = GetTownRoadType(t); - DoCommand(tile, GenRandomRoadBits() | (rt << 4), t->index, DC_EXEC | DC_AUTO, CMD_BUILD_ROAD); + DoCommand(tile, GenRandomRoadBits() | (rt << 4), t->index, DC_EXEC | DC_AUTO | DC_TOWN, CMD_BUILD_ROAD); cur_company.Restore(); return true; } @@ -2366,7 +2383,7 @@ HouseZonesBits GetTownRadiusGroup(const Town *t, TileIndex tile) */ static inline void ClearMakeHouseTile(TileIndex tile, Town *t, byte counter, byte stage, HouseID type, byte random_bits) { - CommandCost cc = DoCommand(tile, 0, 0, DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_LANDSCAPE_CLEAR); + CommandCost cc = DoCommand(tile, 0, 0, DC_EXEC | DC_AUTO | DC_NO_WATER | DC_TOWN, CMD_LANDSCAPE_CLEAR); assert(cc.Succeeded()); @@ -2430,7 +2447,7 @@ static inline CommandCost CanBuildHouseHere(TileIndex tile, TownID town, bool no if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); /* can we clear the land? */ - CommandCost ret = DoCommand(tile, 0, 0, DC_AUTO | DC_NO_WATER, CMD_LANDSCAPE_CLEAR); + CommandCost ret = DoCommand(tile, 0, 0, DC_AUTO | DC_NO_WATER | DC_TOWN, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; /* do not try to build over house owned by another town */ @@ -3261,7 +3278,7 @@ static CommandCost TownActionRoadRebuild(Town *t, DoCommandFlag flags) static bool TryClearTile(TileIndex tile) { Backup cur_company(_current_company, OWNER_NONE, FILE_LINE); - CommandCost r = DoCommand(tile, 0, 0, DC_NONE, CMD_LANDSCAPE_CLEAR); + CommandCost r = DoCommand(tile, 0, 0, DC_TOWN, CMD_LANDSCAPE_CLEAR); cur_company.Restore(); return r.Succeeded(); } @@ -3333,7 +3350,7 @@ static CommandCost TownActionBuildStatue(Town *t, DoCommandFlag flags) if (flags & DC_EXEC) { Backup cur_company(_current_company, OWNER_NONE, FILE_LINE); - DoCommand(statue_data.best_position, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); + DoCommand(statue_data.best_position, 0, 0, DC_EXEC | DC_TOWN, CMD_LANDSCAPE_CLEAR); cur_company.Restore(); BuildObject(OBJECT_STATUE, statue_data.best_position, _current_company, t); SetBit(t->statues, _current_company); // Once found and built, "inform" the Town. diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp index fdd710a93b..86621f90fc 100644 --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -333,6 +333,8 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u TileIndex tile_start = p1; TileIndex tile_end = end_tile; + if ((flags & DC_TOWN) && !(MayTownModifyRoad(tile_start) && MayTownModifyRoad(tile_end))) return CMD_ERROR; + if (company == OWNER_DEITY) { if (transport_type != TRANSPORT_ROAD) return CMD_ERROR; const Town *town = CalcClosestTownFromTile(tile_start);