Merge commit '13.0-RC1~2'

This commit is contained in:
2023-02-25 04:49:57 +00:00
603 changed files with 26913 additions and 18390 deletions

View File

@@ -450,7 +450,7 @@ void RefTable::Resize(SQUnsignedInteger size)
SQUnsignedInteger oldnumofslots = _numofslots;
AllocNodes(size);
//rehash
SQUnsignedInteger nfound = 0;
[[maybe_unused]] SQUnsignedInteger nfound = 0;
for(SQUnsignedInteger n = 0; n < oldnumofslots; n++) {
if(type(t->obj) != OT_NULL) {
//add back;

View File

@@ -10,6 +10,7 @@ add_subdirectory(3rdparty)
add_subdirectory(ai)
add_subdirectory(blitter)
add_subdirectory(core)
add_subdirectory(fontcache)
add_subdirectory(game)
add_subdirectory(lang)
add_subdirectory(linkgraph)
@@ -29,18 +30,15 @@ add_files(
viewport_sprite_sorter_sse4.cpp
CONDITION SSE_FOUND
)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
set_compile_flags(
viewport_sprite_sorter_sse4.cpp
COMPILE_FLAGS -msse4.1)
endif()
add_files(
aircraft.h
aircraft_cmd.cpp
aircraft_cmd.h
aircraft_gui.cpp
airport.cpp
airport.h
airport_cmd.h
airport_gui.cpp
animated_tile.cpp
animated_tile_func.h
@@ -49,6 +47,7 @@ add_files(
autoreplace.cpp
autoreplace_base.h
autoreplace_cmd.cpp
autoreplace_cmd.h
autoreplace_func.h
autoreplace_gui.cpp
autoreplace_gui.h
@@ -86,12 +85,12 @@ add_files(
clear_cmd.cpp
clear_func.h
clear_map.h
cmd_helper.h
command.cpp
command_func.h
command_type.h
company_base.h
company_cmd.cpp
company_cmd.h
company_func.h
company_gui.cpp
company_gui.h
@@ -121,6 +120,7 @@ add_files(
depot.cpp
depot_base.h
depot_cmd.cpp
depot_cmd.h
depot_func.h
depot_gui.cpp
depot_map.h
@@ -129,11 +129,13 @@ add_files(
direction_type.h
disaster_vehicle.cpp
disaster_vehicle.h
dock_cmd.h
dock_gui.cpp
driver.cpp
driver.h
economy.cpp
economy_base.h
economy_cmd.h
economy_func.h
economy_type.h
effectvehicle.cpp
@@ -143,6 +145,7 @@ add_files(
elrail_func.h
engine.cpp
engine_base.h
engine_cmd.h
engine_func.h
engine_gui.cpp
engine_gui.h
@@ -157,7 +160,6 @@ add_files(
fios_gui.cpp
fontcache.cpp
fontcache.h
fontcache_internal.h
fontdetection.h
framerate_gui.cpp
framerate_type.h
@@ -176,6 +178,7 @@ add_files(
gfxinit.h
goal.cpp
goal_base.h
goal_cmd.h
goal_gui.cpp
goal_type.h
graph_gui.cpp
@@ -184,6 +187,7 @@ add_files(
ground_vehicle.hpp
group.h
group_cmd.cpp
group_cmd.h
group_gui.cpp
group_gui.h
group_type.h
@@ -200,6 +204,7 @@ add_files(
house_type.h
industry.h
industry_cmd.cpp
industry_cmd.h
industry_gui.cpp
industry_map.h
industry_type.h
@@ -210,8 +215,15 @@ add_files(
intro_gui.cpp
landscape.cpp
landscape.h
landscape_cmd.h
landscape_type.h
language.h
league_base.h
league_cmd.h
league_cmd.cpp
league_gui.h
league_gui.cpp
league_type.h
livery.h
main_gui.cpp
map.cpp
@@ -219,6 +231,7 @@ add_files(
map_type.h
misc.cpp
misc_cmd.cpp
misc_cmd.h
misc_gui.cpp
mixer.cpp
mixer.h
@@ -279,6 +292,7 @@ add_files(
newgrf_town.h
newgrf_townname.cpp
newgrf_townname.h
news_cmd.h
news_func.h
news_gui.cpp
news_gui.h
@@ -286,6 +300,7 @@ add_files(
object.h
object_base.h
object_cmd.cpp
object_cmd.h
object_gui.cpp
object_map.h
object_type.h
@@ -295,6 +310,7 @@ add_files(
order_backup.h
order_base.h
order_cmd.cpp
order_cmd.h
order_func.h
order_gui.cpp
order_type.h
@@ -307,6 +323,7 @@ add_files(
rail.cpp
rail.h
rail_cmd.cpp
rail_cmd.h
rail_gui.cpp
rail_gui.h
rail_map.h
@@ -329,6 +346,7 @@ add_files(
roadstop_base.h
roadveh.h
roadveh_cmd.cpp
roadveh_cmd.h
roadveh_gui.cpp
safeguards.h
screenshot_gui.cpp
@@ -336,6 +354,7 @@ add_files(
screenshot.cpp
screenshot.h
settings.cpp
settings_cmd.h
settings_func.h
settings_gui.cpp
settings_gui.h
@@ -345,6 +364,7 @@ add_files(
settings_type.h
ship.h
ship_cmd.cpp
ship_cmd.h
ship_gui.cpp
signal.cpp
signal_func.h
@@ -352,6 +372,7 @@ add_files(
signs.cpp
signs_base.h
signs_cmd.cpp
signs_cmd.h
signs_func.h
signs_gui.cpp
signs_type.h
@@ -370,6 +391,7 @@ add_files(
station.cpp
station_base.h
station_cmd.cpp
station_cmd.h
station_func.h
station_gui.cpp
station_gui.h
@@ -381,6 +403,7 @@ add_files(
stdafx.h
story.cpp
story_base.h
story_cmd.h
story_gui.cpp
story_type.h
strgen/strgen.h
@@ -395,11 +418,13 @@ add_files(
strings_type.h
subsidy.cpp
subsidy_base.h
subsidy_cmd.h
subsidy_func.h
subsidy_gui.cpp
subsidy_type.h
tar_type.h
terraform_cmd.cpp
terraform_cmd.h
terraform_gui.cpp
terraform_gui.h
textbuf.cpp
@@ -424,11 +449,13 @@ add_files(
tilematrix_type.hpp
timetable.h
timetable_cmd.cpp
timetable_cmd.h
timetable_gui.cpp
toolbar_gui.cpp
toolbar_gui.h
town.h
town_cmd.cpp
town_cmd.h
town_gui.cpp
town_kdtree.h
town_map.h
@@ -440,24 +467,28 @@ add_files(
track_type.h
train.h
train_cmd.cpp
train_cmd.h
train_gui.cpp
transparency.h
transparency_gui.cpp
transparency_gui.h
transport_type.h
tree_cmd.cpp
tree_cmd.h
tree_gui.cpp
tree_map.h
tunnel_map.cpp
tunnel_map.h
tunnelbridge.h
tunnelbridge_cmd.cpp
tunnelbridge_cmd.h
tunnelbridge_map.h
tutorial_gui.cpp
tutorial_gui.h
vehicle.cpp
vehicle_base.h
vehicle_cmd.cpp
vehicle_cmd.h
vehicle_func.h
vehicle_gui.cpp
vehicle_gui.h
@@ -466,6 +497,7 @@ add_files(
vehiclelist.cpp
vehiclelist.h
viewport.cpp
viewport_cmd.h
viewport_func.h
viewport_gui.cpp
viewport_kdtree.h
@@ -476,10 +508,12 @@ add_files(
walltime_func.h
water.h
water_cmd.cpp
water_cmd.h
water_map.h
waypoint.cpp
waypoint_base.h
waypoint_cmd.cpp
waypoint_cmd.h
waypoint_func.h
waypoint_gui.cpp
widget.cpp

View File

@@ -128,20 +128,15 @@ public:
*/
static void Save(CompanyID company);
/**
* Load data for an AI from a savegame.
*/
static void Load(CompanyID company, int version);
/**
* Get the number of days before the next AI should start.
*/
static int GetStartNextTime();
/** Wrapper function for AIScanner::GetAIConsoleList */
static char *GetConsoleList(char *p, const char *last, bool newest_only = false);
static std::string GetConsoleList(bool newest_only = false);
/** Wrapper function for AIScanner::GetAIConsoleLibraryList */
static char *GetConsoleLibraryList(char *p, const char *last);
static std::string GetConsoleLibraryList();
/** Wrapper function for AIScanner::GetAIInfoList */
static const ScriptInfoList *GetInfoList();
/** Wrapper function for AIScanner::GetUniqueAIInfoList */

View File

@@ -57,6 +57,8 @@
assert(c->ai_instance == nullptr);
c->ai_instance = new AIInstance();
c->ai_instance->Initialize(info);
c->ai_instance->LoadOnStack(config->GetToLoadData());
config->SetToLoadData(nullptr);
cur_company.Restore();
@@ -289,21 +291,6 @@
}
}
/* static */ void AI::Load(CompanyID company, int version)
{
if (!_networking || _network_server) {
Company *c = Company::GetIfValid(company);
assert(c != nullptr && c->ai_instance != nullptr);
Backup<CompanyID> cur_company(_current_company, company, FILE_LINE);
c->ai_instance->Load(version);
cur_company.Restore();
} else {
/* Read, but ignore, the load data */
AIInstance::LoadEmpty();
}
}
/* static */ int AI::GetStartNextTime()
{
/* Find the first company which doesn't exist yet */
@@ -315,14 +302,14 @@
return DAYS_IN_YEAR;
}
/* static */ char *AI::GetConsoleList(char *p, const char *last, bool newest_only)
/* static */ std::string AI::GetConsoleList(bool newest_only)
{
return AI::scanner_info->GetConsoleList(p, last, newest_only);
return AI::scanner_info->GetConsoleList(newest_only);
}
/* static */ char *AI::GetConsoleLibraryList(char *p, const char *last)
/* static */ std::string AI::GetConsoleLibraryList()
{
return AI::scanner_library->GetConsoleList(p, last, true);
return AI::scanner_library->GetConsoleList(true);
}
/* static */ const ScriptInfoList *AI::GetInfoList()

View File

@@ -28,6 +28,8 @@
#include "../hotkeys.h"
#include "../core/geometry_func.hpp"
#include "../guitimer_func.h"
#include "../company_cmd.h"
#include "../misc_cmd.h"
#include "ai.hpp"
#include "ai_gui.hpp"
@@ -110,7 +112,7 @@ struct AIListWindow : public Window {
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
{
if (widget == WID_AIL_LIST) {
this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
this->line_height = FONT_HEIGHT_NORMAL + padding.height;
this->line_height = GetMinButtonSize(this->line_height);
resize->width = 1;
@@ -124,46 +126,45 @@ struct AIListWindow : public Window {
switch (widget) {
case WID_AIL_LIST: {
/* Draw a list of all available AIs. */
int y = this->GetWidget<NWidgetBase>(WID_AIL_LIST)->pos_y;
y = Center(y, this->line_height);
Rect tr = r.Shrink(WidgetDimensions::scaled.matrix);
/* First AI in the list is hardcoded to random */
if (this->vscroll->IsVisible(0)) {
DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_LEFT, y + WD_MATRIX_TOP, this->slot == OWNER_DEITY ? STR_AI_CONFIG_NONE : STR_AI_CONFIG_RANDOM_AI, this->selected == -1 ? TC_WHITE : TC_ORANGE);
y += this->line_height;
DrawString(tr, this->slot == OWNER_DEITY ? STR_AI_CONFIG_NONE : STR_AI_CONFIG_RANDOM_AI, this->selected == -1 ? TC_WHITE : TC_ORANGE);
tr.top += this->line_height;
}
int i = 0;
for (const auto &item : *this->info_list) {
i++;
if (this->vscroll->IsVisible(i)) {
DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, y + WD_MATRIX_TOP, item.second->GetName(), (this->selected == i - 1) ? TC_WHITE : TC_ORANGE);
y += this->line_height;
DrawString(tr, item.second->GetName(), (this->selected == i - 1) ? TC_WHITE : TC_ORANGE);
tr.top += this->line_height;
}
}
break;
}
case WID_AIL_INFO_BG: {
AIInfo *selected_info = nullptr;
ScriptInfo *selected_info = nullptr;
int i = 0;
for (const auto &item : *this->info_list) {
i++;
if (this->selected == i - 1) selected_info = static_cast<AIInfo *>(item.second);
if (this->selected == i - 1) selected_info = static_cast<ScriptInfo *>(item.second);
}
/* Some info about the currently selected AI. */
if (selected_info != nullptr) {
int y = r.top + WD_FRAMERECT_TOP;
Rect tr = r.Shrink(WidgetDimensions::scaled.frametext, WidgetDimensions::scaled.framerect);
SetDParamStr(0, selected_info->GetAuthor());
DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_AI_LIST_AUTHOR);
y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
DrawString(tr, STR_AI_LIST_AUTHOR);
tr.top += FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal;
SetDParam(0, selected_info->GetVersion());
DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_AI_LIST_VERSION);
y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
DrawString(tr, STR_AI_LIST_VERSION);
tr.top += FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal;
if (selected_info->GetURL() != nullptr) {
SetDParamStr(0, selected_info->GetURL());
DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_AI_LIST_URL);
y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
DrawString(tr, STR_AI_LIST_URL);
tr.top += FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal;
}
SetDParamStr(0, selected_info->GetDescription());
DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, r.bottom - WD_FRAMERECT_BOTTOM, STR_JUST_RAW_STRING, TC_WHITE);
DrawStringMultiLine(tr, STR_JUST_RAW_STRING, TC_WHITE);
}
break;
}
@@ -182,7 +183,7 @@ struct AIListWindow : public Window {
for (int i = 0; i < this->selected; i++) it++;
GetConfig(slot)->Change((*it).second->GetName(), (*it).second->GetVersion());
}
InvalidateWindowData(WC_GAME_OPTIONS, WN_GAME_OPTIONS_AI);
InvalidateWindowData(WC_GAME_OPTIONS, slot == OWNER_DEITY ? WN_GAME_OPTIONS_GS : WN_GAME_OPTIONS_AI);
InvalidateWindowClassesData(WC_AI_SETTINGS);
CloseWindowByClass(WC_QUERY_STRING);
InvalidateWindowClassesData(WC_TEXTFILE);
@@ -253,7 +254,7 @@ static const NWidgetPart _nested_ai_list_widgets[] = {
NWidget(WWT_MATRIX, COLOUR_MAUVE, WID_AIL_LIST), SetMinimalSize(188, 112), SetFill(1, 1), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_AI_LIST_TOOLTIP), SetScrollbar(WID_AIL_SCROLLBAR),
NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_AIL_SCROLLBAR),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_MAUVE, WID_AIL_INFO_BG), SetMinimalTextLines(8, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM), SetResize(1, 0),
NWidget(WWT_PANEL, COLOUR_MAUVE, WID_AIL_INFO_BG), SetMinimalTextLines(8, WidgetDimensions::unscaled.framerect.Vertical() + WidgetDimensions::unscaled.vsep_normal * 3), SetResize(1, 0),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
@@ -276,7 +277,7 @@ static WindowDesc _ai_list_desc(
* Open the AI list window to chose an AI for the given company slot.
* @param slot The slot to change the AI of.
*/
static void ShowAIListWindow(CompanyID slot)
void ShowAIListWindow(CompanyID slot)
{
CloseWindowByClass(WC_AI_LIST);
new AIListWindow(&_ai_list_desc, slot);
@@ -296,7 +297,7 @@ struct AISettingsWindow : public Window {
int clicked_row; ///< The clicked row of settings.
int line_height; ///< Height of a row in the matrix widget.
Scrollbar *vscroll; ///< Cache of the vertical scrollbar.
typedef std::vector<const ScriptConfigItem *> VisibleSettingsList;
typedef std::vector<const ScriptConfigItem *> VisibleSettingsList; ///< typdef for a vector of script settings
VisibleSettingsList visible_settings; ///< List of visible AI settings
/**
@@ -320,15 +321,6 @@ struct AISettingsWindow : public Window {
this->RebuildVisibleSettings();
}
void SetStringParameters(int widget) const override
{
switch (widget) {
case WID_AIS_CAPTION:
SetDParam(0, (this->slot == OWNER_DEITY) ? STR_AI_SETTINGS_CAPTION_GAMESCRIPT : STR_AI_SETTINGS_CAPTION_AI);
break;
}
}
/**
* Rebuilds the list of visible settings. AI settings with the flag
* AICONFIG_AI_DEVELOPER set will only be visible if the game setting
@@ -351,7 +343,7 @@ struct AISettingsWindow : public Window {
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
{
if (widget == WID_AIS_BACKGROUND) {
this->line_height = std::max(SETTING_BUTTON_HEIGHT, FONT_HEIGHT_NORMAL) + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
this->line_height = std::max(SETTING_BUTTON_HEIGHT, FONT_HEIGHT_NORMAL) + padding.height;
this->line_height = GetMinButtonSize(this->line_height);
resize->width = 1;
@@ -369,11 +361,10 @@ struct AISettingsWindow : public Window {
int i = 0;
for (; !this->vscroll->IsVisible(i); i++) it++;
Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
bool rtl = _current_text_dir == TD_RTL;
uint buttons_left = rtl ? r.right - SETTING_BUTTON_WIDTH - 3 : r.left + 4;
uint text_left = r.left + (rtl ? WD_FRAMERECT_LEFT : SETTING_BUTTON_WIDTH + 8);
uint text_right = r.right - (rtl ? SETTING_BUTTON_WIDTH + 8 : WD_FRAMERECT_RIGHT);
Rect br = ir.WithWidth(SETTING_BUTTON_WIDTH, rtl);
Rect tr = ir.Indent(SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide, rtl);
int y = r.top;
int button_y_offset = (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
int text_y_offset = (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
@@ -401,13 +392,13 @@ struct AISettingsWindow : public Window {
}
if ((config_item.flags & SCRIPTCONFIG_BOOLEAN) != 0) {
DrawBoolButton(buttons_left, y + button_y_offset, current_value != 0, editable);
DrawBoolButton(br.left, y + button_y_offset, current_value != 0, editable);
SetDParam(idx++, current_value == 0 ? STR_CONFIG_SETTING_OFF : STR_CONFIG_SETTING_ON);
} else {
if (config_item.complete_labels) {
DrawDropDownButton(buttons_left, y + button_y_offset, COLOUR_YELLOW, this->clicked_row == i && clicked_dropdown, editable);
DrawDropDownButton(br.left, y + button_y_offset, COLOUR_YELLOW, this->clicked_row == i && clicked_dropdown, editable);
} else {
DrawArrowButtons(buttons_left, y + button_y_offset, COLOUR_YELLOW, (this->clicked_button == i) ? 1 + (this->clicked_increase != rtl) : 0, editable && current_value > config_item.min_value, editable && current_value < config_item.max_value);
DrawArrowButtons(br.left, y + button_y_offset, COLOUR_YELLOW, (this->clicked_button == i) ? 1 + (this->clicked_increase != rtl) : 0, editable && current_value > config_item.min_value, editable && current_value < config_item.max_value);
}
if (config_item.labels != nullptr && config_item.labels->Contains(current_value)) {
SetDParam(idx++, STR_JUST_RAW_STRING);
@@ -418,7 +409,7 @@ struct AISettingsWindow : public Window {
}
}
DrawString(text_left, text_right, y + text_y_offset, str, colour);
DrawString(tr.left, tr.right, y + text_y_offset, str, colour);
y += this->line_height;
}
}
@@ -436,8 +427,8 @@ struct AISettingsWindow : public Window {
{
switch (widget) {
case WID_AIS_BACKGROUND: {
const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_AIS_BACKGROUND);
int num = (pt.y - wid->pos_y) / this->line_height + this->vscroll->GetPosition();
Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.matrix, RectPadding::zero);
int num = (pt.y - r.top) / this->line_height + this->vscroll->GetPosition();
if (num >= (int)this->visible_settings.size()) break;
VisibleSettingsList::const_iterator it = this->visible_settings.begin();
@@ -454,9 +445,8 @@ struct AISettingsWindow : public Window {
bool bool_item = (config_item.flags & SCRIPTCONFIG_BOOLEAN) != 0;
int x = pt.x - wid->pos_x;
if (_current_text_dir == TD_RTL) x = wid->current_x - 1 - x;
x -= 4;
int x = pt.x - r.left;
if (_current_text_dir == TD_RTL) x = r.Width() - 1 - x;
/* One of the arrows is clicked (or green/red rect in case of bool value) */
int old_val = this->ai_config->GetSetting(config_item.name);
@@ -467,8 +457,7 @@ struct AISettingsWindow : public Window {
this->clicked_dropdown = false;
this->closing_dropdown = false;
} else {
const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_AIS_BACKGROUND);
int rel_y = (pt.y - (int)wid->pos_y) % this->line_height;
int rel_y = (pt.y - r.top) % this->line_height;
Rect wi_rect;
wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);
@@ -476,7 +465,7 @@ struct AISettingsWindow : public Window {
wi_rect.top = pt.y - rel_y + (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
/* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
/* If the mouse is still held but dragged outside of the dropdown list, keep the dropdown open */
if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
this->clicked_dropdown = true;
this->closing_dropdown = false;
@@ -533,24 +522,15 @@ struct AISettingsWindow : public Window {
void OnQueryTextFinished(char *str) override
{
if (StrEmpty(str)) return;
VisibleSettingsList::const_iterator it = this->visible_settings.begin();
for (int i = 0; i < this->clicked_row; i++) it++;
const ScriptConfigItem config_item = **it;
if (_game_mode == GM_NORMAL && ((this->slot == OWNER_DEITY) || Company::IsValidID(this->slot)) && (config_item.flags & SCRIPTCONFIG_INGAME) == 0) return;
int32 value = atoi(str);
this->ai_config->SetSetting(config_item.name, value);
this->SetDirty();
SetValue(value);
}
void OnDropdownSelect(int widget, int index) override
{
assert(this->clicked_dropdown);
VisibleSettingsList::const_iterator it = this->visible_settings.begin();
for (int i = 0; i < this->clicked_row; i++) it++;
const ScriptConfigItem config_item = **it;
if (_game_mode == GM_NORMAL && ((this->slot == OWNER_DEITY) || Company::IsValidID(this->slot)) && (config_item.flags & SCRIPTCONFIG_INGAME) == 0) return;
this->ai_config->SetSetting(config_item.name, index);
this->SetDirty();
SetValue(index);
}
void OnDropdownClose(Point pt, int widget, int index, bool instant_close) override
@@ -592,7 +572,21 @@ struct AISettingsWindow : public Window {
private:
bool IsEditableItem(const ScriptConfigItem &config_item) const
{
return _game_mode == GM_MENU || ((this->slot != OWNER_DEITY) && !Company::IsValidID(this->slot)) || (config_item.flags & SCRIPTCONFIG_INGAME) != 0;
return _game_mode == GM_MENU
|| _game_mode == GM_EDITOR
|| ((this->slot != OWNER_DEITY) && !Company::IsValidID(this->slot))
|| (config_item.flags & SCRIPTCONFIG_INGAME) != 0
|| _settings_client.gui.ai_developer_tools;
}
void SetValue(int value)
{
VisibleSettingsList::const_iterator it = this->visible_settings.begin();
for (int i = 0; i < this->clicked_row; i++) it++;
const ScriptConfigItem config_item = **it;
if (_game_mode == GM_NORMAL && ((this->slot == OWNER_DEITY) || Company::IsValidID(this->slot)) && (config_item.flags & SCRIPTCONFIG_INGAME) == 0) return;
this->ai_config->SetSetting(config_item.name, value);
this->SetDirty();
}
};
@@ -600,7 +594,7 @@ private:
static const NWidgetPart _nested_ai_settings_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
NWidget(WWT_CAPTION, COLOUR_MAUVE, WID_AIS_CAPTION), SetDataTip(STR_AI_SETTINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
NWidget(WWT_CAPTION, COLOUR_MAUVE, WID_AIS_CAPTION), SetDataTip(STR_AI_SETTINGS_CAPTION_AI, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
EndContainer(),
NWidget(NWID_HORIZONTAL),
@@ -680,7 +674,7 @@ void ShowScriptTextfileWindow(TextfileType file_type, CompanyID slot)
static const NWidgetPart _nested_ai_config_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_AI_CONFIG_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_AI_CONFIG_CAPTION_AI, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_MAUVE, WID_AIC_BACKGROUND),
NWidget(NWID_HORIZONTAL), SetPIP(7, 7, 7),
@@ -693,21 +687,18 @@ static const NWidgetPart _nested_ai_config_widgets[] = {
NWidget(NWID_VERTICAL), SetPIP(5, 5, 5),
NWidget(NWID_SPACER), SetMinimalSize(0, 5),
NWidget(NWID_HORIZONTAL), SetPIP(7, 0, 7),
NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_DECREASE), SetFill(0, 1), SetDataTip(AWV_DECREASE, STR_NULL),
NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_INCREASE), SetFill(0, 1), SetDataTip(AWV_INCREASE, STR_NULL),
NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_DECREASE), SetDataTip(AWV_DECREASE, STR_NULL),
NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_INCREASE), SetDataTip(AWV_INCREASE, STR_NULL),
NWidget(NWID_SPACER), SetMinimalSize(6, 0),
NWidget(WWT_TEXT, COLOUR_MAUVE, WID_AIC_NUMBER), SetDataTip(STR_DIFFICULTY_LEVEL_SETTING_MAXIMUM_NO_COMPETITORS, STR_NULL), SetFill(1, 0), SetPadding(1, 0, 0, 0),
EndContainer(),
NWidget(WWT_FRAME, COLOUR_MAUVE), SetDataTip(STR_AI_CONFIG_GAMESCRIPT, STR_NULL), SetPadding(0, 5, 4, 5),
NWidget(WWT_MATRIX, COLOUR_MAUVE, WID_AIC_GAMELIST), SetMinimalSize(288, 14), SetFill(1, 0), SetMatrixDataTip(1, 1, STR_AI_CONFIG_GAMELIST_TOOLTIP),
NWidget(WWT_TEXT, COLOUR_MAUVE, WID_AIC_NUMBER), SetDataTip(STR_AI_CONFIG_MAX_COMPETITORS, STR_NULL), SetFill(1, 0), SetPadding(1, 0, 0, 0),
EndContainer(),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CHANGE), SetFill(1, 0), SetMinimalSize(93, 0), SetDataTip(STR_AI_CONFIG_CHANGE, STR_AI_CONFIG_CHANGE_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CHANGE), SetFill(1, 0), SetMinimalSize(93, 0), SetDataTip(STR_AI_CONFIG_CHANGE_AI, STR_AI_CONFIG_CHANGE_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CONFIGURE), SetFill(1, 0), SetMinimalSize(93, 0), SetDataTip(STR_AI_CONFIG_CONFIGURE, STR_AI_CONFIG_CONFIGURE_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CLOSE), SetFill(1, 0), SetMinimalSize(93, 0), SetDataTip(STR_AI_SETTINGS_CLOSE, STR_NULL),
NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_TEXTFILE + TFT_README), SetFill(1, 0), SetMinimalSize(93, 0), SetDataTip(STR_AI_SETTINGS_CLOSE, STR_NULL),
EndContainer(),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_TEXTFILE + TFT_README), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_NULL),
NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CLOSE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_NULL),
NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_TEXTFILE + TFT_CHANGELOG), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_NULL),
NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_TEXTFILE + TFT_LICENSE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_NULL),
EndContainer(),
@@ -757,54 +748,22 @@ struct AIConfigWindow : public Window {
case WID_AIC_NUMBER:
SetDParam(0, GetGameSettings().difficulty.max_no_competitors);
break;
case WID_AIC_CHANGE:
switch (selected_slot) {
case OWNER_DEITY:
SetDParam(0, STR_AI_CONFIG_CHANGE_GAMESCRIPT);
break;
case INVALID_COMPANY:
SetDParam(0, STR_AI_CONFIG_CHANGE_NONE);
break;
default:
SetDParam(0, STR_AI_CONFIG_CHANGE_AI);
break;
}
break;
}
}
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
{
switch (widget) {
case WID_AIC_GAMELIST:
this->line_height = GetMinButtonSize(FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM);
size->height = this->line_height;
case WID_AIC_DECREASE:
case WID_AIC_INCREASE:
*size = maxdim(*size, NWidgetScrollbar::GetHorizontalDimension());
break;
case WID_AIC_LIST:
this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
this->line_height = GetMinButtonSize(this->line_height);
this->line_height = FONT_HEIGHT_NORMAL + padding.height;
resize->height = this->line_height;
size->height = 8 * this->line_height;
break;
case WID_AIC_CHANGE: {
SetDParam(0, STR_AI_CONFIG_CHANGE_GAMESCRIPT);
Dimension dim = GetStringBoundingBox(STR_AI_CONFIG_CHANGE);
SetDParam(0, STR_AI_CONFIG_CHANGE_NONE);
dim = maxdim(dim, GetStringBoundingBox(STR_AI_CONFIG_CHANGE));
SetDParam(0, STR_AI_CONFIG_CHANGE_AI);
dim = maxdim(dim, GetStringBoundingBox(STR_AI_CONFIG_CHANGE));
dim.width += padding.width;
dim.height += padding.height;
*size = maxdim(*size, dim);
break;
}
}
}
@@ -815,8 +774,6 @@ struct AIConfigWindow : public Window {
*/
static bool IsEditable(CompanyID slot)
{
if (slot == OWNER_DEITY) return _game_mode != GM_NORMAL || Game::GetInstance() != nullptr;
if (_game_mode != GM_NORMAL) {
return slot > 0 && slot <= GetGameSettings().difficulty.max_no_competitors;
}
@@ -832,22 +789,8 @@ struct AIConfigWindow : public Window {
void DrawWidget(const Rect &r, int widget) const override
{
switch (widget) {
case WID_AIC_GAMELIST: {
StringID text = STR_AI_CONFIG_NONE;
if (GameConfig::GetConfig()->GetInfo() != nullptr) {
SetDParamStr(0, GameConfig::GetConfig()->GetInfo()->GetName());
text = STR_JUST_RAW_STRING;
}
DrawString(r.left + 10, r.right - 10, Center(r.top, this->line_height), text,
(this->selected_slot == OWNER_DEITY) ? TC_WHITE : (IsEditable(OWNER_DEITY) ? TC_ORANGE : TC_SILVER));
break;
}
case WID_AIC_LIST: {
int y = Center(r.top, this->line_height);
Rect tr = r.Shrink(WidgetDimensions::scaled.matrix);
for (int i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < MAX_COMPANIES; i++) {
StringID text;
@@ -859,9 +802,9 @@ struct AIConfigWindow : public Window {
} else {
text = STR_AI_CONFIG_RANDOM_AI;
}
DrawString(r.left + 10, r.right - 10, y, text,
DrawString(tr, text,
(this->selected_slot == i) ? TC_WHITE : (IsEditable((CompanyID)i) ? TC_ORANGE : TC_SILVER));
y += this->line_height;
tr.top += this->line_height;
}
break;
}
@@ -890,13 +833,6 @@ struct AIConfigWindow : public Window {
break;
}
case WID_AIC_GAMELIST: {
this->selected_slot = OWNER_DEITY;
this->InvalidateData();
if (click_count > 1 && this->selected_slot != INVALID_COMPANY && _game_mode != GM_NORMAL) ShowAIListWindow((CompanyID)this->selected_slot);
break;
}
case WID_AIC_LIST: { // Select a slot
this->selected_slot = (CompanyID)this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget);
this->InvalidateData();
@@ -938,7 +874,7 @@ struct AIConfigWindow : public Window {
if (!_network_available) {
ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, WL_ERROR);
} else {
ShowNetworkContentListWindow(nullptr, CONTENT_TYPE_AI, CONTENT_TYPE_GAME);
ShowNetworkContentListWindow(nullptr, CONTENT_TYPE_AI);
}
break;
}
@@ -959,10 +895,10 @@ struct AIConfigWindow : public Window {
this->SetWidgetDisabledState(WID_AIC_DECREASE, GetGameSettings().difficulty.max_no_competitors == 0);
this->SetWidgetDisabledState(WID_AIC_INCREASE, GetGameSettings().difficulty.max_no_competitors == MAX_COMPANIES - 1);
this->SetWidgetDisabledState(WID_AIC_CHANGE, (this->selected_slot == OWNER_DEITY && _game_mode == GM_NORMAL) || this->selected_slot == INVALID_COMPANY);
this->SetWidgetDisabledState(WID_AIC_CHANGE, this->selected_slot == INVALID_COMPANY);
this->SetWidgetDisabledState(WID_AIC_CONFIGURE, this->selected_slot == INVALID_COMPANY || GetConfig(this->selected_slot)->GetConfigList()->size() == 0);
this->SetWidgetDisabledState(WID_AIC_MOVE_UP, this->selected_slot == OWNER_DEITY || this->selected_slot == INVALID_COMPANY || !IsEditable((CompanyID)(this->selected_slot - 1)));
this->SetWidgetDisabledState(WID_AIC_MOVE_DOWN, this->selected_slot == OWNER_DEITY || this->selected_slot == INVALID_COMPANY || !IsEditable((CompanyID)(this->selected_slot + 1)));
this->SetWidgetDisabledState(WID_AIC_MOVE_UP, this->selected_slot == INVALID_COMPANY || !IsEditable((CompanyID)(this->selected_slot - 1)));
this->SetWidgetDisabledState(WID_AIC_MOVE_DOWN, this->selected_slot == INVALID_COMPANY || !IsEditable((CompanyID)(this->selected_slot + 1)));
for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
this->SetWidgetDisabledState(WID_AIC_TEXTFILE + tft, this->selected_slot == INVALID_COMPANY || (GetConfig(this->selected_slot)->GetTextfile(tft, this->selected_slot) == nullptr));
@@ -1002,9 +938,6 @@ static bool SetScriptButtonColour(NWidgetCore &button, bool dead, bool paused)
* Window with everything an AI prints via ScriptLog.
*/
struct AIDebugWindow : public Window {
static const int top_offset; ///< Offset of the text at the top of the WID_AID_LOG_PANEL.
static const int bottom_offset; ///< Offset of the text at the bottom of the WID_AID_LOG_PANEL.
static const uint MAX_BREAK_STR_STRING_LENGTH = 256; ///< Maximum length of the break string.
static CompanyID ai_debug_company; ///< The AI that is (was last) being debugged.
@@ -1108,8 +1041,8 @@ struct AIDebugWindow : public Window {
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
{
if (widget == WID_AID_LOG_PANEL) {
resize->height = GetMinButtonSize(FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL);
size->height = 14 * resize->height + this->top_offset + this->bottom_offset;
resize->height = FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal;
size->height = 14 * resize->height + WidgetDimensions::scaled.framerect.Vertical();
}
}
@@ -1124,8 +1057,6 @@ struct AIDebugWindow : public Window {
bool dirty = false;
Dimension d = GetSpriteSize(SPR_COMPANY_ICON);
uint offset_y = Center(0, GetMinButtonSize(d.height + WD_MATRIX_TOP + WD_MATRIX_BOTTOM + 1), d.height);
/* Paint the company icons */
for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
NWidgetCore *button = this->GetWidget<NWidgetCore>(i + WID_AID_COMPANY_BUTTON_START);
@@ -1146,7 +1077,7 @@ struct AIDebugWindow : public Window {
if (!valid) continue;
byte offset = (i == ai_debug_company) ? 1 : 0;
DrawCompanyIcon(i, Center(button->pos_x + offset, button->current_x, d.width), button->pos_y + offset + offset_y);
DrawCompanyIcon(i, button->pos_x + button->current_x / 2 - 7 + offset, this->GetWidget<NWidgetBase>(WID_AID_COMPANY_BUTTON_START + i)->pos_y + 2 + offset);
}
/* Set button colour for Game Script. */
@@ -1225,7 +1156,8 @@ struct AIDebugWindow : public Window {
ScriptLog::LogData *log = this->GetLogPointer();
if (log == nullptr) return;
int y = Center(this->top_offset, this->resize.step_height);
Rect br = r.Shrink(WidgetDimensions::scaled.bevel);
Rect tr = r.Shrink(WidgetDimensions::scaled.framerect);
for (int i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < log->used; i++) {
int pos = (i + log->pos + 1 - log->used + log->count) % log->count;
if (log->lines[pos] == nullptr) break;
@@ -1242,12 +1174,12 @@ struct AIDebugWindow : public Window {
/* Check if the current line should be highlighted */
if (pos == this->highlight_row) {
GfxFillRect(r.left + 1, r.top + y, r.right - 1, r.top + y + this->resize.step_height - WD_PAR_VSEP_NORMAL, PC_BLACK);
GfxFillRect(br.left, tr.top, br.right, tr.top + this->resize.step_height - 1, PC_BLACK);
if (colour == TC_BLACK) colour = TC_WHITE; // Make black text readable by inverting it to white.
}
DrawString(r.left + 7, r.right - 7, r.top + y, log->lines[pos], colour, SA_LEFT | SA_FORCE);
y += this->resize.step_height;
DrawString(tr, log->lines[pos], colour, SA_LEFT | SA_FORCE);
tr.top += this->resize.step_height;
}
break;
}
@@ -1293,8 +1225,8 @@ struct AIDebugWindow : public Window {
case WID_AID_RELOAD_TOGGLE:
if (ai_debug_company == OWNER_DEITY) break;
/* First kill the company of the AI, then start a new one. This should start the current AI again */
DoCommandP(0, CCA_DELETE | ai_debug_company << 16 | CRR_MANUAL << 24, 0, CMD_COMPANY_CTRL);
DoCommandP(0, CCA_NEW_AI | ai_debug_company << 16, 0, CMD_COMPANY_CTRL);
Command<CMD_COMPANY_CTRL>::Post(CCA_DELETE, ai_debug_company, CRR_MANUAL, INVALID_CLIENT_ID);
Command<CMD_COMPANY_CTRL>::Post(CCA_NEW_AI, ai_debug_company, CRR_NONE, INVALID_CLIENT_ID);
break;
case WID_AID_SETTINGS:
@@ -1333,7 +1265,7 @@ struct AIDebugWindow : public Window {
}
if (all_unpaused) {
/* All scripts have been unpaused => unpause the game. */
DoCommandP(0, PM_PAUSED_NORMAL, 0, CMD_PAUSE);
Command<CMD_PAUSE>::Post(PM_PAUSED_NORMAL, false);
}
}
}
@@ -1382,7 +1314,7 @@ struct AIDebugWindow : public Window {
/* Pause the game. */
if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED) {
DoCommandP(0, PM_PAUSED_NORMAL, 1, CMD_PAUSE);
Command<CMD_PAUSE>::Post(PM_PAUSED_NORMAL, true);
}
/* Highlight row that matched */
@@ -1419,14 +1351,12 @@ struct AIDebugWindow : public Window {
void OnResize() override
{
this->vscroll->SetCapacityFromWidget(this, WID_AID_LOG_PANEL);
this->vscroll->SetCapacityFromWidget(this, WID_AID_LOG_PANEL, WidgetDimensions::scaled.framerect.Vertical());
}
static HotkeyList hotkeys;
};
const int AIDebugWindow::top_offset = WD_FRAMERECT_TOP + 2;
const int AIDebugWindow::bottom_offset = WD_FRAMERECT_BOTTOM;
CompanyID AIDebugWindow::ai_debug_company = INVALID_COMPANY;
char AIDebugWindow::break_string[MAX_BREAK_STR_STRING_LENGTH] = "";
bool AIDebugWindow::break_check_enabled = true;

View File

@@ -12,8 +12,10 @@
#include "../company_type.h"
void ShowAIListWindow(CompanyID slot);
Window* ShowAIDebugWindow(CompanyID show_company = INVALID_COMPANY);
void ShowAIConfigWindow();
void ShowScriptTextfileWindow(TextfileType file_type, CompanyID slot);
void ShowAIDebugWindowIfAIError();
void InitializeAIGui();

View File

@@ -25,7 +25,7 @@
*/
static bool CheckAPIVersion(const char *api_version)
{
static const std::set<std::string> versions = { "0.7", "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "1.10", "1.11", "12" };
static const std::set<std::string> versions = { "0.7", "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "1.10", "1.11", "12", "13" };
return versions.find(api_version) != versions.end();
}

View File

@@ -18,6 +18,7 @@
#include "ai.hpp"
#include "../script/script_storage.hpp"
#include "../script/script_cmd.h"
#include "ai_info.hpp"
#include "ai_instance.hpp"
@@ -60,6 +61,9 @@ void AIInstance::Died()
{
ScriptInstance::Died();
/* Intro is not supposed to use AI, but it may have 'dummy' AI which instant dies. */
if (_game_mode == GM_MENU) return;
ShowAIDebugWindow(_current_company);
const AIInfo *info = AIConfig::GetConfig(_current_company, AIConfig::SSS_FORCE_GAME)->GetInfo();
@@ -92,13 +96,13 @@ ScriptInfo *AIInstance::FindLibrary(const char *library, int version)
/**
* DoCommand callback function for all commands executed by AIs.
* @param cmd cmd as given to DoCommandPInternal.
* @param result The result of the command.
* @param tile The tile on which the command was executed.
* @param p1 p1 as given to DoCommandPInternal.
* @param p2 p2 as given to DoCommandPInternal.
* @param cmd cmd as given to DoCommandPInternal.
* @param data Command data as given to Command<>::Post.
* @param result_data Additional returned data from the command.
*/
void CcAI(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
void CcAI(Commands cmd, const CommandCost &result, TileIndex tile, const CommandDataBuffer &data, CommandDataBuffer result_data)
{
/*
* The company might not exist anymore. Check for this.
@@ -109,12 +113,12 @@ void CcAI(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint3
const Company *c = Company::GetIfValid(_current_company);
if (c == nullptr || c->ai_instance == nullptr) return;
if (c->ai_instance->DoCommandCallback(result, tile, p1, p2, cmd)) {
if (c->ai_instance->DoCommandCallback(result, tile, data, std::move(result_data), cmd)) {
c->ai_instance->Continue();
}
}
CommandCallback *AIInstance::GetDoCommandCallback()
CommandCallbackData *AIInstance::GetDoCommandCallback()
{
return &CcAI;
}

View File

@@ -29,7 +29,7 @@ public:
private:
void RegisterAPI() override;
void Died() override;
CommandCallback *GetDoCommandCallback() override;
CommandCallbackData *GetDoCommandCallback() override;
void LoadDummyScript() override;
};

View File

@@ -10,6 +10,7 @@
#include "../stdafx.h"
#include "../debug.h"
#include "../network/network.h"
#include "../openttd.h"
#include "../core/random_func.hpp"
#include "../script/squirrel_class.hpp"
@@ -59,6 +60,11 @@ void AIScannerInfo::RegisterAPI(class Squirrel *engine)
AIInfo *AIScannerInfo::SelectRandomAI() const
{
if (_game_mode == GM_MENU) {
Debug(script, 0, "The intro game should not use AI, loading 'dummy' AI.");
return this->info_dummy;
}
uint num_random_ais = 0;
for (const auto &item : info_single_list) {
AIInfo *i = static_cast<AIInfo *>(item.second);

View File

@@ -91,7 +91,7 @@ struct Aircraft FINAL : public SpecializedVehicle<Aircraft, VEH_AIRCRAFT> {
void MarkDirty();
void UpdateDeltaXY();
ExpensesType GetExpenseType(bool income) const { return income ? EXPENSES_AIRCRAFT_INC : EXPENSES_AIRCRAFT_RUN; }
ExpensesType GetExpenseType(bool income) const { return income ? EXPENSES_AIRCRAFT_REVENUE : EXPENSES_AIRCRAFT_RUN; }
bool IsPrimaryVehicle() const { return this->IsNormalAircraft(); }
void GetImage(Direction direction, EngineImageType image_type, VehicleSpriteSeq *result) const;
int GetDisplaySpeed() const { return this->cur_speed; }

View File

@@ -37,6 +37,8 @@
#include "disaster_vehicle.h"
#include "newgrf_airporttiles.h"
#include "framerate_type.h"
#include "aircraft_cmd.h"
#include "vehicle_cmd.h"
#include "table/strings.h"
@@ -188,7 +190,7 @@ void GetRotorImage(const Aircraft *v, EngineImageType image_type, VehicleSpriteS
const Aircraft *w = v->Next()->Next();
if (is_custom_sprite(v->spritenum)) {
GetCustomRotorSprite(v, false, image_type, result);
GetCustomRotorSprite(v, image_type, result);
if (result->IsValid()) return;
}
@@ -229,7 +231,7 @@ void DrawAircraftEngine(int left, int right, int preferred_x, int y, EngineID en
VehicleSpriteSeq rotor_seq;
GetCustomRotorIcon(engine, image_type, &rotor_seq);
if (!rotor_seq.IsValid()) rotor_seq.Set(SPR_ROTOR_STOPPED);
rotor_seq.Draw(preferred_x, y - ScaleGUITrad(5), PAL_NONE, false);
rotor_seq.Draw(preferred_x, y - ScaleSpriteTrad(5), PAL_NONE, false);
}
}
@@ -250,22 +252,21 @@ void GetAircraftSpriteSize(EngineID engine, uint &width, uint &height, int &xoff
Rect rect;
seq.GetBounds(&rect);
width = UnScaleGUI(rect.right - rect.left + 1);
height = UnScaleGUI(rect.bottom - rect.top + 1);
width = UnScaleGUI(rect.Width());
height = UnScaleGUI(rect.Height());
xoffs = UnScaleGUI(rect.left);
yoffs = UnScaleGUI(rect.top);
}
/**
* Build an aircraft.
* @param tile tile of the depot where aircraft is built.
* @param flags type of operation.
* @param tile tile of the depot where aircraft is built.
* @param e the engine to build.
* @param data unused.
* @param[out] ret the vehicle that has been built.
* @return the cost of this operation or an error.
*/
CommandCost CmdBuildAircraft(TileIndex tile, DoCommandFlag flags, const Engine *e, uint16 data, Vehicle **ret)
CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **ret)
{
const AircraftVehicleInfo *avi = &e->u.air;
const Station *st = Station::GetByTile(tile);
@@ -327,8 +328,6 @@ CommandCost CmdBuildAircraft(TileIndex tile, DoCommandFlag flags, const Engine *
v->reliability_spd_dec = e->reliability_spd_dec;
v->max_age = e->GetLifeLengthInDays();
_new_vehicle_id = v->index;
v->pos = GetVehiclePosOnBuild(tile);
v->state = HANGAR;
@@ -1274,7 +1273,7 @@ void HandleMissingAircraftOrders(Aircraft *v)
const Station *st = GetTargetAirportIfValid(v);
if (st == nullptr) {
Backup<CompanyID> cur_company(_current_company, v->owner, FILE_LINE);
CommandCost ret = DoCommand(v->tile, v->index, 0, DC_EXEC, CMD_SEND_VEHICLE_TO_DEPOT);
CommandCost ret = Command<CMD_SEND_VEHICLE_TO_DEPOT>::Do(DC_EXEC, v->index, DepotCommand::None, {});
cur_company.Restore();
if (ret.Failed()) CrashAirplane(v);
@@ -1341,7 +1340,12 @@ static void CrashAirplane(Aircraft *v)
AI::NewEvent(v->owner, new ScriptEventVehicleCrashed(v->index, vt, st == nullptr ? ScriptEventVehicleCrashed::CRASH_AIRCRAFT_NO_AIRPORT : ScriptEventVehicleCrashed::CRASH_PLANE_LANDING));
Game::NewEvent(new ScriptEventVehicleCrashed(v->index, vt, st == nullptr ? ScriptEventVehicleCrashed::CRASH_AIRCRAFT_NO_AIRPORT : ScriptEventVehicleCrashed::CRASH_PLANE_LANDING));
AddTileNewsItem(newsitem, NT_ACCIDENT, vt, nullptr, st != nullptr ? st->index : INVALID_STATION);
NewsType newstype = NT_ACCIDENT;
if (v->owner != _local_company) {
newstype = NT_ACCIDENT_OTHER;
}
AddTileNewsItem(newsitem, newstype, vt, nullptr, st != nullptr ? st->index : INVALID_STATION);
ModifyStationRatingAround(vt, v->owner, -160, 30);
if (_settings_client.sound.disaster) SndPlayVehicleFx(SND_12_EXPLOSION, v);
@@ -1632,7 +1636,7 @@ static void AircraftEventHandler_HeliTakeOff(Aircraft *v, const AirportFTAClass
/* Send the helicopter to a hangar if needed for replacement */
if (v->NeedsAutomaticServicing()) {
Backup<CompanyID> cur_company(_current_company, v->owner, FILE_LINE);
DoCommand(v->tile, v->index | DEPOT_SERVICE | DEPOT_LOCATE_HANGAR, 0, DC_EXEC, CMD_SEND_VEHICLE_TO_DEPOT);
Command<CMD_SEND_VEHICLE_TO_DEPOT>::Do(DC_EXEC, v->index, DepotCommand::Service | DepotCommand::LocateHangar, {});
cur_company.Restore();
}
}
@@ -1683,7 +1687,7 @@ static void AircraftEventHandler_Landing(Aircraft *v, const AirportFTAClass *apc
/* check if the aircraft needs to be replaced or renewed and send it to a hangar if needed */
if (v->NeedsAutomaticServicing()) {
Backup<CompanyID> cur_company(_current_company, v->owner, FILE_LINE);
DoCommand(v->tile, v->index | DEPOT_SERVICE, 0, DC_EXEC, CMD_SEND_VEHICLE_TO_DEPOT);
Command<CMD_SEND_VEHICLE_TO_DEPOT>::Do(DC_EXEC, v->index, DepotCommand::Service, {});
cur_company.Restore();
}
}

19
src/aircraft_cmd.h Normal file
View File

@@ -0,0 +1,19 @@
/*
* 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 aircraft_cmd.h Command definitions related to aircraft. */
#ifndef AIRCRAFT_CMD_H
#define AIRCRAFT_CMD_H
#include "command_type.h"
#include "engine_type.h"
#include "vehicle_type.h"
CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **v);
#endif /* AIRCRAFT_CMD_H */

View File

@@ -25,59 +25,58 @@
* Draw the details for the given vehicle at the given position
*
* @param v current vehicle
* @param left The left most coordinate to draw
* @param right The right most coordinate to draw
* @param y The y coordinate
* @param r the Rect to draw within
*/
void DrawAircraftDetails(const Aircraft *v, int left, int right, int y)
void DrawAircraftDetails(const Aircraft *v, const Rect &r)
{
int y_offset = (v->Next()->cargo_cap != 0) ? -(FONT_HEIGHT_NORMAL + 1): 0;
Money feeder_share = 0;
int y = r.top;
for (const Aircraft *u = v; u != nullptr; u = u->Next()) {
if (u->IsNormalAircraft()) {
SetDParam(0, u->engine_type);
SetDParam(1, u->build_year);
SetDParam(2, u->value);
DrawString(left, right, y, STR_VEHICLE_INFO_BUILT_VALUE);
DrawString(r.left, r.right, y, STR_VEHICLE_INFO_BUILT_VALUE);
y += FONT_HEIGHT_NORMAL;
SetDParam(0, u->cargo_type);
SetDParam(1, u->cargo_cap);
SetDParam(2, u->Next()->cargo_type);
SetDParam(3, u->Next()->cargo_cap);
SetDParam(4, GetCargoSubtypeText(u));
DrawString(left, right, y + FONT_HEIGHT_NORMAL, (u->Next()->cargo_cap != 0) ? STR_VEHICLE_INFO_CAPACITY_CAPACITY : STR_VEHICLE_INFO_CAPACITY);
DrawString(r.left, r.right, y, (u->Next()->cargo_cap != 0) ? STR_VEHICLE_INFO_CAPACITY_CAPACITY : STR_VEHICLE_INFO_CAPACITY);
y += FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal;
}
if (u->cargo_cap != 0) {
uint cargo_count = u->cargo.StoredCount();
y_offset += FONT_HEIGHT_NORMAL + 1;
if (cargo_count != 0) {
/* Cargo names (fix pluralness) */
SetDParam(0, u->cargo_type);
SetDParam(1, cargo_count);
SetDParam(2, u->cargo.Source());
DrawString(left, right, y + 2 * FONT_HEIGHT_NORMAL + 1 + y_offset, STR_VEHICLE_DETAILS_CARGO_FROM);
DrawString(r.left, r.right, y, STR_VEHICLE_DETAILS_CARGO_FROM);
y += FONT_HEIGHT_NORMAL;
feeder_share += u->cargo.FeederShare();
}
}
}
y += WidgetDimensions::scaled.vsep_normal;
SetDParam(0, feeder_share);
DrawString(left, right, y + 3 * FONT_HEIGHT_NORMAL + 3 + y_offset, STR_VEHICLE_INFO_FEEDER_CARGO_VALUE);
DrawString(r.left, r.right, y, STR_VEHICLE_INFO_FEEDER_CARGO_VALUE);
}
/**
* Draws an image of an aircraft
* @param v Front vehicle
* @param left The minimum horizontal position
* @param right The maximum horizontal position
* @param y Vertical position to draw at
* @param r Rect to draw at
* @param selection Selected vehicle to draw a frame around
*/
void DrawAircraftImage(const Vehicle *v, int left, int right, int y, VehicleID selection, EngineImageType image_type)
void DrawAircraftImage(const Vehicle *v, const Rect &r, VehicleID selection, EngineImageType image_type)
{
bool rtl = _current_text_dir == TD_RTL;
@@ -87,27 +86,29 @@ void DrawAircraftImage(const Vehicle *v, int left, int right, int y, VehicleID s
Rect rect;
seq.GetBounds(&rect);
int width = UnScaleGUI(rect.right - rect.left + 1);
int width = UnScaleGUI(rect.Width());
int x_offs = UnScaleGUI(rect.left);
int x = rtl ? right - width - x_offs : left - x_offs;
int x = rtl ? r.right - width - x_offs : r.left - x_offs;
/* This magic -1 offset is related to the sprite_y_offsets in build_vehicle_gui.cpp */
int y = ScaleSpriteTrad(-1) + CenterBounds(r.top, r.bottom, 0);
bool helicopter = v->subtype == AIR_HELICOPTER;
int y_offs = ScaleGUITrad(10);
int heli_offs = 0;
PaletteID pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(v);
seq.Draw(x, y + y_offs, pal, (v->vehstatus & VS_CRASHED) != 0);
seq.Draw(x, y, pal, (v->vehstatus & VS_CRASHED) != 0);
if (helicopter) {
const Aircraft *a = Aircraft::From(v);
VehicleSpriteSeq rotor_seq;
GetCustomRotorSprite(a, true, image_type, &rotor_seq);
GetCustomRotorSprite(a, image_type, &rotor_seq);
if (!rotor_seq.IsValid()) rotor_seq.Set(SPR_ROTOR_STOPPED);
heli_offs = ScaleGUITrad(5);
rotor_seq.Draw(x, y + y_offs - heli_offs, PAL_NONE, false);
heli_offs = ScaleSpriteTrad(5);
rotor_seq.Draw(x, y - heli_offs, PAL_NONE, false);
}
if (v->index == selection) {
x += x_offs;
y += UnScaleGUI(rect.top) + y_offs - heli_offs;
DrawFrameRect(x - 1, y - 1, x + width + 1, y + UnScaleGUI(rect.bottom - rect.top + 1) + heli_offs + 1, COLOUR_WHITE, FR_BORDERONLY);
y += UnScaleGUI(rect.top) - heli_offs;
Rect hr = {x, y, x + width - 1, y + UnScaleGUI(rect.Height()) + heli_offs - 1};
DrawFrameRect(hr.Expand(WidgetDimensions::scaled.bevel), COLOUR_WHITE, FR_BORDERONLY);
}
}

17
src/airport_cmd.h Normal file
View File

@@ -0,0 +1,17 @@
/*
* 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 airport_cmd.h Command definitions related to airports. */
#ifndef AIRPORT_CMD_H
#define AIRPORT_CMD_H
#include "command_type.h"
CommandCallback CcBuildAirport;
#endif /* AIRPORT_CMD_H */

View File

@@ -8,6 +8,7 @@
/** @file airport_gui.cpp The GUI for airports. */
#include "stdafx.h"
#include "economy_func.h"
#include "window_gui.h"
#include "station_gui.h"
#include "terraform_gui.h"
@@ -27,6 +28,9 @@
#include "vehicle_func.h"
#include "gui.h"
#include "command_func.h"
#include "airport_cmd.h"
#include "station_cmd.h"
#include "zoom_func.h"
#include "build_confirmation_func.h"
#include "widgets/airport_widget.h"
@@ -42,7 +46,7 @@ static void ShowBuildAirportPicker(Window *parent);
SpriteID GetCustomAirportSprite(const AirportSpec *as, byte layout);
void CcBuildAirport(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
void CcBuildAirport(Commands cmd, const CommandCost &result, TileIndex tile)
{
if (result.Failed()) return;
@@ -57,13 +61,20 @@ void CcBuildAirport(const CommandCost &result, TileIndex tile, uint32 p1, uint32
static void PlaceAirport(TileIndex tile)
{
if (_selected_airport_index == -1) return;
uint32 p2 = _ctrl_pressed;
SB(p2, 16, 16, INVALID_STATION); // no station to join
uint32 p1 = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index)->GetIndex();
p1 |= _selected_airport_layout << 8;
CommandContainer cmdcont = { tile, p1, p2, CMD_BUILD_AIRPORT | CMD_MSG(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE), CcBuildAirport, "" };
ShowSelectStationIfNeeded(cmdcont, TileArea(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE));
byte airport_type = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index)->GetIndex();
byte layout = _selected_airport_layout;
bool adjacent = _ctrl_pressed;
auto proc = [=](bool test, StationID to_join) -> bool {
if (test) {
return Command<CMD_BUILD_AIRPORT>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_AIRPORT>()), tile, airport_type, layout, INVALID_STATION, adjacent).Succeeded();
} else {
return Command<CMD_BUILD_AIRPORT>::Post(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE, CcBuildAirport, tile, airport_type, layout, to_join, adjacent);
}
};
ShowSelectStationIfNeeded(TileArea(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE), proc);
}
/** Airport build toolbar window handler. */
@@ -346,10 +357,10 @@ public:
const AirportSpec *as = AirportSpec::Get(i);
if (!as->enabled) continue;
size->width = std::max(size->width, GetStringBoundingBox(as->name).width);
size->width = std::max(size->width, GetStringBoundingBox(as->name).width + padding.width);
}
this->line_height = GetMinButtonSize(FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM);
this->line_height = FONT_HEIGHT_NORMAL + padding.height;
size->height = 5 * this->line_height;
break;
}
@@ -362,8 +373,8 @@ public:
SpriteID sprite = GetCustomAirportSprite(as, layout);
if (sprite != 0) {
Dimension d = GetSpriteSize(sprite);
d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
d.width += WidgetDimensions::scaled.framerect.Horizontal();
d.height += WidgetDimensions::scaled.framerect.Vertical();
*size = maxdim(d, *size);
}
}
@@ -394,17 +405,17 @@ public:
{
switch (widget) {
case WID_AP_AIRPORT_LIST: {
int y = r.top;
Rect row = r.WithHeight(this->line_height).Shrink(WidgetDimensions::scaled.bevel);
Rect text = r.WithHeight(this->line_height).Shrink(WidgetDimensions::scaled.matrix);
AirportClass *apclass = AirportClass::Get(_selected_airport_class);
for (uint i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < apclass->GetSpecCount(); i++) {
const AirportSpec *as = apclass->GetSpec(i);
if (!as->IsAvailable()) {
GfxFillRect(r.left + 1, y + 1, r.right - 1, y + this->line_height - 2, PC_BLACK, FILLRECT_CHECKER);
GfxFillRect(row, PC_BLACK, FILLRECT_CHECKER);
}
DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, Center(y, this->line_height), as->name, ((int)i == _selected_airport_index) ? TC_WHITE : TC_BLACK);
y += this->line_height;
DrawString(text, as->name, ((int)i == _selected_airport_index) ? TC_WHITE : TC_BLACK);
row = row.Translate(0, this->line_height);
text = text.Translate(0, this->line_height);
}
break;
}
@@ -412,7 +423,7 @@ public:
case WID_AP_AIRPORT_SPRITE:
if (this->preview_sprite != 0) {
Dimension d = GetSpriteSize(this->preview_sprite);
DrawSprite(this->preview_sprite, COMPANY_SPRITE_COLOUR(_local_company), (r.left + r.right - d.width) / 2, (r.top + r.bottom - d.height) / 2);
DrawSprite(this->preview_sprite, COMPANY_SPRITE_COLOUR(_local_company), CenterBounds(r.left, r.right, d.width), CenterBounds(r.top, r.bottom, d.height));
}
break;
@@ -433,11 +444,8 @@ public:
{
this->DrawWidgets();
uint16 top = this->GetWidget<NWidgetBase>(WID_AP_BTN_DOHILIGHT)->pos_y + this->GetWidget<NWidgetBase>(WID_AP_BTN_DOHILIGHT)->current_y + WD_PAR_VSEP_NORMAL;
NWidgetBase *panel_nwi = this->GetWidget<NWidgetBase>(WID_AP_BOTTOMPANEL);
int right = panel_nwi->pos_x + panel_nwi->current_x;
int bottom = panel_nwi->pos_y + panel_nwi->current_y;
Rect r = this->GetWidget<NWidgetBase>(WID_AP_ACCEPTANCE)->GetCurrentRect();
int top = r.top + WidgetDimensions::scaled.vsep_normal;
if (_selected_airport_index != -1) {
const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index);
@@ -447,20 +455,27 @@ public:
if (_settings_game.economy.station_noise_level) {
/* show the noise of the selected airport */
SetDParam(0, as->noise_level);
DrawString(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, STR_STATION_BUILD_NOISE);
top += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
DrawString(r.left, r.right, top, STR_STATION_BUILD_NOISE);
top += FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal;
}
if (_settings_game.economy.infrastructure_maintenance) {
Money monthly = _price[PR_INFRASTRUCTURE_AIRPORT] * as->maintenance_cost >> 3;
SetDParam(0, monthly * 12);
DrawString(r.left, r.right, top, STR_STATION_BUILD_INFRASTRUCTURE_COST);
top += FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal;
}
/* strings such as 'Size' and 'Coverage Area' */
top = DrawStationCoverageAreaText(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, false) + WD_PAR_VSEP_NORMAL;
top = DrawStationCoverageAreaText(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, true) + WD_PAR_VSEP_NORMAL;
top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, false) + WidgetDimensions::scaled.vsep_normal;
top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, true) + WidgetDimensions::scaled.vsep_normal;
}
/* Resize background if the window is too small.
* Never make the window smaller to avoid oscillating if the size change affects the acceptance.
* (This is the case, if making the window bigger moves the mouse into the window.) */
if (top > bottom) {
ResizeWindow(this, 0, top - bottom, false);
if (top > r.bottom) {
ResizeWindow(this, 0, top - r.bottom, false);
}
}
@@ -611,8 +626,8 @@ static const NWidgetPart _nested_build_airport_widgets[] = {
NWidget(WWT_EMPTY, COLOUR_DARK_GREEN, WID_AP_EXTRA_TEXT), SetFill(1, 0), SetMinimalSize(150, 0),
EndContainer(),
/* Bottom panel. */
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_AP_BOTTOMPANEL), SetPIP(2, 2, 2),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_AP_BOTTOMPANEL),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetPadding(WidgetDimensions::unscaled.framerect), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_SPACER), SetMinimalSize(14, 0), SetFill(1, 0),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
@@ -623,7 +638,7 @@ static const NWidgetPart _nested_build_airport_widgets[] = {
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(14, 0), SetFill(1, 0),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1), SetFill(1, 0),
NWidget(WWT_EMPTY, COLOUR_DARK_GREEN, WID_AP_ACCEPTANCE), SetPadding(WidgetDimensions::unscaled.framerect), SetResize(0, 1), SetFill(1, 0),
EndContainer(),
EndContainer(),
};

View File

@@ -22,6 +22,11 @@
#include "ai/ai.hpp"
#include "news_func.h"
#include "strings_func.h"
#include "autoreplace_cmd.h"
#include "group_cmd.h"
#include "order_cmd.h"
#include "train_cmd.h"
#include "vehicle_cmd.h"
#include "table/strings.h"
@@ -206,7 +211,7 @@ static int GetIncompatibleRefitOrderIdForAutoreplace(const Vehicle *v, EngineID
const Order *o;
const Vehicle *u = (v->type == VEH_TRAIN) ? v->First() : v;
const OrderList *orders = u->orders.list;
const OrderList *orders = u->orders;
if (orders == nullptr) return -1;
for (VehicleOrderID i = 0; i < orders->GetNumOrders(); i++) {
o = orders->GetOrderAt(i);
@@ -340,23 +345,24 @@ static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehic
}
/* Build the new vehicle */
cost = DoCommand(old_veh->tile, e | (CT_INVALID << 24), 0, DC_EXEC | DC_AUTOREPLACE, GetCmdBuildVeh(old_veh));
VehicleID new_veh_id;
std::tie(cost, new_veh_id, std::ignore, std::ignore) = Command<CMD_BUILD_VEHICLE>::Do(DC_EXEC | DC_AUTOREPLACE, old_veh->tile, e, true, CT_INVALID, INVALID_CLIENT_ID);
if (cost.Failed()) return cost;
Vehicle *new_veh = Vehicle::Get(_new_vehicle_id);
Vehicle *new_veh = Vehicle::Get(new_veh_id);
*new_vehicle = new_veh;
/* Refit the vehicle if needed */
if (refit_cargo != CT_NO_REFIT) {
byte subtype = GetBestFittingSubType(old_veh, new_veh, refit_cargo);
cost.AddCost(DoCommand(0, new_veh->index, refit_cargo | (subtype << 8), DC_EXEC, GetCmdRefitVeh(new_veh)));
cost.AddCost(std::get<0>(Command<CMD_REFIT_VEHICLE>::Do(DC_EXEC, new_veh->index, refit_cargo, subtype, false, false, 0)));
assert(cost.Succeeded()); // This should be ensured by GetNewCargoTypeForReplace()
}
/* Try to reverse the vehicle, but do not care if it fails as the new type might not be reversible */
if (new_veh->type == VEH_TRAIN && HasBit(Train::From(old_veh)->flags, VRF_REVERSE_DIRECTION)) {
DoCommand(0, new_veh->index, true, DC_EXEC, CMD_REVERSE_TRAIN_DIRECTION);
Command<CMD_REVERSE_TRAIN_DIRECTION>::Do(DC_EXEC, new_veh->index, true);
}
return cost;
@@ -368,9 +374,9 @@ static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehic
* @param evaluate_callback shall the start/stop callback be evaluated?
* @return success or error
*/
static inline CommandCost CmdStartStopVehicle(const Vehicle *v, bool evaluate_callback)
static inline CommandCost DoCmdStartStopVehicle(const Vehicle *v, bool evaluate_callback)
{
return DoCommand(0, v->index, evaluate_callback ? 1 : 0, DC_EXEC | DC_AUTOREPLACE, CMD_START_STOP_VEHICLE);
return Command<CMD_START_STOP_VEHICLE>::Do(DC_EXEC | DC_AUTOREPLACE, v->index, evaluate_callback);
}
/**
@@ -383,7 +389,7 @@ static inline CommandCost CmdStartStopVehicle(const Vehicle *v, bool evaluate_ca
*/
static inline CommandCost CmdMoveVehicle(const Vehicle *v, const Vehicle *after, DoCommandFlag flags, bool whole_chain)
{
return DoCommand(0, v->index | (whole_chain ? 1 : 0) << 20, after != nullptr ? after->index : INVALID_VEHICLE, flags | DC_NO_CARGO_CAP_CHECK, CMD_MOVE_RAIL_VEHICLE);
return Command<CMD_MOVE_RAIL_VEHICLE>::Do(flags | DC_NO_CARGO_CAP_CHECK, v->index, after != nullptr ? after->index : INVALID_VEHICLE, whole_chain);
}
/**
@@ -397,19 +403,19 @@ static CommandCost CopyHeadSpecificThings(Vehicle *old_head, Vehicle *new_head,
CommandCost cost = CommandCost();
/* Share orders */
if (cost.Succeeded() && old_head != new_head) cost.AddCost(DoCommand(0, new_head->index | CO_SHARE << 30, old_head->index, DC_EXEC, CMD_CLONE_ORDER));
if (cost.Succeeded() && old_head != new_head) cost.AddCost(Command<CMD_CLONE_ORDER>::Do(DC_EXEC, CO_SHARE, new_head->index, old_head->index));
/* Copy group membership */
if (cost.Succeeded() && old_head != new_head) cost.AddCost(DoCommand(0, old_head->group_id, new_head->index, DC_EXEC, CMD_ADD_VEHICLE_GROUP));
if (cost.Succeeded() && old_head != new_head) cost.AddCost(std::get<0>(Command<CMD_ADD_VEHICLE_GROUP>::Do(DC_EXEC, old_head->group_id, new_head->index, false)));
/* Perform start/stop check whether the new vehicle suits newgrf restrictions etc. */
if (cost.Succeeded()) {
/* Start the vehicle, might be denied by certain things */
assert((new_head->vehstatus & VS_STOPPED) != 0);
cost.AddCost(CmdStartStopVehicle(new_head, true));
cost.AddCost(DoCmdStartStopVehicle(new_head, true));
/* Stop the vehicle again, but do not care about evil newgrfs allowing starting but not stopping :p */
if (cost.Succeeded()) cost.AddCost(CmdStartStopVehicle(new_head, false));
if (cost.Succeeded()) cost.AddCost(DoCmdStartStopVehicle(new_head, false));
}
/* Last do those things which do never fail (resp. we do not care about), but which are not undo-able */
@@ -466,11 +472,11 @@ static CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, b
}
/* Sell the old vehicle */
cost.AddCost(DoCommand(0, old_v->index, 0, flags, GetCmdSellVeh(old_v)));
cost.AddCost(Command<CMD_SELL_VEHICLE>::Do(flags, old_v->index, false, false, INVALID_CLIENT_ID));
/* If we are not in DC_EXEC undo everything */
if ((flags & DC_EXEC) == 0) {
DoCommand(0, new_v->index, 0, DC_EXEC, GetCmdSellVeh(new_v));
Command<CMD_SELL_VEHICLE>::Do(DC_EXEC, new_v->index, false, false, INVALID_CLIENT_ID);
}
}
@@ -597,7 +603,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
assert(RailVehInfo(wagon->engine_type)->railveh_type == RAILVEH_WAGON);
/* Sell wagon */
[[maybe_unused]] CommandCost ret = DoCommand(0, wagon->index, 0, DC_EXEC, GetCmdSellVeh(wagon));
[[maybe_unused]] CommandCost ret = Command<CMD_SELL_VEHICLE>::Do(DC_EXEC, wagon->index, false, false, INVALID_CLIENT_ID);
assert(ret.Succeeded());
new_vehs[i] = nullptr;
@@ -629,7 +635,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
/* Sell the vehicle.
* Note: This might temporarily construct new trains, so use DC_AUTOREPLACE to prevent
* it from failing due to engine limits. */
cost.AddCost(DoCommand(0, w->index, 0, flags | DC_AUTOREPLACE, GetCmdSellVeh(w)));
cost.AddCost(Command<CMD_SELL_VEHICLE>::Do(flags | DC_AUTOREPLACE, w->index, false, false, INVALID_CLIENT_ID));
if ((flags & DC_EXEC) != 0) {
old_vehs[i] = nullptr;
if (i == 0) old_head = nullptr;
@@ -660,7 +666,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
if ((flags & DC_EXEC) == 0) {
for (int i = num_units - 1; i >= 0; i--) {
if (new_vehs[i] != nullptr) {
DoCommand(0, new_vehs[i]->index, 0, DC_EXEC, GetCmdSellVeh(new_vehs[i]));
Command<CMD_SELL_VEHICLE>::Do(DC_EXEC, new_vehs[i]->index, false, false, INVALID_CLIENT_ID);
new_vehs[i] = nullptr;
}
}
@@ -691,12 +697,12 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
}
/* Sell the old vehicle */
cost.AddCost(DoCommand(0, old_head->index, 0, flags, GetCmdSellVeh(old_head)));
cost.AddCost(Command<CMD_SELL_VEHICLE>::Do(flags, old_head->index, false, false, INVALID_CLIENT_ID));
}
/* If we are not in DC_EXEC undo everything */
if ((flags & DC_EXEC) == 0) {
DoCommand(0, new_head->index, 0, DC_EXEC, GetCmdSellVeh(new_head));
Command<CMD_SELL_VEHICLE>::Do(DC_EXEC, new_head->index, false, false, INVALID_CLIENT_ID);
}
}
}
@@ -707,22 +713,18 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
/**
* Autoreplaces a vehicle
* Trains are replaced as a whole chain, free wagons in depot are replaced on their own
* @param tile not used
* @param flags type of operation
* @param p1 Index of vehicle
* @param p2 not used
* @param text unused
* @param veh_id Index of vehicle
* @return the cost of this operation or an error
*/
CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id)
{
Vehicle *v = Vehicle::GetIfValid(p1);
Vehicle *v = Vehicle::GetIfValid(veh_id);
if (v == nullptr) return CMD_ERROR;
CommandCost ret = CheckOwnership(v->owner);
if (ret.Failed()) return ret;
if (!v->IsChainInDepot()) return CMD_ERROR;
if (v->vehstatus & VS_CRASHED) return CMD_ERROR;
bool free_wagon = false;
@@ -734,6 +736,7 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1
} else {
if (!v->IsPrimaryVehicle()) return CMD_ERROR;
}
if (!v->IsChainInDepot()) return CMD_ERROR;
const Company *c = Company::Get(_current_company);
bool wagon_removal = c->settings.renew_keep_length;
@@ -759,7 +762,7 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1
bool was_stopped = free_wagon || ((v->vehstatus & VS_STOPPED) != 0);
/* Stop the vehicle */
if (!was_stopped) cost.AddCost(CmdStartStopVehicle(v, true));
if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, true));
if (cost.Failed()) return cost;
assert(free_wagon || v->IsStoppedInDepot());
@@ -787,7 +790,7 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1
}
/* Restart the vehicle */
if (!was_stopped) cost.AddCost(CmdStartStopVehicle(v, false));
if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false));
}
if (cost.Succeeded() && nothing_to_do) cost = CommandCost(STR_ERROR_AUTOREPLACE_NOTHING_TO_DO);
@@ -796,35 +799,29 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1
/**
* Change engine renewal parameters
* @param tile unused
* @param flags operation to perform
* @param p1 packed data
* - bit 0 = replace when engine gets old?
* - bits 16-31 = engine group
* @param p2 packed data
* - bits 0-15 = old engine type
* - bits 16-31 = new engine type
* @param text unused
* @param id_g engine group
* @param old_engine_type old engine type
* @param new_engine_type new engine type
* @param when_old replace when engine gets old?
* @return the cost of this operation or an error
*/
CommandCost CmdSetAutoReplace(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdSetAutoReplace(DoCommandFlag flags, GroupID id_g, EngineID old_engine_type, EngineID new_engine_type, bool when_old)
{
Company *c = Company::GetIfValid(_current_company);
if (c == nullptr) return CMD_ERROR;
EngineID old_engine_type = GB(p2, 0, 16);
EngineID new_engine_type = GB(p2, 16, 16);
GroupID id_g = GB(p1, 16, 16);
CommandCost cost;
if (Group::IsValidID(id_g) ? Group::Get(id_g)->owner != _current_company : !IsAllGroupID(id_g) && !IsDefaultGroupID(id_g)) return CMD_ERROR;
if (!Engine::IsValidID(old_engine_type)) return CMD_ERROR;
if (Group::IsValidID(id_g) && Group::Get(id_g)->vehicle_type != Engine::Get(old_engine_type)->type) return CMD_ERROR;
if (new_engine_type != INVALID_ENGINE) {
if (!Engine::IsValidID(new_engine_type)) return CMD_ERROR;
if (!CheckAutoreplaceValidity(old_engine_type, new_engine_type, _current_company)) return CMD_ERROR;
cost = AddEngineReplacementForCompany(c, old_engine_type, new_engine_type, id_g, HasBit(p1, 0), flags);
cost = AddEngineReplacementForCompany(c, old_engine_type, new_engine_type, id_g, when_old, flags);
} else {
cost = RemoveEngineReplacementForCompany(c, old_engine_type, id_g, flags);
}

24
src/autoreplace_cmd.h Normal file
View File

@@ -0,0 +1,24 @@
/*
* 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 autoreplace_cmd.h Command definitions related to autoreplace. */
#ifndef AUTOREPLACE_CMD_H
#define AUTOREPLACE_CMD_H
#include "command_type.h"
#include "vehicle_type.h"
#include "engine_type.h"
#include "group_type.h"
CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id);
CommandCost CmdSetAutoReplace(DoCommandFlag flags, GroupID id_g, EngineID old_engine_type, EngineID new_engine_type, bool when_old);
DEF_CMD_TRAIT(CMD_AUTOREPLACE_VEHICLE, CmdAutoreplaceVehicle, 0, CMDT_VEHICLE_MANAGEMENT)
DEF_CMD_TRAIT(CMD_SET_AUTOREPLACE, CmdSetAutoReplace, 0, CMDT_VEHICLE_MANAGEMENT)
#endif /* AUTOREPLACE_CMD_H */

View File

@@ -26,16 +26,19 @@
#include "rail_gui.h"
#include "road_gui.h"
#include "widgets/dropdown_func.h"
#include "autoreplace_cmd.h"
#include "group_cmd.h"
#include "settings_cmd.h"
#include "widgets/autoreplace_widget.h"
#include "safeguards.h"
void DrawEngineList(VehicleType type, int x, int r, int y, const GUIEngineList *eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group);
void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group);
static bool EngineNumberSorter(const EngineID &a, const EngineID &b)
static bool EngineNumberSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
return Engine::Get(a)->list_position < Engine::Get(b)->list_position;
return Engine::Get(a.engine_id)->list_position < Engine::Get(b.engine_id)->list_position;
}
/**
@@ -111,6 +114,26 @@ class ReplaceVehicleWindow : public Window {
return true;
}
void AddChildren(const GUIEngineList &source, GUIEngineList &target, EngineID parent, int indent, int side)
{
for (const auto &item : source) {
if (item.variant_id != parent || item.engine_id == parent) continue;
const Engine *e = Engine::Get(item.engine_id);
EngineDisplayFlags flags = item.flags;
if (e->display_last_variant != INVALID_ENGINE) flags &= ~EngineDisplayFlags::Shaded;
target.emplace_back(e->display_last_variant == INVALID_ENGINE ? item.engine_id : e->display_last_variant, item.engine_id, flags, indent);
/* Add variants if not folded */
if ((item.flags & (EngineDisplayFlags::HasVariants | EngineDisplayFlags::IsFolded)) == EngineDisplayFlags::HasVariants) {
/* Add this engine again as a child */
if ((item.flags & EngineDisplayFlags::Shaded) == EngineDisplayFlags::None) {
target.emplace_back(item.engine_id, item.engine_id, EngineDisplayFlags::None, indent + 1);
}
AddChildren(source, target, item.engine_id, indent + 1, side);
}
}
}
/**
* Generate an engines list
@@ -118,12 +141,12 @@ class ReplaceVehicleWindow : public Window {
*/
void GenerateReplaceVehList(bool draw_left)
{
std::vector<EngineID> variants;
EngineID selected_engine = INVALID_ENGINE;
VehicleType type = (VehicleType)this->window_number;
byte side = draw_left ? 0 : 1;
GUIEngineList *list = &this->engines[side];
list->clear();
GUIEngineList list;
for (const Engine *e : Engine::IterateType(type)) {
if (!draw_left && !this->show_hidden_engines && e->IsHidden(_local_company)) continue;
@@ -153,15 +176,37 @@ class ReplaceVehicleWindow : public Window {
if (!CheckAutoreplaceValidity(this->sel_engine[0], eid, _local_company)) continue;
}
list->push_back(eid);
EngineDisplayFlags flags = (side == 0) ? EngineDisplayFlags::None : e->display_flags;
if (side == 1 && eid == this->sel_engine[0]) flags |= EngineDisplayFlags::Shaded;
list.emplace_back(eid, e->info.variant_id, flags, 0);
if (side == 1 && e->info.variant_id != INVALID_ENGINE) variants.push_back(e->info.variant_id);
if (eid == this->sel_engine[side]) selected_engine = eid; // The selected engine is still in the list
}
if (side == 1) {
/* ensure primary engine of variant group is in list */
for (const auto &variant : variants) {
if (std::find(list.begin(), list.end(), variant) == list.end()) {
const Engine *e = Engine::Get(variant);
list.emplace_back(variant, e->info.variant_id, e->display_flags | EngineDisplayFlags::Shaded, 0);
}
}
}
this->sel_engine[side] = selected_engine; // update which engine we selected (the same or none, if it's not in the list anymore)
if (draw_left) {
EngList_Sort(list, &EngineNumberSorter);
EngList_Sort(&list, &EngineNumberSorter);
} else {
_engine_sort_direction = this->descending_sort_order;
EngList_Sort(list, _engine_sort_functions[this->window_number][this->sort_criteria]);
EngList_Sort(&list, _engine_sort_functions[this->window_number][this->sort_criteria]);
}
this->engines[side].clear();
if (side == 1) {
AddChildren(list, this->engines[side], INVALID_ENGINE, 0, side);
} else {
this->engines[side].swap(list);
}
}
@@ -175,7 +220,7 @@ class ReplaceVehicleWindow : public Window {
this->GenerateReplaceVehList(true);
this->vscroll[0]->SetCount((uint)this->engines[0].size());
if (this->reset_sel_engine && this->sel_engine[0] == INVALID_ENGINE && this->engines[0].size() != 0) {
this->sel_engine[0] = this->engines[0][0];
this->sel_engine[0] = this->engines[0][0].engine_id;
}
}
@@ -196,8 +241,8 @@ class ReplaceVehicleWindow : public Window {
this->vscroll[1]->SetCount((uint)this->engines[1].size());
if (this->reset_sel_engine && this->sel_engine[1] != INVALID_ENGINE) {
int position = 0;
for (EngineID &eid : this->engines[1]) {
if (eid == this->sel_engine[1]) break;
for (const auto &item : this->engines[1]) {
if (item.engine_id == this->sel_engine[1]) break;
++position;
}
this->vscroll[1]->ScrollTowards(position);
@@ -218,7 +263,7 @@ class ReplaceVehicleWindow : public Window {
{
EngineID veh_from = this->sel_engine[0];
EngineID veh_to = this->sel_engine[1];
DoCommandP(0, (replace_when_old ? 1 : 0) | (this->sel_group << 16), veh_from + (veh_to << 16), CMD_SET_AUTOREPLACE);
Command<CMD_SET_AUTOREPLACE>::Post(this->sel_group, veh_from, veh_to, replace_when_old);
}
public:
@@ -302,8 +347,8 @@ public:
case WID_RV_INFO_TAB: {
Dimension d = GetStringBoundingBox(STR_REPLACE_NOT_REPLACING);
d = maxdim(d, GetStringBoundingBox(STR_REPLACE_NOT_REPLACING_VEHICLE_SELECTED));
d.width += WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
d.width += padding.width;
d.height += padding.height;
*size = maxdim(*size, d);
break;
}
@@ -422,7 +467,7 @@ public:
str = STR_REPLACE_NOT_REPLACING_VEHICLE_SELECTED;
}
DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, Center(r.top + WD_FRAMERECT_TOP, r.bottom - r.top - WD_FRAMERECT_TOP), str, TC_BLACK, SA_HOR_CENTER);
DrawString(r.Shrink(WidgetDimensions::scaled.frametext, WidgetDimensions::scaled.framerect), str, TC_BLACK, SA_HOR_CENTER);
break;
}
@@ -433,8 +478,7 @@ public:
EngineID end = static_cast<EngineID>(std::min<size_t>(this->vscroll[side]->GetCapacity() + start, this->engines[side].size()));
/* Do the actual drawing */
DrawEngineList((VehicleType)this->window_number, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP,
&this->engines[side], start, end, this->sel_engine[side], side == 0, this->sel_group);
DrawEngineList((VehicleType)this->window_number, r, this->engines[side], start, end, this->sel_engine[side], side == 0, this->sel_group);
break;
}
}
@@ -485,10 +529,10 @@ public:
ted.cargo = e->GetDefaultCargoType();
ted.capacity = e->GetDisplayDefaultCapacity(&ted.mail_capacity);
NWidgetBase *nwi = this->GetWidget<NWidgetBase>(side == 0 ? WID_RV_LEFT_DETAILS : WID_RV_RIGHT_DETAILS);
int text_end = DrawVehiclePurchaseInfo(nwi->pos_x + WD_FRAMETEXT_LEFT, nwi->pos_x + nwi->current_x - WD_FRAMETEXT_RIGHT,
nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine[side], ted);
needed_height = std::max(needed_height, (text_end - (int)nwi->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL);
const Rect r = this->GetWidget<NWidgetBase>(side == 0 ? WID_RV_LEFT_DETAILS : WID_RV_RIGHT_DETAILS)->GetCurrentRect()
.Shrink(WidgetDimensions::scaled.frametext, WidgetDimensions::scaled.framerect);
int text_end = DrawVehiclePurchaseInfo(r.left, r.right, r.top, this->sel_engine[side], ted);
needed_height = std::max(needed_height, (text_end - r.top) / FONT_HEIGHT_NORMAL);
}
}
if (needed_height != this->details_height) { // Details window are not high enough, enlarge them.
@@ -544,10 +588,10 @@ public:
case WID_RV_TRAIN_WAGONREMOVE_TOGGLE: {
const Group *g = Group::GetIfValid(this->sel_group);
if (g != nullptr) {
DoCommandP(0, this->sel_group | (GroupFlags::GF_REPLACE_WAGON_REMOVAL << 16), (HasBit(g->flags, GroupFlags::GF_REPLACE_WAGON_REMOVAL) ? 0 : 1) | (_ctrl_pressed << 1), CMD_SET_GROUP_FLAG);
Command<CMD_SET_GROUP_FLAG>::Post(this->sel_group, GroupFlags::GF_REPLACE_WAGON_REMOVAL, !HasBit(g->flags, GroupFlags::GF_REPLACE_WAGON_REMOVAL), _ctrl_pressed);
} else {
// toggle renew_keep_length
DoCommandP(0, 0, Company::Get(_local_company)->settings.renew_keep_length ? 0 : 1, CMD_CHANGE_COMPANY_SETTING, nullptr, "company.renew_keep_length");
Command<CMD_CHANGE_COMPANY_SETTING>::Post("company.renew_keep_length", Company::Get(_local_company)->settings.renew_keep_length ? 0 : 1);
}
break;
}
@@ -565,7 +609,7 @@ public:
case WID_RV_STOP_REPLACE: { // Stop replacing
EngineID veh_from = this->sel_engine[0];
DoCommandP(0, this->sel_group << 16, veh_from + (INVALID_ENGINE << 16), CMD_SET_AUTOREPLACE);
Command<CMD_SET_AUTOREPLACE>::Post(this->sel_group, veh_from, INVALID_ENGINE, false);
break;
}
@@ -580,7 +624,32 @@ public:
uint i = this->vscroll[click_side]->GetScrolledRowFromWidget(pt.y, this, widget);
size_t engine_count = this->engines[click_side].size();
EngineID e = engine_count > i ? this->engines[click_side][i] : INVALID_ENGINE;
EngineID e = INVALID_ENGINE;
if (i < engine_count) {
const auto &item = this->engines[click_side][i];
const Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.matrix).WithWidth(WidgetDimensions::scaled.hsep_indent * (item.indent + 1), _current_text_dir == TD_RTL);
if ((item.flags & EngineDisplayFlags::HasVariants) != EngineDisplayFlags::None && IsInsideMM(r.left, r.right, pt.x)) {
/* toggle folded flag on engine */
assert(item.variant_id != INVALID_ENGINE);
Engine *engine = Engine::Get(item.variant_id);
engine->display_flags ^= EngineDisplayFlags::IsFolded;
InvalidateWindowData(WC_REPLACE_VEHICLE, (VehicleType)this->window_number, 0); // Update the autoreplace window
InvalidateWindowClassesData(WC_BUILD_VEHICLE); // The build windows needs updating as well
return;
}
if ((item.flags & EngineDisplayFlags::Shaded) == EngineDisplayFlags::None) e = item.engine_id;
}
/* If Ctrl is pressed on the left side and we don't have any engines of the selected type, stop autoreplacing.
* This is most common when we have finished autoreplacing the engine and want to remove it from the list. */
if (click_side == 0 && _ctrl_pressed && e != INVALID_ENGINE &&
(GetGroupNumEngines(_local_company, sel_group, e) == 0 || GetGroupNumEngines(_local_company, ALL_GROUP, e) == 0)) {
EngineID veh_from = e;
Command<CMD_SET_AUTOREPLACE>::Post(this->sel_group, veh_from, INVALID_ENGINE, false);
break;
}
if (e == this->sel_engine[click_side]) break; // we clicked the one we already selected
this->sel_engine[click_side] = e;
if (click_side == 0) {

View File

@@ -62,8 +62,7 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> {
Owner owner; ///< The owner of this station
StationFacility facilities; ///< The facilities that this station has
uint8 num_specs; ///< Number of specs in the speclist
StationSpecList *speclist; ///< List of station specs of this station
std::vector<StationSpecList> speclist; ///< List of rail station specs of this station.
Date build_date; ///< Date of construction

View File

@@ -58,8 +58,8 @@ public:
void Initialize(const Rect &r)
{
this->tile = TileXY(r.left, r.top);
this->w = r.right - r.left + 1;
this->h = r.bottom - r.top + 1;
this->w = r.Width();
this->h = r.Height();
this->data.clear();
this->data.resize(Index(w, h));
}

View File

@@ -19,6 +19,7 @@
/** Instantiation of the partially SSSE2 32bpp with animation blitter factory. */
static FBlitter_32bppSSE2_Anim iFBlitter_32bppSSE2_Anim;
GNU_TARGET("sse2")
void Blitter_32bppSSE2_Anim::PaletteAnimate(const Palette &palette)
{
assert(!_screen_disable_anim);

View File

@@ -16,6 +16,10 @@
#define SSE_VERSION 2
#endif
#ifndef SSE_TARGET
#define SSE_TARGET "sse2"
#endif
#ifndef FULL_ANIMATION
#define FULL_ANIMATION 1
#endif

View File

@@ -29,6 +29,7 @@ static FBlitter_32bppSSE4_Anim iFBlitter_32bppSSE4_Anim;
*/
IGNORE_UNINITIALIZED_WARNING_START
template <BlitterMode mode, Blitter_32bppSSE2::ReadMode read_mode, Blitter_32bppSSE2::BlockType bt_last, bool translucent, bool animated>
GNU_TARGET("sse4.1")
inline void Blitter_32bppSSE4_Anim::Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom)
{
const byte * const remap = bp->remap;
@@ -52,6 +53,7 @@ inline void Blitter_32bppSSE4_Anim::Draw(const Blitter::BlitterParams *bp, ZoomL
const __m128i a_cm = ALPHA_CONTROL_MASK;
const __m128i pack_low_cm = PACK_LOW_CONTROL_MASK;
const __m128i tr_nom_base = TRANSPARENT_NOM_BASE;
const __m128i a_am = ALPHA_AND_MASK;
for (int y = bp->height; y != 0; y--) {
Colour *dst = dst_line;
@@ -143,7 +145,7 @@ inline void Blitter_32bppSSE4_Anim::Draw(const Blitter::BlitterParams *bp, ZoomL
/* Blend colours. */
bmno_alpha_blend:
srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm);
srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm, a_am);
bmno_full_opacity:
_mm_storel_epi64((__m128i *) dst, srcABCD);
bmno_full_transparency:
@@ -170,7 +172,7 @@ bmno_full_transparency:
} else {
srcABCD = _mm_cvtsi32_si128(src->data);
}
dst->data = _mm_cvtsi128_si32(AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm));
dst->data = _mm_cvtsi128_si32(AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm, a_am));
}
}
break;
@@ -254,7 +256,7 @@ bmno_full_transparency:
/* Blend colours. */
bmcr_alpha_blend:
srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm);
srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm, a_am);
bmcr_full_opacity:
_mm_storel_epi64((__m128i *) dst, srcABCD);
bmcr_full_transparency:
@@ -287,7 +289,7 @@ bmcr_full_transparency:
if (src->a < 255) {
bmcr_alpha_blend_single:
__m128i dstABCD = _mm_cvtsi32_si128(dst->data);
srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm);
srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm, a_am);
}
dst->data = _mm_cvtsi128_si32(srcABCD);
}
@@ -366,6 +368,12 @@ IGNORE_UNINITIALIZED_WARNING_STOP
*/
void Blitter_32bppSSE4_Anim::Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom)
{
if (_screen_disable_anim) {
/* This means our output is not to the screen, so we can't be doing any animation stuff, so use our parent Draw() */
Blitter_32bppSSE4::Draw(bp, mode, zoom);
return;
}
const Blitter_32bppSSE_Base::SpriteFlags sprite_flags = ((const Blitter_32bppSSE_Base::SpriteData *) bp->sprite)->flags;
switch (mode) {
default: {

View File

@@ -16,6 +16,10 @@
#define SSE_VERSION 4
#endif
#ifndef SSE_TARGET
#define SSE_TARGET "sse4.1"
#endif
#ifndef FULL_ANIMATION
#define FULL_ANIMATION 1
#endif
@@ -28,7 +32,7 @@
#define MARGIN_NORMAL_THRESHOLD 4
/** The SSE4 32 bpp blitter with palette animation. */
class Blitter_32bppSSE4_Anim FINAL : public Blitter_32bppSSE2_Anim, public Blitter_32bppSSE_Base {
class Blitter_32bppSSE4_Anim FINAL : public Blitter_32bppSSE2_Anim, public Blitter_32bppSSE4 {
private:
public:
@@ -39,13 +43,14 @@ public:
return Blitter_32bppSSE_Base::Encode(sprite, allocator);
}
const char *GetName() override { return "32bpp-sse4-anim"; }
using Blitter_32bppSSE2_Anim::LookupColourInPalette;
};
/** Factory for the SSE4 32 bpp blitter (with palette animation). */
class FBlitter_32bppSSE4_Anim: public BlitterFactory {
public:
FBlitter_32bppSSE4_Anim() : BlitterFactory("32bpp-sse4-anim", "32bpp SSE4 Blitter (palette animation)", HasCPUIDFlag(1, 2, 19)) {}
Blitter *CreateInstance() override { return new Blitter_32bppSSE4_Anim(); }
Blitter *CreateInstance() override { return static_cast<Blitter_32bppSSE2_Anim *>(new Blitter_32bppSSE4_Anim()); }
};
#endif /* WITH_SSE */

View File

@@ -16,6 +16,10 @@
#define SSE_VERSION 2
#endif
#ifndef SSE_TARGET
#define SSE_TARGET "sse2"
#endif
#ifndef FULL_ANIMATION
#define FULL_ANIMATION 0
#endif

View File

@@ -16,6 +16,10 @@
#define SSE_VERSION 4
#endif
#ifndef SSE_TARGET
#define SSE_TARGET "sse4.1"
#endif
#ifndef FULL_ANIMATION
#define FULL_ANIMATION 0
#endif

View File

@@ -12,6 +12,7 @@
#ifdef WITH_SSE
GNU_TARGET(SSE_TARGET)
static inline void InsertFirstUint32(const uint32 value, __m128i &into)
{
#if (SSE_VERSION >= 4)
@@ -22,6 +23,7 @@ static inline void InsertFirstUint32(const uint32 value, __m128i &into)
#endif
}
GNU_TARGET(SSE_TARGET)
static inline void InsertSecondUint32(const uint32 value, __m128i &into)
{
#if (SSE_VERSION >= 4)
@@ -32,6 +34,7 @@ static inline void InsertSecondUint32(const uint32 value, __m128i &into)
#endif
}
GNU_TARGET(SSE_TARGET)
static inline void LoadUint64(const uint64 value, __m128i &into)
{
#ifdef POINTER_IS_64BIT
@@ -46,6 +49,7 @@ static inline void LoadUint64(const uint64 value, __m128i &into)
#endif
}
GNU_TARGET(SSE_TARGET)
static inline __m128i PackUnsaturated(__m128i from, const __m128i &mask)
{
#if (SSE_VERSION == 2)
@@ -56,36 +60,43 @@ static inline __m128i PackUnsaturated(__m128i from, const __m128i &mask)
#endif
}
GNU_TARGET(SSE_TARGET)
static inline __m128i DistributeAlpha(const __m128i from, const __m128i &mask)
{
#if (SSE_VERSION == 2)
__m128i alphaAB = _mm_shufflelo_epi16(from, 0x3F); // PSHUFLW, put alpha1 in front of each rgb1
return _mm_shufflehi_epi16(alphaAB, 0x3F); // PSHUFHW, put alpha2 in front of each rgb2
alphaAB = _mm_shufflehi_epi16(alphaAB, 0x3F); // PSHUFHW, put alpha2 in front of each rgb2
return _mm_andnot_si128(mask, alphaAB); // PANDN, set alpha fields to 0
#else
return _mm_shuffle_epi8(from, mask);
#endif
}
static inline __m128i AlphaBlendTwoPixels(__m128i src, __m128i dst, const __m128i &distribution_mask, const __m128i &pack_mask)
GNU_TARGET(SSE_TARGET)
static inline __m128i AlphaBlendTwoPixels(__m128i src, __m128i dst, const __m128i &distribution_mask, const __m128i &pack_mask, const __m128i &alpha_mask)
{
__m128i srcAB = _mm_unpacklo_epi8(src, _mm_setzero_si128()); // PUNPCKLBW, expand each uint8 into uint16
__m128i dstAB = _mm_unpacklo_epi8(dst, _mm_setzero_si128());
__m128i alphaAB = _mm_cmpgt_epi16(srcAB, _mm_setzero_si128()); // PCMPGTW, if (alpha > 0) a++;
alphaAB = _mm_srli_epi16(alphaAB, 15);
alphaAB = _mm_add_epi16(alphaAB, srcAB);
__m128i alphaMaskAB = _mm_cmpgt_epi16(srcAB, _mm_setzero_si128()); // PCMPGTW (alpha > 0) ? 0xFFFF : 0
__m128i alphaAB = _mm_sub_epi16(srcAB, alphaMaskAB); // if (alpha > 0) a++;
alphaAB = DistributeAlpha(alphaAB, distribution_mask);
srcAB = _mm_sub_epi16(srcAB, dstAB); // PSUBW, (r - Cr)
srcAB = _mm_mullo_epi16(srcAB, alphaAB); // PMULLW, a*(r - Cr)
srcAB = _mm_srli_epi16(srcAB, 8); // PSRLW, a*(r - Cr)/256
srcAB = _mm_add_epi16(srcAB, dstAB); // PADDW, a*(r - Cr)/256 + Cr
alphaMaskAB = _mm_and_si128(alphaMaskAB, alpha_mask); // PAND, set non alpha fields to 0
srcAB = _mm_or_si128(srcAB, alphaMaskAB); // POR, set alpha fields to 0xFFFF is src alpha was > 0
return PackUnsaturated(srcAB, pack_mask);
}
/* Darken 2 pixels.
* rgb = rgb * ((256/4) * 4 - (alpha/4)) / ((256/4) * 4)
*/
GNU_TARGET(SSE_TARGET)
static inline __m128i DarkenTwoPixels(__m128i src, __m128i dst, const __m128i &distribution_mask, const __m128i &tr_nom_base)
{
__m128i srcAB = _mm_unpacklo_epi8(src, _mm_setzero_si128());
@@ -99,6 +110,7 @@ static inline __m128i DarkenTwoPixels(__m128i src, __m128i dst, const __m128i &d
}
IGNORE_UNINITIALIZED_WARNING_START
GNU_TARGET(SSE_TARGET)
static Colour ReallyAdjustBrightness(Colour colour, uint8 brightness)
{
uint64 c16 = colour.b | (uint64) colour.g << 16 | (uint64) colour.r << 32;
@@ -141,6 +153,7 @@ static inline Colour AdjustBrightneSSE(Colour colour, uint8 brightness)
return ReallyAdjustBrightness(colour, brightness);
}
GNU_TARGET(SSE_TARGET)
static inline __m128i AdjustBrightnessOfTwoPixels(__m128i from, uint32 brightness)
{
#if (SSE_VERSION < 3)
@@ -192,6 +205,7 @@ static inline __m128i AdjustBrightnessOfTwoPixels(__m128i from, uint32 brightnes
*/
IGNORE_UNINITIALIZED_WARNING_START
template <BlitterMode mode, Blitter_32bppSSE2::ReadMode read_mode, Blitter_32bppSSE2::BlockType bt_last, bool translucent>
GNU_TARGET(SSE_TARGET)
#if (SSE_VERSION == 2)
inline void Blitter_32bppSSE2::Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom)
#elif (SSE_VERSION == 3)
@@ -217,9 +231,11 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel
const MapValue *src_mv = src_mv_line;
/* Load these variables into register before loop. */
const __m128i alpha_and = ALPHA_AND_MASK;
#define ALPHA_BLEND_PARAM_3 alpha_and
#if (SSE_VERSION == 2)
const __m128i clear_hi = CLEAR_HIGH_BYTE_MASK;
#define ALPHA_BLEND_PARAM_1 clear_hi
#define ALPHA_BLEND_PARAM_1 alpha_and
#define ALPHA_BLEND_PARAM_2 clear_hi
#define DARKEN_PARAM_1 tr_nom_base
#define DARKEN_PARAM_2 tr_nom_base
@@ -265,7 +281,7 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel
for (uint x = (uint) effective_width / 2; x > 0; x--) {
__m128i srcABCD = _mm_loadl_epi64((const __m128i*) src);
__m128i dstABCD = _mm_loadl_epi64((__m128i*) dst);
_mm_storel_epi64((__m128i*) dst, AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2));
_mm_storel_epi64((__m128i*) dst, AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2, ALPHA_BLEND_PARAM_3));
src += 2;
dst += 2;
}
@@ -273,7 +289,7 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel
if ((bt_last == BT_NONE && effective_width & 1) || bt_last == BT_ODD) {
__m128i srcABCD = _mm_cvtsi32_si128(src->data);
__m128i dstABCD = _mm_cvtsi32_si128(dst->data);
dst->data = _mm_cvtsi128_si32(AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2));
dst->data = _mm_cvtsi128_si32(AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2, ALPHA_BLEND_PARAM_3));
}
break;
@@ -318,7 +334,7 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel
}
/* Blend colours. */
_mm_storel_epi64((__m128i *) dst, AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2));
_mm_storel_epi64((__m128i *) dst, AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2, ALPHA_BLEND_PARAM_3));
dst += 2;
src += 2;
src_mv += 2;
@@ -347,7 +363,7 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel
if (src->a < 255) {
bmcr_alpha_blend_single:
__m128i dstABCD = _mm_cvtsi32_si128(dst->data);
srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2);
srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2, ALPHA_BLEND_PARAM_3);
}
dst->data = _mm_cvtsi128_si32(srcABCD);
}

View File

@@ -51,6 +51,7 @@ typedef union ALIGN(16) um128i {
#define OVERBRIGHT_VALUE_MASK _mm_setr_epi8(-1, 0, -1, 0, -1, 0, 0, 0, -1, 0, -1, 0, -1, 0, 0, 0)
#define OVERBRIGHT_CONTROL_MASK _mm_setr_epi8( 0, 1, 0, 1, 0, 1, 7, 7, 2, 3, 2, 3, 2, 3, 7, 7)
#define TRANSPARENT_NOM_BASE _mm_setr_epi16(256, 256, 256, 256, 256, 256, 256, 256)
#define ALPHA_AND_MASK _mm_setr_epi16( 0, 0, 0, -1, 0, 0, 0, -1)
#endif /* WITH_SSE */
#endif /* BLITTER_32BPP_SSE_TYPE_H */

View File

@@ -16,6 +16,10 @@
#define SSE_VERSION 3
#endif
#ifndef SSE_TARGET
#define SSE_TARGET "ssse3"
#endif
#ifndef FULL_ANIMATION
#define FULL_ANIMATION 0
#endif

View File

@@ -1,5 +1,3 @@
/* $Id$ */
/*
* 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.

View File

@@ -1,5 +1,3 @@
/* $Id$ */
/*
* 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.

View File

@@ -38,21 +38,6 @@ add_files(
CONDITION NOT OPTION_DEDICATED AND OPENGL_FOUND
)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
set_compile_flags(
32bpp_anim_sse2.cpp
32bpp_sse2.cpp
COMPILE_FLAGS -msse2)
set_compile_flags(
32bpp_ssse3.cpp
COMPILE_FLAGS -mssse3)
set_compile_flags(
32bpp_anim_sse4.cpp
32bpp_sse4.cpp
COMPILE_FLAGS -msse4.1)
endif()
add_files(
base.hpp
common.hpp

View File

@@ -101,14 +101,14 @@ public:
{
if (widget == WID_BEM_MESSAGE) {
*size = GetStringBoundingBox(STR_MISSING_GRAPHICS_ERROR);
size->height = GetStringHeight(STR_MISSING_GRAPHICS_ERROR, size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT) + WD_FRAMETEXT_BOTTOM + WD_FRAMETEXT_TOP;
size->height = GetStringHeight(STR_MISSING_GRAPHICS_ERROR, size->width - WidgetDimensions::scaled.frametext.Horizontal()) + WidgetDimensions::scaled.frametext.Vertical();
}
}
void DrawWidget(const Rect &r, int widget) const override
{
if (widget == WID_BEM_MESSAGE) {
DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMETEXT_TOP, r.bottom - WD_FRAMETEXT_BOTTOM, STR_MISSING_GRAPHICS_ERROR, TC_FROMSTRING, SA_CENTER);
DrawStringMultiLine(r.Shrink(WidgetDimensions::scaled.frametext), STR_MISSING_GRAPHICS_ERROR, TC_FROMSTRING, SA_CENTER);
}
}
@@ -123,8 +123,11 @@ public:
/** Nested widgets for the download window. */
static const NWidgetPart _nested_bootstrap_download_status_window_widgets[] = {
NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CONTENT_DOWNLOAD_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
NWidget(WWT_PANEL, COLOUR_GREY, WID_NCDS_BACKGROUND),
NWidget(NWID_SPACER), SetMinimalSize(350, 0), SetMinimalTextLines(3, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 30),
NWidget(WWT_PANEL, COLOUR_GREY),
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_wide, 0), SetPadding(WidgetDimensions::unscaled.modalpopup),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_NCDS_PROGRESS_BAR), SetFill(1, 0),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_NCDS_PROGRESS_TEXT), SetFill(1, 0), SetMinimalSize(350, 0),
EndContainer(),
EndContainer(),
};
@@ -214,15 +217,15 @@ public:
/* We cache the button size. This is safe as no reinit can happen here. */
if (this->button_size.width == 0) {
this->button_size = maxdim(GetStringBoundingBox(STR_MISSING_GRAPHICS_YES_DOWNLOAD), GetStringBoundingBox(STR_MISSING_GRAPHICS_NO_QUIT));
this->button_size.width += WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
this->button_size.height += WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM;
this->button_size.width += WidgetDimensions::scaled.frametext.Horizontal();
this->button_size.height += WidgetDimensions::scaled.frametext.Vertical();
}
switch (widget) {
case WID_BAFD_QUESTION:
/* The question is twice as wide as the buttons, and determine the height based on the width. */
size->width = this->button_size.width * 2;
size->height = GetStringHeight(STR_MISSING_GRAPHICS_SET_MESSAGE, size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT) + WD_FRAMETEXT_BOTTOM + WD_FRAMETEXT_TOP;
size->height = GetStringHeight(STR_MISSING_GRAPHICS_SET_MESSAGE, size->width - WidgetDimensions::scaled.frametext.Horizontal()) + WidgetDimensions::scaled.frametext.Vertical();
break;
case WID_BAFD_YES:
@@ -236,7 +239,7 @@ public:
{
if (widget != 0) return;
DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMETEXT_TOP, r.bottom - WD_FRAMETEXT_BOTTOM, STR_MISSING_GRAPHICS_SET_MESSAGE, TC_FROMSTRING, SA_CENTER);
DrawStringMultiLine(r.Shrink(WidgetDimensions::scaled.frametext), STR_MISSING_GRAPHICS_SET_MESSAGE, TC_FROMSTRING, SA_CENTER);
}
void OnClick(Point pt, int widget, int click_count) override
@@ -286,16 +289,16 @@ bool HandleBootstrap()
/* No user interface, bail out with an error. */
if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) goto failure;
/* If there is no network or no freetype, then there is nothing we can do. Go straight to failure. */
/* If there is no network or no non-sprite font, then there is nothing we can do. Go straight to failure. */
#if (defined(_WIN32) && defined(WITH_UNISCRIBE)) || (defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(__APPLE__))) || defined(WITH_COCOA)
if (!_network_available) goto failure;
/* First tell the game we're bootstrapping. */
_game_mode = GM_BOOTSTRAP;
/* Initialise the freetype font code. */
/* Initialise the font cache. */
InitializeUnicodeGlyphMap();
/* Next "force" finding a suitable freetype font as the local font is missing. */
/* Next "force" finding a suitable non-sprite font as the local font is missing. */
CheckForMissingGlyphs(false);
/* Initialise the palette. The biggest step is 'faking' some recolour sprites.

View File

@@ -20,10 +20,10 @@
#include "sortlist_type.h"
#include "widgets/dropdown_func.h"
#include "core/geometry_func.hpp"
#include "cmd_helper.h"
#include "tunnelbridge_map.h"
#include "road_gui.h"
#include "tilehighlight_func.h"
#include "tunnelbridge_cmd.h"
#include "widgets/bridge_widget.h"
@@ -51,27 +51,22 @@ typedef GUIList<BuildBridgeData> GUIBridgeList; ///< List of bridges, used in #B
* Callback executed after a build Bridge CMD has been called
*
* @param result Whether the build succeeded
* @param end_tile End tile of the bridge.
* @param p1 packed start tile coords (~ dx)
* @param p2 various bitstuffed elements
* - p2 = (bit 0- 7) - bridge type (hi bh)
* - p2 = (bit 8-13) - rail type or road types.
* - p2 = (bit 15-16) - transport type.
* @param cmd unused
* @param end_tile End tile of the bridge.
* @param tile_start start tile
* @param transport_type transport type.
*/
void CcBuildBridge(const CommandCost &result, TileIndex end_tile, uint32 p1, uint32 p2, uint32 cmd)
void CcBuildBridge(Commands cmd, const CommandCost &result, TileIndex end_tile, TileIndex tile_start, TransportType transport_type, BridgeType, byte)
{
if (result.Failed()) return;
if (_settings_client.sound.confirm) SndPlayTileFx(SND_27_CONSTRUCTION_BRIDGE, end_tile);
TransportType transport_type = Extract<TransportType, 15, 2>(p2);
if (transport_type == TRANSPORT_ROAD) {
DiagDirection end_direction = ReverseDiagDir(GetTunnelBridgeDirection(end_tile));
ConnectRoadToStructure(end_tile, end_direction);
DiagDirection start_direction = ReverseDiagDir(GetTunnelBridgeDirection(p1));
ConnectRoadToStructure(p1, start_direction);
DiagDirection start_direction = ReverseDiagDir(GetTunnelBridgeDirection(tile_start));
ConnectRoadToStructure(tile_start, start_direction);
}
}
@@ -88,7 +83,8 @@ private:
/* Internal variables */
TileIndex start_tile;
TileIndex end_tile;
uint32 type;
TransportType transport_type;
byte road_rail_type;
GUIBridgeList *bridges;
int bridgetext_offset; ///< Horizontal offset of the text describing the bridge properties in #WID_BBS_BRIDGE_LIST relative to the left edge.
Scrollbar *vscroll;
@@ -113,13 +109,13 @@ private:
void BuildBridge(uint8 i)
{
switch ((TransportType)(this->type >> 15)) {
switch (this->transport_type) {
case TRANSPORT_RAIL: _last_railbridge_type = this->bridges->at(i).index; break;
case TRANSPORT_ROAD: _last_roadbridge_type = this->bridges->at(i).index; break;
default: break;
}
DoCommandP(this->end_tile, this->start_tile, this->type | this->bridges->at(i).index,
CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), CcBuildBridge);
Command<CMD_BUILD_BRIDGE>::Post(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE, CcBuildBridge,
this->end_tile, this->start_tile, this->transport_type, this->bridges->at(i).index, this->road_rail_type);
}
/** Sort the builable bridges */
@@ -136,19 +132,20 @@ private:
}
public:
BuildBridgeWindow(WindowDesc *desc, TileIndex start, TileIndex end, uint32 br_type, GUIBridgeList *bl) : Window(desc),
BuildBridgeWindow(WindowDesc *desc, TileIndex start, TileIndex end, TransportType transport_type, byte road_rail_type, GUIBridgeList *bl) : Window(desc),
start_tile(start),
end_tile(end),
type(br_type),
transport_type(transport_type),
road_rail_type(road_rail_type),
bridges(bl)
{
this->CreateNestedTree();
this->vscroll = this->GetScrollbar(WID_BBS_SCROLLBAR);
/* Change the data, or the caption of the gui. Set it to road or rail, accordingly. */
this->GetWidget<NWidgetCore>(WID_BBS_CAPTION)->widget_data = (GB(this->type, 15, 2) == TRANSPORT_ROAD) ? STR_SELECT_ROAD_BRIDGE_CAPTION : STR_SELECT_RAIL_BRIDGE_CAPTION;
this->FinishInitNested(GB(br_type, 15, 2)); // Initializes 'this->bridgetext_offset'.
this->GetWidget<NWidgetCore>(WID_BBS_CAPTION)->widget_data = (transport_type == TRANSPORT_ROAD) ? STR_SELECT_ROAD_BRIDGE_CAPTION : STR_SELECT_RAIL_BRIDGE_CAPTION;
this->FinishInitNested(transport_type); // Initializes 'this->bridgetext_offset'.
this->parent = FindWindowById(WC_BUILD_TOOLBAR, GB(this->type, 15, 2));
this->parent = FindWindowById(WC_BUILD_TOOLBAR, transport_type);
this->bridges->SetListing(this->last_sorting);
this->bridges->SetSortFuncs(this->sorter_funcs);
this->bridges->NeedResort();
@@ -198,11 +195,11 @@ public:
}
sprite_dim.height++; // Sprite is rendered one pixel down in the matrix field.
text_dim.height++; // Allowing the bottom row pixels to be rendered on the edge of the matrix field.
resize->height = std::max(sprite_dim.height, text_dim.height) + 2; // Max of both sizes + account for matrix edges.
resize->height = std::max(sprite_dim.height, text_dim.height) + padding.height; // Max of both sizes + account for matrix edges.
resize->height = GetMinButtonSize(resize->height);
this->bridgetext_offset = WD_MATRIX_LEFT + sprite_dim.width + 1; // Left edge of text, 1 pixel distance from the sprite.
size->width = this->bridgetext_offset + text_dim.width + WD_MATRIX_RIGHT;
this->bridgetext_offset = sprite_dim.width + WidgetDimensions::scaled.hsep_normal; // Left edge of text, 1 pixel distance from the sprite.
size->width = this->bridgetext_offset + text_dim.width + padding.width;
size->height = 4 * resize->height; // Smallest bridge gui is 4 entries high in the matrix.
break;
}
@@ -227,7 +224,7 @@ public:
break;
case WID_BBS_BRIDGE_LIST: {
uint y = r.top;
Rect tr = r.WithHeight(this->resize.step_height).Shrink(WidgetDimensions::scaled.matrix);
for (int i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < (int)this->bridges->size(); i++) {
const BridgeSpec *b = this->bridges->at(i).spec;
@@ -235,11 +232,10 @@ public:
SetDParam(1, b->speed);
SetDParam(0, b->material);
uint y_sprite = Center(y, this->resize.step_height, GetSpriteSize(b->sprite).height);
DrawSprite(b->sprite, b->pal, r.left + WD_MATRIX_LEFT, y_sprite);
DrawStringMultiLine(r.left + this->bridgetext_offset, r.right, y + 2, y + this->resize.step_height,
DrawSprite(b->sprite, b->pal, tr.left, tr.bottom - GetSpriteSize(b->sprite).height);
DrawStringMultiLine(tr.Indent(this->bridgetext_offset, false),
_game_mode == GM_EDITOR ? STR_SELECT_BRIDGE_SCENEDIT_INFO : STR_SELECT_BRIDGE_INFO);
y += this->resize.step_height;
tr = tr.Translate(0, this->resize.step_height);
}
break;
}
@@ -365,12 +361,6 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo
{
CloseWindowByClass(WC_BUILD_BRIDGE);
/* Data type for the bridge.
* Bit 16,15 = transport type,
* 14..8 = road/rail types,
* 7..0 = type of bridge */
uint32 type = (transport_type << 15) | (road_rail_type << 8);
/* The bridge length without ramps. */
const uint bridge_len = GetTunnelBridgeLength(start, end);
@@ -386,14 +376,14 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo
default: break; // water ways and air routes don't have bridge types
}
if (_ctrl_pressed && CheckBridgeAvailability(last_bridge_type, bridge_len).Succeeded()) {
DoCommandP(end, start, type | last_bridge_type, CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), CcBuildBridge);
Command<CMD_BUILD_BRIDGE>::Post(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE, CcBuildBridge, end, start, transport_type, last_bridge_type, road_rail_type);
return;
}
/* only query bridge building possibility once, result is the same for all bridges!
* returns CMD_ERROR on failure, and price on success */
StringID errmsg = INVALID_STRING_ID;
CommandCost ret = DoCommand(end, start, type, CommandFlagsToDCFlags(GetCommandFlags(CMD_BUILD_BRIDGE)) | DC_QUERY_COST, CMD_BUILD_BRIDGE);
CommandCost ret = Command<CMD_BUILD_BRIDGE>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_BRIDGE>()) | DC_QUERY_COST, end, start, transport_type, 0, road_rail_type);
GUIBridgeList *bl = nullptr;
if (ret.Failed()) {
@@ -453,7 +443,7 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo
}
if (bl != nullptr && bl->size() != 0) {
new BuildBridgeWindow(&_build_bridge_desc, start, end, type, bl);
new BuildBridgeWindow(&_build_bridge_desc, start, end, transport_type, road_rail_type, bl);
} else {
delete bl;
SetSelectionTilesDirty();

View File

@@ -82,16 +82,16 @@ struct BuildInfoWindow : public Window
size->height = GetStringHeight(STR_STATION_BUILD_COVERAGE_AREA_TITLE, size->width) * (this->station ? 3 : 1);
/* Increase slightly to have some space around the box. */
size->width += 2 + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
size->height += 6 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
size->width += 2 + WidgetDimensions::scaled.framerect.Horizontal();
size->height += 6 + WidgetDimensions::scaled.framerect.Vertical();
}
void DrawWidget(const Rect &r, int widget) const override
{
/* There is only one widget. */
DrawFrameRect(r.left, r.top, r.right, r.bottom, COLOUR_GREY, FR_NONE);
Rect tr = r.Shrink(WidgetDimensions::scaled.frametext);
int top = r.top + WD_FRAMERECT_TOP + 4;
Money cost = BuildInfoWindow::cost;
StringID msg = STR_MESSAGE_ESTIMATED_COST;
SetDParam(0, cost);
@@ -99,13 +99,13 @@ struct BuildInfoWindow : public Window
msg = STR_MESSAGE_ESTIMATED_INCOME;
SetDParam(0, -cost);
}
top = DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, top, INT32_MAX, msg);
tr.top = DrawStringMultiLine(tr, msg);
if (!this->station) return;
top = DrawStationCoverageAreaText(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, top, sct, _thd.outersize.x / TILE_SIZE / 2, false);
tr.top = DrawStationCoverageAreaText(tr.left, tr.right, tr.top, sct, _thd.outersize.x / TILE_SIZE / 2, false);
if (top - r.top <= GetStringHeight(STR_STATION_BUILD_COVERAGE_AREA_TITLE, r.right - r.left) * 1.5) {
DrawStationCoverageAreaText(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, top, sct, _thd.outersize.x / TILE_SIZE / 2, true);
DrawStationCoverageAreaText(tr.left, tr.right, tr.top, sct, _thd.outersize.x / TILE_SIZE / 2, true);
}
}
};

View File

@@ -30,6 +30,10 @@
#include "cargotype.h"
#include "core/geometry_func.hpp"
#include "autoreplace_func.h"
#include "engine_cmd.h"
#include "train_cmd.h"
#include "vehicle_cmd.h"
#include "zoom_func.h"
#include "widgets/build_vehicle_widget.h"
@@ -44,8 +48,7 @@
*/
uint GetEngineListHeight(VehicleType type)
{
uint size = std::max<uint>(FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM, GetVehicleImageCellSize(type, EIT_PURCHASE).height);
return GetMinButtonSize(size);
return std::max<uint>(FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.matrix.Vertical(), GetVehicleImageCellSize(type, EIT_PURCHASE).height);
}
static const NWidgetPart _nested_build_vehicle_widgets[] = {
@@ -108,9 +111,9 @@ static CargoID _engine_sort_last_cargo_criteria[] = {CF_ANY, CF_ANY, CF_ANY, CF_
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool EngineNumberSorter(const EngineID &a, const EngineID &b)
static bool EngineNumberSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
int r = Engine::Get(a)->list_position - Engine::Get(b)->list_position;
int r = Engine::Get(a.engine_id)->list_position - Engine::Get(b.engine_id)->list_position;
return _engine_sort_direction ? r > 0 : r < 0;
}
@@ -121,10 +124,10 @@ static bool EngineNumberSorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool EngineIntroDateSorter(const EngineID &a, const EngineID &b)
static bool EngineIntroDateSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
const int va = Engine::Get(a)->intro_date;
const int vb = Engine::Get(b)->intro_date;
const int va = Engine::Get(a.engine_id)->intro_date;
const int vb = Engine::Get(b.engine_id)->intro_date;
const int r = va - vb;
/* Use EngineID to sort instead since we want consistent sorting */
@@ -141,19 +144,19 @@ static EngineID _last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE };
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool EngineNameSorter(const EngineID &a, const EngineID &b)
static bool EngineNameSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
static char last_name[2][64] = { "", "" };
if (a != _last_engine[0]) {
_last_engine[0] = a;
SetDParam(0, a);
if (a.engine_id != _last_engine[0]) {
_last_engine[0] = a.engine_id;
SetDParam(0, a.engine_id);
GetString(last_name[0], STR_ENGINE_NAME, lastof(last_name[0]));
}
if (b != _last_engine[1]) {
_last_engine[1] = b;
SetDParam(0, b);
if (b.engine_id != _last_engine[1]) {
_last_engine[1] = b.engine_id;
SetDParam(0, b.engine_id);
GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1]));
}
@@ -170,10 +173,10 @@ static bool EngineNameSorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool EngineReliabilitySorter(const EngineID &a, const EngineID &b)
static bool EngineReliabilitySorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
const int va = Engine::Get(a)->reliability;
const int vb = Engine::Get(b)->reliability;
const int va = Engine::Get(a.engine_id)->reliability;
const int vb = Engine::Get(b.engine_id)->reliability;
const int r = va - vb;
/* Use EngineID to sort instead since we want consistent sorting */
@@ -187,10 +190,10 @@ static bool EngineReliabilitySorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool EngineCostSorter(const EngineID &a, const EngineID &b)
static bool EngineCostSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
Money va = Engine::Get(a)->GetCost();
Money vb = Engine::Get(b)->GetCost();
Money va = Engine::Get(a.engine_id)->GetCost();
Money vb = Engine::Get(b.engine_id)->GetCost();
int r = ClampToI32(va - vb);
/* Use EngineID to sort instead since we want consistent sorting */
@@ -204,10 +207,10 @@ static bool EngineCostSorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool EngineSpeedSorter(const EngineID &a, const EngineID &b)
static bool EngineSpeedSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
int va = Engine::Get(a)->GetDisplayMaxSpeed();
int vb = Engine::Get(b)->GetDisplayMaxSpeed();
int va = Engine::Get(a.engine_id)->GetDisplayMaxSpeed();
int vb = Engine::Get(b.engine_id)->GetDisplayMaxSpeed();
int r = va - vb;
/* Use EngineID to sort instead since we want consistent sorting */
@@ -221,10 +224,10 @@ static bool EngineSpeedSorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool EnginePowerSorter(const EngineID &a, const EngineID &b)
static bool EnginePowerSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
int va = Engine::Get(a)->GetPower();
int vb = Engine::Get(b)->GetPower();
int va = Engine::Get(a.engine_id)->GetPower();
int vb = Engine::Get(b.engine_id)->GetPower();
int r = va - vb;
/* Use EngineID to sort instead since we want consistent sorting */
@@ -238,10 +241,10 @@ static bool EnginePowerSorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool EngineTractiveEffortSorter(const EngineID &a, const EngineID &b)
static bool EngineTractiveEffortSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
int va = Engine::Get(a)->GetDisplayMaxTractiveEffort();
int vb = Engine::Get(b)->GetDisplayMaxTractiveEffort();
int va = Engine::Get(a.engine_id)->GetDisplayMaxTractiveEffort();
int vb = Engine::Get(b.engine_id)->GetDisplayMaxTractiveEffort();
int r = va - vb;
/* Use EngineID to sort instead since we want consistent sorting */
@@ -255,10 +258,10 @@ static bool EngineTractiveEffortSorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool EngineRunningCostSorter(const EngineID &a, const EngineID &b)
static bool EngineRunningCostSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
Money va = Engine::Get(a)->GetRunningCost();
Money vb = Engine::Get(b)->GetRunningCost();
Money va = Engine::Get(a.engine_id)->GetRunningCost();
Money vb = Engine::Get(b.engine_id)->GetRunningCost();
int r = ClampToI32(va - vb);
/* Use EngineID to sort instead since we want consistent sorting */
@@ -272,10 +275,10 @@ static bool EngineRunningCostSorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool EnginePowerVsRunningCostSorter(const EngineID &a, const EngineID &b)
static bool EnginePowerVsRunningCostSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
const Engine *e_a = Engine::Get(a);
const Engine *e_b = Engine::Get(b);
const Engine *e_a = Engine::Get(a.engine_id);
const Engine *e_b = Engine::Get(b.engine_id);
uint p_a = e_a->GetPower();
uint p_b = e_b->GetPower();
Money r_a = e_a->GetRunningCost();
@@ -314,13 +317,13 @@ static bool EnginePowerVsRunningCostSorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool TrainEngineCapacitySorter(const EngineID &a, const EngineID &b)
static bool TrainEngineCapacitySorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
const RailVehicleInfo *rvi_a = RailVehInfo(a);
const RailVehicleInfo *rvi_b = RailVehInfo(b);
const RailVehicleInfo *rvi_a = RailVehInfo(a.engine_id);
const RailVehicleInfo *rvi_b = RailVehInfo(b.engine_id);
int va = GetTotalCapacityOfArticulatedParts(a) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
int vb = GetTotalCapacityOfArticulatedParts(b) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
int va = GetTotalCapacityOfArticulatedParts(a.engine_id) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
int vb = GetTotalCapacityOfArticulatedParts(b.engine_id) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
int r = va - vb;
/* Use EngineID to sort instead since we want consistent sorting */
@@ -334,10 +337,10 @@ static bool TrainEngineCapacitySorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool TrainEnginesThenWagonsSorter(const EngineID &a, const EngineID &b)
static bool TrainEnginesThenWagonsSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
int val_a = (RailVehInfo(a)->railveh_type == RAILVEH_WAGON ? 1 : 0);
int val_b = (RailVehInfo(b)->railveh_type == RAILVEH_WAGON ? 1 : 0);
int val_a = (RailVehInfo(a.engine_id)->railveh_type == RAILVEH_WAGON ? 1 : 0);
int val_b = (RailVehInfo(b.engine_id)->railveh_type == RAILVEH_WAGON ? 1 : 0);
int r = val_a - val_b;
/* Use EngineID to sort instead since we want consistent sorting */
@@ -353,10 +356,10 @@ static bool TrainEnginesThenWagonsSorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool RoadVehEngineCapacitySorter(const EngineID &a, const EngineID &b)
static bool RoadVehEngineCapacitySorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
int va = GetTotalCapacityOfArticulatedParts(a);
int vb = GetTotalCapacityOfArticulatedParts(b);
int va = GetTotalCapacityOfArticulatedParts(a.engine_id);
int vb = GetTotalCapacityOfArticulatedParts(b.engine_id);
int r = va - vb;
/* Use EngineID to sort instead since we want consistent sorting */
@@ -372,10 +375,10 @@ static bool RoadVehEngineCapacitySorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool ShipEngineCapacitySorter(const EngineID &a, const EngineID &b)
static bool ShipEngineCapacitySorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
const Engine *e_a = Engine::Get(a);
const Engine *e_b = Engine::Get(b);
const Engine *e_a = Engine::Get(a.engine_id);
const Engine *e_b = Engine::Get(b.engine_id);
int va = e_a->GetDisplayDefaultCapacity();
int vb = e_b->GetDisplayDefaultCapacity();
@@ -394,10 +397,10 @@ static bool ShipEngineCapacitySorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool AircraftEngineCargoSorter(const EngineID &a, const EngineID &b)
static bool AircraftEngineCargoSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
const Engine *e_a = Engine::Get(a);
const Engine *e_b = Engine::Get(b);
const Engine *e_a = Engine::Get(a.engine_id);
const Engine *e_b = Engine::Get(b.engine_id);
uint16 mail_a, mail_b;
int va = e_a->GetDisplayDefaultCapacity(&mail_a);
@@ -422,10 +425,10 @@ static bool AircraftEngineCargoSorter(const EngineID &a, const EngineID &b)
* @param b second engine to compare
* @return for descending order: returns true if a < b. Vice versa for ascending order
*/
static bool AircraftRangeSorter(const EngineID &a, const EngineID &b)
static bool AircraftRangeSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
{
uint16 r_a = Engine::Get(a)->GetRange();
uint16 r_b = Engine::Get(b)->GetRange();
uint16 r_a = Engine::Get(a.engine_id)->GetRange();
uint16 r_b = Engine::Get(b.engine_id)->GetRange();
int r = r_a - r_b;
@@ -539,14 +542,14 @@ const StringID _engine_sort_listing[][12] = {{
}};
/** Filters vehicles by cargo and engine (in case of rail vehicle). */
static bool CDECL CargoAndEngineFilter(const EngineID *eid, const CargoID cid)
static bool CDECL CargoAndEngineFilter(const GUIEngineListItem *item, const CargoID cid)
{
if (cid == CF_ANY) {
return true;
} else if (cid == CF_ENGINES) {
return Engine::Get(*eid)->GetPower() != 0;
return Engine::Get(item->engine_id)->GetPower() != 0;
} else {
CargoTypes refit_mask = GetUnionOfArticulatedRefitMasks(*eid, true) & _standard_cargo_mask;
CargoTypes refit_mask = GetUnionOfArticulatedRefitMasks(item->engine_id, true) & _standard_cargo_mask;
return (cid == CF_NONE ? refit_mask == 0 : HasBit(refit_mask, cid));
}
}
@@ -593,7 +596,7 @@ static int DrawRailWagonPurchaseInfo(int left, int right, int y, EngineID engine
/* Wagon weight - (including cargo) */
uint weight = e->GetDisplayWeight();
SetDParam(0, weight);
uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(te.cargo)->weight * te.capacity / 16 : 0);
uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(te.cargo)->WeightOfNUnitsInTrain(te.capacity) : 0);
SetDParam(1, cargo_weight + weight);
DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
y += FONT_HEIGHT_NORMAL;
@@ -687,7 +690,7 @@ static int DrawRoadVehPurchaseInfo(int left, int right, int y, EngineID engine_n
/* Road vehicle weight - (including cargo) */
int16 weight = e->GetDisplayWeight();
SetDParam(0, weight);
uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(te.cargo)->weight * te.capacity / 16 : 0);
uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(te.cargo)->WeightOfNUnits(te.capacity) : 0);
SetDParam(1, cargo_weight + weight);
DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
y += FONT_HEIGHT_NORMAL;
@@ -956,9 +959,7 @@ int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number,
/**
* Engine drawing loop
* @param type Type of vehicle (VEH_*)
* @param l The left most location of the list
* @param r The right most location of the list
* @param y The top most location of the list
* @param r The Rect of the list
* @param eng_list What engines to draw
* @param min where to start in the list
* @param max where in the list to end
@@ -966,21 +967,23 @@ int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number,
* @param show_count Whether to show the amount of engines or not
* @param selected_group the group to list the engines of
*/
void DrawEngineList(VehicleType type, int l, int r, int y, const GUIEngineList *eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group)
void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group)
{
static const int sprite_y_offsets[] = { -1, -1, -2, -2 };
/* Obligatory sanity checks! */
assert(max <= eng_list->size());
assert(max <= eng_list.size());
bool rtl = _current_text_dir == TD_RTL;
int step_size = GetEngineListHeight(type);
int sprite_left = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_left;
int sprite_right = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_right;
int sprite_width = sprite_left + sprite_right;
int circle_width = std::max(GetScaledSpriteSize(SPR_CIRCLE_FOLDED).width, GetScaledSpriteSize(SPR_CIRCLE_UNFOLDED).width);
int linecolour = _colour_gradient[COLOUR_ORANGE][4];
int sprite_x = rtl ? r - sprite_right - 1 : l + sprite_left + 1;
int sprite_y_offset = sprite_y_offsets[type] + step_size / 2;
Rect ir = r.WithHeight(step_size).Shrink(WidgetDimensions::scaled.matrix);
int sprite_y_offset = ScaleSpriteTrad(sprite_y_offsets[type]) + ir.Height() / 2;
Dimension replace_icon = {0, 0};
int count_width = 0;
@@ -990,33 +993,51 @@ void DrawEngineList(VehicleType type, int l, int r, int y, const GUIEngineList *
count_width = GetStringBoundingBox(STR_TINY_BLACK_COMA).width;
}
int text_left = l + (rtl ? WD_FRAMERECT_LEFT + replace_icon.width + 8 + count_width : sprite_width + WD_FRAMETEXT_LEFT);
int text_right = r - (rtl ? sprite_width + WD_FRAMETEXT_RIGHT : WD_FRAMERECT_RIGHT + replace_icon.width + 8 + count_width);
int replace_icon_left = rtl ? l + WD_FRAMERECT_LEFT : r - WD_FRAMERECT_RIGHT - replace_icon.width;
int count_left = l;
int count_right = rtl ? text_left : r - WD_FRAMERECT_RIGHT - replace_icon.width - 8;
Rect tr = ir.Indent(circle_width + WidgetDimensions::scaled.hsep_normal + sprite_width + WidgetDimensions::scaled.hsep_wide, rtl); // Name position
Rect cr = tr.Indent(replace_icon.width + WidgetDimensions::scaled.hsep_wide, !rtl).WithWidth(count_width, !rtl); // Count position
Rect rr = tr.WithWidth(replace_icon.width, !rtl); // Replace icon position
if (show_count) tr = tr.Indent(count_width + WidgetDimensions::scaled.hsep_normal + replace_icon.width + WidgetDimensions::scaled.hsep_wide, !rtl);
int normal_text_y_offset = (step_size - FONT_HEIGHT_NORMAL) / 2;
int small_text_y_offset = step_size - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1;
int replace_icon_y_offset = (step_size - replace_icon.height) / 2 - 1;
int normal_text_y_offset = (ir.Height() - FONT_HEIGHT_NORMAL) / 2;
int small_text_y_offset = ir.Height() - FONT_HEIGHT_SMALL;
int replace_icon_y_offset = (ir.Height() - replace_icon.height) / 2;
int y = ir.top;
for (; min < max; min++, y += step_size) {
const EngineID engine = (*eng_list)[min];
const auto &item = eng_list[min];
uint indent = item.indent * WidgetDimensions::scaled.hsep_indent;
bool has_variants = (item.flags & EngineDisplayFlags::HasVariants) != EngineDisplayFlags::None;
bool is_folded = (item.flags & EngineDisplayFlags::IsFolded) != EngineDisplayFlags::None;
bool shaded = (item.flags & EngineDisplayFlags::Shaded) != EngineDisplayFlags::None;
/* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
const uint num_engines = GetGroupNumEngines(_local_company, selected_group, engine);
const uint num_engines = GetGroupNumEngines(_local_company, selected_group, item.engine_id);
const Engine *e = Engine::Get(engine);
const Engine *e = Engine::Get(item.engine_id);
bool hidden = HasBit(e->company_hidden, _local_company);
StringID str = hidden ? STR_HIDDEN_ENGINE_NAME : STR_ENGINE_NAME;
TextColour tc = (engine == selected_id) ? TC_WHITE : (TC_NO_SHADE | (hidden ? TC_GREY : TC_BLACK));
TextColour tc = (item.engine_id == selected_id) ? TC_WHITE : (TC_NO_SHADE | ((hidden | shaded) ? TC_GREY : TC_BLACK));
SetDParam(0, engine);
DrawString(text_left, text_right, y + normal_text_y_offset, str, tc);
DrawVehicleEngine(l, r, sprite_x, y + sprite_y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_company), EIT_PURCHASE);
SetDParam(0, item.engine_id);
Rect itr = tr.Indent(indent, rtl);
DrawString(itr.left, itr.right, y + normal_text_y_offset, str, tc);
int sprite_x = ir.Indent(indent + circle_width + WidgetDimensions::scaled.hsep_normal, rtl).WithWidth(sprite_width, rtl).left + sprite_left;
DrawVehicleEngine(r.left, r.right, sprite_x, y + sprite_y_offset, item.engine_id, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(item.engine_id, _local_company), EIT_PURCHASE);
if (show_count) {
SetDParam(0, num_engines);
DrawString(count_left, count_right, y + small_text_y_offset, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
if (EngineHasReplacementForCompany(Company::Get(_local_company), engine, selected_group)) DrawSprite(SPR_GROUP_REPLACE_ACTIVE, num_engines == 0 ? PALETTE_CRASH : PAL_NONE, replace_icon_left, y + replace_icon_y_offset);
DrawString(cr.left, cr.right, y + small_text_y_offset, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
if (EngineHasReplacementForCompany(Company::Get(_local_company), item.engine_id, selected_group)) DrawSprite(SPR_GROUP_REPLACE_ACTIVE, num_engines == 0 ? PALETTE_CRASH : PAL_NONE, rr.left, y + replace_icon_y_offset);
}
if (has_variants) {
Rect fr = ir.Indent(indent, rtl).WithWidth(circle_width, rtl);
DrawSpriteIgnorePadding(is_folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED, PAL_NONE, {fr.left, y, fr.right, y + ir.Height() - 1}, false, SA_CENTER);
}
if (indent > 0) {
/* Draw tree lines */
Rect fr = ir.Indent(indent - WidgetDimensions::scaled.hsep_indent, rtl).WithWidth(circle_width, rtl);
int ycenter = y + normal_text_y_offset + FONT_HEIGHT_NORMAL / 2;
bool continues = (min + 1U) < eng_list.size() && eng_list[min + 1].indent == item.indent;
GfxDrawLine(fr.left + circle_width / 2, y - WidgetDimensions::scaled.matrix.top, fr.left + circle_width / 2, continues ? y - WidgetDimensions::scaled.matrix.top + step_size - 1 : ycenter, linecolour, WidgetDimensions::scaled.fullbevel.top);
GfxDrawLine(fr.left + circle_width / 2, ycenter, fr.right, ycenter, linecolour, WidgetDimensions::scaled.fullbevel.top);
}
}
}
@@ -1081,11 +1102,32 @@ struct BuildVehicleWindow : Window {
}
}
void AddChildren(const GUIEngineList &source, EngineID parent, int indent)
{
for (const auto &item : source) {
if (item.variant_id != parent || item.engine_id == parent) continue;
const Engine *e = Engine::Get(item.engine_id);
EngineDisplayFlags flags = item.flags;
if (e->display_last_variant != INVALID_ENGINE) flags &= ~EngineDisplayFlags::Shaded;
this->eng_list.emplace_back(e->display_last_variant == INVALID_ENGINE ? item.engine_id : e->display_last_variant, item.engine_id, flags, indent);
/* Add variants if not folded */
if ((item.flags & (EngineDisplayFlags::HasVariants | EngineDisplayFlags::IsFolded)) == EngineDisplayFlags::HasVariants) {
/* Add this engine again as a child */
if ((item.flags & EngineDisplayFlags::Shaded) == EngineDisplayFlags::None) {
this->eng_list.emplace_back(item.engine_id, item.engine_id, EngineDisplayFlags::None, indent + 1);
}
AddChildren(source, item.engine_id, indent + 1);
}
}
}
BuildVehicleWindow(WindowDesc *desc, TileIndex tile, VehicleType type) : Window(desc)
{
this->vehicle_type = type;
this->listview_mode = tile == INVALID_TILE;
this->window_number = this->listview_mode ? (int)type : tile;
this->window_number = this->listview_mode ? (int)type : (int)tile;
this->sel_engine = INVALID_ENGINE;
@@ -1120,7 +1162,7 @@ struct BuildVehicleWindow : Window {
this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9);
this->FinishInitNested(tile == INVALID_TILE ? (int)type : tile);
this->FinishInitNested(tile == INVALID_TILE ? (int)type : (int)tile);
this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company;
@@ -1128,7 +1170,7 @@ struct BuildVehicleWindow : Window {
this->GenerateBuildList(); // generate the list, since we need it in the next line
/* Select the first engine in the list as default when opening the window */
if (this->eng_list.size() > 0) {
this->SelectEngine(this->eng_list[0]);
this->SelectEngine(this->eng_list[0].engine_id);
} else {
this->SelectEngine(INVALID_ENGINE);
}
@@ -1232,11 +1274,11 @@ struct BuildVehicleWindow : Window {
if (!this->listview_mode) {
/* Query for cost and refitted capacity */
CommandCost ret = DoCommand(this->window_number, this->sel_engine | (cargo << 24), 0, DC_QUERY_COST, GetCmdBuildVeh(this->vehicle_type));
auto [ret, veh_id, refit_capacity, refit_mail] = Command<CMD_BUILD_VEHICLE>::Do(DC_QUERY_COST, this->window_number, this->sel_engine, true, cargo, INVALID_CLIENT_ID);
if (ret.Succeeded()) {
this->te.cost = ret.GetCost() - e->GetCost();
this->te.capacity = _returned_refit_capacity;
this->te.mail_capacity = _returned_mail_refit_capacity;
this->te.capacity = refit_capacity;
this->te.mail_capacity = refit_mail;
this->te.cargo = (cargo == CT_INVALID) ? e->GetDefaultCargoType() : cargo;
return;
}
@@ -1260,7 +1302,7 @@ struct BuildVehicleWindow : Window {
if (0 == this->eng_list.size()) { // no engine passed through the filter, invalidate the previously selected engine
this->SelectEngine(INVALID_ENGINE);
} else if (std::find(this->eng_list.begin(), this->eng_list.end(), this->sel_engine) == this->eng_list.end()) { // previously selected engine didn't pass the filter, select the first engine of the list
this->SelectEngine(this->eng_list[0]);
this->SelectEngine(this->eng_list[0].engine_id);
}
}
@@ -1268,17 +1310,19 @@ struct BuildVehicleWindow : Window {
bool FilterSingleEngine(EngineID eid)
{
CargoID filter_type = this->cargo_filter[this->cargo_filter_criteria];
return CargoAndEngineFilter(&eid, filter_type);
GUIEngineListItem item = {eid, eid, EngineDisplayFlags::None, 0};
return CargoAndEngineFilter(&item, filter_type);
}
/* Figure out what train EngineIDs to put in the list */
void GenerateBuildTrainList()
void GenerateBuildTrainList(GUIEngineList &list)
{
std::vector<EngineID> variants;
EngineID sel_id = INVALID_ENGINE;
int num_engines = 0;
int num_wagons = 0;
this->eng_list.clear();
list.clear();
/* Make list of all available train engines and wagons.
* Also check to see if the previously selected engine is still available,
@@ -1295,7 +1339,7 @@ struct BuildVehicleWindow : Window {
/* Filter now! So num_engines and num_wagons is valid */
if (!FilterSingleEngine(eid)) continue;
this->eng_list.push_back(eid);
list.emplace_back(eid, e->info.variant_id, e->display_flags, 0);
if (rvi->railveh_type != RAILVEH_WAGON) {
num_engines++;
@@ -1303,9 +1347,18 @@ struct BuildVehicleWindow : Window {
num_wagons++;
}
if (e->info.variant_id != eid && e->info.variant_id != INVALID_ENGINE) variants.push_back(e->info.variant_id);
if (eid == this->sel_engine) sel_id = eid;
}
/* ensure primary engine of variant group is in list */
for (const auto &variant : variants) {
if (std::find(list.begin(), list.end(), variant) == list.end()) {
const Engine *e = Engine::Get(variant);
list.emplace_back(variant, e->info.variant_id, e->display_flags | EngineDisplayFlags::Shaded, 0);
}
}
this->SelectEngine(sel_id);
/* invalidate cached values for name sorter - engine names could change */
@@ -1313,14 +1366,14 @@ struct BuildVehicleWindow : Window {
/* make engines first, and then wagons, sorted by selected sort_criteria */
_engine_sort_direction = false;
EngList_Sort(&this->eng_list, TrainEnginesThenWagonsSorter);
EngList_Sort(&list, TrainEnginesThenWagonsSorter);
/* and then sort engines */
_engine_sort_direction = this->descending_sort_order;
EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], 0, num_engines);
EngList_SortPartial(&list, _engine_sort_functions[0][this->sort_criteria], 0, num_engines);
/* and finally sort wagons */
EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], num_engines, num_wagons);
EngList_SortPartial(&list, _engine_sort_functions[0][this->sort_criteria], num_engines, num_wagons);
}
/* Figure out what road vehicle EngineIDs to put in the list */
@@ -1336,7 +1389,7 @@ struct BuildVehicleWindow : Window {
if (!IsEngineBuildable(eid, VEH_ROAD, _local_company)) continue;
if (this->filter.roadtype != INVALID_ROADTYPE && !HasPowerOnRoad(e->u.road.roadtype, this->filter.roadtype)) continue;
this->eng_list.push_back(eid);
this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0);
if (eid == this->sel_engine) sel_id = eid;
}
@@ -1353,7 +1406,7 @@ struct BuildVehicleWindow : Window {
if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
EngineID eid = e->index;
if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue;
this->eng_list.push_back(eid);
this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0);
if (eid == this->sel_engine) sel_id = eid;
}
@@ -1380,7 +1433,7 @@ struct BuildVehicleWindow : Window {
/* First VEH_END window_numbers are fake to allow a window open for all different types at once */
if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue;
this->eng_list.push_back(eid);
this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0);
if (eid == this->sel_engine) sel_id = eid;
}
@@ -1395,13 +1448,18 @@ struct BuildVehicleWindow : Window {
/* Update filter type in case the road/railtype of the depot got converted */
this->UpdateFilterByTile();
this->eng_list.clear();
GUIEngineList list;
switch (this->vehicle_type) {
default: NOT_REACHED();
case VEH_TRAIN:
this->GenerateBuildTrainList();
this->GenerateBuildTrainList(list);
AddChildren(list, INVALID_ENGINE, 0);
this->eng_list.shrink_to_fit();
this->eng_list.RebuildDone();
return; // trains should not reach the last sorting
return;
case VEH_ROAD:
this->GenerateBuildRoadVehList();
break;
@@ -1415,9 +1473,23 @@ struct BuildVehicleWindow : Window {
this->FilterEngineList();
/* ensure primary engine of variant group is in list after filtering */
std::vector<EngineID> variants;
for (const auto &item : this->eng_list) {
if (item.engine_id != item.variant_id && item.variant_id != INVALID_ENGINE) variants.push_back(item.variant_id);
}
for (const auto &variant : variants) {
if (std::find(this->eng_list.begin(), this->eng_list.end(), variant) == this->eng_list.end()) {
const Engine *e = Engine::Get(variant);
this->eng_list.emplace_back(variant, e->info.variant_id, e->display_flags | EngineDisplayFlags::Shaded, 0);
}
}
_engine_sort_direction = this->descending_sort_order;
EngList_Sort(&this->eng_list, _engine_sort_functions[this->vehicle_type][this->sort_criteria]);
this->eng_list.swap(list);
AddChildren(list, INVALID_ENGINE, 0);
this->eng_list.shrink_to_fit();
this->eng_list.RebuildDone();
}
@@ -1443,7 +1515,23 @@ struct BuildVehicleWindow : Window {
case WID_BV_LIST: {
uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST);
size_t num_items = this->eng_list.size();
this->SelectEngine((i < num_items) ? this->eng_list[i] : INVALID_ENGINE);
EngineID e = INVALID_ENGINE;
if (i < num_items) {
const auto &item = this->eng_list[i];
const Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.matrix).WithWidth(WidgetDimensions::scaled.hsep_indent * (item.indent + 1), _current_text_dir == TD_RTL);
if ((item.flags & EngineDisplayFlags::HasVariants) != EngineDisplayFlags::None && IsInsideMM(r.left, r.right, pt.x)) {
/* toggle folded flag on engine */
assert(item.variant_id != INVALID_ENGINE);
Engine *engine = Engine::Get(item.variant_id);
engine->display_flags ^= EngineDisplayFlags::IsFolded;
InvalidateWindowData(WC_REPLACE_VEHICLE, this->vehicle_type, 0); // Update the autoreplace window
InvalidateWindowClassesData(WC_BUILD_VEHICLE); // The build windows needs updating as well
return;
}
if ((item.flags & EngineDisplayFlags::Shaded) == EngineDisplayFlags::None) e = item.engine_id;
}
this->SelectEngine(e);
this->SetDirty();
if (_ctrl_pressed) {
this->OnClick(pt, WID_BV_SHOW_HIDE, 1);
@@ -1464,7 +1552,7 @@ struct BuildVehicleWindow : Window {
case WID_BV_SHOW_HIDE: {
const Engine *e = (this->sel_engine == INVALID_ENGINE) ? nullptr : Engine::Get(this->sel_engine);
if (e != nullptr) {
DoCommandP(0, 0, this->sel_engine | (e->IsHidden(_current_company) ? 0 : (1u << 31)), CMD_SET_VEHICLE_VISIBILITY);
Command<CMD_SET_VEHICLE_VISIBILITY>::Post(this->sel_engine, !e->IsHidden(_current_company));
}
break;
}
@@ -1472,10 +1560,27 @@ struct BuildVehicleWindow : Window {
case WID_BV_BUILD: {
EngineID sel_eng = this->sel_engine;
if (sel_eng != INVALID_ENGINE) {
CommandCallback *callback = (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) ? CcBuildWagon : CcBuildPrimaryVehicle;
CargoID cargo = this->cargo_filter[this->cargo_filter_criteria];
if (cargo == CF_ANY || cargo == CF_ENGINES) cargo = CF_NONE;
DoCommandP(this->window_number, sel_eng | (cargo << 24), 0, GetCmdBuildVeh(this->vehicle_type), callback);
if (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) {
Command<CMD_BUILD_VEHICLE>::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildWagon, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID);
} else {
Command<CMD_BUILD_VEHICLE>::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildPrimaryVehicle, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID);
}
/* Update last used variant and refresh if necessary. */
bool refresh = false;
int recursion = 10; /* In case of infinite loop */
for (Engine *e = Engine::Get(sel_eng); recursion > 0; e = Engine::Get(e->info.variant_id), --recursion) {
refresh |= (e->display_last_variant != sel_eng);
e->display_last_variant = sel_eng;
if (e->info.variant_id == INVALID_ENGINE) break;
}
if (refresh) {
InvalidateWindowData(WC_REPLACE_VEHICLE, this->vehicle_type, 0); // Update the autoreplace window
InvalidateWindowClassesData(WC_BUILD_VEHICLE); // The build windows needs updating as well
return;
}
}
break;
}
@@ -1551,7 +1656,7 @@ struct BuildVehicleWindow : Window {
case WID_BV_LIST:
resize->height = GetEngineListHeight(this->vehicle_type);
size->height = 3 * resize->height;
size->width = std::max(size->width, GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_left + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_right + 165);
size->width = std::max(size->width, GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_left + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_right + 165) + padding.width;
break;
case WID_BV_PANEL:
@@ -1589,10 +1694,8 @@ struct BuildVehicleWindow : Window {
case WID_BV_LIST:
DrawEngineList(
this->vehicle_type,
r.left + WD_FRAMERECT_LEFT,
r.right - WD_FRAMERECT_RIGHT,
r.top + WD_FRAMERECT_TOP,
&this->eng_list,
r,
this->eng_list,
this->vscroll->GetPosition(),
static_cast<uint16>(std::min<size_t>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->eng_list.size())),
this->sel_engine,
@@ -1623,10 +1726,9 @@ struct BuildVehicleWindow : Window {
int needed_height = this->details_height;
/* Draw details panels. */
if (this->sel_engine != INVALID_ENGINE) {
NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_BV_PANEL);
int text_end = DrawVehiclePurchaseInfo(nwi->pos_x + WD_FRAMETEXT_LEFT, nwi->pos_x + nwi->current_x - WD_FRAMETEXT_RIGHT,
nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine, this->te);
needed_height = std::max(needed_height, (text_end - (int)nwi->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL);
const Rect r = this->GetWidget<NWidgetBase>(WID_BV_PANEL)->GetCurrentRect().Shrink(WidgetDimensions::scaled.frametext, WidgetDimensions::scaled.framerect);
int text_end = DrawVehiclePurchaseInfo(r.left, r.right, r.top, this->sel_engine, this->te);
needed_height = std::max(needed_height, (text_end - r.top) / FONT_HEIGHT_NORMAL);
}
if (needed_height != this->details_height) { // Details window are not high enough, enlarge them.
int resize = needed_height - this->details_height;
@@ -1641,7 +1743,7 @@ struct BuildVehicleWindow : Window {
{
if (str == nullptr) return;
DoCommandP(0, this->rename_engine, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), nullptr, str);
Command<CMD_RENAME_ENGINE>::Post(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type, this->rename_engine, str);
}
void OnDropdownSelect(int widget, int index) override
@@ -1688,7 +1790,7 @@ void ShowBuildVehicleWindow(TileIndex tile, VehicleType type)
* so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number.
* As it always is a low value, it won't collide with any real tile
* number. */
uint num = (tile == INVALID_TILE) ? (int)type : tile;
uint num = (tile == INVALID_TILE) ? (int)type : (int)tile;
assert(IsCompanyBuildableVehicleType(type));

View File

@@ -149,9 +149,9 @@ void AddCargoDelivery(CargoID cargo_type, CompanyID company, uint32 amount, Sour
if (iter != _cargo_deliveries.end()) iter->second += amount;
/* Industry delivery. */
for (Industry *ind : st->industries_near) {
if (ind->index != dest) continue;
CargoMonitorID num = EncodeCargoIndustryMonitor(company, cargo_type, ind->index);
for (const auto &i : st->industries_near) {
if (i.industry->index != dest) continue;
CargoMonitorID num = EncodeCargoIndustryMonitor(company, cargo_type, i.industry->index);
CargoMonitorMap::iterator iter = _cargo_deliveries.find(num);
if (iter != _cargo_deliveries.end()) iter->second += amount;
}

View File

@@ -75,7 +75,7 @@ CargoPacket::CargoPacket(uint16 count, byte days_in_transit, StationID source, T
source_id(source_id),
source(source),
source_xy(source_xy),
loaded_at_xy(loaded_at_xy)
loaded_at_xy(loaded_at_xy.value)
{
assert(count != 0);
this->source_type = source_type;

View File

@@ -12,6 +12,7 @@
#include "newgrf_cargo.h"
#include "string_func.h"
#include "strings_func.h"
#include "settings_type.h"
#include "table/sprites.h"
#include "table/strings.h"
@@ -209,3 +210,8 @@ void InitializeSortedCargoSpecs()
_sorted_standard_cargo_specs = { _sorted_cargo_specs.data(), nb_standard_cargo };
}
uint64 CargoSpec::WeightOfNUnitsInTrain(uint32 n) const
{
if (this->is_freight) n *= _settings_game.vehicle.freight_trains;
return this->WeightOfNUnits(n);
}

View File

@@ -23,7 +23,7 @@
typedef uint32 CargoLabel;
/** Town growth effect when delivering cargo. */
enum TownEffect {
enum TownEffect : byte {
TE_BEGIN = 0,
TE_NONE = TE_BEGIN, ///< Cargo has no effect.
TE_PASSENGERS, ///< Cargo behaves passenger-like.
@@ -66,7 +66,6 @@ struct CargoSpec {
bool is_freight; ///< Cargo type is considered to be freight (affects train freight multiplier).
TownEffect town_effect; ///< The effect that delivering this cargo type has on towns. Also affects destination of subsidies.
uint16 multipliertowngrowth; ///< Size of the effect.
uint8 callback_mask; ///< Bitmask of cargo callbacks that have to be called
StringID name; ///< Name of this type of cargo.
@@ -124,6 +123,13 @@ struct CargoSpec {
SpriteID GetCargoIcon() const;
inline uint64 WeightOfNUnits(uint32 n) const
{
return n * this->weight / 16u;
}
uint64 WeightOfNUnitsInTrain(uint32 n) const;
/**
* Iterator to iterate all valid CargoSpec
*/

View File

@@ -28,6 +28,8 @@
#include "tile_map.h"
#include "newgrf.h"
#include "error.h"
#include "misc_cmd.h"
#include "core/geometry_func.hpp"
#include "widgets/cheat_widget.h"
@@ -54,7 +56,7 @@ static int32 _money_cheat_amount = 10000000;
*/
static int32 ClickMoneyCheat(int32 p1, int32 p2)
{
DoCommandP(0, (uint32)(p2 * _money_cheat_amount), 0, CMD_MONEY_CHEAT);
Command<CMD_MONEY_CHEAT>::Post(p2 * _money_cheat_amount);
return _money_cheat_amount;
}
@@ -206,7 +208,7 @@ static const NWidgetPart _nested_cheat_widgets[] = {
EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_C_PANEL), SetDataTip(0x0, STR_CHEATS_TOOLTIP), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY),
NWidget(WWT_LABEL, COLOUR_GREY, WID_C_NOTE), SetFill(1, 1), SetDataTip(STR_CHEATS_NOTE, STR_NULL), SetPadding(WD_PAR_VSEP_NORMAL, 4, WD_PAR_VSEP_NORMAL, 4),
NWidget(WWT_LABEL, COLOUR_GREY, WID_C_NOTE), SetFill(1, 1), SetDataTip(STR_CHEATS_NOTE, STR_NULL), SetPadding(WidgetDimensions::unscaled.frametext),
EndContainer(),
};
@@ -215,39 +217,48 @@ struct CheatWindow : Window {
int clicked;
int clicked_widget;
uint line_height;
int box_width;
Dimension box; ///< Dimension of box sprite
Dimension icon; ///< Dimension of company icon sprite
CheatWindow(WindowDesc *desc) : Window(desc)
{
this->box_width = GetSpriteSize(SPR_BOX_EMPTY).width;
this->InitNested();
}
void OnInit() override
{
this->box = maxdim(GetSpriteSize(SPR_BOX_EMPTY), GetSpriteSize(SPR_BOX_CHECKED));
this->icon = GetSpriteSize(SPR_COMPANY_ICON);
}
void DrawWidget(const Rect &r, int widget) const override
{
if (widget != WID_C_PANEL) return;
int y = r.top + WD_FRAMERECT_TOP + WD_PAR_VSEP_NORMAL;
const Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
int y = ir.top;
bool rtl = _current_text_dir == TD_RTL;
uint box_left = rtl ? r.right - this->box_width - 5 : r.left + 5;
uint button_left = rtl ? r.right - this->box_width - 10 - SETTING_BUTTON_WIDTH : r.left + this->box_width + 10;
uint text_left = r.left + (rtl ? WD_FRAMERECT_LEFT : 20 + this->box_width + SETTING_BUTTON_WIDTH);
uint text_right = r.right - (rtl ? 20 + this->box_width + SETTING_BUTTON_WIDTH : WD_FRAMERECT_RIGHT);
uint box_left = rtl ? ir.right - this->box.width - WidgetDimensions::scaled.hsep_wide : ir.left + WidgetDimensions::scaled.hsep_wide;
uint button_left = rtl ? ir.right - this->box.width - WidgetDimensions::scaled.hsep_wide * 2 - SETTING_BUTTON_WIDTH : ir.left + this->box.width + WidgetDimensions::scaled.hsep_wide * 2;
uint text_left = ir.left + (rtl ? 0 : WidgetDimensions::scaled.hsep_wide * 4 + this->box.width + SETTING_BUTTON_WIDTH);
uint text_right = ir.right - (rtl ? WidgetDimensions::scaled.hsep_wide * 4 + this->box.width + SETTING_BUTTON_WIDTH : 0);
int text_y_offset = (this->line_height - FONT_HEIGHT_NORMAL) / 2;
int icon_y_offset = (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
int box_y_offset = (this->line_height - this->box.height) / 2;
int button_y_offset = (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
int icon_y_offset = (this->line_height - this->icon.height) / 2;
for (int i = 0; i != lengthof(_cheats_ui); i++) {
const CheatEntry *ce = &_cheats_ui[i];
DrawSprite((*ce->been_used) ? SPR_BOX_CHECKED : SPR_BOX_EMPTY, PAL_NONE, box_left, y + icon_y_offset + 2);
DrawSprite((*ce->been_used) ? SPR_BOX_CHECKED : SPR_BOX_EMPTY, PAL_NONE, box_left, y + box_y_offset);
switch (ce->type) {
case SLE_BOOL: {
bool on = (*(bool*)ce->variable);
DrawBoolButton(button_left, y + icon_y_offset, on, true);
DrawBoolButton(button_left, y + button_y_offset, on, true);
SetDParam(0, on ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF);
break;
}
@@ -257,7 +268,7 @@ struct CheatWindow : Window {
char buf[512];
/* Draw [<][>] boxes for settings of an integer-type */
DrawArrowButtons(button_left, y + icon_y_offset, COLOUR_YELLOW, clicked - (i * 2), true, true);
DrawArrowButtons(button_left, y + button_y_offset, COLOUR_YELLOW, clicked - (i * 2), true, true);
switch (ce->str) {
/* Display date for change date cheat */
@@ -267,8 +278,8 @@ struct CheatWindow : Window {
case STR_CHEAT_CHANGE_COMPANY: {
SetDParam(0, val + 1);
GetString(buf, STR_CHEAT_CHANGE_COMPANY, lastof(buf));
uint offset = 10 + GetStringBoundingBox(buf).width;
DrawCompanyIcon(_local_company, rtl ? text_right - offset - 10 : text_left + offset, y + icon_y_offset + 2);
uint offset = WidgetDimensions::scaled.hsep_indent + GetStringBoundingBox(buf).width;
DrawCompanyIcon(_local_company, rtl ? text_right - offset - WidgetDimensions::scaled.hsep_indent : text_left + offset, y + icon_y_offset);
break;
}
@@ -310,7 +321,7 @@ struct CheatWindow : Window {
/* Draw coloured flag for change company cheat */
case STR_CHEAT_CHANGE_COMPANY:
SetDParamMaxValue(0, MAX_COMPANIES);
width = std::max(width, GetStringBoundingBox(ce->str).width + 10 + 10);
width = std::max(width, GetStringBoundingBox(ce->str).width + WidgetDimensions::scaled.hsep_wide * 4);
break;
default:
@@ -322,21 +333,21 @@ struct CheatWindow : Window {
}
}
this->line_height = std::max(GetSpriteSize(SPR_BOX_CHECKED).height, GetSpriteSize(SPR_BOX_EMPTY).height);
this->line_height = std::max(this->box.height, this->icon.height);
this->line_height = std::max<uint>(this->line_height, SETTING_BUTTON_HEIGHT);
this->line_height = std::max<uint>(this->line_height, FONT_HEIGHT_NORMAL) + WD_PAR_VSEP_NORMAL;
this->line_height = std::max<uint>(this->line_height, FONT_HEIGHT_NORMAL) + WidgetDimensions::scaled.framerect.Vertical();
size->width = width + 20 + this->box_width + SETTING_BUTTON_WIDTH /* stuff on the left */ + 10 /* extra spacing on right */;
size->height = WD_FRAMERECT_TOP + WD_PAR_VSEP_NORMAL + WD_FRAMERECT_BOTTOM + this->line_height * lengthof(_cheats_ui);
size->width = width + WidgetDimensions::scaled.hsep_wide * 4 + this->box.width + SETTING_BUTTON_WIDTH /* stuff on the left */ + WidgetDimensions::scaled.hsep_wide * 2 /* extra spacing on right */;
size->height = WidgetDimensions::scaled.framerect.Vertical() + this->line_height * lengthof(_cheats_ui);
}
void OnClick(Point pt, int widget, int click_count) override
{
const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_C_PANEL);
uint btn = (pt.y - wid->pos_y - WD_FRAMERECT_TOP - WD_PAR_VSEP_NORMAL) / this->line_height;
int x = pt.x - wid->pos_x;
Rect r = this->GetWidget<NWidgetBase>(WID_C_PANEL)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect);
uint btn = (pt.y - r.top) / this->line_height;
uint x = pt.x - r.left;
bool rtl = _current_text_dir == TD_RTL;
if (rtl) x = wid->current_x - x;
if (rtl) x = r.Width() - 1 - x;
if (btn >= lengthof(_cheats_ui)) return;
@@ -344,13 +355,13 @@ struct CheatWindow : Window {
int value = (int32)ReadValue(ce->variable, ce->type);
int oldvalue = value;
if (btn == CHT_CHANGE_DATE && x >= 20 + this->box_width + SETTING_BUTTON_WIDTH) {
if (btn == CHT_CHANGE_DATE && x >= WidgetDimensions::scaled.hsep_wide * 2 + this->box.width + SETTING_BUTTON_WIDTH) {
/* Click at the date text directly. */
clicked_widget = CHT_CHANGE_DATE;
SetDParam(0, value);
ShowQueryString(STR_JUST_INT, STR_CHEAT_CHANGE_DATE_QUERY_CAPT, 8, this, CS_NUMERAL, QSF_ACCEPT_UNCHANGED);
return;
} else if (btn == CHT_EDIT_MAX_HL && x >= 20 + this->box_width + SETTING_BUTTON_WIDTH) {
} else if (btn == CHT_EDIT_MAX_HL && x >= WidgetDimensions::scaled.hsep_wide * 2 + this->box.width + SETTING_BUTTON_WIDTH) {
clicked_widget = CHT_EDIT_MAX_HL;
SetDParam(0, value);
ShowQueryString(STR_JUST_INT, STR_CHEAT_EDIT_MAX_HL_QUERY_CAPT, 8, this, CS_NUMERAL, QSF_ACCEPT_UNCHANGED);
@@ -358,7 +369,7 @@ struct CheatWindow : Window {
}
/* Not clicking a button? */
if (!IsInsideMM(x, 10 + this->box_width, 10 + this->box_width + SETTING_BUTTON_WIDTH)) return;
if (!IsInsideMM(x, WidgetDimensions::scaled.hsep_wide * 2 + this->box.width, WidgetDimensions::scaled.hsep_wide * 2 + this->box.width + SETTING_BUTTON_WIDTH)) return;
*ce->been_used = true;
@@ -370,10 +381,10 @@ struct CheatWindow : Window {
default:
/* Take whatever the function returns */
value = ce->proc(value + ((x >= 10 + this->box_width + SETTING_BUTTON_WIDTH / 2) ? 1 : -1), (x >= 10 + this->box_width + SETTING_BUTTON_WIDTH / 2) ? 1 : -1);
value = ce->proc(value + ((x >= WidgetDimensions::scaled.hsep_wide * 2 + this->box.width + SETTING_BUTTON_WIDTH / 2) ? 1 : -1), (x >= WidgetDimensions::scaled.hsep_wide * 2 + this->box.width + SETTING_BUTTON_WIDTH / 2) ? 1 : -1);
/* The first cheat (money), doesn't return a different value. */
if (value != oldvalue || btn == CHT_MONEY) this->clicked = btn * 2 + 1 + ((x >= 10 + this->box_width + SETTING_BUTTON_WIDTH / 2) != rtl ? 1 : 0);
if (value != oldvalue || btn == CHT_MONEY) this->clicked = btn * 2 + 1 + ((x >= WidgetDimensions::scaled.hsep_wide * 2 + this->box.width + SETTING_BUTTON_WIDTH / 2) != rtl ? 1 : 0);
break;
}

View File

@@ -16,6 +16,7 @@
#include "water.h"
#include "core/random_func.hpp"
#include "newgrf_generic.h"
#include "landscape_cmd.h"
#include "table/strings.h"
#include "table/sprites.h"
@@ -381,7 +382,7 @@ static void ChangeTileOwner_Clear(TileIndex tile, Owner old_owner, Owner new_own
static CommandCost TerraformTile_Clear(TileIndex tile, DoCommandFlag flags, int z_new, Slope tileh_new)
{
return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
extern const TileTypeProcs _tile_type_clear_procs = {

View File

@@ -1,34 +0,0 @@
/*
* 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 cmd_helper.h Helper functions to extract data from command parameters. */
#ifndef CMD_HELPER_H
#define CMD_HELPER_H
#include "core/enum_type.hpp"
#include "core/bitmath_func.hpp"
/**
* Extracts a given type from a value.
* @tparam T The type of data we're looking for.
* @tparam S The offset in the data.
* @tparam N The amount of bits to read.
* @tparam U The type of data passed to us.
* @param v The data to extract the value from.
*/
template<typename T, uint S, uint N, typename U> static inline T Extract(U v)
{
/* Check if there are enough bits in v */
static_assert(N == EnumPropsT<T>::num_bits);
static_assert(S + N <= sizeof(U) * 8);
static_assert(EnumPropsT<T>::end <= (1 << N));
U masked = GB(v, S, N);
return IsInsideMM(masked, EnumPropsT<T>::begin, EnumPropsT<T>::end) ? (T)masked : EnumPropsT<T>::invalid;
}
#endif /* CMD_HELPER_H */

View File

@@ -24,6 +24,39 @@
#include "signal_func.h"
#include "core/backup_type.hpp"
#include "object_base.h"
#include "autoreplace_cmd.h"
#include "company_cmd.h"
#include "depot_cmd.h"
#include "economy_cmd.h"
#include "engine_cmd.h"
#include "goal_cmd.h"
#include "group_cmd.h"
#include "industry_cmd.h"
#include "league_cmd.h"
#include "landscape_cmd.h"
#include "misc_cmd.h"
#include "news_cmd.h"
#include "object_cmd.h"
#include "order_cmd.h"
#include "rail_cmd.h"
#include "road_cmd.h"
#include "roadveh_cmd.h"
#include "settings_cmd.h"
#include "signs_cmd.h"
#include "station_cmd.h"
#include "story_cmd.h"
#include "subsidy_cmd.h"
#include "terraform_cmd.h"
#include "timetable_cmd.h"
#include "town_cmd.h"
#include "train_cmd.h"
#include "tree_cmd.h"
#include "tunnelbridge_cmd.h"
#include "vehicle_cmd.h"
#include "viewport_cmd.h"
#include "water_cmd.h"
#include "waypoint_cmd.h"
#include "misc/endian_buffer.hpp"
#include "string_func.h"
#include "tilehighlight_func.h"
#include "build_confirmation_func.h"
@@ -32,182 +65,29 @@
#include "safeguards.h"
CommandProc CmdBuildRailroadTrack;
CommandProc CmdRemoveRailroadTrack;
CommandProc CmdBuildSingleRail;
CommandProc CmdRemoveSingleRail;
CommandProc CmdLandscapeClear;
int RecursiveCommandCounter::_counter = 0;
CommandProc CmdBuildBridge;
CommandProc CmdBuildRailStation;
CommandProc CmdRemoveFromRailStation;
CommandProc CmdConvertRail;
/**
* Define a command with the flags which belongs to it.
*
* This struct connects a command handler function with the flags created with
* the #CMD_AUTO, #CMD_OFFLINE and #CMD_SERVER values.
*/
struct CommandInfo {
const char *name; ///< A human readable name for the procedure
CommandFlags flags; ///< The (command) flags to that apply to this command
CommandType type; ///< The type of command.
};
/* Helpers to generate the master command table from the command traits. */
template <typename T>
inline constexpr CommandInfo CommandFromTrait() noexcept { return { T::name, T::flags, T::type }; };
CommandProc CmdBuildSingleSignal;
CommandProc CmdRemoveSingleSignal;
CommandProc CmdTerraformLand;
CommandProc CmdBuildObject;
CommandProc CmdSellLandArea;
CommandProc CmdBuildTunnel;
CommandProc CmdBuildTrainDepot;
CommandProc CmdBuildRailWaypoint;
CommandProc CmdRenameWaypoint;
CommandProc CmdRemoveFromRailWaypoint;
CommandProc CmdBuildRoadStop;
CommandProc CmdRemoveRoadStop;
CommandProc CmdBuildLongRoad;
CommandProc CmdRemoveLongRoad;
CommandProc CmdBuildRoad;
CommandProc CmdBuildRoadDepot;
CommandProc CmdConvertRoad;
CommandProc CmdBuildAirport;
CommandProc CmdBuildDock;
CommandProc CmdBuildShipDepot;
CommandProc CmdBuildBuoy;
CommandProc CmdPlantTree;
CommandProc CmdMoveRailVehicle;
CommandProc CmdBuildVehicle;
CommandProc CmdSellVehicle;
CommandProc CmdRefitVehicle;
CommandProc CmdSendVehicleToDepot;
CommandProc CmdSetVehicleVisibility;
CommandProc CmdForceTrainProceed;
CommandProc CmdReverseTrainDirection;
CommandProc CmdClearOrderBackup;
CommandProc CmdModifyOrder;
CommandProc CmdSkipToOrder;
CommandProc CmdDeleteOrder;
CommandProc CmdInsertOrder;
CommandProc CmdChangeServiceInt;
CommandProc CmdBuildIndustry;
CommandProc CmdIndustryCtrl;
CommandProc CmdSetCompanyManagerFace;
CommandProc CmdSetCompanyColour;
CommandProc CmdIncreaseLoan;
CommandProc CmdDecreaseLoan;
CommandProc CmdWantEnginePreview;
CommandProc CmdEngineCtrl;
CommandProc CmdRenameVehicle;
CommandProc CmdRenameEngine;
CommandProc CmdRenameCompany;
CommandProc CmdRenamePresident;
CommandProc CmdRenameStation;
CommandProc CmdRenameDepot;
CommandProc CmdPlaceSign;
CommandProc CmdRenameSign;
CommandProc CmdTurnRoadVeh;
CommandProc CmdPause;
CommandProc CmdBuyShareInCompany;
CommandProc CmdSellShareInCompany;
CommandProc CmdBuyCompany;
CommandProc CmdFoundTown;
CommandProc CmdRenameTown;
CommandProc CmdDoTownAction;
CommandProc CmdTownGrowthRate;
CommandProc CmdTownRating;
CommandProc CmdTownCargoGoal;
CommandProc CmdTownSetText;
CommandProc CmdExpandTown;
CommandProc CmdDeleteTown;
CommandProc CmdChangeSetting;
CommandProc CmdChangeCompanySetting;
CommandProc CmdOrderRefit;
CommandProc CmdCloneOrder;
CommandProc CmdClearArea;
CommandProc CmdGiveMoney;
CommandProc CmdMoneyCheat;
CommandProc CmdChangeBankBalance;
CommandProc CmdBuildCanal;
CommandProc CmdBuildLock;
CommandProc CmdCreateSubsidy;
CommandProc CmdCompanyCtrl;
CommandProc CmdCustomNewsItem;
CommandProc CmdCreateGoal;
CommandProc CmdRemoveGoal;
CommandProc CmdSetGoalText;
CommandProc CmdSetGoalProgress;
CommandProc CmdSetGoalCompleted;
CommandProc CmdGoalQuestion;
CommandProc CmdGoalQuestionAnswer;
CommandProc CmdCreateStoryPage;
CommandProc CmdCreateStoryPageElement;
CommandProc CmdUpdateStoryPageElement;
CommandProc CmdSetStoryPageTitle;
CommandProc CmdSetStoryPageDate;
CommandProc CmdShowStoryPage;
CommandProc CmdRemoveStoryPage;
CommandProc CmdRemoveStoryPageElement;
CommandProc CmdScrollViewport;
CommandProc CmdStoryPageButton;
CommandProc CmdLevelLand;
CommandProc CmdBuildSignalTrack;
CommandProc CmdRemoveSignalTrack;
CommandProc CmdSetAutoReplace;
CommandProc CmdCloneVehicle;
CommandProc CmdStartStopVehicle;
CommandProc CmdMassStartStopVehicle;
CommandProc CmdAutoreplaceVehicle;
CommandProc CmdDepotSellAllVehicles;
CommandProc CmdDepotMassAutoReplace;
CommandProc CmdCreateGroup;
CommandProc CmdAlterGroup;
CommandProc CmdDeleteGroup;
CommandProc CmdAddVehicleGroup;
CommandProc CmdAddSharedVehicleGroup;
CommandProc CmdRemoveAllVehiclesGroup;
CommandProc CmdSetGroupFlag;
CommandProc CmdSetGroupLivery;
CommandProc CmdMoveOrder;
CommandProc CmdChangeTimetable;
CommandProc CmdSetVehicleOnTime;
CommandProc CmdAutofillTimetable;
CommandProc CmdSetTimetableStart;
CommandProc CmdOpenCloseAirport;
#define DEF_CMD(proc, flags, type) {proc, #proc, (CommandFlags)flags, type}
template<typename T, T... i>
inline constexpr auto MakeCommandsFromTraits(std::integer_sequence<T, i...>) noexcept {
return std::array<CommandInfo, sizeof...(i)>{{ CommandFromTrait<CommandTraits<static_cast<Commands>(i)>>()... }};
}
/**
* The master command table
@@ -216,174 +96,18 @@ CommandProc CmdOpenCloseAirport;
* the flags which belongs to it. The indices are the same
* as the value from the CMD_* enums.
*/
static const Command _command_proc_table[] = {
DEF_CMD(CmdBuildRailroadTrack, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_RAILROAD_TRACK
DEF_CMD(CmdRemoveRailroadTrack, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_RAILROAD_TRACK
DEF_CMD(CmdBuildSingleRail, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_SINGLE_RAIL
DEF_CMD(CmdRemoveSingleRail, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_SINGLE_RAIL
DEF_CMD(CmdLandscapeClear, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_LANDSCAPE_CLEAR
DEF_CMD(CmdBuildBridge, CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_BRIDGE
DEF_CMD(CmdBuildRailStation, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_RAIL_STATION
DEF_CMD(CmdBuildTrainDepot, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_TRAIN_DEPOT
DEF_CMD(CmdBuildSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_SIGNALS
DEF_CMD(CmdRemoveSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_SIGNALS
DEF_CMD(CmdTerraformLand, CMD_ALL_TILES | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_TERRAFORM_LAND
DEF_CMD(CmdBuildObject, CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_OBJECT
DEF_CMD(CmdBuildTunnel, CMD_DEITY | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_TUNNEL
DEF_CMD(CmdRemoveFromRailStation, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_FROM_RAIL_STATION
DEF_CMD(CmdConvertRail, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_CONVERT_RAILD
DEF_CMD(CmdBuildRailWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_RAIL_WAYPOINT
DEF_CMD(CmdRenameWaypoint, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_WAYPOINT
DEF_CMD(CmdRemoveFromRailWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_FROM_RAIL_WAYPOINT
static constexpr auto _command_proc_table = MakeCommandsFromTraits(std::make_integer_sequence<std::underlying_type_t<Commands>, CMD_END>{});
DEF_CMD(CmdBuildRoadStop, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_ROAD_STOP
DEF_CMD(CmdRemoveRoadStop, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_ROAD_STOP
DEF_CMD(CmdBuildLongRoad,CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_LONG_ROAD
DEF_CMD(CmdRemoveLongRoad, CMD_NO_TEST | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_LONG_ROAD; towns may disallow removing road bits (as they are connected) in test, but in exec they're removed and thus removing is allowed.
DEF_CMD(CmdBuildRoad, CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_ROAD
DEF_CMD(CmdBuildRoadDepot, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_ROAD_DEPOT
DEF_CMD(CmdConvertRoad, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_CONVERT_ROAD
DEF_CMD(CmdBuildAirport, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_AIRPORT
DEF_CMD(CmdBuildDock, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_DOCK
DEF_CMD(CmdBuildShipDepot, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_SHIP_DEPOT
DEF_CMD(CmdBuildBuoy, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_BUOY
DEF_CMD(CmdPlantTree, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_PLANT_TREE
DEF_CMD(CmdBuildVehicle, CMD_CLIENT_ID, CMDT_VEHICLE_CONSTRUCTION ), // CMD_BUILD_VEHICLE
DEF_CMD(CmdSellVehicle, CMD_CLIENT_ID, CMDT_VEHICLE_CONSTRUCTION ), // CMD_SELL_VEHICLE
DEF_CMD(CmdRefitVehicle, 0, CMDT_VEHICLE_CONSTRUCTION ), // CMD_REFIT_VEHICLE
DEF_CMD(CmdSendVehicleToDepot, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_SEND_VEHICLE_TO_DEPOT
DEF_CMD(CmdSetVehicleVisibility, 0, CMDT_COMPANY_SETTING ), // CMD_SET_VEHICLE_VISIBILITY
DEF_CMD(CmdMoveRailVehicle, 0, CMDT_VEHICLE_CONSTRUCTION ), // CMD_MOVE_RAIL_VEHICLE
DEF_CMD(CmdForceTrainProceed, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_FORCE_TRAIN_PROCEED
DEF_CMD(CmdReverseTrainDirection, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_REVERSE_TRAIN_DIRECTION
DEF_CMD(CmdClearOrderBackup, CMD_CLIENT_ID, CMDT_SERVER_SETTING ), // CMD_CLEAR_ORDER_BACKUP
DEF_CMD(CmdModifyOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_MODIFY_ORDER
DEF_CMD(CmdSkipToOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SKIP_TO_ORDER
DEF_CMD(CmdDeleteOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_DELETE_ORDER
DEF_CMD(CmdInsertOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_INSERT_ORDER
DEF_CMD(CmdChangeServiceInt, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_CHANGE_SERVICE_INT
DEF_CMD(CmdBuildIndustry, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_INDUSTRY
DEF_CMD(CmdIndustryCtrl, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_INDUSTRY_CTRL
DEF_CMD(CmdSetCompanyManagerFace, 0, CMDT_OTHER_MANAGEMENT ), // CMD_SET_COMPANY_MANAGER_FACE
DEF_CMD(CmdSetCompanyColour, 0, CMDT_OTHER_MANAGEMENT ), // CMD_SET_COMPANY_COLOUR
DEF_CMD(CmdIncreaseLoan, 0, CMDT_MONEY_MANAGEMENT ), // CMD_INCREASE_LOAN
DEF_CMD(CmdDecreaseLoan, 0, CMDT_MONEY_MANAGEMENT ), // CMD_DECREASE_LOAN
DEF_CMD(CmdWantEnginePreview, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_WANT_ENGINE_PREVIEW
DEF_CMD(CmdEngineCtrl, CMD_DEITY, CMDT_VEHICLE_MANAGEMENT ), // CMD_ENGINE_CTRL
DEF_CMD(CmdRenameVehicle, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_VEHICLE
DEF_CMD(CmdRenameEngine, CMD_SERVER, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_ENGINE
DEF_CMD(CmdRenameCompany, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_COMPANY
DEF_CMD(CmdRenamePresident, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_PRESIDENT
DEF_CMD(CmdRenameStation, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_STATION
DEF_CMD(CmdRenameDepot, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_DEPOT
DEF_CMD(CmdPlaceSign, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_PLACE_SIGN
DEF_CMD(CmdRenameSign, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_SIGN
DEF_CMD(CmdTurnRoadVeh, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_TURN_ROADVEH
DEF_CMD(CmdPause, CMD_SERVER | CMD_NO_EST, CMDT_SERVER_SETTING ), // CMD_PAUSE
DEF_CMD(CmdBuyShareInCompany, 0, CMDT_MONEY_MANAGEMENT ), // CMD_BUY_SHARE_IN_COMPANY
DEF_CMD(CmdSellShareInCompany, 0, CMDT_MONEY_MANAGEMENT ), // CMD_SELL_SHARE_IN_COMPANY
DEF_CMD(CmdBuyCompany, 0, CMDT_MONEY_MANAGEMENT ), // CMD_BUY_COMANY
DEF_CMD(CmdFoundTown, CMD_DEITY | CMD_NO_TEST, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_FOUND_TOWN; founding random town can fail only in exec run
DEF_CMD(CmdRenameTown, CMD_DEITY | CMD_SERVER, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_TOWN
DEF_CMD(CmdDoTownAction, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_DO_TOWN_ACTION
DEF_CMD(CmdTownCargoGoal, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_CARGO_GOAL
DEF_CMD(CmdTownGrowthRate, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_GROWTH_RATE
DEF_CMD(CmdTownRating, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_RATING
DEF_CMD(CmdTownSetText, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_SET_TEXT
DEF_CMD(CmdExpandTown, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_EXPAND_TOWN
DEF_CMD(CmdDeleteTown, CMD_OFFLINE, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_DELETE_TOWN
DEF_CMD(CmdOrderRefit, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_ORDER_REFIT
DEF_CMD(CmdCloneOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_CLONE_ORDER
DEF_CMD(CmdClearArea, CMD_NO_TEST, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_CLEAR_AREA; destroying multi-tile houses makes town rating differ between test and execution
DEF_CMD(CmdMoneyCheat, CMD_OFFLINE, CMDT_CHEAT ), // CMD_MONEY_CHEAT
DEF_CMD(CmdChangeBankBalance, CMD_DEITY, CMDT_MONEY_MANAGEMENT ), // CMD_CHANGE_BANK_BALANCE
DEF_CMD(CmdBuildCanal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_CANAL
DEF_CMD(CmdCreateSubsidy, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_SUBSIDY
DEF_CMD(CmdCompanyCtrl, CMD_SPECTATOR | CMD_CLIENT_ID | CMD_NO_EST, CMDT_SERVER_SETTING ), // CMD_COMPANY_CTRL
DEF_CMD(CmdCustomNewsItem, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CUSTOM_NEWS_ITEM
DEF_CMD(CmdCreateGoal, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_GOAL
DEF_CMD(CmdRemoveGoal, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_REMOVE_GOAL
DEF_CMD(CmdSetGoalText, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SET_GOAL_TEXT
DEF_CMD(CmdSetGoalProgress, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SET_GOAL_PROGRESS
DEF_CMD(CmdSetGoalCompleted, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SET_GOAL_COMPLETED
DEF_CMD(CmdGoalQuestion, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_GOAL_QUESTION
DEF_CMD(CmdGoalQuestionAnswer, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_GOAL_QUESTION_ANSWER
DEF_CMD(CmdCreateStoryPage, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_STORY_PAGE
DEF_CMD(CmdCreateStoryPageElement, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_STORY_PAGE_ELEMENT
DEF_CMD(CmdUpdateStoryPageElement, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_UPDATE_STORY_PAGE_ELEMENT
DEF_CMD(CmdSetStoryPageTitle, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SET_STORY_PAGE_TITLE
DEF_CMD(CmdSetStoryPageDate, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SET_STORY_PAGE_DATE
DEF_CMD(CmdShowStoryPage, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SHOW_STORY_PAGE
DEF_CMD(CmdRemoveStoryPage, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_REMOVE_STORY_PAGE
DEF_CMD(CmdRemoveStoryPageElement, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_REMOVE_STORY_ELEMENT_PAGE
DEF_CMD(CmdScrollViewport, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SCROLL_VIEWPORT
DEF_CMD(CmdStoryPageButton, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_STORY_PAGE_BUTTON
DEF_CMD(CmdLevelLand, CMD_ALL_TILES | CMD_NO_TEST | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_LEVEL_LAND; test run might clear tiles multiple times, in execution that only happens once
DEF_CMD(CmdBuildLock, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_LOCK
DEF_CMD(CmdBuildSignalTrack, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_SIGNAL_TRACK
DEF_CMD(CmdRemoveSignalTrack, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_SIGNAL_TRACK
DEF_CMD(CmdGiveMoney, 0, CMDT_MONEY_MANAGEMENT ), // CMD_GIVE_MONEY
DEF_CMD(CmdChangeSetting, CMD_SERVER, CMDT_SERVER_SETTING ), // CMD_CHANGE_SETTING
DEF_CMD(CmdChangeCompanySetting, 0, CMDT_COMPANY_SETTING ), // CMD_CHANGE_COMPANY_SETTING
DEF_CMD(CmdSetAutoReplace, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_SET_AUTOREPLACE
DEF_CMD(CmdCloneVehicle, CMD_NO_TEST, CMDT_VEHICLE_CONSTRUCTION ), // CMD_CLONE_VEHICLE; NewGRF callbacks influence building and refitting making it impossible to correctly estimate the cost
DEF_CMD(CmdStartStopVehicle, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_START_STOP_VEHICLE
DEF_CMD(CmdMassStartStopVehicle, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_MASS_START_STOP
DEF_CMD(CmdAutoreplaceVehicle, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_AUTOREPLACE_VEHICLE
DEF_CMD(CmdDepotSellAllVehicles, 0, CMDT_VEHICLE_CONSTRUCTION ), // CMD_DEPOT_SELL_ALL_VEHICLES
DEF_CMD(CmdDepotMassAutoReplace, 0, CMDT_VEHICLE_CONSTRUCTION ), // CMD_DEPOT_MASS_AUTOREPLACE
DEF_CMD(CmdCreateGroup, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_CREATE_GROUP
DEF_CMD(CmdDeleteGroup, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_DELETE_GROUP
DEF_CMD(CmdAlterGroup, 0, CMDT_OTHER_MANAGEMENT ), // CMD_ALTER_GROUP
DEF_CMD(CmdAddVehicleGroup, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_ADD_VEHICLE_GROUP
DEF_CMD(CmdAddSharedVehicleGroup, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_ADD_SHARE_VEHICLE_GROUP
DEF_CMD(CmdRemoveAllVehiclesGroup, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_REMOVE_ALL_VEHICLES_GROUP
DEF_CMD(CmdSetGroupFlag, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_GROUP_FLAG
DEF_CMD(CmdSetGroupLivery, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_GROUP_LIVERY
DEF_CMD(CmdMoveOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_MOVE_ORDER
DEF_CMD(CmdChangeTimetable, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_CHANGE_TIMETABLE
DEF_CMD(CmdSetVehicleOnTime, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_VEHICLE_ON_TIME
DEF_CMD(CmdAutofillTimetable, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_AUTOFILL_TIMETABLE
DEF_CMD(CmdSetTimetableStart, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_TIMETABLE_START
DEF_CMD(CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_OPEN_CLOSE_AIRPORT
};
/*!
* This function range-checks a cmd, and checks if the cmd is not nullptr
* This function range-checks a cmd.
*
* @param cmd The integer value of a command
* @return true if the command is valid (and got a CommandProc function)
*/
bool IsValidCommand(uint32 cmd)
bool IsValidCommand(Commands cmd)
{
cmd &= CMD_ID_MASK;
return cmd < lengthof(_command_proc_table) && _command_proc_table[cmd].proc != nullptr;
return cmd < _command_proc_table.size();
}
/*!
@@ -393,11 +117,11 @@ bool IsValidCommand(uint32 cmd)
* @param cmd The integer value of the command
* @return The flags for this command
*/
CommandFlags GetCommandFlags(uint32 cmd)
CommandFlags GetCommandFlags(Commands cmd)
{
assert(IsValidCommand(cmd));
return _command_proc_table[cmd & CMD_ID_MASK].flags;
return _command_proc_table[cmd].flags;
}
/*!
@@ -407,11 +131,11 @@ CommandFlags GetCommandFlags(uint32 cmd)
* @param cmd The integer value of the command
* @return The name for this command
*/
const char *GetCommandName(uint32 cmd)
const char *GetCommandName(Commands cmd)
{
assert(IsValidCommand(cmd));
return _command_proc_table[cmd & CMD_ID_MASK].name;
return _command_proc_table[cmd].name;
}
/**
@@ -419,7 +143,7 @@ const char *GetCommandName(uint32 cmd)
* @param cmd The command to check.
* @return True if the command is allowed while paused, false otherwise.
*/
bool IsCommandAllowedWhilePaused(uint32 cmd)
bool IsCommandAllowedWhilePaused(Commands cmd)
{
/* Lookup table for the command types that are allowed for a given pause level setting. */
static const int command_type_lookup[] = {
@@ -436,89 +160,7 @@ bool IsCommandAllowedWhilePaused(uint32 cmd)
static_assert(lengthof(command_type_lookup) == CMDT_END);
assert(IsValidCommand(cmd));
return _game_mode == GM_EDITOR || command_type_lookup[_command_proc_table[cmd & CMD_ID_MASK].type] <= _settings_game.construction.command_pause_level;
}
static int _docommand_recursive = 0;
/**
* Shorthand for calling the long DoCommand with a container.
*
* @param container Container with (almost) all information
* @param flags Flags for the command and how to execute the command
* @see CommandProc
* @return the cost
*/
CommandCost DoCommand(const CommandContainer *container, DoCommandFlag flags)
{
return DoCommand(container->tile, container->p1, container->p2, flags, container->cmd & CMD_ID_MASK, container->text);
}
/*!
* This function executes a given command with the parameters from the #CommandProc parameter list.
* Depending on the flags parameter it execute or test a command.
*
* @param tile The tile to apply the command on (for the #CommandProc)
* @param p1 Additional data for the command (for the #CommandProc)
* @param p2 Additional data for the command (for the #CommandProc)
* @param flags Flags for the command and how to execute the command
* @param cmd The command-id to execute (a value of the CMD_* enums)
* @param text The text to pass
* @see CommandProc
* @return the cost
*/
CommandCost DoCommand(TileIndex tile, uint32 p1, uint32 p2, DoCommandFlag flags, uint32 cmd, const std::string &text)
{
CommandCost res;
/* Do not even think about executing out-of-bounds tile-commands */
if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (flags & DC_ALL_TILES) == 0))) return CMD_ERROR;
/* Chop of any CMD_MSG or other flags; we don't need those here */
CommandProc *proc = _command_proc_table[cmd & CMD_ID_MASK].proc;
_docommand_recursive++;
/* only execute the test call if it's toplevel, or we're not execing. */
if (_docommand_recursive == 1 || !(flags & DC_EXEC) ) {
if (_docommand_recursive == 1) _cleared_object_areas.clear();
SetTownRatingTestMode(true);
res = proc(tile, flags & ~DC_EXEC, p1, p2, text);
SetTownRatingTestMode(false);
if (res.Failed()) {
goto error;
}
if (_docommand_recursive == 1 &&
!(flags & DC_QUERY_COST) &&
!(flags & DC_BANKRUPT) &&
!CheckCompanyHasMoney(res)) { // CheckCompanyHasMoney() modifies 'res' to an error if it fails.
goto error;
}
if (!(flags & DC_EXEC)) {
_docommand_recursive--;
return res;
}
}
/* Execute the command here. All cost-relevant functions set the expenses type
* themselves to the cost object at some point */
if (_docommand_recursive == 1) _cleared_object_areas.clear();
res = proc(tile, flags, p1, p2, text);
if (res.Failed()) {
error:
_docommand_recursive--;
return res;
}
/* if toplevel, subtract the money. */
if (--_docommand_recursive == 0 && !(flags & DC_BANKRUPT)) {
SubtractMoneyFromCompany(res);
}
return res;
return _game_mode == GM_EDITOR || command_type_lookup[_command_proc_table[cmd].type] <= _settings_game.construction.command_pause_level;
}
/*!
@@ -535,77 +177,93 @@ Money GetAvailableMoneyForCommand()
return Company::Get(company)->money;
}
/**
* Shortcut for the long DoCommandP when having a container with the data.
* @param container the container with information.
* @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
* @return true if the command succeeded, else false
* Prepare for calling a command proc.
* @param top_level Top level of command execution, i.e. command from a command.
* @param test Test run of command?
*/
bool DoCommandP(const CommandContainer *container, bool my_cmd)
void CommandHelperBase::InternalDoBefore(bool top_level, bool test)
{
return DoCommandP(container->tile, container->p1, container->p2, container->cmd, container->callback, container->text, my_cmd);
if (top_level) _cleared_object_areas.clear();
if (test) SetTownRatingTestMode(true);
}
/*!
* Toplevel network safe docommand function for the current company. Must not be called recursively.
* The callback is called when the command succeeded or failed. The parameters
* \a tile, \a p1, and \a p2 are from the #CommandProc function. The parameter \a cmd is the command to execute.
* The parameter \a my_cmd is used to indicate if the command is from a company or the server.
*
* @param tile The tile to perform a command on (see #CommandProc)
* @param p1 Additional data for the command (see #CommandProc)
* @param p2 Additional data for the command (see #CommandProc)
* @param cmd The command to execute (a CMD_* value)
* @param callback A callback function to call after the command is finished
* @param text The text to pass
* @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
* @return \c true if the command succeeded, else \c false.
/**
* Process result after calling a command proc.
* @param[in,out] res Command result, may be modified.
* @param flags Command flags.
* @param top_level Top level of command execution, i.e. command from a command.
* @param test Test run of command?
*/
bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const std::string &text, bool my_cmd)
void CommandHelperBase::InternalDoAfter(CommandCost &res, DoCommandFlag flags, bool top_level, bool test)
{
if (test) {
SetTownRatingTestMode(false);
if (res.Succeeded() && top_level && !(flags & DC_QUERY_COST) && !(flags & DC_BANKRUPT)) {
CheckCompanyHasMoney(res); // CheckCompanyHasMoney() modifies 'res' to an error if it fails.
}
} else {
/* If top-level, subtract the money. */
if (res.Succeeded() && top_level && !(flags & DC_BANKRUPT)) {
SubtractMoneyFromCompany(res);
}
}
}
/**
* Decide what to do with the command depending on current game state.
* @param cmd Command to execute.
* @param flags Command flags.
* @param tile Tile of command execution.
* @param err_message Message prefix to show on error.
* @param network_command Does this command come from the network?
* @return error state + do only cost estimation? + send to network only?
*/
std::tuple<bool, bool, bool> CommandHelperBase::InternalPostBefore(Commands cmd, CommandFlags flags, TileIndex tile, StringID err_message, bool network_command)
{
/* Cost estimation is generally only done when the
* local user presses shift while doing something.
* However, in case of incoming network commands,
* map generation or the pause button we do want
* to execute. */
bool estimate_only = (_shift_pressed || ConfirmationWindowEstimatingCost()) &&
IsLocalCompany() &&
!_generating_world &&
!(cmd & CMD_NETWORK_COMMAND) &&
!(GetCommandFlags(cmd) & CMD_NO_EST);
if (ConfirmationWindowEstimatingCost() && !estimate_only) {
// We cannot estimate cost, so abort the command - it will be repeated by confirmation dialog later
ShowEstimatedCostOrIncome(0, 0, 0);
return false;
}
bool estimate_only = (_shift_pressed || ConfirmationWindowEstimatingCost()) && IsLocalCompany() && !_generating_world && !network_command && !(flags & CMD_NO_EST);
/* We're only sending the command, so don't do
* fancy things for 'success'. */
bool only_sending = _networking && !(cmd & CMD_NETWORK_COMMAND);
bool only_sending = _networking && !network_command;
/* Where to show the message? */
if (_pause_mode != PM_UNPAUSED && !IsCommandAllowedWhilePaused(cmd) && !estimate_only) {
ShowErrorMessage(err_message, STR_ERROR_NOT_ALLOWED_WHILE_PAUSED, WL_INFO, TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE);
return { true, estimate_only, only_sending };
} else {
return { false, estimate_only, only_sending };
}
}
/**
* Process result of executing a command, possibly displaying any error to the player.
* @param res Command result.
* @param tile Tile of command execution.
* @param estimate_only Is this just cost estimation?
* @param only_sending Was the command only sent to network?
* @param err_message Message prefix to show on error.
* @param my_cmd Is the command from this client?
*/
void CommandHelperBase::InternalPostResult(const CommandCost &res, TileIndex tile, bool estimate_only, bool only_sending, StringID err_message, bool my_cmd)
{
int x = TileX(tile) * TILE_SIZE;
int y = TileY(tile) * TILE_SIZE;
if (_pause_mode != PM_UNPAUSED && !IsCommandAllowedWhilePaused(cmd) && !estimate_only) {
ShowErrorMessage(GB(cmd, 16, 16), STR_ERROR_NOT_ALLOWED_WHILE_PAUSED, WL_INFO, x, y);
return false;
}
/* Only set p2 when the command does not come from the network. */
if (!(cmd & CMD_NETWORK_COMMAND) && GetCommandFlags(cmd) & CMD_CLIENT_ID && p2 == 0) p2 = CLIENT_ID_SERVER;
CommandCost res = DoCommandPInternal(tile, p1, p2, cmd, callback, text, my_cmd, estimate_only);
if (res.Failed()) {
/* Only show the error when it's for us. */
StringID error_part1 = GB(cmd, 16, 16);
if (estimate_only || (IsLocalCompany() && error_part1 != 0 && my_cmd)) {
ShowErrorMessage(error_part1, res.GetErrorMessage(), WL_INFO, x, y, res.GetTextRefStackGRF(), res.GetTextRefStackSize(), res.GetTextRefStack());
if (estimate_only || (IsLocalCompany() && err_message != 0 && my_cmd)) {
ShowErrorMessage(err_message, res.GetErrorMessage(), WL_INFO, x, y, res.GetTextRefStackGRF(), res.GetTextRefStackSize(), res.GetTextRefStack());
}
} else if (estimate_only) {
ShowEstimatedCostOrIncome(res.GetCost(), x, y);
} else if (!only_sending && res.GetCost() != 0 && tile != 0 && IsLocalCompany() && _game_mode != GM_EDITOR) {
} else if (!only_sending && tile != 0 && IsLocalCompany() && _game_mode != GM_EDITOR) {
/* Only show the cost animation when we did actually
* execute the command, i.e. we're not sending it to
* the server, when it has cost the local company
@@ -613,63 +271,23 @@ bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallbac
* concept of cost, so don't show it there either. */
ShowCostOrIncomeAnimation(x, y, GetSlopePixelZ(x, y), res.GetCost());
}
if (!estimate_only && !only_sending && callback != nullptr) {
callback(res, tile, p1, p2, cmd);
}
return res.Succeeded();
}
/** Helper to make a desync log for a command. */
void CommandHelperBase::LogCommandExecution(Commands cmd, StringID err_message, TileIndex tile, const CommandDataBuffer &args, bool failed)
{
Debug(desync, 1, "{}: {:08x}; {:02x}; {:02x}; {:08x}; {:08x}; {:06x}; {} ({})", failed ? "cmdf" : "cmd", _date, _date_fract, (int)_current_company, cmd, err_message, tile, FormatArrayAsHex(args), GetCommandName(cmd));
}
/**
* Helper to deduplicate the code for returning.
* @param cmd the command cost to return.
* Prepare for the test run of a command proc call.
* @param cmd_flags Command flags.
* @param tile Tile of command execution.
* @param[in,out] cur_company Backup of current company at start of command execution.
* @return True if test run can go ahead, false on error.
*/
#define return_dcpi(cmd) { _docommand_recursive = 0; return cmd; }
/*!
* Helper function for the toplevel network safe docommand function for the current company.
*
* @param tile The tile to perform a command on (see #CommandProc)
* @param p1 Additional data for the command (see #CommandProc)
* @param p2 Additional data for the command (see #CommandProc)
* @param cmd The command to execute (a CMD_* value)
* @param callback A callback function to call after the command is finished
* @param text The text to pass
* @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
* @param estimate_only whether to give only the estimate or also execute the command
* @return the command cost of this function.
*/
CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const std::string &text, bool my_cmd, bool estimate_only)
bool CommandHelperBase::InternalExecutePrepTest(CommandFlags cmd_flags, TileIndex tile, Backup<CompanyID> &cur_company)
{
/* Prevent recursion; it gives a mess over the network */
assert(_docommand_recursive == 0);
_docommand_recursive = 1;
/* Reset the state. */
_additional_cash_required = 0;
/* Get pointer to command handler */
byte cmd_id = cmd & CMD_ID_MASK;
assert(cmd_id < lengthof(_command_proc_table));
CommandProc *proc = _command_proc_table[cmd_id].proc;
/* Shouldn't happen, but you never know when someone adds
* NULLs to the _command_proc_table. */
assert(proc != nullptr);
/* Command flags are used internally */
CommandFlags cmd_flags = GetCommandFlags(cmd);
/* Flags get send to the DoCommand */
DoCommandFlag flags = CommandFlagsToDCFlags(cmd_flags);
/* Make sure p2 is properly set to a ClientID. */
assert(!(cmd_flags & CMD_CLIENT_ID) || p2 != 0);
/* Do not even think about executing out-of-bounds tile-commands */
if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (cmd_flags & CMD_ALL_TILES) == 0))) return_dcpi(CMD_ERROR);
/* Always execute server and spectator commands as spectator */
bool exec_as_spectator = (cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) != 0;
@@ -677,65 +295,72 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd,
* The server will ditch any server commands a client sends to it, so effectively
* this guards the server from executing functions for an invalid company. */
if (_game_mode == GM_NORMAL && !exec_as_spectator && !Company::IsValidID(_current_company) && !(_current_company == OWNER_DEITY && (cmd_flags & CMD_DEITY) != 0)) {
return_dcpi(CMD_ERROR);
return false;
}
Backup<CompanyID> cur_company(_current_company, FILE_LINE);
if (exec_as_spectator) cur_company.Change(COMPANY_SPECTATOR);
bool test_and_exec_can_differ = (cmd_flags & CMD_NO_TEST) != 0;
/* Test the command. */
/* Enter test mode. */
_cleared_object_areas.clear();
SetTownRatingTestMode(true);
BasePersistentStorageArray::SwitchMode(PSM_ENTER_TESTMODE);
CommandCost res = proc(tile, flags, p1, p2, text);
return true;
}
/**
* Validate result of test run and prepare for real execution.
* @param cmd_flags Command flags.
* @param[in,out] res Command result of test run, may be modified.
* @param estimate_only Is this just cost estimation?
* @param network_command Does this command come from the network?
* @param[in,out] cur_company Backup of current company at start of command execution.
* @return True if test run can go ahead, false on error.
*/
std::tuple<bool, bool, bool> CommandHelperBase::InternalExecuteValidateTestAndPrepExec(CommandCost &res, CommandFlags cmd_flags, bool estimate_only, bool network_command, Backup<CompanyID> &cur_company)
{
BasePersistentStorageArray::SwitchMode(PSM_LEAVE_TESTMODE);
SetTownRatingTestMode(false);
/* Make sure we're not messing things up here. */
assert(exec_as_spectator ? _current_company == COMPANY_SPECTATOR : cur_company.Verify());
assert((cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) != 0 ? _current_company == COMPANY_SPECTATOR : cur_company.Verify());
/* If the command fails, we're doing an estimate
* or the player does not have enough money
* (unless it's a command where the test and
* execution phase might return different costs)
* we bail out here. */
if (res.Failed() || estimate_only ||
(!test_and_exec_can_differ && !CheckCompanyHasMoney(res))) {
if (!_networking || _generating_world || (cmd & CMD_NETWORK_COMMAND) != 0) {
/* Log the failed command as well. Just to be able to be find
* causes of desyncs due to bad command test implementations. */
Debug(desync, 1, "cmdf: {:08x}; {:02x}; {:02x}; {:06x}; {:08x}; {:08x}; {:08x}; \"{}\" ({})", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd));
}
cur_company.Restore();
return_dcpi(res);
bool test_and_exec_can_differ = (cmd_flags & CMD_NO_TEST) != 0;
if (res.Failed() || estimate_only || (!test_and_exec_can_differ && !CheckCompanyHasMoney(res))) {
return { true, !_networking || _generating_world || network_command, false };
}
/*
* If we are in network, and the command is not from the network
* send it to the command-queue and abort execution
*/
if (_networking && !_generating_world && !(cmd & CMD_NETWORK_COMMAND)) {
NetworkSendCommand(tile, p1, p2, cmd & ~CMD_FLAGS_MASK, callback, text, _current_company);
cur_company.Restore();
bool send_net = _networking && !_generating_world && !network_command;
/* Don't return anything special here; no error, no costs.
* This way it's not handled by DoCommand and only the
* actual execution of the command causes messages. Also
* reset the storages as we've not executed the command. */
return_dcpi(CommandCost());
if (!send_net) {
/* Prepare for command execution. */
_cleared_object_areas.clear();
BasePersistentStorageArray::SwitchMode(PSM_ENTER_COMMAND);
}
Debug(desync, 1, "cmd: {:08x}; {:02x}; {:02x}; {:06x}; {:08x}; {:08x}; {:08x}; \"{}\" ({})", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd));
/* Actually try and execute the command. If no cost-type is given
* use the construction one */
_cleared_object_areas.clear();
BasePersistentStorageArray::SwitchMode(PSM_ENTER_COMMAND);
CommandCost res2 = proc(tile, flags | DC_EXEC, p1, p2, text);
return { false, _debug_desync_level >= 1, send_net };
}
/**
* Process the result of a command test run and execution run.
* @param cmd Command that was executed.
* @param cmd_flags Command flags.
* @param res_test Command result of test run.
* @param tes_exec Command result of real run.
* @param extra_cash Additional cash required for successful command execution.
* @param tile Tile of command execution.
* @param[in,out] cur_company Backup of current company at start of command execution.
* @return Final command result.
*/
CommandCost CommandHelperBase::InternalExecuteProcessResult(Commands cmd, CommandFlags cmd_flags, const CommandCost &res_test, const CommandCost &res_exec, Money extra_cash, TileIndex tile, Backup<CompanyID> &cur_company)
{
BasePersistentStorageArray::SwitchMode(PSM_LEAVE_COMMAND);
if (cmd_id == CMD_COMPANY_CTRL) {
if (cmd == CMD_COMPANY_CTRL) {
cur_company.Trash();
/* We are a new company -> Switch to new local company.
* We were closed down -> Switch to spectator
@@ -743,7 +368,7 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd,
_current_company = _local_company;
} else {
/* Make sure nothing bad happened, like changing the current company. */
assert(exec_as_spectator ? _current_company == COMPANY_SPECTATOR : cur_company.Verify());
assert((cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) != 0 ? _current_company == COMPANY_SPECTATOR : cur_company.Verify());
cur_company.Restore();
}
@@ -751,20 +376,21 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd,
* return of the command. Otherwise we can check whether the
* test and execution have yielded the same result,
* i.e. cost and error state are the same. */
bool test_and_exec_can_differ = (cmd_flags & CMD_NO_TEST) != 0;
if (!test_and_exec_can_differ) {
assert(res.GetCost() == res2.GetCost() && res.Failed() == res2.Failed()); // sanity check
} else if (res2.Failed()) {
return_dcpi(res2);
assert(res_test.GetCost() == res_exec.GetCost() && res_test.Failed() == res_exec.Failed()); // sanity check
} else if (res_exec.Failed()) {
return res_exec;
}
/* If we're needing more money and we haven't done
* anything yet, ask for the money! */
if (_additional_cash_required != 0 && res2.GetCost() == 0) {
if (extra_cash != 0 && res_exec.GetCost() == 0) {
/* It could happen we removed rail, thus gained money, and deleted something else.
* So make sure the signal buffer is empty even in this case */
UpdateSignalsInBuffer();
SetDParam(0, _additional_cash_required);
return_dcpi(CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY));
SetDParam(0, extra_cash);
return CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY);
}
/* update last build coordinate of company. */
@@ -773,14 +399,13 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd,
if (c != nullptr) c->last_build_coordinate = tile;
}
SubtractMoneyFromCompany(res2);
SubtractMoneyFromCompany(res_exec);
/* update signals if needed */
UpdateSignalsInBuffer();
return_dcpi(res2);
return res_exec;
}
#undef return_dcpi
/**

View File

@@ -11,7 +11,12 @@
#define COMMAND_FUNC_H
#include "command_type.h"
#include "network/network_type.h"
#include "company_type.h"
#include "company_func.h"
#include "core/backup_type.hpp"
#include "misc/endian_buffer.hpp"
#include "tile_map.h"
/**
* Define a default return value for a failed command.
@@ -32,30 +37,26 @@ static const CommandCost CMD_ERROR = CommandCost(INVALID_STRING_ID);
*/
#define return_cmd_error(errcode) return CommandCost(errcode);
CommandCost DoCommand(TileIndex tile, uint32 p1, uint32 p2, DoCommandFlag flags, uint32 cmd, const std::string &text = {});
CommandCost DoCommand(const CommandContainer *container, DoCommandFlag flags);
void NetworkSendCommand(Commands cmd, StringID err_message, CommandCallback *callback, CompanyID company, TileIndex location, const CommandDataBuffer &cmd_data);
bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback = nullptr, const std::string &text = {}, bool my_cmd = true);
bool DoCommandP(const CommandContainer *container, bool my_cmd = true);
CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const std::string &text, bool my_cmd, bool estimate_only);
void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const std::string &text, CompanyID company);
extern Money _additional_cash_required;
bool IsValidCommand(uint32 cmd);
CommandFlags GetCommandFlags(uint32 cmd);
const char *GetCommandName(uint32 cmd);
bool IsValidCommand(Commands cmd);
CommandFlags GetCommandFlags(Commands cmd);
const char *GetCommandName(Commands cmd);
Money GetAvailableMoneyForCommand();
bool IsCommandAllowedWhilePaused(uint32 cmd);
bool IsCommandAllowedWhilePaused(Commands cmd);
template <Commands Tcmd>
constexpr CommandFlags GetCommandFlags()
{
return CommandTraits<Tcmd>::flags;
}
/**
* Extracts the DC flags needed for DoCommand from the flags returned by GetCommandFlags
* @param cmd_flags Flags from GetCommandFlags
* @return flags for DoCommand
*/
static inline DoCommandFlag CommandFlagsToDCFlags(CommandFlags cmd_flags)
static constexpr inline DoCommandFlag CommandFlagsToDCFlags(CommandFlags cmd_flags)
{
DoCommandFlag flags = DC_NONE;
if (cmd_flags & CMD_NO_WATER) flags |= DC_NO_WATER;
@@ -64,60 +65,433 @@ static inline DoCommandFlag CommandFlagsToDCFlags(CommandFlags cmd_flags)
return flags;
}
/*** All command callbacks that exist ***/
/** Helper class to keep track of command nesting level. */
struct RecursiveCommandCounter {
RecursiveCommandCounter() noexcept { _counter++; }
~RecursiveCommandCounter() noexcept { _counter--; }
/* ai/ai_instance.cpp */
CommandCallback CcAI;
/** Are we in the top-level command execution? */
bool IsTopLevel() const { return _counter == 1; }
private:
static int _counter;
};
/* airport_gui.cpp */
CommandCallback CcBuildAirport;
#if defined(__GNUC__) && !defined(__clang__)
/*
* We cast specialized function pointers to a generic one, but don't use the
* converted value to call the function, which is safe, except that GCC
* helpfully thinks it is not.
*
* "Any pointer to function can be converted to a pointer to a different function type.
* Calling the function through a pointer to a different function type is undefined,
* but converting such pointer back to pointer to the original function type yields
* the pointer to the original function." */
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# define SILENCE_GCC_FUNCTION_POINTER_CAST
#endif
/* bridge_gui.cpp */
CommandCallback CcBuildBridge;
template<Commands TCmd, typename T, bool THasTile> struct CommandHelper;
/* dock_gui.cpp */
CommandCallback CcBuildDocks;
CommandCallback CcPlaySound_CONSTRUCTION_WATER;
class CommandHelperBase {
protected:
static void InternalDoBefore(bool top_level, bool test);
static void InternalDoAfter(CommandCost &res, DoCommandFlag flags, bool top_level, bool test);
static std::tuple<bool, bool, bool> InternalPostBefore(Commands cmd, CommandFlags flags, TileIndex tile, StringID err_message, bool network_command);
static void InternalPostResult(const CommandCost &res, TileIndex tile, bool estimate_only, bool only_sending, StringID err_message, bool my_cmd);
static bool InternalExecutePrepTest(CommandFlags cmd_flags, TileIndex tile, Backup<CompanyID> &cur_company);
static std::tuple<bool, bool, bool> InternalExecuteValidateTestAndPrepExec(CommandCost &res, CommandFlags cmd_flags, bool estimate_only, bool network_command, Backup<CompanyID> &cur_company);
static CommandCost InternalExecuteProcessResult(Commands cmd, CommandFlags cmd_flags, const CommandCost &res_test, const CommandCost &res_exec, Money extra_cash, TileIndex tile, Backup<CompanyID> &cur_company);
static void LogCommandExecution(Commands cmd, StringID err_message, TileIndex tile, const CommandDataBuffer &args, bool failed);
};
/* depot_gui.cpp */
CommandCallback CcCloneVehicle;
/**
* Templated wrapper that exposes the command parameter arguments
* for the various Command::Do/Post calls.
* @tparam Tcmd The command-id to execute.
* @tparam Tret Return type of the command.
* @tparam Targs The command parameter types.
*/
template <Commands Tcmd, typename Tret, typename... Targs>
struct CommandHelper<Tcmd, Tret(*)(DoCommandFlag, Targs...), true> : protected CommandHelperBase {
private:
/** Extract the \c CommandCost from a command proc result. */
static inline CommandCost &ExtractCommandCost(Tret &ret)
{
if constexpr (std::is_same_v<Tret, CommandCost>) {
return ret;
} else {
return std::get<0>(ret);
}
}
/* game/game_instance.cpp */
CommandCallback CcGame;
/** Make a command proc result from a \c CommandCost. */
static inline Tret MakeResult(const CommandCost &cost)
{
Tret ret{};
ExtractCommandCost(ret) = cost;
return ret;
}
/* group_gui.cpp */
CommandCallback CcCreateGroup;
CommandCallback CcAddVehicleNewGroup;
public:
/**
* This function executes a given command with the parameters from the #CommandProc parameter list.
* Depending on the flags parameter it executes or tests a command.
*
* @note This function is to be called from the StateGameLoop or from within the execution of a Command.
* This function must not be called from the context of a "player" (real person, AI, game script).
* Use ::Post for commands directly triggered by "players".
*
* @param flags Flags for the command and how to execute the command
* @param args Parameters for the command
* @see CommandProc
* @return the cost
*/
static Tret Do(DoCommandFlag flags, Targs... args)
{
if constexpr (std::is_same_v<TileIndex, std::tuple_element_t<0, std::tuple<Targs...>>>) {
/* Do not even think about executing out-of-bounds tile-commands. */
TileIndex tile = std::get<0>(std::make_tuple(args...));
if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (flags & DC_ALL_TILES) == 0))) return MakeResult(CMD_ERROR);
}
/* industry_gui.cpp */
CommandCallback CcBuildIndustry;
RecursiveCommandCounter counter{};
/* main_gui.cpp */
CommandCallback CcPlaySound_EXPLOSION;
CommandCallback CcPlaceSign;
CommandCallback CcTerraform;
/* Only execute the test call if it's toplevel, or we're not execing. */
if (counter.IsTopLevel() || !(flags & DC_EXEC)) {
InternalDoBefore(counter.IsTopLevel(), true);
Tret res = CommandTraits<Tcmd>::proc(flags & ~DC_EXEC, args...);
InternalDoAfter(ExtractCommandCost(res), flags, counter.IsTopLevel(), true); // Can modify res.
/* rail_gui.cpp */
CommandCallback CcPlaySound_CONSTRUCTION_RAIL;
CommandCallback CcRailDepot;
CommandCallback CcStation;
CommandCallback CcBuildRailTunnel;
if (ExtractCommandCost(res).Failed() || !(flags & DC_EXEC)) return res;
}
/* road_gui.cpp */
CommandCallback CcPlaySound_CONSTRUCTION_OTHER;
CommandCallback CcBuildRoadTunnel;
CommandCallback CcRoadDepot;
CommandCallback CcRoadStop;
/* Execute the command here. All cost-relevant functions set the expenses type
* themselves to the cost object at some point. */
InternalDoBefore(counter.IsTopLevel(), false);
Tret res = CommandTraits<Tcmd>::proc(flags, args...);
InternalDoAfter(ExtractCommandCost(res), flags, counter.IsTopLevel(), false);
/* train_gui.cpp */
CommandCallback CcBuildWagon;
return res;
}
/* town_gui.cpp */
CommandCallback CcFoundTown;
CommandCallback CcFoundRandomTown;
/**
* Shortcut for the long Post when not using a callback.
* @param err_message Message prefix to show on error
* @param args Parameters for the command
*/
static inline bool Post(StringID err_message, Targs... args) { return Post<CommandCallback>(err_message, nullptr, std::forward<Targs>(args)...); }
/**
* Shortcut for the long Post when not using an error message.
* @param callback A callback function to call after the command is finished
* @param args Parameters for the command
*/
template <typename Tcallback>
static inline bool Post(Tcallback *callback, Targs... args) { return Post((StringID)0, callback, std::forward<Targs>(args)...); }
/**
* Shortcut for the long Post when not using a callback or an error message.
* @param args Parameters for the command
*/
static inline bool Post(Targs... args) { return Post<CommandCallback>((StringID)0, nullptr, std::forward<Targs>(args)...); }
/* vehicle_gui.cpp */
CommandCallback CcBuildPrimaryVehicle;
CommandCallback CcStartStopVehicle;
/**
* Top-level network safe command execution for the current company.
* Must not be called recursively. The callback is called when the
* command succeeded or failed.
*
* @param err_message Message prefix to show on error
* @param callback A callback function to call after the command is finished
* @param args Parameters for the command
* @return \c true if the command succeeded, else \c false.
*/
template <typename Tcallback>
static bool Post(StringID err_message, Tcallback *callback, Targs... args)
{
return InternalPost(err_message, callback, true, false, std::forward_as_tuple(args...));
}
/**
* Execute a command coming from the network.
* @param err_message Message prefix to show on error
* @param callback A callback function to call after the command is finished
* @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
* @param location Tile location for user feedback.
* @param args Parameters for the command
* @return \c true if the command succeeded, else \c false.
*/
template <typename Tcallback>
static bool PostFromNet(StringID err_message, Tcallback *callback, bool my_cmd, TileIndex location, std::tuple<Targs...> args)
{
if constexpr (std::is_same_v<TileIndex, std::tuple_element_t<0, decltype(args)>>) {
/* Do not even think about executing out-of-bounds tile-commands. */
TileIndex tile = std::get<0>(args);
if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (GetCommandFlags<Tcmd>() & CMD_ALL_TILES) == 0))) return false;
}
return InternalPost(err_message, callback, my_cmd, true, location, std::move(args));
}
/**
* Prepare a command to be send over the network
* @param cmd The command to execute (a CMD_* value)
* @param err_message Message prefix to show on error
* @param company The company that wants to send the command
* @param args Parameters for the command
*/
static void SendNet(StringID err_message, CompanyID company, Targs... args)
{
auto args_tuple = std::forward_as_tuple(args...);
TileIndex tile{};
if constexpr (std::is_same_v<TileIndex, std::tuple_element_t<0, decltype(args_tuple)>>) {
tile = std::get<0>(args_tuple);
}
::NetworkSendCommand(Tcmd, err_message, nullptr, _current_company, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args_tuple));
}
/**
* Top-level network safe command execution without safety checks.
* @param err_message Message prefix to show on error
* @param callback A callback function to call after the command is finished
* @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
* @param estimate_only whether to give only the estimate or also execute the command
* @param location Tile location for user feedback.
* @param args Parameters for the command
* @return the command cost of this function.
*/
template <typename Tcallback>
static Tret Unsafe(StringID err_message, Tcallback *callback, bool my_cmd, bool estimate_only, TileIndex location, std::tuple<Targs...> args)
{
return Execute(err_message, reinterpret_cast<CommandCallback *>(callback), my_cmd, estimate_only, false, location, std::move(args));
}
protected:
/** Helper to process a single ClientID argument. */
template <class T>
static inline void SetClientIdHelper(T &data)
{
if constexpr (std::is_same_v<ClientID, T>) {
if (data == INVALID_CLIENT_ID) data = CLIENT_ID_SERVER;
}
}
/** Set all invalid ClientID's to the proper value. */
template<class Ttuple, size_t... Tindices>
static inline void SetClientIds(Ttuple &values, std::index_sequence<Tindices...>)
{
((SetClientIdHelper(std::get<Tindices>(values))), ...);
}
/** Remove the first element of a tuple. */
template <template <typename...> typename Tt, typename T1, typename... Ts>
static inline Tt<Ts...> RemoveFirstTupleElement(const Tt<T1, Ts...> &tuple)
{
return std::apply([](auto &&, const auto&... args) { return std::tie(args...); }, tuple);
}
template <typename Tcallback>
static bool InternalPost(StringID err_message, Tcallback *callback, bool my_cmd, bool network_command, std::tuple<Targs...> args)
{
/* Where to show the message? */
TileIndex tile{};
if constexpr (std::is_same_v<TileIndex, std::tuple_element_t<0, decltype(args)>>) {
tile = std::get<0>(args);
}
return InternalPost(err_message, callback, my_cmd, network_command, tile, std::move(args));
}
template <typename Tcallback>
static bool InternalPost(StringID err_message, Tcallback *callback, bool my_cmd, bool network_command, TileIndex tile, std::tuple<Targs...> args)
{
/* Do not even think about executing out-of-bounds tile-commands. */
if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (GetCommandFlags<Tcmd>() & CMD_ALL_TILES) == 0))) return false;
auto [err, estimate_only, only_sending] = InternalPostBefore(Tcmd, GetCommandFlags<Tcmd>(), tile, err_message, network_command);
if (err) return false;
/* Only set client IDs when the command does not come from the network. */
if (!network_command && GetCommandFlags<Tcmd>() & CMD_CLIENT_ID) SetClientIds(args, std::index_sequence_for<Targs...>{});
Tret res = Execute(err_message, reinterpret_cast<CommandCallback *>(callback), my_cmd, estimate_only, network_command, tile, args);
InternalPostResult(ExtractCommandCost(res), tile, estimate_only, only_sending, err_message, my_cmd);
if (!estimate_only && !only_sending && callback != nullptr) {
if constexpr (std::is_same_v<Tcallback, CommandCallback>) {
/* Callback that doesn't need any command arguments. */
callback(Tcmd, ExtractCommandCost(res), tile);
} else if constexpr (std::is_same_v<Tcallback, CommandCallbackData>) {
/* Generic callback that takes packed arguments as a buffer. */
if constexpr (std::is_same_v<Tret, CommandCost>) {
callback(Tcmd, ExtractCommandCost(res), tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args), {});
} else {
callback(Tcmd, ExtractCommandCost(res), tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args), EndianBufferWriter<CommandDataBuffer>::FromValue(RemoveFirstTupleElement(res)));
}
} else if constexpr (!std::is_same_v<Tret, CommandCost> && std::is_same_v<Tcallback *, typename CommandTraits<Tcmd>::RetCallbackProc>) {
std::apply(callback, std::tuple_cat(std::make_tuple(Tcmd), res));
} else {
/* Callback with arguments. We assume that the tile is only interesting if it actually is in the command arguments. */
if constexpr (std::is_same_v<Tret, CommandCost>) {
std::apply(callback, std::tuple_cat(std::make_tuple(Tcmd, res), args));
} else {
std::apply(callback, std::tuple_cat(std::make_tuple(Tcmd), res, args));
}
}
}
return ExtractCommandCost(res).Succeeded();
}
/** Helper to process a single ClientID argument. */
template <class T>
static inline bool ClientIdIsSet(T &data)
{
if constexpr (std::is_same_v<ClientID, T>) {
return data != INVALID_CLIENT_ID;
} else {
return true;
}
}
/** Check if all ClientID arguments are set to valid values. */
template<class Ttuple, size_t... Tindices>
static inline bool AllClientIdsSet(Ttuple &values, std::index_sequence<Tindices...>)
{
return (ClientIdIsSet(std::get<Tindices>(values)) && ...);
}
template<class Ttuple>
static inline Money ExtractAdditionalMoney(Ttuple &values)
{
if constexpr (std::is_same_v<std::tuple_element_t<1, Tret>, Money>) {
return std::get<1>(values);
} else {
return {};
}
}
static Tret Execute(StringID err_message, CommandCallback *callback, bool my_cmd, bool estimate_only, bool network_command, TileIndex tile, std::tuple<Targs...> args)
{
/* Prevent recursion; it gives a mess over the network */
RecursiveCommandCounter counter{};
assert(counter.IsTopLevel());
/* Command flags are used internally */
constexpr CommandFlags cmd_flags = GetCommandFlags<Tcmd>();
if constexpr ((cmd_flags & CMD_CLIENT_ID) != 0) {
/* Make sure arguments are properly set to a ClientID also when processing external commands. */
assert(AllClientIdsSet(args, std::index_sequence_for<Targs...>{}));
}
Backup<CompanyID> cur_company(_current_company, FILE_LINE);
if (!InternalExecutePrepTest(cmd_flags, tile, cur_company)) {
cur_company.Trash();
return MakeResult(CMD_ERROR);
}
/* Test the command. */
DoCommandFlag flags = CommandFlagsToDCFlags(cmd_flags);
Tret res = std::apply(CommandTraits<Tcmd>::proc, std::tuple_cat(std::make_tuple(flags), args));
auto [exit_test, desync_log, send_net] = InternalExecuteValidateTestAndPrepExec(ExtractCommandCost(res), cmd_flags, estimate_only, network_command, cur_company);
if (exit_test) {
if (desync_log) LogCommandExecution(Tcmd, err_message, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args), true);
cur_company.Restore();
return res;
}
/* If we are in network, and the command is not from the network
* send it to the command-queue and abort execution. */
if (send_net) {
::NetworkSendCommand(Tcmd, err_message, callback, _current_company, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args));
cur_company.Restore();
/* Don't return anything special here; no error, no costs.
* This way it's not handled by DoCommand and only the
* actual execution of the command causes messages. Also
* reset the storages as we've not executed the command. */
return {};
}
if (desync_log) LogCommandExecution(Tcmd, err_message, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args), false);
/* Actually try and execute the command. */
Tret res2 = std::apply(CommandTraits<Tcmd>::proc, std::tuple_cat(std::make_tuple(flags | DC_EXEC), args));
/* Convention: If the second result element is of type Money,
* this is the additional cash required for the command. */
Money additional_money{};
if constexpr (!std::is_same_v<Tret, CommandCost>) { // No short-circuiting for 'if constexpr'.
additional_money = ExtractAdditionalMoney(res2);
}
if constexpr (std::is_same_v<Tret, CommandCost>) {
return InternalExecuteProcessResult(Tcmd, cmd_flags, res, res2, additional_money, tile, cur_company);
} else {
std::get<0>(res2) = InternalExecuteProcessResult(Tcmd, cmd_flags, ExtractCommandCost(res), ExtractCommandCost(res2), additional_money, tile, cur_company);
return res2;
}
}
};
/**
* Overload for #CommandHelper that exposes additional \c Post variants
* for commands that don't take a TileIndex themselves.
* @tparam Tcmd The command-id to execute.
* @tparam Tret Return type of the command.
* @tparam Targs The command parameter types.
*/
template <Commands Tcmd, typename Tret, typename... Targs>
struct CommandHelper<Tcmd, Tret(*)(DoCommandFlag, Targs...), false> : CommandHelper<Tcmd, Tret(*)(DoCommandFlag, Targs...), true>
{
/* Import Post overloads from our base class. */
using CommandHelper<Tcmd, Tret(*)(DoCommandFlag, Targs...), true>::Post;
/**
* Shortcut for Post when not using an error message.
* @param err_message Message prefix to show on error
* @param location Tile location for user feedback.
* @param args Parameters for the command
*/
static inline bool Post(StringID err_message, TileIndex location, Targs... args) { return Post<CommandCallback>(err_message, nullptr, location, std::forward<Targs>(args)...); }
/**
* Shortcut for Post when not using a callback.
* @param callback A callback function to call after the command is finished
* @param location Tile location for user feedback.
* @param args Parameters for the command
*/
template <typename Tcallback>
static inline bool Post(Tcallback *callback, TileIndex location, Targs... args) { return Post((StringID)0, callback, location, std::forward<Targs>(args)...); }
/**
* Shortcut for Post when not using a callback or an error message.
* @param location Tile location for user feedback.
* @param args Parameters for the command*
*/
static inline bool Post(TileIndex location, Targs... args) { return Post<CommandCallback>((StringID)0, nullptr, location, std::forward<Targs>(args)...); }
/**
* Post variant that takes a TileIndex (for error window location and text effects) for
* commands that don't take a TileIndex by themselves.
* @param err_message Message prefix to show on error
* @param callback A callback function to call after the command is finished
* @param location Tile location for user feedback.
* @param args Parameters for the command
* @return \c true if the command succeeded, else \c false.
*/
template <typename Tcallback>
static inline bool Post(StringID err_message, Tcallback *callback, TileIndex location, Targs... args)
{
return CommandHelper<Tcmd, Tret(*)(DoCommandFlag, Targs...), true>::InternalPost(err_message, callback, true, false, location, std::forward_as_tuple(args...));
}
};
#ifdef SILENCE_GCC_FUNCTION_POINTER_CAST
# pragma GCC diagnostic pop
#endif
template <Commands Tcmd>
using Command = CommandHelper<Tcmd, typename CommandTraits<Tcmd>::ProcType, std::is_same_v<TileIndex, std::tuple_element_t<0, typename CommandTraits<Tcmd>::Args>>>;
#endif /* COMMAND_FUNC_H */

View File

@@ -13,6 +13,7 @@
#include "economy_type.h"
#include "strings_type.h"
#include "tile_type.h"
#include <vector>
struct GRFFile;
@@ -172,7 +173,7 @@ public:
*
* @see _command_proc_table
*/
enum Commands {
enum Commands : uint16 {
CMD_BUILD_RAILROAD_TRACK, ///< build a rail track
CMD_REMOVE_RAILROAD_TRACK, ///< remove a rail track
CMD_BUILD_SINGLE_RAIL, ///< build a single rail track
@@ -185,6 +186,7 @@ enum Commands {
CMD_REMOVE_SIGNALS, ///< remove a signal
CMD_TERRAFORM_LAND, ///< terraform a tile
CMD_BUILD_OBJECT, ///< build an object
CMD_BUILD_OBJECT_AREA, ///< build an area of objects
CMD_BUILD_TUNNEL, ///< build a tunnel
CMD_REMOVE_FROM_RAIL_STATION, ///< remove a (rectangle of) tiles from a rail station
@@ -329,12 +331,19 @@ enum Commands {
CMD_MOVE_ORDER, ///< move an order
CMD_CHANGE_TIMETABLE, ///< change the timetable for a vehicle
CMD_BULK_CHANGE_TIMETABLE, ///< change the timetable for all orders of a vehicle
CMD_SET_VEHICLE_ON_TIME, ///< set the vehicle on time feature (timetable)
CMD_AUTOFILL_TIMETABLE, ///< autofill the timetable
CMD_SET_TIMETABLE_START, ///< set the date that a timetable should start
CMD_OPEN_CLOSE_AIRPORT, ///< open/close an airport to incoming aircraft
CMD_CREATE_LEAGUE_TABLE, ///< create a new league table
CMD_CREATE_LEAGUE_TABLE_ELEMENT, ///< create a new element in a league table
CMD_UPDATE_LEAGUE_TABLE_ELEMENT_DATA, ///< update the data fields of a league table element
CMD_UPDATE_LEAGUE_TABLE_ELEMENT_SCORE, ///< update the score of a league table element
CMD_REMOVE_LEAGUE_TABLE_ELEMENT, ///< remove a league table element
CMD_END, ///< Must ALWAYS be on the end of this list!! (period)
};
@@ -360,28 +369,6 @@ enum DoCommandFlag {
};
DECLARE_ENUM_AS_BIT_SET(DoCommandFlag)
/**
* Used to combine a StringID with the command.
*
* This macro can be used to add a StringID (the error message to show) on a command-id
* (CMD_xxx). Use the binary or-operator "|" to combine the command with the result from
* this macro.
*
* @param x The StringID to combine with a command-id
*/
#define CMD_MSG(x) ((x) << 16)
/**
* Defines some flags.
*
* This enumeration defines some flags which are binary-or'ed on a command.
*/
enum FlaggedCommands {
CMD_NETWORK_COMMAND = 0x0100, ///< execute the command without sending it on the network
CMD_FLAGS_MASK = 0xFF00, ///< mask for all command flags
CMD_ID_MASK = 0x00FF, ///< mask for the command ID
};
/**
* Command flags for the command table _command_proc_table.
*
@@ -425,38 +412,42 @@ enum CommandPauseLevel {
CMDPL_ALL_ACTIONS, ///< All actions may be executed.
};
/**
* Defines the callback type for all command handler functions.
*
* This type defines the function header for all functions which handles a CMD_* command.
* A command handler use the parameters to act according to the meaning of the command.
* The tile parameter defines the tile to perform an action on.
* The flag parameter is filled with flags from the DC_* enumeration. The parameters
* p1 and p2 are filled with parameters for the command like "which road type", "which
* order" or "direction". Each function should mentioned in there doxygen comments
* the usage of these parameters.
*
* @param tile The tile to apply a command on
* @param flags Flags for the command, from the DC_* enumeration
* @param p1 Additional data for the command
* @param p2 Additional data for the command
* @param text Additional text
* @return The CommandCost of the command, which can be succeeded or failed.
*/
typedef CommandCost CommandProc(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text);
/**
* Define a command with the flags which belongs to it.
*
* This struct connect a command handler function with the flags created with
* the #CMD_AUTO, #CMD_OFFLINE and #CMD_SERVER values.
*/
struct Command {
CommandProc *proc; ///< The procedure to actually executing
const char *name; ///< A human readable name for the procedure
CommandFlags flags; ///< The (command) flags to that apply to this command
CommandType type; ///< The type of command.
template <typename T> struct CommandFunctionTraitHelper;
template <typename... Targs>
struct CommandFunctionTraitHelper<CommandCost(*)(DoCommandFlag, Targs...)> {
using Args = std::tuple<std::decay_t<Targs>...>;
using RetTypes = void;
using CbArgs = Args;
using CbProcType = void(*)(Commands, const CommandCost &);
};
template <template <typename...> typename Tret, typename... Tretargs, typename... Targs>
struct CommandFunctionTraitHelper<Tret<CommandCost, Tretargs...>(*)(DoCommandFlag, Targs...)> {
using Args = std::tuple<std::decay_t<Targs>...>;
using RetTypes = std::tuple<std::decay_t<Tretargs>...>;
using CbArgs = std::tuple<std::decay_t<Tretargs>..., std::decay_t<Targs>...>;
using CbProcType = void(*)(Commands, const CommandCost &, Tretargs...);
};
/** Defines the traits of a command. */
template <Commands Tcmd> struct CommandTraits;
#define DEF_CMD_TRAIT(cmd_, proc_, flags_, type_) \
template<> struct CommandTraits<cmd_> { \
using ProcType = decltype(&proc_); \
using Args = typename CommandFunctionTraitHelper<ProcType>::Args; \
using RetTypes = typename CommandFunctionTraitHelper<ProcType>::RetTypes; \
using CbArgs = typename CommandFunctionTraitHelper<ProcType>::CbArgs; \
using RetCallbackProc = typename CommandFunctionTraitHelper<ProcType>::CbProcType; \
static constexpr Commands cmd = cmd_; \
static constexpr auto &proc = proc_; \
static constexpr CommandFlags flags = (CommandFlags)(flags_); \
static constexpr CommandType type = type_; \
static inline constexpr const char *name = #proc_; \
};
/** Storage buffer for serialized command data. */
typedef std::vector<byte> CommandDataBuffer;
/**
* Define a callback function for the client, after the command is finished.
@@ -465,24 +456,27 @@ struct Command {
* are from the #CommandProc callback type. The boolean parameter indicates if the
* command succeeded or failed.
*
* @param cmd The command that was executed
* @param result The result of the executed command
* @param tile The tile of the command action
* @param p1 Additional data of the command
* @param p1 Additional data of the command
* @see CommandProc
*/
typedef void CommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd);
typedef void CommandCallback(Commands cmd, const CommandCost &result, TileIndex tile);
/**
* Structure for buffering the build command when selecting a station to join.
* Define a callback function for the client, after the command is finished.
*
* Functions of this type are called after the command is finished. The parameters
* are from the #CommandProc callback type. The boolean parameter indicates if the
* command succeeded or failed.
*
* @param cmd The command that was executed
* @param result The result of the executed command
* @param tile The tile of the command action
* @param data Additional data of the command
* @param result_data Additional returned data from the command
* @see CommandProc
*/
struct CommandContainer {
TileIndex tile; ///< tile command being executed on.
uint32 p1; ///< parameter p1.
uint32 p2; ///< parameter p2.
uint32 cmd; ///< command being executed.
CommandCallback *callback; ///< any callback function executed upon successful completion of the command.
std::string text; ///< possible text sent for name changes etc.
};
typedef void CommandCallbackData(Commands cmd, const CommandCost &result, TileIndex tile, const CommandDataBuffer &data, CommandDataBuffer result_data);
#endif /* COMMAND_TYPE_H */

View File

@@ -17,6 +17,7 @@
#include "settings_type.h"
#include "group.h"
#include <string>
#include <array>
/** Statistics about the economy. */
struct CompanyEconomyEntry {
@@ -74,7 +75,7 @@ struct CompanyProperties {
TileIndex location_of_HQ; ///< Northern tile of HQ; #INVALID_TILE when there is none.
TileIndex last_build_coordinate; ///< Coordinate of the last build thing by this company.
Owner share_owners[4]; ///< Owners of the 4 shares of the company. #INVALID_OWNER if nobody has bought them yet.
std::array<Owner, MAX_COMPANY_SHARE_OWNERS> share_owners; ///< Owners of the shares of the company. #INVALID_OWNER if nobody has bought them yet.
Year inaugurated_year; ///< Year of starting the company.
@@ -86,6 +87,7 @@ struct CompanyProperties {
uint32 terraform_limit; ///< Amount of tileheights we can (still) terraform (times 65536).
uint32 clear_limit; ///< Amount of tiles we can (still) clear (times 65536).
uint32 tree_limit; ///< Amount of trees we can (still) plant (times 65536).
uint32 build_object_limit; ///< Amount of tiles we can (still) build objects on (times 65536). Also applies to buying land.
/**
* If \c true, the company is (also) controlled by the computer (a NoAI program).
@@ -109,7 +111,7 @@ struct CompanyProperties {
face(0), money(0), money_fraction(0), current_loan(0), colour(0), block_preview(0),
location_of_HQ(0), last_build_coordinate(0), share_owners(), inaugurated_year(0),
months_of_bankruptcy(0), bankrupt_asked(0), bankrupt_timeout(0), bankrupt_value(0),
terraform_limit(0), clear_limit(0), tree_limit(0), is_ai(false), engine_renew_list(nullptr) {}
terraform_limit(0), clear_limit(0), tree_limit(0), build_object_limit(0), is_ai(false), engine_renew_list(nullptr) {}
};
struct Company : CompanyProperties, CompanyPool::PoolItem<&_company_pool> {
@@ -166,6 +168,7 @@ struct Company : CompanyProperties, CompanyPool::PoolItem<&_company_pool> {
};
Money CalculateCompanyValue(const Company *c, bool including_loan = true);
Money CalculateCompanyValueExcludingShares(const Company *c, bool including_loan = true);
extern uint _next_competitor_start;
extern uint _cur_company_tick_index;

View File

@@ -14,7 +14,6 @@
#include "core/backup_type.hpp"
#include "town.h"
#include "news_func.h"
#include "cmd_helper.h"
#include "command_func.h"
#include "network/network.h"
#include "network/network_func.h"
@@ -36,6 +35,7 @@
#include "goal_base.h"
#include "story_base.h"
#include "widgets/statusbar_widget.h"
#include "company_cmd.h"
#include "table/strings.h"
@@ -63,11 +63,12 @@ Company::Company(uint16 name_1, bool is_ai)
this->name_1 = name_1;
this->location_of_HQ = INVALID_TILE;
this->is_ai = is_ai;
this->terraform_limit = (uint32)_settings_game.construction.terraform_frame_burst << 16;
this->clear_limit = (uint32)_settings_game.construction.clear_frame_burst << 16;
this->tree_limit = (uint32)_settings_game.construction.tree_frame_burst << 16;
this->terraform_limit = (uint32)_settings_game.construction.terraform_frame_burst << 16;
this->clear_limit = (uint32)_settings_game.construction.clear_frame_burst << 16;
this->tree_limit = (uint32)_settings_game.construction.tree_frame_burst << 16;
this->build_object_limit = (uint32)_settings_game.construction.build_object_frame_burst << 16;
for (uint j = 0; j < 4; j++) this->share_owners[j] = INVALID_OWNER;
std::fill(this->share_owners.begin(), this->share_owners.end(), INVALID_OWNER);
InvalidateWindowData(WC_PERFORMANCE_DETAIL, 0, INVALID_COMPANY);
}
@@ -114,8 +115,11 @@ void SetLocalCompany(CompanyID new_company)
_current_company = _local_company = new_company;
/* Delete any construction windows... */
if (switching_company) CloseConstructionWindows();
if (switching_company) {
InvalidateWindowClassesData(WC_COMPANY);
/* Delete any construction windows... */
CloseConstructionWindows();
}
/* ... and redraw the whole screen. */
MarkWholeScreenDirty();
@@ -219,17 +223,17 @@ static void SubtractMoneyFromAnyCompany(Company *c, const CommandCost &cost)
c->money -= cost.GetCost();
c->yearly_expenses[0][cost.GetExpensesType()] += cost.GetCost();
if (HasBit(1 << EXPENSES_TRAIN_INC |
1 << EXPENSES_ROADVEH_INC |
1 << EXPENSES_AIRCRAFT_INC |
1 << EXPENSES_SHIP_INC, cost.GetExpensesType())) {
if (HasBit(1 << EXPENSES_TRAIN_REVENUE |
1 << EXPENSES_ROADVEH_REVENUE |
1 << EXPENSES_AIRCRAFT_REVENUE |
1 << EXPENSES_SHIP_REVENUE, cost.GetExpensesType())) {
c->cur_economy.income -= cost.GetCost();
} else if (HasBit(1 << EXPENSES_TRAIN_RUN |
1 << EXPENSES_ROADVEH_RUN |
1 << EXPENSES_AIRCRAFT_RUN |
1 << EXPENSES_SHIP_RUN |
1 << EXPENSES_PROPERTY |
1 << EXPENSES_LOAN_INT, cost.GetExpensesType())) {
1 << EXPENSES_LOAN_INTEREST, cost.GetExpensesType())) {
c->cur_economy.expenses -= cost.GetCost();
}
@@ -267,9 +271,10 @@ void SubtractMoneyFromCompanyFract(CompanyID company, const CommandCost &cst)
void UpdateLandscapingLimits()
{
for (Company *c : Company::Iterate()) {
c->terraform_limit = std::min<uint64>((uint64)c->terraform_limit + _settings_game.construction.terraform_per_64k_frames, (uint64)_settings_game.construction.terraform_frame_burst << 16);
c->clear_limit = std::min<uint64>((uint64)c->clear_limit + _settings_game.construction.clear_per_64k_frames, (uint64)_settings_game.construction.clear_frame_burst << 16);
c->tree_limit = std::min<uint64>((uint64)c->tree_limit + _settings_game.construction.tree_per_64k_frames, (uint64)_settings_game.construction.tree_frame_burst << 16);
c->terraform_limit = std::min<uint64>((uint64)c->terraform_limit + _settings_game.construction.terraform_per_64k_frames, (uint64)_settings_game.construction.terraform_frame_burst << 16);
c->clear_limit = std::min<uint64>((uint64)c->clear_limit + _settings_game.construction.clear_per_64k_frames, (uint64)_settings_game.construction.clear_frame_burst << 16);
c->tree_limit = std::min<uint64>((uint64)c->tree_limit + _settings_game.construction.tree_per_64k_frames, (uint64)_settings_game.construction.tree_frame_burst << 16);
c->build_object_limit = std::min<uint64>((uint64)c->build_object_limit + _settings_game.construction.build_object_per_64k_frames, (uint64)_settings_game.construction.build_object_frame_burst << 16);
}
}
@@ -557,12 +562,19 @@ Company *DoStartupNewCompany(bool is_ai, CompanyID company = INVALID_COMPANY)
c->money = c->current_loan = (std::min<int64>(INITIAL_LOAN, _economy.max_loan) * _economy.inflation_prices >> 16) / 50000 * 50000;
c->share_owners[0] = c->share_owners[1] = c->share_owners[2] = c->share_owners[3] = INVALID_OWNER;
std::fill(c->share_owners.begin(), c->share_owners.end(), INVALID_OWNER);
c->avail_railtypes = GetCompanyRailtypes(c->index);
c->avail_roadtypes = GetCompanyRoadTypes(c->index);
c->inaugurated_year = _cur_year;
RandomCompanyManagerFaceBits(c->face, (GenderEthnicity)Random(), false, false); // create a random company manager face
/* If starting a player company in singleplayer and a favorite company manager face is selected, choose it. Otherwise, use a random face.
* In a network game, we'll choose the favorite face later in CmdCompanyCtrl to sync it to all clients. */
if (_company_manager_face != 0 && !is_ai && !_networking) {
c->face = _company_manager_face;
} else {
RandomCompanyManagerFaceBits(c->face, (GenderEthnicity)Random(), false, false);
}
SetDefaultCompanySettings(c->index);
ClearEnginesHiddenFlagOfCompany(c->index);
@@ -603,7 +615,7 @@ static bool MaybeStartNewCompany()
if (n < (uint)_settings_game.difficulty.max_no_competitors) {
/* Send a command to all clients to start up a new AI.
* Works fine for Multiplayer and Singleplayer */
return DoCommandP(0, CCA_NEW_AI | INVALID_COMPANY << 16, 0, CMD_COMPANY_CTRL);
return Command<CMD_COMPANY_CTRL>::Post(CCA_NEW_AI, INVALID_COMPANY, CRR_NONE, INVALID_CLIENT_ID );
}
return false;
@@ -794,22 +806,17 @@ void CompanyAdminRemove(CompanyID company_id, CompanyRemoveReason reason)
/**
* Control the companies: add, delete, etc.
* @param tile unused
* @param flags operation to perform
* @param p1 various functionality
* - bits 0..15: CompanyCtrlAction
* - bits 16..23: CompanyID
* - bits 24..31: CompanyRemoveReason (with CCA_DELETE)
* @param p2 ClientID
* @param text unused
* @param cca action to perform
* @param company_id company to perform the action on
* @param client_id ClientID
* @return the cost of this operation or an error
*/
CommandCost CmdCompanyCtrl(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdCompanyCtrl(DoCommandFlag flags, CompanyCtrlAction cca, CompanyID company_id, CompanyRemoveReason reason, ClientID client_id)
{
InvalidateWindowData(WC_COMPANY_LEAGUE, 0, 0);
CompanyID company_id = (CompanyID)GB(p1, 16, 8);
switch ((CompanyCtrlAction)GB(p1, 0, 16)) {
switch (cca) {
case CCA_NEW: { // Create a new company
/* This command is only executed in a multiplayer game */
if (!_networking) return CMD_ERROR;
@@ -817,7 +824,6 @@ CommandCost CmdCompanyCtrl(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
/* Has the network client a correct ClientIndex? */
if (!(flags & DC_EXEC)) return CommandCost();
ClientID client_id = (ClientID)p2;
NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(client_id);
/* Delete multiplayer progress bar */
@@ -844,6 +850,10 @@ CommandCost CmdCompanyCtrl(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
NetworkChangeCompanyPassword(_local_company, _settings_client.network.default_company_pass);
}
/* In network games, we need to try setting the company manager face here to sync it to all clients.
* If a favorite company manager face is selected, choose it. Otherwise, use a random face. */
if (_company_manager_face != 0) Command<CMD_SET_COMPANY_MANAGER_FACE>::SendNet(STR_NULL, c->index, _company_manager_face);
/* Now that we have a new company, broadcast our company settings to
* all clients so everything is in sync */
SyncCompanySettings();
@@ -872,7 +882,6 @@ CommandCost CmdCompanyCtrl(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
}
case CCA_DELETE: { // Delete a company
CompanyRemoveReason reason = (CompanyRemoveReason)GB(p1, 24, 8);
if (reason >= CRR_END) return CMD_ERROR;
/* We can't delete the last existing company in singleplayer mode. */
@@ -883,8 +892,6 @@ CommandCost CmdCompanyCtrl(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
if (!(flags & DC_EXEC)) return CommandCost();
/* Delete any open window of the company */
CloseCompanyWindows(c->index);
CompanyNewsInformation *cni = new CompanyNewsInformation(c);
/* Show the bankrupt news */
@@ -921,17 +928,12 @@ CommandCost CmdCompanyCtrl(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
/**
* Change the company manager's face.
* @param tile unused
* @param flags operation to perform
* @param p1 unused
* @param p2 face bitmasked
* @param text unused
* @param cmf face bitmasked
* @return the cost of this operation or an error
*/
CommandCost CmdSetCompanyManagerFace(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdSetCompanyManagerFace(DoCommandFlag flags, CompanyManagerFace cmf)
{
CompanyManagerFace cmf = (CompanyManagerFace)p2;
if (!IsValidCompanyManagerFace(cmf)) return CMD_ERROR;
if (flags & DC_EXEC) {
@@ -943,21 +945,14 @@ CommandCost CmdSetCompanyManagerFace(TileIndex tile, DoCommandFlag flags, uint32
/**
* Change the company's company-colour
* @param tile unused
* @param flags operation to perform
* @param p1 bitstuffed:
* p1 bits 0-7 scheme to set
* p1 bit 8 set first/second colour
* @param p2 new colour for vehicles, property, etc.
* @param text unused
* @param scheme scheme to set
* @param primary set first/second colour
* @param colour new colour for vehicles, property, etc.
* @return the cost of this operation or an error
*/
CommandCost CmdSetCompanyColour(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdSetCompanyColour(DoCommandFlag flags, LiveryScheme scheme, bool primary, Colours colour)
{
Colours colour = Extract<Colours, 0, 8>(p2);
LiveryScheme scheme = Extract<LiveryScheme, 0, 8>(p1);
bool second = HasBit(p1, 8);
if (scheme >= LS_END || (colour >= COLOUR_END && colour != INVALID_COLOUR)) return CMD_ERROR;
/* Default scheme can't be reset to invalid. */
@@ -966,14 +961,14 @@ CommandCost CmdSetCompanyColour(TileIndex tile, DoCommandFlag flags, uint32 p1,
Company *c = Company::Get(_current_company);
/* Ensure no two companies have the same primary colour */
if (scheme == LS_DEFAULT && !second) {
if (scheme == LS_DEFAULT && primary) {
for (const Company *cc : Company::Iterate()) {
if (cc != c && cc->colour == colour) return CMD_ERROR;
}
}
if (flags & DC_EXEC) {
if (!second) {
if (primary) {
if (scheme != LS_DEFAULT) SB(c->livery[scheme].in_use, 0, 1, colour != INVALID_COLOUR);
if (colour == INVALID_COLOUR) colour = (Colours)c->livery[LS_DEFAULT].colour1;
c->livery[scheme].colour1 = colour;
@@ -1056,14 +1051,11 @@ static bool IsUniqueCompanyName(const std::string &name)
/**
* Change the name of the company.
* @param tile unused
* @param flags operation to perform
* @param p1 unused
* @param p2 unused
* @param text the new name or an empty string when resetting to the default
* @return the cost of this operation or an error
*/
CommandCost CmdRenameCompany(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdRenameCompany(DoCommandFlag flags, const std::string &text)
{
bool reset = text.empty();
@@ -1102,14 +1094,11 @@ static bool IsUniquePresidentName(const std::string &name)
/**
* Change the name of the president.
* @param tile unused
* @param flags operation to perform
* @param p1 unused
* @param p2 unused
* @param text the new name or an empty string when resetting to the default
* @return the cost of this operation or an error
*/
CommandCost CmdRenamePresident(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdRenamePresident(DoCommandFlag flags, const std::string &text)
{
bool reset = text.empty();
@@ -1127,7 +1116,7 @@ CommandCost CmdRenamePresident(TileIndex tile, DoCommandFlag flags, uint32 p1, u
c->president_name = text;
if (c->name_1 == STR_SV_UNNAMED && c->name.empty()) {
DoCommand(0, 0, 0, DC_EXEC, CMD_RENAME_COMPANY, text + " Transport");
Command<CMD_RENAME_COMPANY>::Do(DC_EXEC, text + " Transport");
}
}
@@ -1187,20 +1176,17 @@ uint32 CompanyInfrastructure::GetTramTotal() const
* To prevent abuse in multiplayer games you can only send money to other
* companies if you have paid off your loan (either explicitly, or implicitly
* given the fact that you have more money than loan).
* @param tile unused
* @param flags operation to perform
* @param p1 the amount of money to transfer; max 20.000.000
* @param p2 the company to transfer the money to
* @param text unused
* @param money the amount of money to transfer; max 20.000.000
* @param dest_company the company to transfer the money to
* @return the cost of this operation or an error
*/
CommandCost CmdGiveMoney(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdGiveMoney(DoCommandFlag flags, uint32 money, CompanyID dest_company)
{
if (!_settings_game.economy.give_money) return CMD_ERROR;
const Company *c = Company::Get(_current_company);
CommandCost amount(EXPENSES_OTHER, std::min<Money>(p1, 20000000LL));
CompanyID dest_company = (CompanyID)p2;
CommandCost amount(EXPENSES_OTHER, std::min<Money>(money, 20000000LL));
/* You can only transfer funds that is in excess of your loan */
if (c->money - c->current_loan < amount.GetCost() || amount.GetCost() < 0) return_cmd_error(STR_ERROR_INSUFFICIENT_FUNDS);
@@ -1226,3 +1212,31 @@ CommandCost CmdGiveMoney(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
/* Subtract money from local-company */
return amount;
}
/**
* Get the index of the first available company. It attempts,
* from first to last, and as soon as the attempt succeeds,
* to get the index of the company:
* 1st - get the first existing human company.
* 2nd - get the first non-existing company.
* 3rd - get COMPANY_FIRST.
* @return the index of the first available company.
*/
CompanyID GetFirstPlayableCompanyID()
{
for (Company *c : Company::Iterate()) {
if (Company::IsHumanID(c->index)) {
return c->index;
}
}
if (Company::CanAllocateItem()) {
for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
if (!Company::IsValidID(c)) {
return c;
}
}
}
return COMPANY_FIRST;
}

34
src/company_cmd.h Normal file
View File

@@ -0,0 +1,34 @@
/*
* 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 company_cmd.h Command definitions related to companies. */
#ifndef COMPANY_CMD_H
#define COMPANY_CMD_H
#include "command_type.h"
#include "company_type.h"
#include "livery.h"
enum ClientID : uint32;
enum Colours : byte;
CommandCost CmdCompanyCtrl(DoCommandFlag flags, CompanyCtrlAction cca, CompanyID company_id, CompanyRemoveReason reason, ClientID client_id);
CommandCost CmdGiveMoney(DoCommandFlag flags, uint32 money, CompanyID dest_company);
CommandCost CmdRenameCompany(DoCommandFlag flags, const std::string &text);
CommandCost CmdRenamePresident(DoCommandFlag flags, const std::string &text);
CommandCost CmdSetCompanyManagerFace(DoCommandFlag flags, CompanyManagerFace cmf);
CommandCost CmdSetCompanyColour(DoCommandFlag flags, LiveryScheme scheme, bool primary, Colours colour);
DEF_CMD_TRAIT(CMD_COMPANY_CTRL, CmdCompanyCtrl, CMD_SPECTATOR | CMD_CLIENT_ID | CMD_NO_EST, CMDT_SERVER_SETTING)
DEF_CMD_TRAIT(CMD_GIVE_MONEY, CmdGiveMoney, 0, CMDT_MONEY_MANAGEMENT)
DEF_CMD_TRAIT(CMD_RENAME_COMPANY, CmdRenameCompany, 0, CMDT_OTHER_MANAGEMENT)
DEF_CMD_TRAIT(CMD_RENAME_PRESIDENT, CmdRenamePresident, 0, CMDT_OTHER_MANAGEMENT)
DEF_CMD_TRAIT(CMD_SET_COMPANY_MANAGER_FACE, CmdSetCompanyManagerFace, 0, CMDT_OTHER_MANAGEMENT)
DEF_CMD_TRAIT(CMD_SET_COMPANY_COLOUR, CmdSetCompanyColour, 0, CMDT_OTHER_MANAGEMENT)
#endif /* COMPANY_CMD_H */

View File

@@ -27,7 +27,7 @@ void UpdateLandscapingLimits();
bool CheckCompanyHasMoney(CommandCost &cost);
void SubtractMoneyFromCompany(const CommandCost& cost);
void SubtractMoneyFromCompanyFract(CompanyID company, const CommandCost& cost);
CommandCost CheckOwnership(Owner owner, TileIndex tile = 0);
CommandCost CheckOwnership(Owner owner, TileIndex tile = 0U);
CommandCost CheckTileOwnership(TileIndex tile);
extern CompanyID _local_company;
@@ -56,5 +56,6 @@ static inline bool IsInteractiveCompany(CompanyID company)
}
int CompanyServiceInterval(const Company *c, VehicleType type);
CompanyID GetFirstPlayableCompanyID();
#endif /* COMPANY_FUNC_H */

File diff suppressed because it is too large Load Diff

View File

@@ -40,6 +40,7 @@ static const uint MAX_LENGTH_PRESIDENT_NAME_CHARS = 32; ///< The maximum length
static const uint MAX_LENGTH_COMPANY_NAME_CHARS = 32; ///< The maximum length of a company name in characters including '\0'
static const uint MAX_HISTORY_QUARTERS = 24; ///< The maximum number of quarters kept as performance's history
static const uint MAX_COMPANY_SHARE_OWNERS = 4; ///< The maximum number of shares of a company that can be owned by another company.
/** Define basic enum properties */
template <> struct EnumPropsT<Owner> : MakeEnumPropsT<Owner, byte, OWNER_BEGIN, OWNER_END, INVALID_OWNER> {};
@@ -52,16 +53,18 @@ struct Company;
typedef uint32 CompanyManagerFace; ///< Company manager face bits, info see in company_manager_face.h
/** The reason why the company was removed. */
enum CompanyRemoveReason {
enum CompanyRemoveReason : uint8 {
CRR_MANUAL, ///< The company is manually removed.
CRR_AUTOCLEAN, ///< The company is removed due to autoclean.
CRR_BANKRUPT, ///< The company went belly-up.
CRR_END, ///< Sentinel for end.
CRR_NONE = CRR_MANUAL, ///< Dummy reason for actions that don't need one.
};
/** The action to do with CMD_COMPANY_CTRL. */
enum CompanyCtrlAction {
enum CompanyCtrlAction : uint8 {
CCA_NEW, ///< Create a new company.
CCA_NEW_AI, ///< Create a new AI company.
CCA_DELETE, ///< Delete a company.

View File

@@ -123,6 +123,7 @@ void IConsolePrint(TextColour colour_code, const std::string &string)
IConsoleWriteToLogFile(str);
IConsoleGUIPrint(colour_code, str);
free(str);
}
/**

View File

@@ -23,6 +23,7 @@
#include "settings_func.h"
#include "fios.h"
#include "fileio_func.h"
#include "fontcache.h"
#include "screenshot.h"
#include "genworld.h"
#include "strings_func.h"
@@ -42,6 +43,10 @@
#include "game/game.hpp"
#include "table/strings.h"
#include "walltime_func.h"
#include "company_cmd.h"
#include "misc_cmd.h"
#include <sstream>
#include "safeguards.h"
@@ -251,6 +256,59 @@ DEF_CONSOLE_CMD(ConResetTile)
}
#endif /* _DEBUG */
/**
* Zoom map to given level.
* param level As defined by ZoomLevel and as limited by zoom_min/zoom_max from GUISettings.
* @return True when either console help was shown or a proper amount of parameters given.
*/
DEF_CONSOLE_CMD(ConZoomToLevel)
{
switch (argc) {
case 0:
IConsolePrint(CC_HELP, "Set the current zoom level of the main viewport.");
IConsolePrint(CC_HELP, "Usage: 'zoomto <level>'.");
IConsolePrint(
CC_HELP,
ZOOM_LVL_MIN < _settings_client.gui.zoom_min ?
"The lowest zoom-in level allowed by current client settings is {}." :
"The lowest supported zoom-in level is {}.",
std::max(ZOOM_LVL_MIN, _settings_client.gui.zoom_min)
);
IConsolePrint(
CC_HELP,
_settings_client.gui.zoom_max < ZOOM_LVL_MAX ?
"The highest zoom-out level allowed by current client settings is {}." :
"The highest supported zoom-out level is {}.",
std::min(_settings_client.gui.zoom_max, ZOOM_LVL_MAX)
);
return true;
case 2: {
uint32 level;
if (GetArgumentInteger(&level, argv[1])) {
if (level < ZOOM_LVL_MIN) {
IConsolePrint(CC_ERROR, "Zoom-in levels below {} are not supported.", ZOOM_LVL_MIN);
} else if (level < _settings_client.gui.zoom_min) {
IConsolePrint(CC_ERROR, "Current client settings do not allow zooming in below level {}.", _settings_client.gui.zoom_min);
} else if (level > ZOOM_LVL_MAX) {
IConsolePrint(CC_ERROR, "Zoom-in levels above {} are not supported.", ZOOM_LVL_MAX);
} else if (level > _settings_client.gui.zoom_max) {
IConsolePrint(CC_ERROR, "Current client settings do not allow zooming out beyond level {}.", _settings_client.gui.zoom_max);
} else {
Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
Viewport *vp = w->viewport;
while (vp->zoom > level) DoZoomInOutWindow(ZOOM_IN, w);
while (vp->zoom < level) DoZoomInOutWindow(ZOOM_OUT, w);
}
return true;
}
break;
}
}
return false;
}
/**
* Scroll to a tile on the map.
* param x tile number or tile x coordinate.
@@ -262,34 +320,44 @@ DEF_CONSOLE_CMD(ConResetTile)
*/
DEF_CONSOLE_CMD(ConScrollToTile)
{
switch (argc) {
case 0:
IConsolePrint(CC_HELP, "Center the screen on a given tile.");
IConsolePrint(CC_HELP, "Usage: 'scrollto <tile>' or 'scrollto <x> <y>'.");
IConsolePrint(CC_HELP, "Numbers can be either decimal (34161) or hexadecimal (0x4a5B).");
return true;
if (argc == 0) {
IConsolePrint(CC_HELP, "Center the screen on a given tile.");
IConsolePrint(CC_HELP, "Usage: 'scrollto [instant] <tile>' or 'scrollto [instant] <x> <y>'.");
IConsolePrint(CC_HELP, "Numbers can be either decimal (34161) or hexadecimal (0x4a5B).");
IConsolePrint(CC_HELP, "'instant' will immediately move and redraw viewport without smooth scrolling.");
return true;
}
if (argc < 2) return false;
case 2: {
uint32 arg_index = 1;
bool instant = false;
if (strcmp(argv[arg_index], "instant") == 0) {
++arg_index;
instant = true;
}
switch (argc - arg_index) {
case 1: {
uint32 result;
if (GetArgumentInteger(&result, argv[1])) {
if (GetArgumentInteger(&result, argv[arg_index])) {
if (result >= MapSize()) {
IConsolePrint(CC_ERROR, "Tile does not exist.");
return true;
}
ScrollMainWindowToTile((TileIndex)result);
ScrollMainWindowToTile((TileIndex)result, instant);
return true;
}
break;
}
case 3: {
case 2: {
uint32 x, y;
if (GetArgumentInteger(&x, argv[1]) && GetArgumentInteger(&y, argv[2])) {
if (GetArgumentInteger(&x, argv[arg_index]) && GetArgumentInteger(&y, argv[arg_index + 1])) {
if (x >= MapSizeX() || y >= MapSizeY()) {
IConsolePrint(CC_ERROR, "Tile does not exist.");
return true;
}
ScrollMainWindowToTile(TileXY(x, y));
ScrollMainWindowToTile(TileXY(x, y), instant);
return true;
}
break;
@@ -644,7 +712,7 @@ DEF_CONSOLE_CMD(ConPauseGame)
}
if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED) {
DoCommandP(0, PM_PAUSED_NORMAL, 1, CMD_PAUSE);
Command<CMD_PAUSE>::Post(PM_PAUSED_NORMAL, true);
if (!_networking) IConsolePrint(CC_DEFAULT, "Game paused.");
} else {
IConsolePrint(CC_DEFAULT, "Game is already paused.");
@@ -666,7 +734,7 @@ DEF_CONSOLE_CMD(ConUnpauseGame)
}
if ((_pause_mode & PM_PAUSED_NORMAL) != PM_UNPAUSED) {
DoCommandP(0, PM_PAUSED_NORMAL, 0, CMD_PAUSE);
Command<CMD_PAUSE>::Post(PM_PAUSED_NORMAL, false);
if (!_networking) IConsolePrint(CC_DEFAULT, "Game unpaused.");
} else if ((_pause_mode & PM_PAUSED_ERROR) != PM_UNPAUSED) {
IConsolePrint(CC_DEFAULT, "Game is in error state and cannot be unpaused via console.");
@@ -877,7 +945,7 @@ DEF_CONSOLE_CMD(ConResetCompany)
}
/* It is safe to remove this company */
DoCommandP(0, CCA_DELETE | index << 16 | CRR_MANUAL << 24, 0, CMD_COMPANY_CTRL);
Command<CMD_COMPANY_CTRL>::Post(CCA_DELETE, index, CRR_MANUAL, INVALID_CLIENT_ID);
IConsolePrint(CC_DEFAULT, "Company deleted.");
return true;
@@ -1109,58 +1177,65 @@ DEF_CONSOLE_CMD(ConReload)
/**
* Print a text buffer line by line to the console. Lines are separated by '\n'.
* @param buf The buffer to print.
* @note All newlines are replace by '\0' characters.
* @param full_string The multi-line string to print.
*/
static void PrintLineByLine(char *buf)
static void PrintLineByLine(const std::string &full_string)
{
char *p = buf;
/* Print output line by line */
for (char *p2 = buf; *p2 != '\0'; p2++) {
if (*p2 == '\n') {
*p2 = '\0';
IConsolePrint(CC_DEFAULT, p);
p = p2 + 1;
}
std::istringstream in(full_string);
std::string line;
while (std::getline(in, line)) {
IConsolePrint(CC_DEFAULT, line.c_str());
}
}
DEF_CONSOLE_CMD(ConListAILibs)
{
char buf[4096];
AI::GetConsoleLibraryList(buf, lastof(buf));
if (argc == 0) {
IConsolePrint(CC_HELP, "List installed AI libraries. Usage: 'list_ai_libs'.");
return true;
}
PrintLineByLine(buf);
const std::string output_str = AI::GetConsoleLibraryList();
PrintLineByLine(output_str);
return true;
}
DEF_CONSOLE_CMD(ConListAI)
{
char buf[4096];
AI::GetConsoleList(buf, lastof(buf));
if (argc == 0) {
IConsolePrint(CC_HELP, "List installed AIs. Usage: 'list_ai'.");
return true;
}
PrintLineByLine(buf);
const std::string output_str = AI::GetConsoleList();
PrintLineByLine(output_str);
return true;
}
DEF_CONSOLE_CMD(ConListGameLibs)
{
char buf[4096];
Game::GetConsoleLibraryList(buf, lastof(buf));
if (argc == 0) {
IConsolePrint(CC_HELP, "List installed Game Script libraries. Usage: 'list_game_libs'.");
return true;
}
PrintLineByLine(buf);
const std::string output_str = Game::GetConsoleLibraryList();
PrintLineByLine(output_str);
return true;
}
DEF_CONSOLE_CMD(ConListGame)
{
char buf[4096];
Game::GetConsoleList(buf, lastof(buf));
if (argc == 0) {
IConsolePrint(CC_HELP, "List installed Game Scripts. Usage: 'list_game'.");
return true;
}
PrintLineByLine(buf);
const std::string output_str = Game::GetConsoleList();
PrintLineByLine(output_str);
return true;
}
@@ -1234,7 +1309,7 @@ DEF_CONSOLE_CMD(ConStartAI)
}
/* Start a new AI company */
DoCommandP(0, CCA_NEW_AI | INVALID_COMPANY << 16, 0, CMD_COMPANY_CTRL);
Command<CMD_COMPANY_CTRL>::Post(CCA_NEW_AI, INVALID_COMPANY, CRR_NONE, INVALID_CLIENT_ID);
return true;
}
@@ -1270,8 +1345,8 @@ DEF_CONSOLE_CMD(ConReloadAI)
}
/* First kill the company of the AI, then start a new one. This should start the current AI again */
DoCommandP(0, CCA_DELETE | company_id << 16 | CRR_MANUAL << 24, 0, CMD_COMPANY_CTRL);
DoCommandP(0, CCA_NEW_AI | company_id << 16, 0, CMD_COMPANY_CTRL);
Command<CMD_COMPANY_CTRL>::Post(CCA_DELETE, company_id, CRR_MANUAL, INVALID_CLIENT_ID);
Command<CMD_COMPANY_CTRL>::Post(CCA_NEW_AI, company_id, CRR_NONE, INVALID_CLIENT_ID);
IConsolePrint(CC_DEFAULT, "AI reloaded.");
return true;
@@ -1308,7 +1383,7 @@ DEF_CONSOLE_CMD(ConStopAI)
}
/* Now kill the company of the AI. */
DoCommandP(0, CCA_DELETE | company_id << 16 | CRR_MANUAL << 24, 0, CMD_COMPANY_CTRL);
Command<CMD_COMPANY_CTRL>::Post(CCA_DELETE, company_id, CRR_MANUAL, INVALID_CLIENT_ID);
IConsolePrint(CC_DEFAULT, "AI stopped, company deleted.");
return true;
@@ -1433,6 +1508,7 @@ DEF_CONSOLE_CMD(ConScreenShot)
IConsolePrint(CC_HELP, " 'minimap' makes a top-viewed minimap screenshot of the whole world which represents one tile by one pixel.");
IConsolePrint(CC_HELP, " 'no_con' hides the console to create the screenshot (only useful in combination with 'viewport').");
IConsolePrint(CC_HELP, " 'size' sets the width and height of the viewport to make a screenshot of (only useful in combination with 'normal' or 'big').");
IConsolePrint(CC_HELP, " A filename ending in # will prevent overwriting existing files and will number files counting upwards.");
return true;
}
@@ -1536,7 +1612,7 @@ DEF_CONSOLE_CMD(ConDebugLevel)
if (argc == 1) {
IConsolePrint(CC_DEFAULT, "Current debug-level: '{}'", GetDebugString());
} else {
SetDebugString(argv[1]);
SetDebugString(argv[1], [](const char *err) { IConsolePrint(CC_ERROR, std::string(err)); });
}
return true;
@@ -1921,6 +1997,82 @@ DEF_CONSOLE_CMD(ConContent)
}
#endif /* defined(WITH_ZLIB) */
DEF_CONSOLE_CMD(ConFont)
{
if (argc == 0) {
IConsolePrint(CC_HELP, "Manage the fonts configuration.");
IConsolePrint(CC_HELP, "Usage 'font'.");
IConsolePrint(CC_HELP, " Print out the fonts configuration.");
IConsolePrint(CC_HELP, "Usage 'font [medium|small|large|mono] [<name>] [<size>] [aa|noaa]'.");
IConsolePrint(CC_HELP, " Change the configuration for a font.");
IConsolePrint(CC_HELP, " Omitting an argument will keep the current value.");
IConsolePrint(CC_HELP, " Set <name> to \"\" for the sprite font (size and aa have no effect on sprite font).");
return true;
}
FontSize argfs;
for (argfs = FS_BEGIN; argfs < FS_END; argfs++) {
if (argc > 1 && strcasecmp(argv[1], FontSizeToName(argfs)) == 0) break;
}
/* First argument must be a FontSize. */
if (argc > 1 && argfs == FS_END) return false;
if (argc > 2) {
FontCacheSubSetting *setting = GetFontCacheSubSetting(argfs);
std::string font = setting->font;
uint size = setting->size;
bool aa = setting->aa;
byte arg_index = 2;
if (argc > arg_index) {
/* We may encounter "aa" or "noaa" but it must be the last argument. */
if (strcasecmp(argv[arg_index], "aa") == 0 || strcasecmp(argv[arg_index], "noaa") == 0) {
aa = strncasecmp(argv[arg_index++], "no", 2) != 0;
if (argc > arg_index) return false;
} else {
/* For <name> we want a string. */
uint v;
if (!GetArgumentInteger(&v, argv[arg_index])) {
font = argv[arg_index++];
}
}
}
if (argc > arg_index) {
/* For <size> we want a number. */
uint v;
if (GetArgumentInteger(&v, argv[arg_index])) {
size = v;
arg_index++;
}
}
if (argc > arg_index) {
/* Last argument must be "aa" or "noaa". */
if (strcasecmp(argv[arg_index], "aa") != 0 && strcasecmp(argv[arg_index], "noaa") != 0) return false;
aa = strncasecmp(argv[arg_index++], "no", 2) != 0;
if (argc > arg_index) return false;
}
SetFont(argfs, font, size, aa);
}
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
FontCache *fc = FontCache::Get(fs);
FontCacheSubSetting *setting = GetFontCacheSubSetting(fs);
/* Make sure all non sprite fonts are loaded. */
if (!setting->font.empty() && !fc->HasParent()) {
InitFontCache(fs == FS_MONO);
fc = FontCache::Get(fs);
}
IConsolePrint(CC_DEFAULT, "{}: \"{}\" {} {} [\"{}\" {} {}]", FontSizeToName(fs), fc->GetFontName(), fc->GetFontSize(), GetFontAAState(fs), setting->font, setting->size, setting->aa);
}
return true;
}
DEF_CONSOLE_CMD(ConSetting)
{
if (argc == 0) {
@@ -2408,6 +2560,7 @@ void IConsoleStdLibRegister()
IConsole::CmdRegister("return", ConReturn);
IConsole::CmdRegister("screenshot", ConScreenShot);
IConsole::CmdRegister("script", ConScript);
IConsole::CmdRegister("zoomto", ConZoomToLevel);
IConsole::CmdRegister("scrollto", ConScrollToTile);
IConsole::CmdRegister("alias", ConAlias);
IConsole::CmdRegister("load", ConLoad);
@@ -2418,6 +2571,7 @@ void IConsoleStdLibRegister()
IConsole::CmdRegister("cd", ConChangeDirectory);
IConsole::CmdRegister("pwd", ConPrintWorkingDirectory);
IConsole::CmdRegister("clear", ConClearBuffer);
IConsole::CmdRegister("font", ConFont);
IConsole::CmdRegister("setting", ConSetting);
IConsole::CmdRegister("setting_newgame", ConSettingNewgame);
IConsole::CmdRegister("list_settings", ConListSettings);

View File

@@ -22,6 +22,8 @@
#include "rev.h"
#include "video/video_driver.hpp"
#include "textbuf_gui.h"
#include <deque>
#include <string>
#include "widgets/console_widget.h"
@@ -37,7 +39,6 @@
#endif
static const uint ICON_HISTORY_SIZE = 20;
static const uint ICON_LINE_SPACING = 2;
static const uint ICON_RIGHT_BORDERWIDTH = 10;
static const uint ICON_BOTTOM_BORDERWIDTH = 12;
@@ -45,94 +46,36 @@ static const uint ICON_BOTTOM_BORDERWIDTH = 12;
* Container for a single line of console output
*/
struct IConsoleLine {
static IConsoleLine *front; ///< The front of the console backlog buffer
static int size; ///< The amount of items in the backlog
IConsoleLine *previous; ///< The previous console message.
char *buffer; ///< The data to store.
std::string buffer; ///< The data to store.
TextColour colour; ///< The colour of the line.
uint16 time; ///< The amount of time the line is in the backlog.
IConsoleLine() : buffer(), colour(TC_BEGIN), time(0)
{
}
/**
* Initialize the console line.
* @param buffer the data to print.
* @param colour the colour of the line.
*/
IConsoleLine(char *buffer, TextColour colour) :
previous(IConsoleLine::front),
buffer(buffer),
IConsoleLine(std::string buffer, TextColour colour) :
buffer(std::move(buffer)),
colour(colour),
time(0)
{
IConsoleLine::front = this;
IConsoleLine::size++;
}
/**
* Clear this console line and any further ones.
*/
~IConsoleLine()
{
IConsoleLine::size--;
free(buffer);
delete previous;
}
/**
* Get the index-ed item in the list.
*/
static const IConsoleLine *Get(uint index)
{
const IConsoleLine *item = IConsoleLine::front;
while (index != 0 && item != nullptr) {
index--;
item = item->previous;
}
return item;
}
/**
* Truncate the list removing everything older than/more than the amount
* as specified in the config file.
* As a side effect also increase the time the other lines have been in
* the list.
* @return true if and only if items got removed.
*/
static bool Truncate()
{
IConsoleLine *cur = IConsoleLine::front;
if (cur == nullptr) return false;
int count = 1;
for (IConsoleLine *item = cur->previous; item != nullptr; count++, cur = item, item = item->previous) {
if (item->time > _settings_client.gui.console_backlog_timeout &&
count > _settings_client.gui.console_backlog_length) {
delete item;
cur->previous = nullptr;
return true;
}
if (item->time != MAX_UVALUE(uint16)) item->time++;
}
return false;
}
/**
* Reset the complete console line backlog.
*/
static void Reset()
{
delete IConsoleLine::front;
IConsoleLine::front = nullptr;
IConsoleLine::size = 0;
}
};
/* static */ IConsoleLine *IConsoleLine::front = nullptr;
/* static */ int IConsoleLine::size = 0;
/** The console backlog buffer. Item index 0 is the newest line. */
static std::deque<IConsoleLine> _iconsole_buffer;
static bool TruncateBuffer();
/* ** main console cmd buffer ** */
@@ -177,7 +120,7 @@ static WindowDesc _console_window_desc(
struct IConsoleWindow : Window
{
static int scroll;
static size_t scroll;
int line_height; ///< Height of one line of text in the console.
int line_offset;
GUITimer truncate_timer;
@@ -185,8 +128,6 @@ struct IConsoleWindow : Window
IConsoleWindow() : Window(&_console_window_desc)
{
_iconsole_mode = ICONSOLE_OPENED;
this->line_height = FONT_HEIGHT_NORMAL + ICON_LINE_SPACING;
this->line_offset = GetStringBoundingBox("] ").width + 5;
this->InitNested(0);
this->truncate_timer.SetInterval(3000);
@@ -201,6 +142,12 @@ struct IConsoleWindow : Window
#endif // __EMSCRIPTEN__
}
void OnInit() override
{
this->line_height = FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.hsep_normal;
this->line_offset = GetStringBoundingBox("] ").width + WidgetDimensions::scaled.frametext.left;
}
void Close() override
{
_iconsole_mode = ICONSOLE_CLOSED;
@@ -214,26 +161,34 @@ struct IConsoleWindow : Window
*/
void Scroll(int amount)
{
int max_scroll = std::max(0, IConsoleLine::size + 1 - this->height / this->line_height);
IConsoleWindow::scroll = Clamp<int>(IConsoleWindow::scroll + amount, 0, max_scroll);
if (amount < 0) {
size_t namount = (size_t) -amount;
IConsoleWindow::scroll = (namount > IConsoleWindow::scroll) ? 0 : IConsoleWindow::scroll - namount;
} else {
assert(this->height >= 0 && this->line_height > 0);
size_t visible_lines = (size_t)(this->height / this->line_height);
size_t max_scroll = (visible_lines > _iconsole_buffer.size()) ? 0 : _iconsole_buffer.size() + 1 - visible_lines;
IConsoleWindow::scroll = std::min<size_t>(IConsoleWindow::scroll + amount, max_scroll);
}
this->SetDirty();
}
void OnPaint() override
{
const int right = this->width - 5;
const int right = this->width - WidgetDimensions::scaled.frametext.right;
GfxFillRect(0, 0, this->width - 1, this->height - 1, PC_BLACK);
int ypos = this->height - this->line_height;
for (const IConsoleLine *print = IConsoleLine::Get(IConsoleWindow::scroll); print != nullptr; print = print->previous) {
SetDParamStr(0, print->buffer);
ypos = DrawStringMultiLine(5, right, -this->line_height, ypos, STR_JUST_RAW_STRING, print->colour, SA_LEFT | SA_BOTTOM | SA_FORCE) - ICON_LINE_SPACING;
for (size_t line_index = IConsoleWindow::scroll; line_index < _iconsole_buffer.size(); line_index++) {
const IConsoleLine &print = _iconsole_buffer[line_index];
SetDParamStr(0, print.buffer);
ypos = DrawStringMultiLine(WidgetDimensions::scaled.frametext.left, right, -this->line_height, ypos, STR_JUST_RAW_STRING, print.colour, SA_LEFT | SA_BOTTOM | SA_FORCE) - WidgetDimensions::scaled.hsep_normal;
if (ypos < 0) break;
}
/* If the text is longer than the window, don't show the starting ']' */
int delta = this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH;
if (delta > 0) {
DrawString(5, right, this->height - this->line_height, "]", (TextColour)CC_COMMAND, SA_LEFT | SA_FORCE);
DrawString(WidgetDimensions::scaled.frametext.left, right, this->height - this->line_height, "]", (TextColour)CC_COMMAND, SA_LEFT | SA_FORCE);
delta = 0;
}
@@ -278,9 +233,12 @@ struct IConsoleWindow : Window
{
if (this->truncate_timer.CountElapsed(delta_ms) == 0) return;
if (IConsoleLine::Truncate() &&
(IConsoleWindow::scroll > IConsoleLine::size)) {
IConsoleWindow::scroll = std::max(0, IConsoleLine::size - (this->height / this->line_height) + 1);
assert(this->height >= 0 && this->line_height > 0);
size_t visible_lines = (size_t)(this->height / this->line_height);
if (TruncateBuffer() && IConsoleWindow::scroll + visible_lines > _iconsole_buffer.size()) {
size_t max_scroll = (visible_lines > _iconsole_buffer.size()) ? 0 : _iconsole_buffer.size() + 1 - visible_lines;
IConsoleWindow::scroll = std::min<size_t>(IConsoleWindow::scroll, max_scroll);
this->SetDirty();
}
}
@@ -442,14 +400,14 @@ struct IConsoleWindow : Window
}
};
int IConsoleWindow::scroll = 0;
size_t IConsoleWindow::scroll = 0;
void IConsoleGUIInit()
{
IConsoleResetHistoryPos();
_iconsole_mode = ICONSOLE_CLOSED;
IConsoleLine::Reset();
IConsoleClearBuffer();
memset(_iconsole_history, 0, sizeof(_iconsole_history));
IConsolePrint(TC_LIGHT_BLUE, "OpenTTD Game Console Revision 7 - {}", _openttd_revision);
@@ -461,7 +419,7 @@ void IConsoleGUIInit()
void IConsoleClearBuffer()
{
IConsoleLine::Reset();
_iconsole_buffer.clear();
}
void IConsoleGUIFree()
@@ -511,6 +469,7 @@ void IConsoleSwitch()
new IConsoleWindow();
#endif
break;
case ICONSOLE_OPENED: case ICONSOLE_FULL:
CloseWindowById(WC_CONSOLE, 0);
break;
@@ -580,10 +539,38 @@ static void IConsoleHistoryNavigate(int direction)
*/
void IConsoleGUIPrint(TextColour colour_code, char *str)
{
new IConsoleLine(str, colour_code);
_iconsole_buffer.push_front(IConsoleLine(str, colour_code));
SetWindowDirty(WC_CONSOLE, 0);
}
/**
* Remove old lines from the backlog buffer.
* The buffer is limited by a maximum size and a minimum age. Every time truncation runs,
* all lines in the buffer are aged by one. When a line exceeds both the maximum position
* and also the maximum age, it gets removed.
* @return true if any lines were removed
*/
static bool TruncateBuffer()
{
bool need_truncation = false;
size_t count = 0;
for (IConsoleLine &line : _iconsole_buffer) {
count++;
line.time++;
if (line.time > _settings_client.gui.console_backlog_timeout && count > _settings_client.gui.console_backlog_length) {
/* Any messages after this are older and need to be truncated */
need_truncation = true;
break;
}
}
if (need_truncation) {
_iconsole_buffer.resize(count - 1);
}
return need_truncation;
}
/**
* Check whether the given TextColour is valid for console usage.

View File

@@ -26,5 +26,7 @@ add_files(
smallmatrix_type.hpp
smallstack_type.hpp
smallvec_type.hpp
span_type.hpp
string_compare_type.hpp
strong_typedef_type.hpp
)

View File

@@ -12,13 +12,13 @@
/** Some enums need to have allowed incrementing (i.e. StationClassID) */
#define DECLARE_POSTFIX_INCREMENT(enum_type) \
inline enum_type operator ++(enum_type& e, int) \
inline constexpr enum_type operator ++(enum_type& e, int) \
{ \
enum_type e_org = e; \
e = (enum_type)((std::underlying_type<enum_type>::type)e + 1); \
return e_org; \
} \
inline enum_type operator --(enum_type& e, int) \
inline constexpr enum_type operator --(enum_type& e, int) \
{ \
enum_type e_org = e; \
e = (enum_type)((std::underlying_type<enum_type>::type)e - 1); \
@@ -29,13 +29,13 @@
/** Operators to allow to work with enum as with type safe bit set in C++ */
# define DECLARE_ENUM_AS_BIT_SET(mask_t) \
inline mask_t operator | (mask_t m1, mask_t m2) {return (mask_t)((std::underlying_type<mask_t>::type)m1 | m2);} \
inline mask_t operator & (mask_t m1, mask_t m2) {return (mask_t)((std::underlying_type<mask_t>::type)m1 & m2);} \
inline mask_t operator ^ (mask_t m1, mask_t m2) {return (mask_t)((std::underlying_type<mask_t>::type)m1 ^ m2);} \
inline mask_t& operator |= (mask_t& m1, mask_t m2) {m1 = m1 | m2; return m1;} \
inline mask_t& operator &= (mask_t& m1, mask_t m2) {m1 = m1 & m2; return m1;} \
inline mask_t& operator ^= (mask_t& m1, mask_t m2) {m1 = m1 ^ m2; return m1;} \
inline mask_t operator ~(mask_t m) {return (mask_t)(~(std::underlying_type<mask_t>::type)m);}
inline constexpr mask_t operator | (mask_t m1, mask_t m2) {return (mask_t)((std::underlying_type<mask_t>::type)m1 | (std::underlying_type<mask_t>::type)m2);} \
inline constexpr mask_t operator & (mask_t m1, mask_t m2) {return (mask_t)((std::underlying_type<mask_t>::type)m1 & (std::underlying_type<mask_t>::type)m2);} \
inline constexpr mask_t operator ^ (mask_t m1, mask_t m2) {return (mask_t)((std::underlying_type<mask_t>::type)m1 ^ (std::underlying_type<mask_t>::type)m2);} \
inline constexpr mask_t& operator |= (mask_t& m1, mask_t m2) {m1 = m1 | m2; return m1;} \
inline constexpr mask_t& operator &= (mask_t& m1, mask_t m2) {m1 = m1 & m2; return m1;} \
inline constexpr mask_t& operator ^= (mask_t& m1, mask_t m2) {m1 = m1 ^ m2; return m1;} \
inline constexpr mask_t operator ~(mask_t m) {return (mask_t)(~(std::underlying_type<mask_t>::type)m);}
/**

View File

@@ -48,3 +48,5 @@ Rect BoundingRect(const Rect &r1, const Rect &r2)
return r;
}
const RectPadding RectPadding::zero = {0, 0, 0, 0};

View File

@@ -43,12 +43,182 @@ struct Dimension {
}
};
/** Padding dimensions to apply to each side of a Rect. */
struct RectPadding {
uint8 left;
uint8 top;
uint8 right;
uint8 bottom;
static const RectPadding zero;
/**
* Get total horizontal padding of RectPadding.
* @return total horizontal padding.
*/
inline uint Horizontal() const { return this->left + this->right; }
/**
* Get total vertical padding of RectPadding.
* @return total vertical padding.
*/
inline uint Vertical() const { return this->top + this->bottom; }
};
/** Specification of a rectangle with absolute coordinates of all edges */
struct Rect {
int left;
int top;
int right;
int bottom;
/**
* Get width of Rect.
* @return width of Rect.
*/
inline int Width() const { return this->right - this->left + 1; }
/**
* Get height of Rect.
* @return height of Rect.
*/
inline int Height() const { return this->bottom - this->top + 1; }
/**
* Copy and shrink Rect by s pixels.
* @param s number of pixels to remove from each side of Rect.
* @return the new smaller Rect.
*/
[[nodiscard]] inline Rect Shrink(int s) const
{
return {this->left + s, this->top + s, this->right - s, this->bottom - s};
}
/**
* Copy and shrink Rect by h horizontal and v vertical pixels.
* @param h number of pixels to remove from left and right sides.
* @param v number of pixels to remove from top and bottom sides.
* @return the new smaller Rect.
*/
[[nodiscard]] inline Rect Shrink(int h, int v) const
{
return {this->left + h, this->top + v, this->right - h, this->bottom - v};
}
/**
* Copy and shrink Rect by pixels.
* @param left number of pixels to remove from left side.
* @param top number of pixels to remove from top side.
* @param right number of pixels to remove from right side.
* @param bottom number of pixels to remove from bottom side.
* @return the new smaller Rect.
*/
[[nodiscard]] inline Rect Shrink(int left, int top, int right, int bottom) const
{
return {this->left + left, this->top + top, this->right - right, this->bottom - bottom};
}
/**
* Copy and shrink Rect by a RectPadding.
* @param other RectPadding to remove from each side of Rect.
* @return the new smaller Rect.
*/
[[nodiscard]] inline Rect Shrink(const RectPadding &other) const
{
return {this->left + other.left, this->top + other.top, this->right - other.right, this->bottom - other.bottom};
}
/**
* Copy and shrink Rect by a different horizontal and vertical RectPadding.
* @param horz RectPadding to remove from left and right of Rect.
* @param vert RectPadding to remove from top and bottom of Rect.
* @return the new smaller Rect.
*/
[[nodiscard]] inline Rect Shrink(const RectPadding &horz, const RectPadding &vert) const
{
return {this->left + horz.left, this->top + vert.top, this->right - horz.right, this->bottom - vert.bottom};
}
/**
* Copy and expand Rect by s pixels.
* @param s number of pixels to add to each side of Rect.
* @return the new larger Rect.
*/
[[nodiscard]] inline Rect Expand(int s) const
{
return this->Shrink(-s);
}
/**
* Copy and expand Rect by a RectPadding.
* @param other RectPadding to add to each side of Rect.
* @return the new larger Rect.
*/
[[nodiscard]] inline Rect Expand(const RectPadding &other) const
{
return {this->left - other.left, this->top - other.top, this->right + other.right, this->bottom + other.bottom};
}
/**
* Copy and translate Rect by x,y pixels.
* @param x number of pixels to move horizontally.
* @param y number of pixels to move vertically.
* @return the new translated Rect.
*/
[[nodiscard]] inline Rect Translate(int x, int y) const
{
return {this->left + x, this->top + y, this->right + x, this->bottom + y};
}
/**
* Copy Rect and set its width.
* @param width width in pixels for new Rect.
* @param end if set, set width at end of Rect, i.e. on right.
* @return the new resized Rect.
*/
[[nodiscard]] inline Rect WithWidth(int width, bool end) const
{
return end
? Rect {this->right - width + 1, this->top, this->right, this->bottom}
: Rect {this->left, this->top, this->left + width - 1, this->bottom};
}
/**
* Copy Rect and indent it from its position.
* @param indent offset in pixels for new Rect.
* @param end if set, set indent at end of Rect, i.e. on right.
* @return the new resized Rect.
*/
[[nodiscard]] inline Rect Indent(int indent, bool end) const
{
return end
? Rect {this->left, this->top, this->right - indent, this->bottom}
: Rect {this->left + indent, this->top, this->right, this->bottom};
}
/**
* Copy Rect and set its height.
* @param width height in pixels for new Rect.
* @param end if set, set height at end of Rect, i.e. at bottom.
* @return the new resized Rect.
*/
[[nodiscard]] inline Rect WithHeight(int height, bool end = false) const
{
return end
? Rect {this->left, this->bottom - height + 1, this->right, this->bottom}
: Rect {this->left, this->top, this->right, this->top + height - 1};
}
/**
* Test if a point falls inside this Rect.
* @param pt the point to test.
* @return true iif the point falls inside the Rect.
*/
inline bool Contains(const Point &pt) const
{
/* This is a local version of IsInsideMM, to avoid including math_func everywhere. */
return (uint)(pt.x - this->left) < (uint)(this->right - this->left) && (uint)(pt.y - this->top) < (uint)(this->bottom - this->top);
}
};
/**

View File

@@ -201,7 +201,7 @@ static inline bool IsInsideBS(const T x, const size_t base, const size_t size)
* @see IsInsideBS()
*/
template <typename T>
static inline bool IsInsideMM(const T x, const size_t min, const size_t max)
static constexpr inline bool IsInsideMM(const T x, const size_t min, const size_t max) noexcept
{
return (size_t)(x - min) < (max - min);
}

View File

@@ -22,8 +22,8 @@
/**
* Overflow safe template for integers, i.e. integers that will never overflow
* you multiply the maximum value with 2, or add 2, or subtract something from
* the minimum value, etc.
* when you multiply the maximum value with 2, or add 2, or subtract something
* from the minimum value, etc.
* @param T the type these integers are stored with.
*/
template <class T>
@@ -39,9 +39,10 @@ public:
constexpr OverflowSafeInt() : m_value(0) { }
constexpr OverflowSafeInt(const OverflowSafeInt& other) : m_value(other.m_value) { }
constexpr OverflowSafeInt(const int64 int_) : m_value(int_) { }
constexpr OverflowSafeInt(const T int_) : m_value(int_) { }
inline constexpr OverflowSafeInt& operator = (const OverflowSafeInt& other) { this->m_value = other.m_value; return *this; }
inline constexpr OverflowSafeInt& operator = (T other) { this->m_value = other; return *this; }
inline constexpr OverflowSafeInt operator - () const { return OverflowSafeInt(this->m_value == T_MIN ? T_MAX : -this->m_value); }
@@ -174,7 +175,7 @@ public:
inline constexpr bool operator < (const int other) const { return !(*this >= other); }
inline constexpr bool operator <= (const int other) const { return !(*this > other); }
inline constexpr operator int64 () const { return this->m_value; }
inline constexpr operator T () const { return this->m_value; }
};

View File

@@ -31,8 +31,8 @@ struct is_compatible_element
<
C, E, std::void_t<
decltype(std::data(std::declval<C>())),
typename std::remove_pointer<decltype(std::data( std::declval<C&>()))>::type(*)[]>
> : std::is_convertible<typename std::remove_pointer<decltype(std::data(std::declval<C&>()))>::type(*)[], E(*)[]>{};
typename std::remove_pointer_t<decltype(std::data( std::declval<C&>()))>(*)[]>
> : std::is_convertible<typename std::remove_pointer_t<decltype(std::data(std::declval<C&>()))>(*)[], E(*)[]>{};
/* Template to check if a container is compatible. gsl-lite also includes is_array and is_std_array, but as we don't use them, they are omitted. */
template <class C, class E>
@@ -92,6 +92,8 @@ public:
constexpr const_iterator cbegin() const noexcept { return const_iterator(first); }
constexpr const_iterator cend() const noexcept { return const_iterator(last); }
constexpr reference operator[](size_type idx) const { return first[idx]; }
private:
pointer first;
pointer last;

View File

@@ -0,0 +1,73 @@
/*
* 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 strong_typedef_type.hpp Type (helpers) for making a strong typedef that is a distinct type. */
#ifndef STRONG_TYPEDEF_TYPE_HPP
#define STRONG_TYPEDEF_TYPE_HPP
/** Non-templated base for #StrongTypedef for use with type trait queries. */
struct StrongTypedefBase {};
/**
* Templated helper to make a type-safe 'typedef' representing a single POD value.
* A normal 'typedef' is not distinct from its base type and will be treated as
* identical in many contexts. This class provides a distinct type that can still
* be assign from and compared to values of its base type.
*
* @note This is meant to be used as a base class, not directly.
* @tparam T Storage type
* @tparam Tthis Type of the derived class (i.e. the concrete usage of this class).
*/
template <class T, class Tthis>
struct StrongTypedef : StrongTypedefBase {
using Type = T;
T value{}; ///< Backing storage field.
constexpr StrongTypedef() = default;
constexpr StrongTypedef(const StrongTypedef &o) = default;
constexpr StrongTypedef(StrongTypedef &&o) = default;
constexpr StrongTypedef(const T &value) : value(value) {}
constexpr Tthis &operator =(const StrongTypedef &rhs) { this->value = rhs.value; return static_cast<Tthis &>(*this); }
constexpr Tthis &operator =(StrongTypedef &&rhs) { this->value = std::move(rhs.value); return static_cast<Tthis &>(*this); }
constexpr Tthis &operator =(const T &rhs) { this->value = rhs; return static_cast<Tthis &>(*this); }
explicit constexpr operator T() const { return this->value; }
constexpr bool operator ==(const StrongTypedef &rhs) const { return this->value == rhs.value; }
constexpr bool operator !=(const StrongTypedef &rhs) const { return this->value != rhs.value; }
constexpr bool operator ==(const T &rhs) const { return this->value == rhs; }
constexpr bool operator !=(const T &rhs) const { return this->value != rhs; }
};
/**
* Extension of #StrongTypedef with operators for addition and subtraction.
* @tparam T Storage type
* @tparam Tthis Type of the derived class (i.e. the concrete usage of this class).
*/
template <class T, class Tthis>
struct StrongIntegralTypedef : StrongTypedef<T, Tthis> {
using StrongTypedef<T, Tthis>::StrongTypedef;
constexpr Tthis &operator ++() { this->value++; return static_cast<Tthis &>(*this); }
constexpr Tthis &operator --() { this->value--; return static_cast<Tthis &>(*this); }
constexpr Tthis operator ++(int) { auto res = static_cast<Tthis &>(*this); this->value++; return res; }
constexpr Tthis operator --(int) { auto res = static_cast<Tthis &>(*this); this->value--; return res; }
constexpr Tthis &operator +=(const Tthis &rhs) { this->value += rhs.value; return *static_cast<Tthis *>(this); }
constexpr Tthis &operator -=(const Tthis &rhs) { this->value -= rhs.value; return *static_cast<Tthis *>(this); }
constexpr Tthis operator +(const Tthis &rhs) const { return Tthis{ this->value + rhs.value }; }
constexpr Tthis operator -(const Tthis &rhs) const { return Tthis{ this->value - rhs.value }; }
constexpr Tthis operator +(const T &rhs) const { return Tthis{ this->value + rhs }; }
constexpr Tthis operator -(const T &rhs) const { return Tthis{ this->value - rhs }; }
};
#endif /* STRONG_TYPEDEF_TYPE_HPP */

View File

@@ -176,7 +176,7 @@ char *CrashLog::LogConfiguration(char *buffer, const char *last) const
SoundDriver::GetInstance() == nullptr ? "none" : SoundDriver::GetInstance()->GetName(),
BaseSounds::GetUsedSet() == nullptr ? "none" : BaseSounds::GetUsedSet()->name.c_str(),
BaseSounds::GetUsedSet() == nullptr ? UINT32_MAX : BaseSounds::GetUsedSet()->version,
VideoDriver::GetInstance() == nullptr ? "none" : VideoDriver::GetInstance()->GetName()
VideoDriver::GetInstance() == nullptr ? "none" : VideoDriver::GetInstance()->GetInfoString()
);
buffer += seprintf(buffer, last,

View File

@@ -27,7 +27,7 @@ Year _cur_year; ///< Current year, starting at 0
Month _cur_month; ///< Current month (0..11)
Date _date; ///< Current date in days (day counter)
DateFract _date_fract; ///< Fractional part of the day.
uint16 _tick_counter; ///< Ever incrementing (and sometimes wrapping) tick counter for setting off various events
uint64 _tick_counter; ///< Ever incrementing tick counter for setting off various events
/**
* Set the date.

View File

@@ -16,7 +16,7 @@ extern Year _cur_year;
extern Month _cur_month;
extern Date _date;
extern DateFract _date_fract;
extern uint16 _tick_counter;
extern uint64 _tick_counter;
void SetDate(Date date, DateFract fract);
void ConvertDateToYMD(Date date, YearMonthDay *ymd);

View File

@@ -24,6 +24,7 @@
/** Window to select a date graphically by using dropdowns */
struct SetDateWindow : Window {
SetDateCallback *callback; ///< Callback to call when a date has been selected
void *callback_data; ///< Callback data pointer.
YearMonthDay date; ///< The currently selected date
Year min_year; ///< The minimum year in the year dropdown
Year max_year; ///< The maximum year (inclusive) in the year dropdown
@@ -38,9 +39,10 @@ struct SetDateWindow : Window {
* @param max_year the maximum year (inclusive) to show in the year dropdown
* @param callback the callback to call once a date has been selected
*/
SetDateWindow(WindowDesc *desc, WindowNumber window_number, Window *parent, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback) :
SetDateWindow(WindowDesc *desc, WindowNumber window_number, Window *parent, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback, void *callback_data) :
Window(desc),
callback(callback),
callback_data(callback_data),
min_year(std::max(MIN_YEAR, min_year)),
max_year(std::min(MAX_YEAR, max_year))
{
@@ -146,7 +148,7 @@ struct SetDateWindow : Window {
break;
case WID_SD_SET_DATE:
if (this->callback != nullptr) this->callback(this, ConvertYMDToDate(this->date.year, this->date.month, this->date.day));
if (this->callback != nullptr) this->callback(this, ConvertYMDToDate(this->date.year, this->date.month, this->date.day), this->callback_data);
this->Close();
break;
}
@@ -209,9 +211,10 @@ static WindowDesc _set_date_desc(
* @param min_year the minimum year to show in the year dropdown
* @param max_year the maximum year (inclusive) to show in the year dropdown
* @param callback the callback to call once a date has been selected
* @param callback_data extra callback data
*/
void ShowSetDateWindow(Window *parent, int window_number, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback)
void ShowSetDateWindow(Window *parent, int window_number, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback, void *callback_data)
{
CloseWindowByClass(WC_SET_DATE);
new SetDateWindow(&_set_date_desc, window_number, parent, initial_date, min_year, max_year, callback);
new SetDateWindow(&_set_date_desc, window_number, parent, initial_date, min_year, max_year, callback, callback_data);
}

View File

@@ -18,8 +18,8 @@
* @param w the window that sends the callback
* @param date the date that has been chosen
*/
typedef void SetDateCallback(const Window *w, Date date);
typedef void SetDateCallback(const Window *w, Date date, void *data);
void ShowSetDateWindow(Window *parent, int window_number, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback);
void ShowSetDateWindow(Window *parent, int window_number, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback, void *callback_data);
#endif /* DATE_GUI_H */

View File

@@ -50,7 +50,7 @@ int _debug_sprite_level;
int _debug_oldloader_level;
int _debug_npf_level;
int _debug_yapf_level;
int _debug_freetype_level;
int _debug_fontcache_level;
int _debug_script_level;
int _debug_sl_level;
int _debug_gamelog_level;
@@ -76,7 +76,7 @@ struct DebugLevel {
DEBUG_LEVEL(oldloader),
DEBUG_LEVEL(npf),
DEBUG_LEVEL(yapf),
DEBUG_LEVEL(freetype),
DEBUG_LEVEL(fontcache),
DEBUG_LEVEL(script),
DEBUG_LEVEL(sl),
DEBUG_LEVEL(gamelog),
@@ -166,28 +166,31 @@ void DebugPrint(const char *level, const std::string &message)
* For setting individual levels a string like \c "net=3,grf=6" should be used.
* If the string starts with a number, the number is used as global debugging level.
* @param s Text describing the wanted debugging levels.
* @param error_func The function to call if a parse error occurs.
*/
void SetDebugString(const char *s)
void SetDebugString(const char *s, void (*error_func)(const char *))
{
int v;
char *end;
const char *t;
/* global debugging level? */
/* Store planned changes into map during parse */
std::map<const char *, int> new_levels;
/* Global debugging level? */
if (*s >= '0' && *s <= '9') {
const DebugLevel *i;
v = strtoul(s, &end, 0);
s = end;
for (i = debug_level; i != endof(debug_level); ++i) *i->level = v;
for (i = debug_level; i != endof(debug_level); ++i) {
new_levels[i->name] = v;
}
}
/* individual levels */
/* Individual levels */
for (;;) {
const DebugLevel *i;
int *p;
/* skip delimiters */
while (*s == ' ' || *s == ',' || *s == '\t') s++;
if (*s == '\0') break;
@@ -196,10 +199,10 @@ void SetDebugString(const char *s)
while (*s >= 'a' && *s <= 'z') s++;
/* check debugging levels */
p = nullptr;
for (i = debug_level; i != endof(debug_level); ++i) {
const DebugLevel *found = nullptr;
for (const DebugLevel *i = debug_level; i != endof(debug_level); ++i) {
if (s == t + strlen(i->name) && strncmp(t, i->name, s - t) == 0) {
p = i->level;
found = i;
break;
}
}
@@ -207,13 +210,22 @@ void SetDebugString(const char *s)
if (*s == '=') s++;
v = strtoul(s, &end, 0);
s = end;
if (p != nullptr) {
*p = v;
if (found != nullptr) {
new_levels[found->name] = v;
} else {
ShowInfoF("Unknown debug level '%.*s'", (int)(s - t), t);
std::string error_string = fmt::format("Unknown debug level '{}'", std::string(t, s - t));
error_func(error_string.c_str());
return;
}
}
/* Apply the changes after parse is successful */
for (const DebugLevel *i = debug_level; i != endof(debug_level); ++i) {
const auto &nl = new_levels.find(i->name);
if (nl != new_levels.end()) {
*i->level = nl->second;
}
}
}
/**

View File

@@ -46,7 +46,7 @@ extern int _debug_sprite_level;
extern int _debug_oldloader_level;
extern int _debug_npf_level;
extern int _debug_yapf_level;
extern int _debug_freetype_level;
extern int _debug_fontcache_level;
extern int _debug_script_level;
extern int _debug_sl_level;
extern int _debug_gamelog_level;
@@ -57,7 +57,7 @@ extern int _debug_random_level;
#endif
char *DumpDebugFacilityNames(char *buf, char *last);
void SetDebugString(const char *s);
void SetDebugString(const char *s, void (*error_func)(const char *));
const char *GetDebugString();
/* Shorter form for passing filename and linenumber */

View File

@@ -16,6 +16,7 @@
#include "vehicle_gui.h"
#include "vehiclelist.h"
#include "window_func.h"
#include "depot_cmd.h"
#include "table/strings.h"
@@ -37,16 +38,14 @@ static bool IsUniqueDepotName(const std::string &name)
/**
* Rename a depot.
* @param tile unused
* @param flags type of operation
* @param p1 id of depot
* @param p2 unused
* @param depot_id id of depot
* @param text the new name or an empty string when resetting to the default
* @return the cost of this operation or an error
*/
CommandCost CmdRenameDepot(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::string &text)
{
Depot *d = Depot::GetIfValid(p1);
Depot *d = Depot::GetIfValid(depot_id);
if (d == nullptr) return CMD_ERROR;
CommandCost ret = CheckTileOwnership(d->xy);

22
src/depot_cmd.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* 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 depot_cmd.h Command definitions related to depots. */
#ifndef DEPOT_CMD_H
#define DEPOT_CMD_H
#include "command_type.h"
#include "depot_type.h"
CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::string &text);
DEF_CMD_TRAIT(CMD_RENAME_DEPOT, CmdRenameDepot, 0, CMDT_OTHER_MANAGEMENT)
void CcCloneVehicle(Commands cmd, const CommandCost &result, VehicleID veh_id);
#endif /* DEPOT_CMD_H */

View File

@@ -24,8 +24,14 @@
#include "tilehighlight_func.h"
#include "window_gui.h"
#include "vehiclelist.h"
#include "vehicle_func.h"
#include "order_backup.h"
#include "zoom_func.h"
#include "error.h"
#include "depot_cmd.h"
#include "train_cmd.h"
#include "vehicle_cmd.h"
#include "core/geometry_func.hpp"
#include "widgets/depot_widget.h"
@@ -64,8 +70,8 @@ static const NWidgetPart _nested_train_depot_widgets[] = {
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_D_SHOW_SELL_CHAIN),
NWidget(WWT_IMGBTN, COLOUR_GREY, WID_D_SELL_CHAIN), SetDataTip(SPR_SELL_CHAIN_TRAIN, STR_DEPOT_DRAG_WHOLE_TRAIN_TO_SELL_TOOLTIP), SetResize(0, 1), SetFill(0, 1),
EndContainer(),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_SELL_ALL), SetResize(0, 1), SetFill(0, 1), SetDataTip(0x0, STR_NULL),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_AUTOREPLACE), SetResize(0, 1), SetFill(0, 1), SetDataTip(0x0, STR_NULL),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_SELL_ALL), SetDataTip(0x0, STR_NULL),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_AUTOREPLACE), SetDataTip(0x0, STR_NULL),
EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_D_V_SCROLL),
EndContainer(),
@@ -111,17 +117,15 @@ extern void DepotSortList(VehicleList *list);
/**
* This is the Callback method after the cloning attempt of a vehicle
* @param result the result of the cloning command
* @param tile unused
* @param p1 unused
* @param p2 unused
* @param cmd unused
* @param result the result of the cloning command
* @param veh_id cloned vehicle ID
*/
void CcCloneVehicle(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
void CcCloneVehicle(Commands cmd, const CommandCost &result, VehicleID veh_id)
{
if (result.Failed()) return;
const Vehicle *v = Vehicle::Get(_new_vehicle_id);
const Vehicle *v = Vehicle::Get(veh_id);
ShowVehicleViewWindow(v);
}
@@ -141,7 +145,7 @@ static void TrainDepotMoveVehicle(const Vehicle *wagon, VehicleID sel, const Veh
if (wagon == v) return;
DoCommandP(v->tile, v->index | (_ctrl_pressed ? 1 : 0) << 20, wagon == nullptr ? INVALID_VEHICLE : wagon->index, CMD_MOVE_RAIL_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_MOVE_VEHICLE));
Command<CMD_MOVE_RAIL_VEHICLE>::Post(STR_ERROR_CAN_T_MOVE_VEHICLE, v->tile, v->index, wagon == nullptr ? INVALID_VEHICLE : wagon->index, _ctrl_pressed);
}
static VehicleCellSize _base_block_sizes_depot[VEH_COMPANY_END]; ///< Cell size for vehicle images in the depot view.
@@ -189,17 +193,17 @@ static void InitBlocksizeForVehicles(VehicleType type, EngineImageType image_typ
if ((int)x + x_offs > max_extend_right) max_extend_right = x + x_offs;
}
int min_extend = ScaleGUITrad(16);
int max_extend = ScaleGUITrad(98);
int min_extend = ScaleSpriteTrad(16);
int max_extend = ScaleSpriteTrad(98);
switch (image_type) {
case EIT_IN_DEPOT:
_base_block_sizes_depot[type].height = std::max<uint>(ScaleGUITrad(GetVehicleHeight(type)), max_height);
_base_block_sizes_depot[type].height = std::max<uint>(ScaleSpriteTrad(GetVehicleHeight(type)), max_height);
_base_block_sizes_depot[type].extend_left = Clamp(max_extend_left, min_extend, max_extend);
_base_block_sizes_depot[type].extend_right = Clamp(max_extend_right, min_extend, max_extend);
break;
case EIT_PURCHASE:
_base_block_sizes_purchase[type].height = std::max<uint>(ScaleGUITrad(GetVehicleHeight(type)), max_height);
_base_block_sizes_purchase[type].height = std::max<uint>(ScaleSpriteTrad(GetVehicleHeight(type)), max_height);
_base_block_sizes_purchase[type].extend_left = Clamp(max_extend_left, min_extend, max_extend);
_base_block_sizes_purchase[type].extend_right = Clamp(max_extend_right, min_extend, max_extend);
break;
@@ -302,18 +306,15 @@ struct DepotWindow : Window {
/**
* Draw a vehicle in the depot window in the box with the top left corner at x,y.
* @param v Vehicle to draw.
* @param left Left side of the box to draw in.
* @param right Right side of the box to draw in.
* @param y Top of the box to draw in.
* @param r Rect to draw in.
*/
void DrawVehicleInDepot(const Vehicle *v, int left, int right, int y) const
void DrawVehicleInDepot(const Vehicle *v, const Rect &r) const
{
bool free_wagon = false;
int sprite_y = y + (this->resize.step_height - ScaleGUITrad(GetVehicleHeight(v->type))) / 2;
bool rtl = _current_text_dir == TD_RTL;
int image_left = rtl ? left + this->count_width : left + this->header_width;
int image_right = rtl ? right - this->header_width : right - this->count_width;
Rect text = r.Shrink(RectPadding::zero, WidgetDimensions::scaled.matrix); /* Ract for text elements, horizontal is already applied. */
Rect image = r.Indent(this->header_width, rtl).Indent(this->count_width, !rtl); /* Rect for vehicle images */
switch (v->type) {
case VEH_TRAIN: {
@@ -321,47 +322,45 @@ struct DepotWindow : Window {
free_wagon = u->IsFreeWagon();
uint x_space = free_wagon ?
ScaleGUITrad(_consistent_train_width != 0 ? _consistent_train_width : TRAININFO_DEFAULT_VEHICLE_WIDTH) :
ScaleSpriteTrad(_consistent_train_width != 0 ? _consistent_train_width : TRAININFO_DEFAULT_VEHICLE_WIDTH) :
0;
DrawTrainImage(u, image_left + (rtl ? 0 : x_space), image_right - (rtl ? x_space : 0), sprite_y - 1,
this->sel, EIT_IN_DEPOT, free_wagon ? 0 : this->hscroll->GetPosition(), this->vehicle_over);
DrawTrainImage(u, image.Indent(x_space, rtl), this->sel, EIT_IN_DEPOT, free_wagon ? 0 : this->hscroll->GetPosition(), this->vehicle_over);
/* Length of consist in tiles with 1 fractional digit (rounded up) */
SetDParam(0, CeilDiv(u->gcache.cached_total_length * 10, TILE_SIZE));
SetDParam(1, 1);
DrawString(rtl ? left + WD_FRAMERECT_LEFT : right - this->count_width, rtl ? left + this->count_width : right - WD_FRAMERECT_RIGHT, Center(y, this->resize.step_height, FONT_HEIGHT_SMALL), STR_TINY_BLACK_DECIMAL, TC_FROMSTRING, SA_RIGHT); // Draw the counter
Rect count = text.WithWidth(this->count_width - WidgetDimensions::scaled.hsep_normal, !rtl);
DrawString(count.left, count.right, count.bottom - FONT_HEIGHT_SMALL + 1, STR_TINY_BLACK_DECIMAL, TC_FROMSTRING, SA_RIGHT); // Draw the counter
break;
}
case VEH_ROAD: DrawRoadVehImage( v, image_left, image_right, sprite_y, this->sel, EIT_IN_DEPOT); break;
case VEH_SHIP: DrawShipImage( v, image_left, image_right, sprite_y, this->sel, EIT_IN_DEPOT); break;
case VEH_AIRCRAFT: DrawAircraftImage(v, image_left, image_right, sprite_y, this->sel, EIT_IN_DEPOT); break;
case VEH_ROAD: DrawRoadVehImage( v, image, this->sel, EIT_IN_DEPOT); break;
case VEH_SHIP: DrawShipImage( v, image, this->sel, EIT_IN_DEPOT); break;
case VEH_AIRCRAFT: DrawAircraftImage(v, image, this->sel, EIT_IN_DEPOT); break;
default: NOT_REACHED();
}
uint diff_x, y_sprite, y_num;
uint diff_x, diff_y;
if (v->IsGroundVehicle()) {
/* Arrange unitnumber and flag horizontally */
diff_x = this->flag_width + WD_FRAMERECT_LEFT;
y_sprite = Center(y, this->resize.step_height, this->flag_height);
y_num = Center(y, this->resize.step_height);
diff_x = this->flag_size.width + WidgetDimensions::scaled.hsep_normal;
diff_y = WidgetDimensions::scaled.matrix.top;
} else {
/* Arrange unitnumber and flag vertically */
diff_x = WD_FRAMERECT_LEFT;
y_num = Center(y, this->resize.step_height, FONT_HEIGHT_NORMAL + this->flag_height + 2);
y_sprite = y_num + FONT_HEIGHT_NORMAL;
diff_x = 0;
diff_y = WidgetDimensions::scaled.matrix.top + FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal;
}
int text_left = rtl ? right - this->header_width - 1 : left + diff_x;
int text_right = rtl ? right - diff_x : left + this->header_width - 1;
text = text.WithWidth(this->header_width - WidgetDimensions::scaled.hsep_normal, rtl).WithHeight(FONT_HEIGHT_NORMAL).Indent(diff_x, rtl);
if (free_wagon) {
DrawString(text_left, text_right, Center(y, this->resize.step_height), STR_DEPOT_NO_ENGINE);
DrawString(text, STR_DEPOT_NO_ENGINE);
} else {
DrawSprite((v->vehstatus & VS_STOPPED) ? SPR_FLAG_VEH_STOPPED : SPR_FLAG_VEH_RUNNING, PAL_NONE, rtl ? right - this->flag_width : left + WD_FRAMERECT_LEFT, y_sprite);
Rect flag = r.WithWidth(this->flag_size.width, rtl).WithHeight(this->flag_size.height).Translate(0, diff_y);
DrawSpriteIgnorePadding((v->vehstatus & VS_STOPPED) ? SPR_FLAG_VEH_STOPPED : SPR_FLAG_VEH_RUNNING, PAL_NONE, flag, false, SA_CENTER);
SetDParam(0, v->unitnumber);
DrawString(text_left, text_right, y_num, (uint16)(v->max_age - DAYS_IN_LEAP_YEAR) >= v->age ? STR_BLACK_COMMA : STR_RED_COMMA);
DrawString(text, (uint16)(v->max_age - DAYS_IN_LEAP_YEAR) >= v->age ? STR_BLACK_COMMA : STR_RED_COMMA);
}
}
@@ -374,24 +373,28 @@ struct DepotWindow : Window {
/* Set the row and number of boxes in each row based on the number of boxes drawn in the matrix */
const NWidgetCore *wid = this->GetWidget<NWidgetCore>(WID_D_MATRIX);
/* Set up rect for each cell */
Rect ir = r.WithHeight(this->resize.step_height);
if (this->num_columns != 1) ir = ir.WithWidth(this->resize.step_width, rtl);
ir = ir.Shrink(WidgetDimensions::scaled.framerect, RectPadding::zero);
/* Draw vertical separators at whole tiles.
* This only works in two cases:
* - All vehicles use VEHICLEINFO_FULL_VEHICLE_WIDTH as reference width.
* - All vehicles are 8/8. This cannot be checked for NewGRF, so instead we check for "all vehicles are original vehicles".
*/
if (this->type == VEH_TRAIN && _consistent_train_width != 0) {
int w = ScaleGUITrad(2 * _consistent_train_width);
int w = ScaleSpriteTrad(2 * _consistent_train_width);
int col = _colour_gradient[wid->colour][4];
int image_left = rtl ? r.left + this->count_width : r.left + this->header_width;
int image_right = rtl ? r.right - this->header_width : r.right - this->count_width;
Rect image = ir.Indent(this->header_width, rtl).Indent(this->count_width, !rtl);
int first_line = w + (-this->hscroll->GetPosition()) % w;
if (rtl) {
for (int x = image_right - first_line; x >= image_left; x -= w) {
GfxDrawLine(x, r.top, x, r.bottom, col, 1, 3);
for (int x = image.right - first_line; x >= image.left; x -= w) {
GfxDrawLine(x, r.top, x, r.bottom, col, ScaleGUITrad(1), ScaleGUITrad(3));
}
} else {
for (int x = image_left + first_line; x <= image_right; x += w) {
GfxDrawLine(x, r.top, x, r.bottom, col, 1, 3);
for (int x = image.left + first_line; x <= image.right; x += w) {
GfxDrawLine(x, r.top, x, r.bottom, col, ScaleGUITrad(1), ScaleGUITrad(3));
}
}
}
@@ -400,26 +403,22 @@ struct DepotWindow : Window {
uint num = this->vscroll->GetPosition() * this->num_columns;
uint maxval = static_cast<uint>(std::min<size_t>(this->vehicle_list.size(), num + (rows_in_display * this->num_columns)));
int y;
for (y = r.top + 1; num < maxval; y += this->resize.step_height) { // Draw the rows
for (; num < maxval; ir = ir.Translate(0, this->resize.step_height)) { // Draw the rows
Rect cell = ir; /* Keep track of horizontal cells */
for (uint i = 0; i < this->num_columns && num < maxval; i++, num++) {
/* Draw all vehicles in the current row */
const Vehicle *v = this->vehicle_list[num];
if (this->num_columns == 1) {
this->DrawVehicleInDepot(v, r.left, r.right, y);
} else {
int x = r.left + (rtl ? (this->num_columns - i - 1) : i) * this->resize.step_width;
this->DrawVehicleInDepot(v, x, x + this->resize.step_width - 1, y);
}
this->DrawVehicleInDepot(v, cell);
cell = cell.Translate(rtl ? -(int)this->resize.step_width : (int)this->resize.step_width, 0);
}
}
maxval = static_cast<uint>(std::min<size_t>(this->vehicle_list.size() + this->wagon_list.size(), (this->vscroll->GetPosition() * this->num_columns) + (rows_in_display * this->num_columns)));
/* Draw the train wagons without an engine in front. */
for (; num < maxval; num++, y += this->resize.step_height) {
for (; num < maxval; num++, ir = ir.Translate(0, this->resize.step_height)) {
const Vehicle *v = this->wagon_list[num - this->vehicle_list.size()];
this->DrawVehicleInDepot(v, r.left, r.right, y);
this->DrawVehicleInDepot(v, ir);
}
}
@@ -485,7 +484,7 @@ struct DepotWindow : Window {
pos -= (uint)this->vehicle_list.size();
*veh = this->wagon_list[pos];
/* free wagons don't have an initial loco. */
x -= ScaleGUITrad(VEHICLEINFO_FULL_VEHICLE_WIDTH);
x -= ScaleSpriteTrad(VEHICLEINFO_FULL_VEHICLE_WIDTH);
wagon = true;
}
@@ -502,12 +501,12 @@ struct DepotWindow : Window {
FALLTHROUGH;
case VEH_ROAD:
if (xm <= this->flag_width) return MODE_START_STOP;
if (xm <= this->flag_size.width) return MODE_START_STOP;
break;
case VEH_SHIP:
case VEH_AIRCRAFT:
if (xm <= this->flag_width && ym >= (uint)(FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL)) return MODE_START_STOP;
if (xm <= this->flag_size.width && ym >= (uint)(FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal)) return MODE_START_STOP;
break;
default: NOT_REACHED();
@@ -644,10 +643,16 @@ struct DepotWindow : Window {
}
}
uint count_width;
uint header_width;
uint flag_width;
uint flag_height;
uint count_width; ///< Width of length count, including separator.
uint header_width; ///< Width of unit number and flag, including separator.
Dimension flag_size; ///< Size of start/stop flag.
VehicleCellSize cell_size; ///< Vehicle sprite cell size.
void OnInit() override
{
this->cell_size = GetVehicleImageCellSize(this->type, EIT_IN_DEPOT);
this->flag_size = maxdim(GetScaledSpriteSize(SPR_FLAG_VEH_STOPPED), GetScaledSpriteSize(SPR_FLAG_VEH_RUNNING));
}
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
{
@@ -658,34 +663,31 @@ struct DepotWindow : Window {
if (this->type == VEH_TRAIN) {
SetDParamMaxValue(0, 1000, 0, FS_SMALL);
SetDParam(1, 1);
this->count_width = GetStringBoundingBox(STR_TINY_BLACK_DECIMAL).width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
this->count_width = GetStringBoundingBox(STR_TINY_BLACK_DECIMAL).width + WidgetDimensions::scaled.hsep_normal;
} else {
this->count_width = 0;
}
SetDParamMaxDigits(0, this->unitnumber_digits);
Dimension unumber = GetStringBoundingBox(STR_BLACK_COMMA);
const Sprite *spr = GetSprite(SPR_FLAG_VEH_STOPPED, ST_NORMAL);
this->flag_width = UnScaleGUI(spr->width) + WD_FRAMERECT_RIGHT;
this->flag_height = UnScaleGUI(spr->height);
if (this->type == VEH_TRAIN || this->type == VEH_ROAD) {
min_height = std::max<uint>(unumber.height + WD_MATRIX_TOP, UnScaleGUI(spr->height));
this->header_width = unumber.width + this->flag_width + WD_FRAMERECT_LEFT;
min_height = std::max<uint>(unumber.height, this->flag_size.height);
this->header_width = unumber.width + WidgetDimensions::scaled.hsep_normal + this->flag_size.width + WidgetDimensions::scaled.hsep_normal;
} else {
min_height = unumber.height + UnScaleGUI(spr->height) + WD_MATRIX_TOP + WD_PAR_VSEP_NORMAL + WD_MATRIX_BOTTOM;
this->header_width = std::max<uint>(unumber.width, this->flag_width) + WD_FRAMERECT_RIGHT;
min_height = unumber.height + WidgetDimensions::scaled.vsep_normal + this->flag_size.height;
this->header_width = std::max<uint>(unumber.width, this->flag_size.width) + WidgetDimensions::scaled.hsep_normal;
}
int base_width = this->count_width + this->header_width;
int base_width = this->count_width + this->header_width + padding.width;
resize->height = std::max<uint>(GetVehicleImageCellSize(this->type, EIT_IN_DEPOT).height, min_height);
resize->height = std::max<uint>(this->cell_size.height, min_height + padding.height);
resize->height = GetMinButtonSize(resize->height);
if (this->type == VEH_TRAIN) {
resize->width = 1;
size->width = base_width + 2 * ScaleGUITrad(29); // about 2 parts
size->width = base_width + 2 * ScaleSpriteTrad(29); // about 2 parts
size->height = resize->height * 6;
} else {
resize->width = base_width + GetVehicleImageCellSize(this->type, EIT_IN_DEPOT).extend_left + GetVehicleImageCellSize(this->type, EIT_IN_DEPOT).extend_right;
resize->width = base_width + this->cell_size.extend_left + this->cell_size.extend_right;
size->width = resize->width * (this->type == VEH_ROAD ? 5 : 3);
size->height = resize->height * (this->type == VEH_ROAD ? 5 : 3);
}
@@ -725,7 +727,7 @@ struct DepotWindow : Window {
/* determine amount of items for scroller */
if (this->type == VEH_TRAIN) {
uint max_width = ScaleGUITrad(VEHICLEINFO_FULL_VEHICLE_WIDTH);
uint max_width = ScaleSpriteTrad(VEHICLEINFO_FULL_VEHICLE_WIDTH);
for (uint num = 0; num < this->vehicle_list.size(); num++) {
uint width = 0;
for (const Train *v = Train::From(this->vehicle_list[num]); v != nullptr; v = v->Next()) {
@@ -736,7 +738,7 @@ struct DepotWindow : Window {
/* Always have 1 empty row, so people can change the setting of the train */
this->vscroll->SetCount((uint)this->vehicle_list.size() + (uint)this->wagon_list.size() + 1);
/* Always make it longer than the longest train, so you can attach vehicles at the end, and also see the next vertical tile separator line */
this->hscroll->SetCount(max_width + ScaleGUITrad(2 * VEHICLEINFO_FULL_VEHICLE_WIDTH + 1));
this->hscroll->SetCount(max_width + ScaleSpriteTrad(2 * VEHICLEINFO_FULL_VEHICLE_WIDTH + 1));
} else {
this->vscroll->SetCount(CeilDiv((uint)this->vehicle_list.size(), this->num_columns));
}
@@ -805,7 +807,7 @@ struct DepotWindow : Window {
case WID_D_STOP_ALL:
case WID_D_START_ALL: {
VehicleListIdentifier vli(VL_DEPOT_LIST, this->type, this->owner);
DoCommandP(this->window_number, (widget == WID_D_START_ALL ? (1 << 0) : 0), vli.Pack(), CMD_MASS_START_STOP);
Command<CMD_MASS_START_STOP>::Post(this->window_number, widget == WID_D_START_ALL, false, vli);
break;
}
@@ -828,7 +830,7 @@ struct DepotWindow : Window {
break;
case WID_D_AUTOREPLACE:
DoCommandP(this->window_number, this->type, 0, CMD_DEPOT_MASS_AUTOREPLACE);
Command<CMD_DEPOT_MASS_AUTOREPLACE>::Post(this->window_number, this->type);
break;
}
@@ -839,7 +841,7 @@ struct DepotWindow : Window {
if (str == nullptr) return;
/* Do depot renaming */
DoCommandP(0, this->GetDepotIndex(), 0, CMD_RENAME_DEPOT | CMD_MSG(STR_ERROR_CAN_T_RENAME_DEPOT), nullptr, str);
Command<CMD_RENAME_DEPOT>::Post(STR_ERROR_CAN_T_RENAME_DEPOT, this->GetDepotIndex(), str);
}
bool OnRightClick(Point pt, int widget) override
@@ -907,10 +909,10 @@ struct DepotWindow : Window {
{
if (_ctrl_pressed) {
/* Share-clone, do not open new viewport, and keep tool active */
DoCommandP(this->window_number, v->index, 1, CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_TRAIN + v->type), nullptr);
Command<CMD_CLONE_VEHICLE>::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, this->window_number, v->index, true);
} else {
/* Copy-clone, open viewport for new vehicle, and deselect the tool (assume player wants to changs things on new vehicle) */
if (DoCommandP(this->window_number, v->index, 0, CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_TRAIN + v->type), CcCloneVehicle)) {
/* Copy-clone, open viewport for new vehicle, and deselect the tool (assume player wants to change things on new vehicle) */
if (Command<CMD_CLONE_VEHICLE>::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, CcCloneVehicle, this->window_number, v->index, false)) {
ResetObjectToPlace();
}
}
@@ -918,6 +920,49 @@ struct DepotWindow : Window {
return true;
}
/**
* Clones a vehicle from a vehicle list. If this doesn't make sense (because not all vehicles in the list have the same orders), then it displays an error.
* @return This always returns true, which indicates that the contextual action handled the mouse click.
* Note that it's correct behaviour to always handle the click even though an error is displayed,
* because users aren't going to expect the default action to be performed just because they overlooked that cloning doesn't make sense.
*/
bool OnVehicleSelect(VehicleList::const_iterator begin, VehicleList::const_iterator end) override
{
if (!_ctrl_pressed) {
/* If CTRL is not pressed: If all the vehicles in this list have the same orders, then copy orders */
if (AllEqual(begin, end, [](const Vehicle *v1, const Vehicle *v2) {
return VehiclesHaveSameEngineList(v1, v2);
})) {
if (AllEqual(begin, end, [](const Vehicle *v1, const Vehicle *v2) {
return VehiclesHaveSameOrderList(v1, v2);
})) {
OnVehicleSelect(*begin);
} else {
ShowErrorMessage(STR_ERROR_CAN_T_BUY_TRAIN + (*begin)->type, STR_ERROR_CAN_T_COPY_ORDER_VEHICLE_LIST, WL_INFO);
}
} else {
ShowErrorMessage(STR_ERROR_CAN_T_BUY_TRAIN + (*begin)->type, STR_ERROR_CAN_T_CLONE_VEHICLE_LIST, WL_INFO);
}
} else {
/* If CTRL is pressed: If all the vehicles in this list share orders, then copy orders */
if (AllEqual(begin, end, [](const Vehicle *v1, const Vehicle *v2) {
return VehiclesHaveSameEngineList(v1, v2);
})) {
if (AllEqual(begin, end, [](const Vehicle *v1, const Vehicle *v2) {
return v1->FirstShared() == v2->FirstShared();
})) {
OnVehicleSelect(*begin);
} else {
ShowErrorMessage(STR_ERROR_CAN_T_BUY_TRAIN + (*begin)->type, STR_ERROR_CAN_T_SHARE_ORDER_VEHICLE_LIST, WL_INFO);
}
} else {
ShowErrorMessage(STR_ERROR_CAN_T_BUY_TRAIN + (*begin)->type, STR_ERROR_CAN_T_CLONE_VEHICLE_LIST, WL_INFO);
}
}
return true;
}
void OnPlaceObjectAbort() override
{
/* abort clone */
@@ -1004,8 +1049,7 @@ struct DepotWindow : Window {
if (this->GetVehicleFromDepotWndPt(pt.x - nwi->pos_x, pt.y - nwi->pos_y, &v, &gdvp) == MODE_DRAG_VEHICLE && sel != INVALID_VEHICLE) {
if (gdvp.wagon != nullptr && gdvp.wagon->index == sel && _ctrl_pressed) {
DoCommandP(Vehicle::Get(sel)->tile, Vehicle::Get(sel)->index, true,
CMD_REVERSE_TRAIN_DIRECTION | CMD_MSG(STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE));
Command<CMD_REVERSE_TRAIN_DIRECTION>::Post(STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE, Vehicle::Get(sel)->tile, Vehicle::Get(sel)->index, true);
} else if (gdvp.wagon == nullptr || gdvp.wagon->index != sel) {
this->vehicle_over = INVALID_VEHICLE;
TrainDepotMoveVehicle(gdvp.wagon, sel, gdvp.head);
@@ -1029,8 +1073,8 @@ struct DepotWindow : Window {
this->sel = INVALID_VEHICLE;
this->SetDirty();
int sell_cmd = (v->type == VEH_TRAIN && (widget == WID_D_SELL_CHAIN || _ctrl_pressed)) ? 1 : 0;
DoCommandP(v->tile, v->index | sell_cmd << 20 | MAKE_ORDER_BACKUP_FLAG, 0, GetCmdSellVeh(v->type));
bool sell_cmd = (v->type == VEH_TRAIN && (widget == WID_D_SELL_CHAIN || _ctrl_pressed));
Command<CMD_SELL_VEHICLE>::Post(GetCmdSellVehMsg(v->type), v->tile, v->index, sell_cmd, true, INVALID_CLIENT_ID);
break;
}
@@ -1093,8 +1137,8 @@ static void DepotSellAllConfirmationCallback(Window *win, bool confirmed)
if (confirmed) {
DepotWindow *w = (DepotWindow*)win;
TileIndex tile = w->window_number;
byte vehtype = w->type;
DoCommandP(tile, vehtype, 0, CMD_DEPOT_SELL_ALL_VEHICLES);
VehicleType vehtype = w->type;
Command<CMD_DEPOT_SELL_ALL_VEHICLES>::Post(tile, vehtype);
}
}

View File

@@ -10,9 +10,13 @@
#ifndef DEPOT_TYPE_H
#define DEPOT_TYPE_H
#include "station_type.h"
typedef uint16 DepotID; ///< Type for the unique identifier of depots.
struct Depot;
static const DepotID INVALID_DEPOT = (DepotID)INVALID_STATION;
static const uint MAX_LENGTH_DEPOT_NAME_CHARS = 32; ///< The maximum length of a depot name in characters including '\0'
#endif /* DEPOT_TYPE_H */

View File

@@ -102,10 +102,12 @@ template <> struct EnumPropsT<DiagDirection> : MakeEnumPropsT<DiagDirection, byt
* @see DirDiff
*/
enum DiagDirDiff {
DIAGDIRDIFF_BEGIN = 0, ///< Used for iterations
DIAGDIRDIFF_SAME = 0, ///< Same directions
DIAGDIRDIFF_90RIGHT = 1, ///< 90 degrees right
DIAGDIRDIFF_REVERSE = 2, ///< Reverse directions
DIAGDIRDIFF_90LEFT = 3, ///< 90 degrees left
DIAGDIRDIFF_END, ///< Used for iterations
};
/** Allow incrementing of DiagDirDiff variables */
@@ -120,7 +122,7 @@ DECLARE_POSTFIX_INCREMENT(DiagDirDiff)
* (and south-east edge). The Y axis must be so the one which goes
* align the north-east edge (and south-west) edge.
*/
enum Axis {
enum Axis : byte {
AXIS_X = 0, ///< The X axis
AXIS_Y = 1, ///< The y axis
AXIS_END, ///< Used for iterations

View File

@@ -45,6 +45,7 @@
#include "company_base.h"
#include "core/random_func.hpp"
#include "core/backup_type.hpp"
#include "landscape_cmd.h"
#include "table/strings.h"
@@ -61,7 +62,7 @@ static void DisasterClearSquare(TileIndex tile)
case MP_RAILWAY:
if (Company::IsHumanID(GetTileOwner(tile)) && !IsRailDepot(tile)) {
Backup<CompanyID> cur_company(_current_company, OWNER_WATER, FILE_LINE);
DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR);
Command<CMD_LANDSCAPE_CLEAR>::Do(DC_EXEC, tile);
cur_company.Restore();
/* update signals in buffer */
@@ -71,7 +72,7 @@ static void DisasterClearSquare(TileIndex tile)
case MP_HOUSE: {
Backup<CompanyID> cur_company(_current_company, OWNER_NONE, FILE_LINE);
DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR);
Command<CMD_LANDSCAPE_CLEAR>::Do(DC_EXEC, tile);
cur_company.Restore();
break;
}
@@ -941,7 +942,7 @@ void ReleaseDisastersTargetingIndustry(IndustryID i)
/* primary disaster vehicles that have chosen target */
if (v->subtype == ST_AIRPLANE || v->subtype == ST_HELICOPTER) {
/* if it has chosen target, and it is this industry (yes, dest_tile is IndustryID here), set order to "leaving map peacefully" */
if (v->current_order.GetDestination() > 0 && v->dest_tile == i) v->current_order.SetDestination(3);
if (v->current_order.GetDestination() > 0 && v->dest_tile == (uint32)i) v->current_order.SetDestination(3);
}
}
}

18
src/dock_cmd.h Normal file
View File

@@ -0,0 +1,18 @@
/*
* 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 dock_cmd.h Command definitions related to docks. */
#ifndef DOCK_CMD_H
#define DOCK_CMD_H
#include "command_type.h"
CommandCallback CcBuildDocks;
CommandCallback CcPlaySound_CONSTRUCTION_WATER;
#endif /* DOCK_CMD_H */

View File

@@ -25,6 +25,11 @@
#include "hotkeys.h"
#include "gui.h"
#include "zoom_func.h"
#include "tunnelbridge_cmd.h"
#include "dock_cmd.h"
#include "station_cmd.h"
#include "water_cmd.h"
#include "waypoint_cmd.h"
#include "build_confirmation_func.h"
#include "widgets/dock_widget.h"
@@ -39,7 +44,7 @@ static void ShowBuildDocksDepotPicker(Window *parent);
static Axis _ship_depot_direction;
void CcBuildDocks(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
void CcBuildDocks(Commands cmd, const CommandCost &result, TileIndex tile)
{
if (result.Failed()) return;
@@ -47,7 +52,7 @@ void CcBuildDocks(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p
if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
}
void CcPlaySound_CONSTRUCTION_WATER(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
void CcPlaySound_CONSTRUCTION_WATER(Commands cmd, const CommandCost &result, TileIndex tile)
{
if (result.Succeeded() && _settings_client.sound.confirm) SndPlayTileFx(SND_02_CONSTRUCTION_WATER, tile);
}
@@ -99,7 +104,7 @@ struct BuildDocksToolbarWindow : Window {
this->last_clicked_widget = WID_DT_INVALID;
this->InitNested(window_number);
this->OnInvalidateData();
if (_settings_client.gui.link_terraform_toolbar || _settings_client.gui.compact_vertical_toolbar) ShowTerraformToolbar();
if (_settings_client.gui.link_terraform_toolbar || _settings_client.gui.compact_vertical_toolbar) ShowTerraformToolbar(this);
}
void Close() override
@@ -189,12 +194,11 @@ struct BuildDocksToolbarWindow : Window {
{
switch (this->last_clicked_widget) {
case WID_DT_CANAL: // Build canal button
VpStartPlaceSizing(tile, (_game_mode == GM_EDITOR) ? VPM_X_AND_Y : VPM_X_OR_Y, DDSP_CREATE_WATER);
VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_CREATE_WATER);
break;
case WID_DT_LOCK: // Build lock button
/* Reuse DDSP_REMOVE_TRUCKSTOP. */
VpStartPlaceSizing(tile, VPM_SINGLE_TILE, DDSP_REMOVE_TRUCKSTOP);
Command<CMD_BUILD_LOCK>::Post(STR_ERROR_CAN_T_BUILD_LOCKS, CcBuildDocks, tile);
break;
case WID_DT_DEMOLISH: // Demolish aka dynamite button
@@ -228,7 +232,6 @@ struct BuildDocksToolbarWindow : Window {
{
switch (last_clicked_widget) {
case WID_DT_BUILD_AQUEDUCT:
case WID_DT_LOCK:
case WID_DT_STATION:
this->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
break;
@@ -246,44 +249,41 @@ struct BuildDocksToolbarWindow : Window {
GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
break;
case DDSP_CREATE_WATER:
DoCommandP(end_tile, start_tile, (_game_mode == GM_EDITOR && _ctrl_pressed) ? WATER_CLASS_SEA : WATER_CLASS_CANAL, CMD_BUILD_CANAL | CMD_MSG(STR_ERROR_CAN_T_BUILD_CANALS), CcPlaySound_CONSTRUCTION_WATER);
Command<CMD_BUILD_CANAL>::Post(STR_ERROR_CAN_T_BUILD_CANALS, CcPlaySound_CONSTRUCTION_WATER, end_tile, start_tile, (_game_mode == GM_EDITOR && _ctrl_pressed) ? WATER_CLASS_SEA : WATER_CLASS_CANAL, false);
break;
case DDSP_CREATE_RIVER:
DoCommandP(end_tile, start_tile, WATER_CLASS_RIVER | (_ctrl_pressed ? 1 << 2 : 0), CMD_BUILD_CANAL | CMD_MSG(STR_ERROR_CAN_T_PLACE_RIVERS), CcPlaySound_CONSTRUCTION_WATER);
Command<CMD_BUILD_CANAL>::Post(STR_ERROR_CAN_T_PLACE_RIVERS, CcPlaySound_CONSTRUCTION_WATER, end_tile, start_tile, WATER_CLASS_RIVER, _ctrl_pressed);
break;
case DDSP_BUILD_STATION: {
uint32 p2 = (uint32)INVALID_STATION << 16; // no station to join
/* Determine the watery part of the dock. */
DiagDirection dir = GetInclinedSlopeDirection(GetTileSlope(end_tile));
TileIndex tile_to = (dir != INVALID_DIAGDIR ? TileAddByDiagDir(end_tile, ReverseDiagDir(dir)) : end_tile);
/* Tile is always the land tile, so need to evaluate _thd.pos. */
CommandContainer cmdcont = { start_tile, _ctrl_pressed, p2, CMD_BUILD_DOCK | CMD_MSG(STR_ERROR_CAN_T_BUILD_DOCK_HERE), CcBuildDocks, "" };
//SetObjectToPlace(SPR_CURSOR_DOCK, PAL_NONE, HT_SPECIAL, this->window_class, this->window_number);
ShowSelectStationIfNeeded(cmdcont, TileArea(start_tile, end_tile));
bool adjacent = _ctrl_pressed;
auto proc = [=](bool test, StationID to_join) -> bool {
if (test) {
return Command<CMD_BUILD_DOCK>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_DOCK>()), end_tile, INVALID_STATION, adjacent).Succeeded();
} else {
return Command<CMD_BUILD_DOCK>::Post(STR_ERROR_CAN_T_BUILD_DOCK_HERE, CcBuildDocks, end_tile, to_join, adjacent);
}
};
VpStartPreSizing();
break;
}
case DDSP_BUILD_BRIDGE:
DoCommandP(start_tile, GetOtherAqueductEnd(start_tile), TRANSPORT_WATER << 15, CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_AQUEDUCT_HERE), CcBuildBridge);
Command<CMD_BUILD_BRIDGE>::Post(STR_ERROR_CAN_T_BUILD_AQUEDUCT_HERE, CcBuildBridge, end_tile, GetOtherAqueductEnd(end_tile), TRANSPORT_WATER, 0, 0);
VpStartPreSizing();
break;
case DDSP_REMOVE_TRUCKSTOP: { // Reusing for locks.
TileIndex middle_tile = start_tile;
if (start_tile != end_tile) middle_tile = TileAddByDiagDir(start_tile, DiagdirBetweenTiles(start_tile, end_tile));
DoCommandP(middle_tile, 0, 0, CMD_BUILD_LOCK | CMD_MSG(STR_ERROR_CAN_T_BUILD_LOCKS), CcBuildDocks);
VpStartPreSizing();
break;
}
case DDSP_SINGLE_TILE:
assert(start_tile == end_tile);
switch (last_clicked_widget) {
case WID_DT_BUOY:
DoCommandP(end_tile, 0, 0, CMD_BUILD_BUOY | CMD_MSG(STR_ERROR_CAN_T_POSITION_BUOY_HERE), CcBuildDocks);
Command<CMD_BUILD_BUOY>::Post(STR_ERROR_CAN_T_POSITION_BUOY_HERE, CcBuildDocks, end_tile);
break;
case WID_DT_DEPOT: // Build depot button
DoCommandP(end_tile, _ship_depot_direction, 0, CMD_BUILD_SHIP_DEPOT | CMD_MSG(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT), CcBuildDocks);
Command<CMD_BUILD_SHIP_DEPOT>::Post(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT, CcBuildDocks, end_tile, _ship_depot_direction);
break;
default: NOT_REACHED();
}
@@ -459,6 +459,7 @@ enum BuildDockStationWidgets {
BDSW_LT_OFF, ///< 'Off' button of coverage high light.
BDSW_LT_ON, ///< 'On' button of coverage high light.
BDSW_INFO, ///< 'Coverage highlight' label.
BDSW_ACCEPTANCE, ///< Acceptance info.
};
struct BuildDocksStationWindow : public PickerWindowBase {
@@ -488,17 +489,15 @@ public:
}
/* strings such as 'Size' and 'Coverage Area' */
int top = this->GetWidget<NWidgetBase>(BDSW_LT_OFF)->pos_y + this->GetWidget<NWidgetBase>(BDSW_LT_OFF)->current_y + WD_PAR_VSEP_NORMAL;
NWidgetBase *back_nwi = this->GetWidget<NWidgetBase>(BDSW_BACKGROUND);
int right = back_nwi->pos_x + back_nwi->current_x;
int bottom = back_nwi->pos_y + back_nwi->current_y;
top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, false) + WD_PAR_VSEP_NORMAL;
top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, true) + WD_PAR_VSEP_NORMAL;
Rect r = this->GetWidget<NWidgetBase>(BDSW_ACCEPTANCE)->GetCurrentRect();
int top = r.top + WidgetDimensions::scaled.vsep_normal;
top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, false) + WidgetDimensions::scaled.vsep_normal;
top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, true) + WidgetDimensions::scaled.vsep_normal;
/* Resize background if the window is too small.
* Never make the window smaller to avoid oscillating if the size change affects the acceptance.
* (This is the case, if making the window bigger moves the mouse into the window.) */
if (top > bottom) {
ResizeWindow(this, 0, top - bottom, false);
if (top > r.bottom) {
ResizeWindow(this, 0, top - r.bottom, false);
}
}
@@ -530,13 +529,12 @@ static const NWidgetPart _nested_build_dock_station_widgets[] = {
NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_DOCK_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BDSW_BACKGROUND),
NWidget(NWID_SPACER), SetMinimalSize(0, 3),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BDSW_INFO), SetMinimalSize(148, 14), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL),
NWidget(NWID_HORIZONTAL), SetPIP(14, 0, 14),
NWidget(WWT_TEXTBTN, COLOUR_GREY, BDSW_LT_OFF), SetMinimalSize(40, 12), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, BDSW_LT_ON), SetMinimalSize(40, 12), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BDSW_INFO), SetPadding(WidgetDimensions::unscaled.framerect), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(14, 0, 14),
NWidget(WWT_TEXTBTN, COLOUR_GREY, BDSW_LT_OFF), SetMinimalSize(60, 12), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, BDSW_LT_ON), SetMinimalSize(60, 12), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 20), SetResize(0, 1),
NWidget(WWT_EMPTY, COLOUR_GREY, BDSW_ACCEPTANCE), SetPadding(WidgetDimensions::unscaled.framerect), SetResize(0, 1),
EndContainer(),
};
@@ -576,25 +574,35 @@ public:
switch (widget) {
case WID_BDD_X:
case WID_BDD_Y:
size->width = ScaleGUITrad(96) + 2;
size->height = ScaleGUITrad(64) + 2;
size->width = ScaleGUITrad(96) + WidgetDimensions::scaled.fullbevel.Horizontal();
size->height = ScaleGUITrad(64) + WidgetDimensions::scaled.fullbevel.Vertical();
break;
}
}
void OnPaint() override
void DrawWidget(const Rect &r, int widget) const override
{
this->DrawWidgets();
DrawPixelInfo tmp_dpi;
int x1 = ScaleGUITrad(63) + 1;
int x2 = ScaleGUITrad(31) + 1;
int y1 = ScaleGUITrad(17) + 1;
int y2 = ScaleGUITrad(33) + 1;
switch (widget) {
case WID_BDD_X:
case WID_BDD_Y: {
Axis axis = widget == WID_BDD_X ? AXIS_X : AXIS_Y;
DrawShipDepotSprite(this->GetWidget<NWidgetBase>(WID_BDD_X)->pos_x + x1, this->GetWidget<NWidgetBase>(WID_BDD_X)->pos_y + y1, AXIS_X, DEPOT_PART_NORTH);
DrawShipDepotSprite(this->GetWidget<NWidgetBase>(WID_BDD_X)->pos_x + x2, this->GetWidget<NWidgetBase>(WID_BDD_X)->pos_y + y2, AXIS_X, DEPOT_PART_SOUTH);
DrawShipDepotSprite(this->GetWidget<NWidgetBase>(WID_BDD_Y)->pos_x + x2, this->GetWidget<NWidgetBase>(WID_BDD_Y)->pos_y + y1, AXIS_Y, DEPOT_PART_NORTH);
DrawShipDepotSprite(this->GetWidget<NWidgetBase>(WID_BDD_Y)->pos_x + x1, this->GetWidget<NWidgetBase>(WID_BDD_Y)->pos_y + y2, AXIS_Y, DEPOT_PART_SOUTH);
if (FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.Width(), r.Height())) {
DrawPixelInfo *old_dpi = _cur_dpi;
_cur_dpi = &tmp_dpi;
int x = (r.Width() - ScaleSpriteTrad(96)) / 2;
int y = (r.Height() - ScaleSpriteTrad(64)) / 2;
int x1 = ScaleSpriteTrad(63);
int x2 = ScaleSpriteTrad(31);
DrawShipDepotSprite(x + (axis == AXIS_X ? x1 : x2), y + ScaleSpriteTrad(17), axis, DEPOT_PART_NORTH);
DrawShipDepotSprite(x + (axis == AXIS_X ? x2 : x1), y + ScaleSpriteTrad(33), axis, DEPOT_PART_SOUTH);
_cur_dpi = old_dpi;
}
break;
}
}
}
void OnClick(Point pt, int widget, int click_count) override
@@ -619,17 +627,14 @@ static const NWidgetPart _nested_build_docks_depot_widgets[] = {
NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_DEPOT_BUILD_SHIP_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BDD_BACKGROUND),
NWidget(NWID_SPACER), SetMinimalSize(0, 3),
NWidget(NWID_HORIZONTAL_LTR),
NWidget(NWID_SPACER), SetMinimalSize(3, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BDD_X), SetSizingType(NWST_BUTTON), SetMinimalSize(98, 66), SetDataTip(0x0, STR_DEPOT_BUILD_SHIP_ORIENTATION_TOOLTIP),
NWidget(NWID_HORIZONTAL), SetPadding(3),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, 2, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BDD_X), SetMinimalSize(98, 66), SetDataTip(0x0, STR_DEPOT_BUILD_SHIP_ORIENTATION_TOOLTIP), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BDD_Y), SetMinimalSize(98, 66), SetDataTip(0x0, STR_DEPOT_BUILD_SHIP_ORIENTATION_TOOLTIP), EndContainer(),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(2, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BDD_Y), SetSizingType(NWST_BUTTON), SetMinimalSize(98, 66), SetDataTip(0x0, STR_DEPOT_BUILD_SHIP_ORIENTATION_TOOLTIP),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(3, 0),
NWidget(NWID_SPACER), SetFill(1, 0),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 3),
EndContainer(),
};

View File

@@ -48,6 +48,9 @@
#include "goal_base.h"
#include "story_base.h"
#include "linkgraph/refresh.h"
#include "company_cmd.h"
#include "economy_cmd.h"
#include "vehicle_cmd.h"
#include "table/strings.h"
#include "table/pricebase.h"
@@ -96,19 +99,33 @@ const ScoreInfo _score_info[] = {
int64 _score_part[MAX_COMPANIES][SCORE_END];
Economy _economy;
Prices _price;
Money _additional_cash_required;
static PriceMultipliers _price_base_multiplier;
extern int GetAmountOwnedBy(const Company *c, Owner owner);
/**
* Calculate the value of the company. That is the value of all
* assets (vehicles, stations, etc) and money minus the loan,
* assets (vehicles, stations, shares) and money minus the loan,
* except when including_loan is \c false which is useful when
* we want to calculate the value for bankruptcy.
* @param c the company to get the value of.
* @param c the company to get the value of.
* @param including_loan include the loan in the company value.
* @return the value of the company.
*/
Money CalculateCompanyValue(const Company *c, bool including_loan)
{
Money owned_shares_value = 0;
for (const Company *co : Company::Iterate()) {
int shares_owned = GetAmountOwnedBy(co, c->index);
if (shares_owned > 0) owned_shares_value += (CalculateCompanyValueExcludingShares(co) / 4) * shares_owned;
}
return owned_shares_value + CalculateCompanyValueExcludingShares(c);
}
Money CalculateCompanyValueExcludingShares(const Company *c, bool including_loan)
{
Owner owner = c->index;
@@ -304,43 +321,39 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner)
assert(old_owner != new_owner);
{
uint i;
/* See if the old_owner had shares in other companies */
for (const Company *c : Company::Iterate()) {
for (i = 0; i < 4; i++) {
if (c->share_owners[i] == old_owner) {
/* Sell its shares */
CommandCost res = DoCommand(0, c->index, 0, DC_EXEC | DC_BANKRUPT, CMD_SELL_SHARE_IN_COMPANY);
/* Because we are in a DoCommand, we can't just execute another one and
* expect the money to be removed. We need to do it ourself! */
SubtractMoneyFromCompany(res);
}
}
}
/* Sell all the shares that people have on this company */
Backup<CompanyID> cur_company2(_current_company, FILE_LINE);
Company *c = Company::Get(old_owner);
for (i = 0; i < 4; i++) {
if (c->share_owners[i] == INVALID_OWNER) continue;
if (c->bankrupt_value == 0 && c->share_owners[i] == new_owner) {
/* You are the one buying the company; so don't sell the shares back to you. */
c->share_owners[i] = INVALID_OWNER;
} else {
cur_company2.Change(c->share_owners[i]);
/* Sell the shares */
CommandCost res = DoCommand(0, old_owner, 0, DC_EXEC | DC_BANKRUPT, CMD_SELL_SHARE_IN_COMPANY);
/* See if the old_owner had shares in other companies */
for (const Company *c : Company::Iterate()) {
for (auto share_owner : c->share_owners) {
if (share_owner == old_owner) {
/* Sell its shares */
CommandCost res = Command<CMD_SELL_SHARE_IN_COMPANY>::Do(DC_EXEC | DC_BANKRUPT, c->index);
/* Because we are in a DoCommand, we can't just execute another one and
* expect the money to be removed. We need to do it ourself! */
SubtractMoneyFromCompany(res);
}
}
cur_company2.Restore();
}
/* Sell all the shares that people have on this company */
Backup<CompanyID> cur_company2(_current_company, FILE_LINE);
Company *c = Company::Get(old_owner);
for (auto &share_owner : c->share_owners) {
if (share_owner == INVALID_OWNER) continue;
if (c->bankrupt_value == 0 && share_owner == new_owner) {
/* You are the one buying the company; so don't sell the shares back to you. */
share_owner = INVALID_OWNER;
} else {
cur_company2.Change(share_owner);
/* Sell the shares */
CommandCost res = Command<CMD_SELL_SHARE_IN_COMPANY>::Do(DC_EXEC | DC_BANKRUPT, old_owner);
/* Because we are in a DoCommand, we can't just execute another one and
* expect the money to be removed. We need to do it ourself! */
SubtractMoneyFromCompany(res);
}
}
cur_company2.Restore();
/* Temporarily increase the company's money, to be sure that
* removing their property doesn't fail because of lack of money.
* Not too drastically though, because it could overflow */
@@ -448,7 +461,7 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner)
* However, do not rely on that behaviour.
*/
int interval = CompanyServiceInterval(new_company, v->type);
DoCommand(v->tile, v->index, interval | (new_company->settings.vehicle.servint_ispercent << 17), DC_EXEC | DC_BANKRUPT, CMD_CHANGE_SERVICE_INT);
Command<CMD_CHANGE_SERVICE_INT>::Do(DC_EXEC | DC_BANKRUPT, v->index, interval, false, new_company->settings.vehicle.servint_ispercent);
}
v->owner = new_owner;
@@ -627,7 +640,7 @@ static void CompanyCheckBankrupt(Company *c)
* player we are sure (the above check) that we are not the local
* company and thus we won't be moved. */
if (!_networking || _network_server) {
DoCommandP(0, CCA_DELETE | (c->index << 16) | (CRR_BANKRUPT << 24), 0, CMD_COMPANY_CTRL);
Command<CMD_COMPANY_CTRL>::Post(CCA_DELETE, c->index, CRR_BANKRUPT, INVALID_CLIENT_ID);
return;
}
break;
@@ -650,13 +663,8 @@ static void CompaniesGenStatistics()
Backup<CompanyID> cur_company(_current_company, FILE_LINE);
if (!_settings_game.economy.infrastructure_maintenance) {
for (const Station *st : Station::Iterate()) {
cur_company.Change(st->owner);
CommandCost cost(EXPENSES_PROPERTY, _price[PR_STATION_VALUE] >> 1);
SubtractMoneyFromCompany(cost);
}
} else {
/* Pay Infrastructure Maintenance, if enabled */
if (_settings_game.economy.infrastructure_maintenance) {
/* Improved monthly infrastructure costs. */
for (const Company *c : Company::Iterate()) {
cur_company.Change(c->index);
@@ -838,7 +846,7 @@ static void CompaniesPayInterest()
Money up_to_previous_month = yearly_fee * _cur_month / 12;
Money up_to_this_month = yearly_fee * (_cur_month + 1) / 12;
SubtractMoneyFromCompany(CommandCost(EXPENSES_LOAN_INT, up_to_this_month - up_to_previous_month));
SubtractMoneyFromCompany(CommandCost(EXPENSES_LOAN_INTEREST, up_to_this_month - up_to_previous_month));
SubtractMoneyFromCompany(CommandCost(EXPENSES_OTHER, _price[PR_STATION_VALUE] >> 2));
}
@@ -1038,9 +1046,10 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint n
uint accepted = 0;
for (Industry *ind : st->industries_near) {
for (const auto &i : st->industries_near) {
if (num_pieces == 0) break;
Industry *ind = i.industry;
if (ind->index == source) continue;
uint cargo_index;
@@ -1194,7 +1203,7 @@ CargoPayment::~CargoPayment()
if (this->visual_transfer != 0) {
ShowFeederIncomeAnimation(this->front->x_pos, this->front->y_pos,
this->front->z_pos, this->visual_transfer, -this->visual_profit);
} else if (this->visual_profit != 0) {
} else {
ShowCostOrIncomeAnimation(this->front->x_pos, this->front->y_pos,
this->front->z_pos, -this->visual_profit);
}
@@ -1266,7 +1275,7 @@ void PrepareUnload(Vehicle *front_v)
front_v->cargo_payment = new CargoPayment(front_v);
StationIDStack next_station = front_v->GetNextStoppingStation();
if (front_v->orders.list == nullptr || (front_v->current_order.GetUnloadType() & OUFB_NO_UNLOAD) == 0) {
if (front_v->orders == nullptr || (front_v->current_order.GetUnloadType() & OUFB_NO_UNLOAD) == 0) {
Station *st = Station::Get(front_v->last_station_visited);
for (Vehicle *v = front_v; v != nullptr; v = v->Next()) {
const GoodsEntry *ge = &st->goods[v->cargo_type];
@@ -1485,14 +1494,14 @@ static void HandleStationRefit(Vehicle *v, CargoArray &consist_capleft, Station
if (st->goods[cid].cargo.HasCargoFor(next_station)) {
/* Try to find out if auto-refitting would succeed. In case the refit is allowed,
* the returned refit capacity will be greater than zero. */
DoCommand(v_start->tile, v_start->index, cid | 1U << 24 | 0xFF << 8 | 1U << 16, DC_QUERY_COST, GetCmdRefitVeh(v_start)); // Auto-refit and only this vehicle including artic parts.
auto [cc, refit_capacity, mail_capacity] = Command<CMD_REFIT_VEHICLE>::Do(DC_QUERY_COST, v_start->index, cid, 0xFF, true, false, 1); // Auto-refit and only this vehicle including artic parts.
/* Try to balance different loadable cargoes between parts of the consist, so that
* all of them can be loaded. Avoid a situation where all vehicles suddenly switch
* to the first loadable cargo for which there is only one packet. If the capacities
* are equal refit to the cargo of which most is available. This is important for
* consists of only a single vehicle as those will generally have a consist_capleft
* of 0 for all cargoes. */
if (_returned_refit_capacity > 0 && (consist_capleft[cid] < consist_capleft[new_cid] ||
if (refit_capacity > 0 && (consist_capleft[cid] < consist_capleft[new_cid] ||
(consist_capleft[cid] == consist_capleft[new_cid] &&
st->goods[cid].cargo.AvailableCount() > st->goods[new_cid].cargo.AvailableCount()))) {
new_cid = cid;
@@ -1508,7 +1517,7 @@ static void HandleStationRefit(Vehicle *v, CargoArray &consist_capleft, Station
* "via any station" before reserving. We rather produce some more "any station" cargo than
* misrouting it. */
IterateVehicleParts(v_start, ReturnCargoAction(st, INVALID_STATION));
CommandCost cost = DoCommand(v_start->tile, v_start->index, new_cid | 1U << 24 | 0xFF << 8 | 1U << 16, DC_EXEC, GetCmdRefitVeh(v_start)); // Auto-refit and only this vehicle including artic parts.
CommandCost cost = std::get<0>(Command<CMD_REFIT_VEHICLE>::Do(DC_EXEC, v_start->index, new_cid, 0xFF, true, false, 1)); // Auto-refit and only this vehicle including artic parts.
if (cost.Succeeded()) v->First()->profit_this_year -= cost.GetCost() << 8;
}
@@ -2005,21 +2014,15 @@ static void DoAcquireCompany(Company *c)
delete c;
}
extern int GetAmountOwnedBy(const Company *c, Owner owner);
/**
* Acquire shares in an opposing company.
* @param tile unused
* @param flags type of operation
* @param p1 company to buy the shares from
* @param p2 unused
* @param text unused
* @param target_company company to buy the shares from
* @return the cost of this operation or an error
*/
CommandCost CmdBuyShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdBuyShareInCompany(DoCommandFlag flags, CompanyID target_company)
{
CommandCost cost(EXPENSES_OTHER);
CompanyID target_company = (CompanyID)p1;
Company *c = Company::GetIfValid(target_company);
/* Check if buying shares is allowed (protection against modified clients)
@@ -2030,9 +2033,9 @@ CommandCost CmdBuyShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1,
if (_cur_year - c->inaugurated_year < _settings_game.economy.min_years_for_shares) return_cmd_error(STR_ERROR_PROTECTED);
/* Those lines are here for network-protection (clients can be slow) */
if (GetAmountOwnedBy(c, COMPANY_SPECTATOR) == 0) return cost;
if (GetAmountOwnedBy(c, INVALID_OWNER) == 0) return cost;
if (GetAmountOwnedBy(c, COMPANY_SPECTATOR) == 1) {
if (GetAmountOwnedBy(c, INVALID_OWNER) == 1) {
if (!c->is_ai) return cost; // We can not buy out a real company (temporarily). TODO: well, enable it obviously.
if (GetAmountOwnedBy(c, _current_company) == 3 && !MayCompanyTakeOver(_current_company, target_company)) return_cmd_error(STR_ERROR_TOO_MANY_VEHICLES_IN_GAME);
@@ -2041,17 +2044,14 @@ CommandCost CmdBuyShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1,
cost.AddCost(CalculateCompanyValue(c) >> 2);
if (flags & DC_EXEC) {
Owner *b = c->share_owners;
auto unowned_share = std::find(c->share_owners.begin(), c->share_owners.end(), INVALID_OWNER);
assert(unowned_share != c->share_owners.end()); // share owners is guaranteed to contain at least one INVALID_OWNER, i.e. unowned share
*unowned_share = _current_company;
while (*b != COMPANY_SPECTATOR) b++; // share owners is guaranteed to contain at least one COMPANY_SPECTATOR
*b = _current_company;
for (int i = 0; c->share_owners[i] == _current_company;) {
if (++i == 4) {
c->bankrupt_value = 0;
DoAcquireCompany(c);
break;
}
auto current_company_owns_share = [](auto share_owner) { return share_owner == _current_company; };
if (std::all_of(c->share_owners.begin(), c->share_owners.end(), current_company_owns_share)) {
c->bankrupt_value = 0;
DoAcquireCompany(c);
}
InvalidateWindowData(WC_COMPANY, target_company);
CompanyAdminUpdate(c);
@@ -2061,16 +2061,12 @@ CommandCost CmdBuyShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1,
/**
* Sell shares in an opposing company.
* @param tile unused
* @param flags type of operation
* @param p1 company to sell the shares from
* @param p2 unused
* @param text unused
* @param target_company company to sell the shares from
* @return the cost of this operation or an error
*/
CommandCost CmdSellShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdSellShareInCompany(DoCommandFlag flags, CompanyID target_company)
{
CompanyID target_company = (CompanyID)p1;
Company *c = Company::GetIfValid(target_company);
/* Cannot sell own shares */
@@ -2088,9 +2084,9 @@ CommandCost CmdSellShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1
cost = -(cost - (cost >> 7));
if (flags & DC_EXEC) {
Owner *b = c->share_owners;
while (*b != _current_company) b++; // share owners is guaranteed to contain company
*b = COMPANY_SPECTATOR;
auto our_owner = std::find(c->share_owners.begin(), c->share_owners.end(), _current_company);
assert(our_owner != c->share_owners.end()); // share owners is guaranteed to contain at least one INVALID_OWNER
*our_owner = INVALID_OWNER;
InvalidateWindowData(WC_COMPANY, target_company);
CompanyAdminUpdate(c);
}
@@ -2102,16 +2098,12 @@ CommandCost CmdSellShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1
* When a competing company is gone bankrupt you get the chance to purchase
* that company.
* @todo currently this only works for AI companies
* @param tile unused
* @param flags type of operation
* @param p1 company to buy up
* @param p2 unused
* @param text unused
* @param target_company company to buy up
* @return the cost of this operation or an error
*/
CommandCost CmdBuyCompany(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdBuyCompany(DoCommandFlag flags, CompanyID target_company)
{
CompanyID target_company = (CompanyID)p1;
Company *c = Company::GetIfValid(target_company);
if (c == nullptr) return CMD_ERROR;

24
src/economy_cmd.h Normal file
View File

@@ -0,0 +1,24 @@
/*
* 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 economy_cmd.h Command definitions related to the economy. */
#ifndef ECONOMY_CMD_H
#define ECONOMY_CMD_H
#include "command_type.h"
#include "company_type.h"
CommandCost CmdBuyShareInCompany(DoCommandFlag flags, CompanyID target_company);
CommandCost CmdSellShareInCompany(DoCommandFlag flags, CompanyID target_company);
CommandCost CmdBuyCompany(DoCommandFlag flags, CompanyID target_company);
DEF_CMD_TRAIT(CMD_BUY_SHARE_IN_COMPANY, CmdBuyShareInCompany, 0, CMDT_MONEY_MANAGEMENT)
DEF_CMD_TRAIT(CMD_SELL_SHARE_IN_COMPANY, CmdSellShareInCompany, 0, CMDT_MONEY_MANAGEMENT)
DEF_CMD_TRAIT(CMD_BUY_COMPANY, CmdBuyCompany, 0, CMDT_MONEY_MANAGEMENT)
#endif /* ECONOMY_CMD_H */

View File

@@ -162,11 +162,11 @@ enum ExpensesType : byte {
EXPENSES_AIRCRAFT_RUN, ///< Running costs aircraft.
EXPENSES_SHIP_RUN, ///< Running costs ships.
EXPENSES_PROPERTY, ///< Property costs.
EXPENSES_TRAIN_INC, ///< Income from trains.
EXPENSES_ROADVEH_INC, ///< Income from road vehicles.
EXPENSES_AIRCRAFT_INC, ///< Income from aircraft.
EXPENSES_SHIP_INC, ///< Income from ships.
EXPENSES_LOAN_INT, ///< Interest payments over the loan.
EXPENSES_TRAIN_REVENUE, ///< Revenue from trains.
EXPENSES_ROADVEH_REVENUE, ///< Revenue from road vehicles.
EXPENSES_AIRCRAFT_REVENUE, ///< Revenue from aircraft.
EXPENSES_SHIP_REVENUE, ///< Revenue from ships.
EXPENSES_LOAN_INTEREST, ///< Interest payments over the loan.
EXPENSES_OTHER, ///< Other expenses.
EXPENSES_END, ///< Number of expense types.
INVALID_EXPENSES = 0xFF, ///< Invalid expense type.

View File

@@ -485,10 +485,11 @@ static void DrawRailCatenaryRailway(const TileInfo *ti)
/*
* The "wire"-sprite position is inside the tile, i.e. 0 <= sss->?_offset < TILE_SIZE.
* Therefore it is safe to use GetSlopePixelZ() for the elevation.
* Also note that the result of GetSlopePixelZ() is very special for bridge-ramps.
* Also note that the result of GetSlopePixelZ() is very special for bridge-ramps, so we round the result up or
* down to the nearest full height change.
*/
AddSortableSpriteToDraw(wire_base + sss->image_offset, PAL_NONE, ti->x + sss->x_offset, ti->y + sss->y_offset,
sss->x_size, sss->y_size, sss->z_size, GetSlopePixelZ(ti->x + sss->x_offset, ti->y + sss->y_offset) + sss->z_offset,
sss->x_size, sss->y_size, sss->z_size, (GetSlopePixelZ(ti->x + sss->x_offset, ti->y + sss->y_offset) + 4) / 8 * 8 + sss->z_offset,
IsTransparencySet(TO_CATENARY));
}
}

View File

@@ -29,6 +29,7 @@
#include "vehicle_func.h"
#include "articulated_vehicles.h"
#include "error.h"
#include "engine_base.h"
#include "table/strings.h"
#include "table/engines.h"
@@ -72,9 +73,12 @@ Engine::Engine(VehicleType type, EngineID base)
this->grf_prop.local_id = base;
this->list_position = base;
this->preview_company = INVALID_COMPANY;
this->display_last_variant = INVALID_ENGINE;
/* Check if this base engine is within the original engine data range */
if (base >= _engine_counts[type]) {
/* 'power' defaults to zero, so we also have to default to 'wagon' */
if (type == VEH_TRAIN) this->u.rail.railveh_type = RAILVEH_WAGON;
/* Set model life to maximum to make wagons available */
this->info.base_life = 0xFF;
/* Set road vehicle tractive effort to the default value */
@@ -90,6 +94,8 @@ Engine::Engine(VehicleType type, EngineID base)
}
/* Set cargo aging period to the default value. */
this->info.cargo_age_period = CARGO_AGING_TICKS;
/* Not a variant */
this->info.variant_id = INVALID_ENGINE;
return;
}
@@ -532,7 +538,7 @@ void SetupEngines()
_engine_pool.CleanPool();
assert(_engine_mngr.size() >= _engine_mngr.NUM_DEFAULT_ENGINES);
uint index = 0;
[[maybe_unused]] uint index = 0;
for (const EngineIDMapping &eid : _engine_mngr) {
/* Assert is safe; there won't be more than 256 original vehicles
* in any case, and we just cleaned the pool. */
@@ -556,13 +562,32 @@ static bool IsWagon(EngineID index)
return e->type == VEH_TRAIN && e->u.rail.railveh_type == RAILVEH_WAGON;
}
/**
* Ensure engine is not set as the last used variant for any other engine.
* @param engine_id Engine being removed.
* @param type Type of engine.
*/
static void ClearLastVariant(EngineID engine_id, VehicleType type)
{
for (Engine *e : Engine::IterateType(type)) {
if (e->display_last_variant == engine_id) e->display_last_variant = INVALID_ENGINE;
}
}
/**
* Update #Engine::reliability and (if needed) update the engine GUIs.
* @param e %Engine to update.
*/
static void CalcEngineReliability(Engine *e)
void CalcEngineReliability(Engine *e, bool new_month)
{
uint age = e->age;
/* Get source engine for reliability age. This is normally our engine unless variant reliability syncing is requested. */
Engine *re = e;
while (re->info.variant_id != INVALID_ENGINE && re->info.variant_id != re->index && (re->info.extra_flags & ExtraEngineFlags::SyncReliability) != ExtraEngineFlags::None) {
re = Engine::Get(re->info.variant_id);
}
uint age = re->age;
if (new_month && re->index > e->index && age != MAX_DAY) age++; /* parent variant's age has not yet updated. */
/* Check for early retirement */
if (e->company_avail != 0 && !_settings_game.vehicle.never_expire_vehicles && e->info.base_life != 0xFF) {
@@ -571,6 +596,7 @@ static void CalcEngineReliability(Engine *e)
if (retire_early != 0 && age >= retire_early_max_age) {
/* Early retirement is enabled and we're past the date... */
e->company_avail = 0;
ClearLastVariant(e->index, e->type);
AddRemoveEngineFromAutoreplaceAndBuildWindows(e->type);
}
}
@@ -591,10 +617,10 @@ static void CalcEngineReliability(Engine *e)
e->company_avail = 0;
e->reliability = e->reliability_final;
/* Kick this engine out of the lists */
ClearLastVariant(e->index, e->type);
AddRemoveEngineFromAutoreplaceAndBuildWindows(e->type);
}
SetWindowClassesDirty(WC_BUILD_VEHICLE); // Update to show the new reliability
SetWindowClassesDirty(WC_REPLACE_VEHICLE);
}
/** Compute the value for #_year_engine_aging_stops. */
@@ -622,8 +648,9 @@ void SetYearEngineAgingStops()
* Start/initialise one engine.
* @param e The engine to initialise.
* @param aging_date The date used for age calculations.
* @param seed Random seed.
*/
void StartupOneEngine(Engine *e, Date aging_date)
void StartupOneEngine(Engine *e, Date aging_date, uint32 seed)
{
const EngineInfo *ei = &e->info;
@@ -636,7 +663,7 @@ void StartupOneEngine(Engine *e, Date aging_date)
* Make sure they use the same randomisation of the date. */
SavedRandomSeeds saved_seeds;
SaveRandomSeeds(&saved_seeds);
SetRandomSeed(_settings_game.game_creation.generation_seed ^
SetRandomSeed(_settings_game.game_creation.generation_seed ^ seed ^
ei->base_intro ^
e->type ^
e->GetGRFID());
@@ -652,7 +679,17 @@ void StartupOneEngine(Engine *e, Date aging_date)
e->flags |= ENGINE_AVAILABLE;
}
RestoreRandomSeeds(saved_seeds);
/* Get parent variant index for syncing reliability via random seed. */
const Engine *re = e;
while (re->info.variant_id != INVALID_ENGINE && re->info.variant_id != re->index && (re->info.extra_flags & ExtraEngineFlags::SyncReliability) != ExtraEngineFlags::None) {
re = Engine::Get(re->info.variant_id);
}
SetRandomSeed(_settings_game.game_creation.generation_seed ^ seed ^
(re->index << 16) ^ (re->info.base_intro << 12) ^ (re->info.decay_speed << 8) ^
(re->info.lifelength << 4) ^ re->info.retire_early ^
e->type ^
e->GetGRFID());
r = Random();
e->reliability_start = GB(r, 16, 14) + 0x7AE0;
@@ -664,9 +701,9 @@ void StartupOneEngine(Engine *e, Date aging_date)
e->duration_phase_2 = GB(r, 5, 4) + ei->base_life * 12 - 96;
e->duration_phase_3 = GB(r, 9, 7) + 120;
e->reliability_spd_dec = ei->decay_speed << 2;
RestoreRandomSeeds(saved_seeds);
CalcEngineReliability(e);
e->reliability_spd_dec = ei->decay_speed << 2;
/* prevent certain engines from ever appearing. */
if (!HasBit(ei->climates, _settings_game.game_creation.landscape)) {
@@ -683,9 +720,13 @@ void StartupEngines()
{
/* Aging of vehicles stops, so account for that when starting late */
const Date aging_date = std::min(_date, ConvertYMDToDate(_year_engine_aging_stops, 0, 1));
uint32 seed = Random();
for (Engine *e : Engine::Iterate()) {
StartupOneEngine(e, aging_date);
StartupOneEngine(e, aging_date, seed);
}
for (Engine *e : Engine::Iterate()) {
CalcEngineReliability(e, false);
}
/* Update the bitmasks for the vehicle lists */
@@ -696,6 +737,9 @@ void StartupEngines()
/* Invalidate any open purchase lists */
InvalidateWindowClassesData(WC_BUILD_VEHICLE);
SetWindowClassesDirty(WC_BUILD_VEHICLE);
SetWindowClassesDirty(WC_REPLACE_VEHICLE);
}
/**
@@ -744,6 +788,7 @@ static void DisableEngineForCompany(EngineID eid, CompanyID company)
}
if (company == _local_company) {
ClearLastVariant(e->index, e->type);
AddRemoveEngineFromAutoreplaceAndBuildWindows(e->type);
}
}
@@ -752,8 +797,9 @@ static void DisableEngineForCompany(EngineID eid, CompanyID company)
* Company \a company accepts engine \a eid for preview.
* @param eid Engine being accepted (is under preview).
* @param company Current company previewing the engine.
* @param recursion_depth Recursion depth to avoid infinite loop.
*/
static void AcceptEnginePreview(EngineID eid, CompanyID company)
static void AcceptEnginePreview(EngineID eid, CompanyID company, int recursion_depth = 0)
{
Engine *e = Engine::Get(eid);
@@ -768,6 +814,16 @@ static void AcceptEnginePreview(EngineID eid, CompanyID company)
* we have to use the GUI-scope scheduling of InvalidateWindowData.
*/
InvalidateWindowData(WC_ENGINE_PREVIEW, eid);
/* Don't search for variants to include if we are 10 levels deep already. */
if (recursion_depth >= 10) return;
/* Find variants to be included in preview. */
for (Engine *ve : Engine::IterateType(e->type)) {
if (ve->index != eid && ve->info.variant_id == eid && (ve->info.extra_flags & ExtraEngineFlags::JoinPreview) != ExtraEngineFlags::None) {
AcceptEnginePreview(ve->index, company, recursion_depth + 1);
}
}
}
/**
@@ -874,21 +930,19 @@ void ClearEnginesHiddenFlagOfCompany(CompanyID cid)
/**
* Set the visibility of an engine.
* @param tile Unused.
* @param flags Operation to perform.
* @param p1 Unused.
* @param p2 Bit 31: 0=visible, 1=hidden, other bits for the #EngineID.
* @param text Unused.
* @param engine_id Engine id..
* @param hide Set for hidden, unset for visible.
* @return The cost of this operation or an error.
*/
CommandCost CmdSetVehicleVisibility(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdSetVehicleVisibility(DoCommandFlag flags, EngineID engine_id, bool hide)
{
Engine *e = Engine::GetIfValid(GB(p2, 0, 31));
Engine *e = Engine::GetIfValid(engine_id);
if (e == nullptr || _current_company >= MAX_COMPANIES) return CMD_ERROR;
if (!IsEngineBuildable(e->index, e->type, _current_company)) return CMD_ERROR;
if ((flags & DC_EXEC) != 0) {
SB(e->company_hidden, _current_company, 1, GB(p2, 31, 1));
SB(e->company_hidden, _current_company, 1, hide ? 1 : 0);
AddRemoveEngineFromAutoreplaceAndBuildWindows(e->type);
}
@@ -898,40 +952,31 @@ CommandCost CmdSetVehicleVisibility(TileIndex tile, DoCommandFlag flags, uint32
/**
* Accept an engine prototype. XXX - it is possible that the top-company
* changes while you are waiting to accept the offer? Then it becomes invalid
* @param tile unused
* @param flags operation to perform
* @param p1 engine-prototype offered
* @param p2 unused
* @param text unused
* @param engine_id engine-prototype offered
* @return the cost of this operation or an error
*/
CommandCost CmdWantEnginePreview(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdWantEnginePreview(DoCommandFlag flags, EngineID engine_id)
{
Engine *e = Engine::GetIfValid(p1);
Engine *e = Engine::GetIfValid(engine_id);
if (e == nullptr || !(e->flags & ENGINE_EXCLUSIVE_PREVIEW) || e->preview_company != _current_company) return CMD_ERROR;
if (flags & DC_EXEC) AcceptEnginePreview(p1, _current_company);
if (flags & DC_EXEC) AcceptEnginePreview(engine_id, _current_company);
return CommandCost();
}
/**
* Allow or forbid a specific company to use an engine
* @param tile unused
* @param flags operation to perform
* @param p1 engine id
* @param p2 various bitstuffed elements
* - p2 = (bit 0 - 7) - Company to allow/forbid the use of an engine.
* - p2 = (bit 31) - 0 to forbid, 1 to allow.
* @param text unused
* @param engine_id engine id
* @param company_id Company to allow/forbid the use of an engine.
* @param allow false to forbid, true to allow.
* @return the cost of this operation or an error
*/
CommandCost CmdEngineCtrl(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdEngineCtrl(DoCommandFlag flags, EngineID engine_id, CompanyID company_id, bool allow)
{
if (_current_company != OWNER_DEITY) return CMD_ERROR;
EngineID engine_id = (EngineID)p1;
CompanyID company_id = (CompanyID)GB(p2, 0, 8);
bool allow = HasBit(p2, 31);
if (!Engine::IsValidID(engine_id) || !Company::IsValidID(company_id)) return CMD_ERROR;
@@ -1002,7 +1047,7 @@ static void NewVehicleAvailable(Engine *e)
if (!IsVehicleTypeDisabled(e->type, true)) AI::BroadcastNewEvent(new ScriptEventEngineAvailable(index));
/* Only provide the "New Vehicle available" news paper entry, if engine can be built. */
if (!IsVehicleTypeDisabled(e->type, false)) {
if (!IsVehicleTypeDisabled(e->type, false) && (e->info.extra_flags & ExtraEngineFlags::NoNews) == ExtraEngineFlags::None) {
SetDParam(0, GetEngineCategoryName(index));
SetDParam(1, index);
AddNewsItem(STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE_WITH_TYPE, NT_NEW_VEHICLES, NF_VEHICLE, NR_ENGINE, index);
@@ -1021,11 +1066,13 @@ static void NewVehicleAvailable(Engine *e)
void EnginesMonthlyLoop()
{
if (_cur_year < _year_engine_aging_stops) {
bool refresh = false;
for (Engine *e : Engine::Iterate()) {
/* Age the vehicle */
if ((e->flags & ENGINE_AVAILABLE) && e->age != MAX_DAY) {
e->age++;
CalcEngineReliability(e);
CalcEngineReliability(e, true);
refresh = true;
}
/* Do not introduce invalid engines */
@@ -1044,6 +1091,9 @@ void EnginesMonthlyLoop()
/* Do not introduce new rail wagons */
if (IsWagon(e->index)) continue;
/* Engine has no preview */
if ((e->info.extra_flags & ExtraEngineFlags::NoPreview) != ExtraEngineFlags::None) continue;
/* Show preview dialog to one of the companies. */
e->flags |= ENGINE_EXCLUSIVE_PREVIEW;
e->preview_company = INVALID_COMPANY;
@@ -1052,6 +1102,11 @@ void EnginesMonthlyLoop()
}
InvalidateWindowClassesData(WC_BUILD_VEHICLE); // rebuild the purchase list (esp. when sorted by reliability)
if (refresh) {
SetWindowClassesDirty(WC_BUILD_VEHICLE);
SetWindowClassesDirty(WC_REPLACE_VEHICLE);
}
}
}
@@ -1071,16 +1126,14 @@ static bool IsUniqueEngineName(const std::string &name)
/**
* Rename an engine.
* @param tile unused
* @param flags operation to perform
* @param p1 engine ID to rename
* @param p2 unused
* @param engine_id engine ID to rename
* @param text the new name or an empty string when resetting to the default
* @return the cost of this operation or an error
*/
CommandCost CmdRenameEngine(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
CommandCost CmdRenameEngine(DoCommandFlag flags, EngineID engine_id, const std::string &text)
{
Engine *e = Engine::GetIfValid(p1);
Engine *e = Engine::GetIfValid(engine_id);
if (e == nullptr) return CMD_ERROR;
bool reset = text.empty();

View File

@@ -21,6 +21,15 @@ struct WagonOverride {
const SpriteGroup *group;
};
/** Flags used client-side in the purchase/autorenew engine list. */
enum class EngineDisplayFlags : byte {
None = 0, ///< No flag set.
HasVariants = (1U << 0), ///< Set if engine has variants.
IsFolded = (1U << 1), ///< Set if display of variants should be folded (hidden).
Shaded = (1U << 2), ///< Set if engine should be masked.
};
DECLARE_ENUM_AS_BIT_SET(EngineDisplayFlags)
typedef Pool<Engine, EngineID, 64, 64000> EnginePool;
extern EnginePool _engine_pool;
@@ -45,6 +54,9 @@ struct Engine : EnginePool::PoolItem<&_engine_pool> {
uint8 original_image_index; ///< Original vehicle image index, thus the image index of the overridden vehicle
VehicleType type; ///< %Vehicle type, ie #VEH_ROAD, #VEH_TRAIN, etc.
EngineDisplayFlags display_flags; ///< NOSAVE client-side-only display flags for build engine list.
EngineID display_last_variant; ///< NOSAVE client-side-only last variant selected.
EngineInfo info;
union {

25
src/engine_cmd.h Normal file
View File

@@ -0,0 +1,25 @@
/*
* 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 engine_cmd.h Command definitions related to engines. */
#ifndef ENGINE_CMD_H
#define ENGINE_CMD_H
#include "command_type.h"
CommandCost CmdWantEnginePreview(DoCommandFlag flags, EngineID engine_id);
CommandCost CmdEngineCtrl(DoCommandFlag flags, EngineID engine_id, CompanyID company_id, bool allow);
CommandCost CmdRenameEngine(DoCommandFlag flags, EngineID engine_id, const std::string &text);
CommandCost CmdSetVehicleVisibility(DoCommandFlag flags, EngineID engine_id, bool hide);
DEF_CMD_TRAIT(CMD_WANT_ENGINE_PREVIEW, CmdWantEnginePreview, 0, CMDT_VEHICLE_MANAGEMENT)
DEF_CMD_TRAIT(CMD_ENGINE_CTRL, CmdEngineCtrl, CMD_DEITY, CMDT_VEHICLE_MANAGEMENT)
DEF_CMD_TRAIT(CMD_RENAME_ENGINE, CmdRenameEngine, CMD_SERVER, CMDT_OTHER_MANAGEMENT)
DEF_CMD_TRAIT(CMD_SET_VEHICLE_VISIBILITY, CmdSetVehicleVisibility, 0, CMDT_COMPANY_SETTING)
#endif /* ENGINE_CMD_H */

View File

@@ -26,7 +26,8 @@ bool IsEngineBuildable(EngineID engine, VehicleType type, CompanyID company);
bool IsEngineRefittable(EngineID engine);
void GetArticulatedVehicleCargoesAndRefits(EngineID engine, CargoArray *cargoes, CargoTypes *refits, CargoID cargo_type, uint cargo_capacity);
void SetYearEngineAgingStops();
void StartupOneEngine(Engine *e, Date aging_date);
void CalcEngineReliability(Engine *e, bool new_month);
void StartupOneEngine(Engine *e, Date aging_date, uint32 seed);
uint GetTotalCapacityOfArticulatedParts(EngineID engine);

View File

@@ -23,6 +23,8 @@
#include "roadveh.h"
#include "ship.h"
#include "aircraft.h"
#include "engine_cmd.h"
#include "zoom_func.h"
#include "widgets/engine_widget.h"
@@ -93,11 +95,11 @@ struct EnginePreviewWindow : Window {
case VEH_SHIP: GetShipSpriteSize( engine, x, y, x_offs, y_offs, image_type); break;
case VEH_AIRCRAFT: GetAircraftSpriteSize(engine, x, y, x_offs, y_offs, image_type); break;
}
this->vehicle_space = std::max<int>(40, y - y_offs);
this->vehicle_space = std::max<int>(ScaleSpriteTrad(40), y - y_offs);
size->width = std::max(size->width, x - x_offs);
SetDParam(0, GetEngineCategoryName(engine));
size->height = GetStringHeight(STR_ENGINE_PREVIEW_MESSAGE, size->width) + WD_PAR_VSEP_WIDE + FONT_HEIGHT_NORMAL + this->vehicle_space;
size->height = GetStringHeight(STR_ENGINE_PREVIEW_MESSAGE, size->width) + WidgetDimensions::scaled.vsep_wide + FONT_HEIGHT_NORMAL + this->vehicle_space;
SetDParam(0, engine);
size->height += GetStringHeight(GetEngineInfoString(engine), size->width);
}
@@ -108,24 +110,23 @@ struct EnginePreviewWindow : Window {
EngineID engine = this->window_number;
SetDParam(0, GetEngineCategoryName(engine));
int y = r.top + GetStringHeight(STR_ENGINE_PREVIEW_MESSAGE, r.right - r.left + 1);
y = DrawStringMultiLine(r.left, r.right, r.top, y, STR_ENGINE_PREVIEW_MESSAGE, TC_FROMSTRING, SA_CENTER) + WD_PAR_VSEP_WIDE;
int y = DrawStringMultiLine(r, STR_ENGINE_PREVIEW_MESSAGE, TC_FROMSTRING, SA_HOR_CENTER | SA_TOP) + WidgetDimensions::scaled.vsep_wide;
SetDParam(0, engine);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_ENGINE_NAME, TC_BLACK, SA_HOR_CENTER);
DrawString(r.left, r.right, y, STR_ENGINE_NAME, TC_BLACK, SA_HOR_CENTER);
y += FONT_HEIGHT_NORMAL;
DrawVehicleEngine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, this->width >> 1, y + this->vehicle_space / 2, engine, GetEnginePalette(engine, _local_company), EIT_PREVIEW);
DrawVehicleEngine(r.left, r.right, this->width >> 1, y + this->vehicle_space / 2, engine, GetEnginePalette(engine, _local_company), EIT_PREVIEW);
y += this->vehicle_space;
DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, r.bottom, GetEngineInfoString(engine), TC_FROMSTRING, SA_CENTER);
DrawStringMultiLine(r.left, r.right, y, r.bottom, GetEngineInfoString(engine), TC_FROMSTRING, SA_CENTER);
}
void OnClick(Point pt, int widget, int click_count) override
{
switch (widget) {
case WID_EP_YES:
DoCommandP(0, this->window_number, 0, CMD_WANT_ENGINE_PREVIEW);
Command<CMD_WANT_ENGINE_PREVIEW>::Post(this->window_number);
FALLTHROUGH;
case WID_EP_NO:
if (!_shift_pressed) this->Close();

View File

@@ -14,10 +14,23 @@
#include "sortlist_type.h"
#include "gfx_type.h"
#include "vehicle_type.h"
#include "engine_base.h"
typedef GUIList<EngineID, CargoID> GUIEngineList;
struct GUIEngineListItem {
EngineID engine_id; ///< Engine to display in build purchase list
EngineID variant_id; ///< Variant group of the engine.
EngineDisplayFlags flags; ///< Flags for toggling/drawing (un)folded status and controlling indentation.
int8 indent; ///< Display indentation level.
typedef bool EngList_SortTypeFunction(const EngineID&, const EngineID&); ///< argument type for #EngList_Sort.
GUIEngineListItem(EngineID engine_id, EngineID variant_id, EngineDisplayFlags flags, int indent) : engine_id(engine_id), variant_id(variant_id), flags(flags), indent(indent) {}
/* Used when searching list only by engine_id. */
bool operator == (const EngineID &other) const { return this->engine_id == other; }
};
typedef GUIList<GUIEngineListItem, CargoID> GUIEngineList;
typedef bool EngList_SortTypeFunction(const GUIEngineListItem&, const GUIEngineListItem&); ///< argument type for #EngList_Sort.
void EngList_Sort(GUIEngineList *el, EngList_SortTypeFunction compare);
void EngList_SortPartial(GUIEngineList *el, EngList_SortTypeFunction compare, uint begin, uint num_items);

View File

@@ -126,6 +126,15 @@ struct RoadVehicleInfo {
RoadType roadtype; ///< Road type
};
enum class ExtraEngineFlags : uint32 {
None = 0,
NoNews = (1U << 0), ///< No 'new vehicle' news will be generated.
NoPreview = (1U << 1), ///< No exclusive preview will be offered.
JoinPreview = (1U << 2), ///< Engine will join exclusive preview with variant parent.
SyncReliability = (1U << 3), ///< Engine reliability will be synced with variant parent.
};
DECLARE_ENUM_AS_BIT_SET(ExtraEngineFlags);
/**
* Information about a vehicle
* @see table/engines.h
@@ -145,6 +154,8 @@ struct EngineInfo {
int8 retire_early; ///< Number of years early to retire vehicle
StringID string_id; ///< Default name of engine
uint16 cargo_age_period; ///< Number of ticks before carried cargo is aged.
EngineID variant_id; ///< Engine variant ID. If set, will be treated specially in purchase lists.
ExtraEngineFlags extra_flags;
};
/**
@@ -155,7 +166,7 @@ enum EngineMiscFlags {
EF_ROAD_TRAM = 0, ///< Road vehicle is a tram/light rail vehicle
EF_USES_2CC = 1, ///< Vehicle uses two company colours
EF_RAIL_IS_MU = 2, ///< Rail vehicle is a multiple-unit (DMU/EMU)
EF_RAIL_FLIPS = 3, ///< Rail vehicle can be flipped in the depot
EF_RAIL_FLIPS = 3, ///< Rail vehicle has old depot-flip handling
EF_AUTO_REFIT = 4, ///< Automatic refitting is allowed
EF_NO_DEFAULT_CARGO_MULTIPLIER = 5, ///< Use the new capacity algorithm. The default cargotype of the vehicle does not affect capacity multipliers. CB 15 is also called in purchase list.
EF_NO_BREAKDOWN_SMOKE = 6, ///< Do not show black smoke during a breakdown.

View File

@@ -35,10 +35,7 @@ static const NWidgetPart _nested_errmsg_widgets[] = {
NWidget(WWT_CAPTION, COLOUR_RED, WID_EM_CAPTION), SetDataTip(STR_ERROR_MESSAGE_CAPTION, STR_NULL),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_RED),
NWidget(WWT_TEXT, COLOUR_RED), SetDataTip(STR_EMPTY, STR_NULL), // Add some borders
NWidget(WWT_EMPTY, COLOUR_RED, WID_EM_MESSAGE), SetPadding(4, 4, 4, 4), SetMinimalSize(236, 32),
NWidget(WWT_TEXT, COLOUR_RED), SetDataTip(STR_EMPTY, STR_NULL),
NWidget(WWT_PUSHTXTBTN, COLOUR_RED, WID_EM_CLOSE), SetPadding(4, 4, 4, 4), SetDataTip(STR_BUTTON_OK, STR_NULL),
NWidget(WWT_EMPTY, COLOUR_RED, WID_EM_MESSAGE), SetPadding(WidgetDimensions::unscaled.modalpopup), SetFill(1, 0), SetMinimalSize(236, 0),
EndContainer(),
};
@@ -55,13 +52,10 @@ static const NWidgetPart _nested_errmsg_face_widgets[] = {
NWidget(WWT_CAPTION, COLOUR_RED, WID_EM_CAPTION), SetDataTip(STR_ERROR_MESSAGE_CAPTION_OTHER_COMPANY, STR_NULL),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_RED),
NWidget(WWT_TEXT, COLOUR_RED), SetDataTip(STR_EMPTY, STR_NULL), // Add some borders
NWidget(NWID_HORIZONTAL), SetPIP(2, 1, 2),
NWidget(WWT_EMPTY, COLOUR_RED, WID_EM_FACE), SetMinimalSize(92, 119), SetFill(0, 1), SetPadding(4, 4, 4, 4),
NWidget(WWT_EMPTY, COLOUR_RED, WID_EM_MESSAGE), SetFill(0, 1), SetMinimalSize(238, 123), SetPadding(4, 4, 4, 4),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_EMPTY, COLOUR_RED, WID_EM_FACE), SetPadding(2, 0, 2, 2), SetFill(0, 1), SetMinimalSize(92, 119),
NWidget(WWT_EMPTY, COLOUR_RED, WID_EM_MESSAGE), SetPadding(WidgetDimensions::unscaled.modalpopup), SetFill(1, 1), SetMinimalSize(236, 0),
EndContainer(),
NWidget(WWT_TEXT, COLOUR_RED), SetDataTip(STR_EMPTY, STR_NULL),
NWidget(WWT_PUSHTXTBTN, COLOUR_RED, WID_EM_CLOSE), SetPadding(4, 4, 4, 4), SetDataTip(STR_BUTTON_OK, STR_NULL),
EndContainer(),
};
@@ -206,14 +200,13 @@ public:
CopyInDParam(0, this->decode_params, lengthof(this->decode_params));
if (this->textref_stack_size > 0) StartTextRefStackUsage(this->textref_stack_grffile, this->textref_stack_size, this->textref_stack);
int text_width = std::max(0, (int)size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT);
this->height_summary = GetStringHeight(this->summary_msg, text_width);
this->height_detailed = (this->detailed_msg == INVALID_STRING_ID) ? 0 : GetStringHeight(this->detailed_msg, text_width);
this->height_summary = GetStringHeight(this->summary_msg, size->width);
this->height_detailed = (this->detailed_msg == INVALID_STRING_ID) ? 0 : GetStringHeight(this->detailed_msg, size->width);
if (this->textref_stack_size > 0) StopTextRefStackUsage();
uint panel_height = WD_FRAMERECT_TOP + this->height_summary + WD_FRAMERECT_BOTTOM;
if (this->detailed_msg != INVALID_STRING_ID) panel_height += this->height_detailed + WD_PAR_VSEP_WIDE;
uint panel_height = this->height_summary;
if (this->detailed_msg != INVALID_STRING_ID) panel_height += this->height_detailed + WidgetDimensions::scaled.vsep_wide;
size->height = std::max(size->height, panel_height);
break;
@@ -252,8 +245,8 @@ public:
pt.y = UnScaleByZoom(pt.y - vp->virtual_top, vp->zoom) + vp->top;
pt.y = (pt.y < (_screen.height >> 1)) ? scr_bot - sm_height : scr_top;
} else {
pt.x = Clamp(UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left - (sm_width / 2), 0, _screen.width - sm_width);
pt.y = Clamp(UnScaleByZoom(pt.y - vp->virtual_top, vp->zoom) + vp->top - (sm_height / 2), scr_top, scr_bot - sm_height);
pt.x = std::min(std::max(UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left - (sm_width / 2), 0), _screen.width - sm_width);
pt.y = std::min(std::max(UnScaleByZoom(pt.y - vp->virtual_top, vp->zoom) + vp->top - (sm_height / 2), scr_top), scr_bot - sm_height);
}
return pt;
}
@@ -288,19 +281,14 @@ public:
if (this->textref_stack_size > 0) StartTextRefStackUsage(this->textref_stack_grffile, this->textref_stack_size, this->textref_stack);
if (this->detailed_msg == INVALID_STRING_ID) {
DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM,
this->summary_msg, TC_FROMSTRING, SA_CENTER);
DrawStringMultiLine(r, this->summary_msg, TC_FROMSTRING, SA_CENTER);
} else {
int extra = (r.bottom - r.top + 1 - this->height_summary - this->height_detailed - WD_PAR_VSEP_WIDE) / 2;
/* Extra space when message is shorter than company face window */
int extra = (r.Height() - this->height_summary - this->height_detailed - WidgetDimensions::scaled.vsep_wide) / 2;
/* Note: NewGRF supplied error message often do not start with a colour code, so default to white. */
int top = r.top + WD_FRAMERECT_TOP;
int bottom = top + this->height_summary + extra;
DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, top, bottom, this->summary_msg, TC_WHITE, SA_CENTER);
bottom = r.bottom - WD_FRAMERECT_BOTTOM;
top = bottom - this->height_detailed - extra;
DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, top, bottom, this->detailed_msg, TC_WHITE, SA_CENTER);
DrawStringMultiLine(r.WithHeight(this->height_summary + extra, false), this->summary_msg, TC_WHITE, SA_CENTER);
DrawStringMultiLine(r.WithHeight(this->height_detailed + extra, true), this->detailed_msg, TC_WHITE, SA_CENTER);
}
if (this->textref_stack_size > 0) StopTextRefStackUsage();
@@ -331,18 +319,6 @@ public:
this->Window::Close();
}
void OnClick(Point pt, int widget, int click_count) override
{
switch (widget) {
case WID_EM_CLOSE:
this->Close();
break;
default:
break;
}
}
/**
* Check whether the currently shown error message was critical or not.
* @return True iff the message was critical.

View File

@@ -418,7 +418,7 @@ uint TarScanner::DoScan(Subdirectory sd)
/* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
{
Debug(misc, 1, "Scanning for tars");
Debug(misc, 2, "Scanning for tars");
TarScanner fs;
uint num = 0;
if (mode & TarScanner::BASESET) {
@@ -439,7 +439,7 @@ uint TarScanner::DoScan(Subdirectory sd)
num += fs.DoScan(SCENARIO_DIR);
num += fs.DoScan(HEIGHTMAP_DIR);
}
Debug(misc, 1, "Scan complete, found {} files", num);
Debug(misc, 2, "Scan complete, found {} files", num);
return num;
}
@@ -571,7 +571,7 @@ bool TarScanner::AddFile(const std::string &filename, size_t basepath_length, co
/* Only allow relative links */
if (link[0] == PATHSEPCHAR) {
Debug(misc, 1, "Ignoring absolute link in tar: {} -> {}", name, link);
Debug(misc, 5, "Ignoring absolute link in tar: {} -> {}", name, link);
break;
}
@@ -597,7 +597,7 @@ bool TarScanner::AddFile(const std::string &filename, size_t basepath_length, co
} else if (strcmp(pos, "..") == 0) {
/* level up */
if (dest[0] == '\0') {
Debug(misc, 1, "Ignoring link pointing outside of data directory: {} -> {}", name, link);
Debug(misc, 5, "Ignoring link pointing outside of data directory: {} -> {}", name, link);
break;
}
@@ -652,7 +652,7 @@ bool TarScanner::AddFile(const std::string &filename, size_t basepath_length, co
pos += skip;
}
Debug(misc, 1, "Found tar '{}' with {} new files", filename, num);
Debug(misc, 4, "Found tar '{}' with {} new files", filename, num);
fclose(f);
/* Resolve file links and store directory links.
@@ -690,7 +690,7 @@ bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
/* The file doesn't have a sub directory! */
if (dirname.empty()) {
Debug(misc, 1, "Extracting {} failed; archive rejected, the contents must be in a sub directory", tar_filename);
Debug(misc, 3, "Extracting {} failed; archive rejected, the contents must be in a sub directory", tar_filename);
return false;
}
@@ -987,7 +987,7 @@ void DeterminePaths(const char *exe, bool only_local_path)
for (Searchpath sp : _valid_searchpaths) {
if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
Debug(misc, 4, "{} added as search path", _searchpaths[sp]);
Debug(misc, 3, "{} added as search path", _searchpaths[sp]);
}
std::string config_dir;
@@ -1020,7 +1020,7 @@ void DeterminePaths(const char *exe, bool only_local_path)
_config_file = config_dir + "openttd.cfg";
}
Debug(misc, 3, "{} found as config directory", config_dir);
Debug(misc, 1, "{} found as config directory", config_dir);
_highscore_file = config_dir + "hs.dat";
extern std::string _hotkeys_file;
@@ -1056,7 +1056,7 @@ void DeterminePaths(const char *exe, bool only_local_path)
FioCreateDirectory(_personal_dir);
#endif
Debug(misc, 3, "{} found as personal directory", _personal_dir);
Debug(misc, 1, "{} found as personal directory", _personal_dir);
static const Subdirectory default_subdirs[] = {
SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
@@ -1068,7 +1068,7 @@ void DeterminePaths(const char *exe, bool only_local_path)
/* If we have network we make a directory for the autodownloading of content */
_searchpaths[SP_AUTODOWNLOAD_DIR] = _personal_dir + "content_download" PATHSEP;
Debug(misc, 4, "{} added as search path", _searchpaths[SP_AUTODOWNLOAD_DIR]);
Debug(misc, 3, "{} added as search path", _searchpaths[SP_AUTODOWNLOAD_DIR]);
FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
FillValidSearchPaths(only_local_path);

View File

@@ -27,6 +27,7 @@
#include "core/geometry_func.hpp"
#include "gamelog.h"
#include "stringfilter_type.h"
#include "misc_cmd.h"
#include "widgets/fios_widget.h"
@@ -82,13 +83,15 @@ static const NWidgetPart _nested_load_dialog_widgets[] = {
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
/* Left side : filter box and available files */
NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 1),
NWidget(NWID_VERTICAL),
/* Filter box with label */
NWidget(NWID_HORIZONTAL), SetPadding(WD_FRAMERECT_TOP, 0, WD_FRAMERECT_BOTTOM, 0),
SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, 0),
NWidget(WWT_TEXT, COLOUR_GREY), SetFill(0, 1), SetDataTip(STR_SAVELOAD_FILTER_TITLE , STR_NULL),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 1),
NWidget(NWID_HORIZONTAL), SetPadding(WidgetDimensions::unscaled.framerect.top, 0, WidgetDimensions::unscaled.framerect.bottom, 0),
SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.frametext.right, 0),
NWidget(WWT_TEXT, COLOUR_GREY), SetFill(0, 1), SetDataTip(STR_SAVELOAD_FILTER_TITLE , STR_NULL),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
EndContainer(),
EndContainer(),
/* Sort buttons */
NWidget(NWID_HORIZONTAL),
@@ -99,22 +102,24 @@ static const NWidgetPart _nested_load_dialog_widgets[] = {
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
EndContainer(),
/* Files */
NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 1, 2, 2),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 2, 2, 2),
SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SL_CONTENT_DOWNLOAD_SEL),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_CONTENT_DOWNLOAD), SetResize(1, 0),
SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
EndContainer(),
/* Online Content button */
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SL_CONTENT_DOWNLOAD_SEL),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_CONTENT_DOWNLOAD), SetResize(1, 0),
SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
EndContainer(),
EndContainer(),
/* Right side : game details */
NWidget(WWT_PANEL, COLOUR_GREY),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SL_DETAILS), SetResize(1, 1), SetFill(1, 1),
NWidget(NWID_VERTICAL),
NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_DETAILS), SetResize(1, 1), SetFill(1, 1),
EndContainer(),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_MISSING_NEWGRFS), SetDataTip(STR_NEWGRF_SETTINGS_FIND_MISSING_CONTENT_BUTTON, STR_NEWGRF_SETTINGS_FIND_MISSING_CONTENT_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
@@ -140,37 +145,38 @@ static const NWidgetPart _nested_load_heightmap_dialog_widgets[] = {
/* Current directory and free space */
NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
/* Filter box with label */
NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 1),
/* Filter box with label */
NWidget(NWID_HORIZONTAL), SetPadding(WD_FRAMERECT_TOP, 0, WD_FRAMERECT_BOTTOM, 0),
SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, 0),
NWidget(NWID_HORIZONTAL), SetPadding(WidgetDimensions::unscaled.framerect.top, 0, WidgetDimensions::unscaled.framerect.bottom, 0),
SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.frametext.right, 0),
NWidget(WWT_TEXT, COLOUR_GREY), SetFill(0, 1), SetDataTip(STR_SAVELOAD_FILTER_TITLE , STR_NULL),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
EndContainer(),
/* Sort Buttons */
NWidget(NWID_HORIZONTAL),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
EndContainer(),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
EndContainer(),
/* Sort Buttons */
NWidget(NWID_HORIZONTAL),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
EndContainer(),
/* Files */
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
EndContainer(),
/* Files */
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 1, 2, 2),
SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
EndContainer(),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_CONTENT_DOWNLOAD), SetResize(1, 0), SetFill(1, 0),
SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_LOAD_BUTTON), SetResize(1, 0), SetFill(1, 0),
SetDataTip(STR_SAVELOAD_LOAD_BUTTON, STR_SAVELOAD_LOAD_HEIGHTMAP_TOOLTIP),
NWidget(WWT_RESIZEBOX, COLOUR_GREY),
EndContainer(),
NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 2, 2, 2),
SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(),
EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
EndContainer(),
/* Online Content and Load button */
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_CONTENT_DOWNLOAD), SetResize(1, 0), SetFill(1, 0),
SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_LOAD_BUTTON), SetResize(1, 0), SetFill(1, 0),
SetDataTip(STR_SAVELOAD_LOAD_BUTTON, STR_SAVELOAD_LOAD_HEIGHTMAP_TOOLTIP),
NWidget(WWT_RESIZEBOX, COLOUR_GREY),
EndContainer(),
};
@@ -185,13 +191,15 @@ static const NWidgetPart _nested_save_dialog_widgets[] = {
NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
/* Left side : filter box and available files */
NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 1),
NWidget(NWID_VERTICAL),
/* Filter box with label */
NWidget(NWID_HORIZONTAL), SetPadding(WD_FRAMERECT_TOP, 0, WD_FRAMERECT_BOTTOM, 0),
SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, 0),
NWidget(WWT_TEXT, COLOUR_GREY), SetFill(0, 1), SetDataTip(STR_SAVELOAD_FILTER_TITLE , STR_NULL),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 1),
NWidget(NWID_HORIZONTAL), SetPadding(WidgetDimensions::unscaled.framerect.top, 0, WidgetDimensions::unscaled.framerect.bottom, 0),
SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.frametext.right, 0),
NWidget(WWT_TEXT, COLOUR_GREY), SetFill(0, 1), SetDataTip(STR_SAVELOAD_FILTER_TITLE , STR_NULL),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
EndContainer(),
EndContainer(),
/* Sort buttons */
NWidget(NWID_HORIZONTAL),
@@ -202,13 +210,15 @@ static const NWidgetPart _nested_save_dialog_widgets[] = {
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
EndContainer(),
/* Files */
NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetPadding(2, 1, 0, 2),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetPadding(2, 2, 2, 2),
SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
EndContainer(),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_SAVE_OSK_TITLE), SetPadding(3, 2, 2, 2), SetFill(1, 0), SetResize(1, 0),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_SAVE_OSK_TITLE), SetPadding(2, 2, 2, 2), SetFill(1, 0), SetResize(1, 0),
SetDataTip(STR_SAVELOAD_OSKTITLE, STR_SAVELOAD_EDITBOX_TOOLTIP),
EndContainer(),
/* Save/delete buttons */
@@ -219,8 +229,8 @@ static const NWidgetPart _nested_save_dialog_widgets[] = {
EndContainer(),
/* Right side : game details */
NWidget(WWT_PANEL, COLOUR_GREY),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SL_DETAILS), SetResize(1, 1), SetFill(1, 1),
NWidget(NWID_VERTICAL),
NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_DETAILS), SetResize(1, 1), SetFill(1, 1), EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SL_SAVE_NETWORK_BUTTON), SetDataTip(STR_SAVELOAD_SAVE_NETWORK_BUTTON, STR_SAVELOAD_SAVE_NETWORK_TOOLTIP), SetFill(1, 1), SetResize(1, 0),
NWidget(WWT_RESIZEBOX, COLOUR_GREY),
@@ -365,7 +375,7 @@ public:
/* pause is only used in single-player, non-editor mode, non-menu mode. It
* will be unpaused in the WE_DESTROY event handler. */
if (_game_mode != GM_MENU && !_networking && _game_mode != GM_EDITOR) {
DoCommandP(0, PM_PAUSED_SAVELOAD, 1, CMD_PAUSE);
Command<CMD_PAUSE>::Post(PM_PAUSED_SAVELOAD, true);
}
SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0);
@@ -409,7 +419,7 @@ public:
{
/* pause is only used in single-player, non-editor mode, non menu mode */
if (!_networking && _game_mode != GM_EDITOR && _game_mode != GM_MENU) {
DoCommandP(0, PM_PAUSED_SAVELOAD, 0, CMD_PAUSE);
Command<CMD_PAUSE>::Post(PM_PAUSED_SAVELOAD, false);
}
this->Window::Close();
}
@@ -434,18 +444,21 @@ public:
_fios_path_changed = false;
}
Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
if (str != STR_ERROR_UNABLE_TO_READ_DRIVE) SetDParam(0, tot);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP, str);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, path, TC_BLACK);
DrawString(ir.left, ir.right, ir.top + FONT_HEIGHT_NORMAL, str);
DrawString(ir.left, ir.right, ir.top, path, TC_BLACK);
break;
}
case WID_SL_DRIVES_DIRECTORIES_LIST: {
GfxFillRect(r.left + 1, r.top + 1, r.right, r.bottom, PC_BLACK);
const Rect br = r.Shrink(WidgetDimensions::scaled.bevel);
GfxFillRect(br, PC_BLACK);
uint y = r.top + WD_FRAMERECT_TOP;
Rect tr = r.Shrink(WidgetDimensions::scaled.inset).WithHeight(this->resize.step_height);
uint scroll_pos = this->vscroll->GetPosition();
for (uint row = 0; row < this->fios_items.size(); row++) {
for (uint row = 0; row < this->fios_items.size() && tr.top < br.bottom; row++) {
if (!this->fios_items_shown[row]) {
/* The current item is filtered out : we do not show it */
scroll_pos++;
@@ -455,108 +468,117 @@ public:
const FiosItem *item = &this->fios_items[row];
if (item == this->selected) {
GfxFillRect(r.left + 1, y, r.right, y + this->resize.step_height, PC_DARK_BLUE);
GfxFillRect(br.left, tr.top, br.right, tr.bottom, PC_DARK_BLUE);
} else if (item == this->highlighted) {
GfxFillRect(r.left + 1, y, r.right, y + this->resize.step_height, PC_VERY_DARK_BLUE);
GfxFillRect(br.left, tr.top, br.right, tr.bottom, PC_VERY_DARK_BLUE);
}
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, Center(y, this->resize.step_height), item->title, _fios_colours[GetDetailedFileType(item->type)]);
y += this->resize.step_height;
if (y >= this->vscroll->GetCapacity() * this->resize.step_height + r.top + WD_FRAMERECT_TOP) break;
DrawString(tr, item->title, _fios_colours[GetDetailedFileType(item->type)]);
tr = tr.Translate(0, this->resize.step_height);
}
break;
}
case WID_SL_DETAILS: {
GfxFillRect(r.left + WD_FRAMERECT_LEFT, r.top + WD_FRAMERECT_TOP,
r.right - WD_FRAMERECT_RIGHT, r.top + FONT_HEIGHT_NORMAL * 2 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM, PC_GREY);
DrawString(r.left, r.right, r.top + FONT_HEIGHT_NORMAL / 2 + WD_FRAMERECT_TOP, STR_SAVELOAD_DETAIL_CAPTION, TC_FROMSTRING, SA_HOR_CENTER);
if (this->selected == nullptr) break;
uint y = r.top + FONT_HEIGHT_NORMAL * 2 + WD_PAR_VSEP_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
uint y_max = r.bottom - FONT_HEIGHT_NORMAL - WD_FRAMERECT_BOTTOM;
if (y > y_max) break;
if (!_load_check_data.checkable) {
/* Old savegame, no information available */
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_DETAIL_NOT_AVAILABLE);
y += FONT_HEIGHT_NORMAL;
} else if (_load_check_data.error != INVALID_STRING_ID) {
/* Incompatible / broken savegame */
SetDParamStr(0, _load_check_data.error_data);
y = DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT,
y, r.bottom - WD_FRAMERECT_BOTTOM, _load_check_data.error, TC_RED);
} else {
/* Mapsize */
SetDParam(0, _load_check_data.map_size_x);
SetDParam(1, _load_check_data.map_size_y);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_MAP_SIZE);
y += FONT_HEIGHT_NORMAL;
if (y > y_max) break;
/* Climate */
byte landscape = _load_check_data.settings.game_creation.landscape;
if (landscape < NUM_LANDSCAPE) {
SetDParam(0, STR_CHEAT_SWITCH_CLIMATE_TEMPERATE_LANDSCAPE + landscape);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_LANDSCAPE);
y += FONT_HEIGHT_NORMAL;
}
y += WD_PAR_VSEP_NORMAL;
if (y > y_max) break;
/* Start date (if available) */
if (_load_check_data.settings.game_creation.starting_year != 0) {
SetDParam(0, ConvertYMDToDate(_load_check_data.settings.game_creation.starting_year, 0, 1));
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_START_DATE);
y += FONT_HEIGHT_NORMAL;
}
if (y > y_max) break;
/* Hide current date for scenarios */
if (this->abstract_filetype != FT_SCENARIO) {
/* Current date */
SetDParam(0, _load_check_data.current_date);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CURRENT_DATE);
y += FONT_HEIGHT_NORMAL;
}
/* Hide the NewGRF stuff when saving. We also hide the button. */
if (this->fop == SLO_LOAD && (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO)) {
y += WD_PAR_VSEP_NORMAL;
if (y > y_max) break;
/* NewGrf compatibility */
SetDParam(0, _load_check_data.grfconfig == nullptr ? STR_NEWGRF_LIST_NONE :
STR_NEWGRF_LIST_ALL_FOUND + _load_check_data.grf_compatibility);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_DETAIL_GRFSTATUS);
y += FONT_HEIGHT_NORMAL;
}
if (y > y_max) break;
/* Hide the company stuff for scenarios */
if (this->abstract_filetype != FT_SCENARIO) {
y += FONT_HEIGHT_NORMAL;
if (y > y_max) break;
/* Companies / AIs */
for (auto &pair : _load_check_data.companies) {
SetDParam(0, pair.first + 1);
const CompanyProperties &c = *pair.second;
if (!c.name.empty()) {
SetDParam(1, STR_JUST_RAW_STRING);
SetDParamStr(2, c.name);
} else {
SetDParam(1, c.name_1);
SetDParam(2, c.name_2);
}
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_DETAIL_COMPANY_INDEX);
y += FONT_HEIGHT_NORMAL;
if (y > y_max) break;
}
}
}
case WID_SL_DETAILS:
this->DrawDetails(r);
break;
}
}
void DrawDetails(const Rect &r) const
{
/* Header panel */
int HEADER_HEIGHT = FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.frametext.Vertical();
Rect hr = r.WithHeight(HEADER_HEIGHT).Shrink(WidgetDimensions::scaled.frametext);
Rect tr = r.Shrink(WidgetDimensions::scaled.frametext);
tr.top += HEADER_HEIGHT;
/* Create the nice grayish rectangle at the details top */
GfxFillRect(r.WithHeight(HEADER_HEIGHT).Shrink(WidgetDimensions::scaled.bevel.left, WidgetDimensions::scaled.bevel.top, WidgetDimensions::scaled.bevel.right, 0), PC_GREY);
DrawString(hr.left, hr.right, hr.top, STR_SAVELOAD_DETAIL_CAPTION, TC_FROMSTRING, SA_HOR_CENTER);
if (this->selected == nullptr) return;
/* Details panel */
tr.bottom -= FONT_HEIGHT_NORMAL - 1;
if (tr.top > tr.bottom) return;
if (!_load_check_data.checkable) {
/* Old savegame, no information available */
DrawString(tr, STR_SAVELOAD_DETAIL_NOT_AVAILABLE);
tr.top += FONT_HEIGHT_NORMAL;
} else if (_load_check_data.error != INVALID_STRING_ID) {
/* Incompatible / broken savegame */
SetDParamStr(0, _load_check_data.error_data);
tr.top = DrawStringMultiLine(tr, _load_check_data.error, TC_RED);
} else {
/* Mapsize */
SetDParam(0, _load_check_data.map_size_x);
SetDParam(1, _load_check_data.map_size_y);
DrawString(tr, STR_NETWORK_SERVER_LIST_MAP_SIZE);
tr.top += FONT_HEIGHT_NORMAL;
if (tr.top > tr.bottom) return;
/* Climate */
byte landscape = _load_check_data.settings.game_creation.landscape;
if (landscape < NUM_LANDSCAPE) {
SetDParam(0, STR_CLIMATE_TEMPERATE_LANDSCAPE + landscape);
DrawString(tr, STR_NETWORK_SERVER_LIST_LANDSCAPE);
tr.top += FONT_HEIGHT_NORMAL;
}
tr.top += WidgetDimensions::scaled.vsep_normal;
if (tr.top > tr.bottom) return;
/* Start date (if available) */
if (_load_check_data.settings.game_creation.starting_year != 0) {
SetDParam(0, ConvertYMDToDate(_load_check_data.settings.game_creation.starting_year, 0, 1));
DrawString(tr, STR_NETWORK_SERVER_LIST_START_DATE);
tr.top += FONT_HEIGHT_NORMAL;
}
if (tr.top > tr.bottom) return;
/* Hide current date for scenarios */
if (this->abstract_filetype != FT_SCENARIO) {
/* Current date */
SetDParam(0, _load_check_data.current_date);
DrawString(tr, STR_NETWORK_SERVER_LIST_CURRENT_DATE);
tr.top += FONT_HEIGHT_NORMAL;
}
/* Hide the NewGRF stuff when saving. We also hide the button. */
if (this->fop == SLO_LOAD && (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO)) {
tr.top += WidgetDimensions::scaled.vsep_normal;
if (tr.top > tr.bottom) return;
/* NewGrf compatibility */
SetDParam(0, _load_check_data.grfconfig == nullptr ? STR_NEWGRF_LIST_NONE :
STR_NEWGRF_LIST_ALL_FOUND + _load_check_data.grf_compatibility);
DrawString(tr, STR_SAVELOAD_DETAIL_GRFSTATUS);
tr.top += FONT_HEIGHT_NORMAL;
}
if (tr.top > tr.bottom) return;
/* Hide the company stuff for scenarios */
if (this->abstract_filetype != FT_SCENARIO) {
tr.top += WidgetDimensions::scaled.vsep_wide;
if (tr.top > tr.bottom) return;
/* Companies / AIs */
for (auto &pair : _load_check_data.companies) {
SetDParam(0, pair.first + 1);
const CompanyProperties &c = *pair.second;
if (!c.name.empty()) {
SetDParam(1, STR_JUST_RAW_STRING);
SetDParamStr(2, c.name);
} else {
SetDParam(1, c.name_1);
SetDParam(2, c.name_2);
}
DrawString(tr, STR_SAVELOAD_DETAIL_COMPANY_INDEX);
tr.top += FONT_HEIGHT_NORMAL;
if (tr.top > tr.bottom) break;
}
}
}
}
@@ -565,12 +587,12 @@ public:
{
switch (widget) {
case WID_SL_BACKGROUND:
size->height = 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
size->height = 2 * FONT_HEIGHT_NORMAL + padding.height;
break;
case WID_SL_DRIVES_DIRECTORIES_LIST:
resize->height = GetMinButtonSize(FONT_HEIGHT_NORMAL);
size->height = resize->height * 5 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
resize->height = FONT_HEIGHT_NORMAL;
size->height = resize->height * 10 + padding.height;
break;
case WID_SL_SORT_BYNAME:
case WID_SL_SORT_BYDATE: {
@@ -650,7 +672,7 @@ public:
break;
case WID_SL_DRIVES_DIRECTORIES_LIST: { // Click the listbox
int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SL_DRIVES_DIRECTORIES_LIST, WD_FRAMERECT_TOP);
int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SL_DRIVES_DIRECTORIES_LIST, WidgetDimensions::scaled.inset.top);
if (y == INT_MAX) return;
/* Get the corresponding non-filtered out item from the list */
@@ -738,13 +760,10 @@ public:
}
}
void OnMouseLoop() override
void OnMouseOver(Point pt, int widget) override
{
const Point pt{ _cursor.pos.x - this->left, _cursor.pos.y - this->top };
const int widget = GetWidgetFromPos(this, pt.x, pt.y);
if (widget == WID_SL_DRIVES_DIRECTORIES_LIST) {
int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SL_DRIVES_DIRECTORIES_LIST, WD_FRAMERECT_TOP);
int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SL_DRIVES_DIRECTORIES_LIST, WidgetDimensions::scaled.inset.top);
if (y == INT_MAX) return;
/* Get the corresponding non-filtered out item from the list */

View File

@@ -9,30 +9,23 @@
#include "stdafx.h"
#include "fontcache.h"
#include "fontcache_internal.h"
#include "fontdetection.h"
#include "blitter/factory.hpp"
#include "core/math_func.hpp"
#include "core/smallmap_type.hpp"
#include "strings_func.h"
#include "zoom_type.h"
#include "gfx_layout.h"
#include "zoom_func.h"
#include "fileio_func.h"
#include "table/sprites.h"
#include "table/control_codes.h"
#include "table/unicode.h"
#include "fontcache/spritefontcache.h"
#include "openttd.h"
#include "settings_func.h"
#include "strings_func.h"
#include "viewport_func.h"
#include "window_func.h"
#include "safeguards.h"
static const int ASCII_LETTERSTART = 32; ///< First printable ASCII letter.
/** Default heights for the different sizes of fonts. */
static const int _default_font_height[FS_END] = {10, 6, 18, 10};
static const int _default_font_ascender[FS_END] = { 8, 5, 15, 8};
FreeTypeSettings _freetype;
FontCacheSettings _fcsettings;
/**
* Create a new font cache.
@@ -72,613 +65,67 @@ int GetCharacterHeight(FontSize size)
}
/** Font cache for fonts that are based on a freetype font. */
class SpriteFontCache : public FontCache {
private:
SpriteID **glyph_to_spriteid_map; ///< Mapping of glyphs to sprite IDs.
void ClearGlyphToSpriteMap();
public:
SpriteFontCache(FontSize fs);
~SpriteFontCache();
virtual SpriteID GetUnicodeGlyph(WChar key);
virtual void SetUnicodeGlyph(WChar key, SpriteID sprite);
virtual void InitializeUnicodeGlyphMap();
virtual void ClearFontCache();
virtual const Sprite *GetGlyph(GlyphID key);
virtual uint GetGlyphWidth(GlyphID key);
virtual bool GetDrawGlyphShadow();
virtual GlyphID MapCharToGlyph(WChar key) { assert(IsPrintable(key)); return SPRITE_GLYPH | key; }
virtual const void *GetFontTable(uint32 tag, size_t &length) { length = 0; return nullptr; }
virtual const char *GetFontName() { return "sprite"; }
virtual bool IsBuiltInFont() { return true; }
};
/**
* Create a new sprite font cache.
* @param fs The font size to create the cache for.
*/
SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs), glyph_to_spriteid_map(nullptr)
{
this->InitializeUnicodeGlyphMap();
this->height = ScaleFontTrad(this->GetDefaultFontHeight(this->fs));
}
/**
* Free everything we allocated.
*/
SpriteFontCache::~SpriteFontCache()
{
this->ClearGlyphToSpriteMap();
}
SpriteID SpriteFontCache::GetUnicodeGlyph(WChar key)
{
if (this->glyph_to_spriteid_map[GB(key, 8, 8)] == nullptr) return 0;
return this->glyph_to_spriteid_map[GB(key, 8, 8)][GB(key, 0, 8)];
}
void SpriteFontCache::SetUnicodeGlyph(WChar key, SpriteID sprite)
{
if (this->glyph_to_spriteid_map == nullptr) this->glyph_to_spriteid_map = CallocT<SpriteID*>(256);
if (this->glyph_to_spriteid_map[GB(key, 8, 8)] == nullptr) this->glyph_to_spriteid_map[GB(key, 8, 8)] = CallocT<SpriteID>(256);
this->glyph_to_spriteid_map[GB(key, 8, 8)][GB(key, 0, 8)] = sprite;
}
void SpriteFontCache::InitializeUnicodeGlyphMap()
{
/* Clear out existing glyph map if it exists */
this->ClearGlyphToSpriteMap();
SpriteID base;
switch (this->fs) {
default: NOT_REACHED();
case FS_MONO: // Use normal as default for mono spaced font
case FS_NORMAL: base = SPR_ASCII_SPACE; break;
case FS_SMALL: base = SPR_ASCII_SPACE_SMALL; break;
case FS_LARGE: base = SPR_ASCII_SPACE_BIG; break;
}
for (uint i = ASCII_LETTERSTART; i < 256; i++) {
SpriteID sprite = base + i - ASCII_LETTERSTART;
if (!SpriteExists(sprite)) continue;
this->SetUnicodeGlyph(i, sprite);
this->SetUnicodeGlyph(i + SCC_SPRITE_START, sprite);
}
for (uint i = 0; i < lengthof(_default_unicode_map); i++) {
byte key = _default_unicode_map[i].key;
if (key == CLRA) {
/* Clear the glyph. This happens if the glyph at this code point
* is non-standard and should be accessed by an SCC_xxx enum
* entry only. */
this->SetUnicodeGlyph(_default_unicode_map[i].code, 0);
} else {
SpriteID sprite = base + key - ASCII_LETTERSTART;
this->SetUnicodeGlyph(_default_unicode_map[i].code, sprite);
}
}
}
/**
* Clear the glyph to sprite mapping.
*/
void SpriteFontCache::ClearGlyphToSpriteMap()
{
if (this->glyph_to_spriteid_map == nullptr) return;
for (uint i = 0; i < 256; i++) {
free(this->glyph_to_spriteid_map[i]);
}
free(this->glyph_to_spriteid_map);
this->glyph_to_spriteid_map = nullptr;
}
void SpriteFontCache::ClearFontCache()
{
Layouter::ResetFontCache(this->fs);
this->height = ScaleFontTrad(this->GetDefaultFontHeight(this->fs));
}
const Sprite *SpriteFontCache::GetGlyph(GlyphID key)
{
SpriteID sprite = this->GetUnicodeGlyph(key);
if (sprite == 0) sprite = this->GetUnicodeGlyph('?');
return GetSprite(sprite, ST_FONT);
}
uint SpriteFontCache::GetGlyphWidth(GlyphID key)
{
SpriteID sprite = this->GetUnicodeGlyph(key);
if (sprite == 0) sprite = this->GetUnicodeGlyph('?');
return SpriteExists(sprite) ? GetSprite(sprite, ST_FONT)->width + ScaleFontTrad(this->fs != FS_NORMAL ? 1 : 0) : 0;
}
bool SpriteFontCache::GetDrawGlyphShadow()
{
return false;
}
/* static */ FontCache *FontCache::caches[FS_END] = { new SpriteFontCache(FS_NORMAL), new SpriteFontCache(FS_SMALL), new SpriteFontCache(FS_LARGE), new SpriteFontCache(FS_MONO) };
/**
* Create a new TrueTypeFontCache.
* @param fs The font size that is going to be cached.
* @param pixels The number of pixels this font should be high.
*/
TrueTypeFontCache::TrueTypeFontCache(FontSize fs, int pixels) : FontCache(fs), req_size(pixels), glyph_to_sprite(nullptr)
{
}
/**
* Free everything that was allocated for this font cache.
*/
TrueTypeFontCache::~TrueTypeFontCache()
{
/* Virtual functions get called statically in destructors, so make it explicit to remove any confusion. */
this->TrueTypeFontCache::ClearFontCache();
for (auto &iter : this->font_tables) {
free(iter.second.second);
}
}
/**
* Reset cached glyphs.
*/
void TrueTypeFontCache::ClearFontCache()
{
if (this->glyph_to_sprite == nullptr) return;
for (int i = 0; i < 256; i++) {
if (this->glyph_to_sprite[i] == nullptr) continue;
for (int j = 0; j < 256; j++) {
if (this->glyph_to_sprite[i][j].duplicate) continue;
free(this->glyph_to_sprite[i][j].sprite);
}
free(this->glyph_to_sprite[i]);
}
free(this->glyph_to_sprite);
this->glyph_to_sprite = nullptr;
Layouter::ResetFontCache(this->fs);
}
TrueTypeFontCache::GlyphEntry *TrueTypeFontCache::GetGlyphPtr(GlyphID key)
{
if (this->glyph_to_sprite == nullptr) return nullptr;
if (this->glyph_to_sprite[GB(key, 8, 8)] == nullptr) return nullptr;
return &this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)];
}
void TrueTypeFontCache::SetGlyphPtr(GlyphID key, const GlyphEntry *glyph, bool duplicate)
{
if (this->glyph_to_sprite == nullptr) {
Debug(freetype, 3, "Allocating root glyph cache for size {}", this->fs);
this->glyph_to_sprite = CallocT<GlyphEntry*>(256);
}
if (this->glyph_to_sprite[GB(key, 8, 8)] == nullptr) {
Debug(freetype, 3, "Allocating glyph cache for range 0x{:02X}00, size {}", GB(key, 8, 8), this->fs);
this->glyph_to_sprite[GB(key, 8, 8)] = CallocT<GlyphEntry>(256);
}
Debug(freetype, 4, "Set glyph for unicode character 0x{:04X}, size {}", key, this->fs);
this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].sprite = glyph->sprite;
this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].width = glyph->width;
this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].duplicate = duplicate;
}
/* Check if a glyph should be rendered with anti-aliasing. */
static bool GetFontAAState(FontSize size, bool check_blitter = true)
bool GetFontAAState(FontSize size, bool check_blitter)
{
/* AA is only supported for 32 bpp */
if (check_blitter && BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 32 && BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 16) return false;
if (check_blitter && BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 32) return false;
switch (size) {
default: NOT_REACHED();
case FS_NORMAL: return _freetype.medium.aa;
case FS_SMALL: return _freetype.small.aa;
case FS_LARGE: return _freetype.large.aa;
case FS_MONO: return _freetype.mono.aa;
}
return GetFontCacheSubSetting(size)->aa;
}
bool TrueTypeFontCache::GetDrawGlyphShadow()
void SetFont(FontSize fontsize, const std::string& font, uint size, bool aa)
{
return this->fs == FS_NORMAL && GetFontAAState(FS_NORMAL);
}
FontCacheSubSetting *setting = GetFontCacheSubSetting(fontsize);
bool changed = false;
uint TrueTypeFontCache::GetGlyphWidth(GlyphID key)
{
if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyphWidth(key);
GlyphEntry *glyph = this->GetGlyphPtr(key);
if (glyph == nullptr || glyph->sprite == nullptr) {
this->GetGlyph(key);
glyph = this->GetGlyphPtr(key);
if (setting->font != font) {
setting->font = font;
changed = true;
}
return glyph->width;
}
if (setting->size != size) {
setting->size = size;
changed = true;
}
const Sprite *TrueTypeFontCache::GetGlyph(GlyphID key)
{
if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyph(key);
if (setting->aa != aa) {
setting->aa = aa;
changed = true;
}
/* Check for the glyph in our cache */
GlyphEntry *glyph = this->GetGlyphPtr(key);
if (glyph != nullptr && glyph->sprite != nullptr) return glyph->sprite;
if (!changed) return;
if (key == 0) {
GlyphID question_glyph = this->MapCharToGlyph('?');
if (question_glyph == 0) {
/* The font misses the '?' character. Use built-in sprite.
* Note: We cannot use the baseset as this also has to work in the bootstrap GUI. */
#define CPSET { 0, 0, 0, 0, 1 }
#define CP___ { 0, 0, 0, 0, 0 }
static SpriteLoader::CommonPixel builtin_questionmark_data[10 * 8] = {
CP___, CP___, CPSET, CPSET, CPSET, CPSET, CP___, CP___,
CP___, CPSET, CPSET, CP___, CP___, CPSET, CPSET, CP___,
CP___, CP___, CP___, CP___, CP___, CPSET, CPSET, CP___,
CP___, CP___, CP___, CP___, CPSET, CPSET, CP___, CP___,
CP___, CP___, CP___, CPSET, CPSET, CP___, CP___, CP___,
CP___, CP___, CP___, CPSET, CPSET, CP___, CP___, CP___,
CP___, CP___, CP___, CPSET, CPSET, CP___, CP___, CP___,
CP___, CP___, CP___, CP___, CP___, CP___, CP___, CP___,
CP___, CP___, CP___, CPSET, CPSET, CP___, CP___, CP___,
CP___, CP___, CP___, CPSET, CPSET, CP___, CP___, CP___,
};
#undef CPSET
#undef CP___
static const SpriteLoader::Sprite builtin_questionmark = {
10, // height
8, // width
0, // x_offs
0, // y_offs
ST_FONT,
SCC_PAL,
builtin_questionmark_data
};
Sprite *spr = BlitterFactory::GetCurrentBlitter()->Encode(&builtin_questionmark, SimpleSpriteAlloc);
assert(spr != nullptr);
GlyphEntry new_glyph;
new_glyph.sprite = spr;
new_glyph.width = spr->width + (this->fs != FS_NORMAL);
this->SetGlyphPtr(key, &new_glyph, false);
return new_glyph.sprite;
} else {
/* Use '?' for missing characters. */
this->GetGlyph(question_glyph);
glyph = this->GetGlyphPtr(question_glyph);
this->SetGlyphPtr(key, glyph, true);
return glyph->sprite;
}
}
return this->InternalGetGlyph(key, GetFontAAState(this->fs));
}
const void *TrueTypeFontCache::GetFontTable(uint32 tag, size_t &length)
{
const FontTable::iterator iter = this->font_tables.Find(tag);
if (iter != this->font_tables.data() + this->font_tables.size()) {
length = iter->second.first;
return iter->second.second;
}
const void *result = this->InternalGetFontTable(tag, length);
this->font_tables.Insert(tag, std::pair<size_t, const void *>(length, result));
return result;
}
#ifdef WITH_FREETYPE
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_TRUETYPE_TABLES_H
/** Font cache for fonts that are based on a freetype font. */
class FreeTypeFontCache : public TrueTypeFontCache {
private:
FT_Face face; ///< The font face associated with this font.
void SetFontSize(FontSize fs, FT_Face face, int pixels);
virtual const void *InternalGetFontTable(uint32 tag, size_t &length);
virtual const Sprite *InternalGetGlyph(GlyphID key, bool aa);
public:
FreeTypeFontCache(FontSize fs, FT_Face face, int pixels);
~FreeTypeFontCache();
virtual void ClearFontCache();
virtual GlyphID MapCharToGlyph(WChar key);
virtual const char *GetFontName() { return face->family_name; }
virtual bool IsBuiltInFont() { return false; }
};
FT_Library _library = nullptr;
/**
* Create a new FreeTypeFontCache.
* @param fs The font size that is going to be cached.
* @param face The font that has to be loaded.
* @param pixels The number of pixels this font should be high.
*/
FreeTypeFontCache::FreeTypeFontCache(FontSize fs, FT_Face face, int pixels) : TrueTypeFontCache(fs, pixels), face(face)
{
assert(face != nullptr);
this->SetFontSize(fs, face, pixels);
}
void FreeTypeFontCache::SetFontSize(FontSize fs, FT_Face face, int pixels)
{
if (pixels == 0) {
/* Try to determine a good height based on the minimal height recommended by the font. */
int scaled_height = ScaleFontTrad(this->GetDefaultFontHeight(this->fs));
pixels = scaled_height;
TT_Header *head = (TT_Header *)FT_Get_Sfnt_Table(this->face, ft_sfnt_head);
if (head != nullptr) {
/* Font height is minimum height plus the difference between the default
* height for this font size and the small size. */
int diff = scaled_height - ScaleFontTrad(this->GetDefaultFontHeight(FS_SMALL));
pixels = Clamp(std::min<uint>(head->Lowest_Rec_PPEM, MAX_FONT_MIN_REC_SIZE) + diff, scaled_height, MAX_FONT_SIZE);
if (fontsize != FS_MONO) {
/* Try to reload only the modified font. */
FontCacheSettings backup = _fcsettings;
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
if (fs == fontsize) continue;
FontCache *fc = FontCache::Get(fs);
GetFontCacheSubSetting(fs)->font = fc->HasParent() ? fc->GetFontName() : "";
}
CheckForMissingGlyphs();
_fcsettings = backup;
} else {
pixels = ScaleFontTrad(pixels);
}
this->used_size = pixels;
FT_Error err = FT_Set_Pixel_Sizes(this->face, 0, pixels);
if (err != FT_Err_Ok) {
/* Find nearest size to that requested */
FT_Bitmap_Size *bs = this->face->available_sizes;
int i = this->face->num_fixed_sizes;
if (i > 0) { // In pathetic cases one might get no fixed sizes at all.
int n = bs->height;
FT_Int chosen = 0;
for (; --i; bs++) {
if (abs(pixels - bs->height) >= abs(pixels - n)) continue;
n = bs->height;
chosen = this->face->num_fixed_sizes - i;
}
/* Don't use FT_Set_Pixel_Sizes here - it might give us another
* error, even though the size is available (FS#5885). */
err = FT_Select_Size(this->face, chosen);
}
InitFontCache(true);
}
if (err == FT_Err_Ok) {
this->units_per_em = this->face->units_per_EM;
this->ascender = this->face->size->metrics.ascender >> 6;
this->descender = this->face->size->metrics.descender >> 6;
this->height = this->ascender - this->descender;
} else {
/* Both FT_Set_Pixel_Sizes and FT_Select_Size failed. */
Debug(freetype, 0, "Font size selection failed. Using FontCache defaults.");
}
LoadStringWidthTable();
UpdateAllVirtCoords();
ReInitAllWindows(true);
if (_save_config) SaveToConfig();
}
/**
* Loads the freetype font.
* First type to load the fontname as if it were a path. If that fails,
* try to resolve the filename of the font using fontconfig, where the
* format is 'font family name' or 'font family name, font style'.
* @param fs The font size to load.
*/
static void LoadFreeTypeFont(FontSize fs)
{
FreeTypeSubSetting *settings = nullptr;
switch (fs) {
default: NOT_REACHED();
case FS_SMALL: settings = &_freetype.small; break;
case FS_NORMAL: settings = &_freetype.medium; break;
case FS_LARGE: settings = &_freetype.large; break;
case FS_MONO: settings = &_freetype.mono; break;
}
if (settings->font.empty()) return;
if (_library == nullptr) {
if (FT_Init_FreeType(&_library) != FT_Err_Ok) {
ShowInfoF("Unable to initialize FreeType, using sprite fonts instead");
return;
}
Debug(freetype, 2, "Initialized");
}
const char *font_name = settings->font.c_str();
FT_Face face = nullptr;
/* If font is an absolute path to a ttf, try loading that first. */
FT_Error error = FT_New_Face(_library, font_name, 0, &face);
#if defined(WITH_COCOA)
extern void MacOSRegisterExternalFont(const char *file_path);
if (error == FT_Err_Ok) MacOSRegisterExternalFont(font_name);
#endif
if (error != FT_Err_Ok) {
/* Check if font is a relative filename in one of our search-paths. */
std::string full_font = FioFindFullPath(BASE_DIR, font_name);
if (!full_font.empty()) {
error = FT_New_Face(_library, full_font.c_str(), 0, &face);
#if defined(WITH_COCOA)
if (error == FT_Err_Ok) MacOSRegisterExternalFont(full_font.c_str());
#endif
}
}
/* Try loading based on font face name (OS-wide fonts). */
if (error != FT_Err_Ok) error = GetFontByFaceName(font_name, &face);
if (error == FT_Err_Ok) {
Debug(freetype, 2, "Requested '{}', using '{} {}'", font_name, face->family_name, face->style_name);
/* Attempt to select the unicode character map */
error = FT_Select_Charmap(face, ft_encoding_unicode);
if (error == FT_Err_Ok) goto found_face; // Success
if (error == FT_Err_Invalid_CharMap_Handle) {
/* Try to pick a different character map instead. We default to
* the first map, but platform_id 0 encoding_id 0 should also
* be unicode (strange system...) */
FT_CharMap found = face->charmaps[0];
int i;
for (i = 0; i < face->num_charmaps; i++) {
FT_CharMap charmap = face->charmaps[i];
if (charmap->platform_id == 0 && charmap->encoding_id == 0) {
found = charmap;
}
}
if (found != nullptr) {
error = FT_Set_Charmap(face, found);
if (error == FT_Err_Ok) goto found_face;
}
}
}
FT_Done_Face(face);
static const char *SIZE_TO_NAME[] = { "medium", "small", "large", "mono" };
ShowInfoF("Unable to use '%s' for %s font, FreeType reported error 0x%X, using sprite font instead", font_name, SIZE_TO_NAME[fs], error);
return;
found_face:
new FreeTypeFontCache(fs, face, RescaleFrom854x480(settings->size));
}
/**
* Free everything that was allocated for this font cache.
*/
FreeTypeFontCache::~FreeTypeFontCache()
{
FT_Done_Face(this->face);
this->face = nullptr;
this->ClearFontCache();
}
/**
* Reset cached glyphs.
*/
void FreeTypeFontCache::ClearFontCache()
{
/* Font scaling might have changed, determine font size anew if it was automatically selected. */
if (this->face != nullptr) this->SetFontSize(this->fs, this->face, this->req_size);
this->TrueTypeFontCache::ClearFontCache();
}
const Sprite *FreeTypeFontCache::InternalGetGlyph(GlyphID key, bool aa)
{
FT_GlyphSlot slot = this->face->glyph;
FT_Load_Glyph(this->face, key, aa ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO);
FT_Render_Glyph(this->face->glyph, aa ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
/* Despite requesting a normal glyph, FreeType may have returned a bitmap */
aa = (slot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY);
/* Add 1 pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel */
uint width = std::max(1U, (uint)slot->bitmap.width + (this->fs == FS_NORMAL));
uint height = std::max(1U, (uint)slot->bitmap.rows + (this->fs == FS_NORMAL));
/* Limit glyph size to prevent overflows later on. */
if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) usererror("Font glyph is too large");
/* FreeType has rendered the glyph, now we allocate a sprite and copy the image into it */
SpriteLoader::Sprite sprite;
sprite.AllocateData(ZOOM_LVL_NORMAL, width * height);
sprite.type = ST_FONT;
sprite.colours = (aa ? SCC_PAL | SCC_ALPHA : SCC_PAL);
sprite.width = width;
sprite.height = height;
sprite.x_offs = slot->bitmap_left;
sprite.y_offs = this->ascender - slot->bitmap_top;
/* Draw shadow for medium size */
if (this->fs == FS_NORMAL && !aa) {
for (uint y = 0; y < (uint)slot->bitmap.rows; y++) {
for (uint x = 0; x < (uint)slot->bitmap.width; x++) {
if (HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) {
sprite.data[1 + x + (1 + y) * sprite.width].m = SHADOW_COLOUR;
sprite.data[1 + x + (1 + y) * sprite.width].a = 0xFF;
}
}
}
}
for (uint y = 0; y < (uint)slot->bitmap.rows; y++) {
for (uint x = 0; x < (uint)slot->bitmap.width; x++) {
if (aa ? (slot->bitmap.buffer[x + y * slot->bitmap.pitch] > 0) : HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) {
sprite.data[x + y * sprite.width].m = FACE_COLOUR;
sprite.data[x + y * sprite.width].a = aa ? slot->bitmap.buffer[x + y * slot->bitmap.pitch] : 0xFF;
}
}
}
GlyphEntry new_glyph;
new_glyph.sprite = BlitterFactory::GetCurrentBlitter()->Encode(&sprite, SimpleSpriteAlloc);
new_glyph.width = slot->advance.x >> 6;
this->SetGlyphPtr(key, &new_glyph);
return new_glyph.sprite;
}
GlyphID FreeTypeFontCache::MapCharToGlyph(WChar key)
{
assert(IsPrintable(key));
if (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) {
return this->parent->MapCharToGlyph(key);
}
return FT_Get_Char_Index(this->face, key);
}
const void *FreeTypeFontCache::InternalGetFontTable(uint32 tag, size_t &length)
{
FT_ULong len = 0;
FT_Byte *result = nullptr;
FT_Load_Sfnt_Table(this->face, tag, 0, nullptr, &len);
if (len > 0) {
result = MallocT<FT_Byte>(len);
FT_Load_Sfnt_Table(this->face, tag, 0, result, &len);
}
length = len;
return result;
}
#endif /* WITH_FREETYPE */
/**
* (Re)initialize the freetype related things, i.e. load the non-sprite fonts.
* (Re)initialize the font cache related things, i.e. load the non-sprite fonts.
* @param monospace Whether to initialise the monospace or regular fonts.
*/
void InitFreeType(bool monospace)
void InitFontCache(bool monospace)
{
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
if (monospace != (fs == FS_MONO)) continue;
@@ -687,6 +134,7 @@ void InitFreeType(bool monospace)
if (fc->HasParent()) delete fc;
#ifdef WITH_FREETYPE
extern void LoadFreeTypeFont(FontSize fs);
LoadFreeTypeFont(fs);
#elif defined(_WIN32)
extern void LoadWin32Font(FontSize fs);
@@ -701,7 +149,7 @@ void InitFreeType(bool monospace)
/**
* Free everything allocated w.r.t. fonts.
*/
void UninitFreeType()
void UninitFontCache()
{
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
FontCache *fc = FontCache::Get(fs);
@@ -709,8 +157,8 @@ void UninitFreeType()
}
#ifdef WITH_FREETYPE
FT_Done_FreeType(_library);
_library = nullptr;
extern void UninitFreeType();
UninitFreeType();
#endif /* WITH_FREETYPE */
}
@@ -729,9 +177,5 @@ bool HasAntialiasedFonts()
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA)
#ifdef WITH_FREETYPE
FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) { return FT_Err_Cannot_Open_Resource; }
#endif /* WITH_FREETYPE */
bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) { return false; }
bool SetFallbackFont(FontCacheSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) { return false; }
#endif /* !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) */

View File

@@ -29,12 +29,12 @@ protected:
int descender; ///< The descender value of the font.
int units_per_em; ///< The units per EM value of the font.
static int GetDefaultFontHeight(FontSize fs);
public:
FontCache(FontSize fs);
virtual ~FontCache();
static int GetDefaultFontHeight(FontSize fs);
/**
* Get the FontSize of the font.
* @return The FontSize.
@@ -71,13 +71,6 @@ public:
*/
virtual int GetFontSize() const { return this->height; }
/**
* Get the SpriteID mapped to the given key
* @param key The key to get the sprite for.
* @return The sprite.
*/
virtual SpriteID GetUnicodeGlyph(WChar key) = 0;
/**
* Map a SpriteID to the key
* @param key The key to map to.
@@ -166,12 +159,6 @@ public:
virtual bool IsBuiltInFont() = 0;
};
/** Get the SpriteID mapped to the given font size and key */
static inline SpriteID GetUnicodeGlyph(FontSize size, WChar key)
{
return FontCache::Get(size)->GetUnicodeGlyph(key);
}
/** Map a SpriteID to the font size and key */
static inline void SetUnicodeGlyph(FontSize size, WChar key, SpriteID sprite)
{
@@ -212,8 +199,8 @@ static inline bool GetDrawGlyphShadow(FontSize size)
return FontCache::Get(size)->GetDrawGlyphShadow();
}
/** Settings for a single freetype font. */
struct FreeTypeSubSetting {
/** Settings for a single font. */
struct FontCacheSubSetting {
std::string font; ///< The name of the font, or path to the font.
uint size; ///< The (requested) size of the font.
bool aa; ///< Whether to do anti aliasing or not.
@@ -221,18 +208,37 @@ struct FreeTypeSubSetting {
const void *os_handle = nullptr; ///< Optional native OS font info. Only valid during font search.
};
/** Settings for the freetype fonts. */
struct FreeTypeSettings {
FreeTypeSubSetting small; ///< The smallest font; mostly used for zoomed out view.
FreeTypeSubSetting medium; ///< The normal font size.
FreeTypeSubSetting large; ///< The largest font; mostly used for newspapers.
FreeTypeSubSetting mono; ///< The mono space font used for license/readme viewers.
/** Settings for the four different fonts. */
struct FontCacheSettings {
FontCacheSubSetting small; ///< The smallest font; mostly used for zoomed out view.
FontCacheSubSetting medium; ///< The normal font size.
FontCacheSubSetting large; ///< The largest font; mostly used for newspapers.
FontCacheSubSetting mono; ///< The mono space font used for license/readme viewers.
};
extern FreeTypeSettings _freetype;
extern FontCacheSettings _fcsettings;
void InitFreeType(bool monospace);
void UninitFreeType();
/**
* Get the settings of a given font size.
* @param fs The font size to look up.
* @return The settings.
*/
static inline FontCacheSubSetting *GetFontCacheSubSetting(FontSize fs)
{
switch (fs) {
default: NOT_REACHED();
case FS_SMALL: return &_fcsettings.small;
case FS_NORMAL: return &_fcsettings.medium;
case FS_LARGE: return &_fcsettings.large;
case FS_MONO: return &_fcsettings.mono;
}
}
void InitFontCache(bool monospace);
void UninitFontCache();
bool HasAntialiasedFonts();
bool GetFontAAState(FontSize size, bool check_blitter = true);
void SetFont(FontSize fontsize, const std::string &font, uint size, bool aa);
#endif /* FONTCACHE_H */

View File

@@ -0,0 +1,11 @@
add_files(
freetypefontcache.cpp
CONDITION Freetype_FOUND
)
add_files(
spritefontcache.cpp
spritefontcache.h
truetypefontcache.cpp
truetypefontcache.h
)

View File

@@ -0,0 +1,324 @@
/*
* 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 freetypefontcache.cpp FreeType font cache implementation. */
#include "../stdafx.h"
#include "../debug.h"
#include "../fontcache.h"
#include "../fontdetection.h"
#include "../blitter/factory.hpp"
#include "../core/math_func.hpp"
#include "../zoom_func.h"
#include "../fileio_func.h"
#include "truetypefontcache.h"
#include "../table/control_codes.h"
#include "../safeguards.h"
#ifdef WITH_FREETYPE
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_TRUETYPE_TABLES_H
/** Font cache for fonts that are based on a freetype font. */
class FreeTypeFontCache : public TrueTypeFontCache {
private:
FT_Face face; ///< The font face associated with this font.
void SetFontSize(FontSize fs, FT_Face face, int pixels);
virtual const void *InternalGetFontTable(uint32 tag, size_t &length);
virtual const Sprite *InternalGetGlyph(GlyphID key, bool aa);
public:
FreeTypeFontCache(FontSize fs, FT_Face face, int pixels);
~FreeTypeFontCache();
virtual void ClearFontCache();
virtual GlyphID MapCharToGlyph(WChar key);
virtual const char *GetFontName() { return face->family_name; }
virtual bool IsBuiltInFont() { return false; }
};
FT_Library _library = nullptr;
/**
* Create a new FreeTypeFontCache.
* @param fs The font size that is going to be cached.
* @param face The font that has to be loaded.
* @param pixels The number of pixels this font should be high.
*/
FreeTypeFontCache::FreeTypeFontCache(FontSize fs, FT_Face face, int pixels) : TrueTypeFontCache(fs, pixels), face(face)
{
assert(face != nullptr);
this->SetFontSize(fs, face, pixels);
}
void FreeTypeFontCache::SetFontSize(FontSize fs, FT_Face face, int pixels)
{
if (pixels == 0) {
/* Try to determine a good height based on the minimal height recommended by the font. */
int scaled_height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs));
pixels = scaled_height;
TT_Header *head = (TT_Header *)FT_Get_Sfnt_Table(this->face, ft_sfnt_head);
if (head != nullptr) {
/* Font height is minimum height plus the difference between the default
* height for this font size and the small size. */
int diff = scaled_height - ScaleGUITrad(FontCache::GetDefaultFontHeight(FS_SMALL));
/* Clamp() is not used as scaled_height could be greater than MAX_FONT_SIZE, which is not permitted in Clamp(). */
pixels = std::min(std::max(std::min<int>(head->Lowest_Rec_PPEM, MAX_FONT_MIN_REC_SIZE) + diff, scaled_height), MAX_FONT_SIZE);
}
} else {
pixels = ScaleGUITrad(pixels);
}
this->used_size = pixels;
FT_Error err = FT_Set_Pixel_Sizes(this->face, 0, pixels);
if (err != FT_Err_Ok) {
/* Find nearest size to that requested */
FT_Bitmap_Size *bs = this->face->available_sizes;
int i = this->face->num_fixed_sizes;
if (i > 0) { // In pathetic cases one might get no fixed sizes at all.
int n = bs->height;
FT_Int chosen = 0;
for (; --i; bs++) {
if (abs(pixels - bs->height) >= abs(pixels - n)) continue;
n = bs->height;
chosen = this->face->num_fixed_sizes - i;
}
/* Don't use FT_Set_Pixel_Sizes here - it might give us another
* error, even though the size is available (FS#5885). */
err = FT_Select_Size(this->face, chosen);
}
}
if (err == FT_Err_Ok) {
this->units_per_em = this->face->units_per_EM;
this->ascender = this->face->size->metrics.ascender >> 6;
this->descender = this->face->size->metrics.descender >> 6;
this->height = this->ascender - this->descender;
} else {
/* Both FT_Set_Pixel_Sizes and FT_Select_Size failed. */
Debug(fontcache, 0, "Font size selection failed. Using FontCache defaults.");
}
}
/**
* Loads the freetype font.
* First type to load the fontname as if it were a path. If that fails,
* try to resolve the filename of the font using fontconfig, where the
* format is 'font family name' or 'font family name, font style'.
* @param fs The font size to load.
*/
void LoadFreeTypeFont(FontSize fs)
{
FontCacheSubSetting *settings = GetFontCacheSubSetting(fs);
if (settings->font.empty()) return;
if (_library == nullptr) {
if (FT_Init_FreeType(&_library) != FT_Err_Ok) {
ShowInfoF("Unable to initialize FreeType, using sprite fonts instead");
return;
}
Debug(fontcache, 2, "Initialized");
}
const char *font_name = settings->font.c_str();
FT_Face face = nullptr;
/* If font is an absolute path to a ttf, try loading that first. */
FT_Error error = FT_New_Face(_library, font_name, 0, &face);
#if defined(WITH_COCOA)
extern void MacOSRegisterExternalFont(const char *file_path);
if (error == FT_Err_Ok) MacOSRegisterExternalFont(font_name);
#endif
if (error != FT_Err_Ok) {
/* Check if font is a relative filename in one of our search-paths. */
std::string full_font = FioFindFullPath(BASE_DIR, font_name);
if (!full_font.empty()) {
error = FT_New_Face(_library, full_font.c_str(), 0, &face);
#if defined(WITH_COCOA)
if (error == FT_Err_Ok) MacOSRegisterExternalFont(full_font.c_str());
#endif
}
}
/* Try loading based on font face name (OS-wide fonts). */
if (error != FT_Err_Ok) error = GetFontByFaceName(font_name, &face);
if (error == FT_Err_Ok) {
Debug(fontcache, 2, "Requested '{}', using '{} {}'", font_name, face->family_name, face->style_name);
/* Attempt to select the unicode character map */
error = FT_Select_Charmap(face, ft_encoding_unicode);
if (error == FT_Err_Ok) goto found_face; // Success
if (error == FT_Err_Invalid_CharMap_Handle) {
/* Try to pick a different character map instead. We default to
* the first map, but platform_id 0 encoding_id 0 should also
* be unicode (strange system...) */
FT_CharMap found = face->charmaps[0];
int i;
for (i = 0; i < face->num_charmaps; i++) {
FT_CharMap charmap = face->charmaps[i];
if (charmap->platform_id == 0 && charmap->encoding_id == 0) {
found = charmap;
}
}
if (found != nullptr) {
error = FT_Set_Charmap(face, found);
if (error == FT_Err_Ok) goto found_face;
}
}
}
FT_Done_Face(face);
ShowInfoF("Unable to use '%s' for %s font, FreeType reported error 0x%X, using sprite font instead", font_name, FontSizeToName(fs), error);
return;
found_face:
new FreeTypeFontCache(fs, face, settings->size);
}
/**
* Free everything that was allocated for this font cache.
*/
FreeTypeFontCache::~FreeTypeFontCache()
{
FT_Done_Face(this->face);
this->face = nullptr;
this->ClearFontCache();
}
/**
* Reset cached glyphs.
*/
void FreeTypeFontCache::ClearFontCache()
{
/* Font scaling might have changed, determine font size anew if it was automatically selected. */
if (this->face != nullptr) this->SetFontSize(this->fs, this->face, this->req_size);
this->TrueTypeFontCache::ClearFontCache();
}
const Sprite *FreeTypeFontCache::InternalGetGlyph(GlyphID key, bool aa)
{
FT_GlyphSlot slot = this->face->glyph;
FT_Load_Glyph(this->face, key, aa ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO);
FT_Render_Glyph(this->face->glyph, aa ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
/* Despite requesting a normal glyph, FreeType may have returned a bitmap */
aa = (slot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY);
/* Add 1 scaled pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel */
uint shadow = (this->fs == FS_NORMAL) ? ScaleGUITrad(1) : 0;
uint width = std::max(1U, (uint)slot->bitmap.width + shadow);
uint height = std::max(1U, (uint)slot->bitmap.rows + shadow);
/* Limit glyph size to prevent overflows later on. */
if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) usererror("Font glyph is too large");
/* FreeType has rendered the glyph, now we allocate a sprite and copy the image into it */
SpriteLoader::Sprite sprite;
sprite.AllocateData(ZOOM_LVL_NORMAL, width * height);
sprite.type = ST_FONT;
sprite.colours = (aa ? SCC_PAL | SCC_ALPHA : SCC_PAL);
sprite.width = width;
sprite.height = height;
sprite.x_offs = slot->bitmap_left;
sprite.y_offs = this->ascender - slot->bitmap_top;
/* Draw shadow for medium size */
if (this->fs == FS_NORMAL && !aa) {
for (uint y = 0; y < (uint)slot->bitmap.rows; y++) {
for (uint x = 0; x < (uint)slot->bitmap.width; x++) {
if (HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) {
sprite.data[shadow + x + (shadow + y) * sprite.width].m = SHADOW_COLOUR;
sprite.data[shadow + x + (shadow + y) * sprite.width].a = 0xFF;
}
}
}
}
for (uint y = 0; y < (uint)slot->bitmap.rows; y++) {
for (uint x = 0; x < (uint)slot->bitmap.width; x++) {
if (aa ? (slot->bitmap.buffer[x + y * slot->bitmap.pitch] > 0) : HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) {
sprite.data[x + y * sprite.width].m = FACE_COLOUR;
sprite.data[x + y * sprite.width].a = aa ? slot->bitmap.buffer[x + y * slot->bitmap.pitch] : 0xFF;
}
}
}
GlyphEntry new_glyph;
new_glyph.sprite = BlitterFactory::GetCurrentBlitter()->Encode(&sprite, SimpleSpriteAlloc);
new_glyph.width = slot->advance.x >> 6;
this->SetGlyphPtr(key, &new_glyph);
return new_glyph.sprite;
}
GlyphID FreeTypeFontCache::MapCharToGlyph(WChar key)
{
assert(IsPrintable(key));
if (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) {
return this->parent->MapCharToGlyph(key);
}
return FT_Get_Char_Index(this->face, key);
}
const void *FreeTypeFontCache::InternalGetFontTable(uint32 tag, size_t &length)
{
FT_ULong len = 0;
FT_Byte *result = nullptr;
FT_Load_Sfnt_Table(this->face, tag, 0, nullptr, &len);
if (len > 0) {
result = MallocT<FT_Byte>(len);
FT_Load_Sfnt_Table(this->face, tag, 0, result, &len);
}
length = len;
return result;
}
/**
* Free everything allocated w.r.t. freetype.
*/
void UninitFreeType()
{
FT_Done_FreeType(_library);
_library = nullptr;
}
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA)
FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) { return FT_Err_Cannot_Open_Resource; }
#endif /* !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) */
#endif /* WITH_FREETYPE */

View File

@@ -0,0 +1,129 @@
/*
* 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 spritefontcache.cpp Sprite fontcache implementation. */
#include "../stdafx.h"
#include "../fontcache.h"
#include "../gfx_layout.h"
#include "../zoom_func.h"
#include "spritefontcache.h"
#include "../table/sprites.h"
#include "../table/control_codes.h"
#include "../table/unicode.h"
#include "../safeguards.h"
static const int ASCII_LETTERSTART = 32; ///< First printable ASCII letter.
/**
* Create a new sprite font cache.
* @param fs The font size to create the cache for.
*/
SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs), glyph_to_spriteid_map(nullptr)
{
this->InitializeUnicodeGlyphMap();
this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs));
this->ascender = (this->height - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2;
}
/**
* Free everything we allocated.
*/
SpriteFontCache::~SpriteFontCache()
{
this->ClearGlyphToSpriteMap();
}
SpriteID SpriteFontCache::GetUnicodeGlyph(WChar key)
{
if (this->glyph_to_spriteid_map[GB(key, 8, 8)] == nullptr) return 0;
return this->glyph_to_spriteid_map[GB(key, 8, 8)][GB(key, 0, 8)];
}
void SpriteFontCache::SetUnicodeGlyph(WChar key, SpriteID sprite)
{
if (this->glyph_to_spriteid_map == nullptr) this->glyph_to_spriteid_map = CallocT<SpriteID*>(256);
if (this->glyph_to_spriteid_map[GB(key, 8, 8)] == nullptr) this->glyph_to_spriteid_map[GB(key, 8, 8)] = CallocT<SpriteID>(256);
this->glyph_to_spriteid_map[GB(key, 8, 8)][GB(key, 0, 8)] = sprite;
}
void SpriteFontCache::InitializeUnicodeGlyphMap()
{
/* Clear out existing glyph map if it exists */
this->ClearGlyphToSpriteMap();
SpriteID base;
switch (this->fs) {
default: NOT_REACHED();
case FS_MONO: // Use normal as default for mono spaced font
case FS_NORMAL: base = SPR_ASCII_SPACE; break;
case FS_SMALL: base = SPR_ASCII_SPACE_SMALL; break;
case FS_LARGE: base = SPR_ASCII_SPACE_BIG; break;
}
for (uint i = ASCII_LETTERSTART; i < 256; i++) {
SpriteID sprite = base + i - ASCII_LETTERSTART;
if (!SpriteExists(sprite)) continue;
this->SetUnicodeGlyph(i, sprite);
this->SetUnicodeGlyph(i + SCC_SPRITE_START, sprite);
}
for (uint i = 0; i < lengthof(_default_unicode_map); i++) {
byte key = _default_unicode_map[i].key;
if (key == CLRA) {
/* Clear the glyph. This happens if the glyph at this code point
* is non-standard and should be accessed by an SCC_xxx enum
* entry only. */
this->SetUnicodeGlyph(_default_unicode_map[i].code, 0);
} else {
SpriteID sprite = base + key - ASCII_LETTERSTART;
this->SetUnicodeGlyph(_default_unicode_map[i].code, sprite);
}
}
}
/**
* Clear the glyph to sprite mapping.
*/
void SpriteFontCache::ClearGlyphToSpriteMap()
{
if (this->glyph_to_spriteid_map == nullptr) return;
for (uint i = 0; i < 256; i++) {
free(this->glyph_to_spriteid_map[i]);
}
free(this->glyph_to_spriteid_map);
this->glyph_to_spriteid_map = nullptr;
}
void SpriteFontCache::ClearFontCache()
{
Layouter::ResetFontCache(this->fs);
this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs));
this->ascender = (this->height - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2;
}
const Sprite *SpriteFontCache::GetGlyph(GlyphID key)
{
SpriteID sprite = this->GetUnicodeGlyph(key);
if (sprite == 0) sprite = this->GetUnicodeGlyph('?');
return GetSprite(sprite, ST_FONT);
}
uint SpriteFontCache::GetGlyphWidth(GlyphID key)
{
SpriteID sprite = this->GetUnicodeGlyph(key);
if (sprite == 0) sprite = this->GetUnicodeGlyph('?');
return SpriteExists(sprite) ? GetSprite(sprite, ST_FONT)->width + ScaleSpriteTrad(this->fs != FS_NORMAL ? 1 : 0) : 0;
}
bool SpriteFontCache::GetDrawGlyphShadow()
{
return false;
}

View File

@@ -0,0 +1,38 @@
/*
* 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 spritefontcache.h Sprite font cache implementation definition. */
#ifndef SPRITEFONTCACHE_H
#define SPRITEFONTCACHE_H
#include "../string_func.h"
#include "../fontcache.h"
/** Font cache for fonts that are based on a freetype font. */
class SpriteFontCache : public FontCache {
private:
SpriteID **glyph_to_spriteid_map; ///< Mapping of glyphs to sprite IDs.
SpriteID GetUnicodeGlyph(WChar key);
void ClearGlyphToSpriteMap();
public:
SpriteFontCache(FontSize fs);
~SpriteFontCache();
virtual void SetUnicodeGlyph(WChar key, SpriteID sprite);
virtual void InitializeUnicodeGlyphMap();
virtual void ClearFontCache();
virtual const Sprite *GetGlyph(GlyphID key);
virtual uint GetGlyphWidth(GlyphID key);
virtual bool GetDrawGlyphShadow();
virtual GlyphID MapCharToGlyph(WChar key) { assert(IsPrintable(key)); return SPRITE_GLYPH | key; }
virtual const void *GetFontTable(uint32 tag, size_t &length) { length = 0; return nullptr; }
virtual const char *GetFontName() { return "sprite"; }
virtual bool IsBuiltInFont() { return true; }
};
#endif /* SPRITEFONTCACHE_H */

View File

@@ -0,0 +1,180 @@
/*
* 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 truetypefontcache.cpp Common base implementation for font file based font caches. */
#include "../stdafx.h"
#include "../debug.h"
#include "../fontcache.h"
#include "../blitter/factory.hpp"
#include "../core/bitmath_func.hpp"
#include "../gfx_layout.h"
#include "truetypefontcache.h"
#include "../safeguards.h"
/**
* Create a new TrueTypeFontCache.
* @param fs The font size that is going to be cached.
* @param pixels The number of pixels this font should be high.
*/
TrueTypeFontCache::TrueTypeFontCache(FontSize fs, int pixels) : FontCache(fs), req_size(pixels), glyph_to_sprite(nullptr)
{
}
/**
* Free everything that was allocated for this font cache.
*/
TrueTypeFontCache::~TrueTypeFontCache()
{
/* Virtual functions get called statically in destructors, so make it explicit to remove any confusion. */
this->TrueTypeFontCache::ClearFontCache();
for (auto &iter : this->font_tables) {
free(iter.second.second);
}
}
/**
* Reset cached glyphs.
*/
void TrueTypeFontCache::ClearFontCache()
{
if (this->glyph_to_sprite == nullptr) return;
for (int i = 0; i < 256; i++) {
if (this->glyph_to_sprite[i] == nullptr) continue;
for (int j = 0; j < 256; j++) {
if (this->glyph_to_sprite[i][j].duplicate) continue;
free(this->glyph_to_sprite[i][j].sprite);
}
free(this->glyph_to_sprite[i]);
}
free(this->glyph_to_sprite);
this->glyph_to_sprite = nullptr;
Layouter::ResetFontCache(this->fs);
}
TrueTypeFontCache::GlyphEntry *TrueTypeFontCache::GetGlyphPtr(GlyphID key)
{
if (this->glyph_to_sprite == nullptr) return nullptr;
if (this->glyph_to_sprite[GB(key, 8, 8)] == nullptr) return nullptr;
return &this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)];
}
void TrueTypeFontCache::SetGlyphPtr(GlyphID key, const GlyphEntry *glyph, bool duplicate)
{
if (this->glyph_to_sprite == nullptr) {
Debug(fontcache, 3, "Allocating root glyph cache for size {}", this->fs);
this->glyph_to_sprite = CallocT<GlyphEntry*>(256);
}
if (this->glyph_to_sprite[GB(key, 8, 8)] == nullptr) {
Debug(fontcache, 3, "Allocating glyph cache for range 0x{:02X}00, size {}", GB(key, 8, 8), this->fs);
this->glyph_to_sprite[GB(key, 8, 8)] = CallocT<GlyphEntry>(256);
}
Debug(fontcache, 4, "Set glyph for unicode character 0x{:04X}, size {}", key, this->fs);
this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].sprite = glyph->sprite;
this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].width = glyph->width;
this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].duplicate = duplicate;
}
bool TrueTypeFontCache::GetDrawGlyphShadow()
{
return this->fs == FS_NORMAL && GetFontAAState(FS_NORMAL);
}
uint TrueTypeFontCache::GetGlyphWidth(GlyphID key)
{
if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyphWidth(key);
GlyphEntry *glyph = this->GetGlyphPtr(key);
if (glyph == nullptr || glyph->sprite == nullptr) {
this->GetGlyph(key);
glyph = this->GetGlyphPtr(key);
}
return glyph->width;
}
const Sprite *TrueTypeFontCache::GetGlyph(GlyphID key)
{
if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyph(key);
/* Check for the glyph in our cache */
GlyphEntry *glyph = this->GetGlyphPtr(key);
if (glyph != nullptr && glyph->sprite != nullptr) return glyph->sprite;
if (key == 0) {
GlyphID question_glyph = this->MapCharToGlyph('?');
if (question_glyph == 0) {
/* The font misses the '?' character. Use built-in sprite.
* Note: We cannot use the baseset as this also has to work in the bootstrap GUI. */
#define CPSET { 0, 0, 0, 0, 1 }
#define CP___ { 0, 0, 0, 0, 0 }
static SpriteLoader::CommonPixel builtin_questionmark_data[10 * 8] = {
CP___, CP___, CPSET, CPSET, CPSET, CPSET, CP___, CP___,
CP___, CPSET, CPSET, CP___, CP___, CPSET, CPSET, CP___,
CP___, CP___, CP___, CP___, CP___, CPSET, CPSET, CP___,
CP___, CP___, CP___, CP___, CPSET, CPSET, CP___, CP___,
CP___, CP___, CP___, CPSET, CPSET, CP___, CP___, CP___,
CP___, CP___, CP___, CPSET, CPSET, CP___, CP___, CP___,
CP___, CP___, CP___, CPSET, CPSET, CP___, CP___, CP___,
CP___, CP___, CP___, CP___, CP___, CP___, CP___, CP___,
CP___, CP___, CP___, CPSET, CPSET, CP___, CP___, CP___,
CP___, CP___, CP___, CPSET, CPSET, CP___, CP___, CP___,
};
#undef CPSET
#undef CP___
static const SpriteLoader::Sprite builtin_questionmark = {
10, // height
8, // width
0, // x_offs
0, // y_offs
ST_FONT,
SCC_PAL,
builtin_questionmark_data
};
Sprite *spr = BlitterFactory::GetCurrentBlitter()->Encode(&builtin_questionmark, SimpleSpriteAlloc);
assert(spr != nullptr);
GlyphEntry new_glyph;
new_glyph.sprite = spr;
new_glyph.width = spr->width + (this->fs != FS_NORMAL);
this->SetGlyphPtr(key, &new_glyph, false);
return new_glyph.sprite;
} else {
/* Use '?' for missing characters. */
this->GetGlyph(question_glyph);
glyph = this->GetGlyphPtr(question_glyph);
this->SetGlyphPtr(key, glyph, true);
return glyph->sprite;
}
}
return this->InternalGetGlyph(key, GetFontAAState(this->fs));
}
const void *TrueTypeFontCache::GetFontTable(uint32 tag, size_t &length)
{
const FontTable::iterator iter = this->font_tables.Find(tag);
if (iter != this->font_tables.data() + this->font_tables.size()) {
length = iter->second.first;
return iter->second.second;
}
const void *result = this->InternalGetFontTable(tag, length);
this->font_tables.Insert(tag, std::pair<size_t, const void *>(length, result));
return result;
}

Some files were not shown because too many files have changed in this diff Show More