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 "BannerPlaceAction.h"
11 
12 #include "../management/Finance.h"
13 #include "../world/Banner.h"
14 #include "../world/MapAnimation.h"
15 #include "../world/Scenery.h"
16 #include "../world/TileElementsView.h"
17 #include "GameAction.h"
18 
19 using namespace OpenRCT2;
20 
BannerPlaceAction(const CoordsXYZD & loc,ObjectEntryIndex bannerType,colour_t primaryColour)21 BannerPlaceAction::BannerPlaceAction(const CoordsXYZD& loc, ObjectEntryIndex bannerType, colour_t primaryColour)
22     : _loc(loc)
23     , _bannerType(bannerType)
24     , _primaryColour(primaryColour)
25 {
26 }
27 
AcceptParameters(GameActionParameterVisitor & visitor)28 void BannerPlaceAction::AcceptParameters(GameActionParameterVisitor& visitor)
29 {
30     visitor.Visit(_loc);
31     visitor.Visit("object", _bannerType);
32     visitor.Visit("primaryColour", _primaryColour);
33 }
34 
GetActionFlags() const35 uint16_t BannerPlaceAction::GetActionFlags() const
36 {
37     return GameAction::GetActionFlags();
38 }
39 
Serialise(DataSerialiser & stream)40 void BannerPlaceAction::Serialise(DataSerialiser& stream)
41 {
42     GameAction::Serialise(stream);
43 
44     stream << DS_TAG(_loc) << DS_TAG(_bannerType) << DS_TAG(_primaryColour);
45 }
46 
Query() const47 GameActions::Result::Ptr BannerPlaceAction::Query() const
48 {
49     auto res = MakeResult();
50     res->Position.x = _loc.x + 16;
51     res->Position.y = _loc.y + 16;
52     res->Position.z = _loc.z;
53     res->Expenditure = ExpenditureType::Landscaping;
54     res->ErrorTitle = STR_CANT_POSITION_THIS_HERE;
55 
56     if (!LocationValid(_loc))
57     {
58         return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
59     }
60 
61     if (!MapCheckCapacityAndReorganise(_loc))
62     {
63         log_error("No free map elements.");
64         return MakeResult(GameActions::Status::NoFreeElements, STR_CANT_POSITION_THIS_HERE, STR_TILE_ELEMENT_LIMIT_REACHED);
65     }
66 
67     auto pathElement = GetValidPathElement();
68 
69     if (pathElement == nullptr)
70     {
71         return MakeResult(
72             GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_CAN_ONLY_BE_BUILT_ACROSS_PATHS);
73     }
74 
75     if (!map_can_build_at(_loc))
76     {
77         return MakeResult(GameActions::Status::NotOwned, STR_CANT_POSITION_THIS_HERE, STR_LAND_NOT_OWNED_BY_PARK);
78     }
79 
80     auto baseHeight = _loc.z + PATH_HEIGHT_STEP;
81     BannerElement* existingBannerElement = map_get_banner_element_at({ _loc.x, _loc.y, baseHeight }, _loc.direction);
82     if (existingBannerElement != nullptr)
83     {
84         return MakeResult(GameActions::Status::ItemAlreadyPlaced, STR_CANT_POSITION_THIS_HERE, STR_BANNER_SIGN_IN_THE_WAY);
85     }
86 
87     if (HasReachedBannerLimit())
88     {
89         log_error("No free banners available");
90         return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_TOO_MANY_BANNERS_IN_GAME);
91     }
92 
93     auto* bannerEntry = get_banner_entry(_bannerType);
94     if (bannerEntry == nullptr)
95     {
96         log_error("Invalid banner object type. bannerType = ", _bannerType);
97         return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
98     }
99     res->Cost = bannerEntry->price;
100 
101     return res;
102 }
103 
Execute() const104 GameActions::Result::Ptr BannerPlaceAction::Execute() const
105 {
106     auto res = MakeResult();
107     res->Position.x = _loc.x + 16;
108     res->Position.y = _loc.y + 16;
109     res->Position.z = _loc.z;
110     res->Expenditure = ExpenditureType::Landscaping;
111     res->ErrorTitle = STR_CANT_POSITION_THIS_HERE;
112 
113     if (!MapCheckCapacityAndReorganise(_loc))
114     {
115         log_error("No free map elements.");
116         return MakeResult(GameActions::Status::NoFreeElements, STR_CANT_POSITION_THIS_HERE, STR_TILE_ELEMENT_LIMIT_REACHED);
117     }
118 
119     auto* bannerEntry = get_banner_entry(_bannerType);
120     if (bannerEntry == nullptr)
121     {
122         log_error("Invalid banner object type. bannerType = ", _bannerType);
123         return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
124     }
125 
126     auto banner = CreateBanner();
127     if (banner == nullptr)
128     {
129         log_error("No free banners available");
130         return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_TOO_MANY_BANNERS_IN_GAME);
131     }
132     banner->flags = 0;
133     banner->text = {};
134     banner->text_colour = 2;
135     banner->type = _bannerType; // Banner must be deleted after this point in an early return
136     banner->colour = _primaryColour;
137     banner->position = TileCoordsXY(_loc);
138 
139     res->SetData(BannerPlaceActionResult{ banner->id });
140     auto* bannerElement = TileElementInsert<BannerElement>({ _loc, _loc.z + (2 * COORDS_Z_STEP) }, 0b0000);
141     Guard::Assert(bannerElement != nullptr);
142 
143     bannerElement->SetClearanceZ(_loc.z + PATH_CLEARANCE);
144     bannerElement->SetPosition(_loc.direction);
145     bannerElement->ResetAllowedEdges();
146     bannerElement->SetIndex(banner->id);
147     bannerElement->SetGhost(GetFlags() & GAME_COMMAND_FLAG_GHOST);
148 
149     map_invalidate_tile_full(_loc);
150     map_animation_create(MAP_ANIMATION_TYPE_BANNER, CoordsXYZ{ _loc, bannerElement->GetBaseZ() });
151 
152     res->Cost = bannerEntry->price;
153     return res;
154 }
155 
GetValidPathElement() const156 PathElement* BannerPlaceAction::GetValidPathElement() const
157 {
158     for (auto* pathElement : TileElementsView<PathElement>(_loc))
159     {
160         if (pathElement->GetBaseZ() != _loc.z && pathElement->GetBaseZ() != _loc.z - PATH_HEIGHT_STEP)
161             continue;
162 
163         if (!(pathElement->GetEdges() & (1 << _loc.direction)))
164             continue;
165 
166         if (pathElement->IsGhost() && !(GetFlags() & GAME_COMMAND_FLAG_GHOST))
167             continue;
168 
169         return pathElement;
170     }
171 
172     return nullptr;
173 }
174