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 "RideDemolishAction.h"
11 
12 #include "../Cheats.h"
13 #include "../Context.h"
14 #include "../GameState.h"
15 #include "../core/MemoryStream.h"
16 #include "../drawing/Drawing.h"
17 #include "../interface/Window.h"
18 #include "../localisation/Localisation.h"
19 #include "../management/NewsItem.h"
20 #include "../peep/RideUseSystem.h"
21 #include "../ride/Ride.h"
22 #include "../ride/RideData.h"
23 #include "../ui/UiContext.h"
24 #include "../ui/WindowManager.h"
25 #include "../world/Banner.h"
26 #include "../world/EntityList.h"
27 #include "../world/Park.h"
28 #include "MazeSetTrackAction.h"
29 #include "TrackRemoveAction.h"
30 
31 using namespace OpenRCT2;
32 
RideDemolishAction(ride_id_t rideIndex,uint8_t modifyType)33 RideDemolishAction::RideDemolishAction(ride_id_t rideIndex, uint8_t modifyType)
34     : _rideIndex(rideIndex)
35     , _modifyType(modifyType)
36 {
37 }
38 
AcceptParameters(GameActionParameterVisitor & visitor)39 void RideDemolishAction::AcceptParameters(GameActionParameterVisitor& visitor)
40 {
41     visitor.Visit("ride", _rideIndex);
42     visitor.Visit("modifyType", _modifyType);
43 }
44 
GetCooldownTime() const45 uint32_t RideDemolishAction::GetCooldownTime() const
46 {
47     return 1000;
48 }
49 
Serialise(DataSerialiser & stream)50 void RideDemolishAction::Serialise(DataSerialiser& stream)
51 {
52     GameAction::Serialise(stream);
53 
54     stream << DS_TAG(_rideIndex) << DS_TAG(_modifyType);
55 }
56 
Query() const57 GameActions::Result::Ptr RideDemolishAction::Query() const
58 {
59     auto ride = get_ride(_rideIndex);
60     if (ride == nullptr)
61     {
62         log_warning("Invalid game command for ride %u", uint32_t(_rideIndex));
63         return std::make_unique<GameActions::Result>(GameActions::Status::InvalidParameters, STR_CANT_DEMOLISH_RIDE, STR_NONE);
64     }
65 
66     if (ride->lifecycle_flags & (RIDE_LIFECYCLE_INDESTRUCTIBLE | RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK)
67         && _modifyType == RIDE_MODIFY_DEMOLISH)
68     {
69         return std::make_unique<GameActions::Result>(
70             GameActions::Status::NoClearance, STR_CANT_DEMOLISH_RIDE,
71             STR_LOCAL_AUTHORITY_FORBIDS_DEMOLITION_OR_MODIFICATIONS_TO_THIS_RIDE);
72     }
73 
74     GameActions::Result::Ptr result = std::make_unique<GameActions::Result>();
75 
76     if (_modifyType == RIDE_MODIFY_RENEW)
77     {
78         if (ride->status != RideStatus::Closed && ride->status != RideStatus::Simulating)
79         {
80             return std::make_unique<GameActions::Result>(
81                 GameActions::Status::Disallowed, STR_CANT_REFURBISH_RIDE, STR_MUST_BE_CLOSED_FIRST);
82         }
83 
84         if (ride->num_riders > 0)
85         {
86             return std::make_unique<GameActions::Result>(
87                 GameActions::Status::Disallowed, STR_CANT_REFURBISH_RIDE, STR_RIDE_NOT_YET_EMPTY);
88         }
89 
90         if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_EVER_BEEN_OPENED)
91             || ride->GetRideTypeDescriptor().AvailableBreakdowns == 0)
92         {
93             return std::make_unique<GameActions::Result>(
94                 GameActions::Status::Disallowed, STR_CANT_REFURBISH_RIDE, STR_CANT_REFURBISH_NOT_NEEDED);
95         }
96 
97         result->ErrorTitle = STR_CANT_REFURBISH_RIDE;
98         result->Cost = GetRefurbishPrice(ride);
99     }
100 
101     return result;
102 }
103 
Execute() const104 GameActions::Result::Ptr RideDemolishAction::Execute() const
105 {
106     auto ride = get_ride(_rideIndex);
107     if (ride == nullptr)
108     {
109         log_warning("Invalid game command for ride %u", uint32_t(_rideIndex));
110         return std::make_unique<GameActions::Result>(GameActions::Status::InvalidParameters, STR_CANT_DEMOLISH_RIDE, STR_NONE);
111     }
112 
113     switch (_modifyType)
114     {
115         case RIDE_MODIFY_DEMOLISH:
116             return DemolishRide(ride);
117         case RIDE_MODIFY_RENEW:
118             return RefurbishRide(ride);
119     }
120 
121     return std::make_unique<GameActions::Result>(GameActions::Status::InvalidParameters, STR_CANT_DO_THIS, STR_NONE);
122 }
123 
DemolishRide(Ride * ride) const124 GameActions::Result::Ptr RideDemolishAction::DemolishRide(Ride* ride) const
125 {
126     money32 refundPrice = DemolishTracks();
127 
128     ride_clear_for_construction(ride);
129     ride_remove_peeps(ride);
130     ride->StopGuestsQueuing();
131 
132     ride->ValidateStations();
133     ride_clear_leftover_entrances(ride);
134 
135     const auto rideId = ride->id;
136     News::DisableNewsItems(News::ItemType::Ride, EnumValue(rideId));
137 
138     UnlinkAllBannersForRide(ride->id);
139 
140     RideUse::GetHistory().RemoveValue(ride->id);
141     for (auto peep : EntityList<Guest>())
142     {
143         peep->RemoveRideFromMemory(ride->id);
144     }
145 
146     MarketingCancelCampaignsForRide(_rideIndex);
147 
148     auto res = std::make_unique<GameActions::Result>();
149     res->Expenditure = ExpenditureType::RideConstruction;
150     res->Cost = refundPrice;
151 
152     if (!ride->overall_view.IsNull())
153     {
154         auto xy = ride->overall_view.ToTileCentre();
155         res->Position = { xy, tile_element_height(xy) };
156     }
157 
158     ride->Delete();
159     gParkValue = GetContext()->GetGameState()->GetPark().CalculateParkValue();
160 
161     // Close windows related to the demolished ride
162     window_close_by_number(WC_RIDE_CONSTRUCTION, EnumValue(rideId));
163     window_close_by_number(WC_RIDE, EnumValue(rideId));
164     window_close_by_number(WC_DEMOLISH_RIDE_PROMPT, EnumValue(rideId));
165     window_close_by_class(WC_NEW_CAMPAIGN);
166 
167     // Refresh windows that display the ride name
168     auto windowManager = OpenRCT2::GetContext()->GetUiContext()->GetWindowManager();
169     windowManager->BroadcastIntent(Intent(INTENT_ACTION_REFRESH_CAMPAIGN_RIDE_LIST));
170     windowManager->BroadcastIntent(Intent(INTENT_ACTION_REFRESH_RIDE_LIST));
171     windowManager->BroadcastIntent(Intent(INTENT_ACTION_REFRESH_GUEST_LIST));
172 
173     scrolling_text_invalidate();
174     gfx_invalidate_screen();
175 
176     return res;
177 }
178 
MazeRemoveTrack(const CoordsXYZD & coords) const179 money32 RideDemolishAction::MazeRemoveTrack(const CoordsXYZD& coords) const
180 {
181     auto setMazeTrack = MazeSetTrackAction(coords, false, _rideIndex, GC_SET_MAZE_TRACK_FILL);
182     setMazeTrack.SetFlags(GetFlags());
183 
184     auto execRes = GameActions::ExecuteNested(&setMazeTrack);
185     if (execRes->Error == GameActions::Status::Ok)
186     {
187         return execRes->Cost;
188     }
189 
190     return MONEY32_UNDEFINED;
191 }
192 
DemolishTracks() const193 money32 RideDemolishAction::DemolishTracks() const
194 {
195     money32 refundPrice = 0;
196 
197     uint8_t oldpaused = gGamePaused;
198     gGamePaused = 0;
199 
200     tile_element_iterator it;
201 
202     tile_element_iterator_begin(&it);
203     while (tile_element_iterator_next(&it))
204     {
205         if (it.element->GetType() != TILE_ELEMENT_TYPE_TRACK)
206             continue;
207 
208         if (it.element->AsTrack()->GetRideIndex() != static_cast<ride_id_t>(_rideIndex))
209             continue;
210 
211         auto location = CoordsXYZD(TileCoordsXY(it.x, it.y).ToCoordsXY(), it.element->GetBaseZ(), it.element->GetDirection());
212         auto type = it.element->AsTrack()->GetTrackType();
213 
214         if (type != TrackElemType::Maze)
215         {
216             auto trackRemoveAction = TrackRemoveAction(type, it.element->AsTrack()->GetSequenceIndex(), location);
217             trackRemoveAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND);
218 
219             auto removRes = GameActions::ExecuteNested(&trackRemoveAction);
220 
221             if (removRes->Error != GameActions::Status::Ok)
222             {
223                 tile_element_remove(it.element);
224             }
225             else
226             {
227                 refundPrice += removRes->Cost;
228             }
229 
230             tile_element_iterator_restart_for_tile(&it);
231             continue;
232         }
233 
234         static constexpr const CoordsXY DirOffsets[] = {
235             { 0, 0 },
236             { 0, 16 },
237             { 16, 16 },
238             { 16, 0 },
239         };
240 
241         for (Direction dir : ALL_DIRECTIONS)
242         {
243             const CoordsXYZ off = { DirOffsets[dir], 0 };
244             money32 removePrice = MazeRemoveTrack({ location + off, dir });
245             if (removePrice != MONEY32_UNDEFINED)
246                 refundPrice += removePrice;
247             else
248                 break;
249         }
250 
251         tile_element_iterator_restart_for_tile(&it);
252     }
253 
254     gGamePaused = oldpaused;
255     return refundPrice;
256 }
257 
RefurbishRide(Ride * ride) const258 GameActions::Result::Ptr RideDemolishAction::RefurbishRide(Ride* ride) const
259 {
260     auto res = std::make_unique<GameActions::Result>();
261     res->Expenditure = ExpenditureType::RideConstruction;
262     res->Cost = GetRefurbishPrice(ride);
263 
264     ride->Renew();
265 
266     ride->lifecycle_flags &= ~RIDE_LIFECYCLE_EVER_BEEN_OPENED;
267     ride->last_crash_type = RIDE_CRASH_TYPE_NONE;
268 
269     ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE | RIDE_INVALIDATE_RIDE_CUSTOMER;
270 
271     if (!ride->overall_view.IsNull())
272     {
273         auto location = ride->overall_view.ToTileCentre();
274         res->Position = { location, tile_element_height(location) };
275     }
276 
277     window_close_by_number(WC_DEMOLISH_RIDE_PROMPT, EnumValue(_rideIndex));
278 
279     return res;
280 }
281 
GetRefurbishPrice(const Ride * ride) const282 money32 RideDemolishAction::GetRefurbishPrice(const Ride* ride) const
283 {
284     return -GetRefundPrice(ride) / 2;
285 }
286 
GetRefundPrice(const Ride * ride) const287 money32 RideDemolishAction::GetRefundPrice(const Ride* ride) const
288 {
289     return ride_get_refund_price(ride);
290 }
291