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