diff --git a/modfiles/locale/en/config.cfg b/modfiles/locale/en/config.cfg index 0a8a1738d..34f98b79c 100644 --- a/modfiles/locale/en/config.cfg +++ b/modfiles/locale/en/config.cfg @@ -387,6 +387,7 @@ warning_no_other_machine_choice=No other machine can craft this recipe warning_no_prioritizing_single_product=Recipes with a single relevant product can’t be prioritized hint_tutorial=If you are new to Factory Planner, consider taking a look the the tutorial ⟶ hint_byproducts_removed=Disabling the matrix solver removes all byproduct recipes +hint_fluid_belt_barrel=assuming 50 fluid/barrel # Units prefix_kilo=k diff --git a/modfiles/ui/elements/production_handler.lua b/modfiles/ui/elements/production_handler.lua index 4102e9008..921ea02fa 100644 --- a/modfiles/ui/elements/production_handler.lua +++ b/modfiles/ui/elements/production_handler.lua @@ -127,7 +127,7 @@ local function compile_machine_chooser_buttons(player, line, applicable_prototyp local machine_count = calculation.util.determine_machine_count(crafts_per_tick, line.uncapped_production_ratio, timescale, machine_proto.launch_sequence_time) - local button_number = (round_button_numbers) and math.ceil(machine_count) or machine_count + local button_number = ui_util.format_number_ceil((round_button_numbers) and math.ceil(machine_count) or machine_count) -- Have to do this stupid crap because localisation plurals only work on integers local formatted_number = ui_util.format_number(machine_count, 4) diff --git a/modfiles/ui/elements/production_table.lua b/modfiles/ui/elements/production_table.lua index 635daa9ae..51fae4dd5 100644 --- a/modfiles/ui/elements/production_table.lua +++ b/modfiles/ui/elements/production_table.lua @@ -117,13 +117,13 @@ function builders.machine(line, parent_flow, metadata) enabled=false, number=machine_count, tooltip=tooltip} else -- Machine - machine_count = ui_util.format_number(machine_count, 4) - local tooltip_count = machine_count - if machine_count == "0" and line.production_ratio > 0 then + local tooltip_count = ui_util.format_number(machine_count, 4) + local icon_count = ui_util.format_number_ceil(machine_count) + if tooltip_count == "0" and line.production_ratio > 0 then tooltip_count = "<0.0001" - machine_count = "0.01" -- shows up as 0.0 on the button + icon_count = 0 -- shows up as 0.0 on the button end - if metadata.round_button_numbers then machine_count = math.ceil(machine_count) end + if metadata.round_button_numbers then icon_count = math.ceil(icon_count) end local style, indication, machine_limit = "flib_slot_button_default_small", "", line.machine.limit if not metadata.matrix_solver_active and machine_limit ~= nil then @@ -147,13 +147,13 @@ function builders.machine(line, parent_flow, metadata) effects_tooltip = data_util.format_module_effects(module_effects, 1, true) end - local plural_parameter = (machine_count == "1") and 1 or 2 + local plural_parameter = (tooltip_count == "1") and 1 or 2 local number_line = {"fp.newline", {"fp.two_word_title", tooltip_count, {"fp.pl_machine", plural_parameter}}} local tutorial_tooltip = metadata.machine_tutorial_tooltip local tooltip = {"", machine_proto.localised_name, number_line, indication, effects_tooltip, tutorial_tooltip} parent_flow.add{type="sprite-button", tags={mod="fp", on_gui_click="act_on_line_machine", line_id=line.id}, - style=style, sprite=machine_proto.sprite, number=machine_count, tooltip=tooltip, + style=style, sprite=machine_proto.sprite, number=icon_count, tooltip=tooltip, mouse_button_filter={"left-and-right"}} -- Modules - can only be added to machines that have any module slots diff --git a/modfiles/ui/elements/view_state.lua b/modfiles/ui/elements/view_state.lua index 8d70c0840..aa86fb7ba 100644 --- a/modfiles/ui/elements/view_state.lua +++ b/modfiles/ui/elements/view_state.lua @@ -4,32 +4,37 @@ view_state = {} -- ** LOCAL UTIL ** local processors = {} -- individual functions for each kind of view state function processors.items_per_timescale(metadata, raw_amount, item_proto, _) - local number = ui_util.format_number(raw_amount, metadata.formatting_precision) - local tooltip = nil if metadata.include_tooltip then - local plural_parameter = (number == "1") and 1 or 2 + local tooltip_number = ui_util.format_number(raw_amount, metadata.formatting_precision) + local plural_parameter = (tooltip_number == "1") and 1 or 2 local type_string = (item_proto.type == "fluid") and {"fp.l_fluid"} or {"fp.pl_item", plural_parameter} - tooltip = {"fp.two_word_title", number, {"fp.per_title", type_string, metadata.timescale_string}} + tooltip = {"fp.two_word_title", tooltip_number, {"fp.per_title", type_string, metadata.timescale_string}} end - return number, tooltip + local icon_number = ui_util.format_number_ceil(raw_amount) + return icon_number, tooltip end function processors.belts_or_lanes(metadata, raw_amount, item_proto, _) - if item_proto.type == "fluid" then return nil, nil end - - local raw_number = raw_amount * metadata.throughput_multiplier * metadata.timescale_inverse - local number = ui_util.format_number(raw_number, metadata.formatting_precision) + local raw_number = raw_amount * metadata.throughput_multiplier * metadata.timescale_inverse / (item_proto.type == "fluid" and 50 or 1) local tooltip = nil if metadata.include_tooltip then - local plural_parameter = (number == "1") and 1 or 2 - tooltip = {"fp.two_word_title", number, {"fp.pl_" .. metadata.belt_or_lane, plural_parameter}} + local tooltip_number = ui_util.format_number(raw_number, metadata.formatting_precision) + local plural_parameter = (tooltip_number == "1") and 1 or 2 + + if item_proto.type == "fluid" then + -- 3.5 belts (assuming 50 fluid/barrel) + tooltip = {"fp.annotated_title", {"fp.two_word_title", tooltip_number, {"fp.pl_" .. metadata.belt_or_lane, plural_parameter}}, {"fp.hint_fluid_belt_barrel"}} + else + -- 3.5 belts + tooltip = {"fp.two_word_title", tooltip_number, {"fp.pl_" .. metadata.belt_or_lane, plural_parameter}} + end end - local return_number = (metadata.round_button_numbers) and math.ceil(raw_number) or number - return return_number, tooltip + local icon_number = ui_util.format_number_ceil((metadata.round_button_numbers) and math.ceil(raw_number) or raw_number) + return icon_number, tooltip end function processors.wagons_per_timescale(metadata, raw_amount, item_proto, _) @@ -38,33 +43,35 @@ function processors.wagons_per_timescale(metadata, raw_amount, item_proto, _) local wagon_capacity = (item_proto.type == "fluid") and metadata.fluid_wagon_capacity or metadata.cargo_wagon_capactiy * item_proto.stack_size local wagon_count = raw_amount / wagon_capacity - local number = ui_util.format_number(wagon_count, metadata.formatting_precision) local tooltip = nil if metadata.include_tooltip then - local plural_parameter = (number == "1") and 1 or 2 - tooltip = {"fp.two_word_title", number, {"fp.per_title", {"fp.pl_wagon", plural_parameter}, + local tooltip_number = ui_util.format_number(wagon_count, metadata.formatting_precision) + local plural_parameter = (tooltip_number == "1") and 1 or 2 + tooltip = {"fp.two_word_title", tooltip_number, {"fp.per_title", {"fp.pl_wagon", plural_parameter}, metadata.timescale_string}} end - return number, tooltip + local icon_number = ui_util.format_number_ceil(wagon_count) + return icon_number, tooltip end function processors.items_per_second_per_machine(metadata, raw_amount, item_proto, machine_count) local raw_number = raw_amount * metadata.timescale_inverse / (math.ceil(machine_count or 1)) - local number = ui_util.format_number(raw_number, metadata.formatting_precision) local tooltip = nil if metadata.include_tooltip then - local plural_parameter = (number == "1") and 1 or 2 + local tooltip_number = ui_util.format_number(raw_number, metadata.formatting_precision) + local plural_parameter = (tooltip_number == "1") and 1 or 2 local type_string = (item_proto.type == "fluid") and {"fp.l_fluid"} or {"fp.pl_item", plural_parameter} local item_per_second = {"fp.per_title", type_string, {"fp.second"}} -- If machine_count is nil, this shouldn't show /machine local per_machine = (machine_count ~= nil) and {"fp.per_title", "", {"fp.pl_machine", 1}} or "" - tooltip = {"fp.two_word_title", number, {"", item_per_second, per_machine}} + tooltip = {"fp.two_word_title", tooltip_number, {"", item_per_second, per_machine}} end - return number, tooltip + local icon_number = ui_util.format_number_ceil(raw_number) + return icon_number, tooltip end diff --git a/modfiles/ui/ui_util.lua b/modfiles/ui/ui_util.lua index 0244a2dea..4d8ec6b4d 100755 --- a/modfiles/ui/ui_util.lua +++ b/modfiles/ui/ui_util.lua @@ -109,6 +109,102 @@ function ui_util.format_number(number, precision) end end +-- Formats given number to fixed number of significant digits based on icon size, using ceil rounding +function ui_util.format_number_ceil(number) + if number == nil then return nil end + + -- Set very small numbers to 0 + if number < 0.0001 then + return 0 + end + + -- Figure out how many decimals we have + local base_decimals = math.floor(math.log10(number)) + -- Example 1 for the sake of documentation: pretend our number is 23456.78912 + -- base_decimals is math.floor(4.3702) = 4 + -- Example 2 for the sake of documentation: pretend our number is 999.9 + -- base_decimals is math.floor(2.99995656838) = 2 + + -- Visual example of the mapping we intend: + -- decimals = -2 -> .0234567891 -> 0.1 (we just round up to 0.1) + -- decimals = -1 -> .2345678912 -> 0.2 (one decimal of data) + -- decimals = 0 -> 2.345678912 -> 2.4 (two decimals of data) + -- decimals = 1 -> 23.45678912 -> 23.5 (three decimals of data) + -- decimals = 2 -> 234.5678912 -> 235 (three decimals of data) + -- decimals = 3 -> 2345.678912 -> 2.4k (two decimals of data) + -- decimals = 4 -> 23456.78912 -> 24k (two decimals of data) + -- decimals = 5 -> 234567.8912 -> 235k (three decimals of data) + -- decimals = 6 -> 2345678.912 -> 2.4M (two decimals of data) + -- decimals = 7 -> 23456789.12 -> 24M (two decimals of data) + -- decimals = 8 -> 234567891.2 -> 235M (three decimals of data) + -- tl;dr: hardcoded result if it's <-1, one decimal if it's <0, three decimals if it's in [1, 2) or if (it%3) is in [2, 3), otherwise two + local desired_decimals = 2 + if base_decimals < -1 then + return "0.1" + elseif base_decimals < 0 then + desired_decimals = 1 + elseif base_decimals == 1 or base_decimals % 3 == 2 then + desired_decimals = 3 + end + -- Example 1: desired_decimals is 2 + -- Example 2: desired_decimals is 3 + + -- Take the number, shove it down to our target, ceil it, and bring it back up + local shift = (10 ^ (math.floor(base_decimals) - desired_decimals + 1)) + local shifted_number = number / shift + -- Example 1: shifted_number is 23456.78912 / (10 ^ (4 - 2 + 1)) = 23456.78912 / (10 ^ 3) = 23.45678912 + -- Example 2: shifted_number is 999.9 / (10 ^ (2 - 3 + 1)) = 999.9 / (10 ^ 0) = 999.9 + + -- Add a slight magic number adjustment to compensate for floating-point inaccuracy + local ceiled_number = math.ceil(shifted_number - 0.00001) + -- Example 1: ceiled_number is 24 + -- Example 2: ceiled_number is 1000 + + -- Uhoh, we have a problem! In `math.ceil()`, example 2 has now gained a digit. + -- But this is actually OK! It's gained a digit, but all digits aside from the first one are 0's. + -- Factorio's formatting code is going to just do the right thing here. + + local returned_number = ceiled_number * shift + -- Example 1: returned_number is 24000 + -- Example 2: returned_number is 1000 + + -- This is just to add a decimal at the end if it's not an actual perfect round (plus or minus an epsilon value) + if math.abs(number - returned_number) > 0.00001 then + returned_number = returned_number + 0.00001 + end + + return returned_number +end + +local function format_number_tests() + local errlist = "" + local function check(input, expected) + local result = ui_util.format_number_ceil(input) + if math.abs(result - expected) > 0.000001 then -- less than the final adjustment value + errlist = errlist .. ("%f -> %f, should be %f\n"):format(input, result, expected) + end + end + + check(0, 0) + check(0.1, 0.1) + check(0.08, 0.1) -- doesn't get the added decimal addition because it drops out much earlier + check(0.002, 0.1) -- doesn't get the added decimal addition because it drops out much earlier + check(23456.78912, 24000.00001) + check(1000, 1000) + check(3, 3) + check(2.99999, 3.00001) + check(3.5, 3.5) + check(3.56, 3.60001) + check(0.123, 0.20001) + check(999, 999) + check(999.9, 1000.00001) + + if errlist ~= "" then + error(errlist) + end +end +format_number_tests() + -- Returns string representing the given power function ui_util.format_SI_value(value, unit, precision) local prefixes = {"", "kilo", "mega", "giga", "tera", "peta", "exa", "zetta", "yotta"}