1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "LargeSceneryPlaceAction.h"
11 
12 #include "../OpenRCT2.h"
13 #include "../management/Finance.h"
14 #include "../object/ObjectLimits.h"
15 #include "../ride/Ride.h"
16 #include "../world/Banner.h"
17 #include "../world/ConstructionClearance.h"
18 #include "../world/MapAnimation.h"
19 #include "../world/Surface.h"
20 
LargeSceneryPlaceAction(const CoordsXYZD & loc,ObjectEntryIndex sceneryType,uint8_t primaryColour,uint8_t secondaryColour)21 LargeSceneryPlaceAction::LargeSceneryPlaceAction(
22     const CoordsXYZD& loc, ObjectEntryIndex sceneryType, uint8_t primaryColour, uint8_t secondaryColour)
23     : _loc(loc)
24     , _sceneryType(sceneryType)
25     , _primaryColour(primaryColour)
26     , _secondaryColour(secondaryColour)
27 {
28 }
29 
AcceptParameters(GameActionParameterVisitor & visitor)30 void LargeSceneryPlaceAction::AcceptParameters(GameActionParameterVisitor& visitor)
31 {
32     visitor.Visit(_loc);
33     visitor.Visit("object", _sceneryType);
34     visitor.Visit("primaryColour", _primaryColour);
35     visitor.Visit("secondaryColour", _secondaryColour);
36 }
37 
GetActionFlags() const38 uint16_t LargeSceneryPlaceAction::GetActionFlags() const
39 {
40     return GameAction::GetActionFlags();
41 }
42 
Serialise(DataSerialiser & stream)43 void LargeSceneryPlaceAction::Serialise(DataSerialiser& stream)
44 {
45     GameAction::Serialise(stream);
46 
47     stream << DS_TAG(_loc) << DS_TAG(_sceneryType) << DS_TAG(_primaryColour) << DS_TAG(_secondaryColour);
48 }
49 
Query() const50 GameActions::Result::Ptr LargeSceneryPlaceAction::Query() const
51 {
52     auto res = MakeResult();
53     res->ErrorTitle = STR_CANT_POSITION_THIS_HERE;
54     res->Expenditure = ExpenditureType::Landscaping;
55     int16_t surfaceHeight = tile_element_height(_loc);
56     res->Position.x = _loc.x + 16;
57     res->Position.y = _loc.y + 16;
58     res->Position.z = surfaceHeight;
59 
60     auto resultData = LargeSceneryPlaceActionResult{};
61 
62     money32 supportsCost = 0;
63 
64     if (_primaryColour > TILE_ELEMENT_COLOUR_MASK || _secondaryColour > TILE_ELEMENT_COLOUR_MASK)
65     {
66         log_error(
67             "Invalid game command for scenery placement, primaryColour = %u, secondaryColour = %u", _primaryColour,
68             _secondaryColour);
69         return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
70     }
71 
72     if (_sceneryType >= MAX_LARGE_SCENERY_OBJECTS)
73     {
74         log_error("Invalid game command for scenery placement, sceneryType = %u", _sceneryType);
75         return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
76     }
77 
78     auto* sceneryEntry = get_large_scenery_entry(_sceneryType);
79     if (sceneryEntry == nullptr)
80     {
81         log_error("Invalid game command for scenery placement, sceneryType = %u", _sceneryType);
82         return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
83     }
84 
85     uint32_t totalNumTiles = GetTotalNumTiles(sceneryEntry->tiles);
86     int16_t maxHeight = GetMaxSurfaceHeight(sceneryEntry->tiles);
87 
88     if (_loc.z != 0)
89     {
90         maxHeight = _loc.z;
91     }
92 
93     res->Position.z = maxHeight;
94 
95     if (sceneryEntry->scrolling_mode != SCROLLING_MODE_NONE)
96     {
97         if (HasReachedBannerLimit())
98         {
99             log_error("No free banners available");
100             return MakeResult(
101                 GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_TOO_MANY_BANNERS_IN_GAME);
102         }
103     }
104 
105     uint8_t tileNum = 0;
106     for (rct_large_scenery_tile* tile = sceneryEntry->tiles; tile->x_offset != -1; tile++, tileNum++)
107     {
108         auto curTile = CoordsXY{ tile->x_offset, tile->y_offset }.Rotate(_loc.direction);
109 
110         curTile.x += _loc.x;
111         curTile.y += _loc.y;
112 
113         int32_t zLow = tile->z_offset + maxHeight;
114         int32_t zHigh = tile->z_clearance + zLow;
115 
116         QuarterTile quarterTile = QuarterTile{ static_cast<uint8_t>(tile->flags >> 12), 0 }.Rotate(_loc.direction);
117         const auto isTree = (sceneryEntry->flags & LARGE_SCENERY_FLAG_IS_TREE) != 0;
118         auto canBuild = MapCanConstructWithClearAt(
119             { curTile, zLow, zHigh }, &map_place_scenery_clear_func, quarterTile, GetFlags(), CREATE_CROSSING_MODE_NONE,
120             isTree);
121         if (canBuild->Error != GameActions::Status::Ok)
122         {
123             canBuild->ErrorTitle = STR_CANT_POSITION_THIS_HERE;
124             return canBuild;
125         }
126 
127         supportsCost += canBuild->Cost;
128 
129         const auto clearanceData = canBuild->GetData<ConstructClearResult>();
130         int32_t tempSceneryGroundFlags = clearanceData.GroundFlags & (ELEMENT_IS_ABOVE_GROUND | ELEMENT_IS_UNDERGROUND);
131         if (!gCheatsDisableClearanceChecks)
132         {
133             if ((clearanceData.GroundFlags & ELEMENT_IS_UNDERWATER) || (clearanceData.GroundFlags & ELEMENT_IS_UNDERGROUND))
134             {
135                 return MakeResult(GameActions::Status::Disallowed, STR_CANT_POSITION_THIS_HERE, STR_CANT_BUILD_THIS_UNDERWATER);
136             }
137             if (resultData.GroundFlags && !(resultData.GroundFlags & tempSceneryGroundFlags))
138             {
139                 return MakeResult(
140                     GameActions::Status::Disallowed, STR_CANT_POSITION_THIS_HERE,
141                     STR_CANT_BUILD_PARTLY_ABOVE_AND_PARTLY_BELOW_GROUND);
142             }
143         }
144 
145         resultData.GroundFlags = tempSceneryGroundFlags;
146 
147         if (!LocationValid(curTile) || map_is_edge(curTile))
148         {
149             return MakeResult(GameActions::Status::Disallowed, STR_CANT_POSITION_THIS_HERE, STR_OFF_EDGE_OF_MAP);
150         }
151 
152         if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !map_is_location_owned({ curTile, zLow }) && !gCheatsSandboxMode)
153         {
154             return MakeResult(GameActions::Status::Disallowed, STR_CANT_POSITION_THIS_HERE, STR_LAND_NOT_OWNED_BY_PARK);
155         }
156     }
157 
158     if (!CheckMapCapacity(sceneryEntry->tiles, totalNumTiles))
159     {
160         log_error("No free map elements available");
161         return MakeResult(GameActions::Status::NoFreeElements, STR_CANT_POSITION_THIS_HERE, STR_TILE_ELEMENT_LIMIT_REACHED);
162     }
163 
164     // Force ride construction to recheck area
165     _currentTrackSelectionFlags |= TRACK_SELECTION_FLAG_RECHECK;
166 
167     res->Cost = (sceneryEntry->price * 10) + supportsCost;
168     res->SetData(std::move(resultData));
169 
170     return res;
171 }
172 
Execute() const173 GameActions::Result::Ptr LargeSceneryPlaceAction::Execute() const
174 {
175     auto res = MakeResult();
176     res->ErrorTitle = STR_CANT_POSITION_THIS_HERE;
177     res->Expenditure = ExpenditureType::Landscaping;
178 
179     int16_t surfaceHeight = tile_element_height(_loc);
180     res->Position.x = _loc.x + 16;
181     res->Position.y = _loc.y + 16;
182     res->Position.z = surfaceHeight;
183 
184     auto resultData = LargeSceneryPlaceActionResult{};
185 
186     money32 supportsCost = 0;
187 
188     auto* sceneryEntry = get_large_scenery_entry(_sceneryType);
189     if (sceneryEntry == nullptr)
190     {
191         log_error("Invalid game command for scenery placement, sceneryType = %u", _sceneryType);
192         return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
193     }
194 
195     if (sceneryEntry->tiles == nullptr)
196     {
197         log_error("Invalid large scenery object, sceneryType = %u", _sceneryType);
198         return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
199     }
200 
201     int16_t maxHeight = GetMaxSurfaceHeight(sceneryEntry->tiles);
202 
203     if (_loc.z != 0)
204     {
205         maxHeight = _loc.z;
206     }
207 
208     res->Position.z = maxHeight;
209 
210     // Allocate banner
211     Banner* banner = nullptr;
212     if (sceneryEntry->scrolling_mode != SCROLLING_MODE_NONE)
213     {
214         banner = CreateBanner();
215         if (banner == nullptr)
216         {
217             log_error("No free banners available");
218             return MakeResult(
219                 GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_TOO_MANY_BANNERS_IN_GAME);
220         }
221 
222         banner->text = {};
223         banner->colour = 2;
224         banner->text_colour = 2;
225         banner->flags = BANNER_FLAG_IS_LARGE_SCENERY;
226         banner->type = 0;
227         banner->position = TileCoordsXY(_loc);
228 
229         ride_id_t rideIndex = banner_get_closest_ride_index({ _loc, maxHeight });
230         if (rideIndex != RIDE_ID_NULL)
231         {
232             banner->ride_index = rideIndex;
233             banner->flags |= BANNER_FLAG_LINKED_TO_RIDE;
234         }
235 
236         resultData.bannerId = banner->id;
237     }
238 
239     uint8_t tileNum = 0;
240     for (rct_large_scenery_tile* tile = sceneryEntry->tiles; tile->x_offset != -1; tile++, tileNum++)
241     {
242         auto curTile = CoordsXY{ tile->x_offset, tile->y_offset }.Rotate(_loc.direction);
243 
244         curTile.x += _loc.x;
245         curTile.y += _loc.y;
246 
247         int32_t zLow = tile->z_offset + maxHeight;
248         int32_t zHigh = tile->z_clearance + zLow;
249 
250         QuarterTile quarterTile = QuarterTile{ static_cast<uint8_t>(tile->flags >> 12), 0 }.Rotate(_loc.direction);
251         const auto isTree = (sceneryEntry->flags & LARGE_SCENERY_FLAG_IS_TREE) != 0;
252         auto canBuild = MapCanConstructWithClearAt(
253             { curTile, zLow, zHigh }, &map_place_scenery_clear_func, quarterTile, GetFlags(), CREATE_CROSSING_MODE_NONE,
254             isTree);
255         if (canBuild->Error != GameActions::Status::Ok)
256         {
257             if (banner != nullptr)
258             {
259                 DeleteBanner(banner->id);
260             }
261             canBuild->ErrorTitle = STR_CANT_POSITION_THIS_HERE;
262             return canBuild;
263         }
264 
265         supportsCost += canBuild->Cost;
266 
267         const auto clearanceData = canBuild->GetData<ConstructClearResult>();
268         resultData.GroundFlags = clearanceData.GroundFlags & (ELEMENT_IS_ABOVE_GROUND | ELEMENT_IS_UNDERGROUND);
269 
270         if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST))
271         {
272             footpath_remove_litter({ curTile, zLow });
273             if (!gCheatsDisableClearanceChecks)
274             {
275                 wall_remove_at({ curTile, zLow, zHigh });
276             }
277         }
278 
279         auto* newSceneryElement = TileElementInsert<LargeSceneryElement>(
280             CoordsXYZ{ curTile.x, curTile.y, zLow }, quarterTile.GetBaseQuarterOccupied());
281         Guard::Assert(newSceneryElement != nullptr);
282         newSceneryElement->SetClearanceZ(zHigh);
283 
284         SetNewLargeSceneryElement(*newSceneryElement, tileNum);
285         if (banner != nullptr)
286         {
287             newSceneryElement->SetBannerIndex(banner->id);
288         }
289 
290         map_animation_create(MAP_ANIMATION_TYPE_LARGE_SCENERY, { curTile, zLow });
291         map_invalidate_tile_full(curTile);
292 
293         if (tileNum == 0)
294         {
295             resultData.firstTileHeight = zLow;
296         }
297     }
298 
299     // Force ride construction to recheck area
300     _currentTrackSelectionFlags |= TRACK_SELECTION_FLAG_RECHECK;
301 
302     res->Cost = (sceneryEntry->price * 10) + supportsCost;
303     res->SetData(std::move(resultData));
304 
305     return res;
306 }
307 
GetTotalNumTiles(rct_large_scenery_tile * tiles) const308 int16_t LargeSceneryPlaceAction::GetTotalNumTiles(rct_large_scenery_tile* tiles) const
309 {
310     uint32_t totalNumTiles = 0;
311     for (rct_large_scenery_tile* tile = tiles; tile->x_offset != -1; tile++)
312     {
313         totalNumTiles++;
314     }
315     return totalNumTiles;
316 }
317 
CheckMapCapacity(rct_large_scenery_tile * tiles,int16_t numTiles) const318 bool LargeSceneryPlaceAction::CheckMapCapacity(rct_large_scenery_tile* tiles, int16_t numTiles) const
319 {
320     for (rct_large_scenery_tile* tile = tiles; tile->x_offset != -1; tile++)
321     {
322         auto curTile = CoordsXY{ tile->x_offset, tile->y_offset }.Rotate(_loc.direction);
323 
324         curTile.x += _loc.x;
325         curTile.y += _loc.y;
326         if (!MapCheckCapacityAndReorganise(curTile, numTiles))
327         {
328             return false;
329         }
330     }
331     return true;
332 }
333 
GetMaxSurfaceHeight(rct_large_scenery_tile * tiles) const334 int16_t LargeSceneryPlaceAction::GetMaxSurfaceHeight(rct_large_scenery_tile* tiles) const
335 {
336     int16_t maxHeight = -1;
337     for (rct_large_scenery_tile* tile = tiles; tile->x_offset != -1; tile++)
338     {
339         auto curTile = CoordsXY{ tile->x_offset, tile->y_offset }.Rotate(_loc.direction);
340 
341         curTile.x += _loc.x;
342         curTile.y += _loc.y;
343 
344         if (!map_is_location_valid(curTile))
345         {
346             continue;
347         }
348 
349         auto* surfaceElement = map_get_surface_element_at(curTile);
350         if (surfaceElement == nullptr)
351             continue;
352 
353         int32_t baseZ = surfaceElement->GetBaseZ();
354         int32_t slope = surfaceElement->GetSlope();
355 
356         if ((slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) != TILE_ELEMENT_SLOPE_FLAT)
357         {
358             baseZ += LAND_HEIGHT_STEP;
359             if (slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
360             {
361                 baseZ += LAND_HEIGHT_STEP;
362             }
363         }
364 
365         if (baseZ > maxHeight)
366         {
367             maxHeight = baseZ;
368         }
369     }
370     return maxHeight;
371 }
372 
SetNewLargeSceneryElement(LargeSceneryElement & sceneryElement,uint8_t tileNum) const373 void LargeSceneryPlaceAction::SetNewLargeSceneryElement(LargeSceneryElement& sceneryElement, uint8_t tileNum) const
374 {
375     sceneryElement.SetDirection(_loc.direction);
376     sceneryElement.SetEntryIndex(_sceneryType);
377     sceneryElement.SetSequenceIndex(tileNum);
378     sceneryElement.SetPrimaryColour(_primaryColour);
379     sceneryElement.SetSecondaryColour(_secondaryColour);
380 
381     if (GetFlags() & GAME_COMMAND_FLAG_GHOST)
382     {
383         sceneryElement.SetGhost(true);
384     }
385 }
386