2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file story.cpp Handling of stories. */
11 #include "story_base.h"
12 #include "core/pool_func.hpp"
13 #include "cmd_helper.h"
14 #include "command_func.h"
15 #include "company_base.h"
16 #include "company_func.h"
17 #include "string_func.h"
18 #include "date_func.h"
20 #include "goal_type.h"
21 #include "goal_base.h"
22 #include "window_func.h"
24 #include "vehicle_base.h"
25 #include "game/game.hpp"
26 #include "script/api/script_story_page.hpp"
27 #include "script/api/script_event_types.hpp"
29 #include "safeguards.h"
32 StoryPageElementID _new_story_page_element_id
;
33 StoryPageID _new_story_page_id
;
34 uint32 _story_page_element_next_sort_value
;
35 uint32 _story_page_next_sort_value
;
37 StoryPageElementPool
_story_page_element_pool("StoryPageElement");
38 StoryPagePool
_story_page_pool("StoryPage");
39 INSTANTIATE_POOL_METHODS(StoryPageElement
)
40 INSTANTIATE_POOL_METHODS(StoryPage
)
43 * This helper for Create/Update PageElement Cmd procedure verifies if the page
44 * element parameters are correct for the given page element type.
45 * @param page_id The page id of the page which the page element (will) belong to
46 * @param type The type of the page element to create/update
47 * @param tile The tile parameter of the DoCommand proc
48 * @param reference The reference parameter of the DoCommand proc (p2)
49 * @param text The text parameter of the DoCommand proc
50 * @return true, if and only if the given parameters are valid for the given page element type and page id.
52 static bool VerifyElementContentParameters(StoryPageID page_id
, StoryPageElementType type
, TileIndex tile
, uint32 reference
, const char *text
)
54 StoryPageButtonData button_data
{ reference
};
58 if (StrEmpty(text
)) return false;
61 if (StrEmpty(text
)) return false;
62 if (!IsValidTile(tile
)) return false;
65 if (!Goal::IsValidID((GoalID
)reference
)) return false;
66 /* Reject company specific goals on global pages */
67 if (StoryPage::Get(page_id
)->company
== INVALID_COMPANY
&& Goal::Get((GoalID
)reference
)->company
!= INVALID_COMPANY
) return false;
69 case SPET_BUTTON_PUSH
:
70 if (!button_data
.ValidateColour()) return false;
72 case SPET_BUTTON_TILE
:
73 if (!button_data
.ValidateColour()) return false;
74 if (!button_data
.ValidateCursor()) return false;
76 case SPET_BUTTON_VEHICLE
:
77 if (!button_data
.ValidateColour()) return false;
78 if (!button_data
.ValidateCursor()) return false;
79 if (!button_data
.ValidateVehicleType()) return false;
89 * This helper for Create/Update PageElement Cmd procedure updates a page
90 * element with new content data.
91 * @param pe The page element to update
92 * @param tile The tile parameter of the DoCommand proc
93 * @param reference The reference parameter of the DoCommand proc (p2)
94 * @param text The text parameter of the DoCommand proc
96 static void UpdateElement(StoryPageElement
&pe
, TileIndex tile
, uint32 reference
, const char *text
)
100 pe
.text
= stredup(text
);
103 pe
.text
= stredup(text
);
104 pe
.referenced_id
= tile
;
107 pe
.referenced_id
= (GoalID
)reference
;
109 case SPET_BUTTON_PUSH
:
110 case SPET_BUTTON_TILE
:
111 case SPET_BUTTON_VEHICLE
:
112 pe
.text
= stredup(text
);
113 pe
.referenced_id
= reference
;
115 default: NOT_REACHED();
119 /** Set the button background colour. */
120 void StoryPageButtonData::SetColour(Colours button_colour
)
122 assert(button_colour
< COLOUR_END
);
123 SB(this->referenced_id
, 0, 8, button_colour
);
126 void StoryPageButtonData::SetFlags(StoryPageButtonFlags flags
)
128 SB(this->referenced_id
, 24, 8, flags
);
131 /** Set the mouse cursor used while waiting for input for the button. */
132 void StoryPageButtonData::SetCursor(StoryPageButtonCursor cursor
)
134 assert(cursor
< SPBC_END
);
135 SB(this->referenced_id
, 8, 8, cursor
);
138 /** Set the type of vehicles that are accepted by the button */
139 void StoryPageButtonData::SetVehicleType(VehicleType vehtype
)
141 assert(vehtype
== VEH_INVALID
|| vehtype
< VEH_COMPANY_END
);
142 SB(this->referenced_id
, 16, 8, vehtype
);
145 /** Get the button background colour. */
146 Colours
StoryPageButtonData::GetColour() const
148 return Extract
<Colours
, 0, 8>(this->referenced_id
);
151 StoryPageButtonFlags
StoryPageButtonData::GetFlags() const
153 return (StoryPageButtonFlags
)GB(this->referenced_id
, 24, 8);
156 /** Get the mouse cursor used while waiting for input for the button. */
157 StoryPageButtonCursor
StoryPageButtonData::GetCursor() const
159 return Extract
<StoryPageButtonCursor
, 8, 8>(this->referenced_id
);
162 /** Get the type of vehicles that are accepted by the button */
163 VehicleType
StoryPageButtonData::GetVehicleType() const
165 return (VehicleType
)GB(this->referenced_id
, 16, 8);
168 /** Verify that the data stored a valid Colour value */
169 bool StoryPageButtonData::ValidateColour() const
171 return GB(this->referenced_id
, 0, 8) < COLOUR_END
;
174 bool StoryPageButtonData::ValidateFlags() const
176 byte flags
= GB(this->referenced_id
, 24, 8);
177 /* Don't allow float left and right together */
178 if ((flags
& SPBF_FLOAT_LEFT
) && (flags
& SPBF_FLOAT_RIGHT
)) return false;
179 /* Don't allow undefined flags */
180 if (flags
& ~(SPBF_FLOAT_LEFT
| SPBF_FLOAT_RIGHT
)) return false;
184 /** Verify that the data stores a valid StoryPageButtonCursor value */
185 bool StoryPageButtonData::ValidateCursor() const
187 return GB(this->referenced_id
, 8, 8) < SPBC_END
;
190 /** Verity that the data stored a valid VehicleType value */
191 bool StoryPageButtonData::ValidateVehicleType() const
193 byte vehtype
= GB(this->referenced_id
, 16, 8);
194 return vehtype
== VEH_INVALID
|| vehtype
< VEH_COMPANY_END
;
198 * Create a new story page.
199 * @param tile unused.
200 * @param flags type of operation
201 * @param p1 various bitstuffed elements
202 * - p1 = (bit 0 - 7) - Company for which this story page belongs to.
204 * @param text Title of the story page. Null is allowed in which case a generic page title is provided by OpenTTD.
205 * @return the cost of this operation or an error
207 CommandCost
CmdCreateStoryPage(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
209 if (!StoryPage::CanAllocateItem()) return CMD_ERROR
;
211 CompanyID company
= (CompanyID
)GB(p1
, 0, 8);
213 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
214 if (company
!= INVALID_COMPANY
&& !Company::IsValidID(company
)) return CMD_ERROR
;
216 if (flags
& DC_EXEC
) {
217 if (_story_page_pool
.items
== 0) {
218 /* Initialize the next sort value variable. */
219 _story_page_next_sort_value
= 0;
222 StoryPage
*s
= new StoryPage();
223 s
->sort_value
= _story_page_next_sort_value
;
225 s
->company
= company
;
229 s
->title
= stredup(text
.c_str());
232 InvalidateWindowClassesData(WC_STORY_BOOK
, -1);
233 if (StoryPage::GetNumItems() == 1) InvalidateWindowData(WC_MAIN_TOOLBAR
, 0);
235 _new_story_page_id
= s
->index
;
236 _story_page_next_sort_value
++;
239 return CommandCost();
243 * Create a new story page element.
244 * @param tile Tile location if it is a location page element, otherwise unused.
245 * @param flags type of operation
246 * @param p1 various bitstuffed elements
247 * - p1 = (bit 0 - 15) - The page which the element belongs to.
248 * (bit 16 - 23) - Page element type
249 * @param p2 Id of referenced object
250 * @param text Text content in case it is a text or location page element
251 * @return the cost of this operation or an error
253 CommandCost
CmdCreateStoryPageElement(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
255 if (!StoryPageElement::CanAllocateItem()) return CMD_ERROR
;
257 StoryPageID page_id
= (StoryPageID
)GB(p1
, 0, 16);
258 StoryPageElementType type
= Extract
<StoryPageElementType
, 16, 8>(p1
);
260 /* Allow at most 128 elements per page. */
261 uint16 element_count
= 0;
262 for (StoryPageElement
*iter
: StoryPageElement::Iterate()) {
263 if (iter
->page
== page_id
) element_count
++;
265 if (element_count
>= 128) return CMD_ERROR
;
267 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
268 if (!StoryPage::IsValidID(page_id
)) return CMD_ERROR
;
269 if (!VerifyElementContentParameters(page_id
, type
, tile
, p2
, text
.c_str())) return CMD_ERROR
;
271 if (flags
& DC_EXEC
) {
272 if (_story_page_element_pool
.items
== 0) {
273 /* Initialize the next sort value variable. */
274 _story_page_element_next_sort_value
= 0;
277 StoryPageElement
*pe
= new StoryPageElement();
278 pe
->sort_value
= _story_page_element_next_sort_value
;
281 UpdateElement(*pe
, tile
, p2
, text
.c_str());
283 InvalidateWindowClassesData(WC_STORY_BOOK
, page_id
);
285 _new_story_page_element_id
= pe
->index
;
286 _story_page_element_next_sort_value
++;
289 return CommandCost();
293 * Update a new story page element.
294 * @param tile Tile location if it is a location page element, otherwise unused.
295 * @param flags type of operation
296 * @param p1 various bitstuffed elements
297 * - p1 = (bit 0 - 15) - The page element to update.
298 * (bit 16 - 31) - unused
299 * @param p2 Id of referenced object
300 * @param text Text content in case it is a text or location page element
301 * @return the cost of this operation or an error
303 CommandCost
CmdUpdateStoryPageElement(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
305 StoryPageElementID page_element_id
= (StoryPageElementID
)GB(p1
, 0, 16);
307 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
308 if (!StoryPageElement::IsValidID(page_element_id
)) return CMD_ERROR
;
310 StoryPageElement
*pe
= StoryPageElement::Get(page_element_id
);
311 StoryPageID page_id
= pe
->page
;
312 StoryPageElementType type
= pe
->type
;
314 if (!VerifyElementContentParameters(page_id
, type
, tile
, p2
, text
.c_str())) return CMD_ERROR
;
316 if (flags
& DC_EXEC
) {
317 UpdateElement(*pe
, tile
, p2
, text
.c_str());
318 InvalidateWindowClassesData(WC_STORY_BOOK
, pe
->page
);
321 return CommandCost();
325 * Update title of a story page.
326 * @param tile unused.
327 * @param flags type of operation
328 * @param p1 = (bit 0 - 15) - StoryPageID to update.
330 * @param text title text of the story page.
331 * @return the cost of this operation or an error
333 CommandCost
CmdSetStoryPageTitle(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
335 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
336 StoryPageID page_id
= (StoryPageID
)GB(p1
, 0, 16);
337 if (!StoryPage::IsValidID(page_id
)) return CMD_ERROR
;
339 if (flags
& DC_EXEC
) {
340 StoryPage
*p
= StoryPage::Get(page_id
);
345 p
->title
= stredup(text
.c_str());
348 InvalidateWindowClassesData(WC_STORY_BOOK
, page_id
);
351 return CommandCost();
355 * Update date of a story page.
356 * @param tile unused.
357 * @param flags type of operation
358 * @param p1 = (bit 0 - 15) - StoryPageID to update.
359 * @param p2 = (bit 0 - 31) - date
361 * @return the cost of this operation or an error
363 CommandCost
CmdSetStoryPageDate(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
365 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
366 StoryPageID page_id
= (StoryPageID
)GB(p1
, 0, 16);
367 if (!StoryPage::IsValidID(page_id
)) return CMD_ERROR
;
368 Date date
= (Date
)p2
;
370 if (flags
& DC_EXEC
) {
371 StoryPage
*p
= StoryPage::Get(page_id
);
374 InvalidateWindowClassesData(WC_STORY_BOOK
, page_id
);
377 return CommandCost();
381 * Display a story page for all clients that are allowed to
382 * view the story page.
383 * @param tile unused.
384 * @param flags type of operation
385 * @param p1 = (bit 0 - 15) - StoryPageID to show.
388 * @return the cost of this operation or an error
390 CommandCost
CmdShowStoryPage(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
392 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
393 StoryPageID page_id
= (StoryPageID
)GB(p1
, 0, 16);
394 if (!StoryPage::IsValidID(page_id
)) return CMD_ERROR
;
396 if (flags
& DC_EXEC
) {
397 StoryPage
*g
= StoryPage::Get(page_id
);
398 if ((g
->company
!= INVALID_COMPANY
&& g
->company
== _local_company
) || (g
->company
== INVALID_COMPANY
&& Company::IsValidID(_local_company
))) ShowStoryBook(_local_company
, page_id
);
401 return CommandCost();
404 * Remove a story page and associated story page elements.
405 * @param tile unused.
406 * @param flags type of operation
407 * @param p1 = (bit 0 - 15) - StoryPageID to remove.
409 * @param text unused.
410 * @return the cost of this operation or an error
412 CommandCost
CmdRemoveStoryPage(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
414 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
415 StoryPageID page_id
= (StoryPageID
)p1
;
416 if (!StoryPage::IsValidID(page_id
)) return CMD_ERROR
;
418 if (flags
& DC_EXEC
) {
419 StoryPage
*p
= StoryPage::Get(page_id
);
421 for (StoryPageElement
*pe
: StoryPageElement::Iterate()) {
422 if (pe
->page
== p
->index
) {
429 InvalidateWindowClassesData(WC_STORY_BOOK
, -1);
430 if (StoryPage::GetNumItems() == 0) InvalidateWindowData(WC_MAIN_TOOLBAR
, 0);
433 return CommandCost();
437 * Remove a story page element
438 * @param tile unused.
439 * @param flags type of operation
440 * @param p1 = (bit 0 - 15) - StoryPageElementID to remove.
442 * @param text unused.
443 * @return the cost of this operation or an error
445 CommandCost
CmdRemoveStoryPageElement(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
447 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
448 StoryPageElementID page_element_id
= (StoryPageElementID
)p1
;
449 if (!StoryPageElement::IsValidID(page_element_id
)) return CMD_ERROR
;
451 if (flags
& DC_EXEC
) {
452 StoryPageElement
*pe
= StoryPageElement::Get(page_element_id
);
453 StoryPageID page_id
= pe
->page
;
457 InvalidateWindowClassesData(WC_STORY_BOOK
, page_id
);
460 return CommandCost();
464 * Clicked/used a button on a story page.
465 * @param tile Tile selected, for tile selection buttons, otherwise unused.
466 * @param flags Type of operation.
467 * @param p1 Bit 0..15 = story page element id of button.
468 * @param p2 ID of selected item for buttons that select an item (e.g. vehicle), otherwise unused.
469 * @param text Unused.
470 * @return The cost of the operation, or an error.
472 CommandCost
CmdStoryPageButton(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
474 StoryPageElementID page_element_id
= (StoryPageElementID
)GB(p1
, 0, 16);
476 if (!StoryPageElement::IsValidID(page_element_id
)) return CMD_ERROR
;
477 const StoryPageElement
*const pe
= StoryPageElement::Get(page_element_id
);
479 /* Check the player belongs to the company that owns the page. */
480 const StoryPage
*const sp
= StoryPage::Get(pe
->page
);
481 if (sp
->company
!= INVALID_COMPANY
&& sp
->company
!= _current_company
) return CMD_ERROR
;
484 case SPET_BUTTON_PUSH
:
485 /* No validation required */
486 if (flags
& DC_EXEC
) Game::NewEvent(new ScriptEventStoryPageButtonClick(_current_company
, pe
->page
, page_element_id
));
488 case SPET_BUTTON_TILE
:
489 if (!IsValidTile(tile
)) return CMD_ERROR
;
490 if (flags
& DC_EXEC
) Game::NewEvent(new ScriptEventStoryPageTileSelect(_current_company
, pe
->page
, page_element_id
, tile
));
492 case SPET_BUTTON_VEHICLE
:
493 if (!Vehicle::IsValidID(p2
)) return CMD_ERROR
;
494 if (flags
& DC_EXEC
) Game::NewEvent(new ScriptEventStoryPageVehicleSelect(_current_company
, pe
->page
, page_element_id
, (VehicleID
)p2
));
497 /* Invalid page element type, not a button. */
501 return CommandCost();