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 "Scenery.h"
11
12 #include "../Cheats.h"
13 #include "../Context.h"
14 #include "../Game.h"
15 #include "../OpenRCT2.h"
16 #include "../actions/BannerRemoveAction.h"
17 #include "../actions/FootpathAdditionRemoveAction.h"
18 #include "../actions/LargeSceneryRemoveAction.h"
19 #include "../actions/SmallSceneryRemoveAction.h"
20 #include "../actions/WallRemoveAction.h"
21 #include "../common.h"
22 #include "../localisation/Localisation.h"
23 #include "../network/network.h"
24 #include "../object/ObjectList.h"
25 #include "../object/ObjectManager.h"
26 #include "../scenario/Scenario.h"
27 #include "Climate.h"
28 #include "Footpath.h"
29 #include "Fountain.h"
30 #include "LargeScenery.h"
31 #include "Map.h"
32 #include "Park.h"
33 #include "SmallScenery.h"
34 #include "Wall.h"
35
36 uint8_t gSceneryQuadrant;
37
38 money32 gSceneryPlaceCost;
39 ScenerySelection gSceneryPlaceObject;
40 int16_t gSceneryPlaceZ;
41 uint8_t gSceneryPlaceRotation;
42
43 uint8_t gSceneryGhostType;
44 CoordsXYZ gSceneryGhostPosition;
45 uint8_t gSceneryGhostWallRotation;
46
47 int16_t gSceneryShiftPressed;
48 int16_t gSceneryShiftPressX;
49 int16_t gSceneryShiftPressY;
50 int16_t gSceneryShiftPressZOffset;
51
52 int16_t gSceneryCtrlPressed;
53 int16_t gSceneryCtrlPressZ;
54
55 money64 gClearSceneryCost;
56
57 static std::vector<ScenerySelection> _restrictedScenery;
58
59 // rct2: 0x009A3E74
60 const CoordsXY SceneryQuadrantOffsets[] = {
61 { 7, 7 },
62 { 7, 23 },
63 { 23, 23 },
64 { 23, 7 },
65 };
66
scenery_update_tile(const CoordsXY & sceneryPos)67 void scenery_update_tile(const CoordsXY& sceneryPos)
68 {
69 TileElement* tileElement;
70
71 tileElement = map_get_first_element_at(sceneryPos);
72 if (tileElement == nullptr)
73 return;
74 do
75 {
76 // Ghosts are purely this-client-side and should not cause any interaction,
77 // as that may lead to a desync.
78 if (network_get_mode() != NETWORK_MODE_NONE)
79 {
80 if (tileElement->IsGhost())
81 continue;
82 }
83
84 if (tileElement->GetType() == TILE_ELEMENT_TYPE_SMALL_SCENERY)
85 {
86 tileElement->AsSmallScenery()->UpdateAge(sceneryPos);
87 }
88 else if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
89 {
90 if (tileElement->AsPath()->HasAddition() && !tileElement->AsPath()->AdditionIsGhost())
91 {
92 auto* pathAddEntry = tileElement->AsPath()->GetAdditionEntry();
93 if (pathAddEntry != nullptr)
94 {
95 if (pathAddEntry->flags & PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER)
96 {
97 JumpingFountain::StartAnimation(JumpingFountainType::Water, sceneryPos, tileElement);
98 }
99 else if (pathAddEntry->flags & PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW)
100 {
101 JumpingFountain::StartAnimation(JumpingFountainType::Snow, sceneryPos, tileElement);
102 }
103 }
104 }
105 }
106 } while (!(tileElement++)->IsLastForTile());
107 }
108
109 /**
110 *
111 * rct2: 0x006E33D9
112 */
UpdateAge(const CoordsXY & sceneryPos)113 void SmallSceneryElement::UpdateAge(const CoordsXY& sceneryPos)
114 {
115 auto* sceneryEntry = GetEntry();
116 if (sceneryEntry == nullptr)
117 {
118 return;
119 }
120
121 if (gCheatsDisablePlantAging && sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_CAN_BE_WATERED))
122 {
123 return;
124 }
125
126 if (!sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_CAN_BE_WATERED) || WeatherIsDry(gClimateCurrent.Weather) || GetAge() < 5)
127 {
128 IncreaseAge(sceneryPos);
129 return;
130 }
131
132 // Check map elements above, presumably to see if map element is blocked from weather
133 TileElement* tileElementAbove = reinterpret_cast<TileElement*>(this);
134 // Change from original: RCT2 only checked for the first three quadrants, which was very likely to be a bug.
135 while (!(tileElementAbove->GetOccupiedQuadrants()))
136 {
137 tileElementAbove++;
138
139 // Ghosts are purely this-client-side and should not cause any interaction,
140 // as that may lead to a desync.
141 if (tileElementAbove->IsGhost())
142 continue;
143
144 switch (tileElementAbove->GetType())
145 {
146 case TILE_ELEMENT_TYPE_LARGE_SCENERY:
147 case TILE_ELEMENT_TYPE_ENTRANCE:
148 case TILE_ELEMENT_TYPE_PATH:
149 map_invalidate_tile_zoom1({ sceneryPos, tileElementAbove->GetBaseZ(), tileElementAbove->GetClearanceZ() });
150 IncreaseAge(sceneryPos);
151 return;
152 case TILE_ELEMENT_TYPE_SMALL_SCENERY:
153 sceneryEntry = tileElementAbove->AsSmallScenery()->GetEntry();
154 if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE))
155 {
156 IncreaseAge(sceneryPos);
157 return;
158 }
159 break;
160 }
161 }
162
163 // Reset age / water plant
164 SetAge(0);
165 map_invalidate_tile_zoom1({ sceneryPos, GetBaseZ(), GetClearanceZ() });
166 }
167
168 /**
169 *
170 * rct2: 0x006E2712
171 */
scenery_remove_ghost_tool_placement()172 void scenery_remove_ghost_tool_placement()
173 {
174 if (gSceneryGhostType & SCENERY_GHOST_FLAG_0)
175 {
176 gSceneryGhostType &= ~SCENERY_GHOST_FLAG_0;
177
178 auto removeSceneryAction = SmallSceneryRemoveAction(
179 gSceneryGhostPosition, gSceneryQuadrant, gSceneryPlaceObject.EntryIndex);
180 removeSceneryAction.SetFlags(
181 GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
182 removeSceneryAction.Execute();
183 }
184
185 if (gSceneryGhostType & SCENERY_GHOST_FLAG_1)
186 {
187 gSceneryGhostType &= ~SCENERY_GHOST_FLAG_1;
188 TileElement* tileElement = map_get_first_element_at(gSceneryGhostPosition);
189
190 do
191 {
192 if (tileElement == nullptr)
193 break;
194
195 if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
196 continue;
197
198 if (tileElement->GetBaseZ() != gSceneryGhostPosition.z)
199 continue;
200
201 auto footpathAdditionRemoveAction = FootpathAdditionRemoveAction(gSceneryGhostPosition);
202 footpathAdditionRemoveAction.SetFlags(GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_GHOST);
203 GameActions::Execute(&footpathAdditionRemoveAction);
204 break;
205 } while (!(tileElement++)->IsLastForTile());
206 }
207
208 if (gSceneryGhostType & SCENERY_GHOST_FLAG_2)
209 {
210 gSceneryGhostType &= ~SCENERY_GHOST_FLAG_2;
211
212 CoordsXYZD wallLocation = { gSceneryGhostPosition, gSceneryGhostWallRotation };
213 auto wallRemoveAction = WallRemoveAction(wallLocation);
214 wallRemoveAction.SetFlags(GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_PATH_SCENERY);
215 wallRemoveAction.Execute();
216 }
217
218 if (gSceneryGhostType & SCENERY_GHOST_FLAG_3)
219 {
220 gSceneryGhostType &= ~SCENERY_GHOST_FLAG_3;
221
222 auto removeSceneryAction = LargeSceneryRemoveAction({ gSceneryGhostPosition, gSceneryPlaceRotation }, 0);
223 removeSceneryAction.SetFlags(
224 GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
225 | GAME_COMMAND_FLAG_NO_SPEND);
226 removeSceneryAction.Execute();
227 }
228
229 if (gSceneryGhostType & SCENERY_GHOST_FLAG_4)
230 {
231 gSceneryGhostType &= ~SCENERY_GHOST_FLAG_4;
232
233 auto removeSceneryAction = BannerRemoveAction({ gSceneryGhostPosition, gSceneryPlaceRotation });
234 removeSceneryAction.SetFlags(
235 GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND);
236 GameActions::Execute(&removeSceneryAction);
237 }
238 }
239
get_wall_entry(ObjectEntryIndex entryIndex)240 WallSceneryEntry* get_wall_entry(ObjectEntryIndex entryIndex)
241 {
242 WallSceneryEntry* result = nullptr;
243 auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
244 auto obj = objMgr.GetLoadedObject(ObjectType::Walls, entryIndex);
245 if (obj != nullptr)
246 {
247 result = static_cast<WallSceneryEntry*>(obj->GetLegacyData());
248 }
249 return result;
250 }
251
get_banner_entry(ObjectEntryIndex entryIndex)252 BannerSceneryEntry* get_banner_entry(ObjectEntryIndex entryIndex)
253 {
254 BannerSceneryEntry* result = nullptr;
255 auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
256 auto obj = objMgr.GetLoadedObject(ObjectType::Banners, entryIndex);
257 if (obj != nullptr)
258 {
259 result = static_cast<BannerSceneryEntry*>(obj->GetLegacyData());
260 }
261 return result;
262 }
263
get_footpath_item_entry(ObjectEntryIndex entryIndex)264 PathBitEntry* get_footpath_item_entry(ObjectEntryIndex entryIndex)
265 {
266 PathBitEntry* result = nullptr;
267 auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
268 auto obj = objMgr.GetLoadedObject(ObjectType::PathBits, entryIndex);
269 if (obj != nullptr)
270 {
271 result = static_cast<PathBitEntry*>(obj->GetLegacyData());
272 }
273 return result;
274 }
275
get_scenery_group_entry(ObjectEntryIndex entryIndex)276 rct_scenery_group_entry* get_scenery_group_entry(ObjectEntryIndex entryIndex)
277 {
278 rct_scenery_group_entry* result = nullptr;
279 auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
280 auto obj = objMgr.GetLoadedObject(ObjectType::SceneryGroup, entryIndex);
281 if (obj != nullptr)
282 {
283 result = static_cast<rct_scenery_group_entry*>(obj->GetLegacyData());
284 }
285 return result;
286 }
287
wall_entry_get_door_sound(const WallSceneryEntry * wallEntry)288 int32_t wall_entry_get_door_sound(const WallSceneryEntry* wallEntry)
289 {
290 return (wallEntry->flags2 & WALL_SCENERY_2_DOOR_SOUND_MASK) >> WALL_SCENERY_2_DOOR_SOUND_SHIFT;
291 }
292
IsSceneryAvailableToBuild(const ScenerySelection & item)293 bool IsSceneryAvailableToBuild(const ScenerySelection& item)
294 {
295 // All scenery can be built when in the scenario editor
296 if (gScreenFlags & SCREEN_FLAGS_EDITOR)
297 {
298 return true;
299 }
300
301 if (!gCheatsIgnoreResearchStatus)
302 {
303 if (!scenery_is_invented(item))
304 {
305 return false;
306 }
307 }
308
309 if (!gCheatsSandboxMode)
310 {
311 if (IsSceneryItemRestricted(item))
312 {
313 return false;
314 }
315 }
316
317 return true;
318 }
319
GetMaxObjectsForSceneryType(const uint8_t sceneryType)320 static size_t GetMaxObjectsForSceneryType(const uint8_t sceneryType)
321 {
322 switch (sceneryType)
323 {
324 case SCENERY_TYPE_SMALL:
325 return MAX_SMALL_SCENERY_OBJECTS;
326 case SCENERY_TYPE_PATH_ITEM:
327 return MAX_PATH_ADDITION_OBJECTS;
328 case SCENERY_TYPE_WALL:
329 return MAX_WALL_SCENERY_OBJECTS;
330 case SCENERY_TYPE_LARGE:
331 return MAX_LARGE_SCENERY_OBJECTS;
332 case SCENERY_TYPE_BANNER:
333 return MAX_BANNER_OBJECTS;
334 default:
335 return 0;
336 }
337 }
338
GetSceneryEntry(const ScenerySelection & item)339 static SceneryEntryBase* GetSceneryEntry(const ScenerySelection& item)
340 {
341 switch (item.SceneryType)
342 {
343 case SCENERY_TYPE_SMALL:
344 return get_small_scenery_entry(item.EntryIndex);
345 case SCENERY_TYPE_PATH_ITEM:
346 return get_footpath_item_entry(item.EntryIndex);
347 case SCENERY_TYPE_WALL:
348 return get_wall_entry(item.EntryIndex);
349 case SCENERY_TYPE_LARGE:
350 return get_large_scenery_entry(item.EntryIndex);
351 case SCENERY_TYPE_BANNER:
352 return get_banner_entry(item.EntryIndex);
353 default:
354 return nullptr;
355 }
356 }
357
IsSceneryItemRestricted(const ScenerySelection & item)358 bool IsSceneryItemRestricted(const ScenerySelection& item)
359 {
360 return std::find(std::begin(_restrictedScenery), std::end(_restrictedScenery), item) != std::end(_restrictedScenery);
361 }
362
ClearRestrictedScenery()363 void ClearRestrictedScenery()
364 {
365 _restrictedScenery.clear();
366 }
367
GetRestrictedScenery()368 std::vector<ScenerySelection>& GetRestrictedScenery()
369 {
370 return _restrictedScenery;
371 }
372
RestrictAllMiscScenery()373 void RestrictAllMiscScenery()
374 {
375 std::vector<ScenerySelection> nonMiscScenery;
376 for (ObjectEntryIndex i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++)
377 {
378 const auto* sgEntry = get_scenery_group_entry(i);
379 if (sgEntry != nullptr)
380 {
381 for (size_t j = 0; j < sgEntry->entry_count; j++)
382 {
383 nonMiscScenery.push_back(sgEntry->scenery_entries[j]);
384 }
385 }
386 }
387 for (uint8_t sceneryType = SCENERY_TYPE_SMALL; sceneryType < SCENERY_TYPE_COUNT; sceneryType++)
388 {
389 const auto maxObjects = GetMaxObjectsForSceneryType(sceneryType);
390 for (ObjectEntryIndex i = 0; i < maxObjects; i++)
391 {
392 const ScenerySelection sceneryItem = { sceneryType, i };
393 const auto* sceneryEntry = GetSceneryEntry(sceneryItem);
394 if (sceneryEntry != nullptr)
395 {
396 if (std::find(std::begin(nonMiscScenery), std::end(nonMiscScenery), sceneryItem) == std::end(nonMiscScenery))
397 {
398 _restrictedScenery.push_back(sceneryItem);
399 }
400 }
401 }
402 }
403 }
404