Fix #8316: Make sort industries by production and transported with a cargo filter...
[openttd-github.git] / src / story.cpp
blob641ed7fbf550c24517f6a0defff06d5404606c11
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 "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"
19 #include "tile_map.h"
20 #include "goal_type.h"
21 #include "goal_base.h"
22 #include "window_func.h"
23 #include "gui.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)
42 /**
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 };
56 switch (type) {
57 case SPET_TEXT:
58 if (StrEmpty(text)) return false;
59 break;
60 case SPET_LOCATION:
61 if (StrEmpty(text)) return false;
62 if (!IsValidTile(tile)) return false;
63 break;
64 case SPET_GOAL:
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;
68 break;
69 case SPET_BUTTON_PUSH:
70 if (!button_data.ValidateColour()) return false;
71 return true;
72 case SPET_BUTTON_TILE:
73 if (!button_data.ValidateColour()) 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.ValidateCursor()) return false;
79 if (!button_data.ValidateVehicleType()) return false;
80 return true;
81 default:
82 return false;
85 return true;
88 /**
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)
98 switch (pe.type) {
99 case SPET_TEXT:
100 pe.text = stredup(text);
101 break;
102 case SPET_LOCATION:
103 pe.text = stredup(text);
104 pe.referenced_id = tile;
105 break;
106 case SPET_GOAL:
107 pe.referenced_id = (GoalID)reference;
108 break;
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;
114 break;
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;
181 return true;
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.
203 * @param p2 unused.
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;
224 s->date = _date;
225 s->company = company;
226 if (text.empty()) {
227 s->title = nullptr;
228 } else {
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;
279 pe->type = type;
280 pe->page = page_id;
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.
329 * @param p2 unused
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);
341 free(p->title);
342 if (text.empty()) {
343 p->title = nullptr;
344 } else {
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
360 * @param text unused
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);
372 p->date = date;
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.
386 * @param p2 unused
387 * @param text unused
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.
408 * @param p2 unused.
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) {
423 delete pe;
427 delete p;
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.
441 * @param p2 unused.
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;
455 delete pe;
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;
483 switch (pe->type) {
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));
487 break;
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));
491 break;
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));
495 break;
496 default:
497 /* Invalid page element type, not a button. */
498 return CMD_ERROR;
501 return CommandCost();