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