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