# HG changeset patch # User Tyler Trahan # Date 2022-02-03 00:24:52 # Node ID 10f21bbdb7758dc544844d48dcd63d2b2f533fe7 # Parent 31785ae094bdac587a679f02a8cbad5a0568f0b4 Feature: Build objects by area diff --git a/src/command_type.h b/src/command_type.h --- a/src/command_type.h +++ b/src/command_type.h @@ -186,6 +186,7 @@ enum Commands : uint16 { CMD_REMOVE_SIGNALS, ///< remove a signal CMD_TERRAFORM_LAND, ///< terraform a tile CMD_BUILD_OBJECT, ///< build an object + CMD_BUILD_OBJECT_AREA, ///< build an area of objects CMD_BUILD_TUNNEL, ///< build a tunnel CMD_REMOVE_FROM_RAIL_STATION, ///< remove a (rectangle of) tiles from a rail station diff --git a/src/company_base.h b/src/company_base.h --- a/src/company_base.h +++ b/src/company_base.h @@ -87,6 +87,7 @@ struct CompanyProperties { uint32 terraform_limit; ///< Amount of tileheights we can (still) terraform (times 65536). uint32 clear_limit; ///< Amount of tiles we can (still) clear (times 65536). uint32 tree_limit; ///< Amount of trees we can (still) plant (times 65536). + uint32 build_object_limit; ///< Amount of tiles we can (still) build objects on (times 65536). /** * If \c true, the company is (also) controlled by the computer (a NoAI program). @@ -110,7 +111,7 @@ struct CompanyProperties { face(0), money(0), money_fraction(0), current_loan(0), colour(0), block_preview(0), location_of_HQ(0), last_build_coordinate(0), share_owners(), inaugurated_year(0), months_of_bankruptcy(0), bankrupt_asked(0), bankrupt_timeout(0), bankrupt_value(0), - terraform_limit(0), clear_limit(0), tree_limit(0), is_ai(false), engine_renew_list(nullptr) {} + terraform_limit(0), clear_limit(0), tree_limit(0), build_object_limit(0), is_ai(false), engine_renew_list(nullptr) {} }; struct Company : CompanyProperties, CompanyPool::PoolItem<&_company_pool> { diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -63,9 +63,10 @@ Company::Company(uint16 name_1, bool is_ this->name_1 = name_1; this->location_of_HQ = INVALID_TILE; this->is_ai = is_ai; - this->terraform_limit = (uint32)_settings_game.construction.terraform_frame_burst << 16; - this->clear_limit = (uint32)_settings_game.construction.clear_frame_burst << 16; - this->tree_limit = (uint32)_settings_game.construction.tree_frame_burst << 16; + this->terraform_limit = (uint32)_settings_game.construction.terraform_frame_burst << 16; + this->clear_limit = (uint32)_settings_game.construction.clear_frame_burst << 16; + this->tree_limit = (uint32)_settings_game.construction.tree_frame_burst << 16; + this->build_object_limit = (uint32)_settings_game.construction.build_object_frame_burst << 16; std::fill(this->share_owners.begin(), this->share_owners.end(), INVALID_OWNER); InvalidateWindowData(WC_PERFORMANCE_DETAIL, 0, INVALID_COMPANY); @@ -267,9 +268,10 @@ void SubtractMoneyFromCompanyFract(Compa void UpdateLandscapingLimits() { for (Company *c : Company::Iterate()) { - c->terraform_limit = std::min((uint64)c->terraform_limit + _settings_game.construction.terraform_per_64k_frames, (uint64)_settings_game.construction.terraform_frame_burst << 16); - c->clear_limit = std::min((uint64)c->clear_limit + _settings_game.construction.clear_per_64k_frames, (uint64)_settings_game.construction.clear_frame_burst << 16); - c->tree_limit = std::min((uint64)c->tree_limit + _settings_game.construction.tree_per_64k_frames, (uint64)_settings_game.construction.tree_frame_burst << 16); + c->terraform_limit = std::min((uint64)c->terraform_limit + _settings_game.construction.terraform_per_64k_frames, (uint64)_settings_game.construction.terraform_frame_burst << 16); + c->clear_limit = std::min((uint64)c->clear_limit + _settings_game.construction.clear_per_64k_frames, (uint64)_settings_game.construction.clear_frame_burst << 16); + c->tree_limit = std::min((uint64)c->tree_limit + _settings_game.construction.tree_per_64k_frames, (uint64)_settings_game.construction.tree_frame_burst << 16); + c->build_object_limit = std::min((uint64)c->build_object_limit + _settings_game.construction.build_object_per_64k_frames, (uint64)_settings_game.construction.build_object_frame_burst << 16); } } diff --git a/src/lang/english.txt b/src/lang/english.txt --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -378,7 +378,7 @@ STR_SCENEDIT_TOOLBAR_ROAD_CONSTRUCTION STR_SCENEDIT_TOOLBAR_TRAM_CONSTRUCTION :{BLACK}Tramway construction STR_SCENEDIT_TOOLBAR_PLANT_TREES :{BLACK}Plant trees. Shift toggles building/showing cost estimate STR_SCENEDIT_TOOLBAR_PLACE_SIGN :{BLACK}Place sign -STR_SCENEDIT_TOOLBAR_PLACE_OBJECT :{BLACK}Place object. Shift toggles building/showing cost estimate +STR_SCENEDIT_TOOLBAR_PLACE_OBJECT :{BLACK}Place object. Ctrl selects the area diagonally. Shift toggles building/showing cost estimate # Scenario editor file menu ###length 7 @@ -2816,7 +2816,7 @@ STR_LANDSCAPING_TOOLTIP_PURCHASE_LAND # Object construction window STR_OBJECT_BUILD_CAPTION :{WHITE}Object Selection -STR_OBJECT_BUILD_TOOLTIP :{BLACK}Select object to build. Shift toggles building/showing cost estimate +STR_OBJECT_BUILD_TOOLTIP :{BLACK}Select object to build. Ctrl selects the area diagonally. Shift toggles building/showing cost estimate STR_OBJECT_BUILD_CLASS_TOOLTIP :{BLACK}Select class of the object to build STR_OBJECT_BUILD_PREVIEW_TOOLTIP :{BLACK}Preview of the object STR_OBJECT_BUILD_SIZE :{BLACK}Size: {GOLD}{NUM} x {NUM} tiles @@ -4925,6 +4925,7 @@ STR_ERROR_OBJECT_IN_THE_WAY STR_ERROR_COMPANY_HEADQUARTERS_IN :{WHITE}... company headquarters in the way STR_ERROR_CAN_T_PURCHASE_THIS_LAND :{WHITE}Can't purchase this land area... STR_ERROR_YOU_ALREADY_OWN_IT :{WHITE}... you already own it! +STR_ERROR_BUILD_OBJECT_LIMIT_REACHED :{WHITE}... object construction limit reached # Group related errors STR_ERROR_GROUP_CAN_T_CREATE :{WHITE}Can't create group... diff --git a/src/object_cmd.cpp b/src/object_cmd.cpp --- a/src/object_cmd.cpp +++ b/src/object_cmd.cpp @@ -311,6 +311,7 @@ CommandCost CmdBuildObject(DoCommandFlag } int hq_score = 0; + uint build_object_size = 1; switch (type) { case OBJECT_TRANSMITTER: case OBJECT_LIGHTHOUSE: @@ -349,20 +350,85 @@ CommandCost CmdBuildObject(DoCommandFlag return CMD_ERROR; default: // i.e. NewGRF provided. + build_object_size = size_x * size_y; break; } + /* Don't allow building more objects if the company has reached its limit. */ + Company *c = Company::GetIfValid(_current_company); + if (c != nullptr && GB(c->build_object_limit, 16, 16) < build_object_size) { + return_cmd_error(STR_ERROR_BUILD_OBJECT_LIMIT_REACHED); + } + if (flags & DC_EXEC) { BuildObject(type, tile, _current_company == OWNER_DEITY ? OWNER_NONE : _current_company, nullptr, view); /* Make sure the HQ starts at the right size. */ if (type == OBJECT_HQ) UpdateCompanyHQ(tile, hq_score); + + /* Subtract the tile from the build limit. */ + if (c != nullptr) c->build_object_limit -= build_object_size << 16; } - cost.AddCost(ObjectSpec::Get(type)->GetBuildCost() * size_x * size_y); + cost.AddCost(spec->GetBuildCost() * build_object_size); return cost; } +/** + * Construct multiple objects in an area + * @param flags of operation to conduct + * @param tile end tile of area dragging + * @param start_tile start tile of area dragging + * @param type the object type to build + * @param view the view for the object + * @param diagonal Whether to use the Orthogonal (0) or Diagonal (1) iterator. + * @return the cost of this operation or an error + */ +CommandCost CmdBuildObjectArea(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, ObjectType type, uint8 view, bool diagonal) +{ + if (start_tile >= MapSize()) return CMD_ERROR; + + if (type >= NUM_OBJECTS) return CMD_ERROR; + const ObjectSpec *spec = ObjectSpec::Get(type); + if (view >= spec->views) return CMD_ERROR; + + if (spec->size != OBJECT_SIZE_1X1) return CMD_ERROR; + + Money money = GetAvailableMoneyForCommand(); + CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost last_error = CMD_ERROR; + bool had_success = false; + + const Company *c = Company::GetIfValid(_current_company); + int limit = (c == nullptr ? INT32_MAX : GB(c->build_object_limit, 16, 16)); + + TileIterator *iter = diagonal ? (TileIterator *)new DiagonalTileIterator(tile, start_tile) : new OrthogonalTileIterator(tile, start_tile); + for (; *iter != INVALID_TILE; ++(*iter)) { + TileIndex t = *iter; + CommandCost ret = Command::Do(flags & ~DC_EXEC, t, type, view); + + /* If we've reached the limit, stop building (or testing). */ + if (c != nullptr && --limit <= 0) break; + + if (ret.Failed()) { + last_error = ret; + continue; + } + + had_success = true; + if (flags & DC_EXEC) { + money -= ret.GetCost(); + + /* If we run out of money, stop building. */ + if (ret.GetCost() > 0 && money < 0) break; + Command::Do(flags, t, type, view); + } + cost.AddCost(ret); + } + + delete iter; + return had_success ? cost : last_error; +} static Foundation GetFoundation_Object(TileIndex tile, Slope tileh); diff --git a/src/object_cmd.h b/src/object_cmd.h --- a/src/object_cmd.h +++ b/src/object_cmd.h @@ -14,7 +14,9 @@ #include "object_type.h" CommandCost CmdBuildObject(DoCommandFlag flags, TileIndex tile, ObjectType type, uint8 view); +CommandCost CmdBuildObjectArea(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, ObjectType type, uint8 view, bool diagonal); DEF_CMD_TRAIT(CMD_BUILD_OBJECT, CmdBuildObject, CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_BUILD_OBJECT_AREA, CmdBuildObjectArea, CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) #endif /* OBJECT_CMD_H */ diff --git a/src/object_gui.cpp b/src/object_gui.cpp --- a/src/object_gui.cpp +++ b/src/object_gui.cpp @@ -458,7 +458,7 @@ public: } if (_selected_object_index != -1) { - SetObjectToPlaceWnd(SPR_CURSOR_TRANSMITTER, PAL_NONE, HT_RECT, this); + SetObjectToPlaceWnd(SPR_CURSOR_TRANSMITTER, PAL_NONE, HT_RECT | HT_DIAGONAL, this); } this->UpdateButtons(_selected_object_class, _selected_object_index, _selected_object_view); @@ -543,9 +543,38 @@ public: void OnPlaceObject(Point pt, TileIndex tile) override { - ObjectClass *objclass = ObjectClass::Get(_selected_object_class); - Command::Post(STR_ERROR_CAN_T_BUILD_OBJECT, CcPlaySound_CONSTRUCTION_OTHER, - tile, objclass->GetSpec(_selected_object_index)->Index(), _selected_object_view); + const ObjectSpec *spec = ObjectClass::Get(_selected_object_class)->GetSpec(_selected_object_index); + + if (spec->size == OBJECT_SIZE_1X1) { + VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_BUILD_OBJECT); + } else { + Command::Post(STR_ERROR_CAN_T_BUILD_OBJECT, CcPlaySound_CONSTRUCTION_OTHER, tile, spec->Index(), _selected_object_view); + } + } + + void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt) override + { + VpSelectTilesWithMethod(pt.x, pt.y, select_method); + } + + void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile) override + { + if (pt.x == -1) return; + + switch (select_proc) { + default: NOT_REACHED(); + case DDSP_BUILD_OBJECT: + if (!_settings_game.construction.freeform_edges) { + /* When end_tile is MP_VOID, the error tile will not be visible to the + * user. This happens when terraforming at the southern border. */ + if (TileX(end_tile) == MapMaxX()) end_tile += TileDiffXY(-1, 0); + if (TileY(end_tile) == MapMaxY()) end_tile += TileDiffXY(0, -1); + } + const ObjectSpec *spec = ObjectClass::Get(_selected_object_class)->GetSpec(_selected_object_index); + Command::Post(STR_ERROR_CAN_T_BUILD_OBJECT, CcPlaySound_CONSTRUCTION_OTHER, + end_tile, start_tile, spec->Index(), _selected_object_view, (_ctrl_pressed ? true : false)); + break; + } } void OnPlaceObjectAbort() override diff --git a/src/settings_type.h b/src/settings_type.h --- a/src/settings_type.h +++ b/src/settings_type.h @@ -355,6 +355,8 @@ struct ConstructionSettings { uint16 clear_frame_burst; ///< how many tiles may, over a short period, be cleared? uint32 tree_per_64k_frames; ///< how many trees may, over a long period, be planted per 65536 frames? uint16 tree_frame_burst; ///< how many trees may, over a short period, be planted? + uint32 build_object_per_64k_frames; ///< how many tiles may, over a long period, have objects built on them per 65536 frames? + uint16 build_object_frame_burst; ///< how many tiles may, over a short period, have objects built on them? }; /** Settings related to the AI. */ diff --git a/src/table/settings/world_settings.ini b/src/table/settings/world_settings.ini --- a/src/table/settings/world_settings.ini +++ b/src/table/settings/world_settings.ini @@ -415,6 +415,24 @@ max = 1 << 15 interval = 1 cat = SC_EXPERT +[SDT_VAR] +var = construction.build_object_per_64k_frames +type = SLE_UINT32 +def = 32 << 16 +min = 0 +max = 1 << 30 +interval = 1 +cat = SC_EXPERT + +[SDT_VAR] +var = construction.build_object_frame_burst +type = SLE_UINT16 +def = 2048 +min = 0 +max = 1 << 15 +interval = 1 +cat = SC_EXPERT + [SDT_BOOL] var = construction.autoslope from = SLV_75 diff --git a/src/viewport_type.h b/src/viewport_type.h --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -123,6 +123,7 @@ enum ViewportDragDropSelectionProcess { DDSP_CREATE_RIVER, ///< Create rivers DDSP_PLANT_TREES, ///< Plant trees DDSP_BUILD_BRIDGE, ///< Bridge placement + DDSP_BUILD_OBJECT, ///< Build an object /* Rail specific actions */ DDSP_PLACE_RAIL, ///< Rail placement