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 "GameAction.h"
11 
12 #include "../Context.h"
13 #include "../ReplayManager.h"
14 #include "../core/Guard.hpp"
15 #include "../core/Memory.hpp"
16 #include "../core/MemoryStream.h"
17 #include "../localisation/Localisation.h"
18 #include "../network/network.h"
19 #include "../platform/platform.h"
20 #include "../scenario/Scenario.h"
21 #include "../scripting/Duktape.hpp"
22 #include "../scripting/HookEngine.h"
23 #include "../scripting/ScriptEngine.h"
24 #include "../ui/UiContext.h"
25 #include "../ui/WindowManager.h"
26 #include "../world/MoneyEffect.h"
27 #include "../world/Park.h"
28 #include "../world/Scenery.h"
29 
30 #include <algorithm>
31 #include <iterator>
32 
33 using namespace OpenRCT2;
34 
35 namespace GameActions
36 {
37     struct QueuedGameAction
38     {
39         uint32_t tick;
40         uint32_t uniqueId;
41         GameAction::Ptr action;
42 
QueuedGameActionGameActions::QueuedGameAction43         explicit QueuedGameAction(uint32_t t, std::unique_ptr<GameAction>&& ga, uint32_t id)
44             : tick(t)
45             , uniqueId(id)
46             , action(std::move(ga))
47         {
48         }
49 
operator <GameActions::QueuedGameAction50         bool operator<(const QueuedGameAction& comp) const
51         {
52             // First sort by tick
53             if (tick < comp.tick)
54                 return true;
55             if (tick > comp.tick)
56                 return false;
57 
58             // If the ticks are equal sort by commandIndex
59             return uniqueId < comp.uniqueId;
60         }
61     };
62 
63     static std::multiset<QueuedGameAction> _actionQueue;
64     static uint32_t _nextUniqueId = 0;
65     static bool _suspended = false;
66 
SuspendQueue()67     void SuspendQueue()
68     {
69         _suspended = true;
70     }
71 
ResumeQueue()72     void ResumeQueue()
73     {
74         _suspended = false;
75     }
76 
Enqueue(const GameAction * ga,uint32_t tick)77     void Enqueue(const GameAction* ga, uint32_t tick)
78     {
79         auto action = Clone(ga);
80         Enqueue(std::move(action), tick);
81     }
82 
Enqueue(GameAction::Ptr && ga,uint32_t tick)83     void Enqueue(GameAction::Ptr&& ga, uint32_t tick)
84     {
85         if (ga->GetPlayer() == -1 && network_get_mode() != NETWORK_MODE_NONE)
86         {
87             // Server can directly invoke actions and will have no player id assigned
88             // as that normally happens when receiving them over network.
89             ga->SetPlayer(network_get_current_player_id());
90         }
91         _actionQueue.emplace(tick, std::move(ga), _nextUniqueId++);
92     }
93 
ProcessQueue()94     void ProcessQueue()
95     {
96         if (_suspended)
97         {
98             // Do nothing if suspended, this is usually the case between connect and map loads.
99             return;
100         }
101 
102         const uint32_t currentTick = gCurrentTicks;
103 
104         while (_actionQueue.begin() != _actionQueue.end())
105         {
106             // run all the game commands at the current tick
107             const QueuedGameAction& queued = *_actionQueue.begin();
108 
109             if (network_get_mode() == NETWORK_MODE_CLIENT)
110             {
111                 if (queued.tick < currentTick)
112                 {
113                     // This should never happen.
114                     Guard::Assert(
115                         false,
116                         "Discarding game action %s (%u) from tick behind current tick, ID: %08X, Action Tick: %08X, Current "
117                         "Tick: "
118                         "%08X\n",
119                         queued.action->GetName(), queued.action->GetType(), queued.uniqueId, queued.tick, currentTick);
120                 }
121                 else if (queued.tick > currentTick)
122                 {
123                     return;
124                 }
125             }
126 
127             // Remove ghost scenery so it doesn't interfere with incoming network command
128             switch (queued.action->GetType())
129             {
130                 case GameCommand::PlaceWall:
131                 case GameCommand::PlaceLargeScenery:
132                 case GameCommand::PlaceBanner:
133                 case GameCommand::PlaceScenery:
134                     scenery_remove_ghost_tool_placement();
135                     break;
136                 default:
137                     break;
138             }
139 
140             GameAction* action = queued.action.get();
141             action->SetFlags(action->GetFlags() | GAME_COMMAND_FLAG_NETWORKED);
142 
143             Guard::Assert(action != nullptr);
144 
145             GameActions::Result::Ptr result = Execute(action);
146             if (result->Error == GameActions::Status::Ok && network_get_mode() == NETWORK_MODE_SERVER)
147             {
148                 // Relay this action to all other clients.
149                 network_send_game_action(action);
150             }
151 
152             _actionQueue.erase(_actionQueue.begin());
153         }
154     }
155 
ClearQueue()156     void ClearQueue()
157     {
158         _actionQueue.clear();
159     }
160 
Clone(const GameAction * action)161     GameAction::Ptr Clone(const GameAction* action)
162     {
163         std::unique_ptr<GameAction> ga = GameActions::Create(action->GetType());
164         ga->SetCallback(action->GetCallback());
165 
166         // Serialise action data into stream.
167         DataSerialiser dsOut(true);
168         action->Serialise(dsOut);
169 
170         // Serialise into new action.
171         IStream& stream = dsOut.GetStream();
172         stream.SetPosition(0);
173 
174         DataSerialiser dsIn(false, stream);
175         ga->Serialise(dsIn);
176 
177         return ga;
178     }
179 
CheckActionInPausedMode(uint32_t actionFlags)180     static bool CheckActionInPausedMode(uint32_t actionFlags)
181     {
182         if (gGamePaused == 0)
183             return true;
184         if (gCheatsBuildInPauseMode)
185             return true;
186         if (actionFlags & GameActions::Flags::AllowWhilePaused)
187             return true;
188         return false;
189     }
190 
QueryInternal(const GameAction * action,bool topLevel)191     static GameActions::Result::Ptr QueryInternal(const GameAction* action, bool topLevel)
192     {
193         Guard::ArgumentNotNull(action);
194 
195         uint16_t actionFlags = action->GetActionFlags();
196         if (topLevel && !CheckActionInPausedMode(actionFlags))
197         {
198             GameActions::Result::Ptr result = std::make_unique<GameActions::Result>();
199 
200             result->Error = GameActions::Status::GamePaused;
201             result->ErrorTitle = STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE;
202             result->ErrorMessage = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED;
203 
204             return result;
205         }
206 
207         auto result = action->Query();
208 
209         if (result->Error == GameActions::Status::Ok)
210         {
211             if (!finance_check_affordability(result->Cost, action->GetFlags()))
212             {
213                 result->Error = GameActions::Status::InsufficientFunds;
214                 result->ErrorTitle = STR_CANT_DO_THIS;
215                 result->ErrorMessage = STR_NOT_ENOUGH_CASH_REQUIRES;
216                 Formatter(result->ErrorMessageArgs.data()).Add<uint32_t>(result->Cost);
217             }
218         }
219         return result;
220     }
221 
Query(const GameAction * action)222     GameActions::Result::Ptr Query(const GameAction* action)
223     {
224         return QueryInternal(action, true);
225     }
226 
QueryNested(const GameAction * action)227     GameActions::Result::Ptr QueryNested(const GameAction* action)
228     {
229         return QueryInternal(action, false);
230     }
231 
GetRealm()232     static const char* GetRealm()
233     {
234         if (network_get_mode() == NETWORK_MODE_CLIENT)
235             return "cl";
236         if (network_get_mode() == NETWORK_MODE_SERVER)
237             return "sv";
238         return "sp";
239     }
240 
241     struct ActionLogContext_t
242     {
243         MemoryStream output;
244     };
245 
LogActionBegin(ActionLogContext_t & ctx,const GameAction * action)246     static void LogActionBegin(ActionLogContext_t& ctx, const GameAction* action)
247     {
248         MemoryStream& output = ctx.output;
249 
250         char temp[128] = {};
251         snprintf(
252             temp, sizeof(temp), "[%s] Tick: %u, GA: %s (%08X) (", GetRealm(), gCurrentTicks, action->GetName(),
253             EnumValue(action->GetType()));
254 
255         output.Write(temp, strlen(temp));
256 
257         DataSerialiser ds(true, ctx.output, true); // Logging mode.
258 
259         // Write all parameters into output as text.
260         action->Serialise(ds);
261     }
262 
LogActionFinish(ActionLogContext_t & ctx,const GameAction * action,const GameActions::Result::Ptr & result)263     static void LogActionFinish(ActionLogContext_t& ctx, const GameAction* action, const GameActions::Result::Ptr& result)
264     {
265         MemoryStream& output = ctx.output;
266 
267         char temp[128] = {};
268 
269         if (result->Error != GameActions::Status::Ok)
270         {
271             snprintf(temp, sizeof(temp), ") Failed, %u", static_cast<uint32_t>(result->Error));
272         }
273         else
274         {
275             snprintf(temp, sizeof(temp), ") OK");
276         }
277 
278         output.Write(temp, strlen(temp) + 1);
279 
280         const char* text = static_cast<const char*>(output.GetData());
281         log_verbose("%s", text);
282 
283         network_append_server_log(text);
284     }
285 
ExecuteInternal(const GameAction * action,bool topLevel)286     static GameActions::Result::Ptr ExecuteInternal(const GameAction* action, bool topLevel)
287     {
288         Guard::ArgumentNotNull(action);
289 
290         uint16_t actionFlags = action->GetActionFlags();
291         uint32_t flags = action->GetFlags();
292 
293         auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
294         if (replayManager != nullptr && (replayManager->IsReplaying() || replayManager->IsNormalising()))
295         {
296             // We only accept replay commands as long the replay is active.
297             if ((flags & GAME_COMMAND_FLAG_REPLAY) == 0)
298             {
299                 // TODO: Introduce proper error.
300                 GameActions::Result::Ptr result = std::make_unique<GameActions::Result>();
301 
302                 result->Error = GameActions::Status::GamePaused;
303                 result->ErrorTitle = STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE;
304                 result->ErrorMessage = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED;
305 
306                 return result;
307             }
308         }
309 
310         GameActions::Result::Ptr result = QueryInternal(action, topLevel);
311 #ifdef ENABLE_SCRIPTING
312         if (result->Error == GameActions::Status::Ok
313             && ((network_get_mode() == NETWORK_MODE_NONE) || (flags & GAME_COMMAND_FLAG_NETWORKED)))
314         {
315             auto& scriptEngine = GetContext()->GetScriptEngine();
316             scriptEngine.RunGameActionHooks(*action, result, false);
317             // Script hooks may now have changed the game action result...
318         }
319 #endif
320         if (result->Error == GameActions::Status::Ok)
321         {
322             if (topLevel)
323             {
324                 // Networked games send actions to the server to be run
325                 if (network_get_mode() == NETWORK_MODE_CLIENT)
326                 {
327                     // As a client we have to wait or send it first.
328                     if (!(actionFlags & GameActions::Flags::ClientOnly) && !(flags & GAME_COMMAND_FLAG_NETWORKED))
329                     {
330                         log_verbose("[%s] GameAction::Execute %s (Out)", GetRealm(), action->GetName());
331                         network_send_game_action(action);
332 
333                         return result;
334                     }
335                 }
336                 else if (network_get_mode() == NETWORK_MODE_SERVER)
337                 {
338                     // If player is the server it would execute right away as where clients execute the commands
339                     // at the beginning of the frame, so we have to put them into the queue.
340                     if (!(actionFlags & GameActions::Flags::ClientOnly) && !(flags & GAME_COMMAND_FLAG_NETWORKED))
341                     {
342                         log_verbose("[%s] GameAction::Execute %s (Queue)", GetRealm(), action->GetName());
343                         Enqueue(action, gCurrentTicks);
344 
345                         return result;
346                     }
347                 }
348             }
349 
350             ActionLogContext_t logContext;
351             LogActionBegin(logContext, action);
352 
353             // Execute the action, changing the game state
354             result = action->Execute();
355 #ifdef ENABLE_SCRIPTING
356             if (result->Error == GameActions::Status::Ok)
357             {
358                 auto& scriptEngine = GetContext()->GetScriptEngine();
359                 scriptEngine.RunGameActionHooks(*action, result, true);
360                 // Script hooks may now have changed the game action result...
361             }
362 #endif
363 
364             LogActionFinish(logContext, action, result);
365 
366             // If not top level just give away the result.
367             if (!topLevel)
368                 return result;
369 
370             // Update money balance
371             if (result->Error == GameActions::Status::Ok && finance_check_money_required(flags) && result->Cost != 0)
372             {
373                 finance_payment(result->Cost, result->Expenditure);
374                 MoneyEffect::Create(result->Cost, result->Position);
375             }
376 
377             if (!(actionFlags & GameActions::Flags::ClientOnly) && result->Error == GameActions::Status::Ok)
378             {
379                 if (network_get_mode() != NETWORK_MODE_NONE)
380                 {
381                     NetworkPlayerId_t playerId = action->GetPlayer();
382 
383                     int32_t playerIndex = network_get_player_index(playerId.id);
384                     Guard::Assert(
385                         playerIndex != -1, "Unable to find player %u for game action %u", playerId, action->GetType());
386 
387                     network_set_player_last_action(playerIndex, action->GetType());
388                     if (result->Cost != 0)
389                     {
390                         network_add_player_money_spent(playerIndex, result->Cost);
391                     }
392 
393                     if (!result->Position.IsNull())
394                     {
395                         network_set_player_last_action_coord(playerIndex, result->Position);
396                     }
397                 }
398                 else
399                 {
400                     bool commandExecutes = (flags & GAME_COMMAND_FLAG_GHOST) == 0 && (flags & GAME_COMMAND_FLAG_NO_SPEND) == 0;
401 
402                     bool recordAction = false;
403                     if (replayManager != nullptr)
404                     {
405                         if (replayManager->IsRecording() && commandExecutes)
406                             recordAction = true;
407                         else if (replayManager->IsNormalising() && (flags & GAME_COMMAND_FLAG_REPLAY) != 0)
408                             recordAction = true; // In normalisation we only feed back actions issued by the replay manager.
409                     }
410                     if (recordAction)
411                     {
412                         replayManager->AddGameAction(gCurrentTicks, action);
413                     }
414                 }
415             }
416 
417             // Allow autosave to commence
418             if (gLastAutoSaveUpdate == AUTOSAVE_PAUSE)
419             {
420                 gLastAutoSaveUpdate = platform_get_ticks();
421             }
422         }
423 
424         // Call callback for asynchronous events
425         auto cb = action->GetCallback();
426         if (cb != nullptr)
427         {
428             cb(action, result.get());
429         }
430 
431         // Only show errors when its not a ghost and not a preview and also top level action.
432         bool shouldShowError = !(flags & GAME_COMMAND_FLAG_GHOST) && !(flags & GAME_COMMAND_FLAG_NO_SPEND) && topLevel;
433 
434         // In network mode the error should be only shown to the issuer of the action.
435         if (network_get_mode() != NETWORK_MODE_NONE)
436         {
437             // If the action was never networked and query fails locally the player id is not assigned.
438             // So compare only if the action went into the queue otherwise show errors by default.
439             const bool isActionFromNetwork = (action->GetFlags() & GAME_COMMAND_FLAG_NETWORKED) != 0;
440             if (isActionFromNetwork && action->GetPlayer() != network_get_current_player_id())
441             {
442                 shouldShowError = false;
443             }
444         }
445 
446         if (result->Error != GameActions::Status::Ok && shouldShowError)
447         {
448             auto windowManager = GetContext()->GetUiContext()->GetWindowManager();
449             windowManager->ShowError(result->GetErrorTitle(), result->GetErrorMessage());
450         }
451 
452         return result;
453     }
454 
Execute(const GameAction * action)455     GameActions::Result::Ptr Execute(const GameAction* action)
456     {
457         return ExecuteInternal(action, true);
458     }
459 
ExecuteNested(const GameAction * action)460     GameActions::Result::Ptr ExecuteNested(const GameAction* action)
461     {
462         return ExecuteInternal(action, false);
463     }
464 } // namespace GameActions
465 
GetName() const466 const char* GameAction::GetName() const
467 {
468     return GameActions::GetName(_type);
469 }
470 
LocationValid(const CoordsXY & coords) const471 bool GameAction::LocationValid(const CoordsXY& coords) const
472 {
473     auto result = map_is_location_valid(coords);
474     if (!result)
475         return false;
476 #ifdef ENABLE_SCRIPTING
477     auto& hookEngine = GetContext()->GetScriptEngine().GetHookEngine();
478     if (hookEngine.HasSubscriptions(OpenRCT2::Scripting::HOOK_TYPE::ACTION_LOCATION))
479     {
480         auto ctx = GetContext()->GetScriptEngine().GetContext();
481 
482         // Create event args object
483         auto obj = OpenRCT2::Scripting::DukObject(ctx);
484         obj.Set("x", coords.x);
485         obj.Set("y", coords.y);
486         obj.Set("player", _playerId);
487         obj.Set("type", EnumValue(_type));
488 
489         auto flags = GetActionFlags();
490         obj.Set("isClientOnly", (flags & GameActions::Flags::ClientOnly) != 0);
491         obj.Set("result", true);
492 
493         // Call the subscriptions
494         auto e = obj.Take();
495         hookEngine.Call(OpenRCT2::Scripting::HOOK_TYPE::ACTION_LOCATION, e, true);
496 
497         auto scriptResult = OpenRCT2::Scripting::AsOrDefault(e["result"], true);
498 
499         return scriptResult;
500     }
501 #endif
502     return true;
503 }
504