Codefix: Documentation comment in IndustryDirectoryWindow (#13059)
[openttd-github.git] / src / story.cpp
blob44898ffab887fdca13248f5caef65aecf996d2f1
1 /*
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/>.
6 */
8 /** @file story.cpp Handling of stories. */
10 #include "stdafx.h"
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"
18 #include "tile_map.h"
19 #include "goal_type.h"
20 #include "goal_base.h"
21 #include "window_func.h"
22 #include "gui.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)
40 /**
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 };
54 switch (type) {
55 case SPET_TEXT:
56 if (text.empty()) return false;
57 break;
58 case SPET_LOCATION:
59 if (text.empty()) return false;
60 if (!IsValidTile(tile)) return false;
61 break;
62 case SPET_GOAL:
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;
66 break;
67 case SPET_BUTTON_PUSH:
68 if (!button_data.ValidateColour()) return false;
69 if (!button_data.ValidateFlags()) return false;
70 return true;
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;
75 return true;
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;
81 return true;
82 default:
83 return false;
86 return true;
89 /**
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)
99 switch (pe.type) {
100 case SPET_TEXT:
101 pe.text = text;
102 break;
103 case SPET_LOCATION:
104 pe.text = text;
105 pe.referenced_id = tile.base();
106 break;
107 case SPET_GOAL:
108 pe.referenced_id = (GoalID)reference;
109 break;
110 case SPET_BUTTON_PUSH:
111 case SPET_BUTTON_TILE:
112 case SPET_BUTTON_VEHICLE:
113 pe.text = text;
114 pe.referenced_id = reference;
115 break;
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;
151 return 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;
164 return cursor;
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;
186 return true;
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;
226 s->title = text;
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;
272 pe->type = type;
273 pe->page = page_id;
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);
327 p->title = text;
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.
339 * @param date date
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);
349 p->date = date;
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) {
392 delete pe;
396 delete p;
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;
420 delete pe;
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;
445 switch (pe->type) {
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));
449 break;
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));
453 break;
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));
457 break;
458 default:
459 /* Invalid page element type, not a button. */
460 return CMD_ERROR;
463 return CommandCost();