Merge branch 'master' into jgrpp
# Conflicts: # cmake/SourceList.cmake # src/build_vehicle_gui.cpp # src/company_gui.cpp # src/console_cmds.cpp # src/depot_base.h # src/elrail.cpp # src/network/core/udp.cpp # src/network/network_admin.cpp # src/network/network_chat_gui.cpp # src/network/network_gui.cpp # src/network/network_server.cpp # src/newgrf.cpp # src/newgrf_engine.cpp # src/newgrf_railtype.cpp # src/newgrf_railtype.h # src/newgrf_storage.h # src/os/unix/crashlog_unix.cpp # src/rail.h # src/rail_cmd.cpp # src/rail_gui.cpp # src/road_cmd.cpp # src/road_map.h # src/saveload/labelmaps_sl.cpp # src/settings_gui.cpp # src/settings_type.h # src/sl/oldloader_sl.cpp # src/station_cmd.cpp # src/station_gui.cpp # src/table/settings/world_settings.ini # src/tests/test_script_admin.cpp # src/textfile_gui.cpp # src/toolbar_gui.cpp # src/train_cmd.cpp # src/tunnelbridge_cmd.cpp # src/vehicle_gui.cpp # src/widget.cpp # src/window.cpp # src/window_gui.h # src/window_type.hpull/611/head
commit
c929f7075e
@ -0,0 +1,214 @@
|
||||
# OpenTTD's Savegame Format
|
||||
|
||||
Last updated: 2021-06-15
|
||||
|
||||
## Outer container
|
||||
|
||||
Savegames for OpenTTD start with an outer container, to contain the compressed data for the rest of the savegame.
|
||||
|
||||
`[0..3]` - The first four bytes indicate what compression is used.
|
||||
In ASCII, these values are possible:
|
||||
|
||||
- `OTTD` - Compressed with LZO (deprecated, only really old savegames would use this).
|
||||
- `OTTN` - No compression.
|
||||
- `OTTZ` - Compressed with zlib.
|
||||
- `OTTX` - Compressed with LZMA.
|
||||
|
||||
`[4..5]` - The next two bytes indicate which savegame version used.
|
||||
|
||||
`[6..7]` - The next two bytes can be ignored, and were only used in really old savegames.
|
||||
|
||||
`[8..N]` - Next follows a binary blob which is compressed with the indicated compression algorithm.
|
||||
|
||||
The rest of this document talks about this decompressed blob of data.
|
||||
|
||||
## Data types
|
||||
|
||||
The savegame is written in Big Endian, so when we talk about a 16-bit unsigned integer (`uint16`), we mean it is stored in Big Endian.
|
||||
|
||||
The following types are valid:
|
||||
|
||||
- `1` - `int8` / `SLE_FILE_I8` -8-bit signed integer
|
||||
- `2` - `uint8` / `SLE_FILE_U8` - 8-bit unsigned integer
|
||||
- `3` - `int16` / `SLE_FILE_I16` - 16-bit signed integer
|
||||
- `4` - `uint16` / `SLE_FILE_U16` - 16-bit unsigned integer
|
||||
- `5` - `int32` / `SLE_FILE_I32` - 32-bit signed integer
|
||||
- `6` - `uint32` / `SLE_FILE_U32` - 32-bit unsigned integer
|
||||
- `7` - `int64` / `SLE_FILE_I64` - 64-bit signed integer
|
||||
- `8` - `uint64` / `SLE_FILE_U64` - 64-bit unsigned integer
|
||||
- `9` - `StringID` / `SLE_FILE_STRINGID` - a StringID inside the OpenTTD's string table
|
||||
- `10` - `str` / `SLE_FILE_STRING` - a string (prefixed with a length-field)
|
||||
- `11` - `struct` / `SLE_FILE_STRUCT` - a struct
|
||||
|
||||
### Gamma value
|
||||
|
||||
There is also a field-type called `gamma`.
|
||||
This is most often used for length-fields, and uses as few bytes as possible to store an integer.
|
||||
For values <= 127, it uses a single byte.
|
||||
For values > 127, it uses two bytes and sets the highest bit to high.
|
||||
For values > 32767, it uses three bytes and sets the two highest bits to high.
|
||||
And this continues till the value fits.
|
||||
In a more visual approach:
|
||||
```
|
||||
0xxxxxxx
|
||||
10xxxxxx xxxxxxxx
|
||||
110xxxxx xxxxxxxx xxxxxxxx
|
||||
1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
|
||||
11110--- xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
|
||||
```
|
||||
|
||||
## Chunks
|
||||
|
||||
Savegames for OpenTTD store their data in chunks.
|
||||
Each chunk contains data for a certain part of the game, for example "Companies", "Vehicles", etc.
|
||||
|
||||
`[0..3]` - Each chunk starts with four bytes to indicate the tag.
|
||||
If the tag is `\x00\x00\x00\x00` it means the end of the savegame is reached.
|
||||
An example of a valid tag is `PLYR` when looking at it via ASCII, which contains the information of all the companies.
|
||||
|
||||
`[4..4]` - Next follows a byte where the lower 4 bits contain the type.
|
||||
The possible valid types are:
|
||||
|
||||
- `0` - `CH_RIFF` - This chunk is a binary blob.
|
||||
- `1` - `CH_ARRAY` - This chunk is a list of items.
|
||||
- `2` - `CH_SPARSE_ARRAY` - This chunk is a list of items.
|
||||
- `3` - `CH_TABLE` - This chunk is self-describing list of items.
|
||||
- `4` - `CH_SPARSE_TABLE` - This chunk is self-describing list of items.
|
||||
|
||||
Now per type the format is (slightly) different.
|
||||
|
||||
### CH_RIFF
|
||||
|
||||
(since savegame version 295, this chunk type is only used for MAP-chunks, containing bit-information about each tile on the map)
|
||||
|
||||
A `CH_RIFF` starts with an `uint24` which together with the upper-bits of the type defines the length of the chunk.
|
||||
In pseudo-code:
|
||||
|
||||
```
|
||||
type = read uint8
|
||||
if type == 0
|
||||
length = read uint24
|
||||
length |= ((type >> 4) << 24)
|
||||
```
|
||||
|
||||
The next `length` bytes are part of the chunk.
|
||||
What those bytes mean depends on the tag of the chunk; further details per chunk can be found in the source-code.
|
||||
|
||||
### CH_ARRAY / CH_SPARSE_ARRAY
|
||||
|
||||
(this chunk type is deprecated since savegame version 295 and is no longer in use)
|
||||
|
||||
`[0..G1]` - A `CH_ARRAY` / `CH_SPARSE_ARRAY` starts with a `gamma`, indicating the size of the next item plus one.
|
||||
If this size value is zero, it indicates the end of the list.
|
||||
This indicates the full length of the next item minus one.
|
||||
In psuedo-code:
|
||||
|
||||
```
|
||||
loop
|
||||
size = read gamma - 1
|
||||
if size == -1
|
||||
break loop
|
||||
read <size> bytes
|
||||
```
|
||||
|
||||
`[]` - For `CH_ARRAY` there is an implicit index.
|
||||
The loop starts at zero, and every iteration adds one to the index.
|
||||
For entries in the game that were not allocated, the `size` will be zero.
|
||||
|
||||
`[G1+1..G2]` - For `CH_SPARSE_ARRAY` there is an explicit index.
|
||||
The `gamma` following the size indicates the index.
|
||||
|
||||
The content of the item is a binary blob, and similar to `CH_RIFF`, it depends on the tag of the chunk what it means.
|
||||
Please check the source-code for further details.
|
||||
|
||||
### CH_TABLE / CH_SPARSE_TABLE
|
||||
|
||||
(this chunk type only exists since savegame version 295)
|
||||
|
||||
Both `CH_TABLE` and `CH_SPARSE_TABLE` are very similar to `CH_ARRAY` / `CH_SPARSE_ARRAY` respectively.
|
||||
The only change is that the chunk starts with a header.
|
||||
This header describes the chunk in details; with the header you know the meaning of each byte in the binary blob that follows.
|
||||
|
||||
`[0..G]` - The header starts with a `gamma` to indicate the size of all the headers in this chunk plus one.
|
||||
If this size value is zero, it means there is no header, which should never be the case.
|
||||
|
||||
Next follows a list of `(type, key)` pairs:
|
||||
|
||||
- `[0..0]` - Type of the field.
|
||||
- `[1..G]` - `gamma` to indicate length of key.
|
||||
- `[G+1..N]` - Key (in UTF-8) of the field.
|
||||
|
||||
If at any point `type` is zero, the list stops (and no `key` follows).
|
||||
|
||||
The `type`'s lower 4 bits indicate the data-type (see chapter above).
|
||||
The `type`'s 5th bit (so `0x10`) indicates if the field is a list, and if this field in every record starts with a `gamma` to indicate how many times the `type` is repeated.
|
||||
|
||||
If the `type` indicates either a `struct` or `str`, the `0x10` flag is also always set.
|
||||
|
||||
As the savegame format allows (list of) structs in structs, if any `struct` type is found, this header will be followed by a header of that struct.
|
||||
This nesting of structs is stored depth-first, so given this table:
|
||||
|
||||
```
|
||||
type | key
|
||||
-----------------
|
||||
uint8 | counter
|
||||
struct | substruct1
|
||||
struct | substruct2
|
||||
```
|
||||
|
||||
With `substruct1` being like:
|
||||
|
||||
```
|
||||
type | key
|
||||
-----------------
|
||||
uint8 | counter
|
||||
struct | substruct3
|
||||
```
|
||||
|
||||
The headers will be, in order: `table`, `substruct1`, `substruct3`, `substruct2`, each ending with a `type` is zero field.
|
||||
|
||||
After reading all the fields of all the headers, there is a list of records.
|
||||
To read this, see `CH_ARRAY` / `CH_SPARSE_ARRAY` for details.
|
||||
|
||||
As each `type` has a well defined length, you can read the records even without knowing anything about the chunk-tag yourself.
|
||||
|
||||
Do remember, that if the `type` had the `0x10` flag active, the field in the record first has a `gamma` to indicate how many times that `type` is repeated.
|
||||
|
||||
#### Guidelines for network-compatible patch-packs
|
||||
|
||||
For network-compatible patch-packs (client-side patches that can play together with unpatched clients) we advise to prefix the field-name with `__<shortname>` when introducing new fields to an existing chunk.
|
||||
|
||||
Example: you have an extra setting called `auto_destroy_rivers` you want to store in the savegame for your patched client called `mypp`.
|
||||
We advise you to call this setting `__mypp_auto_destroy_rivers` in the settings chunk.
|
||||
|
||||
Doing it this way ensures that a savegame created by these patch-packs can still safely be loaded by unpatched clients.
|
||||
They will simply ignore the field and continue loading the savegame as usual.
|
||||
The prefix is strongly advised to avoid conflicts with future-settings in an unpatched client or conflicts with other patch-packs.
|
||||
|
||||
## Scripts custom data format
|
||||
|
||||
Script chunks (`AIPL` and `GSDT`) use `CH_TABLE` chunk type.
|
||||
|
||||
At the end of each record there's an `uint8` to indicate if there's custom data (1) or not (0).
|
||||
|
||||
There are 6 data types for scripts, called `script-data-type`.
|
||||
When saving, each `script-data-type` starts with the type marker saved as `uint8` followed by the actual data.
|
||||
- `0` - `SQSL_INT`:
|
||||
- an `int64` with the actual value (`int32` before savegame version 296).
|
||||
- `1` - `SQSL_STRING`:
|
||||
- an `uint8` with the string length.
|
||||
- a list of `int8` for the string itself.
|
||||
- `2` - `SQSL_ARRAY`:
|
||||
- each element saved as `script-data-type`.
|
||||
- an `SQSL_ARRAY_TABLE_END` (0xFF) marker (`uint8`).
|
||||
- `3` - `SQSL_TABLE`:
|
||||
- for each element:
|
||||
- key saved as `script-data-type`.
|
||||
- value saved as `script-data-type`.
|
||||
- an `SQSL_ARRAY_TABLE_END` (0xFF) marker (`uint8`).
|
||||
- `4` - `SQSL_BOOL`:
|
||||
- an `uint8` with 0 (false) or 1 (true).
|
||||
- `5` - `SQSL_NULL`:
|
||||
- (no data follows)
|
||||
|
||||
The first data type is always a `SQSL_TABLE`.
|
@ -0,0 +1,34 @@
|
||||
# OpenTTD's Symbol Server
|
||||
|
||||
For all official releases, OpenTTD collects the Breakpad Symbols (SYM-files) and Microsoft's Symbols (PDB-files), and publishes them on our own Symbol Server (https://symbols.openttd.org).
|
||||
|
||||
These symbol files are needed to analyze `crash.dmp` files as attached to issues by users.
|
||||
A `crash.dmp` is created on Windows, Linux, and MacOS when a crash happens.
|
||||
This combined with the `crash.log` should give a pretty good indication what was going on at the moment the game crashed.
|
||||
|
||||
## Analyzing a crash.dmp
|
||||
|
||||
### MSVC
|
||||
|
||||
In MSVC you can add the above URL as Symbol Server (and please enable MSVC's for all other libraries), allowing you to analyze `crash.dmp`.
|
||||
|
||||
Now simply open up the `crash.dmp`, and start debugging.
|
||||
|
||||
### All other platforms
|
||||
|
||||
The best tool to use is `minidump-stackwalk` as published in the Rust's cargo index:
|
||||
|
||||
```bash
|
||||
cargo install minidump-stackwalk
|
||||
```
|
||||
|
||||
For how to install Rust, please see [here](https://doc.rust-lang.org/cargo/getting-started/installation.html).
|
||||
|
||||
Now run the tool like:
|
||||
|
||||
```bash
|
||||
minidump-stackwalk <crash.dmp> --symbols-url https://symbols.openttd.org
|
||||
```
|
||||
|
||||
For convenience, the above Symbol Server also check with Mozilla's Symbol Server in case any other library but OpenTTD is requested.
|
||||
This means files like `libc`, `kernel32.dll`, etc are all available on the above mentioned Symbol Server.
|
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* 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.
|
||||
* 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.
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/** @file help_gui.cpp GUI to access manuals and related. */
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gui.h"
|
||||
#include "window_gui.h"
|
||||
#include "textfile_gui.h"
|
||||
#include "fileio_func.h"
|
||||
#include "table/control_codes.h"
|
||||
#include "string_func.h"
|
||||
#include "openttd.h"
|
||||
|
||||
#include "help_gui.h"
|
||||
#include "widgets/help_widget.h"
|
||||
#include "widgets/misc_widget.h"
|
||||
|
||||
#include "safeguards.h"
|
||||
|
||||
static const std::string README_FILENAME = "README.md";
|
||||
static const std::string CHANGELOG_FILENAME = "changelog.txt";
|
||||
static const std::string KNOWN_BUGS_FILENAME = "known-bugs.txt";
|
||||
static const std::string LICENSE_FILENAME = "COPYING.md";
|
||||
|
||||
static const std::string WEBSITE_LINK = "https://www.openttd.org/";
|
||||
static const std::string WIKI_LINK = "https://wiki.openttd.org/";
|
||||
static const std::string BUGTRACKER_LINK = "https://bugs.openttd.org/";
|
||||
static const std::string COMMUNITY_LINK = "https://community.openttd.org/";
|
||||
|
||||
/** Only show the first 20 changelog versions in the textfile viewer. */
|
||||
static constexpr size_t CHANGELOG_VERSIONS_LIMIT = 20;
|
||||
|
||||
/**
|
||||
* Find the path to the game manual file.
|
||||
*
|
||||
* @param filename The filename to find.
|
||||
* @return std::string The path to the filename if found.
|
||||
*/
|
||||
static std::optional<std::string> FindGameManualFilePath(std::string_view filename)
|
||||
{
|
||||
static const Searchpath searchpaths[] = {
|
||||
SP_APPLICATION_BUNDLE_DIR, SP_INSTALLATION_DIR, SP_SHARED_DIR, SP_BINARY_DIR, SP_WORKING_DIR
|
||||
};
|
||||
|
||||
for (Searchpath sp : searchpaths) {
|
||||
auto file_path = FioGetDirectory(sp, BASE_DIR) + filename.data();
|
||||
if (FioCheckFileExists(file_path, NO_DIRECTORY)) return file_path;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/** Window class displaying the game manual textfile viewer. */
|
||||
struct GameManualTextfileWindow : public TextfileWindow {
|
||||
GameManualTextfileWindow(std::string_view filename) : TextfileWindow(TFT_GAME_MANUAL)
|
||||
{
|
||||
/* Mark the content of these files as trusted. */
|
||||
this->trusted = true;
|
||||
|
||||
auto filepath = FindGameManualFilePath(filename);
|
||||
/* The user could, in theory, have moved the file. So just show an empty window if that is the case. */
|
||||
if (!filepath.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->filepath = filepath.value();
|
||||
this->LoadTextfile(this->filepath, NO_DIRECTORY);
|
||||
this->OnClick({ 0, 0 }, WID_TF_WRAPTEXT, 1);
|
||||
}
|
||||
|
||||
void SetStringParameters(int widget) const override
|
||||
{
|
||||
if (widget == WID_TF_CAPTION) {
|
||||
SetDParamStr(0, this->filename);
|
||||
}
|
||||
}
|
||||
|
||||
void AfterLoadText() override
|
||||
{
|
||||
if (this->filename == CHANGELOG_FILENAME) {
|
||||
this->link_anchors.clear();
|
||||
this->AfterLoadChangelog();
|
||||
this->GetWidget<NWidgetStacked>(WID_TF_SEL_JUMPLIST)->SetDisplayedPlane(this->jumplist.empty() ? SZSP_HORIZONTAL : 0);
|
||||
} else {
|
||||
this->TextfileWindow::AfterLoadText();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For changelog files, add a jumplist entry for each version.
|
||||
*
|
||||
* This is hardcoded and assumes "---" are used to separate versions.
|
||||
*/
|
||||
void AfterLoadChangelog()
|
||||
{
|
||||
/* Look for lines beginning with ---, they indicate that the previous line was a release name. */
|
||||
for (size_t line_index = 0; line_index < this->lines.size(); ++line_index) {
|
||||
const Line &line = this->lines[line_index];
|
||||
if (line.text.find("---", 0) != 0) continue;
|
||||
|
||||
if (this->jumplist.size() >= CHANGELOG_VERSIONS_LIMIT) {
|
||||
this->lines.resize(line_index - 2);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Mark the version header with a colour, and add it to the jumplist. */
|
||||
this->lines[line_index - 1].colour = TC_GOLD;
|
||||
this->lines[line_index].colour = TC_GOLD;
|
||||
this->jumplist.push_back(line_index - 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Window class displaying the help window. */
|
||||
struct HelpWindow : public Window {
|
||||
|
||||
HelpWindow(WindowDesc *desc, WindowNumber number) : Window(desc)
|
||||
{
|
||||
this->InitNested(number);
|
||||
|
||||
this->EnableTextfileButton(README_FILENAME, WID_HW_README);
|
||||
this->EnableTextfileButton(CHANGELOG_FILENAME, WID_HW_CHANGELOG);
|
||||
this->EnableTextfileButton(KNOWN_BUGS_FILENAME, WID_HW_KNOWN_BUGS);
|
||||
this->EnableTextfileButton(LICENSE_FILENAME, WID_HW_LICENSE);
|
||||
}
|
||||
|
||||
void OnClick(Point pt, int widget, int click_count) override
|
||||
{
|
||||
switch (widget) {
|
||||
case WID_HW_README:
|
||||
new GameManualTextfileWindow(README_FILENAME);
|
||||
break;
|
||||
case WID_HW_CHANGELOG:
|
||||
new GameManualTextfileWindow(CHANGELOG_FILENAME);
|
||||
break;
|
||||
case WID_HW_KNOWN_BUGS:
|
||||
new GameManualTextfileWindow(KNOWN_BUGS_FILENAME);
|
||||
break;
|
||||
case WID_HW_LICENSE:
|
||||
new GameManualTextfileWindow(LICENSE_FILENAME);
|
||||
break;
|
||||
case WID_HW_WEBSITE:
|
||||
OpenBrowser(WEBSITE_LINK.c_str());
|
||||
break;
|
||||
case WID_HW_WIKI:
|
||||
OpenBrowser(WIKI_LINK.c_str());
|
||||
break;
|
||||
case WID_HW_BUGTRACKER:
|
||||
OpenBrowser(BUGTRACKER_LINK.c_str());
|
||||
break;
|
||||
case WID_HW_COMMUNITY:
|
||||
OpenBrowser(COMMUNITY_LINK.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void EnableTextfileButton(std::string_view filename, int button_widget)
|
||||
{
|
||||
this->GetWidget<NWidgetLeaf>(button_widget)->SetDisabled(!FindGameManualFilePath(filename).has_value());
|
||||
}
|
||||
};
|
||||
|
||||
static const NWidgetPart _nested_helpwin_widgets[] = {
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
|
||||
NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_HELP_WINDOW_CAPTION, STR_NULL),
|
||||
EndContainer(),
|
||||
|
||||
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
|
||||
NWidget(NWID_SPACER), SetMinimalSize(0, 8),
|
||||
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(NWID_SPACER), SetMinimalSize(10, 0),
|
||||
|
||||
NWidget(NWID_VERTICAL), SetPIP(0, 2, 0),
|
||||
NWidget(WWT_FRAME, COLOUR_DARK_GREEN), SetDataTip(STR_HELP_WINDOW_WEBSITES, STR_NULL),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_HW_WEBSITE), SetDataTip(STR_HELP_WINDOW_MAIN_WEBSITE, STR_NULL), SetMinimalSize(128, 12), SetFill(1, 0),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_HW_WIKI), SetDataTip(STR_HELP_WINDOW_MANUAL_WIKI, STR_NULL), SetMinimalSize(128, 12), SetFill(1, 0),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_HW_BUGTRACKER), SetDataTip(STR_HELP_WINDOW_BUGTRACKER, STR_NULL), SetMinimalSize(128, 12), SetFill(1, 0),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_HW_COMMUNITY), SetDataTip(STR_HELP_WINDOW_COMMUNITY, STR_NULL), SetMinimalSize(128, 12), SetFill(1, 0),
|
||||
EndContainer(),
|
||||
EndContainer(),
|
||||
|
||||
NWidget(NWID_SPACER), SetMinimalSize(10, 0),
|
||||
|
||||
NWidget(NWID_VERTICAL), SetPIP(0, 2, 0),
|
||||
NWidget(WWT_FRAME, COLOUR_DARK_GREEN), SetDataTip(STR_HELP_WINDOW_DOCUMENTS, STR_NULL),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_HW_README), SetDataTip(STR_HELP_WINDOW_README, STR_NULL), SetMinimalSize(128, 12), SetFill(1, 0),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_HW_CHANGELOG), SetDataTip(STR_HELP_WINDOW_CHANGELOG, STR_NULL), SetMinimalSize(128, 12), SetFill(1, 0),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_HW_KNOWN_BUGS),SetDataTip(STR_HELP_WINDOW_KNOWN_BUGS, STR_NULL), SetMinimalSize(128, 12), SetFill(1, 0),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_HW_LICENSE), SetDataTip(STR_HELP_WINDOW_LICENSE, STR_NULL), SetMinimalSize(128, 12), SetFill(1, 0),
|
||||
EndContainer(),
|
||||
EndContainer(),
|
||||
|
||||
NWidget(NWID_SPACER), SetMinimalSize(10, 0),
|
||||
EndContainer(),
|
||||
|
||||
NWidget(NWID_SPACER), SetMinimalSize(0, 8),
|
||||
EndContainer(),
|
||||
};
|
||||
|
||||
static WindowDesc _helpwin_desc(
|
||||
WDP_CENTER, nullptr, 0, 0,
|
||||
WC_HELPWIN, WC_NONE,
|
||||
0,
|
||||
std::begin(_nested_helpwin_widgets), std::end(_nested_helpwin_widgets)
|
||||
);
|
||||
|
||||
void ShowHelpWindow()
|
||||
{
|
||||
AllocateWindowDescFront<HelpWindow>(&_helpwin_desc, 0);
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* 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.
|
||||
* 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.
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/** @file help_gui.h GUI to access manuals and related. */
|
||||
|
||||
#ifndef HELP_GUI_H
|
||||
#define HELP_GUI_H
|
||||
|
||||
void ShowHelpWindow();
|
||||
|
||||
#endif /* HELP_GUI_H */
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue