OpenTTD Source 20260311-master-g511d3794ce
story.cpp
Go to the documentation of this file.
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
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"
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"
28
29#include "safeguards.h"
30
31
32uint32_t _story_page_element_next_sort_value;
33uint32_t _story_page_next_sort_value;
34
35StoryPageElementPool _story_page_element_pool("StoryPageElement");
36StoryPagePool _story_page_pool("StoryPage");
39
40
41StoryPage::~StoryPage()
42{
43 if (CleaningPool()) return;
44
46 if (spe->page == this->index) delete spe;
47 }
48}
49
60static bool VerifyElementContentParameters(StoryPageID page_id, StoryPageElementType type, TileIndex tile, uint32_t reference, const EncodedString &text)
61{
62 StoryPageButtonData button_data{ reference };
63
64 switch (type) {
65 case SPET_TEXT:
66 if (text.empty()) return false;
67 break;
68 case SPET_LOCATION:
69 if (text.empty()) return false;
70 if (!IsValidTile(tile)) return false;
71 break;
72 case SPET_GOAL:
73 if (!Goal::IsValidID((GoalID)reference)) return false;
74 /* Reject company specific goals on global pages */
75 if (StoryPage::Get(page_id)->company == CompanyID::Invalid() && Goal::Get((GoalID)reference)->company != CompanyID::Invalid()) return false;
76 break;
78 if (!button_data.ValidateColour()) return false;
79 if (!button_data.ValidateFlags()) return false;
80 return true;
82 if (!button_data.ValidateColour()) return false;
83 if (!button_data.ValidateFlags()) return false;
84 if (!button_data.ValidateCursor()) return false;
85 return true;
87 if (!button_data.ValidateColour()) return false;
88 if (!button_data.ValidateFlags()) return false;
89 if (!button_data.ValidateCursor()) return false;
90 if (!button_data.ValidateVehicleType()) return false;
91 return true;
92 default:
93 return false;
94 }
95
96 return true;
97}
98
107static void UpdateElement(StoryPageElement &pe, TileIndex tile, uint32_t reference, const EncodedString &text)
108{
109 switch (pe.type) {
110 case SPET_TEXT:
111 pe.text = text;
112 break;
113 case SPET_LOCATION:
114 pe.text = text;
115 pe.referenced_id = tile.base();
116 break;
117 case SPET_GOAL:
118 pe.referenced_id = reference;
119 break;
120 case SPET_BUTTON_PUSH:
121 case SPET_BUTTON_TILE:
123 pe.text = text;
124 pe.referenced_id = reference;
125 break;
126 default: NOT_REACHED();
127 }
128}
129
134void StoryPageButtonData::SetColour(Colours button_colour)
135{
136 assert(button_colour < COLOUR_END);
137 SB(this->referenced_id, 0, 8, button_colour);
138}
139
145{
146 SB(this->referenced_id, 24, 8, flags);
147}
148
154{
155 assert(cursor < SPBC_END);
156 SB(this->referenced_id, 8, 8, cursor);
157}
158
164{
165 assert(vehtype == VEH_INVALID || vehtype < VEH_COMPANY_END);
166 SB(this->referenced_id, 16, 8, vehtype);
167}
168
174{
175 Colours colour = static_cast<Colours>(GB(this->referenced_id, 0, 8));
176 if (!IsValidColours(colour)) return INVALID_COLOUR;
177 return colour;
178}
179
185{
186 return (StoryPageButtonFlags)GB(this->referenced_id, 24, 8);
187}
188
194{
195 StoryPageButtonCursor cursor = (StoryPageButtonCursor)GB(this->referenced_id, 8, 8);
196 if (!IsValidStoryPageButtonCursor(cursor)) return INVALID_SPBC;
197 return cursor;
198}
199
205{
206 return (VehicleType)GB(this->referenced_id, 16, 8);
207}
208
214{
215 return GB(this->referenced_id, 0, 8) < COLOUR_END;
216}
217
224{
225 uint8_t flags = GB(this->referenced_id, 24, 8);
226 /* Don't allow float left and right together */
227 if ((flags & SPBF_FLOAT_LEFT) && (flags & SPBF_FLOAT_RIGHT)) return false;
228 /* Don't allow undefined flags */
229 if (flags & ~(SPBF_FLOAT_LEFT | SPBF_FLOAT_RIGHT)) return false;
230 return true;
231}
232
238{
239 return GB(this->referenced_id, 8, 8) < SPBC_END;
240}
241
247{
248 uint8_t vehtype = GB(this->referenced_id, 16, 8);
249 return vehtype == VEH_INVALID || vehtype < VEH_COMPANY_END;
250}
251
259std::tuple<CommandCost, StoryPageID> CmdCreateStoryPage(DoCommandFlags flags, CompanyID company, const EncodedString &text)
260{
261 if (!StoryPage::CanAllocateItem()) return { CMD_ERROR, StoryPageID::Invalid() };
262
263 if (_current_company != OWNER_DEITY) return { CMD_ERROR, StoryPageID::Invalid() };
264 if (company != CompanyID::Invalid() && !Company::IsValidID(company)) return { CMD_ERROR, StoryPageID::Invalid() };
265
266 if (flags.Test(DoCommandFlag::Execute)) {
267 if (StoryPage::GetNumItems() == 0) {
268 /* Initialize the next sort value variable. */
269 _story_page_next_sort_value = 0;
270 }
271
272 StoryPage *s = StoryPage::Create(_story_page_next_sort_value, TimerGameCalendar::date, company, text);
273
276
277 _story_page_next_sort_value++;
278 return { CommandCost(), s->index };
279 }
280
281 return { CommandCost(), StoryPageID::Invalid() };
282}
283
294std::tuple<CommandCost, StoryPageElementID> CmdCreateStoryPageElement(DoCommandFlags flags, TileIndex tile, StoryPageID page_id, StoryPageElementType type, uint32_t reference, const EncodedString &text)
295{
296 if (!StoryPageElement::CanAllocateItem()) return { CMD_ERROR, StoryPageElementID::Invalid() };
297
298 /* Allow at most 128 elements per page. */
299 uint16_t element_count = 0;
301 if (iter->page == page_id) element_count++;
302 }
303 if (element_count >= 128) return { CMD_ERROR, StoryPageElementID::Invalid() };
304
305 if (_current_company != OWNER_DEITY) return { CMD_ERROR, StoryPageElementID::Invalid() };
306 if (!StoryPage::IsValidID(page_id)) return { CMD_ERROR, StoryPageElementID::Invalid() };
307 if (!VerifyElementContentParameters(page_id, type, tile, reference, text)) return { CMD_ERROR, StoryPageElementID::Invalid() };
308
309
310 if (flags.Test(DoCommandFlag::Execute)) {
312 /* Initialize the next sort value variable. */
313 _story_page_element_next_sort_value = 0;
314 }
315
316 StoryPageElement *pe = StoryPageElement::Create(_story_page_element_next_sort_value, type, page_id);
317 UpdateElement(*pe, tile, reference, text);
318
320
321 _story_page_element_next_sort_value++;
322 return { CommandCost(), pe->index };
323 }
324
325 return { CommandCost(), StoryPageElementID::Invalid() };
326}
327
337CommandCost CmdUpdateStoryPageElement(DoCommandFlags flags, TileIndex tile, StoryPageElementID page_element_id, uint32_t reference, const EncodedString &text)
338{
340 if (!StoryPageElement::IsValidID(page_element_id)) return CMD_ERROR;
341
342 StoryPageElement *pe = StoryPageElement::Get(page_element_id);
343 StoryPageID page_id = pe->page;
344 StoryPageElementType type = pe->type;
345
346 if (!VerifyElementContentParameters(page_id, type, tile, reference, text)) return CMD_ERROR;
347
348 if (flags.Test(DoCommandFlag::Execute)) {
349 UpdateElement(*pe, tile, reference, text);
351 }
352
353 return CommandCost();
354}
355
363CommandCost CmdSetStoryPageTitle(DoCommandFlags flags, StoryPageID page_id, const EncodedString &text)
364{
366 if (!StoryPage::IsValidID(page_id)) return CMD_ERROR;
367
368 if (flags.Test(DoCommandFlag::Execute)) {
369 StoryPage *p = StoryPage::Get(page_id);
370 p->title = text;
371
373 }
374
375 return CommandCost();
376}
377
385CommandCost CmdSetStoryPageDate(DoCommandFlags flags, StoryPageID page_id, TimerGameCalendar::Date date)
386{
388 if (!StoryPage::IsValidID(page_id)) return CMD_ERROR;
389
390 if (flags.Test(DoCommandFlag::Execute)) {
391 StoryPage *p = StoryPage::Get(page_id);
392 p->date = date;
393
395 }
396
397 return CommandCost();
398}
399
407CommandCost CmdShowStoryPage(DoCommandFlags flags, StoryPageID page_id)
408{
410 if (!StoryPage::IsValidID(page_id)) return CMD_ERROR;
411
412 if (flags.Test(DoCommandFlag::Execute)) {
413 StoryPage *g = StoryPage::Get(page_id);
414 if ((g->company != CompanyID::Invalid() && g->company == _local_company) || (g->company == CompanyID::Invalid() && Company::IsValidID(_local_company))) ShowStoryBook(_local_company, page_id, true);
415 }
416
417 return CommandCost();
418}
419
425CommandCost CmdRemoveStoryPage(DoCommandFlags flags, StoryPageID page_id)
426{
428 if (!StoryPage::IsValidID(page_id)) return CMD_ERROR;
429
430 if (flags.Test(DoCommandFlag::Execute)) {
431 StoryPage *p = StoryPage::Get(page_id);
432
434 if (pe->page == p->index) {
435 delete pe;
436 }
437 }
438
439 delete p;
440
443 }
444
445 return CommandCost();
446}
447
454CommandCost CmdRemoveStoryPageElement(DoCommandFlags flags, StoryPageElementID page_element_id)
455{
457 if (!StoryPageElement::IsValidID(page_element_id)) return CMD_ERROR;
458
459 if (flags.Test(DoCommandFlag::Execute)) {
460 StoryPageElement *pe = StoryPageElement::Get(page_element_id);
461 StoryPageID page_id = pe->page;
462
463 delete pe;
464
466 }
467
468 return CommandCost();
469}
470
479CommandCost CmdStoryPageButton(DoCommandFlags flags, TileIndex tile, StoryPageElementID page_element_id, VehicleID reference)
480{
481 if (!StoryPageElement::IsValidID(page_element_id)) return CMD_ERROR;
482 const StoryPageElement *const pe = StoryPageElement::Get(page_element_id);
483
484 /* Check the player belongs to the company that owns the page. */
485 const StoryPage *const sp = StoryPage::Get(pe->page);
486 if (sp->company != CompanyID::Invalid() && sp->company != _current_company) return CMD_ERROR;
487
488 switch (pe->type) {
489 case SPET_BUTTON_PUSH:
490 /* No validation required */
491 if (flags.Test(DoCommandFlag::Execute)) Game::NewEvent(new ScriptEventStoryPageButtonClick(_current_company, pe->page, page_element_id));
492 break;
493 case SPET_BUTTON_TILE:
494 if (!IsValidTile(tile)) return CMD_ERROR;
495 if (flags.Test(DoCommandFlag::Execute)) Game::NewEvent(new ScriptEventStoryPageTileSelect(_current_company, pe->page, page_element_id, tile));
496 break;
498 if (!Vehicle::IsValidID(reference)) return CMD_ERROR;
499 if (flags.Test(DoCommandFlag::Execute)) Game::NewEvent(new ScriptEventStoryPageVehicleSelect(_current_company, pe->page, page_element_id, reference));
500 break;
501 default:
502 /* Invalid page element type, not a button. */
503 return CMD_ERROR;
504 }
505
506 return CommandCost();
507}
508
constexpr T SB(T &x, const uint8_t s, const uint8_t n, const U d)
Set n bits in x starting at bit s to d.
static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
Common return value for all commands.
Container for an encoded string, created by GetEncodedString.
static void NewEvent(class ScriptEvent *event)
Queue a new event for the game script.
static Date date
Current date in days (day counter).
Functions related to commands.
static const CommandCost CMD_ERROR
Define a default return value for a failed command.
@ Execute
execute the given command
Definition of stuff that is very close to a company, like the company struct itself.
CompanyID _local_company
Company controlled by the human player at this client. Can also be COMPANY_SPECTATOR.
CompanyID _current_company
Company currently doing an action.
Functions related to companies.
static constexpr Owner OWNER_DEITY
The object is owned by a superuser / goal script.
Base functions for all Games.
Goal base class.
Basic types related to goals.
PoolID< uint16_t, struct GoalIDTag, 64000, 0xFFFF > GoalID
ID of a goal.
Definition goal_type.h:38
GUI functions that shouldn't be here.
void ShowStoryBook(CompanyID company, StoryPageID page_id=StoryPageID::Invalid(), bool centered=false)
Raise or create the story book window for company, at page page_id.
bool IsValidColours(Colours colours)
Checks if a Colours value is valid.
Some methods of Pool are placed here in order to reduce compilation time and binary size.
#define INSTANTIATE_POOL_METHODS(name)
Force instantiation of pool methods so we don't get linker errors.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
std::tuple< CommandCost, StoryPageElementID > CmdCreateStoryPageElement(DoCommandFlags flags, TileIndex tile, StoryPageID page_id, StoryPageElementType type, uint32_t reference, const EncodedString &text)
Create a new story page element.
Definition story.cpp:294
static bool VerifyElementContentParameters(StoryPageID page_id, StoryPageElementType type, TileIndex tile, uint32_t reference, const EncodedString &text)
This helper for Create/Update PageElement Cmd procedure verifies if the page element parameters are c...
Definition story.cpp:60
std::tuple< CommandCost, StoryPageID > CmdCreateStoryPage(DoCommandFlags flags, CompanyID company, const EncodedString &text)
Create a new story page.
Definition story.cpp:259
CommandCost CmdUpdateStoryPageElement(DoCommandFlags flags, TileIndex tile, StoryPageElementID page_element_id, uint32_t reference, const EncodedString &text)
Update a new story page element.
Definition story.cpp:337
static void UpdateElement(StoryPageElement &pe, TileIndex tile, uint32_t reference, const EncodedString &text)
This helper for Create/Update PageElement Cmd procedure updates a page element with new content data.
Definition story.cpp:107
CommandCost CmdRemoveStoryPageElement(DoCommandFlags flags, StoryPageElementID page_element_id)
Remove a story page element.
Definition story.cpp:454
CommandCost CmdRemoveStoryPage(DoCommandFlags flags, StoryPageID page_id)
Remove a story page and associated story page elements.
Definition story.cpp:425
CommandCost CmdStoryPageButton(DoCommandFlags flags, TileIndex tile, StoryPageElementID page_element_id, VehicleID reference)
Clicked/used a button on a story page.
Definition story.cpp:479
CommandCost CmdSetStoryPageTitle(DoCommandFlags flags, StoryPageID page_id, const EncodedString &text)
Update title of a story page.
Definition story.cpp:363
CommandCost CmdSetStoryPageDate(DoCommandFlags flags, StoryPageID page_id, TimerGameCalendar::Date date)
Update date of a story page.
Definition story.cpp:385
CommandCost CmdShowStoryPage(DoCommandFlags flags, StoryPageID page_id)
Display a story page for all clients that are allowed to view the story page.
Definition story.cpp:407
StoryPage base class.
StoryPageButtonFlags
Flags available for buttons.
Definition story_base.h:41
StoryPageElementType
Each story page element is one of these types.
Definition story_base.h:29
@ SPET_LOCATION
An element that references a tile along with a one-line text.
Definition story_base.h:31
@ SPET_GOAL
An element that references a goal.
Definition story_base.h:32
@ SPET_BUTTON_PUSH
A push button that triggers an immediate event.
Definition story_base.h:33
@ SPET_BUTTON_TILE
A button that allows the player to select a tile, and triggers an event with the tile.
Definition story_base.h:34
@ SPET_TEXT
A text element.
Definition story_base.h:30
@ SPET_BUTTON_VEHICLE
A button that allows the player to select a vehicle, and triggers an event with the vehicle.
Definition story_base.h:35
StoryPageButtonCursor
Mouse cursors usable by story page buttons.
Definition story_base.h:49
bool IsValidStoryPageButtonCursor(StoryPageButtonCursor cursor)
Checks if a StoryPageButtonCursor value is valid.
Definition story_base.h:115
Command definitions related to stories.
PoolID< uint16_t, struct StoryPageIDTag, 64000, 0xFFFF > StoryPageID
ID of a story page.
Definition story_type.h:16
PoolID< uint16_t, struct StoryPageElementIDTag, 64000, 0xFFFF > StoryPageElementID
ID of a story page element.
Definition story_type.h:15
Functions related to low-level strings.
static Pool::IterateWrapper< StoryPageElement > Iterate(size_t from=0)
static StoryPage * Get(auto index)
static bool IsValidID(auto index)
static T * Create(Targs &&... args)
Helper to construct packed "id" values for button-type StoryPageElement.
Definition story_base.h:121
Colours GetColour() const
Get the button background colour.
Definition story.cpp:173
VehicleType GetVehicleType() const
Get the type of vehicles that are accepted by the button.
Definition story.cpp:204
void SetColour(Colours button_colour)
Set the button background colour.
Definition story.cpp:134
bool ValidateVehicleType() const
Verity that the data stored a valid VehicleType value.
Definition story.cpp:246
StoryPageButtonFlags GetFlags() const
Get the button flags.
Definition story.cpp:184
bool ValidateFlags() const
Verify that valid flags were set.
Definition story.cpp:223
bool ValidateColour() const
Verify that the data stored a valid Colour value.
Definition story.cpp:213
void SetVehicleType(VehicleType vehtype)
Set the type of vehicles that are accepted by the button.
Definition story.cpp:163
StoryPageButtonCursor GetCursor() const
Get the mouse cursor used while waiting for input for the button.
Definition story.cpp:193
bool ValidateCursor() const
Verify that the data stores a valid StoryPageButtonCursor value.
Definition story.cpp:237
void SetCursor(StoryPageButtonCursor cursor)
Set the mouse cursor used while waiting for input for the button.
Definition story.cpp:153
void SetFlags(StoryPageButtonFlags flags)
Set the button flags.
Definition story.cpp:144
Struct about story page elements.
Definition story_base.h:143
uint32_t referenced_id
Id of referenced object (location, goal etc.).
Definition story_base.h:148
EncodedString text
Static content text of page element.
Definition story_base.h:149
StoryPageElementType type
Type of page element.
Definition story_base.h:146
StoryPageID page
Id of the page which the page element belongs to.
Definition story_base.h:145
Struct about stories, current and completed.
Definition story_base.h:162
EncodedString title
Title of story page.
Definition story_base.h:167
CompanyID company
StoryPage is for a specific company; CompanyID::Invalid() if it is global.
Definition story_base.h:165
TimerGameCalendar::Date date
Date when the page was created.
Definition story_base.h:164
Map writing/reading functions for tiles.
bool IsValidTile(Tile tile)
Checks if a tile is valid.
Definition tile_map.h:161
StrongType::Typedef< uint32_t, struct TileIndexTag, StrongType::Compare, StrongType::Integer, StrongType::Compatible< int32_t >, StrongType::Compatible< int64_t > > TileIndex
The index/ID of a Tile.
Definition tile_type.h:92
Definition of the game-calendar-timer.
Base class for all vehicles.
PoolID< uint32_t, struct VehicleIDTag, 0xFF000, 0xFFFFF > VehicleID
The type all our vehicle IDs have.
VehicleType
Available vehicle types.
@ VEH_INVALID
Non-existing type of vehicle.
@ VEH_COMPANY_END
Last company-ownable type.
void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
Mark window data of the window of a given class and specific window number as invalid (in need of re-...
Definition window.cpp:3321
void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
Mark window data of all windows of a given class as invalid (in need of re-computing) Note that by de...
Definition window.cpp:3339
Window functions not directly related to making/drawing windows.
@ WC_STORY_BOOK
Story book; Window numbers:
@ WC_MAIN_TOOLBAR
Main toolbar (the long bar at the top); Window numbers:
Definition window_type.h:63