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