Merge commit '13.0-RC1~2'
This commit is contained in:
2
src/3rdparty/squirrel/squirrel/sqstate.cpp
vendored
2
src/3rdparty/squirrel/squirrel/sqstate.cpp
vendored
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
private:
|
||||
void RegisterAPI() override;
|
||||
void Died() override;
|
||||
CommandCallback *GetDoCommandCallback() override;
|
||||
CommandCallbackData *GetDoCommandCallback() override;
|
||||
void LoadDummyScript() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
19
src/aircraft_cmd.h
Normal 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 */
|
||||
@@ -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
17
src/airport_cmd.h
Normal 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 */
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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
24
src/autoreplace_cmd.h
Normal 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 */
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 */
|
||||
751
src/command.cpp
751
src/command.cpp
@@ -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
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
34
src/company_cmd.h
Normal 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 */
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -123,6 +123,7 @@ void IConsolePrint(TextColour colour_code, const std::string &string)
|
||||
|
||||
IConsoleWriteToLogFile(str);
|
||||
IConsoleGUIPrint(colour_code, str);
|
||||
free(str);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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);}
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,3 +48,5 @@ Rect BoundingRect(const Rect &r1, const Rect &r2)
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
const RectPadding RectPadding::zero = {0, 0, 0, 0};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
73
src/core/strong_typedef_type.hpp
Normal file
73
src/core/strong_typedef_type.hpp
Normal 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 */
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
22
src/depot_cmd.h
Normal 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 */
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
18
src/dock_cmd.h
Normal 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 */
|
||||
127
src/dock_gui.cpp
127
src/dock_gui.cpp
@@ -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(),
|
||||
};
|
||||
|
||||
|
||||
160
src/economy.cpp
160
src/economy.cpp
@@ -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
24
src/economy_cmd.h
Normal 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 */
|
||||
@@ -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.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
139
src/engine.cpp
139
src/engine.cpp
@@ -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();
|
||||
|
||||
@@ -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
25
src/engine_cmd.h
Normal 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 */
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
345
src/fios_gui.cpp
345
src/fios_gui.cpp
@@ -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 */
|
||||
|
||||
@@ -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) */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
11
src/fontcache/CMakeLists.txt
Normal file
11
src/fontcache/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
add_files(
|
||||
freetypefontcache.cpp
|
||||
CONDITION Freetype_FOUND
|
||||
)
|
||||
|
||||
add_files(
|
||||
spritefontcache.cpp
|
||||
spritefontcache.h
|
||||
truetypefontcache.cpp
|
||||
truetypefontcache.h
|
||||
)
|
||||
324
src/fontcache/freetypefontcache.cpp
Normal file
324
src/fontcache/freetypefontcache.cpp
Normal 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 */
|
||||
129
src/fontcache/spritefontcache.cpp
Normal file
129
src/fontcache/spritefontcache.cpp
Normal 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;
|
||||
}
|
||||
38
src/fontcache/spritefontcache.h
Normal file
38
src/fontcache/spritefontcache.h
Normal 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 */
|
||||
180
src/fontcache/truetypefontcache.cpp
Normal file
180
src/fontcache/truetypefontcache.cpp
Normal 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
Reference in New Issue
Block a user