388 lines
13 KiB
Lua

local mod_gui = require('mod-gui')
local get_mod_button_flow = mod_gui.get_button_flow
local mod_button_style = mod_gui.button_style
local migrations = require('migrations')
local calc = require('calc')
local floor = math.floor
local lib = require('lib')
local get_stack_size = lib.get_stack_size
local strlen = string.len
local format = string.format
local draw_text = rendering.draw_text
local destroy_text = rendering.destroy
local text_color = {1, 1, 1}
local text_offset = {0.5, -0.5}
local stopwatch = require('stopwatch')
local stopwatch_tick = stopwatch.tick
local stopwatch_timeout = 3600 -- in ticks
-- COMPATIBILITY
local name_blacklist = nil
if not script.active_mods['miniloader'] then
name_blacklist = {} -- disable blacklist
end
local function populate_name_blacklist()
name_blacklist = {}
local inserters = game.get_filtered_entity_prototypes{{filter = 'type', type = 'inserter'}}
for name in pairs(inserters) do
if string.find(name, 'miniloader', nil, true) then
name_blacklist[name] = true
end
end
end
-- LOGIC
local function vector(from, to)
return {to.x - from.x, to.y - from.y}
end
local function number_to_string_with_precision(number, precision)
local result = tostring(number)
if precision > 0 then
local rounded = format(format('%%.%if', precision), number)
if strlen(rounded) < strlen(result) then
result = rounded
end
end
return result
end
local function get_throughput_info(inserter, precision)
local inserter_position = inserter.position
local prototype = (inserter.type == 'entity-ghost'
and inserter.ghost_prototype or inserter.prototype)
local pickup_position = inserter.pickup_position
local pickup_target = inserter.pickup_target
if not pickup_target then
pickup_target = inserter.surface.find_entities_filtered{
position = pickup_position, limit = 1}[1]
end
local pickup_belt_speed
if pickup_target then
if pickup_target.type == 'entity-ghost' then
pickup_belt_speed = pickup_target.ghost_prototype.belt_speed
else
pickup_belt_speed = pickup_target.prototype.belt_speed
end
end
local drop_position = inserter.drop_position
local drop_target = inserter.drop_target
if not drop_target then
drop_target = inserter.surface.find_entities_filtered{
position = drop_position, limit = 1}[1]
end
local drop_belt_speed
if drop_target then
if drop_target.type == 'entity-ghost' then
drop_belt_speed = drop_target.ghost_prototype.belt_speed
else
drop_belt_speed = drop_target.prototype.belt_speed
end
end
local value = calc(
prototype.inserter_rotation_speed,
prototype.inserter_extension_speed,
vector(inserter_position, pickup_position),
vector(inserter_position, drop_position),
get_stack_size(inserter, prototype),
pickup_belt_speed,
drop_belt_speed
)
return {'', number_to_string_with_precision(value, precision), {'per-second-suffix'}}
end
-- GUI
local function update_toggle_button_state(toggle_button, new_state)
if new_state then
toggle_button.style = 'inserter_throughput_pressed_button'
else
toggle_button.style = mod_button_style
end
end
local function init_toggle_button(player)
local top_gui = get_mod_button_flow(player)
local toggle_element = top_gui['inserter-throughput-toggle']
if not toggle_element then
toggle_element = top_gui.add{
type = 'sprite-button',
name = 'inserter-throughput-toggle',
tooltip = {'inserter-throughput.toggle-button-tooltip'},
sprite = 'inserter-throughput-toggle-button'
}
end
local player_settings = player.mod_settings
toggle_element.visible = player_settings['inserter-throughput-show-toggle'].value
update_toggle_button_state(toggle_element, player_settings['inserter-throughput-enabled'].value)
end
local function on_entity_selected(event)
-- event.player_index current player
-- event.last_entity previous entity
local player_index = event.player_index
local player_settings = settings.get_player_settings(player_index)
if not player_settings['inserter-throughput-enabled'].value then
return
end
local current_entity = game.get_player(player_index).selected
local global_player_data = global.player_data
local data = global_player_data[player_index]
local text_id = data and data.text_id
if current_entity then
local entity_type = current_entity.type
local entity_name = nil
if entity_type == 'entity-ghost' then
entity_type = current_entity.ghost_type
entity_name = current_entity.ghost_name
else
entity_name = current_entity.name
end
if not name_blacklist then
populate_name_blacklist()
end
if entity_type ~= 'inserter' or name_blacklist[entity_name] then
current_entity = nil
end
end
if current_entity then -- a valid inserter by now
if not data then
data = {}
global_player_data[player_index] = data
elseif data.text_id then
destroy_text(data.text_id)
end
local precision = player_settings['inserter-throughput-rounding-precision'].value
data.text_id = draw_text{
text = get_throughput_info(current_entity, precision),
surface = current_entity.surface,
target = current_entity,
target_offset = text_offset,
color = text_color,
players = {player_index},
scale_with_zoom = true,
vertical_alignment = 'baseline',
}
elseif text_id then
destroy_text(text_id)
data.text_id = nil
end
end
local function on_setting_changed(event)
-- event.player_index player whose setting has changed, if applicable (API seems to be wrong)
-- event.setting name of the setting, as seen in the prototype
-- event.setting_type type of the setting, as seen in the prototype
local setting_name = event.setting
local player_index = event.player_index
if setting_name == 'inserter-throughput-show-toggle' then
local button = get_mod_button_flow(game.get_player(player_index))['inserter-throughput-toggle']
if button then
local new_state = settings.get_player_settings(player_index)[setting_name].value
button.visible = new_state
else
init_toggle_button(game.get_player(player_index))
end
elseif setting_name == 'inserter-throughput-enabled' then
local button = get_mod_button_flow(game.get_player(player_index))['inserter-throughput-toggle']
if button then
local new_state = settings.get_player_settings(player_index)[setting_name].value
update_toggle_button_state(button, new_state)
if not new_state then -- remove the tooltip on disable, if any
local data = global.player_data[player_index]
local text_id = data and data.text_id
if text_id then
destroy_text(text_id)
data.text_id = nil
end
end
else
init_toggle_button(game.get_player(player_index))
end
end
end
local function on_toggle(event)
-- event.player_index player who triggered the custom input (unlisted in API)
local player_settings = settings.get_player_settings(event.player_index)
local enabled_setting = player_settings['inserter-throughput-enabled']
enabled_setting.value = not enabled_setting.value
-- this will trigger on_setting_changed above
player_settings['inserter-throughput-enabled'] = enabled_setting
end
local function on_gui_clicked(event)
-- event.element clicked element
-- event.player_index player who clicked
-- event.button mouse button
-- event.alt state of Alt key
-- event.control state of Ctrl key
-- event.shift state of Shift key
if event.element.name == 'inserter-throughput-toggle' then
on_toggle(event)
end
end
-- COMMANDS
local function stopwatch_command(event)
-- event.player_index player who ran the command, if any
-- event.parameter the rest of the command string
if not event.player_index then
-- no player implies no ability to select the inserter
-- so responding with the help text will inform them of this fact
localised_print{'inserter-throughput.stopwatch-help'}
return
end
local player = game.get_player(event.player_index)
local inserter = player.selected
if not name_blacklist then
populate_name_blacklist()
end
if not inserter or inserter.type ~= 'inserter' or name_blacklist[inserter.name] then
player.print{'inserter-throughput.stopwatch-help'}
return
end
local entry = stopwatch.new(inserter)
entry.player = player
entry.entity_name = inserter.name
entry.entity_position = inserter.position
entry.entity_surface = inserter.surface.name
entry.timeout_tick = event.tick + stopwatch_timeout
if not global.stopwatch then
global.stopwatch = {entry}
else
table.insert(global.stopwatch, entry)
end
end
commands.add_command(
'it-stopwatch',
{'', {'inserter-throughput.stopwatch-command-description'}, ' ', {'inserter-throughput.stopwatch-help'}},
stopwatch_command)
local function stopwatch_results(player, entry)
local result = stopwatch.get_throughput(entry)
local precision = player.mod_settings['inserter-throughput-rounding-precision'].value
local pos = entry.entity_position
local message = {'',
{'inserter-throughput.stopwatch-finished', {'inserter-throughput.inserter-at',
entry.entity_name, pos.x, pos.y, entry.entity_surface}},
'\n',
}
local n = #message + 1
if not result.stable then
message[n] = {'inserter-throughput.stopwatch-warning-not-max'}
message[n + 1] = '\n'
n = n + 2
end
message[n] = {'inserter-throughput.stopwatch-result',
number_to_string_with_precision(result.throughput, precision),
{'per-second-suffix'},
result.items,
result.ticks,
}
player.print(message)
end
-- EVENTS
local function init()
if not global.player_data then
global.player_data = {}
end
-- player_data[player_index] = table
-- .text_id id of the rendered text
for _, player in pairs(game.players) do
init_toggle_button(player)
end
end
local function on_player_joined_game(event)
-- event.player_index current player
init_toggle_button(game.get_player(event.player_index))
end
local function on_tick(event)
local watches = global.stopwatch
if not watches then
return
end
local tick = event.tick
local n = #watches
for i = n, 1, -1 do
local entry = watches[i]
local player = entry.player
local inserter = entry.inserter
local end_watch = false
if not player.valid then -- they're gone, just stop wasting time
end_watch = true
elseif not inserter.valid then -- inserter is gone
local pos = entry.entity_position
player.print{'inserter-throughput.stopwatch-interrupted', {'inserter-throughput.inserter-at',
entry.entity_name, pos.x, pos.y, entry.entity_surface}}
end_watch = true
elseif tick > entry.timeout_tick then
local pos = entry.entity_position
player.print{'inserter-throughput.stopwatch-timeout', {'inserter-throughput.inserter-at',
entry.entity_name, pos.x, pos.y, entry.entity_surface}}
end_watch = true
else
local finished, changed = stopwatch_tick(entry, tick)
if finished then
stopwatch_results(player, entry)
end_watch = true
elseif changed then
entry.timeout_tick = tick + stopwatch_timeout
end
end
if end_watch then
watches[i] = watches[n]
watches[n] = nil
n = n - 1
end
end
if n == 0 then
global.stopwatch = nil
end
end
function on_configuration_changed(event)
-- event.old_version old map version
-- event.new_version new map version
-- event.mod_changes[mod_name] only the ones that changed
-- .old_version old version of the mod
-- .new_version new version of the mod
-- event.mod_startup_settings_changed
-- event.migration_applied if mod prototype migration was executed
local versions = event.mod_changes['inserter-throughput']
if versions and versions.old_version then
local env = {
init_toggle_button = init_toggle_button,
}
migrations(env, versions.old_version)
end
end
script.on_event(defines.events.on_player_joined_game, on_player_joined_game)
script.on_event(defines.events.on_selected_entity_changed, on_entity_selected)
script.on_event(defines.events.on_runtime_mod_setting_changed, on_setting_changed)
script.on_event(defines.events.on_gui_click, on_gui_clicked)
script.on_event('inserter-throughput-toggle', on_toggle)
script.on_event(defines.events.on_tick, on_tick)
script.on_init(init)
script.on_configuration_changed(on_configuration_changed)