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 "command_func.h"
14 #include "company_base.h"
15 #include "company_func.h"
16 #include "string_func.h"
17 #include "timer/timer_game_calendar.h"
19 #include "goal_type.h"
20 #include "goal_base.h"
21 #include "window_func.h"
23 #include "vehicle_base.h"
24 #include "game/game.hpp"
25 #include "script/api/script_story_page.hpp"
26 #include "script/api/script_event_types.hpp"
27 #include "story_cmd.h"
29 #include "safeguards.h"
32 uint32_t _story_page_element_next_sort_value
;
33 uint32_t _story_page_next_sort_value
;
35 StoryPageElementPool
_story_page_element_pool("StoryPageElement");
36 StoryPagePool
_story_page_pool("StoryPage");
37 INSTANTIATE_POOL_METHODS(StoryPageElement
)
38 INSTANTIATE_POOL_METHODS(StoryPage
)
41 * This helper for Create/Update PageElement Cmd procedure verifies if the page
42 * element parameters are correct for the given page element type.
43 * @param page_id The page id of the page which the page element (will) belong to
44 * @param type The type of the page element to create/update
45 * @param tile The tile parameter of the DoCommand proc
46 * @param reference The reference parameter of the DoCommand proc (p2)
47 * @param text The text parameter of the DoCommand proc
48 * @return true, if and only if the given parameters are valid for the given page element type and page id.
50 static bool VerifyElementContentParameters(StoryPageID page_id
, StoryPageElementType type
, TileIndex tile
, uint32_t reference
, const std::string
&text
)
52 StoryPageButtonData button_data
{ reference
};
56 if (text
.empty()) return false;
59 if (text
.empty()) return false;
60 if (!IsValidTile(tile
)) return false;
63 if (!Goal::IsValidID((GoalID
)reference
)) return false;
64 /* Reject company specific goals on global pages */
65 if (StoryPage::Get(page_id
)->company
== INVALID_COMPANY
&& Goal::Get((GoalID
)reference
)->company
!= INVALID_COMPANY
) return false;
67 case SPET_BUTTON_PUSH
:
68 if (!button_data
.ValidateColour()) return false;
69 if (!button_data
.ValidateFlags()) return false;
71 case SPET_BUTTON_TILE
:
72 if (!button_data
.ValidateColour()) return false;
73 if (!button_data
.ValidateFlags()) 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
.ValidateFlags()) return false;
79 if (!button_data
.ValidateCursor()) return false;
80 if (!button_data
.ValidateVehicleType()) return false;
90 * This helper for Create/Update PageElement Cmd procedure updates a page
91 * element with new content data.
92 * @param pe The page element to update
93 * @param tile The tile parameter of the DoCommand proc
94 * @param reference The reference parameter of the DoCommand proc (p2)
95 * @param text The text parameter of the DoCommand proc
97 static void UpdateElement(StoryPageElement
&pe
, TileIndex tile
, uint32_t reference
, const std::string
&text
)
105 pe
.referenced_id
= tile
.base();
108 pe
.referenced_id
= (GoalID
)reference
;
110 case SPET_BUTTON_PUSH
:
111 case SPET_BUTTON_TILE
:
112 case SPET_BUTTON_VEHICLE
:
114 pe
.referenced_id
= reference
;
116 default: NOT_REACHED();
120 /** Set the button background colour. */
121 void StoryPageButtonData::SetColour(Colours button_colour
)
123 assert(button_colour
< COLOUR_END
);
124 SB(this->referenced_id
, 0, 8, button_colour
);
127 void StoryPageButtonData::SetFlags(StoryPageButtonFlags flags
)
129 SB(this->referenced_id
, 24, 8, flags
);
132 /** Set the mouse cursor used while waiting for input for the button. */
133 void StoryPageButtonData::SetCursor(StoryPageButtonCursor cursor
)
135 assert(cursor
< SPBC_END
);
136 SB(this->referenced_id
, 8, 8, cursor
);
139 /** Set the type of vehicles that are accepted by the button */
140 void StoryPageButtonData::SetVehicleType(VehicleType vehtype
)
142 assert(vehtype
== VEH_INVALID
|| vehtype
< VEH_COMPANY_END
);
143 SB(this->referenced_id
, 16, 8, vehtype
);
146 /** Get the button background colour. */
147 Colours
StoryPageButtonData::GetColour() const
149 Colours colour
= static_cast<Colours
>(GB(this->referenced_id
, 0, 8));
150 if (!IsValidColours(colour
)) return INVALID_COLOUR
;
154 StoryPageButtonFlags
StoryPageButtonData::GetFlags() const
156 return (StoryPageButtonFlags
)GB(this->referenced_id
, 24, 8);
159 /** Get the mouse cursor used while waiting for input for the button. */
160 StoryPageButtonCursor
StoryPageButtonData::GetCursor() const
162 StoryPageButtonCursor cursor
= (StoryPageButtonCursor
)GB(this->referenced_id
, 8, 8);
163 if (!IsValidStoryPageButtonCursor(cursor
)) return INVALID_SPBC
;
167 /** Get the type of vehicles that are accepted by the button */
168 VehicleType
StoryPageButtonData::GetVehicleType() const
170 return (VehicleType
)GB(this->referenced_id
, 16, 8);
173 /** Verify that the data stored a valid Colour value */
174 bool StoryPageButtonData::ValidateColour() const
176 return GB(this->referenced_id
, 0, 8) < COLOUR_END
;
179 bool StoryPageButtonData::ValidateFlags() const
181 uint8_t flags
= GB(this->referenced_id
, 24, 8);
182 /* Don't allow float left and right together */
183 if ((flags
& SPBF_FLOAT_LEFT
) && (flags
& SPBF_FLOAT_RIGHT
)) return false;
184 /* Don't allow undefined flags */
185 if (flags
& ~(SPBF_FLOAT_LEFT
| SPBF_FLOAT_RIGHT
)) return false;
189 /** Verify that the data stores a valid StoryPageButtonCursor value */
190 bool StoryPageButtonData::ValidateCursor() const
192 return GB(this->referenced_id
, 8, 8) < SPBC_END
;
195 /** Verity that the data stored a valid VehicleType value */
196 bool StoryPageButtonData::ValidateVehicleType() const
198 uint8_t vehtype
= GB(this->referenced_id
, 16, 8);
199 return vehtype
== VEH_INVALID
|| vehtype
< VEH_COMPANY_END
;
203 * Create a new story page.
204 * @param flags type of operation
205 * @param company Company for which this story page belongs to.
206 * @param text Title of the story page. Null is allowed in which case a generic page title is provided by OpenTTD.
207 * @return the cost of this operation or an error
209 std::tuple
<CommandCost
, StoryPageID
> CmdCreateStoryPage(DoCommandFlag flags
, CompanyID company
, const std::string
&text
)
211 if (!StoryPage::CanAllocateItem()) return { CMD_ERROR
, INVALID_STORY_PAGE
};
213 if (_current_company
!= OWNER_DEITY
) return { CMD_ERROR
, INVALID_STORY_PAGE
};
214 if (company
!= INVALID_COMPANY
&& !Company::IsValidID(company
)) return { CMD_ERROR
, INVALID_STORY_PAGE
};
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
;
224 s
->date
= TimerGameCalendar::date
;
225 s
->company
= company
;
228 InvalidateWindowClassesData(WC_STORY_BOOK
, -1);
229 if (StoryPage::GetNumItems() == 1) InvalidateWindowData(WC_MAIN_TOOLBAR
, 0);
231 _story_page_next_sort_value
++;
232 return { CommandCost(), s
->index
};
235 return { CommandCost(), INVALID_STORY_PAGE
};
239 * Create a new story page element.
240 * @param flags type of operation
241 * @param tile Tile location if it is a location page element, otherwise unused.
242 * @param page_id The page which the element belongs to.
243 * @param type Page element type
244 * @param reference Id of referenced object
245 * @param text Text content in case it is a text or location page element
246 * @return the cost of this operation or an error
248 std::tuple
<CommandCost
, StoryPageElementID
> CmdCreateStoryPageElement(DoCommandFlag flags
, TileIndex tile
, StoryPageID page_id
, StoryPageElementType type
, uint32_t reference
, const std::string
&text
)
250 if (!StoryPageElement::CanAllocateItem()) return { CMD_ERROR
, INVALID_STORY_PAGE_ELEMENT
};
252 /* Allow at most 128 elements per page. */
253 uint16_t element_count
= 0;
254 for (StoryPageElement
*iter
: StoryPageElement::Iterate()) {
255 if (iter
->page
== page_id
) element_count
++;
257 if (element_count
>= 128) return { CMD_ERROR
, INVALID_STORY_PAGE_ELEMENT
};
259 if (_current_company
!= OWNER_DEITY
) return { CMD_ERROR
, INVALID_STORY_PAGE_ELEMENT
};
260 if (!StoryPage::IsValidID(page_id
)) return { CMD_ERROR
, INVALID_STORY_PAGE_ELEMENT
};
261 if (!VerifyElementContentParameters(page_id
, type
, tile
, reference
, text
)) return { CMD_ERROR
, INVALID_STORY_PAGE_ELEMENT
};
264 if (flags
& DC_EXEC
) {
265 if (_story_page_element_pool
.items
== 0) {
266 /* Initialize the next sort value variable. */
267 _story_page_element_next_sort_value
= 0;
270 StoryPageElement
*pe
= new StoryPageElement();
271 pe
->sort_value
= _story_page_element_next_sort_value
;
274 UpdateElement(*pe
, tile
, reference
, text
);
276 InvalidateWindowClassesData(WC_STORY_BOOK
, page_id
);
278 _story_page_element_next_sort_value
++;
279 return { CommandCost(), pe
->index
};
282 return { CommandCost(), INVALID_STORY_PAGE_ELEMENT
};
286 * Update a new story page element.
287 * @param flags type of operation
288 * @param tile Tile location if it is a location page element, otherwise unused.
289 * @param page_element_id The page element to update.
290 * @param reference Id of referenced object
291 * @param text Text content in case it is a text or location page element
292 * @return the cost of this operation or an error
294 CommandCost
CmdUpdateStoryPageElement(DoCommandFlag flags
, TileIndex tile
, StoryPageElementID page_element_id
, uint32_t reference
, const std::string
&text
)
296 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
297 if (!StoryPageElement::IsValidID(page_element_id
)) return CMD_ERROR
;
299 StoryPageElement
*pe
= StoryPageElement::Get(page_element_id
);
300 StoryPageID page_id
= pe
->page
;
301 StoryPageElementType type
= pe
->type
;
303 if (!VerifyElementContentParameters(page_id
, type
, tile
, reference
, text
)) return CMD_ERROR
;
305 if (flags
& DC_EXEC
) {
306 UpdateElement(*pe
, tile
, reference
, text
);
307 InvalidateWindowClassesData(WC_STORY_BOOK
, pe
->page
);
310 return CommandCost();
314 * Update title of a story page.
315 * @param flags type of operation
316 * @param page_id StoryPageID to update.
317 * @param text title text of the story page.
318 * @return the cost of this operation or an error
320 CommandCost
CmdSetStoryPageTitle(DoCommandFlag flags
, StoryPageID page_id
, const std::string
&text
)
322 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
323 if (!StoryPage::IsValidID(page_id
)) return CMD_ERROR
;
325 if (flags
& DC_EXEC
) {
326 StoryPage
*p
= StoryPage::Get(page_id
);
329 InvalidateWindowClassesData(WC_STORY_BOOK
, page_id
);
332 return CommandCost();
336 * Update date of a story page.
337 * @param flags type of operation
338 * @param page_id StoryPageID to update.
340 * @return the cost of this operation or an error
342 CommandCost
CmdSetStoryPageDate(DoCommandFlag flags
, StoryPageID page_id
, TimerGameCalendar::Date date
)
344 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
345 if (!StoryPage::IsValidID(page_id
)) return CMD_ERROR
;
347 if (flags
& DC_EXEC
) {
348 StoryPage
*p
= StoryPage::Get(page_id
);
351 InvalidateWindowClassesData(WC_STORY_BOOK
, page_id
);
354 return CommandCost();
358 * Display a story page for all clients that are allowed to
359 * view the story page.
360 * @param flags type of operation
361 * @param page_id StoryPageID to show.
362 * @return the cost of this operation or an error
364 CommandCost
CmdShowStoryPage(DoCommandFlag flags
, StoryPageID page_id
)
366 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
367 if (!StoryPage::IsValidID(page_id
)) return CMD_ERROR
;
369 if (flags
& DC_EXEC
) {
370 StoryPage
*g
= StoryPage::Get(page_id
);
371 if ((g
->company
!= INVALID_COMPANY
&& g
->company
== _local_company
) || (g
->company
== INVALID_COMPANY
&& Company::IsValidID(_local_company
))) ShowStoryBook(_local_company
, page_id
, true);
374 return CommandCost();
377 * Remove a story page and associated story page elements.
378 * @param flags type of operation
379 * @param page_id StoryPageID to remove.
380 * @return the cost of this operation or an error
382 CommandCost
CmdRemoveStoryPage(DoCommandFlag flags
, StoryPageID page_id
)
384 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
385 if (!StoryPage::IsValidID(page_id
)) return CMD_ERROR
;
387 if (flags
& DC_EXEC
) {
388 StoryPage
*p
= StoryPage::Get(page_id
);
390 for (StoryPageElement
*pe
: StoryPageElement::Iterate()) {
391 if (pe
->page
== p
->index
) {
398 InvalidateWindowClassesData(WC_STORY_BOOK
, -1);
399 if (StoryPage::GetNumItems() == 0) InvalidateWindowData(WC_MAIN_TOOLBAR
, 0);
402 return CommandCost();
406 * Remove a story page element
407 * @param flags type of operation
408 * @param page_element_id StoryPageElementID to remove.
409 * @return the cost of this operation or an error
411 CommandCost
CmdRemoveStoryPageElement(DoCommandFlag flags
, StoryPageElementID page_element_id
)
413 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
414 if (!StoryPageElement::IsValidID(page_element_id
)) return CMD_ERROR
;
416 if (flags
& DC_EXEC
) {
417 StoryPageElement
*pe
= StoryPageElement::Get(page_element_id
);
418 StoryPageID page_id
= pe
->page
;
422 InvalidateWindowClassesData(WC_STORY_BOOK
, page_id
);
425 return CommandCost();
429 * Clicked/used a button on a story page.
430 * @param flags Type of operation.
431 * @param tile Tile selected, for tile selection buttons, otherwise unused.
432 * @param page_element_id story page element id of button.
433 * @param reference ID of selected item for buttons that select an item (e.g. vehicle), otherwise unused.
434 * @return The cost of the operation, or an error.
436 CommandCost
CmdStoryPageButton(DoCommandFlag flags
, TileIndex tile
, StoryPageElementID page_element_id
, VehicleID reference
)
438 if (!StoryPageElement::IsValidID(page_element_id
)) return CMD_ERROR
;
439 const StoryPageElement
*const pe
= StoryPageElement::Get(page_element_id
);
441 /* Check the player belongs to the company that owns the page. */
442 const StoryPage
*const sp
= StoryPage::Get(pe
->page
);
443 if (sp
->company
!= INVALID_COMPANY
&& sp
->company
!= _current_company
) return CMD_ERROR
;
446 case SPET_BUTTON_PUSH
:
447 /* No validation required */
448 if (flags
& DC_EXEC
) Game::NewEvent(new ScriptEventStoryPageButtonClick(_current_company
, pe
->page
, page_element_id
));
450 case SPET_BUTTON_TILE
:
451 if (!IsValidTile(tile
)) return CMD_ERROR
;
452 if (flags
& DC_EXEC
) Game::NewEvent(new ScriptEventStoryPageTileSelect(_current_company
, pe
->page
, page_element_id
, tile
));
454 case SPET_BUTTON_VEHICLE
:
455 if (!Vehicle::IsValidID(reference
)) return CMD_ERROR
;
456 if (flags
& DC_EXEC
) Game::NewEvent(new ScriptEventStoryPageVehicleSelect(_current_company
, pe
->page
, page_element_id
, reference
));
459 /* Invalid page element type, not a button. */
463 return CommandCost();