Allow overriding town settings on a per-town basis

Add a setting for whether this is allowed for non-privileged
multiplayer clients
This commit is contained in:
Jonathan G Rennison 2022-10-23 22:07:51 +01:00
parent e1defedb2a
commit a8361cd608
14 changed files with 331 additions and 30 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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[] = {

View File

@ -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 &&

View File

@ -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));

View File

@ -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. */

View File

@ -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

View File

@ -59,6 +59,14 @@ struct TownCache {
BuildingCounts<uint16> 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));

View File

@ -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 <typename Func>
static void ForAllStationsNearTown(Town *t, Func func)
{

View File

@ -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<NWidgetStacked>(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;
}

View File

@ -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. */