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 "MapAnimation.h"
11 
12 #include "../Context.h"
13 #include "../Game.h"
14 #include "../interface/Viewport.h"
15 #include "../object/StationObject.h"
16 #include "../peep/Peep.h"
17 #include "../ride/Ride.h"
18 #include "../ride/RideData.h"
19 #include "../ride/Track.h"
20 #include "../world/Wall.h"
21 #include "Banner.h"
22 #include "EntityList.h"
23 #include "Footpath.h"
24 #include "LargeScenery.h"
25 #include "Map.h"
26 #include "Scenery.h"
27 #include "SmallScenery.h"
28 
29 using map_animation_invalidate_event_handler = bool (*)(const CoordsXYZ& loc);
30 
31 static std::vector<MapAnimation> _mapAnimations;
32 
33 constexpr size_t MAX_ANIMATED_OBJECTS = 2000;
34 
35 static bool InvalidateMapAnimation(const MapAnimation& obj);
36 
DoesAnimationExist(int32_t type,const CoordsXYZ & location)37 static bool DoesAnimationExist(int32_t type, const CoordsXYZ& location)
38 {
39     for (const auto& a : _mapAnimations)
40     {
41         if (a.type == type && a.location == location)
42         {
43             // Animation already exists
44             return true;
45         }
46     }
47     return false;
48 }
49 
map_animation_create(int32_t type,const CoordsXYZ & loc)50 void map_animation_create(int32_t type, const CoordsXYZ& loc)
51 {
52     if (!DoesAnimationExist(type, loc))
53     {
54         if (_mapAnimations.size() < MAX_ANIMATED_OBJECTS)
55         {
56             // Create new animation
57             _mapAnimations.push_back({ static_cast<uint8_t>(type), loc });
58         }
59         else
60         {
61             log_error("Exceeded the maximum number of animations");
62         }
63     }
64 }
65 
66 /**
67  *
68  *  rct2: 0x0068AFAD
69  */
map_animation_invalidate_all()70 void map_animation_invalidate_all()
71 {
72     auto it = _mapAnimations.begin();
73     while (it != _mapAnimations.end())
74     {
75         if (InvalidateMapAnimation(*it))
76         {
77             // Map animation has finished, remove it
78             it = _mapAnimations.erase(it);
79         }
80         else
81         {
82             it++;
83         }
84     }
85 }
86 
87 /**
88  *
89  *  rct2: 0x00666670
90  */
map_animation_invalidate_ride_entrance(const CoordsXYZ & loc)91 static bool map_animation_invalidate_ride_entrance(const CoordsXYZ& loc)
92 {
93     TileCoordsXYZ tileLoc{ loc };
94     auto tileElement = map_get_first_element_at(loc);
95     if (tileElement == nullptr)
96         return true;
97     do
98     {
99         if (tileElement->base_height != tileLoc.z)
100             continue;
101         if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
102             continue;
103         if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_ENTRANCE)
104             continue;
105 
106         auto ride = get_ride(tileElement->AsEntrance()->GetRideIndex());
107         if (ride != nullptr)
108         {
109             auto stationObj = ride_get_station_object(ride);
110             if (stationObj != nullptr)
111             {
112                 int32_t height = loc.z + stationObj->Height + 8;
113                 map_invalidate_tile_zoom1({ loc, height, height + 16 });
114             }
115         }
116         return false;
117     } while (!(tileElement++)->IsLastForTile());
118 
119     return true;
120 }
121 
122 /**
123  *
124  *  rct2: 0x006A7BD4
125  */
map_animation_invalidate_queue_banner(const CoordsXYZ & loc)126 static bool map_animation_invalidate_queue_banner(const CoordsXYZ& loc)
127 {
128     TileCoordsXYZ tileLoc{ loc };
129     TileElement* tileElement;
130 
131     tileElement = map_get_first_element_at(loc);
132     if (tileElement == nullptr)
133         return true;
134     do
135     {
136         if (tileElement->base_height != tileLoc.z)
137             continue;
138         if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
139             continue;
140         if (!(tileElement->AsPath()->IsQueue()))
141             continue;
142         if (!tileElement->AsPath()->HasQueueBanner())
143             continue;
144 
145         int32_t direction = (tileElement->AsPath()->GetQueueBannerDirection() + get_current_rotation()) & 3;
146         if (direction == TILE_ELEMENT_DIRECTION_NORTH || direction == TILE_ELEMENT_DIRECTION_EAST)
147         {
148             map_invalidate_tile_zoom1({ loc, loc.z + 16, loc.z + 30 });
149         }
150         return false;
151     } while (!(tileElement++)->IsLastForTile());
152 
153     return true;
154 }
155 
156 /**
157  *
158  *  rct2: 0x006E32C9
159  */
map_animation_invalidate_small_scenery(const CoordsXYZ & loc)160 static bool map_animation_invalidate_small_scenery(const CoordsXYZ& loc)
161 {
162     TileCoordsXYZ tileLoc{ loc };
163 
164     auto tileElement = map_get_first_element_at(loc);
165     if (tileElement == nullptr)
166         return true;
167     do
168     {
169         if (tileElement->base_height != tileLoc.z)
170             continue;
171         if (tileElement->GetType() != TILE_ELEMENT_TYPE_SMALL_SCENERY)
172             continue;
173         if (tileElement->IsGhost())
174             continue;
175 
176         auto* sceneryEntry = tileElement->AsSmallScenery()->GetEntry();
177         if (sceneryEntry == nullptr)
178             continue;
179 
180         if (sceneryEntry->HasFlag(
181                 SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1 | SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4 | SMALL_SCENERY_FLAG_SWAMP_GOO
182                 | SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS))
183         {
184             map_invalidate_tile_zoom1({ loc, loc.z, tileElement->GetClearanceZ() });
185             return false;
186         }
187 
188         if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_IS_CLOCK))
189         {
190             // Peep, looking at scenery
191             if (!(gCurrentTicks & 0x3FF) && game_is_not_paused())
192             {
193                 int32_t direction = tileElement->GetDirection();
194                 auto quad = EntityTileList<Peep>(CoordsXY{ loc } - CoordsDirectionDelta[direction]);
195                 for (auto peep : quad)
196                 {
197                     if (peep->State != PeepState::Walking)
198                         continue;
199                     if (peep->z != loc.z)
200                         continue;
201                     if (peep->Action < PeepActionType::Idle)
202                         continue;
203 
204                     peep->Action = PeepActionType::CheckTime;
205                     peep->ActionFrame = 0;
206                     peep->ActionSpriteImageOffset = 0;
207                     peep->UpdateCurrentActionSpriteType();
208                     peep->Invalidate();
209                     break;
210                 }
211             }
212             map_invalidate_tile_zoom1({ loc, loc.z, tileElement->GetClearanceZ() });
213             return false;
214         }
215 
216     } while (!(tileElement++)->IsLastForTile());
217     return true;
218 }
219 
220 /**
221  *
222  *  rct2: 0x00666C63
223  */
map_animation_invalidate_park_entrance(const CoordsXYZ & loc)224 static bool map_animation_invalidate_park_entrance(const CoordsXYZ& loc)
225 {
226     TileCoordsXYZ tileLoc{ loc };
227     TileElement* tileElement;
228 
229     tileElement = map_get_first_element_at(loc);
230     if (tileElement == nullptr)
231         return true;
232     do
233     {
234         if (tileElement->base_height != tileLoc.z)
235             continue;
236         if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
237             continue;
238         if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE)
239             continue;
240         if (tileElement->AsEntrance()->GetSequenceIndex())
241             continue;
242 
243         map_invalidate_tile_zoom1({ loc, loc.z + 32, loc.z + 64 });
244         return false;
245     } while (!(tileElement++)->IsLastForTile());
246 
247     return true;
248 }
249 
250 /**
251  *
252  *  rct2: 0x006CE29E
253  */
map_animation_invalidate_track_waterfall(const CoordsXYZ & loc)254 static bool map_animation_invalidate_track_waterfall(const CoordsXYZ& loc)
255 {
256     TileCoordsXYZ tileLoc{ loc };
257     TileElement* tileElement;
258 
259     tileElement = map_get_first_element_at(loc);
260     if (tileElement == nullptr)
261         return true;
262     do
263     {
264         if (tileElement->base_height != tileLoc.z)
265             continue;
266         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
267             continue;
268 
269         if (tileElement->AsTrack()->GetTrackType() == TrackElemType::Waterfall)
270         {
271             map_invalidate_tile_zoom1({ loc, loc.z + 14, loc.z + 46 });
272             return false;
273         }
274     } while (!(tileElement++)->IsLastForTile());
275 
276     return true;
277 }
278 
279 /**
280  *
281  *  rct2: 0x006CE2F3
282  */
map_animation_invalidate_track_rapids(const CoordsXYZ & loc)283 static bool map_animation_invalidate_track_rapids(const CoordsXYZ& loc)
284 {
285     TileCoordsXYZ tileLoc{ loc };
286     TileElement* tileElement;
287 
288     tileElement = map_get_first_element_at(loc);
289     if (tileElement == nullptr)
290         return true;
291     do
292     {
293         if (tileElement->base_height != tileLoc.z)
294             continue;
295         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
296             continue;
297 
298         if (tileElement->AsTrack()->GetTrackType() == TrackElemType::Rapids)
299         {
300             map_invalidate_tile_zoom1({ loc, loc.z + 14, loc.z + 18 });
301             return false;
302         }
303     } while (!(tileElement++)->IsLastForTile());
304 
305     return true;
306 }
307 
308 /**
309  *
310  *  rct2: 0x006CE39D
311  */
map_animation_invalidate_track_onridephoto(const CoordsXYZ & loc)312 static bool map_animation_invalidate_track_onridephoto(const CoordsXYZ& loc)
313 {
314     TileCoordsXYZ tileLoc{ loc };
315     TileElement* tileElement;
316 
317     tileElement = map_get_first_element_at(loc);
318     if (tileElement == nullptr)
319         return true;
320     do
321     {
322         if (tileElement->base_height != tileLoc.z)
323             continue;
324         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
325             continue;
326 
327         if (tileElement->AsTrack()->GetTrackType() == TrackElemType::OnRidePhoto)
328         {
329             map_invalidate_tile_zoom1({ loc, loc.z, tileElement->GetClearanceZ() });
330             if (game_is_paused())
331             {
332                 return false;
333             }
334             if (tileElement->AsTrack()->IsTakingPhoto())
335             {
336                 tileElement->AsTrack()->DecrementPhotoTimeout();
337                 return false;
338             }
339 
340             return true;
341         }
342     } while (!(tileElement++)->IsLastForTile());
343 
344     return true;
345 }
346 
347 /**
348  *
349  *  rct2: 0x006CE348
350  */
map_animation_invalidate_track_whirlpool(const CoordsXYZ & loc)351 static bool map_animation_invalidate_track_whirlpool(const CoordsXYZ& loc)
352 {
353     TileCoordsXYZ tileLoc{ loc };
354     TileElement* tileElement;
355 
356     tileElement = map_get_first_element_at(loc);
357     if (tileElement == nullptr)
358         return true;
359     do
360     {
361         if (tileElement->base_height != tileLoc.z)
362             continue;
363         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
364             continue;
365 
366         if (tileElement->AsTrack()->GetTrackType() == TrackElemType::Whirlpool)
367         {
368             map_invalidate_tile_zoom1({ loc, loc.z + 14, loc.z + 18 });
369             return false;
370         }
371     } while (!(tileElement++)->IsLastForTile());
372 
373     return true;
374 }
375 
376 /**
377  *
378  *  rct2: 0x006CE3FA
379  */
map_animation_invalidate_track_spinningtunnel(const CoordsXYZ & loc)380 static bool map_animation_invalidate_track_spinningtunnel(const CoordsXYZ& loc)
381 {
382     TileCoordsXYZ tileLoc{ loc };
383     TileElement* tileElement;
384 
385     tileElement = map_get_first_element_at(loc);
386     if (tileElement == nullptr)
387         return true;
388     do
389     {
390         if (tileElement->base_height != tileLoc.z)
391             continue;
392         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
393             continue;
394 
395         if (tileElement->AsTrack()->GetTrackType() == TrackElemType::SpinningTunnel)
396         {
397             map_invalidate_tile_zoom1({ loc, loc.z + 14, loc.z + 32 });
398             return false;
399         }
400     } while (!(tileElement++)->IsLastForTile());
401 
402     return true;
403 }
404 
405 /**
406  *
407  *  rct2: 0x0068DF8F
408  */
map_animation_invalidate_remove(const CoordsXYZ & loc)409 static bool map_animation_invalidate_remove([[maybe_unused]] const CoordsXYZ& loc)
410 {
411     return true;
412 }
413 
414 /**
415  *
416  *  rct2: 0x006BA2BB
417  */
map_animation_invalidate_banner(const CoordsXYZ & loc)418 static bool map_animation_invalidate_banner(const CoordsXYZ& loc)
419 {
420     TileCoordsXYZ tileLoc{ loc };
421     TileElement* tileElement;
422 
423     tileElement = map_get_first_element_at(loc);
424     if (tileElement == nullptr)
425         return true;
426     do
427     {
428         if (tileElement->base_height != tileLoc.z)
429             continue;
430         if (tileElement->GetType() != TILE_ELEMENT_TYPE_BANNER)
431             continue;
432         map_invalidate_tile_zoom1({ loc, loc.z, loc.z + 16 });
433         return false;
434     } while (!(tileElement++)->IsLastForTile());
435 
436     return true;
437 }
438 
439 /**
440  *
441  *  rct2: 0x006B94EB
442  */
map_animation_invalidate_large_scenery(const CoordsXYZ & loc)443 static bool map_animation_invalidate_large_scenery(const CoordsXYZ& loc)
444 {
445     TileCoordsXYZ tileLoc{ loc };
446     TileElement* tileElement;
447 
448     bool wasInvalidated = false;
449     tileElement = map_get_first_element_at(loc);
450     if (tileElement == nullptr)
451         return true;
452     do
453     {
454         if (tileElement->base_height != tileLoc.z)
455             continue;
456         if (tileElement->GetType() != TILE_ELEMENT_TYPE_LARGE_SCENERY)
457             continue;
458 
459         auto* sceneryEntry = tileElement->AsLargeScenery()->GetEntry();
460         if (sceneryEntry->flags & LARGE_SCENERY_FLAG_ANIMATED)
461         {
462             map_invalidate_tile_zoom1({ loc, loc.z, loc.z + 16 });
463             wasInvalidated = true;
464         }
465     } while (!(tileElement++)->IsLastForTile());
466 
467     return !wasInvalidated;
468 }
469 
470 /**
471  *
472  *  rct2: 0x006E5B50
473  */
map_animation_invalidate_wall_door(const CoordsXYZ & loc)474 static bool map_animation_invalidate_wall_door(const CoordsXYZ& loc)
475 {
476     TileCoordsXYZ tileLoc{ loc };
477     TileElement* tileElement;
478 
479     if (gCurrentTicks & 1)
480         return false;
481 
482     bool removeAnimation = true;
483     tileElement = map_get_first_element_at(loc);
484     if (tileElement == nullptr)
485         return removeAnimation;
486     do
487     {
488         if (tileElement->base_height != tileLoc.z)
489             continue;
490         if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
491             continue;
492 
493         auto* wallEntry = tileElement->AsWall()->GetEntry();
494         if (wallEntry == nullptr || !(wallEntry->flags & WALL_SCENERY_IS_DOOR))
495             continue;
496 
497         if (game_is_paused())
498         {
499             return false;
500         }
501 
502         bool invalidate = false;
503 
504         uint8_t currentFrame = tileElement->AsWall()->GetAnimationFrame();
505         if (currentFrame != 0)
506         {
507             if (currentFrame == 15)
508             {
509                 currentFrame = 0;
510             }
511             else
512             {
513                 removeAnimation = false;
514                 if (currentFrame != 5)
515                 {
516                     currentFrame++;
517                     if (currentFrame == 13 && !(wallEntry->flags & WALL_SCENERY_LONG_DOOR_ANIMATION))
518                         currentFrame = 15;
519 
520                     invalidate = true;
521                 }
522             }
523         }
524         tileElement->AsWall()->SetAnimationFrame(currentFrame);
525         if (invalidate)
526         {
527             map_invalidate_tile_zoom1({ loc, loc.z, loc.z + 32 });
528         }
529     } while (!(tileElement++)->IsLastForTile());
530 
531     return removeAnimation;
532 }
533 
534 /**
535  *
536  *  rct2: 0x006E5EE4
537  */
map_animation_invalidate_wall(const CoordsXYZ & loc)538 static bool map_animation_invalidate_wall(const CoordsXYZ& loc)
539 {
540     TileCoordsXYZ tileLoc{ loc };
541     TileElement* tileElement;
542 
543     bool wasInvalidated = false;
544     tileElement = map_get_first_element_at(loc);
545     if (tileElement == nullptr)
546         return true;
547     do
548     {
549         if (tileElement->base_height != tileLoc.z)
550             continue;
551         if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
552             continue;
553 
554         auto* wallEntry = tileElement->AsWall()->GetEntry();
555 
556         if (wallEntry == nullptr
557             || (!(wallEntry->flags2 & WALL_SCENERY_2_ANIMATED) && wallEntry->scrolling_mode == SCROLLING_MODE_NONE))
558             continue;
559 
560         map_invalidate_tile_zoom1({ loc, loc.z, loc.z + 16 });
561         wasInvalidated = true;
562     } while (!(tileElement++)->IsLastForTile());
563 
564     return !wasInvalidated;
565 }
566 
567 /**
568  *
569  *  rct2: 0x009819DC
570  */
571 static constexpr const map_animation_invalidate_event_handler _animatedObjectEventHandlers[MAP_ANIMATION_TYPE_COUNT] = {
572     map_animation_invalidate_ride_entrance,
573     map_animation_invalidate_queue_banner,
574     map_animation_invalidate_small_scenery,
575     map_animation_invalidate_park_entrance,
576     map_animation_invalidate_track_waterfall,
577     map_animation_invalidate_track_rapids,
578     map_animation_invalidate_track_onridephoto,
579     map_animation_invalidate_track_whirlpool,
580     map_animation_invalidate_track_spinningtunnel,
581     map_animation_invalidate_remove,
582     map_animation_invalidate_banner,
583     map_animation_invalidate_large_scenery,
584     map_animation_invalidate_wall_door,
585     map_animation_invalidate_wall,
586 };
587 
588 /**
589  * @returns true if the animation should be removed.
590  */
InvalidateMapAnimation(const MapAnimation & a)591 static bool InvalidateMapAnimation(const MapAnimation& a)
592 {
593     if (a.type < std::size(_animatedObjectEventHandlers))
594     {
595         return _animatedObjectEventHandlers[a.type](a.location);
596     }
597     return true;
598 }
599 
GetMapAnimations()600 const std::vector<MapAnimation>& GetMapAnimations()
601 {
602     return _mapAnimations;
603 }
604 
ClearMapAnimations()605 static void ClearMapAnimations()
606 {
607     _mapAnimations.clear();
608 }
609 
AutoCreateMapAnimations()610 void AutoCreateMapAnimations()
611 {
612     ClearMapAnimations();
613 
614     tile_element_iterator it;
615     tile_element_iterator_begin(&it);
616     while (tile_element_iterator_next(&it))
617     {
618         auto el = it.element;
619         auto loc = CoordsXYZ{ TileCoordsXY(it.x, it.y).ToCoordsXY(), el->GetBaseZ() };
620         switch (el->GetType())
621         {
622             case TILE_ELEMENT_TYPE_BANNER:
623                 map_animation_create(MAP_ANIMATION_TYPE_BANNER, loc);
624                 break;
625             case TILE_ELEMENT_TYPE_WALL:
626             {
627                 auto wallEl = el->AsWall();
628                 auto* entry = wallEl->GetEntry();
629                 if (entry != nullptr
630                     && ((entry->flags2 & WALL_SCENERY_2_ANIMATED) || entry->scrolling_mode != SCROLLING_MODE_NONE))
631                 {
632                     map_animation_create(MAP_ANIMATION_TYPE_WALL, loc);
633                 }
634                 break;
635             }
636             case TILE_ELEMENT_TYPE_SMALL_SCENERY:
637             {
638                 auto sceneryEl = el->AsSmallScenery();
639                 auto* sceneryEntry = sceneryEl->GetEntry();
640                 if (sceneryEntry != nullptr && sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_ANIMATED))
641                 {
642                     map_animation_create(MAP_ANIMATION_TYPE_SMALL_SCENERY, loc);
643                 }
644                 break;
645             }
646             case TILE_ELEMENT_TYPE_LARGE_SCENERY:
647             {
648                 auto sceneryEl = el->AsLargeScenery();
649                 auto entry = sceneryEl->GetEntry();
650                 if (entry != nullptr && (entry->flags & LARGE_SCENERY_FLAG_ANIMATED))
651                 {
652                     map_animation_create(MAP_ANIMATION_TYPE_LARGE_SCENERY, loc);
653                 }
654                 break;
655             }
656             case TILE_ELEMENT_TYPE_PATH:
657             {
658                 auto path = el->AsPath();
659                 if (path->HasQueueBanner())
660                 {
661                     map_animation_create(MAP_ANIMATION_TYPE_QUEUE_BANNER, loc);
662                 }
663                 break;
664             }
665             case TILE_ELEMENT_TYPE_ENTRANCE:
666             {
667                 auto entrance = el->AsEntrance();
668                 switch (entrance->GetEntranceType())
669                 {
670                     case ENTRANCE_TYPE_PARK_ENTRANCE:
671                         if (entrance->GetSequenceIndex() == 0)
672                         {
673                             map_animation_create(MAP_ANIMATION_TYPE_PARK_ENTRANCE, loc);
674                         }
675                         break;
676                     case ENTRANCE_TYPE_RIDE_ENTRANCE:
677                         map_animation_create(MAP_ANIMATION_TYPE_RIDE_ENTRANCE, loc);
678                         break;
679                 }
680                 break;
681             }
682             case TILE_ELEMENT_TYPE_TRACK:
683             {
684                 auto track = el->AsTrack();
685                 switch (track->GetTrackType())
686                 {
687                     case TrackElemType::Waterfall:
688                         map_animation_create(MAP_ANIMATION_TYPE_TRACK_WATERFALL, loc);
689                         break;
690                     case TrackElemType::Rapids:
691                         map_animation_create(MAP_ANIMATION_TYPE_TRACK_RAPIDS, loc);
692                         break;
693                     case TrackElemType::Whirlpool:
694                         map_animation_create(MAP_ANIMATION_TYPE_TRACK_WHIRLPOOL, loc);
695                         break;
696                     case TrackElemType::SpinningTunnel:
697                         map_animation_create(MAP_ANIMATION_TYPE_TRACK_SPINNINGTUNNEL, loc);
698                         break;
699                 }
700                 break;
701             }
702         }
703     }
704 }
705