diff --git a/distribution/changelog.txt b/distribution/changelog.txt index e219d89b3f..3598127006 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,5 +1,6 @@ 0.2.0+ (in development) ------------------------------------------------------------------------ +- Feature: [#5993] Ride window prices can now be set via text input. - Feature: [#6998] Guests now wait for passing vehicles before crossing railway tracks. - Fix: [#7628] Always-researched items can be modified in the inventory list. - Fix: [#7643] No Money scenarios with funding set to zero. diff --git a/src/openrct2-ui/windows/Cheats.cpp b/src/openrct2-ui/windows/Cheats.cpp index d626383c66..994301f7cf 100644 --- a/src/openrct2-ui/windows/Cheats.cpp +++ b/src/openrct2-ui/windows/Cheats.cpp @@ -806,7 +806,7 @@ static void window_cheats_money_mouseup(rct_window *w, rct_widgetindex widgetInd game_do_command(0, GAME_COMMAND_FLAG_APPLY, CHEAT_NOMONEY, gParkFlags & PARK_FLAGS_NO_MONEY ? 0 : 1, GAME_COMMAND_CHEAT, 0, 0); break; case WIDX_MONEY_SPINNER: - money_to_string(_moneySpinnerValue, _moneySpinnerText, MONEY_STRING_MAXLENGTH); + money_to_string(_moneySpinnerValue, _moneySpinnerText, MONEY_STRING_MAXLENGTH, false); window_text_input_raw_open(w, WIDX_MONEY_SPINNER, STR_ENTER_NEW_VALUE, STR_ENTER_NEW_VALUE, _moneySpinnerText, MONEY_STRING_MAXLENGTH); break; case WIDX_SET_MONEY: diff --git a/src/openrct2-ui/windows/Ride.cpp b/src/openrct2-ui/windows/Ride.cpp index 4836e5127a..0aa7f5932f 100644 --- a/src/openrct2-ui/windows/Ride.cpp +++ b/src/openrct2-ui/windows/Ride.cpp @@ -465,9 +465,11 @@ static constexpr const uint64 window_ride_page_enabled_widgets[] = { (1ULL << WIDX_GRAPH_VERTICAL) | (1ULL << WIDX_GRAPH_LATERAL), MAIN_RIDE_ENABLED_WIDGETS | + (1ULL << WIDX_PRIMARY_PRICE) | (1ULL << WIDX_PRIMARY_PRICE_INCREASE) | (1ULL << WIDX_PRIMARY_PRICE_DECREASE) | (1ULL << WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK) | + (1ULL << WIDX_SECONDARY_PRICE) | (1ULL << WIDX_SECONDARY_PRICE_INCREASE) | (1ULL << WIDX_SECONDARY_PRICE_DECREASE) | (1ULL << WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK), @@ -591,6 +593,7 @@ static void window_ride_income_mouseup(rct_window *w, rct_widgetindex widgetInde static void window_ride_income_resize(rct_window *w); static void window_ride_income_mousedown(rct_window *w, rct_widgetindex widgetIndex, rct_widget *widget); static void window_ride_income_update(rct_window *w); +static void window_ride_income_textinput(rct_window *w, rct_widgetindex widgetIndex, char *text); static void window_ride_income_invalidate(rct_window *w); static void window_ride_income_paint(rct_window *w, rct_drawpixelinfo *dpi); static bool window_ride_income_can_modify_primary_price(rct_window* w); @@ -880,7 +883,7 @@ static rct_window_event_list window_ride_income_events = { nullptr, nullptr, nullptr, - nullptr, + window_ride_income_textinput, nullptr, nullptr, nullptr, @@ -5840,6 +5843,8 @@ static void window_ride_graphs_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi #pragma region Income +static utf8 _moneyInputText[MONEY_STRING_MAXLENGTH]; + static void update_same_price_throughout_flags(uint32 shop_item) { uint32 newFlags; @@ -5922,6 +5927,11 @@ static void window_ride_income_toggle_secondary_price(rct_window *w) game_do_command(0, 1, 0, (1 << 8) | w->number, GAME_COMMAND_SET_RIDE_PRICE, price, 0); } +static void window_ride_income_set_primary_price(rct_window *w, money16 price) +{ + game_do_command(0, GAME_COMMAND_FLAG_APPLY, 0, w->number, GAME_COMMAND_SET_RIDE_PRICE, price, 0); +} + /** * * rct2: 0x006AE1E4 @@ -5936,7 +5946,7 @@ static void window_ride_income_increase_primary_price(rct_window *w) if (price < MONEY(20, 00)) price++; - game_do_command(0, 1, 0, w->number, GAME_COMMAND_SET_RIDE_PRICE, price, 0); + window_ride_income_set_primary_price(w, price); } /** @@ -5953,7 +5963,19 @@ static void window_ride_income_decrease_primary_price(rct_window *w) if (price > MONEY(0, 00)) price--; - game_do_command(0, 1, 0, w->number, GAME_COMMAND_SET_RIDE_PRICE, price, 0); + window_ride_income_set_primary_price(w, price); +} + +static money16 window_ride_income_get_secondary_price(rct_window *w) +{ + Ride *ride = get_ride(w->number); + money16 price = ride->price_secondary; + return price; +} + +static void window_ride_income_set_secondary_price(rct_window *w, money16 price) +{ + game_do_command(0, GAME_COMMAND_FLAG_APPLY, 0, (w->number & 0x00FF) | 0x0100, GAME_COMMAND_SET_RIDE_PRICE, price, 0); } static bool window_ride_income_can_modify_primary_price(rct_window* w) @@ -5973,15 +5995,12 @@ static bool window_ride_income_can_modify_primary_price(rct_window* w) */ static void window_ride_income_increase_secondary_price(rct_window *w) { - Ride *ride; + money16 price = window_ride_income_get_secondary_price(w); - ride = get_ride(w->number); - - money16 price = ride->price_secondary; if (price < MONEY(20, 00)) price++; - game_do_command(0, 1, 0, (w->number & 0x00FF) | 0x0100, GAME_COMMAND_SET_RIDE_PRICE, price, 0); + window_ride_income_set_secondary_price(w, price); } /** @@ -5990,15 +6009,12 @@ static void window_ride_income_increase_secondary_price(rct_window *w) */ static void window_ride_income_decrease_secondary_price(rct_window *w) { - Ride *ride; - - ride = get_ride(w->number); - - money16 price = ride->price_secondary; + money16 price = window_ride_income_get_secondary_price(w); + if (price > MONEY(0, 00)) price--; - game_do_command(0, 1, 0, (w->number & 0x00FF) | 0x0100, GAME_COMMAND_SET_RIDE_PRICE, price, 0); + window_ride_income_set_secondary_price(w, price); } /** @@ -6007,7 +6023,8 @@ static void window_ride_income_decrease_secondary_price(rct_window *w) */ static void window_ride_income_mouseup(rct_window *w, rct_widgetindex widgetIndex) { - switch (widgetIndex) { + switch (widgetIndex) + { case WIDX_CLOSE: window_close(w); break; @@ -6023,9 +6040,26 @@ static void window_ride_income_mouseup(rct_window *w, rct_widgetindex widgetInde case WIDX_TAB_10: window_ride_set_page(w, widgetIndex - WIDX_TAB_1); break; + case WIDX_PRIMARY_PRICE: + { + if (!window_ride_income_can_modify_primary_price(w)) + return; + + Ride* ride = get_ride(w->number); + + money_to_string((money32)ride->price, _moneyInputText, MONEY_STRING_MAXLENGTH, true); + window_text_input_raw_open(w, WIDX_PRIMARY_PRICE, STR_ENTER_NEW_VALUE, STR_ENTER_NEW_VALUE, _moneyInputText, MONEY_STRING_MAXLENGTH); + break; + } case WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK: window_ride_income_toggle_primary_price(w); break; + case WIDX_SECONDARY_PRICE:{ + money32 price32 = (money32)window_ride_income_get_secondary_price(w); + + money_to_string(price32, _moneyInputText, MONEY_STRING_MAXLENGTH, true); + window_text_input_raw_open(w, WIDX_SECONDARY_PRICE, STR_ENTER_NEW_VALUE, STR_ENTER_NEW_VALUE, _moneyInputText, MONEY_STRING_MAXLENGTH); + }break; case WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK: window_ride_income_toggle_secondary_price(w); break; @@ -6082,6 +6116,30 @@ static void window_ride_income_update(rct_window *w) } } +static void window_ride_income_textinput(rct_window* w, rct_widgetindex widgetIndex, char* text) +{ + if ((widgetIndex != WIDX_PRIMARY_PRICE && widgetIndex != WIDX_SECONDARY_PRICE) || text == nullptr) + return; + + money32 price = string_to_money(text); + if (price == MONEY32_UNDEFINED) + { + return; + } + + price = Math::Clamp(MONEY(0, 00), price, MONEY(20, 00)); + money16 price16 = (money16)price; + + if (widgetIndex == WIDX_PRIMARY_PRICE) + { + window_ride_income_set_primary_price(w, price16); + } + else + { + window_ride_income_set_secondary_price(w, price16); + } +} + /** * * rct2: 0x006ADAA3 diff --git a/src/openrct2/localisation/Localisation.cpp b/src/openrct2/localisation/Localisation.cpp index 23d387386d..5f9b292bda 100644 --- a/src/openrct2/localisation/Localisation.cpp +++ b/src/openrct2/localisation/Localisation.cpp @@ -1233,6 +1233,7 @@ void format_string_to_upper(utf8 *dest, size_t size, rct_string_id format, void money32 string_to_money(const char* string_to_monetise) { const char* decimal_char = language_get_string(STR_LOCALE_DECIMAL_POINT); + const currency_descriptor* currencyDesc = &CurrencyDescriptors[gConfigGeneral.currency_format]; char processedString[128] = {}; Guard::Assert(strlen(string_to_monetise) < sizeof(processedString)); @@ -1256,6 +1257,11 @@ money32 string_to_money(const char* string_to_monetise) return MONEY32_UNDEFINED; else hasDecSep = true; + + // Replace localised decimal separator with an English one. + *dst_ptr++ = '.'; + src_ptr++; + continue; } else if (*src_ptr == '-') { @@ -1301,34 +1307,14 @@ money32 string_to_money(const char* string_to_monetise) processedString[0] = '0'; } - int number = 0, decimal = 0; - if (strstr(processedString, decimal_char) == nullptr) - { - // If decimal char does not exist, no tokenising is needed. - number = atoi(processedString); - } - else - { - char *numberText = strtok(processedString, decimal_char); - char *decimalText = strtok(nullptr, decimal_char); + auto number = std::stod(processedString, nullptr); + number /= (currencyDesc->rate / 10.0); + auto whole = static_cast(number); + auto fraction = static_cast((number - whole) * 100); - if (numberText != nullptr) - number = atoi(numberText); - if (decimalText != nullptr) - decimal = atoi(decimalText); - - // The second parameter in MONEY must be two digits in length, while the game only ever uses - // the first of the two digits. - // Convert invalid numbers, such as ".6", ".234", ".05", to ".60", ".20", ".00" (respectively) - while (decimal > 10) - decimal /= 10; - if (decimal < 10) - decimal *= 10; - } - - money32 result = MONEY(number, decimal); + money32 result = MONEY(whole, fraction); // Check if MONEY resulted in overflow - if ((number > 0 && result < 0) || result / 10 < number) + if ((whole > 0 && result < 0) || result / 10 < whole) { result = INT_MAX; } @@ -1336,26 +1322,50 @@ money32 string_to_money(const char* string_to_monetise) return result; } -void money_to_string(money32 amount, char * buffer_to_put_value_to, size_t buffer_len) +/** + * + * @param amount The amount in tens of pounds, e.g. 123 = £ 12.30 + * @param buffer_to_put_value_to Output parameter. + * @param buffer_len Length of the buffer. + * @param forceDecimals Show decimals, even if the amount does not have them. Will be ignored if the current exchange + * rate is too big to have decimals. + */ +void money_to_string(money32 amount, char * buffer_to_put_value_to, size_t buffer_len, bool forceDecimals) { - if (amount == MONEY32_UNDEFINED) { + if (amount == MONEY32_UNDEFINED) + { snprintf(buffer_to_put_value_to, buffer_len, "0"); return; } + + const currency_descriptor *currencyDesc = &CurrencyDescriptors[gConfigGeneral.currency_format]; + int sign = amount >= 0 ? 1 : -1; - int a = abs(amount); - if (a / 10 > 0 && a % 10 > 0) { // If whole and decimal exist + int a = abs(amount) * currencyDesc->rate; + + bool amountIsInteger = (a / 100 > 0) && (a % 100 == 0); + + // If whole and decimal exist + if ((a / 100 > 0 && a % 100 > 0) || (amountIsInteger && forceDecimals && currencyDesc->rate < 100)) + { const char* decimal_char = language_get_string(STR_LOCALE_DECIMAL_POINT); - snprintf(buffer_to_put_value_to, buffer_len, "%d%s%d0", (a / 10) * sign, decimal_char, a % 10); + auto decimalPart = a % 100; + auto precedingZero = (decimalPart < 10) ? "0" : ""; + snprintf(buffer_to_put_value_to, buffer_len, "%d%s%s%d", (a / 100) * sign, decimal_char, precedingZero, decimalPart); } - else if (a / 10 > 0 && a % 10 == 0) { // If whole exists, but not decimal - snprintf(buffer_to_put_value_to, buffer_len, "%d", (a / 10) * sign); + // If whole exists, but not decimal + else if (amountIsInteger) + { + snprintf(buffer_to_put_value_to, buffer_len, "%d", (a / 100) * sign); } - else if (a / 10 == 0 && a % 10 > 0) { // If decimal exists, but not whole + // If decimal exists, but not whole + else if (a / 100 == 0 && a % 100 > 0) + { const char* decimal_char = language_get_string(STR_LOCALE_DECIMAL_POINT); - snprintf(buffer_to_put_value_to, buffer_len, "%s0%s%d0", sign < 0 ? "-" : "", decimal_char, a % 10); + snprintf(buffer_to_put_value_to, buffer_len, "%s0%s%d", sign < 0 ? "-" : "", decimal_char, a % 100); } - else { + else + { snprintf(buffer_to_put_value_to, buffer_len, "0"); } } diff --git a/src/openrct2/localisation/Localisation.h b/src/openrct2/localisation/Localisation.h index 70eb7adb48..63490b9a1e 100644 --- a/src/openrct2/localisation/Localisation.h +++ b/src/openrct2/localisation/Localisation.h @@ -43,7 +43,7 @@ sint32 get_string_length(const utf8 *text); // The maximum number of characters allowed for string/money conversions (anything above will risk integer overflow issues) #define MONEY_STRING_MAXLENGTH 14 money32 string_to_money(const char* string_to_monetise); -void money_to_string(money32 amount, char * buffer_to_put_value_to, size_t buffer_len); +void money_to_string(money32 amount, char * buffer_to_put_value_to, size_t buffer_len, bool forceDecimals); void user_string_clear_all(); rct_string_id user_string_allocate(sint32 base, const utf8 *text);