diff --git a/src/command.cpp b/src/command.cpp index 4262809124..b2ee33f4c8 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -155,6 +155,7 @@ CommandProc CmdFoundTown; CommandProc CmdRenameTown; CommandProc CmdRenameTownNonAdmin; CommandProc CmdDoTownAction; +CommandProc CmdOverrideTownSetting; CommandProc CmdTownGrowthRate; CommandProc CmdTownRating; CommandProc CmdTownCargoGoal; @@ -401,6 +402,7 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdRenameTown, CMD_DEITY | CMD_SERVER, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_TOWN DEF_CMD(CmdRenameTownNonAdmin, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_TOWN_NON_ADMIN DEF_CMD(CmdDoTownAction, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_DO_TOWN_ACTION + DEF_CMD(CmdOverrideTownSetting, 0, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_SETTING_OVERRIDE DEF_CMD(CmdTownCargoGoal, CMD_LOG_AUX | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_CARGO_GOAL DEF_CMD(CmdTownGrowthRate, CMD_LOG_AUX | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_GROWTH_RATE DEF_CMD(CmdTownRating, CMD_LOG_AUX | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_RATING diff --git a/src/command_type.h b/src/command_type.h index 5bfc1c7b19..c2c7e38076 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -347,6 +347,7 @@ enum Commands { CMD_RENAME_TOWN, ///< rename a town CMD_RENAME_TOWN_NON_ADMIN, ///< rename a town, non-admin command CMD_DO_TOWN_ACTION, ///< do a action from the town detail window (like advertises or bribe) + CMD_TOWN_SETTING_OVERRIDE, ///< override a town setting CMD_TOWN_CARGO_GOAL, ///< set the goal of a cargo for a town CMD_TOWN_GROWTH_RATE, ///< set the town growth rate CMD_TOWN_RATING, ///< set rating of a company in a town diff --git a/src/lang/english.txt b/src/lang/english.txt index 2620dc06a2..f805a169cc 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1332,6 +1332,8 @@ STR_CONFIG_SETTING_MONEY_CHEAT_MULTIPLAYER :Allow multiplay STR_CONFIG_SETTING_MONEY_CHEAT_MULTIPLAYER_HELPTEXT :If enabled, non-admin multiplayer clients can use the money cheat. The money cheat is always available in single-player mode, and to the multiplayer server admin. STR_CONFIG_SETTING_RENAME_TOWNS_MULTIPLAYER :Allow multiplayer clients to rename towns: {STRING2} STR_CONFIG_SETTING_RENAME_TOWNS_MULTIPLAYER_HELPTEXT :If enabled, non-admin multiplayer clients which are not spectating can rename towns. Renaming towns is always available in single-player mode, and to the multiplayer server admin. +STR_CONFIG_SETTING_OVERRIDE_TOWN_SETTINGS_MULTIPLAYER :Allow multiplayer clients to override town settings: {STRING2} +STR_CONFIG_SETTING_OVERRIDE_TOWN_SETTINGS_MULTIPLAYER_HELPTEXT :If enabled, non-admin multiplayer clients which are not spectating can override town settings, on a per-town basis. Overriding individual town settings is always available in single-player mode, and to the multiplayer server admin. STR_CONFIG_SETTING_MAP_HEIGHT_LIMIT :Map height limit: {STRING2} STR_CONFIG_SETTING_MAP_HEIGHT_LIMIT_HELPTEXT :Set the maximum height of the map terrain. With "(auto)" a good value will be picked after terrain generation @@ -4478,6 +4480,17 @@ STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_NEW_BUILDINGS :{YELLOW}Fund th STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_EXCLUSIVE_TRANSPORT :{YELLOW}Buy 1 year's exclusive transport rights in town.{}Town authority will not allow passengers and cargo to use your competitors' stations.{}Cost: {CURRENCY_LONG} STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_BRIBE :{YELLOW}Bribe the local authority to increase your rating, at the risk of a severe penalty if caught.{}Cost: {CURRENCY_LONG} +###length 4 +STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_ALLOW_ROADS :Allowed to build roads +STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_ALLOW_LEVEL_CROSSINGS :Allowed to level crossings +STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_TUNNELS :Allowed to build tunnels +STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_MAX_ROAD_SLOPE :Limit building continuous inclined roads + +STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_DEFAULT :Default ({STRING1}) +STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_STR :{STRING}: {PUSH_COLOUR}{YELLOW}{STRING2}{POP_COLOUR} +STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_TEXT :{YELLOW}Override the following setting for this individual town:{}{STRING}{}{STRING} +STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_TOOLTIP :{BLACK}Change setting override + # Goal window STR_GOALS_CAPTION :{WHITE}{COMPANY} Goals STR_GOALS_SPECTATOR_CAPTION :{WHITE}Global Goals diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index f0f65bd207..dc50be408e 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -175,6 +175,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_INDUSTRY_ANIM_MASK, XSCF_IGNORABLE_ALL, 1, 1, "industry_anim_mask", nullptr, nullptr, nullptr }, { XSLFI_NEW_SIGNAL_STYLES, XSCF_NULL, 2, 2, "new_signal_styles", nullptr, nullptr, "XBST,NSID" }, { XSLFI_NO_TREE_COUNTER, XSCF_IGNORABLE_ALL, 1, 1, "no_tree_counter", nullptr, nullptr, nullptr }, + { XSLFI_TOWN_SETTING_OVERRIDE, XSCF_NULL, 1, 1, "town_setting_override", nullptr, nullptr, nullptr }, { XSLFI_SCRIPT_INT64, XSCF_NULL, 1, 1, "script_int64", nullptr, nullptr, nullptr }, { XSLFI_U64_TICK_COUNTER, XSCF_NULL, 1, 1, "u64_tick_counter", nullptr, nullptr, nullptr }, { XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index 0617e8b60e..dd3739b777 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -128,6 +128,7 @@ enum SlXvFeatureIndex { XSLFI_INDUSTRY_ANIM_MASK, ///< Industry tile animation masking XSLFI_NEW_SIGNAL_STYLES, ///< New signal styles XSLFI_NO_TREE_COUNTER, ///< No tree counter + XSLFI_TOWN_SETTING_OVERRIDE, ///< Town setting overrides XSLFI_SCRIPT_INT64, ///< See: SLV_SCRIPT_INT64 XSLFI_U64_TICK_COUNTER, ///< See: SLV_U64_TICK_COUNTER diff --git a/src/saveload/town_sl.cpp b/src/saveload/town_sl.cpp index 6a50e5f0c9..aa0f25906a 100644 --- a/src/saveload/town_sl.cpp +++ b/src/saveload/town_sl.cpp @@ -243,6 +243,11 @@ static const SaveLoad _town_desc[] = { SLE_CONDNULL(4, SLV_166, SLV_EXTEND_CARGOTYPES), ///< cargo_produced, no longer in use SLE_CONDNULL(8, SLV_EXTEND_CARGOTYPES, SLV_REMOVE_TOWN_CARGO_CACHE), ///< cargo_produced, no longer in use SLE_CONDNULL(30, SLV_2, SLV_REMOVE_TOWN_CARGO_CACHE), ///< old reserved space + + SLE_CONDVAR_X(Town, override_flags, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_TOWN_SETTING_OVERRIDE)), + SLE_CONDVAR_X(Town, override_values, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_TOWN_SETTING_OVERRIDE)), + SLE_CONDVAR_X(Town, build_tunnels, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_TOWN_SETTING_OVERRIDE)), + SLE_CONDVAR_X(Town, max_road_slope, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_TOWN_SETTING_OVERRIDE)), }; static const SaveLoad _town_supplied_desc[] = { diff --git a/src/settings.cpp b/src/settings.cpp index 2be4033a7b..2aefc6af7b 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1465,6 +1465,11 @@ static void DifficultyRenameTownsMultiplayerChange(int32 new_value) SetWindowClassesDirty(WC_TOWN_VIEW); } +static void DifficultyOverrideTownSettingsMultiplayerChange(int32 new_value) +{ + SetWindowClassesDirty(WC_TOWN_AUTHORITY); +} + static void MaxNoAIsChange(int32 new_value) { if (GetGameSettings().difficulty.max_no_competitors != 0 && diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 54faf8e3bf..d822258e6a 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2283,6 +2283,7 @@ static SettingsContainer &GetSettingsTree() ai->Add(new SettingEntry("economy.min_years_for_shares")); ai->Add(new SettingEntry("difficulty.money_cheat_in_multiplayer")); ai->Add(new SettingEntry("difficulty.rename_towns_in_multiplayer")); + ai->Add(new SettingEntry("difficulty.override_town_settings_in_multiplayer")); } SettingsPage *scenario = main->Add(new SettingsPage(STR_CONFIG_SETTING_SCENARIO_EDITOR)); diff --git a/src/settings_type.h b/src/settings_type.h index b4fa82c64e..6cf5e6e966 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -97,6 +97,7 @@ struct DifficultySettings { byte town_council_tolerance; ///< minimum required town ratings to be allowed to demolish stuff bool money_cheat_in_multiplayer; ///< is the money cheat permitted for non-admin multiplayer clients bool rename_towns_in_multiplayer; ///< is renaming towns permitted for non-admin multiplayer clients + bool override_town_settings_in_multiplayer; ///< is overriding town settings permitted for non-admin multiplayer clients }; /** Settings relating to viewport/smallmap scrolling. */ diff --git a/src/table/settings/settings.ini b/src/table/settings/settings.ini index 3b17f30aaf..a2f489b180 100644 --- a/src/table/settings/settings.ini +++ b/src/table/settings/settings.ini @@ -27,6 +27,7 @@ static bool TownCouncilToleranceAdjust(int32 &new_value); static void DifficultyNoiseChange(int32 new_value); static void DifficultyMoneyCheatMultiplayerChange(int32 new_value); static void DifficultyRenameTownsMultiplayerChange(int32 new_value); +static void DifficultyOverrideTownSettingsMultiplayerChange(int32 new_value); static void MaxNoAIsChange(int32 new_value); static bool CheckRoadSide(int32 &new_value); static void RoadSideChanged(int32 new_value); @@ -476,6 +477,15 @@ post_cb = DifficultyRenameTownsMultiplayerChange cat = SC_EXPERT patxname = ""cheat.difficulty.rename_towns_in_multiplayer"" +[SDT_BOOL] +var = difficulty.override_town_settings_in_multiplayer +def = false +str = STR_CONFIG_SETTING_OVERRIDE_TOWN_SETTINGS_MULTIPLAYER +strhelp = STR_CONFIG_SETTING_OVERRIDE_TOWN_SETTINGS_MULTIPLAYER_HELPTEXT +post_cb = DifficultyOverrideTownSettingsMultiplayerChange +cat = SC_EXPERT +patxname = ""cheat.difficulty.override_town_settings_in_multiplayer"" + [SDTG_VAR] name = ""diff_level"" var = _old_diff_level diff --git a/src/town.h b/src/town.h index 4d0fe7da69..47fcc5dcef 100644 --- a/src/town.h +++ b/src/town.h @@ -59,6 +59,14 @@ struct TownCache { BuildingCounts building_counts; ///< The number of each type of building in the town }; +/** Town setting override flags */ +enum TownSettingOverrideFlags { + TSOF_OVERRIDE_BUILD_ROADS = 0, + TSOF_OVERRIDE_BUILD_LEVEL_CROSSINGS = 1, + TSOF_OVERRIDE_BUILD_TUNNELS = 2, + TSOF_OVERRIDE_BUILD_INCLINED_ROADS = 3, +}; + /** Town data structure. */ struct Town : TownPool::PoolItem<&_town_pool> { TileIndex xy; ///< town center tile @@ -73,6 +81,12 @@ struct Town : TownPool::PoolItem<&_town_pool> { mutable std::string cached_name; ///< NOSAVE: Cache of the resolved name of the town, if not using a custom town name byte flags; ///< See #TownFlags. + + byte override_flags; ///< Bitmask of enabled flag overrides. See #TownSettingOverrideFlags. + byte override_values; ///< Bitmask of flag override values. See #TownSettingOverrideFlags. + TownTunnelMode build_tunnels; ///< If/when towns are allowed to build road tunnels (if TSOF_OVERRIDE_BUILD_TUNNELS set in override_flags) + uint8 max_road_slope; ///< Maximum number of consecutive sloped road tiles which towns are allowed to build (if TSOF_OVERRIDE_BUILD_INCLINED_ROADS set in override_flags) + uint16 church_count; ///< Number of church buildings in the town. uint16 stadium_count; ///< Number of stadium buildings in the town. @@ -173,6 +187,26 @@ struct Town : TownPool::PoolItem<&_town_pool> { return this->cached_name.c_str(); } + inline bool GetAllowBuildRoads() const + { + return HasBit(this->override_flags, TSOF_OVERRIDE_BUILD_ROADS) ? HasBit(this->override_values, TSOF_OVERRIDE_BUILD_ROADS) : _settings_game.economy.allow_town_roads; + } + + inline bool GetAllowBuildLevelCrossings() const + { + return HasBit(this->override_flags, TSOF_OVERRIDE_BUILD_LEVEL_CROSSINGS) ? HasBit(this->override_values, TSOF_OVERRIDE_BUILD_LEVEL_CROSSINGS) : _settings_game.economy.allow_town_level_crossings; + } + + inline TownTunnelMode GetBuildTunnelMode() const + { + return HasBit(this->override_flags, TSOF_OVERRIDE_BUILD_TUNNELS) ? this->build_tunnels : _settings_game.economy.town_build_tunnels; + } + + inline uint8 GetBuildMaxRoadSlope() const + { + return HasBit(this->override_flags, TSOF_OVERRIDE_BUILD_INCLINED_ROADS) ? this->max_road_slope : _settings_game.economy.town_max_road_slope; + } + static inline Town *GetByTile(TileIndex tile) { return Town::Get(GetTownIndex(tile)); diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index abc784cb7e..ad2950e9ec 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -67,7 +67,7 @@ static Rect _record_house_rect; TownPool _town_pool("Town"); INSTANTIATE_POOL_METHODS(Town) -static bool CanFollowRoad(TileIndex tile, DiagDirection dir); +static bool CanFollowRoad(const Town *t, TileIndex tile, DiagDirection dir); TownKdtree _town_kdtree(&Kdtree_TownXYFunc); @@ -1311,7 +1311,7 @@ static bool CanRoadContinueIntoNextTile(const Town *t, const TileIndex tile, con /* If the next tile is a railroad track, check if towns are allowed to build level crossings. * If level crossing are not allowed, reject the construction. Else allow DoCommand to determine if the rail track is buildable. */ - if (IsTileType(next_tile, MP_RAILWAY) && !_settings_game.economy.allow_town_level_crossings) return false; + if (IsTileType(next_tile, MP_RAILWAY) && !t->GetAllowBuildLevelCrossings()) return false; /* If a road tile can be built, the construction is allowed. */ return DoCommand(next_tile, rcmd | (rt << 4), t->index, DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD).Succeeded(); @@ -1452,7 +1452,8 @@ static bool GrowTownWithTunnel(const Town *t, const TileIndex tile, const DiagDi { assert(tunnel_dir < DIAGDIR_END); - if (_settings_game.economy.town_build_tunnels == TTM_FORBIDDEN) return false; + const TownTunnelMode tunnel_mode = t->GetBuildTunnelMode(); + if (tunnel_mode == TTM_FORBIDDEN) return false; Slope slope = GetTileSlope(tile); @@ -1467,7 +1468,7 @@ static bool GrowTownWithTunnel(const Town *t, const TileIndex tile, const DiagDi /* There are two conditions for building tunnels: Under a mountain and under an obstruction. */ if (CanRoadContinueIntoNextTile(t, tile, tunnel_dir)) { - if (_settings_game.economy.town_build_tunnels != TTM_ALLOWED) return false; + if (tunnel_mode != TTM_ALLOWED) return false; /* Only tunnel under a mountain if the slope is continuous for at least 4 tiles. We want tunneling to be a last resort for large hills. */ TileIndex slope_tile = tile; @@ -1571,8 +1572,8 @@ static void GrowTownInTile(TileIndex *tile_ptr, RoadBits cur_rb, DiagDirection t * to say that this is the last iteration. */ _grow_town_result = GROWTH_SEARCH_STOPPED; - if (!_settings_game.economy.allow_town_roads && !_generating_world) return; - if (!_settings_game.economy.allow_town_level_crossings && IsTileType(tile, MP_RAILWAY)) return; + if (!t1->GetAllowBuildRoads() && !_generating_world) return; + if (!t1->GetAllowBuildLevelCrossings() && IsTileType(tile, MP_RAILWAY)) return; if (!MayTownModifyRoad(tile)) return; /* Remove hills etc */ @@ -1618,7 +1619,8 @@ static void GrowTownInTile(TileIndex *tile_ptr, RoadBits cur_rb, DiagDirection t break; } - if (_settings_game.economy.town_max_road_slope > 0 && ((rcmd == ROAD_X) || (rcmd == ROAD_Y))) { + const uint8 max_road_slope = t1->GetBuildMaxRoadSlope(); + if (max_road_slope > 0 && ((rcmd == ROAD_X) || (rcmd == ROAD_Y))) { /* Limit consecutive sloped road tiles */ auto get_road_slope = [rcmd](TileIndex t) -> Slope { @@ -1633,7 +1635,7 @@ static void GrowTownInTile(TileIndex *tile_ptr, RoadBits cur_rb, DiagDirection t const int delta = TileOffsByDiagDir(ReverseDiagDir(target_dir)); bool ok = false; TileIndex t = tile; - for (uint i = 0; i < _settings_game.economy.town_max_road_slope; i++) { + for (uint i = 0; i < max_road_slope; i++) { t += delta; if (!IsValidTile(t) || !IsNormalRoadTile(t) || GetRoadBits(t, RTT_ROAD) != rcmd || get_road_slope(t) != slope) { ok = true; @@ -1653,7 +1655,7 @@ static void GrowTownInTile(TileIndex *tile_ptr, RoadBits cur_rb, DiagDirection t * the fitting RoadBits */ _grow_town_result = GROWTH_SEARCH_STOPPED; - if (!_settings_game.economy.allow_town_roads && !_generating_world) return; + if (!t1->GetAllowBuildRoads() && !_generating_world) return; switch (t1->layout) { default: NOT_REACHED(); @@ -1691,7 +1693,7 @@ static void GrowTownInTile(TileIndex *tile_ptr, RoadBits cur_rb, DiagDirection t target_bits = DiagDirToRoadBits(target_dir); } while (!(cur_rb & target_bits)); cur_rb &= ~target_bits; - } while (!(target_dir == GetTunnelBridgeDirection(tile) || CanFollowRoad(tile, target_dir))); + } while (!(target_dir == GetTunnelBridgeDirection(tile) || CanFollowRoad(t1, tile, target_dir))); if (target_dir == GetTunnelBridgeDirection(tile)) { /* cross the bridge */ *tile_ptr = GetOtherTunnelBridgeEnd(tile); @@ -1746,7 +1748,7 @@ static void GrowTownInTile(TileIndex *tile_ptr, RoadBits cur_rb, DiagDirection t if (!IsValidTile(house_tile)) return; - if (target_dir != DIAGDIR_END && (_settings_game.economy.allow_town_roads || _generating_world)) { + if (target_dir != DIAGDIR_END && (t1->GetAllowBuildRoads() || _generating_world)) { switch (t1->layout) { default: NOT_REACHED(); @@ -1811,18 +1813,19 @@ static void GrowTownInTile(TileIndex *tile_ptr, RoadBits cur_rb, DiagDirection t /** * Checks whether a road can be followed or is a dead end, that can not be extended to the next tile. * This only checks trivial but often cases. + * @param t Town doing the following * @param tile Start tile for road. * @param dir Direction for road to follow or build. * @return true If road is or can be connected in the specified direction. */ -static bool CanFollowRoad(TileIndex tile, DiagDirection dir) +static bool CanFollowRoad(const Town *t, TileIndex tile, DiagDirection dir) { TileIndex target_tile = tile + TileOffsByDiagDir(dir); if (!IsValidTile(target_tile)) return false; if (HasTileWaterGround(target_tile)) return false; RoadBits target_rb = GetTownRoadBits(target_tile); - if (_settings_game.economy.allow_town_roads || _generating_world) { + if (t->GetAllowBuildRoads() || _generating_world) { /* Check whether a road connection exists or can be build. */ switch (GetTileType(target_tile)) { case MP_ROAD: @@ -1922,7 +1925,7 @@ static bool GrowTownAtRoad(Town *t, TileIndex tile) target_bits = DiagDirToRoadBits(target_dir); } while (!(cur_rb & target_bits)); cur_rb &= ~target_bits; - } while (!CanFollowRoad(tile, target_dir)); + } while (!CanFollowRoad(t, tile, target_dir)); } tile = TileAddByDiagDir(tile, target_dir); @@ -2001,7 +2004,7 @@ static bool GrowTown(Town *t) /* No road available, try to build a random road block by * clearing some land and then building a road there. */ - if (_settings_game.economy.allow_town_roads || _generating_world) { + if (t->GetAllowBuildRoads() || _generating_world) { tile = t->xy; for (ptr = _town_coord_mod; ptr != endof(_town_coord_mod); ++ptr) { /* Only work with plain land that not already has a house */ @@ -2789,7 +2792,7 @@ static inline CommandCost IsAnotherHouseTypeAllowedInTown(Town *t, HouseID house static inline bool TownLayoutAllowsHouseHere(Town *t, const TileArea &ta) { /* Allow towns everywhere when we don't build roads */ - if (!_settings_game.economy.allow_town_roads && !_generating_world) return true; + if (!t->GetAllowBuildRoads() && !_generating_world) return true; TileIndexDiffC grid_pos = TileIndexToTileIndexDiffC(t->xy, ta.tile); @@ -3792,6 +3795,67 @@ CommandCost CmdDoTownAction(TileIndex tile, DoCommandFlag flags, uint32 p1, uint return cost; } +/** + * Override a town setting + * @param tile unused + * @param flags type of operation + * @param p1 town to do the action at + * @param p2 various bitstuffed elements + * - p2 = (bit 0 - 7) - what setting to change + * - p2 = (bit 8 - 15) - the data to modify + * - p2 = (bit 16) - whether to override the value, or use the default + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdOverrideTownSetting(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + if (_networking && !_settings_game.difficulty.override_town_settings_in_multiplayer) return CMD_ERROR; + + Town *t = Town::GetIfValid(p1); + if (t == nullptr) return CMD_ERROR; + + const uint8 setting = GB(p2, 0, 8); + const bool is_override = HasBit(p2, 16); + const uint8 value = GB(p2, 8, 8); + switch (setting) { + case TSOF_OVERRIDE_BUILD_ROADS: + case TSOF_OVERRIDE_BUILD_LEVEL_CROSSINGS: + if (is_override && value != 0 && value != 1) return CMD_ERROR; + break; + case TSOF_OVERRIDE_BUILD_TUNNELS: + if (is_override && value >= TTM_END) return CMD_ERROR; + break; + case TSOF_OVERRIDE_BUILD_INCLINED_ROADS: + if (is_override && value > 8) return CMD_ERROR; + break; + default: + return CMD_ERROR; + } + + if (flags & DC_EXEC) { + SB(t->override_flags, setting, 1, is_override ? 1 : 0); + if (is_override) { + switch (setting) { + case TSOF_OVERRIDE_BUILD_ROADS: + case TSOF_OVERRIDE_BUILD_LEVEL_CROSSINGS: + SB(t->override_values, setting, 1, value & 1); + break; + case TSOF_OVERRIDE_BUILD_TUNNELS: + t->build_tunnels = (TownTunnelMode)value; + break; + case TSOF_OVERRIDE_BUILD_INCLINED_ROADS: + t->max_road_slope = value; + break; + default: + NOT_REACHED(); + } + } + SetWindowDirty(WC_TOWN_AUTHORITY, p1); + } + + return CommandCost(); +} + template static void ForAllStationsNearTown(Town *t, Func func) { diff --git a/src/town_gui.cpp b/src/town_gui.cpp index 2bae45c0b2..3129d5a515 100644 --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -68,7 +68,10 @@ static const NWidgetPart _nested_town_authority_widgets[] = { EndContainer(), NWidget(WWT_PANEL, COLOUR_BROWN, WID_TA_ACTION_INFO), SetMinimalSize(317, 52), SetResize(1, 0), EndContainer(), NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_TA_EXECUTE), SetMinimalSize(317, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_LOCAL_AUTHORITY_DO_IT_BUTTON, STR_LOCAL_AUTHORITY_DO_IT_TOOLTIP), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_TA_BTN_SEL), + NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_TA_EXECUTE), SetMinimalSize(317, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_LOCAL_AUTHORITY_DO_IT_BUTTON, STR_LOCAL_AUTHORITY_DO_IT_TOOLTIP), + NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_TA_SETTING), SetMinimalSize(317, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BLACK_STRING1, STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_TOOLTIP), + EndContainer(), NWidget(WWT_RESIZEBOX, COLOUR_BROWN), EndContainer() }; @@ -101,6 +104,14 @@ private: return -1; } + static bool ChangeSettingsDisabled() + { + return _networking && !(_network_server || _network_settings_access) && + !(_local_company != COMPANY_SPECTATOR && _settings_game.difficulty.override_town_settings_in_multiplayer); + } + + static const uint SETTING_OVERRIDE_COUNT = 4; + public: TownAuthorityWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc), sel_index(-1), displayed_actions_on_previous_painting(0) { @@ -114,17 +125,20 @@ public: { int numact; uint buttons = GetMaskOfTownActions(&numact, _local_company, this->town); + numact += SETTING_OVERRIDE_COUNT; if (buttons != displayed_actions_on_previous_painting) this->SetDirty(); displayed_actions_on_previous_painting = buttons; this->vscroll->SetCount(numact + 1); - if (this->sel_index != -1 && !HasBit(buttons, this->sel_index)) { + if (this->sel_index != -1 && this->sel_index < 0x100 && !HasBit(buttons, this->sel_index)) { this->sel_index = -1; } this->SetWidgetLoweredState(WID_TA_ZONE_BUTTON, this->town->show_zone); - this->SetWidgetDisabledState(WID_TA_EXECUTE, this->sel_index == -1); + this->SetWidgetDisabledState(WID_TA_EXECUTE, this->sel_index == -1 || this->sel_index >= 0x100); + this->SetWidgetDisabledState(WID_TA_SETTING, ChangeSettingsDisabled()); + this->GetWidget(WID_TA_BTN_SEL)->SetDisplayedPlane(this->sel_index >= 0x100 ? 1 : 0); this->DrawWidgets(); if (!this->IsShaded()) this->DrawRatings(); @@ -193,7 +207,31 @@ public: void SetStringParameters(int widget) const override { - if (widget == WID_TA_CAPTION) SetDParam(0, this->window_number); + if (widget == WID_TA_CAPTION) { + SetDParam(0, this->window_number); + } else if (widget == WID_TA_SETTING) { + SetDParam(0, STR_EMPTY); + if (this->sel_index >= 0x100 && this->sel_index < (int)(0x100 + SETTING_OVERRIDE_COUNT)) { + if (!HasBit(this->town->override_flags, this->sel_index - 0x100)) { + SetDParam(0, STR_COLOUR_DEFAULT); + } else { + int idx = this->sel_index - 0x100; + switch (idx) { + case TSOF_OVERRIDE_BUILD_ROADS: + case TSOF_OVERRIDE_BUILD_LEVEL_CROSSINGS: + SetDParam(0, HasBit(this->town->override_values, idx) ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); + break; + case TSOF_OVERRIDE_BUILD_TUNNELS: + SetDParam(0, STR_CONFIG_SETTING_TOWN_TUNNELS_FORBIDDEN + this->town->build_tunnels); + break; + case TSOF_OVERRIDE_BUILD_INCLINED_ROADS: + SetDParam(0, STR_CONFIG_SETTING_TOWN_MAX_ROAD_SLOPE_VALUE + ((this->town->max_road_slope == 0) ? 1 : 0)); + SetDParam(1, this->town->max_road_slope); + break; + } + } + } + } } void DrawWidget(const Rect &r, int widget) const override @@ -201,14 +239,36 @@ public: switch (widget) { case WID_TA_ACTION_INFO: if (this->sel_index != -1) { - SetDParam(0, _price[PR_TOWN_ACTION] * _town_action_costs[this->sel_index] >> 8); - DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, - STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_SMALL_ADVERTISING + this->sel_index); + StringID text = STR_NULL; + if (this->sel_index >= 0x100) { + SetDParam(1, STR_EMPTY); + switch (this->sel_index - 0x100) { + case TSOF_OVERRIDE_BUILD_ROADS: + SetDParam(1, STR_CONFIG_SETTING_ALLOW_TOWN_ROADS_HELPTEXT); + break; + case TSOF_OVERRIDE_BUILD_LEVEL_CROSSINGS: + SetDParam(1, STR_CONFIG_SETTING_ALLOW_TOWN_LEVEL_CROSSINGS_HELPTEXT); + break; + case TSOF_OVERRIDE_BUILD_TUNNELS: + SetDParam(1, STR_CONFIG_SETTING_TOWN_TUNNELS_HELPTEXT); + break; + case TSOF_OVERRIDE_BUILD_INCLINED_ROADS: + SetDParam(1, STR_CONFIG_SETTING_TOWN_MAX_ROAD_SLOPE_HELPTEXT); + break; + } + text = STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_TEXT; + SetDParam(0, STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_ALLOW_ROADS + this->sel_index - 0x100); + } else { + text = STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_SMALL_ADVERTISING + this->sel_index; + SetDParam(0, _price[PR_TOWN_ACTION] * _town_action_costs[this->sel_index] >> 8); + } + DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, text); } break; case WID_TA_COMMAND_LIST: { int numact; uint buttons = GetMaskOfTownActions(&numact, _local_company, this->town); + numact += SETTING_OVERRIDE_COUNT; int y = r.top + WD_FRAMERECT_TOP; int pos = this->vscroll->GetPosition(); @@ -218,14 +278,47 @@ public: } for (int i = 0; buttons; i++, buttons >>= 1) { - if (pos <= -5) break; ///< Draw only the 5 fitting lines - if ((buttons & 1) && --pos < 0) { DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_LOCAL_AUTHORITY_ACTION_SMALL_ADVERTISING_CAMPAIGN + i, this->sel_index == i ? TC_WHITE : TC_ORANGE); y += FONT_HEIGHT_NORMAL; } } + for (int i = 0; i < (int)SETTING_OVERRIDE_COUNT; i++) { + if (--pos < 0) { + const bool disabled = ChangeSettingsDisabled(); + const bool selected = (this->sel_index == (0x100 + i)); + const TextColour tc = disabled ? (TC_NO_SHADE | (selected ? TC_SILVER : TC_GREY)) : (selected ? TC_WHITE : TC_ORANGE); + const bool overriden = HasBit(this->town->override_flags, i); + SetDParam(0, STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_ALLOW_ROADS + i); + SetDParam(1, overriden ? STR_JUST_STRING1 : STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_DEFAULT); + switch (i) { + case TSOF_OVERRIDE_BUILD_ROADS: + SetDParam(2, this->town->GetAllowBuildRoads() ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); + break; + + case TSOF_OVERRIDE_BUILD_LEVEL_CROSSINGS: + SetDParam(2, this->town->GetAllowBuildLevelCrossings() ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); + break; + + case TSOF_OVERRIDE_BUILD_TUNNELS: { + TownTunnelMode tunnel_mode = this->town->GetBuildTunnelMode(); + SetDParam(2, STR_CONFIG_SETTING_TOWN_TUNNELS_FORBIDDEN + tunnel_mode); + break; + } + + case TSOF_OVERRIDE_BUILD_INCLINED_ROADS: { + uint8 max_slope = this->town->GetBuildMaxRoadSlope(); + SetDParam(2, STR_CONFIG_SETTING_TOWN_MAX_ROAD_SLOPE_VALUE + ((max_slope == 0) ? 1 : 0)); + SetDParam(3, max_slope); + break; + } + } + DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, + STR_LOCAL_AUTHORITY_SETTING_OVERRIDE_STR, tc); + y += FONT_HEIGHT_NORMAL; + } + } break; } } @@ -250,7 +343,7 @@ public: } case WID_TA_COMMAND_LIST: - size->height = WD_FRAMERECT_TOP + 5 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM; + size->height = WD_FRAMERECT_TOP + (5 + SETTING_OVERRIDE_COUNT) * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM; size->width = GetStringBoundingBox(STR_LOCAL_AUTHORITY_ACTIONS_TITLE).width; for (uint i = 0; i < TACT_COUNT; i++ ) { size->width = std::max(size->width, GetStringBoundingBox(STR_LOCAL_AUTHORITY_ACTION_SMALL_ADVERTISING_CAMPAIGN + i).width); @@ -283,10 +376,16 @@ public: case WID_TA_COMMAND_LIST: { int y = this->GetRowFromWidget(pt.y, WID_TA_COMMAND_LIST, 1, FONT_HEIGHT_NORMAL); - if (!IsInsideMM(y, 0, 5)) return; + if (!IsInsideMM(y, 0, 5 + SETTING_OVERRIDE_COUNT)) return; - y = GetNthSetBit(GetMaskOfTownActions(nullptr, _local_company, this->town), y + this->vscroll->GetPosition() - 1); - if (y >= 0) { + const uint setting_override_offset = 32 - SETTING_OVERRIDE_COUNT; + + y = GetNthSetBit(GetMaskOfTownActions(nullptr, _local_company, this->town) | (UINT32_MAX << setting_override_offset), y + this->vscroll->GetPosition() - 1); + if (y >= (int)setting_override_offset) { + this->sel_index = y + 0x100 - setting_override_offset; + this->SetDirty(); + break; + } else if (y >= 0) { this->sel_index = y; this->SetDirty(); } @@ -298,9 +397,71 @@ public: case WID_TA_EXECUTE: DoCommandP(this->town->xy, this->window_number, this->sel_index, CMD_DO_TOWN_ACTION | CMD_MSG(STR_ERROR_CAN_T_DO_THIS)); break; + + case WID_TA_SETTING: { + uint8 idx = this->sel_index - 0x100; + switch (idx) { + case TSOF_OVERRIDE_BUILD_ROADS: + case TSOF_OVERRIDE_BUILD_LEVEL_CROSSINGS: { + int value = HasBit(this->town->override_flags, idx) ? (HasBit(this->town->override_values, idx) ? 2 : 1) : 0; + const StringID names[] = { + STR_COLOUR_DEFAULT, + STR_CONFIG_SETTING_OFF, + STR_CONFIG_SETTING_ON, + INVALID_STRING_ID + }; + ShowDropDownMenu(this, names, value, WID_TA_SETTING, 0, 0); + break; + } + case TSOF_OVERRIDE_BUILD_TUNNELS: { + const StringID names[] = { + STR_COLOUR_DEFAULT, + STR_CONFIG_SETTING_TOWN_TUNNELS_FORBIDDEN, + STR_CONFIG_SETTING_TOWN_TUNNELS_ALLOWED_OBSTRUCTION, + STR_CONFIG_SETTING_TOWN_TUNNELS_ALLOWED, + INVALID_STRING_ID + }; + ShowDropDownMenu(this, names, HasBit(this->town->override_flags, idx) ? this->town->build_tunnels + 1 : 0, WID_TA_SETTING, 0, 0); + break; + } + case TSOF_OVERRIDE_BUILD_INCLINED_ROADS: + DropDownList dlist; + dlist.emplace_back(new DropDownListStringItem(STR_COLOUR_DEFAULT, 0, false)); + dlist.emplace_back(new DropDownListStringItem(STR_CONFIG_SETTING_TOWN_MAX_ROAD_SLOPE_ZERO, 1, false)); + for (int i = 1; i <= 8; i++) { + DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_CONFIG_SETTING_TOWN_MAX_ROAD_SLOPE_VALUE, i + 1, false); + item->SetParam(0, i); + dlist.emplace_back(item); + } + ShowDropDownList(this, std::move(dlist), HasBit(this->town->override_flags, idx) ? this->town->max_road_slope + 1 : 0, WID_TA_SETTING); + break; + } + break; + } } } + + virtual void OnDropdownSelect(int widget, int index) override + { + switch (widget) { + case WID_TA_SETTING: { + if (index < 0) break; + uint32 p2 = this->sel_index - 0x100; + if (index > 0) { + SetBit(p2, 16); + p2 |= (index - 1) << 8; + } + DoCommandP(this->town->xy, this->window_number, p2, CMD_TOWN_SETTING_OVERRIDE | CMD_MSG(STR_ERROR_CAN_T_DO_THIS)); + break; + } + + default: NOT_REACHED(); + } + + this->SetDirty(); + } + void OnHundredthTick() override { this->SetDirty(); @@ -477,7 +638,7 @@ public: /* Warn the user if towns are not allowed to build roads, but do this only once per OpenTTD run. */ static bool _warn_town_no_roads = false; - if (!_settings_game.economy.allow_town_roads && !_warn_town_no_roads) { + if (!Town::Get(this->window_number)->GetAllowBuildRoads() && !_warn_town_no_roads) { ShowErrorMessage(STR_ERROR_TOWN_EXPAND_WARN_NO_ROADS, INVALID_STRING_ID, WL_WARNING); _warn_town_no_roads = true; } diff --git a/src/widgets/town_widget.h b/src/widgets/town_widget.h index 6ee9803eac..3a8f1e3b6d 100644 --- a/src/widgets/town_widget.h +++ b/src/widgets/town_widget.h @@ -29,6 +29,8 @@ enum TownAuthorityWidgets { WID_TA_SCROLLBAR, ///< Scrollbar of the list of commands. WID_TA_ACTION_INFO, ///< Additional information about the action. WID_TA_EXECUTE, ///< Do-it button. + WID_TA_SETTING, ///< Setting drop-down. + WID_TA_BTN_SEL, ///< Button selector. }; /** Widgets of the #TownViewWindow class. */