mirror of https://github.com/OpenTTD/OpenTTD
Compare commits
9 Commits
49c022a8f5
...
ea14d69aae
Author | SHA1 | Date |
---|---|---|
|
ea14d69aae | |
|
2cdd50f40e | |
|
56942a15c7 | |
|
5eeda026a4 | |
|
edc5b8ea1f | |
|
e6b45731c0 | |
|
24f173e7a8 | |
|
ccfb40944b | |
|
5b0e9f82d5 |
|
@ -449,6 +449,7 @@ if(WIN32)
|
||||||
psapi
|
psapi
|
||||||
winhttp
|
winhttp
|
||||||
bcrypt
|
bcrypt
|
||||||
|
xinput
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -1067,6 +1067,7 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoType cargo_type, uint
|
||||||
|
|
||||||
uint amount = std::min(num_pieces, 0xFFFFu - it->waiting);
|
uint amount = std::min(num_pieces, 0xFFFFu - it->waiting);
|
||||||
it->waiting += amount;
|
it->waiting += amount;
|
||||||
|
it->GetOrCreateHistory()[THIS_MONTH].accepted += amount;
|
||||||
it->last_accepted = TimerGameEconomy::date;
|
it->last_accepted = TimerGameEconomy::date;
|
||||||
num_pieces -= amount;
|
num_pieces -= amount;
|
||||||
accepted += amount;
|
accepted += amount;
|
||||||
|
|
16
src/gfx.cpp
16
src/gfx.cpp
|
@ -580,10 +580,11 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
|
||||||
|
|
||||||
const uint shadow_offset = ScaleGUITrad(1);
|
const uint shadow_offset = ScaleGUITrad(1);
|
||||||
|
|
||||||
auto draw_line = [&](const ParagraphLayouter::Line &line, bool do_shadow, int left, int min_x, int max_x, bool truncation, TextColour &last_colour) {
|
auto draw_line = [&](const ParagraphLayouter::Line &line, bool do_shadow, int left, int min_x, int max_x, bool truncation, TextColour initial_colour) {
|
||||||
const DrawPixelInfo *dpi = _cur_dpi;
|
const DrawPixelInfo *dpi = _cur_dpi;
|
||||||
int dpi_left = dpi->left;
|
int dpi_left = dpi->left;
|
||||||
int dpi_right = dpi->left + dpi->width - 1;
|
int dpi_right = dpi->left + dpi->width - 1;
|
||||||
|
TextColour last_colour = initial_colour;
|
||||||
|
|
||||||
for (int run_index = 0; run_index < line.CountRuns(); run_index++) {
|
for (int run_index = 0; run_index < line.CountRuns(); run_index++) {
|
||||||
const ParagraphLayouter::VisualRun &run = line.GetVisualRun(run_index);
|
const ParagraphLayouter::VisualRun &run = line.GetVisualRun(run_index);
|
||||||
|
@ -593,13 +594,10 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
|
||||||
|
|
||||||
FontCache *fc = f->fc;
|
FontCache *fc = f->fc;
|
||||||
TextColour colour = f->colour;
|
TextColour colour = f->colour;
|
||||||
if (colour == TC_INVALID || HasFlag(last_colour, TC_FORCED)) {
|
if (colour == TC_INVALID || HasFlag(initial_colour, TC_FORCED)) colour = initial_colour;
|
||||||
colour = last_colour;
|
bool colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
|
||||||
} else {
|
|
||||||
/* Update the last colour for the truncation ellipsis. */
|
/* Update the last colour for the truncation ellipsis. */
|
||||||
last_colour = colour;
|
last_colour = colour;
|
||||||
}
|
|
||||||
bool colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
|
|
||||||
if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue;
|
if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue;
|
||||||
SetColourRemap(do_shadow ? TC_BLACK : colour);
|
SetColourRemap(do_shadow ? TC_BLACK : colour);
|
||||||
|
|
||||||
|
@ -625,16 +623,16 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
|
||||||
GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap);
|
GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return last_colour;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Draw shadow, then foreground */
|
/* Draw shadow, then foreground */
|
||||||
for (bool do_shadow : {true, false}) {
|
for (bool do_shadow : {true, false}) {
|
||||||
TextColour last_colour = default_colour;
|
TextColour colour = draw_line(line, do_shadow, left - offset_x, min_x, max_x, truncation, default_colour);
|
||||||
draw_line(line, do_shadow, left - offset_x, min_x, max_x, truncation, last_colour);
|
|
||||||
|
|
||||||
if (truncation) {
|
if (truncation) {
|
||||||
int x = (_current_text_dir == TD_RTL) ? left : (right - truncation_width);
|
int x = (_current_text_dir == TD_RTL) ? left : (right - truncation_width);
|
||||||
draw_line(*truncation_layout->front(), do_shadow, x, INT32_MIN, INT32_MAX, false, last_colour);
|
draw_line(*truncation_layout->front(), do_shadow, x, INT32_MIN, INT32_MAX, false, colour);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,7 @@ void HandleTextInput(std::string_view str, bool marked = false,
|
||||||
std::optional<size_t> insert_location = std::nullopt, std::optional<size_t> replacement_end = std::nullopt);
|
std::optional<size_t> insert_location = std::nullopt, std::optional<size_t> replacement_end = std::nullopt);
|
||||||
void HandleCtrlChanged();
|
void HandleCtrlChanged();
|
||||||
void HandleMouseEvents();
|
void HandleMouseEvents();
|
||||||
|
void HandleGamepadScrolling(int stick_x, int stick_y, int max_axis_value);
|
||||||
void UpdateWindows();
|
void UpdateWindows();
|
||||||
void ChangeGameSpeed(bool enable_fast_forward);
|
void ChangeGameSpeed(bool enable_fast_forward);
|
||||||
|
|
||||||
|
|
|
@ -182,8 +182,9 @@ protected:
|
||||||
static const int MIN_GRAPH_NUM_LINES_Y = 9; ///< Minimal number of horizontal lines to draw.
|
static const int MIN_GRAPH_NUM_LINES_Y = 9; ///< Minimal number of horizontal lines to draw.
|
||||||
static const int MIN_GRID_PIXEL_SIZE = 20; ///< Minimum distance between graph lines.
|
static const int MIN_GRID_PIXEL_SIZE = 20; ///< Minimum distance between graph lines.
|
||||||
|
|
||||||
uint64_t excluded_data = 0; ///< bitmask of the datasets that shouldn't be displayed.
|
uint64_t excluded_data = 0; ///< bitmask of datasets hidden by the player.
|
||||||
uint64_t excluded_range = 0; ///< bitmask of ranges that should not be displayed.
|
uint64_t excluded_range = 0; ///< bitmask of ranges hidden by the player.
|
||||||
|
uint64_t masked_range = 0; ///< bitmask of ranges that are not available for the current data.
|
||||||
uint8_t num_on_x_axis = 0;
|
uint8_t num_on_x_axis = 0;
|
||||||
uint8_t num_vert_lines = GRAPH_NUM_MONTHS;
|
uint8_t num_vert_lines = GRAPH_NUM_MONTHS;
|
||||||
|
|
||||||
|
@ -216,13 +217,20 @@ protected:
|
||||||
uint8_t highlight_range = UINT8_MAX; ///< Data range that should be highlighted, or UINT8_MAX for none.
|
uint8_t highlight_range = UINT8_MAX; ///< Data range that should be highlighted, or UINT8_MAX for none.
|
||||||
bool highlight_state = false; ///< Current state of highlight, toggled every TIMER_BLINK_INTERVAL period.
|
bool highlight_state = false; ///< Current state of highlight, toggled every TIMER_BLINK_INTERVAL period.
|
||||||
|
|
||||||
template <typename Tprojection>
|
struct BaseFiller {
|
||||||
struct Filler {
|
|
||||||
DataSet &dataset; ///< Dataset to fill.
|
DataSet &dataset; ///< Dataset to fill.
|
||||||
|
|
||||||
|
inline void MakeZero(uint i) const { this->dataset.values[i] = 0; }
|
||||||
|
inline void MakeInvalid(uint i) const { this->dataset.values[i] = INVALID_DATAPOINT; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Tprojection>
|
||||||
|
struct Filler : BaseFiller {
|
||||||
const Tprojection &proj; ///< Projection to apply.
|
const Tprojection &proj; ///< Projection to apply.
|
||||||
|
|
||||||
|
constexpr Filler(DataSet &dataset, const Tprojection &proj) : BaseFiller(dataset), proj(proj) {}
|
||||||
|
|
||||||
inline void Fill(uint i, const auto &data) const { this->dataset.values[i] = std::invoke(this->proj, data); }
|
inline void Fill(uint i, const auto &data) const { this->dataset.values[i] = std::invoke(this->proj, data); }
|
||||||
inline void MakeInvalid(uint i) const { this->dataset.values[i] = INVALID_DATAPOINT; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -675,13 +683,17 @@ public:
|
||||||
uint index = 0;
|
uint index = 0;
|
||||||
Rect line = r.WithHeight(line_height);
|
Rect line = r.WithHeight(line_height);
|
||||||
for (const auto &str : this->ranges) {
|
for (const auto &str : this->ranges) {
|
||||||
bool lowered = !HasBit(this->excluded_range, index);
|
bool lowered = !HasBit(this->excluded_range, index) && !HasBit(this->masked_range, index);
|
||||||
|
|
||||||
/* Redraw frame if lowered */
|
/* Redraw frame if lowered */
|
||||||
if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
|
if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
|
||||||
|
|
||||||
const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
|
const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
|
||||||
DrawString(text, str, TC_BLACK, SA_CENTER, false, FS_SMALL);
|
DrawString(text, str, (this->highlight_state && this->highlight_range == index) ? TC_WHITE : TC_BLACK, SA_CENTER, false, FS_SMALL);
|
||||||
|
|
||||||
|
if (HasBit(this->masked_range, index)) {
|
||||||
|
GfxFillRect(line.Shrink(WidgetDimensions::scaled.bevel), GetColourGradient(COLOUR_BROWN, SHADE_DARKER), FILLRECT_CHECKER);
|
||||||
|
}
|
||||||
|
|
||||||
line = line.Translate(0, line_height);
|
line = line.Translate(0, line_height);
|
||||||
++index;
|
++index;
|
||||||
|
@ -704,6 +716,7 @@ public:
|
||||||
case WID_GRAPH_RANGE_MATRIX: {
|
case WID_GRAPH_RANGE_MATRIX: {
|
||||||
int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
|
int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
|
||||||
|
|
||||||
|
if (HasBit(this->masked_range, row)) break;
|
||||||
ToggleBit(this->excluded_range, row);
|
ToggleBit(this->excluded_range, row);
|
||||||
this->SetDirty();
|
this->SetDirty();
|
||||||
break;
|
break;
|
||||||
|
@ -1115,6 +1128,7 @@ struct BaseCargoGraphWindow : BaseGraphWindow {
|
||||||
{
|
{
|
||||||
this->CreateNestedTree();
|
this->CreateNestedTree();
|
||||||
|
|
||||||
|
this->excluded_range = this->masked_range;
|
||||||
this->cargo_types = this->GetCargoTypes(number);
|
this->cargo_types = this->GetCargoTypes(number);
|
||||||
|
|
||||||
this->vscroll = this->GetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR);
|
this->vscroll = this->GetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR);
|
||||||
|
@ -1608,7 +1622,9 @@ CompanyID PerformanceRatingDetailWindow::company = CompanyID::Invalid();
|
||||||
struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
||||||
static inline constexpr StringID RANGE_LABELS[] = {
|
static inline constexpr StringID RANGE_LABELS[] = {
|
||||||
STR_GRAPH_INDUSTRY_RANGE_PRODUCED,
|
STR_GRAPH_INDUSTRY_RANGE_PRODUCED,
|
||||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED
|
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED,
|
||||||
|
STR_GRAPH_INDUSTRY_RANGE_DELIVERED,
|
||||||
|
STR_GRAPH_INDUSTRY_RANGE_WAITING,
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline CargoTypes excluded_cargo_types{};
|
static inline CargoTypes excluded_cargo_types{};
|
||||||
|
@ -1623,6 +1639,10 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
||||||
this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
|
this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
|
||||||
this->ranges = RANGE_LABELS;
|
this->ranges = RANGE_LABELS;
|
||||||
|
|
||||||
|
const Industry *i = Industry::Get(window_number);
|
||||||
|
if (!i->IsCargoProduced()) this->masked_range = (1U << 0) | (1U << 1);
|
||||||
|
if (!i->IsCargoAccepted()) this->masked_range = (1U << 2) | (1U << 3);
|
||||||
|
|
||||||
this->InitializeWindow(window_number, STR_GRAPH_LAST_24_MINUTES_TIME_LABEL);
|
this->InitializeWindow(window_number, STR_GRAPH_LAST_24_MINUTES_TIME_LABEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1630,6 +1650,9 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
||||||
{
|
{
|
||||||
CargoTypes cargo_types{};
|
CargoTypes cargo_types{};
|
||||||
const Industry *i = Industry::Get(window_number);
|
const Industry *i = Industry::Get(window_number);
|
||||||
|
for (const auto &a : i->accepted) {
|
||||||
|
if (IsValidCargoType(a.cargo)) SetBit(cargo_types, a.cargo);
|
||||||
|
}
|
||||||
for (const auto &p : i->produced) {
|
for (const auto &p : i->produced) {
|
||||||
if (IsValidCargoType(p.cargo)) SetBit(cargo_types, p.cargo);
|
if (IsValidCargoType(p.cargo)) SetBit(cargo_types, p.cargo);
|
||||||
}
|
}
|
||||||
|
@ -1643,7 +1666,7 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
||||||
|
|
||||||
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
|
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
|
||||||
{
|
{
|
||||||
if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_INDUSTRY_PRODUCTION_CAPTION, this->window_number);
|
if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_INDUSTRY_CAPTION, this->window_number);
|
||||||
|
|
||||||
return this->Window::GetWidgetString(widget, stringid);
|
return this->Window::GetWidgetString(widget, stringid);
|
||||||
}
|
}
|
||||||
|
@ -1691,6 +1714,33 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
||||||
FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, produced_filler, transported_filler);
|
FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, produced_filler, transported_filler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto &a : i->accepted) {
|
||||||
|
if (!IsValidCargoType(a.cargo)) continue;
|
||||||
|
const CargoSpec *cs = CargoSpec::Get(a.cargo);
|
||||||
|
|
||||||
|
this->data.reserve(this->data.size() + 2);
|
||||||
|
|
||||||
|
DataSet &accepted = this->data.emplace_back();
|
||||||
|
accepted.colour = cs->legend_colour;
|
||||||
|
accepted.exclude_bit = cs->Index();
|
||||||
|
accepted.range_bit = 2;
|
||||||
|
accepted.dash = 1;
|
||||||
|
auto accepted_filler = Filler{accepted, &Industry::AcceptedHistory::accepted};
|
||||||
|
|
||||||
|
DataSet &waiting = this->data.emplace_back();
|
||||||
|
waiting.colour = cs->legend_colour;
|
||||||
|
waiting.exclude_bit = cs->Index();
|
||||||
|
waiting.range_bit = 3;
|
||||||
|
waiting.dash = 4;
|
||||||
|
auto waiting_filler = Filler{waiting, &Industry::AcceptedHistory::waiting};
|
||||||
|
|
||||||
|
if (a.history == nullptr) {
|
||||||
|
FillFromEmpty<GRAPH_NUM_MONTHS>(i->valid_history, accepted_filler, waiting_filler);
|
||||||
|
} else {
|
||||||
|
FillFromHistory<GRAPH_NUM_MONTHS>(*a.history, i->valid_history, accepted_filler, waiting_filler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this->SetDirty();
|
this->SetDirty();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -77,10 +77,27 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> {
|
||||||
HistoryData<ProducedHistory> history{}; ///< History of cargo produced and transported for this month and 24 previous months
|
HistoryData<ProducedHistory> history{}; ///< History of cargo produced and transported for this month and 24 previous months
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AcceptedHistory {
|
||||||
|
uint16_t accepted = 0; /// Total accepted.
|
||||||
|
uint16_t waiting = 0; /// Average waiting.
|
||||||
|
};
|
||||||
|
|
||||||
struct AcceptedCargo {
|
struct AcceptedCargo {
|
||||||
CargoType cargo = 0; ///< Cargo type
|
CargoType cargo = 0; ///< Cargo type
|
||||||
uint16_t waiting = 0; ///< Amount of cargo waiting to processed
|
uint16_t waiting = 0; ///< Amount of cargo waiting to processed
|
||||||
|
uint32_t accumulated_waiting = 0; ///< Accumulated waiting total over the last month, used to calculate average.
|
||||||
TimerGameEconomy::Date last_accepted{}; ///< Last day cargo was accepted by this industry
|
TimerGameEconomy::Date last_accepted{}; ///< Last day cargo was accepted by this industry
|
||||||
|
std::unique_ptr<HistoryData<AcceptedHistory>> history{}; ///< History of accepted and waiting cargo.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get history data, creating it if necessary.
|
||||||
|
* @return Accepted history data.
|
||||||
|
*/
|
||||||
|
inline HistoryData<AcceptedHistory> &GetOrCreateHistory()
|
||||||
|
{
|
||||||
|
if (this->history == nullptr) this->history = std::make_unique<HistoryData<AcceptedHistory>>();
|
||||||
|
return *this->history;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using ProducedCargoes = std::vector<ProducedCargo>;
|
using ProducedCargoes = std::vector<ProducedCargo>;
|
||||||
|
@ -151,7 +168,7 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> {
|
||||||
*/
|
*/
|
||||||
inline const AcceptedCargo &GetAccepted(size_t slot) const
|
inline const AcceptedCargo &GetAccepted(size_t slot) const
|
||||||
{
|
{
|
||||||
static const AcceptedCargo empty{INVALID_CARGO, 0, {}};
|
static const AcceptedCargo empty{INVALID_CARGO, 0, 0, {}, {}};
|
||||||
return slot < this->accepted.size() ? this->accepted[slot] : empty;
|
return slot < this->accepted.size() ? this->accepted[slot] : empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1245,6 +1245,10 @@ void OnTick_Industry()
|
||||||
|
|
||||||
for (Industry *i : Industry::Iterate()) {
|
for (Industry *i : Industry::Iterate()) {
|
||||||
ProduceIndustryGoods(i);
|
ProduceIndustryGoods(i);
|
||||||
|
|
||||||
|
if ((TimerGameTick::counter + i->index) % Ticks::DAY_TICKS == 0) {
|
||||||
|
for (auto &a : i->accepted) a.accumulated_waiting += a.waiting;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2505,6 +2509,14 @@ static void UpdateIndustryStatistics(Industry *i)
|
||||||
RotateHistory(p.history);
|
RotateHistory(p.history);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto &a : i->accepted) {
|
||||||
|
if (!IsValidCargoType(a.cargo)) continue;
|
||||||
|
if (a.history == nullptr) continue;
|
||||||
|
|
||||||
|
(*a.history)[THIS_MONTH].waiting = GetAndResetAccumulatedAverage<uint16_t>(a.accumulated_waiting);
|
||||||
|
RotateHistory(*a.history);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -27,6 +27,4 @@ DEF_CMD_TRAIT(CMD_INDUSTRY_SET_EXCLUSIVITY, CmdIndustrySetExclusivity, CommandFl
|
||||||
DEF_CMD_TRAIT(CMD_INDUSTRY_SET_TEXT, CmdIndustrySetText, CommandFlags({CommandFlag::Deity, CommandFlag::StrCtrl}), CMDT_OTHER_MANAGEMENT)
|
DEF_CMD_TRAIT(CMD_INDUSTRY_SET_TEXT, CmdIndustrySetText, CommandFlags({CommandFlag::Deity, CommandFlag::StrCtrl}), CMDT_OTHER_MANAGEMENT)
|
||||||
DEF_CMD_TRAIT(CMD_INDUSTRY_SET_PRODUCTION, CmdIndustrySetProduction, CommandFlag::Deity, CMDT_OTHER_MANAGEMENT)
|
DEF_CMD_TRAIT(CMD_INDUSTRY_SET_PRODUCTION, CmdIndustrySetProduction, CommandFlag::Deity, CMDT_OTHER_MANAGEMENT)
|
||||||
|
|
||||||
void CcBuildIndustry(Commands cmd, const CommandCost &result, TileIndex tile, IndustryType indtype, uint32_t, bool, uint32_t);
|
|
||||||
|
|
||||||
#endif /* INDUSTRY_CMD_H */
|
#endif /* INDUSTRY_CMD_H */
|
||||||
|
|
|
@ -257,25 +257,6 @@ void SortIndustryTypes()
|
||||||
std::sort(_sorted_industry_types.begin(), _sorted_industry_types.end(), IndustryTypeNameSorter);
|
std::sort(_sorted_industry_types.begin(), _sorted_industry_types.end(), IndustryTypeNameSorter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Command callback. In case of failure to build an industry, show an error message.
|
|
||||||
* @param result Result of the command.
|
|
||||||
* @param tile Tile where the industry is placed.
|
|
||||||
* @param indtype Industry type.
|
|
||||||
*/
|
|
||||||
void CcBuildIndustry(Commands, const CommandCost &result, TileIndex tile, IndustryType indtype, uint32_t, bool, uint32_t)
|
|
||||||
{
|
|
||||||
if (result.Succeeded()) return;
|
|
||||||
|
|
||||||
if (indtype < NUM_INDUSTRYTYPES) {
|
|
||||||
const IndustrySpec *indsp = GetIndustrySpec(indtype);
|
|
||||||
if (indsp->enabled) {
|
|
||||||
ShowErrorMessage(GetEncodedString(STR_ERROR_CAN_T_BUILD_HERE, indsp->name),
|
|
||||||
GetEncodedString(result.GetErrorMessage()), WL_INFO, TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr NWidgetPart _nested_build_industry_widgets[] = {
|
static constexpr NWidgetPart _nested_build_industry_widgets[] = {
|
||||||
NWidget(NWID_HORIZONTAL),
|
NWidget(NWID_HORIZONTAL),
|
||||||
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
|
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
|
||||||
|
@ -745,7 +726,7 @@ public:
|
||||||
AutoRestoreBackup backup_generating_world(_generating_world, true);
|
AutoRestoreBackup backup_generating_world(_generating_world, true);
|
||||||
AutoRestoreBackup backup_ignore_industry_restritions(_ignore_industry_restrictions, true);
|
AutoRestoreBackup backup_ignore_industry_restritions(_ignore_industry_restrictions, true);
|
||||||
|
|
||||||
Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, &CcBuildIndustry, tile, this->selected_type, layout_index, false, seed);
|
Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed);
|
||||||
} else {
|
} else {
|
||||||
success = Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed);
|
success = Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed);
|
||||||
}
|
}
|
||||||
|
@ -845,7 +826,7 @@ public:
|
||||||
nvp->InitializeViewport(this, Industry::Get(window_number)->location.GetCenterTile(), ScaleZoomGUI(ZoomLevel::Industry));
|
nvp->InitializeViewport(this, Industry::Get(window_number)->location.GetCenterTile(), ScaleZoomGUI(ZoomLevel::Industry));
|
||||||
|
|
||||||
const Industry *i = Industry::Get(window_number);
|
const Industry *i = Industry::Get(window_number);
|
||||||
if (!i->IsCargoProduced()) this->DisableWidget(WID_IV_GRAPH);
|
if (!i->IsCargoProduced() && !i->IsCargoAccepted()) this->DisableWidget(WID_IV_GRAPH);
|
||||||
|
|
||||||
this->InvalidateData();
|
this->InvalidateData();
|
||||||
}
|
}
|
||||||
|
@ -1242,7 +1223,7 @@ static constexpr NWidgetPart _nested_industry_view_widgets[] = {
|
||||||
EndContainer(),
|
EndContainer(),
|
||||||
NWidget(NWID_HORIZONTAL),
|
NWidget(NWID_HORIZONTAL),
|
||||||
NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_DISPLAY), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
|
NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_DISPLAY), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
|
||||||
NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_GRAPH), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_VIEW_PRODUCTION_GRAPH, STR_INDUSTRY_VIEW_PRODUCTION_GRAPH_TOOLTIP),
|
NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_GRAPH), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_VIEW_CARGO_GRAPH, STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP),
|
||||||
NWidget(WWT_RESIZEBOX, COLOUR_CREAM),
|
NWidget(WWT_RESIZEBOX, COLOUR_CREAM),
|
||||||
EndContainer(),
|
EndContainer(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -634,9 +634,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Display
|
||||||
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Toggle graph of this cargo type
|
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Toggle graph of this cargo type
|
||||||
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
|
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
|
||||||
|
|
||||||
STR_GRAPH_INDUSTRY_PRODUCTION_CAPTION :{WHITE}{INDUSTRY} - Production History
|
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Cargo History
|
||||||
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Produced
|
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Produced
|
||||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Transported
|
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Transported
|
||||||
|
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Delivered
|
||||||
|
STR_GRAPH_INDUSTRY_RANGE_WAITING :Waiting
|
||||||
|
|
||||||
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Show detailed performance ratings
|
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Show detailed performance ratings
|
||||||
|
|
||||||
|
@ -1698,6 +1700,25 @@ STR_CONFIG_SETTING_SCROLLWHEEL_ZOOM :Zoom map
|
||||||
STR_CONFIG_SETTING_SCROLLWHEEL_SCROLL :Scroll map
|
STR_CONFIG_SETTING_SCROLLWHEEL_SCROLL :Scroll map
|
||||||
STR_CONFIG_SETTING_SCROLLWHEEL_OFF :Off
|
STR_CONFIG_SETTING_SCROLLWHEEL_OFF :Off
|
||||||
|
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION :Gamepad stick for scrolling: {STRING2}
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION_HELPTEXT :Select which analog stick to use for map scrolling
|
||||||
|
###length 3
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_STICK_DISABLED :Disabled
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_STICK_LEFT :Left stick
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_STICK_RIGHT :Right stick
|
||||||
|
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_DEADZONE :Gamepad deadzone: {STRING2}%
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_DEADZONE_HELPTEXT :Minimum stick movement required before scrolling starts (0-100%)
|
||||||
|
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY :Gamepad sensitivity: {STRING2}
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY_HELPTEXT :Control the sensitivity of gamepad analog stick scrolling
|
||||||
|
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_INVERT_X :Invert gamepad X-axis: {STRING2}
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_INVERT_X_HELPTEXT :Invert the horizontal axis movement of the gamepad analog stick
|
||||||
|
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_INVERT_Y :Invert gamepad Y-axis: {STRING2}
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_INVERT_Y_HELPTEXT :Invert the vertical axis movement of the gamepad analog stick
|
||||||
|
|
||||||
STR_CONFIG_SETTING_OSK_ACTIVATION :On screen keyboard: {STRING2}
|
STR_CONFIG_SETTING_OSK_ACTIVATION :On screen keyboard: {STRING2}
|
||||||
STR_CONFIG_SETTING_OSK_ACTIVATION_HELPTEXT :Select the method to open the on screen keyboard for entering text into editboxes only using the pointing device. This is meant for small devices without actual keyboard
|
STR_CONFIG_SETTING_OSK_ACTIVATION_HELPTEXT :Select the method to open the on screen keyboard for entering text into editboxes only using the pointing device. This is meant for small devices without actual keyboard
|
||||||
###length 4
|
###length 4
|
||||||
|
@ -4024,8 +4045,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Producti
|
||||||
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Production last minute:
|
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Production last minute:
|
||||||
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{RAW_STRING}{BLACK} ({COMMA}% transported)
|
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{RAW_STRING}{BLACK} ({COMMA}% transported)
|
||||||
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Centre the main view on industry location. Ctrl+Click to open a new viewport on industry location
|
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Centre the main view on industry location. Ctrl+Click to open a new viewport on industry location
|
||||||
STR_INDUSTRY_VIEW_PRODUCTION_GRAPH :{BLACK}Production Graph
|
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Cargo Graph
|
||||||
STR_INDUSTRY_VIEW_PRODUCTION_GRAPH_TOOLTIP :{BLACK}Shows the graph of industry production history
|
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Shows the graph of industry cargo history
|
||||||
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Production level: {YELLOW}{COMMA}%
|
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Production level: {YELLOW}{COMMA}%
|
||||||
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}The industry has announced imminent closure!
|
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}The industry has announced imminent closure!
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#define HISTORY_FUNC_HPP
|
#define HISTORY_FUNC_HPP
|
||||||
|
|
||||||
#include "../core/bitmath_func.hpp"
|
#include "../core/bitmath_func.hpp"
|
||||||
|
#include "../core/math_func.hpp"
|
||||||
|
#include "../timer/timer_game_economy.h"
|
||||||
#include "history_type.hpp"
|
#include "history_type.hpp"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,6 +36,19 @@ void RotateHistory(HistoryData<T> &history)
|
||||||
history[THIS_MONTH] = {};
|
history[THIS_MONTH] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an average value for the previous month, as reset for the next month.
|
||||||
|
* @param total Accrued total to average. Will be reset to zero.
|
||||||
|
* @return Average value for the month.
|
||||||
|
*/
|
||||||
|
template <typename T, typename Taccrued>
|
||||||
|
T GetAndResetAccumulatedAverage(Taccrued &total)
|
||||||
|
{
|
||||||
|
T result = ClampTo<T>(total / std::max(1U, TimerGameEconomy::days_since_last_month));
|
||||||
|
total = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill some data with historical data.
|
* Fill some data with historical data.
|
||||||
* @param history Historical data to fill from.
|
* @param history Historical data to fill from.
|
||||||
|
@ -53,4 +68,21 @@ void FillFromHistory(const HistoryData<T> &history, ValidHistoryMask valid_histo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill some data with empty records.
|
||||||
|
* @param valid_history Mask of valid history records.
|
||||||
|
* @param fillers Fillers to fill with history data.
|
||||||
|
*/
|
||||||
|
template <uint N, typename... Tfillers>
|
||||||
|
void FillFromEmpty(ValidHistoryMask valid_history, Tfillers... fillers)
|
||||||
|
{
|
||||||
|
for (uint i = 0; i != N; ++i) {
|
||||||
|
if (HasBit(valid_history, N - i)) {
|
||||||
|
(fillers.MakeZero(i), ...);
|
||||||
|
} else {
|
||||||
|
(fillers.MakeInvalid(i), ...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* HISTORY_FUNC_HPP */
|
#endif /* HISTORY_FUNC_HPP */
|
||||||
|
|
|
@ -79,7 +79,6 @@ static constexpr auto _callback_tuple = std::make_tuple(
|
||||||
&CcCreateGroup,
|
&CcCreateGroup,
|
||||||
&CcFoundRandomTown,
|
&CcFoundRandomTown,
|
||||||
&CcRoadStop,
|
&CcRoadStop,
|
||||||
&CcBuildIndustry,
|
|
||||||
&CcStartStopVehicle,
|
&CcStartStopVehicle,
|
||||||
&CcGame,
|
&CcGame,
|
||||||
&CcAddVehicleNewGroup
|
&CcAddVehicleNewGroup
|
||||||
|
|
|
@ -19,12 +19,50 @@
|
||||||
|
|
||||||
static OldPersistentStorage _old_ind_persistent_storage;
|
static OldPersistentStorage _old_ind_persistent_storage;
|
||||||
|
|
||||||
|
class SlIndustryAcceptedHistory : public DefaultSaveLoadHandler<SlIndustryAcceptedHistory, Industry::AcceptedCargo> {
|
||||||
|
public:
|
||||||
|
static inline const SaveLoad description[] = {
|
||||||
|
SLE_VAR(Industry::AcceptedHistory, accepted, SLE_UINT16),
|
||||||
|
SLE_VAR(Industry::AcceptedHistory, waiting, SLE_UINT16),
|
||||||
|
};
|
||||||
|
static inline const SaveLoadCompatTable compat_description = _industry_produced_history_sl_compat;
|
||||||
|
|
||||||
|
void Save(Industry::AcceptedCargo *a) const override
|
||||||
|
{
|
||||||
|
if (!IsValidCargoType(a->cargo) || a->history == nullptr) {
|
||||||
|
/* Don't save any history if cargo slot isn't used. */
|
||||||
|
SlSetStructListLength(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SlSetStructListLength(a->history->size());
|
||||||
|
|
||||||
|
for (auto &h : *a->history) {
|
||||||
|
SlObject(&h, this->GetDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Load(Industry::AcceptedCargo *a) const override
|
||||||
|
{
|
||||||
|
size_t len = SlGetStructListLength(UINT32_MAX);
|
||||||
|
if (len == 0) return;
|
||||||
|
|
||||||
|
auto &history = a->GetOrCreateHistory();
|
||||||
|
for (auto &h : history) {
|
||||||
|
if (--len > history.size()) break; // unsigned so wraps after hitting zero.
|
||||||
|
SlObject(&h, this->GetDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class SlIndustryAccepted : public VectorSaveLoadHandler<SlIndustryAccepted, Industry, Industry::AcceptedCargo, INDUSTRY_NUM_INPUTS> {
|
class SlIndustryAccepted : public VectorSaveLoadHandler<SlIndustryAccepted, Industry, Industry::AcceptedCargo, INDUSTRY_NUM_INPUTS> {
|
||||||
public:
|
public:
|
||||||
static inline const SaveLoad description[] = {
|
static inline const SaveLoad description[] = {
|
||||||
SLE_VAR(Industry::AcceptedCargo, cargo, SLE_UINT8),
|
SLE_VAR(Industry::AcceptedCargo, cargo, SLE_UINT8),
|
||||||
SLE_VAR(Industry::AcceptedCargo, waiting, SLE_UINT16),
|
SLE_VAR(Industry::AcceptedCargo, waiting, SLE_UINT16),
|
||||||
SLE_VAR(Industry::AcceptedCargo, last_accepted, SLE_INT32),
|
SLE_VAR(Industry::AcceptedCargo, last_accepted, SLE_INT32),
|
||||||
|
SLE_CONDVAR(Industry::AcceptedCargo, accumulated_waiting, SLE_UINT32, SLV_INDUSTRY_ACCEPTED_HISTORY, SL_MAX_VERSION),
|
||||||
|
SLEG_CONDSTRUCTLIST("history", SlIndustryAcceptedHistory, SLV_INDUSTRY_ACCEPTED_HISTORY, SL_MAX_VERSION),
|
||||||
};
|
};
|
||||||
static inline const SaveLoadCompatTable compat_description = _industry_accepts_sl_compat;
|
static inline const SaveLoadCompatTable compat_description = _industry_accepts_sl_compat;
|
||||||
|
|
||||||
|
|
|
@ -858,7 +858,7 @@ static bool LoadOldIndustry(LoadgameState &ls, int num)
|
||||||
|
|
||||||
if (i->location.tile != 0) {
|
if (i->location.tile != 0) {
|
||||||
/* Copy data from old fixed arrays to industry. */
|
/* Copy data from old fixed arrays to industry. */
|
||||||
std::copy(std::begin(_old_accepted), std::end(_old_accepted), std::back_inserter(i->accepted));
|
std::move(std::begin(_old_accepted), std::end(_old_accepted), std::back_inserter(i->accepted));
|
||||||
std::copy(std::begin(_old_produced), std::end(_old_produced), std::back_inserter(i->produced));
|
std::copy(std::begin(_old_produced), std::end(_old_produced), std::back_inserter(i->produced));
|
||||||
|
|
||||||
i->town = RemapTown(i->location.tile);
|
i->town = RemapTown(i->location.tile);
|
||||||
|
|
|
@ -405,6 +405,7 @@ enum SaveLoadVersion : uint16_t {
|
||||||
|
|
||||||
SLV_FACE_STYLES, ///< 355 PR#14319 Addition of face styles, replacing gender and ethnicity.
|
SLV_FACE_STYLES, ///< 355 PR#14319 Addition of face styles, replacing gender and ethnicity.
|
||||||
SLV_INDUSTRY_NUM_VALID_HISTORY, ///< 356 PR#14416 Store number of valid history records for industries.
|
SLV_INDUSTRY_NUM_VALID_HISTORY, ///< 356 PR#14416 Store number of valid history records for industries.
|
||||||
|
SLV_INDUSTRY_ACCEPTED_HISTORY, ///< 357 PR#14321 Add per-industry history of cargo delivered and waiting.
|
||||||
|
|
||||||
SL_MAX_VERSION, ///< Highest possible saveload version
|
SL_MAX_VERSION, ///< Highest possible saveload version
|
||||||
};
|
};
|
||||||
|
|
|
@ -681,6 +681,11 @@ SettingsContainer &GetSettingsTree()
|
||||||
* Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
|
* Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
|
||||||
viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
|
viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
|
||||||
viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
|
viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
|
||||||
|
viewports->Add(new SettingEntry("gui.gamepad_stick_selection"));
|
||||||
|
viewports->Add(new SettingEntry("gui.gamepad_deadzone"));
|
||||||
|
viewports->Add(new SettingEntry("gui.gamepad_sensitivity"));
|
||||||
|
viewports->Add(new SettingEntry("gui.gamepad_invert_x"));
|
||||||
|
viewports->Add(new SettingEntry("gui.gamepad_invert_y"));
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
/* We might need to emulate a right mouse button on mac */
|
/* We might need to emulate a right mouse button on mac */
|
||||||
viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
|
viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
|
||||||
|
|
|
@ -140,6 +140,13 @@ enum ScrollWheelScrollingSetting : uint8_t {
|
||||||
SWS_OFF = 2 ///< Scroll wheel has no effect.
|
SWS_OFF = 2 ///< Scroll wheel has no effect.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Settings related to gamepad stick selection. */
|
||||||
|
enum GamepadStickSelection : uint8_t {
|
||||||
|
GSS_DISABLED = 0, ///< Gamepad scrolling disabled.
|
||||||
|
GSS_LEFT_STICK = 1, ///< Use left analog stick for scrolling.
|
||||||
|
GSS_RIGHT_STICK = 2, ///< Use right analog stick for scrolling.
|
||||||
|
};
|
||||||
|
|
||||||
/** Settings related to the GUI and other stuff that is not saved in the savegame. */
|
/** Settings related to the GUI and other stuff that is not saved in the savegame. */
|
||||||
struct GUISettings {
|
struct GUISettings {
|
||||||
bool sg_full_load_any; ///< new full load calculation, any cargo must be full read from pre v93 savegames
|
bool sg_full_load_any; ///< new full load calculation, any cargo must be full read from pre v93 savegames
|
||||||
|
@ -183,6 +190,11 @@ struct GUISettings {
|
||||||
uint8_t right_mouse_btn_emulation; ///< should we emulate right mouse clicking?
|
uint8_t right_mouse_btn_emulation; ///< should we emulate right mouse clicking?
|
||||||
uint8_t scrollwheel_scrolling; ///< scrolling using the scroll wheel?
|
uint8_t scrollwheel_scrolling; ///< scrolling using the scroll wheel?
|
||||||
uint8_t scrollwheel_multiplier; ///< how much 'wheel' per incoming event from the OS?
|
uint8_t scrollwheel_multiplier; ///< how much 'wheel' per incoming event from the OS?
|
||||||
|
uint8_t gamepad_deadzone; ///< deadzone for gamepad analog sticks (0-100)
|
||||||
|
uint8_t gamepad_sensitivity; ///< sensitivity multiplier for gamepad scrolling
|
||||||
|
bool gamepad_invert_x; ///< invert X axis for gamepad scrolling?
|
||||||
|
bool gamepad_invert_y; ///< invert Y axis for gamepad scrolling?
|
||||||
|
uint8_t gamepad_stick_selection; ///< which stick to use for scrolling (left/right/disabled)
|
||||||
bool timetable_arrival_departure; ///< show arrivals and departures in vehicle timetables
|
bool timetable_arrival_departure; ///< show arrivals and departures in vehicle timetables
|
||||||
RightClickClose right_click_wnd_close; ///< close window with right click
|
RightClickClose right_click_wnd_close; ///< close window with right click
|
||||||
bool pause_on_newgame; ///< whether to start new games paused or not
|
bool pause_on_newgame; ///< whether to start new games paused or not
|
||||||
|
|
|
@ -912,3 +912,62 @@ post_cb = [](auto) { SetupWidgetDimensions(); ReInitAllWindows(true); }
|
||||||
cat = SC_BASIC
|
cat = SC_BASIC
|
||||||
startup = true
|
startup = true
|
||||||
|
|
||||||
|
[SDTC_VAR]
|
||||||
|
var = gui.gamepad_deadzone
|
||||||
|
type = SLE_UINT8
|
||||||
|
flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync
|
||||||
|
def = 10
|
||||||
|
min = 0
|
||||||
|
max = 100
|
||||||
|
interval = 5
|
||||||
|
str = STR_CONFIG_SETTING_GAMEPAD_DEADZONE
|
||||||
|
strhelp = STR_CONFIG_SETTING_GAMEPAD_DEADZONE_HELPTEXT
|
||||||
|
strval = STR_JUST_COMMA
|
||||||
|
cat = SC_BASIC
|
||||||
|
startup = true
|
||||||
|
|
||||||
|
[SDTC_VAR]
|
||||||
|
var = gui.gamepad_sensitivity
|
||||||
|
type = SLE_UINT8
|
||||||
|
flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync
|
||||||
|
def = 10
|
||||||
|
min = 1
|
||||||
|
max = 100
|
||||||
|
interval = 5
|
||||||
|
str = STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY
|
||||||
|
strhelp = STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY_HELPTEXT
|
||||||
|
strval = STR_JUST_COMMA
|
||||||
|
cat = SC_BASIC
|
||||||
|
startup = true
|
||||||
|
|
||||||
|
[SDTC_BOOL]
|
||||||
|
var = gui.gamepad_invert_x
|
||||||
|
flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync
|
||||||
|
def = false
|
||||||
|
str = STR_CONFIG_SETTING_GAMEPAD_INVERT_X
|
||||||
|
strhelp = STR_CONFIG_SETTING_GAMEPAD_INVERT_X_HELPTEXT
|
||||||
|
cat = SC_BASIC
|
||||||
|
startup = true
|
||||||
|
|
||||||
|
[SDTC_BOOL]
|
||||||
|
var = gui.gamepad_invert_y
|
||||||
|
flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync
|
||||||
|
def = false
|
||||||
|
str = STR_CONFIG_SETTING_GAMEPAD_INVERT_Y
|
||||||
|
strhelp = STR_CONFIG_SETTING_GAMEPAD_INVERT_Y_HELPTEXT
|
||||||
|
cat = SC_BASIC
|
||||||
|
startup = true
|
||||||
|
|
||||||
|
[SDTC_VAR]
|
||||||
|
var = gui.gamepad_stick_selection
|
||||||
|
type = SLE_UINT8
|
||||||
|
flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync, SettingFlag::GuiDropdown
|
||||||
|
def = GSS_LEFT_STICK
|
||||||
|
min = GSS_DISABLED
|
||||||
|
max = GSS_RIGHT_STICK
|
||||||
|
str = STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION
|
||||||
|
strhelp = STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION_HELPTEXT
|
||||||
|
strval = STR_CONFIG_SETTING_GAMEPAD_STICK_DISABLED
|
||||||
|
cat = SC_BASIC
|
||||||
|
startup = true
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ TimerGameEconomy::Year TimerGameEconomy::year = {};
|
||||||
TimerGameEconomy::Month TimerGameEconomy::month = {};
|
TimerGameEconomy::Month TimerGameEconomy::month = {};
|
||||||
TimerGameEconomy::Date TimerGameEconomy::date = {};
|
TimerGameEconomy::Date TimerGameEconomy::date = {};
|
||||||
TimerGameEconomy::DateFract TimerGameEconomy::date_fract = {};
|
TimerGameEconomy::DateFract TimerGameEconomy::date_fract = {};
|
||||||
|
uint TimerGameEconomy::days_since_last_month = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a Date to a Year, Month & Day.
|
* Converts a Date to a Year, Month & Day.
|
||||||
|
@ -133,6 +134,7 @@ bool TimerManager<TimerGameEconomy>::Elapsed([[maybe_unused]] TimerGameEconomy::
|
||||||
|
|
||||||
/* increase day counter */
|
/* increase day counter */
|
||||||
TimerGameEconomy::date++;
|
TimerGameEconomy::date++;
|
||||||
|
++TimerGameEconomy::days_since_last_month;
|
||||||
|
|
||||||
TimerGameEconomy::YearMonthDay ymd = TimerGameEconomy::ConvertDateToYMD(TimerGameEconomy::date);
|
TimerGameEconomy::YearMonthDay ymd = TimerGameEconomy::ConvertDateToYMD(TimerGameEconomy::date);
|
||||||
|
|
||||||
|
@ -177,6 +179,8 @@ bool TimerManager<TimerGameEconomy>::Elapsed([[maybe_unused]] TimerGameEconomy::
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (new_month) TimerGameEconomy::days_since_last_month = 0;
|
||||||
|
|
||||||
/* check if we reached the maximum year, decrement dates by a year */
|
/* check if we reached the maximum year, decrement dates by a year */
|
||||||
if (TimerGameEconomy::year == EconomyTime::MAX_YEAR + 1) {
|
if (TimerGameEconomy::year == EconomyTime::MAX_YEAR + 1) {
|
||||||
TimerGameEconomy::year--;
|
TimerGameEconomy::year--;
|
||||||
|
|
|
@ -37,6 +37,8 @@ public:
|
||||||
static Date date; ///< Current date in days (day counter).
|
static Date date; ///< Current date in days (day counter).
|
||||||
static DateFract date_fract; ///< Fractional part of the day.
|
static DateFract date_fract; ///< Fractional part of the day.
|
||||||
|
|
||||||
|
static uint days_since_last_month; ///< Number of days that have elapsed since the last month.
|
||||||
|
|
||||||
static YearMonthDay ConvertDateToYMD(Date date);
|
static YearMonthDay ConvertDateToYMD(Date date);
|
||||||
static Date ConvertYMDToDate(Year year, Month month, Day day);
|
static Date ConvertYMDToDate(Year year, Month month, Day day);
|
||||||
static void SetDate(Date date, DateFract fract);
|
static void SetDate(Date date, DateFract fract);
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "../fileio_func.h"
|
#include "../fileio_func.h"
|
||||||
#include "../framerate_type.h"
|
#include "../framerate_type.h"
|
||||||
#include "../window_func.h"
|
#include "../window_func.h"
|
||||||
|
#include "../zoom_func.h"
|
||||||
#include "sdl2_v.h"
|
#include "sdl2_v.h"
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
|
@ -524,6 +525,27 @@ bool VideoDriver_SDL_Base::PollEvent()
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SDL_CONTROLLERDEVICEADDED: {
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad device added, index: {}", ev.cdevice.which);
|
||||||
|
/* Try to open the newly connected gamepad */
|
||||||
|
if (this->gamepad == nullptr) {
|
||||||
|
this->OpenGamepad();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SDL_CONTROLLERDEVICEREMOVED: {
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad device removed, instance ID: {}", ev.cdevice.which);
|
||||||
|
/* Close gamepad if it was removed */
|
||||||
|
if (this->gamepad != nullptr && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(this->gamepad))) {
|
||||||
|
Debug(driver, 1, "SDL2: Current gamepad was removed, closing and reopening");
|
||||||
|
this->CloseGamepad();
|
||||||
|
/* Try to open another gamepad if available */
|
||||||
|
this->OpenGamepad();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -539,6 +561,12 @@ static std::optional<std::string_view> InitializeSDL()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) return SDL_GetError();
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) return SDL_GetError();
|
||||||
|
|
||||||
|
/* Initialize gamepad subsystem for gamepad scrolling support */
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) {
|
||||||
|
Debug(driver, 1, "SDL2: Failed to initialize gamepad subsystem: {}", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,6 +618,11 @@ std::optional<std::string_view> VideoDriver_SDL_Base::Start(const StringList &pa
|
||||||
SDL_StopTextInput();
|
SDL_StopTextInput();
|
||||||
this->edit_box_focused = false;
|
this->edit_box_focused = false;
|
||||||
|
|
||||||
|
/* Initialize gamepad for scrolling */
|
||||||
|
Debug(driver, 1, "SDL2: Attempting to initialize gamepad support");
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad stick selection setting: {}", _settings_client.gui.gamepad_stick_selection);
|
||||||
|
this->OpenGamepad();
|
||||||
|
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
this->is_game_threaded = false;
|
this->is_game_threaded = false;
|
||||||
#else
|
#else
|
||||||
|
@ -601,6 +634,8 @@ std::optional<std::string_view> VideoDriver_SDL_Base::Start(const StringList &pa
|
||||||
|
|
||||||
void VideoDriver_SDL_Base::Stop()
|
void VideoDriver_SDL_Base::Stop()
|
||||||
{
|
{
|
||||||
|
this->CloseGamepad();
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
|
if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
|
||||||
SDL_Quit(); // If there's nothing left, quit SDL
|
SDL_Quit(); // If there's nothing left, quit SDL
|
||||||
|
@ -629,6 +664,9 @@ void VideoDriver_SDL_Base::InputLoop()
|
||||||
(keys[SDL_SCANCODE_DOWN] ? 8 : 0);
|
(keys[SDL_SCANCODE_DOWN] ? 8 : 0);
|
||||||
|
|
||||||
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
||||||
|
|
||||||
|
/* Process gamepad input for scrolling */
|
||||||
|
this->ProcessGamepadInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoDriver_SDL_Base::LoopOnce()
|
void VideoDriver_SDL_Base::LoopOnce()
|
||||||
|
@ -755,3 +793,84 @@ void VideoDriver_SDL_Base::UnlockVideoBuffer()
|
||||||
|
|
||||||
this->buffer_locked = false;
|
this->buffer_locked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VideoDriver_SDL_Base::OpenGamepad()
|
||||||
|
{
|
||||||
|
/* Don't open gamepad if already open or if gamepad scrolling is disabled */
|
||||||
|
if (this->gamepad != nullptr) {
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad already open, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_DISABLED) {
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad scrolling disabled, not opening gamepad");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if any gamepads are available */
|
||||||
|
int num_gamepads = SDL_NumJoysticks();
|
||||||
|
Debug(driver, 1, "SDL2: Found {} joystick(s)", num_gamepads);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_gamepads; i++) {
|
||||||
|
if (SDL_IsGameController(i)) {
|
||||||
|
Debug(driver, 1, "SDL2: Joystick {} is a gamepad, attempting to open", i);
|
||||||
|
this->gamepad = SDL_GameControllerOpen(i);
|
||||||
|
if (this->gamepad != nullptr) {
|
||||||
|
Debug(driver, 1, "SDL2: Opened gamepad: {}", SDL_GameControllerName(this->gamepad));
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
Debug(driver, 1, "SDL2: Failed to open gamepad {}: {}", i, SDL_GetError());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Debug(driver, 1, "SDL2: Joystick {} is not a gamepad", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->gamepad == nullptr) {
|
||||||
|
Debug(driver, 1, "SDL2: No gamepad opened");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoDriver_SDL_Base::CloseGamepad()
|
||||||
|
{
|
||||||
|
if (this->gamepad != nullptr) {
|
||||||
|
SDL_GameControllerClose(this->gamepad);
|
||||||
|
this->gamepad = nullptr;
|
||||||
|
Debug(driver, 1, "SDL2: Closed gamepad");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoDriver_SDL_Base::ProcessGamepadInput()
|
||||||
|
{
|
||||||
|
/* Skip if gamepad is not available */
|
||||||
|
if (this->gamepad == nullptr) {
|
||||||
|
static bool logged_no_gamepad = false;
|
||||||
|
if (!logged_no_gamepad) {
|
||||||
|
Debug(driver, 2, "SDL2: No gamepad available for input processing");
|
||||||
|
logged_no_gamepad = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if gamepad is still connected */
|
||||||
|
if (!SDL_GameControllerGetAttached(this->gamepad)) {
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad disconnected, closing and reopening");
|
||||||
|
this->CloseGamepad();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get analog stick values based on stick selection */
|
||||||
|
Sint16 stick_x = 0, stick_y = 0;
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_LEFT_STICK) {
|
||||||
|
stick_x = SDL_GameControllerGetAxis(this->gamepad, SDL_CONTROLLER_AXIS_LEFTX);
|
||||||
|
stick_y = SDL_GameControllerGetAxis(this->gamepad, SDL_CONTROLLER_AXIS_LEFTY);
|
||||||
|
Debug(driver, 3, "SDL2: Left stick raw values: x={}, y={}", stick_x, stick_y);
|
||||||
|
} else if (_settings_client.gui.gamepad_stick_selection == GSS_RIGHT_STICK) {
|
||||||
|
stick_x = SDL_GameControllerGetAxis(this->gamepad, SDL_CONTROLLER_AXIS_RIGHTX);
|
||||||
|
stick_y = SDL_GameControllerGetAxis(this->gamepad, SDL_CONTROLLER_AXIS_RIGHTY);
|
||||||
|
Debug(driver, 3, "SDL2: Right stick raw values: x={}, y={}", stick_x, stick_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use the common gamepad handling function */
|
||||||
|
HandleGamepadScrolling(stick_x, stick_y, 32767);
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,10 @@
|
||||||
|
|
||||||
#include "video_driver.hpp"
|
#include "video_driver.hpp"
|
||||||
|
|
||||||
|
/* Forward declaration of SDL_GameController */
|
||||||
|
struct _SDL_GameController;
|
||||||
|
typedef struct _SDL_GameController SDL_GameController;
|
||||||
|
|
||||||
/** The SDL video driver. */
|
/** The SDL video driver. */
|
||||||
class VideoDriver_SDL_Base : public VideoDriver {
|
class VideoDriver_SDL_Base : public VideoDriver {
|
||||||
public:
|
public:
|
||||||
|
@ -69,6 +73,13 @@ protected:
|
||||||
/** Create the main window. */
|
/** Create the main window. */
|
||||||
virtual bool CreateMainWindow(uint w, uint h, uint flags = 0);
|
virtual bool CreateMainWindow(uint w, uint h, uint flags = 0);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** Gamepad support for map scrolling */
|
||||||
|
SDL_GameController *gamepad = nullptr; ///< Currently opened gamepad.
|
||||||
|
void OpenGamepad();
|
||||||
|
void CloseGamepad();
|
||||||
|
void ProcessGamepadInput();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void LoopOnce();
|
void LoopOnce();
|
||||||
void MainLoopCleanup();
|
void MainLoopCleanup();
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <imm.h>
|
#include <imm.h>
|
||||||
#include <versionhelpers.h>
|
#include <versionhelpers.h>
|
||||||
|
#include <xinput.h>
|
||||||
#if defined(_MSC_VER) && defined(NTDDI_WIN10_RS4)
|
#if defined(_MSC_VER) && defined(NTDDI_WIN10_RS4)
|
||||||
#include <winrt/Windows.UI.ViewManagement.h>
|
#include <winrt/Windows.UI.ViewManagement.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -984,6 +985,9 @@ void VideoDriver_Win32Base::InputLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
||||||
|
|
||||||
|
/* Process gamepad input for scrolling */
|
||||||
|
this->ProcessGamepadInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoDriver_Win32Base::PollEvent()
|
bool VideoDriver_Win32Base::PollEvent()
|
||||||
|
@ -1143,6 +1147,106 @@ void VideoDriver_Win32Base::UnlockVideoBuffer()
|
||||||
this->buffer_locked = false;
|
this->buffer_locked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VideoDriver_Win32Base::OpenGamepad()
|
||||||
|
{
|
||||||
|
/* Don't open gamepad if already open or if gamepad scrolling is disabled */
|
||||||
|
if (this->gamepad_user_index != XUSER_MAX_COUNT) {
|
||||||
|
Debug(driver, 1, "Win32: Gamepad already open, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_DISABLED) {
|
||||||
|
Debug(driver, 1, "Win32: Gamepad scrolling disabled, not opening gamepad");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for any connected gamepads */
|
||||||
|
for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) {
|
||||||
|
XINPUT_STATE state = {};
|
||||||
|
|
||||||
|
if (XInputGetState(i, &state) == ERROR_SUCCESS) {
|
||||||
|
this->gamepad_user_index = i;
|
||||||
|
Debug(driver, 1, "Win32: Opened gamepad at index {}", i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoDriver_Win32Base::CloseGamepad()
|
||||||
|
{
|
||||||
|
if (this->gamepad_user_index != XUSER_MAX_COUNT) {
|
||||||
|
this->gamepad_user_index = XUSER_MAX_COUNT;
|
||||||
|
Debug(driver, 1, "Win32: Closed gamepad");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoDriver_Win32Base::ProcessGamepadInput()
|
||||||
|
{
|
||||||
|
/* Skip if gamepad scrolling is disabled */
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_DISABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If no gamepad is currently open, try to reconnect periodically */
|
||||||
|
if (this->gamepad_user_index == XUSER_MAX_COUNT) {
|
||||||
|
static bool logged_no_gamepad = false;
|
||||||
|
|
||||||
|
/* Only try to reconnect every 60 frames (~1 second at 60 FPS) to avoid spam */
|
||||||
|
if (this->gamepad_reconnect_timer > 0) {
|
||||||
|
this->gamepad_reconnect_timer--;
|
||||||
|
if (!logged_no_gamepad) {
|
||||||
|
Debug(driver, 2, "Win32: No gamepad available for input processing");
|
||||||
|
logged_no_gamepad = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try to open gamepad */
|
||||||
|
this->OpenGamepad();
|
||||||
|
|
||||||
|
/* If still no gamepad, set timer for next retry */
|
||||||
|
if (this->gamepad_user_index == XUSER_MAX_COUNT) {
|
||||||
|
this->gamepad_reconnect_timer = 60; /* Retry in ~1 second */
|
||||||
|
if (!logged_no_gamepad) {
|
||||||
|
Debug(driver, 2, "Win32: No gamepad available for input processing");
|
||||||
|
logged_no_gamepad = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
/* Successfully reconnected */
|
||||||
|
logged_no_gamepad = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get gamepad state */
|
||||||
|
XINPUT_STATE state = {};
|
||||||
|
|
||||||
|
if (XInputGetState(this->gamepad_user_index, &state) != ERROR_SUCCESS) {
|
||||||
|
Debug(driver, 1, "Win32: Gamepad disconnected, closing and will retry connection");
|
||||||
|
this->CloseGamepad();
|
||||||
|
this->gamepad_reconnect_timer = 60; /* Start retry timer */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get analog stick values based on stick selection
|
||||||
|
* Note: XInput uses SHORT values for stick positions, but we have to extend to INT
|
||||||
|
* to avoid overflow when inverting the Y-axis value */
|
||||||
|
INT stick_x = 0, stick_y = 0;
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_LEFT_STICK) {
|
||||||
|
stick_x = state.Gamepad.sThumbLX;
|
||||||
|
stick_y = state.Gamepad.sThumbLY;
|
||||||
|
Debug(driver, 3, "Win32: Left stick raw values: x={}, y={}", stick_x, stick_y);
|
||||||
|
} else if (_settings_client.gui.gamepad_stick_selection == GSS_RIGHT_STICK) {
|
||||||
|
stick_x = state.Gamepad.sThumbRX;
|
||||||
|
stick_y = state.Gamepad.sThumbRY;
|
||||||
|
Debug(driver, 3, "Win32: Right stick raw values: x={}, y={}", stick_x, stick_y);
|
||||||
|
}
|
||||||
|
stick_y = -stick_y; // Xinput Y-axis is inverted from other libraries
|
||||||
|
|
||||||
|
/* Use the common gamepad handling function */
|
||||||
|
HandleGamepadScrolling(stick_x, stick_y, 32767);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static FVideoDriver_Win32GDI iFVideoDriver_Win32GDI;
|
static FVideoDriver_Win32GDI iFVideoDriver_Win32GDI;
|
||||||
|
|
||||||
|
@ -1158,6 +1262,11 @@ std::optional<std::string_view> VideoDriver_Win32GDI::Start(const StringList &pa
|
||||||
|
|
||||||
MarkWholeScreenDirty();
|
MarkWholeScreenDirty();
|
||||||
|
|
||||||
|
/* Initialize gamepad for scrolling */
|
||||||
|
Debug(driver, 1, "Win32: Attempting to initialize gamepad support");
|
||||||
|
Debug(driver, 1, "Win32: Gamepad stick selection setting: {}", _settings_client.gui.gamepad_stick_selection);
|
||||||
|
this->OpenGamepad();
|
||||||
|
|
||||||
this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
|
this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -1165,6 +1274,7 @@ std::optional<std::string_view> VideoDriver_Win32GDI::Start(const StringList &pa
|
||||||
|
|
||||||
void VideoDriver_Win32GDI::Stop()
|
void VideoDriver_Win32GDI::Stop()
|
||||||
{
|
{
|
||||||
|
this->CloseGamepad();
|
||||||
DeleteObject(this->gdi_palette);
|
DeleteObject(this->gdi_palette);
|
||||||
DeleteObject(this->dib_sect);
|
DeleteObject(this->dib_sect);
|
||||||
|
|
||||||
|
@ -1472,6 +1582,11 @@ std::optional<std::string_view> VideoDriver_Win32OpenGL::Start(const StringList
|
||||||
|
|
||||||
MarkWholeScreenDirty();
|
MarkWholeScreenDirty();
|
||||||
|
|
||||||
|
/* Initialize gamepad for scrolling */
|
||||||
|
Debug(driver, 1, "Win32: Attempting to initialize gamepad support");
|
||||||
|
Debug(driver, 1, "Win32: Gamepad stick selection setting: {}", _settings_client.gui.gamepad_stick_selection);
|
||||||
|
this->OpenGamepad();
|
||||||
|
|
||||||
this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
|
this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -1479,6 +1594,7 @@ std::optional<std::string_view> VideoDriver_Win32OpenGL::Start(const StringList
|
||||||
|
|
||||||
void VideoDriver_Win32OpenGL::Stop()
|
void VideoDriver_Win32OpenGL::Stop()
|
||||||
{
|
{
|
||||||
|
this->CloseGamepad();
|
||||||
this->DestroyContext();
|
this->DestroyContext();
|
||||||
this->VideoDriver_Win32Base::Stop();
|
this->VideoDriver_Win32Base::Stop();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <xinput.h>
|
||||||
|
|
||||||
/** Base class for Windows video drivers. */
|
/** Base class for Windows video drivers. */
|
||||||
class VideoDriver_Win32Base : public VideoDriver {
|
class VideoDriver_Win32Base : public VideoDriver {
|
||||||
|
@ -48,6 +49,13 @@ protected:
|
||||||
|
|
||||||
bool buffer_locked; ///< Video buffer was locked by the main thread.
|
bool buffer_locked; ///< Video buffer was locked by the main thread.
|
||||||
|
|
||||||
|
/** Gamepad support for map scrolling */
|
||||||
|
DWORD gamepad_user_index = XUSER_MAX_COUNT; ///< Index of currently opened gamepad (XUSER_MAX_COUNT = no gamepad).
|
||||||
|
uint32_t gamepad_reconnect_timer = 0; ///< Timer for retrying gamepad connection after disconnect.
|
||||||
|
void OpenGamepad();
|
||||||
|
void CloseGamepad();
|
||||||
|
void ProcessGamepadInput();
|
||||||
|
|
||||||
Dimension GetScreenSize() const override;
|
Dimension GetScreenSize() const override;
|
||||||
float GetDPIScale() override;
|
float GetDPIScale() override;
|
||||||
void InputLoop() override;
|
void InputLoop() override;
|
||||||
|
|
|
@ -3572,3 +3572,81 @@ void PickerWindowBase::Close([[maybe_unused]] int data)
|
||||||
ResetObjectToPlace();
|
ResetObjectToPlace();
|
||||||
this->Window::Close();
|
this->Window::Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process gamepad analog stick input for viewport scrolling.
|
||||||
|
* This is a common function that can be used by any video driver that supports gamepads.
|
||||||
|
* @param stick_x Raw analog stick X value (typically -32768 to 32767 range)
|
||||||
|
* @param stick_y Raw analog stick Y value (typically -32768 to 32767 range)
|
||||||
|
* @param max_axis_value Maximum value for the analog stick axes (e.g., 32767 for SDL2)
|
||||||
|
*/
|
||||||
|
void HandleGamepadScrolling(int stick_x, int stick_y, int max_axis_value)
|
||||||
|
{
|
||||||
|
/* Skip if gamepad stick selection is disabled */
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_DISABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply deadzone (convert percentage to axis range) */
|
||||||
|
const int deadzone = (_settings_client.gui.gamepad_deadzone * max_axis_value) / 100;
|
||||||
|
|
||||||
|
if (abs(stick_x) < deadzone) stick_x = 0;
|
||||||
|
if (abs(stick_y) < deadzone) stick_y = 0;
|
||||||
|
|
||||||
|
/* Skip if no movement after deadzone */
|
||||||
|
if (stick_x == 0 && stick_y == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate scroll delta with sensitivity */
|
||||||
|
float sensitivity = _settings_client.gui.gamepad_sensitivity / 10.0f;
|
||||||
|
int delta_x = (int)(stick_x * sensitivity / (max_axis_value / 16)); // Scale down from axis range
|
||||||
|
int delta_y = (int)(stick_y * sensitivity / (max_axis_value / 16));
|
||||||
|
|
||||||
|
/* Apply axis inversion */
|
||||||
|
if (_settings_client.gui.gamepad_invert_x) delta_x = -delta_x;
|
||||||
|
if (_settings_client.gui.gamepad_invert_y) delta_y = -delta_y;
|
||||||
|
|
||||||
|
/* Skip if deltas are too small */
|
||||||
|
if (abs(delta_x) < 1 && abs(delta_y) < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply scrolling based on cursor position */
|
||||||
|
if (_game_mode != GM_MENU && _game_mode != GM_BOOTSTRAP) {
|
||||||
|
Window *target_window = nullptr;
|
||||||
|
|
||||||
|
/* Check if cursor is over a window with a viewport */
|
||||||
|
Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
|
||||||
|
if (w != nullptr && w->viewport != nullptr) {
|
||||||
|
/* Check if cursor is actually over the viewport area within the window */
|
||||||
|
Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
|
||||||
|
if (pt.x >= w->viewport->left - w->left &&
|
||||||
|
pt.x < w->viewport->left - w->left + w->viewport->width &&
|
||||||
|
pt.y >= w->viewport->top - w->top &&
|
||||||
|
pt.y < w->viewport->top - w->top + w->viewport->height) {
|
||||||
|
target_window = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If no viewport under cursor, use main window */
|
||||||
|
if (target_window == nullptr) {
|
||||||
|
target_window = GetMainWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply scrolling to the target viewport */
|
||||||
|
if (target_window != nullptr && target_window->viewport != nullptr) {
|
||||||
|
/* Check if the viewport is following a vehicle (similar to mouse scroll behavior) */
|
||||||
|
if (target_window == GetMainWindow() && target_window->viewport->follow_vehicle != VehicleID::Invalid()) {
|
||||||
|
/* If following a vehicle, center on it and stop following (like mouse scroll) */
|
||||||
|
const Vehicle *veh = Vehicle::Get(target_window->viewport->follow_vehicle);
|
||||||
|
ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
|
||||||
|
return; // Don't apply gamepad scroll, just like mouse scroll returns ES_NOT_HANDLED
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply the scroll using the same method as keyboard scrolling */
|
||||||
|
target_window->viewport->dest_scrollpos_x += ScaleByZoom(delta_x, target_window->viewport->zoom);
|
||||||
|
target_window->viewport->dest_scrollpos_y += ScaleByZoom(delta_y, target_window->viewport->zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue