1
0
mirror of https://github.com/OpenTTD/OpenTTD synced 2026-01-19 02:12:37 +01:00

Feature: Allow placing an area of 1x1 houses (#14708)

This commit is contained in:
Tyler Trahan
2026-01-08 15:43:50 -05:00
committed by GitHub
parent e97213e7e2
commit 09a8b1cf5c
6 changed files with 81 additions and 2 deletions

View File

@@ -299,6 +299,7 @@ enum Commands : uint8_t {
CMD_EXPAND_TOWN, ///< expand a town
CMD_DELETE_TOWN, ///< delete a town
CMD_PLACE_HOUSE, ///< place a house
CMD_PLACE_HOUSE_AREA, ///< place an area of houses
CMD_ORDER_REFIT, ///< change the refit information of an order (for "goto depot" )
CMD_CLONE_ORDER, ///< clone (and share) an order

View File

@@ -105,7 +105,7 @@ struct CompanyProperties {
uint32_t terraform_limit = 0; ///< Amount of tileheights we can (still) terraform (times 65536).
uint32_t clear_limit = 0; ///< Amount of tiles we can (still) clear (times 65536).
uint32_t tree_limit = 0; ///< Amount of trees we can (still) plant (times 65536).
uint32_t build_object_limit = 0; ///< Amount of tiles we can (still) build objects on (times 65536). Also applies to buying land.
uint32_t build_object_limit = 0; ///< Amount of tiles we can (still) build objects on (times 65536). Also applies to buying land and placing houses.
/**
* If \c true, the company is (also) controlled by the computer (a NoAI program).

View File

@@ -3000,6 +3000,59 @@ CommandCost CmdPlaceHouse(DoCommandFlags flags, TileIndex tile, HouseID house, b
return CommandCost();
}
/**
* Construct multiple houses in an area
* @param flags Type of operation.
* @param tile End tile of area dragging.
* @param start_tile Start tile of area dragging.
* @param HouseID The HouseID of the house spec.
* @param is_protected Whether the house is protected from the town upgrading it.
* @param replace Whether we can replace existing houses.
* @param diagonal Whether to use the Diagonal or Orthogonal tile iterator.
* @return Empty cost or an error.
*/
CommandCost CmdPlaceHouseArea(DoCommandFlags flags, TileIndex tile, TileIndex start_tile, HouseID house, bool is_protected, bool replace, bool diagonal)
{
if (start_tile >= Map::Size()) return CMD_ERROR;
if (_game_mode != GM_EDITOR && _settings_game.economy.place_houses == PH_FORBIDDEN) return CMD_ERROR;
if (Town::GetNumItems() == 0) return CommandCost(STR_ERROR_MUST_FOUND_TOWN_FIRST);
if (static_cast<size_t>(house) >= HouseSpec::Specs().size()) return CMD_ERROR;
const HouseSpec *hs = HouseSpec::Get(house);
if (!hs->enabled) return CMD_ERROR;
/* Only allow placing an area of 1x1 houses. */
if (!hs->building_flags.Test(BuildingFlag::Size1x1)) return CMD_ERROR;
/* Use the built object limit to rate limit house placement. */
const Company *c = Company::GetIfValid(_current_company);
int limit = (c == nullptr ? INT32_MAX : GB(c->build_object_limit, 16, 16));
CommandCost last_error = CMD_ERROR;
bool had_success = false;
std::unique_ptr<TileIterator> iter = TileIterator::Create(tile, start_tile, diagonal);
for (; *iter != INVALID_TILE; ++(*iter)) {
TileIndex t = *iter;
CommandCost ret = Command<CMD_PLACE_HOUSE>::Do(DoCommandFlags{flags}.Reset(DoCommandFlag::Execute), t, house, is_protected, replace);
/* If we've reached the limit, stop building (or testing). */
if (c != nullptr && limit-- <= 0) break;
if (ret.Failed()) {
last_error = std::move(ret);
continue;
}
if (flags.Test(DoCommandFlag::Execute)) Command<CMD_PLACE_HOUSE>::Do(flags, t, house, is_protected, replace);
had_success = true;
}
return had_success ? CommandCost{} : last_error;
}
/**
* Update data structures when a house is removed
* @param tile Tile of the house

View File

@@ -28,6 +28,7 @@ CommandCost CmdTownSetText(DoCommandFlags flags, TownID town_id, const EncodedSt
CommandCost CmdExpandTown(DoCommandFlags flags, TownID town_id, uint32_t grow_amount, TownExpandModes modes);
CommandCost CmdDeleteTown(DoCommandFlags flags, TownID town_id);
CommandCost CmdPlaceHouse(DoCommandFlags flags, TileIndex tile, HouseID house, bool house_protected, bool replace);
CommandCost CmdPlaceHouseArea(DoCommandFlags flags, TileIndex tile, TileIndex start_tile, HouseID house, bool is_protected, bool replace, bool diagonal);
DEF_CMD_TRAIT(CMD_FOUND_TOWN, CmdFoundTown, CommandFlags({CommandFlag::Deity, CommandFlag::NoTest}), CommandType::LandscapeConstruction) // founding random town can fail only in exec run
DEF_CMD_TRAIT(CMD_RENAME_TOWN, CmdRenameTown, CommandFlags({CommandFlag::Deity, CommandFlag::Server}), CommandType::OtherManagement)
@@ -39,6 +40,8 @@ DEF_CMD_TRAIT(CMD_TOWN_SET_TEXT, CmdTownSetText, CommandFlags({CommandFlag
DEF_CMD_TRAIT(CMD_EXPAND_TOWN, CmdExpandTown, CommandFlags({CommandFlag::Deity}), CommandType::LandscapeConstruction)
DEF_CMD_TRAIT(CMD_DELETE_TOWN, CmdDeleteTown, CommandFlags({CommandFlag::Offline}), CommandType::LandscapeConstruction)
DEF_CMD_TRAIT(CMD_PLACE_HOUSE, CmdPlaceHouse, CommandFlags({CommandFlag::Deity}), CommandType::OtherManagement)
DEF_CMD_TRAIT(CMD_PLACE_HOUSE_AREA, CmdPlaceHouseArea, CommandFlags({ CommandFlag::Deity }), CommandType::OtherManagement)
CommandCallback CcFoundTown;
void CcFoundRandomTown(Commands cmd, const CommandCost &result, Money, TownID town_id);

View File

@@ -1798,7 +1798,28 @@ struct BuildHouseWindow : public PickerWindow {
void OnPlaceObject([[maybe_unused]] Point pt, TileIndex tile) override
{
const HouseSpec *spec = HouseSpec::Get(HousePickerCallbacks::sel_type);
Command<CMD_PLACE_HOUSE>::Post(STR_ERROR_CAN_T_BUILD_HOUSE, CcPlaySound_CONSTRUCTION_OTHER, tile, spec->Index(), BuildHouseWindow::house_protected, BuildHouseWindow::replace);
if (spec->building_flags.Test(BuildingFlag::Size1x1)) {
VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_PLACE_HOUSE);
} else {
Command<CMD_PLACE_HOUSE>::Post(STR_ERROR_CAN_T_BUILD_HOUSE, CcPlaySound_CONSTRUCTION_OTHER, tile, spec->Index(), BuildHouseWindow::house_protected, BuildHouseWindow::replace);
}
}
void OnPlaceDrag(ViewportPlaceMethod select_method, [[maybe_unused]] ViewportDragDropSelectionProcess select_proc, [[maybe_unused]] Point pt) override
{
VpSelectTilesWithMethod(pt.x, pt.y, select_method);
}
void OnPlaceMouseUp([[maybe_unused]] ViewportPlaceMethod select_method, [[maybe_unused]] ViewportDragDropSelectionProcess select_proc, [[maybe_unused]] Point pt, TileIndex start_tile, TileIndex end_tile) override
{
if (pt.x == -1) return;
assert(select_proc == DDSP_PLACE_HOUSE);
const HouseSpec *spec = HouseSpec::Get(HousePickerCallbacks::sel_type);
Command<CMD_PLACE_HOUSE_AREA>::Post(STR_ERROR_CAN_T_BUILD_HOUSE, CcPlaySound_CONSTRUCTION_OTHER,
end_tile, start_tile, spec->Index(), BuildHouseWindow::house_protected, BuildHouseWindow::replace, _ctrl_pressed);
}
const IntervalTimer<TimerWindow> view_refresh_interval = {std::chrono::milliseconds(2500), [this](auto) {

View File

@@ -124,6 +124,7 @@ enum ViewportDragDropSelectionProcess : uint8_t {
DDSP_PLANT_TREES, ///< Plant trees
DDSP_BUILD_BRIDGE, ///< Bridge placement
DDSP_BUILD_OBJECT, ///< Build an object
DDSP_PLACE_HOUSE, ///< Place a house
/* Rail specific actions */
DDSP_PLACE_RAIL, ///< Rail placement