From d99c1337a2e0bbafa4b6fcfba66d1efb4c8066d6 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 14 May 2024 17:29:57 +0100 Subject: [PATCH] Add: Command to build an individual house on a specific tile. --- src/command_type.h | 1 + src/town_cmd.cpp | 106 +++++++++++++++++++++++++++++++++++---------- src/town_cmd.h | 3 ++ 3 files changed, 88 insertions(+), 22 deletions(-) diff --git a/src/command_type.h b/src/command_type.h index 8f3cbedd93..193802ef17 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -283,6 +283,7 @@ enum Commands : uint16_t { CMD_TOWN_SET_TEXT, ///< set the custom text of a town CMD_EXPAND_TOWN, ///< expand a town CMD_DELETE_TOWN, ///< delete a town + CMD_PLACE_HOUSE, ///< place a house CMD_ORDER_REFIT, ///< change the refit information of an order (for "goto depot" ) CMD_CLONE_ORDER, ///< clone (and share) an order diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index e7a8a0e58e..5a339161f7 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -2665,6 +2665,39 @@ static bool CheckTownBuild2x2House(TileIndex *tile, Town *t, int maxz, bool nosl return false; } +/** + * Build a house at this tile. + * @param t The town the house will belong to. + * @param tile The tile to try building on. + * @param hs The @a HouseSpec of the house. + * @param house The @a HouseID of the house. + * @param random_bits The random data to be associated with the house. + */ +static void BuildTownHouse(Town *t, TileIndex tile, const HouseSpec *hs, HouseID house, uint8_t random_bits) +{ + /* build the house */ + t->cache.num_houses++; + + uint8_t construction_counter = 0; + uint8_t construction_stage = 0; + + if (_generating_world || _game_mode == GM_EDITOR) { + uint32_t construction_random = Random(); + + construction_stage = TOWN_HOUSE_COMPLETED; + if (Chance16(1, 7)) construction_stage = GB(construction_random, 0, 2); + + if (construction_stage == TOWN_HOUSE_COMPLETED) { + ChangePopulation(t, hs->population); + } else { + construction_counter = GB(construction_random, 2, 2); + } + } + + MakeTownHouse(tile, t, construction_counter, construction_stage, house, random_bits); + UpdateTownRadius(t); + UpdateTownGrowthRate(t); +} /** * Tries to build a house at this tile. @@ -2784,31 +2817,10 @@ static bool TryBuildTownHouse(Town *t, TileIndex tile) if (callback_res != CALLBACK_FAILED && !Convert8bitBooleanCallback(hs->grf_prop.grffile, CBID_HOUSE_ALLOW_CONSTRUCTION, callback_res)) continue; } - /* build the house */ - t->cache.num_houses++; - /* Special houses that there can be only one of. */ t->flags |= oneof; - uint8_t construction_counter = 0; - uint8_t construction_stage = 0; - - if (_generating_world || _game_mode == GM_EDITOR) { - uint32_t construction_random = Random(); - - construction_stage = TOWN_HOUSE_COMPLETED; - if (Chance16(1, 7)) construction_stage = GB(construction_random, 0, 2); - - if (construction_stage == TOWN_HOUSE_COMPLETED) { - ChangePopulation(t, hs->population); - } else { - construction_counter = GB(construction_random, 2, 2); - } - } - - MakeTownHouse(tile, t, construction_counter, construction_stage, house, random_bits); - UpdateTownRadius(t); - UpdateTownGrowthRate(t); + BuildTownHouse(t, tile, hs, house, random_bits); return true; } @@ -2816,6 +2828,56 @@ static bool TryBuildTownHouse(Town *t, TileIndex tile) return false; } +CommandCost CmdPlaceHouse(DoCommandFlag flags, TileIndex tile, HouseID house) +{ + if (_game_mode != GM_EDITOR) return CMD_ERROR; + if (Town::GetNumItems() == 0) return_cmd_error(STR_ERROR_MUST_FOUND_TOWN_FIRST); + + if (static_cast(house) >= HouseSpec::Specs().size()) return CMD_ERROR; + const HouseSpec *hs = HouseSpec::Get(house); + if (!hs->enabled) return CMD_ERROR; + + if (TimerGameCalendar::year < hs->min_year || TimerGameCalendar::year > hs->max_year) return CMD_ERROR; + + Town *t = ClosestTownFromTile(tile, UINT_MAX); + + /* cannot build on these slopes... */ + Slope slope = GetTileSlope(tile); + if (IsSteepSlope(slope)) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION); + + /* building under a bridge? */ + if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + + /* can we clear the land? */ + CommandCost cost = Command::Do(DC_AUTO | DC_NO_WATER, tile); + if (!cost.Succeeded()) return cost; + + int maxz = GetTileMaxZ(tile); + + /* Make sure there is no slope? */ + bool noslope = (hs->building_flags & TILE_NOT_SLOPED) != 0; + if (noslope && slope != SLOPE_FLAT) return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + + TileArea ta = tile; + if (hs->building_flags & TILE_SIZE_2x2) ta.Add(TileAddXY(tile, 1, 1)); + if (hs->building_flags & TILE_SIZE_2x1) ta.Add(TileAddByDiagDir(tile, DIAGDIR_SW)); + if (hs->building_flags & TILE_SIZE_1x2) ta.Add(TileAddByDiagDir(tile, DIAGDIR_SE)); + + /* Check additonal tiles covered by this house. */ + for (const TileIndex &subtile : ta) { + cost = Command::Do(DC_AUTO | DC_NO_WATER, subtile); + if (!cost.Succeeded()) return cost; + + if (!CheckBuildHouseSameZ(subtile, maxz, noslope)) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION); + } + + if (flags & DC_EXEC) { + BuildTownHouse(t, tile, hs, house, Random()); + } + + return CommandCost(); +} + /** * Update data structures when a house is removed * @param tile Tile of the house diff --git a/src/town_cmd.h b/src/town_cmd.h index edabfd0433..3c492eb434 100644 --- a/src/town_cmd.h +++ b/src/town_cmd.h @@ -15,6 +15,7 @@ #include "town_type.h" enum TownAcceptanceEffect : uint8_t; +using HouseID = uint16_t; std::tuple CmdFoundTown(DoCommandFlag flags, TileIndex tile, TownSize size, bool city, TownLayout layout, bool random_location, uint32_t townnameparts, const std::string &text); CommandCost CmdRenameTown(DoCommandFlag flags, TownID town_id, const std::string &text); @@ -25,6 +26,7 @@ CommandCost CmdTownCargoGoal(DoCommandFlag flags, TownID town_id, TownAcceptance CommandCost CmdTownSetText(DoCommandFlag flags, TownID town_id, const std::string &text); CommandCost CmdExpandTown(DoCommandFlag flags, TownID town_id, uint32_t grow_amount); CommandCost CmdDeleteTown(DoCommandFlag flags, TownID town_id); +CommandCost CmdPlaceHouse(DoCommandFlag flags, TileIndex tile, HouseID house); DEF_CMD_TRAIT(CMD_FOUND_TOWN, CmdFoundTown, CMD_DEITY | CMD_NO_TEST, CMDT_LANDSCAPE_CONSTRUCTION) // founding random town can fail only in exec run DEF_CMD_TRAIT(CMD_RENAME_TOWN, CmdRenameTown, CMD_DEITY | CMD_SERVER, CMDT_OTHER_MANAGEMENT) @@ -35,6 +37,7 @@ DEF_CMD_TRAIT(CMD_TOWN_RATING, CmdTownRating, CMD_DEITY, DEF_CMD_TRAIT(CMD_TOWN_SET_TEXT, CmdTownSetText, CMD_DEITY | CMD_STR_CTRL, CMDT_OTHER_MANAGEMENT) DEF_CMD_TRAIT(CMD_EXPAND_TOWN, CmdExpandTown, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_DELETE_TOWN, CmdDeleteTown, CMD_OFFLINE, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_PLACE_HOUSE, CmdPlaceHouse, CMD_DEITY, CMDT_OTHER_MANAGEMENT) CommandCallback CcFoundTown; void CcFoundRandomTown(Commands cmd, const CommandCost &result, Money, TownID town_id);