Aleksei-bird 7c9c708c92 Первый фикс
Пачки некоторых позиций увеличены
2024-03-01 20:54:33 +03:00

1756 lines
59 KiB
Lua

-- control.lua
-- Max Rate Calculator mod for Factorio
--
-- This calculates max possible rates for a selected set of machines.
-- Does not compute actual running rates - see the Efficen-See mod for that
-- (from which I learned and borrowed)
require("calculator")
show_energy = false
g_marc_units = {}
-- .16 values for belts m/d = 3/40, 3/80, 1/40
g_marc_units[1] = {name="marc-gui-persec", localized_name = {"marc-gui-persec"}, multiplier = 1, divisor = 1, infotype="time"}
g_marc_units[2] = {name="marc-gui-permin", localized_name = {"marc-gui-permin"}, multiplier = 60, divisor = 1, infotype="time"}
-- g_marc_units[3] = {name="marc-transport-belt", localized_name = {"marc-transport-belt"}, multiplier = 3, divisor = 45, infotype="transport"}
-- g_marc_units[4] = {name="marc-fast-transport-belt", localized_name = {"marc-fast-transport-belt"}, multiplier = 3, divisor = 90, infotype="transport"}
-- g_marc_units[5] = {name="marc-express-transport-belt", localized_name = {"marc-express-transport-belt"}, multiplier = 1, divisor = 45, infotype="transport"}
g_marc_units[3] = {name="marc-burner-inserter", localized_name = {"marc-burner-inserter"}, multiplier = 3, divisor = 1.76, infotype="inserter"} -- divisor from https://wiki.factorio.com/Inserters
g_marc_units[4] = {name="marc-basic-inserter", localized_name = {"marc-basic-inserter"}, multiplier = 3, divisor = 2.50, infotype="inserter"}
g_marc_units[5] = {name="marc-long-inserter", localized_name = {"marc-long-inserter"}, multiplier = 3, divisor = 3.46, infotype="inserter"}
g_marc_units[6] = {name="marc-fast-inserter", localized_name = {"marc-fast-inserter"}, multiplier = 1, divisor = 2.31, infotype="inserter"}
g_marc_units[7] = {name="marc-stack-inserter", localized_name = {"marc-stack-inserter"}, multiplier = 12, divisor = 27.70, infotype="stack-inserter"}
g_marc_units[8] = {name="marc-wagon-permin", localized_name = {"marc-wagon-permin"}, multiplier = 60, divisor =1 , infotype="wagon"}
g_marc_units[9] = {name="marc-wagon-perhr", localized_name = {"marc-wagon-perhr"}, multiplier = 3600, divisor =1 , infotype="wagon"}
g_marc_units_default = 2 -- per minutes is the default
g_belts_added = false
-- string formats so numbers are displayed in a consistent way
local persec_format = "%16.3f"
local permin_format = "%16.1f"
local watt_format = "%16.1f"
global.marc_win_loc_x = 0
global.marc_win_loc_y = 176 -- puts us just below rocket stats
-- ----------------------------------------------------------------
function boolstr(bool)
if bool
then return "T"
else return "F"
end
end
-- ----------------------------------------------------------------
function debug_print(str)
if global.marc_debug
then
game.print(str)
end
end
-- ----------------------------------------------------------------
function __FUNC__()
if type(debug) == "table"
then
return debug.getinfo(2, 'n').name
else
return "debug not table"
end
end
-- ----------------------------------------------------------------
function debug_log(f, str)
if global.marc_debug
then
game.print(f .. ": " .. str)
end
end
-- ----------------------------------------------------------------
function string:split( inSplitPattern, outResults )
if not outResults then
outResults = { }
end
local theStart = 1
local theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart )
while theSplitStart do
table.insert( outResults, string.sub( self, theStart, theSplitStart-1 ) )
theStart = theSplitEnd + 1
theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart )
end
table.insert( outResults, string.sub( self, theStart ) )
return outResults
end
-- ----------------------------------------------------------------
function printObj(obj)
for k,v in pairs(obj)
do
debug_print(k)
if(type(v) == "userdata")
then
debug_print(k .. " is userdata")
else
if (check4property(v,name))
then
debug_print(k .. "=" .. v.name)
else
debug_print(k .. " has something")
end
end
end
end
-- ----------------------------------------------------------------
function check4property(obj, prop)
return ({pcall(function()if(typeof(obj[prop])=="Instance")then error()end end)})[1]
end
-- ----------------------------------------------------------------
local function compatible_units(item_or_fluid, unit_type)
if item_or_fluid == "fluid" and
(unit_type ~= "time") and
(unit_type ~= "wagon")
then
return false
else
return true
end
end
-- ----------------------------------------------------------------
local function get_entity_recipe(entity)
if entity.type == "furnace"
then
return (entity.get_recipe() or entity.previous_recipe)
elseif entity.type == "assembling-machine" or entity.type == "rocket-silo"
then
return entity.get_recipe()
else
return nil
end
end
-- ----------------------------------------------------------------
-- Return a flag indicating if name refers to an item (such as iron-plate) or a fluid
local function get_item_or_fluid(name)
local proto = game.item_prototypes[name]
item_or_fluid = "item"
if proto == nil
then
proto = game.fluid_prototypes[name]
if proto ~= nil
then
item_or_fluid = "fluid"
else
debug_log(__FUNC__(), name .. " neither item nor prototype nor fish nor fowl")
item_or_fluid = "item"
end
end
return item_or_fluid
end
-- ----------------------------------------------------------------
-- return the prototype
local function get_proto(name)
local p
local itype
if get_item_or_fluid(name) == "item"
then
p = game.item_prototypes[name]
itype = "item"
else
itype = "fluid"
p = game.fluid_prototypes[name]
end
if(p == nil)
then
debug_print(__FUNC__() .. " could not find proto for " .. itype .. " " .. name)
end
return p
end
local function add_value_to_marcalc_clickable_list(inout_data, label_name, count)
inout_data.clickable_values[label_name] = count
end
-- ----------------------------------------------------------------
local function get_gui_root(player)
-- return player.gui.left
return player.gui.screen
end
-- ----------------------------------------------------------------
function destroy_marc_gui(player)
local root = get_gui_root(player)
global.marc_win_loc_x = root.marc_gui_top.location.x
global.marc_win_loc_y = root.marc_gui_top.location.y
root.marc_gui_top.destroy()
end
-- ----------------------------------------------------------------
local function init_modeffects()
return { speed = 0, prod = 0, consumption = 0, pollution = 0 }
end
-- fill out the first part of a row with the icon and the rate. Used for both inputs and outputs
local function build_gui_row(guirow, name, count, rownum, machine_count, unit_type, inout_data)
if machine_count == nil
then
machine_count = 0
end
local proto = get_proto(name)
if proto == nil -- not sure what this is
then
return false
end
local item_or_fluid = get_item_or_fluid(name)
if not compatible_units(item_or_fluid, unit_type)
then
-- showing inserter/belt rate for a fluid makes no sense
return false
end
localized_name = proto.localised_name
guirow.add({type = "sprite-button", sprite = item_or_fluid .. "/" .. name, name = "marc_sprite" .. rownum, style = "sprite_obj_marc_style", tooltip = {"marc-gui-tt-item-sprite",localized_name, machine_count}})
local label_name = "marc_per_min" .. rownum
guirow.add({type = "label", name = label_name, caption = string.format(persec_format, count ), tooltip={"marc-gui-tt-rate"} })
add_value_to_marcalc_clickable_list(inout_data, label_name, count)
return true
end
-- ----------------------------------------------------------------
local function find_belts()
if g_belts_added
then
return
end
g_belts_added = true
local items_per_belt = 8
for _,entity_proto in pairs(game.entity_prototypes)
do
debug_print(__FUNC__(), "looking" .. entity_proto.name)
debug_print(__FUNC__(), "type " .. entity_proto.type)
if entity_proto.type == "transport-belt"
then
local p = entity_proto
debug_log(__FUNC__(), " found a belt " .. entity_proto.name)
local maxh = entity_proto.max_health
local speed = entity_proto.belt_speed
local denom = items_per_belt * speed * 60
local sz = #g_marc_units
debug_print (entity_proto.name .. " has speed " .. speed .. " denom is " .. denom .. " size " .. #g_marc_units)
table.insert(g_marc_units,{name=p.name, localized_name = p.localised_name, multiplier = 1, divisor =denom , infotype="transport"})
else
debug_print(__FUNC__(), "keep looking" .. entity_proto.name)
end
end
end
-- ----------------------------------------------------------------
local function find_inserters()
debug_log(__FUNC__(), "looking for inserters")
g_inserters_added = false
for _,entity_proto in pairs(game.entity_prototypes)
do
debug_print(__FUNC__(), "type " .. entity_proto.type)
if entity_proto.type == "inserter"
then
local p = entity_proto
debug_print(__FUNC__(), " found a inserter " .. entity_proto.name)
-- local maxh = entity_proto.max_health
-- local speed = entity_proto.inserter_speed
-- local denom = items_per_inserter * speed * 60
-- local sz = #g_marc_units
-- debug_print (entity_proto.name .. " has speed " .. speed .. " denom is " .. denom .. " size " .. #g_marc_units)
--table.insert(g_marc_units,{name=p.name, localized_name = p.localised_name, multiplier = 1, divisor =denom , infotype="transport"})
end
end
end
-- ----------------------------------------------------------------
-- create the list of rate units the user can choose from
local function build_units_dropdown_list()
local item_list = {}
local listix = 1
for _, marc_unit in ipairs(g_marc_units)
do
item_list[listix] = g_marc_units[listix].localized_name
listix = listix + 1
end
return item_list
end
-- ----------------------------------------------------------------
local function init_selected_units(player_index)
global.marc_selected_units = global.marc_selected_units or {}
global.marc_selected_units[player_index] = global.marc_selected_units[player_index] or g_marc_units_default
if global.marc_selected_units[player_index] == 0 or global.marc_selected_units[player_index] > #g_marc_units
then
global.marc_selected_units[player_index] = g_marc_units_default
end
end
-- ----------------------------------------------------------------
-- scale the count based on the unit.
-- some rate units require more than just the multiplier/divisor in the g_marc_units table
local function scale_rate(player, name, count)
local root = get_gui_root(player)
local selected = root.marc_gui_top.marc_gui_upper.maxrate_units.selected_index
local unit_entry = g_marc_units[selected]
local divisor = unit_entry.divisor
local multiplier = unit_entry.multiplier
local unit_type = unit_entry.infotype
-- debug_print("scale_rate " .. name .. " mult " .. multiplier .. " div " .. divisor .. " count " .. count)
local proto = get_proto(name)
if proto == nil -- not sure what this is
then
debug_print("neither item nor fluid?")
return -1
end
local item_or_fluid = get_item_or_fluid(name)
if not compatible_units(item_or_fluid, unit_type)
then
debug_print("not compatible:" .. item_or_fluid .. " name " .. name .. " unit_type" .. unit_type)
-- showing inserter/belt rate for a fluid makes no sense
return -1
end
if unit_type == "inserter"
then
local old_div = divisor
stack_size = player.force.inserter_stack_size_bonus + 1
divisor = divisor * stack_size
elseif unit_type == "stack-inserter"
then
stack_size = player.force.stack_inserter_capacity_bonus + 1
local old_div = divisor
divisor = divisor * stack_size
elseif unit_type == "wagon"
then
local total_capacity
if item_or_fluid == "item"
then
cargo_wagon_proto = game.entity_prototypes["cargo-wagon"]
total_capacity = cargo_wagon_proto.get_inventory_size(1) * proto.stack_size
else
fluid_wagon_proto = game.entity_prototypes["fluid-wagon"]
total_capacity = fluid_wagon_proto.fluid_capacity
end
divisor = divisor * total_capacity
debug_print("divisor " .. divisor .. " total_capacity " .. total_capacity .. " multiplier " .. multiplier )
debug = true
end
-- debug_print("scale_rate now " .. name .. " mult " .. multiplier .. " div " .. divisor .. " count " .. count)
return multiplier*count/divisor
end
local function build_watt_string(watts)
debug_log(__FUNC__(),"watts " .. watts);
local temp;
if(watts < 1000)
then
return string.format( watt_format,watts) .. " watts"
elseif (watts < 1000000)
then
return string.format( watt_format,watts/1000) .. " kW"
elseif (watts < 1000000000)
then
return string.format( watt_format,watts/1000000) .. " MW"
elseif (watts < 1000000000000)
then;
return string.format( watt_format,watts/1000000000) .. " GW"
else
return string.format( watt_format,watts/1000000000000) .. " TW"
end
end
-- ----------------------------------------------------------------
-- Puts the calculated info into a frame on the left side of the window
--
local function write_marc_gui(player, inout_data)
local inputs = inout_data.inputs
local outputs = inout_data.outputs
local machines = inout_data.machines
local machines_fed = inout_data.machines_fed or {}
inout_data.clickable_values = {}
local root = get_gui_root(player)
-- count input, output items and number in common between them
local input_items = 0
for input_name, input_count in pairs(inout_data.inputs)
do
input_items = input_items + 1
end
local both_input_and_output_items = 0
local output_items = 0
for output_name, output_count in pairs(outputs)
do
output_items = output_items + 1
if inputs[output_name] ~= nil
then
both_input_and_output_items = both_input_and_output_items + 1
end
end
if (input_items == 0 and output_items == 0) and show_energy == false
then
-- nothing to see here, move on
if root.marc_gui_top
then
destroy_marc_gui(player)
end
return
end
root.add({type = "frame", name = "marc_gui_top", direction = "vertical", caption={"marc-gui-top-label"}})
local marc_gui_top = root.marc_gui_top
debug_log(__FUNC__(), "marc location " .. root.marc_gui_top.location.x .. "," .. root.marc_gui_top.location.y)
if( global.marc_win_loc_x == nil )
then
global.marc_win_loc_x = 0
end
if( global.marc_win_loc_y == nil )
then
global.marc_win_loc_y = 172
end
root.marc_gui_top.location = { global.marc_win_loc_x, global.marc_win_loc_y }
-- upper section has Rate <dropdown> <close button>
local marc_gui_upper = marc_gui_top.add({type = "flow", name = "marc_gui_upper", direction = "horizontal"})
marc_gui_upper.add({type = "label", name="marc_upper_rate_label", caption={"marc-gui-rate-colon"}, tooltip={"marc-gui-tt-rate-select"}})
debug_print("#g_marc_units " .. #g_marc_units)
init_selected_units(player.index)
local ix = global.marc_selected_units[player.index]
debug_print("global.marc_selected_units[" .. player.index .. "] = " .. ix)
marc_gui_upper.add({type="drop-down", name="maxrate_units", items=build_units_dropdown_list(), selected_index=ix, tooltip={"marc-gui-tt-rate-select"}})
-- can't figure out a nicer way to right justify the close button
if both_input_and_output_items > 0
then
marc_gui_upper.add({type = "label", name = "marc_top1_spacer" , caption = " "})
else
marc_gui_upper.add({type = "label", name = "marc_top1_spacer" , caption = " "})
end
marc_gui_upper.add({type = "sprite-button", sprite = "sprite_marc_calculator", name = "marc_calculator_button" ,align = "right", style = "sprite_obj_marc_style"})
marc_gui_upper.add({type = "sprite-button", sprite = "sprite_marc_close", name = "marc_close_button" ,align = "right", style = "sprite_obj_marc_style"})
-- main marc gui has two frames, one for inputs, one for outputs
local marc_gui = marc_gui_top.add({type = "flow", name = "marc_gui", direction = "horizontal"})
-- marc_gui contains two frames, one for inputs and one for outputs
-- what units are we displaying in?
local selected = root.marc_gui_top.marc_gui_upper.maxrate_units.selected_index
local unit_entry = g_marc_units[selected]
if unit_entry == nil
then
game.print("unit_entry was nil. selected is " .. selected)
unit_entry = g_marc_units[g_marc_units_default]
if unit_entry == nil
then
game.print("still nil, wtf?")
end
end
local divisor = unit_entry.divisor
local multiplier = unit_entry.multiplier
local unit_type = unit_entry.infotype
-- Input ingredients
--
if input_items > 0
then
-- frame to hold the rows of input items
gui_input_frame = marc_gui.add({type = "frame", name = "marc_inputs", direction = "vertical", caption = {"marc-gui-inputs"}})
gui_input_scrollpane = gui_input_frame.add({type = "scroll-pane", name = "marc_inputs_pane", vertical_scroll_policy = "auto", style = "scroll_pane_marc_style", direction = "vertical", caption = {"marc-gui-inputs"}})
-- three columns - item icon, rate per second, rate per minute
gui_inrows= gui_input_scrollpane.add({type = "table", name = "marc_inrows", style = table_marc_style, column_count = 2 })
gui_inrows.style.column_alignments[2] = "right" -- numbers look best right justified
-- column headers
gui_inrows.add({type = "label", name="marc_placeholder", caption="" })
gui_inrows.add({type = "label", name = "marc_header_rate", caption = {"marc-gui-rate"}, tooltip={"marc-gui-tt-rate-input"} })
-- add a row for each input item, with sexy icon (sprite button), rate
local rownum = 1
local name
local count
local sorted_names = {}
for name in pairs(inputs) do table.insert(sorted_names, name) end
table.sort(sorted_names)
for i, name in ipairs(sorted_names)
do
count = inputs[name]
local scaled_count = scale_rate(player, name, count)
build_gui_row(gui_inrows, name, scaled_count, rownum, machines_fed[name], unit_type, inout_data)
rownum = rownum+1
end
end
-- Output products
--
if output_items > 0
then
gui_output_frame = marc_gui.add({type = "frame", name = "marc_outputs", direction = "vertical", caption = {"marc-gui-outputs"}})
gui_output_scrollpane = gui_output_frame.add({type = "scroll-pane", name = "marc_outputs_pane", vertical_scroll_policy = "auto", style = "scroll_pane_marc_style", direction = "vertical", caption = {"marc-gui-inputs"}})
-- if there were items both consumed and produced, we'll have two more columns to show the net result
if both_input_and_output_items > 0
then
cols = 5
else
cols = 3
end
gui_outrows = gui_output_scrollpane.add({type = "table", name = "marc_outrows", style = table_marc_style, column_count = cols })
-- right justify the numbers
for i=1,cols
do
gui_outrows.style.column_alignments[i] = "right"
end
-- column headers
gui_outrows.add({type = "label", name="marc_header1_placeholder", caption=""}) -- this goes where the widget is in the rows below
gui_outrows.add({type = "label", name = "marc_header1_rate", caption = "" , tooltip={"marc-gui-tt-rate-output"}})
gui_outrows.add({type = "label", name = "marc_header1_machine_rate", caption = {"marc-gui-items-per"}, tooltip={"marc-gui-tt-items-per-machine"} })
if both_input_and_output_items > 0
then
gui_outrows.add({type = "label", name = "marc_header1_net_per_sec", caption = {"marc-gui-net"} , tooltip={"marc-gui-tt-net-rate"}})
gui_outrows.add({type = "label", name = "marc_header1_net_machine_count", caption = {"marc-gui-net"} , tooltip={"marc-gui-tt-net-machines"}})
end
-- second row of column headers
gui_outrows.add({type = "label", name="marc_placeholder", caption=""}) -- this goes where the widget is in the rows below
gui_outrows.add({type = "label", name = "marc_header_rate", caption = {"marc-gui-rate"} , tooltip={"marc-gui-tt-rate-output"}})
gui_outrows.add({type = "label", name = "marc_header_machine_rate", caption = {"marc-gui-machine"} , tooltip={"marc-gui-tt-items-per-machine"}})
if both_input_and_output_items > 0
then
gui_outrows.add({type = "label", name = "marc_header_net_per_sec", caption = {"marc-gui-rate"} , tooltip={"marc-gui-tt-net-rate"}})
gui_outrows.add({type = "label", name = "marc_header_net_machine_count", caption = {"marc-gui-machines"} , tooltip={"marc-gui-tt-net-machines"}})
end
local rownum = 1
local sorted_names = {}
for name in pairs(outputs) do table.insert(sorted_names, name) end
table.sort(sorted_names)
for i, name in ipairs(sorted_names)
do
count = outputs[name]
local scaled_count = scale_rate(player, name, count)
local legit = build_gui_row(gui_outrows, name, scaled_count, rownum, machines[name], unit_type, inout_data)
if legit -- only add to row if unit_type is compatible with the item
then
local average_per_machine = count/machines[name]
local scaled_average_per_machine = scale_rate(player, name, average_per_machine) -- multiplier*average_per_machine/divisor
local label_name = "marc_machine_rate" .. rownum
gui_outrows.add({type = "label", name = label_name, caption = string.format( persec_format,scaled_average_per_machine), tooltip={"marc-gui-tt-items-per-machine"} })
add_value_to_marcalc_clickable_list(inout_data, label_name, scaled_average_per_machine)
-- add extra columns if an item appears in both inputs and outputs
input_count = inout_data.inputs[name]
if input_count ~= nil
then
local net_difference = (count - input_count)
local net_count = scale_rate(player, name,net_difference)
label_name = "marc_net_per_min" .. rownum
gui_outrows.add({type = "label", name = label_name, caption = string.format( persec_format, net_count ), tooltip={"marc-gui-tt-net-rate"}})
add_value_to_marcalc_clickable_list(inout_data, label_name, net_count)
local net_machines = net_difference/average_per_machine
label_name = "marc_net_machines" .. rownum
gui_outrows.add({type = "label", name = label_name, caption = string.format( persec_format, net_machines ), tooltip={"marc-gui-tt-net-machines"}})
add_value_to_marcalc_clickable_list(inout_data, label_name, net_machines)
elseif both_input_and_output_items > 0
then
-- five column display, but this item doesn't have net info
gui_outrows.add({type = "label", name = "marc_net_per_min" .. rownum, caption = "" })
gui_outrows.add({type = "label", name = "marc_net_machines" .. rownum, caption = "" })
end
rownum = rownum+1
end
end
end
if show_energy
then
local marc_gui_energy = marc_gui_top.add({type = "flow", name = "marc_gui_energy", direction = "horizontal"})
marc_gui_energy.add({type = "label", name="marc-gui-energy-label", caption={"marc-gui-energy"}})
marc_gui_energy.add({type = "label", name = minwattlabel_name, caption = build_watt_string(inout_data.min_consumption), tooltip="" })
marc_gui_energy.add({type = "label", name = dashwattlabel_name, caption = "-", tooltip="" })
marc_gui_energy.add({type = "label", name = maxwattlabel_name, caption = build_watt_string(inout_data.max_consumption), tooltip="" })
end
end
-- ----------------------------------------------------------------
-- show the gui with the rate calculations
local function open_gui(event, inout_data)
local player = game.players[event.player_index]
local root = get_gui_root(player)
if root.marc_gui_top
then
root.marc_gui_top.destroy()
end
-- script.on_event(defines.events.on_tick, on_tick)
write_marc_gui(player, inout_data)
end
-- ----------------------------------------------------------------
local function effect_allowed_for_machine(effectname, machine_proto)
if machine_proto.allowed_effects ~= nil
then
return machine_proto.allowed_effects[effectname]
else
debug_log(__FUNC__(), "machine_proto.name " .. machine_proto.name .. "has no allowed effects table")
return false;
end
end
-- ----------------------------------------------------------------
-- calculate the speed and productivity effects of a single module
local function calc_mod( modname, modeffects, modquant, effectivity, machine_proto )
-- debug_print("calc_mod help " .. game.item_prototypes[modname])
local dummy = 0
protoeffects = game.item_prototypes[modname].module_effects
debug_print("mod is " .. modname .. " quantity " .. modquant .. " num machine_proto " .. machine_proto.name)
for effectname,effectvals in pairs(protoeffects)
do
debug_print("...effectname is " .. effectname .. " modquant " .. modquant)
for _,bonamount in pairs(effectvals)
do
local allowed = effect_allowed_for_machine(effectname, machine_proto)
debug_log(__FUNC__(),"...effectname " .. effectname .. ", bonamount " .. bonamount .. "...allowed " .. boolstr(allowed))
if allowed
then
if effectname == "speed"
then
-- debug_print("...adjust speed by " .. ( bonamount * modquant ))
modeffects.speed = modeffects.speed + ( bonamount * modquant * effectivity)
elseif effectname == "productivity"
then
-- debug_print("...adjust productivity by " .. ( bonamount * modquant ))
modeffects.prod = modeffects.prod + (bonamount * modquant * effectivity)
elseif effectname == "consumption"
then
modeffects.consumption = modeffects.consumption + (bonamount * modquant * effectivity)
elseif effectname == "pollution"
then
debug_log(__FUNC__(),"POLLUTION! " .. modeffects.pollution)
modeffects.pollution = modeffects.pollution + (bonamount * modquant * effectivity)
end
end
end
end
end
-- ----------------------------------------------------------------
-- calculate the effects of all the modules in the entity
local function calc_mods(entity, modeffects, effectivity, machine_proto)
modinv = entity.get_module_inventory()
modcontents = modinv.get_contents()
local ix = 1
for modname,modquant in pairs(modcontents)
do
debug_print("calc_mods proto is " .. game.item_prototypes[modname].name)
debug_print("calc_mods modname,modquant " .. modname .. "," .. modquant)
calc_mod(modname, modeffects, modquant, effectivity, machine_proto)
ix = ix + 1
end
return modeffects
end
-- ----------------------------------------------------------------
local function print_bounding_box(name, b)
debug_print(name .. " " .. b.left_top.x .. "," .. b.left_top.y .. " " .. b.right_bottom.x .. "," .. b.right_bottom.y)
end
-- ----------------------------------------------------------------
-- thanks to Psihuis for this one:
local function do_boxes_intersect(a, b)
local a_left = a.left_top.x
local a_right = a.right_bottom.x
local a_top = a.left_top.y
local a_bottom = a.right_bottom.y
local b_left = b.left_top.x
local b_right = b.right_bottom.x
local b_top = b.left_top.y
local b_bottom = b.right_bottom.y
return (a_left < b_right and a_right > b_left) and (a_top < b_bottom and a_bottom > b_top)
end
-- ----------------------------------------------------------------
local function is_machine_in_range_of_beacon(entity, beacon)
debug_print("is machine in range")
local machine_selection_box = entity.prototype.selection_box
local beac_dist = game.entity_prototypes[beacon.name].supply_area_distance
-- debug_print("beac prot selbox" .. beacon.prototype.selection_box.left_top.x .. " - " .. beacon.prototype.selection_box.right_bottom.y )
local beacsel_left = beacon.prototype.selection_box.left_top.x
local beacsel_top = beacon.prototype.selection_box.left_top.y
local beacsel_right = beacon.prototype.selection_box.right_bottom.x
local beacsel_bottom = beacon.prototype.selection_box.right_bottom.y
debug_print(beacsel_left .. " " .. beacsel_top .. " X " .. beacsel_right .. " " .. beacsel_bottom)
debug_print(beacsel_left .. " " .. beacsel_top .. " X " .. beacsel_right .. " " .. beacsel_bottom)
local beacon_left_top = {x = beacsel_left + beacon.position.x - beac_dist,
y = beacsel_top + beacon.position.y - beac_dist}
local beacon_right_bottom = {x = beacsel_right + beacon.position.x + beac_dist,
y = beacsel_bottom + beacon.position.y + beac_dist}
local beacon_box = { left_top = beacon_left_top, right_bottom = beacon_right_bottom }
local machine_left_top = { x = entity.position.x + machine_selection_box.left_top.x,
y = entity.position.y + machine_selection_box.left_top.y }
local machine_right_bottom = { x = entity.position.x + machine_selection_box.right_bottom.x,
y = entity.position.y + machine_selection_box.right_bottom.y }
local machine_box = { left_top = machine_left_top, right_bottom = machine_right_bottom }
-- print_bounding_box(" machine_box", machine_box)
-- debug_print("..........")
--print_bounding_box(" beacon_box ", beacon_box)
-- debug_print(",,,,,,,,,,")
local ans = do_boxes_intersect(beacon_box, machine_box)
return ans
end
-- ----------------------------------------------------------------
max_beacon_dist = -1
-- calculate effects of beacons. For our purposes, only speed effects count
local function check_beacons(surface, entity)
local x = entity.position.x
local y = entity.position.y
local machine_proto = game.entity_prototypes[entity.name]
if max_beacon_dist == -1
then
debug_print("beacon distance was -1 so look")
for _,entity_proto in pairs(game.entity_prototypes)
do
if entity_proto.type == "beacon"
then
debug_print("found a beacon " .. entity_proto.name)
local distance = game.entity_prototypes[entity_proto.name].supply_area_distance
if distance > max_beacon_dist
then
max_beacon_dist = distance
end
end
end
-- max_beacon_dist = game.entity_prototypes["beacon"].supply_area_distance
debug_print("beacon distance is " .. max_beacon_dist)
end
debug_print("check_beacons searching around " .. x .. "," .. y .. " beacon dist is " .. max_beacon_dist)
machine_box = entity.prototype.selection_box
debug_print("check_beacons box is " .. machine_box.left_top.x .. "," .. machine_box.left_top.y .. " thru " .. machine_box.right_bottom.x .. "," .. machine_box.right_bottom.y)
modeffects = init_modeffects()
local beacons = 0
local mods = 0
-- assumes all beacons have same effect radius
search_area = { { x + machine_box.left_top.x - max_beacon_dist, y + machine_box.left_top.y - max_beacon_dist },
{ max_beacon_dist + x + machine_box.right_bottom.x, max_beacon_dist + y + machine_box.right_bottom.y }}
debug_print(" upper left " .. x + machine_box.left_top.x - max_beacon_dist .. "," .. y + machine_box.left_top.y - max_beacon_dist)
for _,beacon in pairs(surface.find_entities_filtered{ area=search_area, type="beacon"})
do
debug_print("test a beacon")
if is_machine_in_range_of_beacon(entity, beacon)
then
debug_print(" beacon area is " .. beacon.prototype.supply_area_distance .. " at " .. beacon.position.x .. "," .. beacon.position.y)
beacons = beacons + 1
local effectivity = beacon.prototype.distribution_effectivity
debug_print("effectivity is " .. effectivity)
calc_mods( beacon, modeffects, effectivity, machine_proto)
end
end
debug_print("check_beacons - Saw " .. beacons)
return modeffects
end
-- ----------------------------------------------------------------
function energy_usage(inout_data, prodproto, beacon_modeffects, mod_effects)
local energy_usage = 0
if prodproto.energy_usage ~= nil
then
energy_usage = prodproto.energy_usage
end
debug_log(__FUNC__(),prodproto.name .. " energy_usage is " .. energy_usage)
local active_usage = energy_usage * 60
local esource = prodproto.electric_energy_source_prototype
local idle_usage = 0
local drain = 0
if esource ~= nil
then
debug_log(__FUNC__(), prodproto.name .. " drain is " .. esource.drain)
drain = esource.drain
end
local max_usage = idle_usage + active_usage
local idle_usage = drain * 60
local beacon_consumption = 0
if beacon_modeffects and beacon_modeffects.consumption ~= nil
then
beacon_consumption = beacon_modeffects.consumption
end
local mod_consumption = 0
if mod_effects and mod_effects.consumption ~= nil
then
mod_consumption = mod_effects.consumption
end
local total_consumption_effects = beacon_consumption + mod_consumption
debug_log(__FUNC__(), "total_consumption_effects = " .. total_consumption_effects)
if total_consumption_effects < -0.80
then
total_consumption_effects = -0.80
end
if drain ~= 0
then
max_usage = idle_usage + active_usage * (1 + total_consumption_effects)
debug_log(__FUNC__(), "have drain, adjusted max consumption " .. max_usage)
inout_data.max_consumption = inout_data.max_consumption + max_usage
inout_data.min_consumption = inout_data.min_consumption + idle_usage
else
max_usage = active_usage * (1 + total_consumption_effects)
debug_log(__FUNC__(), "no drain, adjusted max consumption " .. max_usage)
inout_data.max_consumption = inout_data.max_consumption + max_usage
inout_data.min_consumption = inout_data.min_consumption + max_usage
end
debug_log(__FUNC__(), "total min " .. inout_data.min_consumption .. " max " .. inout_data.max_consumption)
end
-- ----------------------------------------------------------------
local function record_input(inout_data, name, amount)
if inout_data.inputs[name] ~= nil
then
inout_data.inputs[name] = inout_data.inputs[name] + amount
else
inout_data.inputs[name] = amount
end
if inout_data.machines_fed[name] ~= nil
then
inout_data.machines_fed[name] = inout_data.machines_fed[name] + 1
else
inout_data.machines_fed[name] = 1
end
end
-- ----------------------------------------------------------------
local function record_output(inout_data, name, amount)
if inout_data.outputs[name] ~= nil
then
inout_data.outputs[name] = inout_data.outputs[name] + amount
else
inout_data.outputs[name] = amount
end
if inout_data.machines[name] ~= nil
then
inout_data.machines[name] = inout_data.machines[name] + 1
else
inout_data.machines[name] = 1
end
end
-- ----------------------------------------------------------------
-- for an individual assembler, calculate the rates all the inputs are used at and the outputs are produced at, per second
local function calc_assembler(entity, inout_data, beacon_modeffects)
local prodproto = game.entity_prototypes[entity.name]
-- get the machines base crafting speed, in cycles per second
local crafting_speed = entity.prototype.crafting_speed
debug_log(__FUNC__(),"entity.name " .. entity.name .. " crafting_speed " .. crafting_speed)
modeffects = init_modeffects()
local effectivity = 1
modeffects = calc_mods(entity, modeffects, effectivity, prodproto)
-- adjust crafting speed based on modules and beacons
local total_speed_effect = modeffects.speed + beacon_modeffects.speed
if total_speed_effect < -0.80 -- no worse than 20%
then
total_speed_effect = -0.80
end
-- ---------------- ENERY -------------------------
energy_usage(inout_data, prodproto, beacon_modeffects, mod_effects)
-- emissions values are always zero in the entity prototype, so we're not calculating pollution now
-- local pollution = esource.emissions -- * esource.emissions * 60
-- debug_log(__FUNC__()," esource.emissions " .. pollution)
--debug_log(__FUNC__()," esource.emissions_per_minute " .. esource.emissions_per_minute)
-- ---------------- ENERGY AND POLLUTION! -------------------------
-- issue reported for Pyanodon's alien life mod says some machines don't have consumption and pollution
-- in allowed effects. I'm not seeing this in the prototype, nor in run-time tests, the fawogae plantations
-- do have consumption included in allowed_effects, and the plantation's water consumption is affected by
-- speed modules in beacons or in the plantation itself
--
-- consumption applies to energy? Pyanodon seems to treat it as item inputs
debug_log(__FUNC__(), "cspeed " .. crafting_speed .. " modspeed " .. modeffects.speed .. " beacon_modeffects.speed " .. beacon_modeffects.speed .. " total_speed_effect " .. total_speed_effect)
crafting_speed = crafting_speed * ( 1 + total_speed_effect)
-- how long does the item take to craft if no modules and crafting speed was 1? It's in the recipe.energy!
local recipe = get_entity_recipe(entity)
if get_entity_recipe(entity) ~= nil
then
crafting_time = recipe.energy
local ideal_rate = crafting_speed / crafting_time
local game_limited_rate = math.min(ideal_rate, 60)
debug_log(__FUNC__(),"crafting time " .. crafting_time .. " modeffects.speed " .. modeffects.speed .. " beacon_modeffects.speed " .. beacon_modeffects.speed .. " ideal rate " .. ideal_rate .. " game limited rate " .. game_limited_rate)
if(crafting_time == 0)
then
crafting_time = 1
debug_log(__FUNC__(),"(entity.get_recipe() or entity.previous_recipe) .energy = 0, wtf?")
end
-- for all the ingredients in the recipe, calculate the rate
-- they're consumed at. Add to the inputs table.
for _, ingred in ipairs(recipe .ingredients)
do
local amount = ingred.amount * game_limited_rate
record_input(inout_data, ingred.name, amount)
end
--[[
-- initial code to compute fuel consumption by stone & electric furnaces
-- not sure who cares, not in game's production graph. would also need to consider burner inserter
-- Rseding says use burner_prototype info
fuel_inventory = entity.get_fuel_inventory()
if fuel_inventory ~= nil
then
local fuel_name = fuel_inventory[1].name
debug_log(__FUNC__(),entity.name .. " has fuel " .. fuel_name)
fuel_proto = game.item_prototypes[fuel_name]
debug_log(__FUNC__(),"fuel value " .. fuel_proto.fuel_value)
end
]]--
-- for all the products in the recipe (usually just one)
-- calculate the rate they're produced at and add each product to the outputs
-- table
for _, prod in ipairs(get_entity_recipe(entity) .products)
do
local chance
local amount
-- sometime in 0.17 factorio changed uranium recipe to use a probability value * amount
-- rather than probability with a range of amount_min and amount_max
-- but some mods like Bob's Greenhouse still was using a range
if prod.probability ~= nil
then
-- debug_log(__FUNC__(),"probability is " .. prod.probability)
chance = prod.probability
if prod.amount_min ~= nil
then
amount = chance * (prod.amount_min + prod.amount_max) / 2
else
amount = prod.amount * chance
end
else
-- debug_log(__FUNC__(),"probability is nil")
chance = 1
if prod.amount ~= nil
then
-- debug_log(__FUNC__(),"prod amount, modeffects.prod " .. prod.name .. " " .. prod.amount .. "," .. modeffects.prod )
amount = prod.amount
else
amount = (prod.amount_min + prod.amount_max) / 2
end
end
-- gotta handle super beacons - they can affect prod too
-- debug_log(__FUNC__(), prod.name .. " amount " .. amount .. " modeffects " .. ( 1 + modeffects.prod) .. " cspeed " .. crafting_speed .. " crafting_time" .. crafting_time)
local catalyst_amount = 0
if prod.catalyst_amount ~= nil
then
catalyst_amount = prod.catalyst_amount
end
local productivity = modeffects.prod + beacon_modeffects.prod
if productivity < 0
then
productivity = 0
end
-- speed bonus applies fully to productivity bonus, but is limited to no more that 60 for non-prod boosted output
amount = amount * game_limited_rate + (amount - catalyst_amount) * productivity * ideal_rate
record_output(inout_data, prod.name, amount)
end
end
end
-- ----------------------------------------------------------------
-- for an individual mining- drill, calculate the rates all the inputs are used at and the outputs are produced at, per second
local function calc_mining(inout_data, surface, player, entity)
local beacon_modeffects = { speed = 0, prod = 0 }
if entity.prototype.module_inventory_size > 0
then
beacon_modeffects = check_beacons(surface, entity)
end
local drilling_bonus = player.force.mining_drill_productivity_bonus
local x = entity.position.x
local y = entity.position.y
--debug_log(__FUNC__(),"Found a drill")
local prod = entity.mining_target
if prod == nil
then
--debug_log(__FUNC__(),"nil mining_target " .. x .. "," .. y)
return
end
local prodproto = entity.mining_target.prototype
local drillproto = entity.prototype
debug_log(__FUNC__(),"bse = " .. beacon_modeffects.speed)
debug_log(__FUNC__(),"target is " .. prod.name .. " type " .. prod.type)
debug_log(__FUNC__(),"target amount " .. prod.amount .. " normal amount " .. prodproto.normal_resource_amount)
debug_log(__FUNC__(),"progress " .. entity.mining_progress .. " bonus " .. entity.bonus_mining_progress)
debug_log(__FUNC__(),"speed " .. drillproto.mining_speed )
-- get the machines base crafting speed, in cycles per second
local mining_speed = entity.prototype.mining_speed
local mining_power = 1 -- entity.prototype.mining_power
local mining_time = prodproto.mineable_properties.mining_time
debug_log(__FUNC__(),prodproto.name .. " number of mineable products " .. #prodproto.mineable_properties.products)
modeffects = init_modeffects()
local effectivity = 1
modeffects = calc_mods(entity, modeffects, effectivity, entity.prototype)
energy_usage(inout_data, drillproto, beacon_modeffects, nil)
-- adjust crafting speed based on modules and beacons
local total_speed_effect = modeffects.speed + beacon_modeffects.speed
if total_speed_effect < -0.80 -- no worse than 20%
then
total_speed_effect = -0.80
end
debug_log(__FUNC__(), "cspeed " .. mining_speed .. " modspeed " .. modeffects.speed .. " beacon_modeffects.speed " .. beacon_modeffects.speed .. " total_speed_effect " .. total_speed_effect)
mining_speed = mining_speed * ( 1 + total_speed_effect)
local amount = mining_speed / mining_time
local oldamount = amount
amount = amount * ( 1 + drilling_bonus + modeffects.prod + beacon_modeffects.prod)
debug_log(__FUNC__(),"amount = amount* ( 1 + drilling_bonus _modeffects.prod + beacon_modeffects.prod)")
debug_log(__FUNC__(),amount.." = " .. oldamount .. " * ( 1 +" ..drilling_bonus.. "+" .. modeffects.prod.. " +" .. beacon_modeffects.prod..")")
for _,mineable_product in pairs(prodproto.mineable_properties.products)
do
local product_name = mineable_product.name
local result_type = get_item_or_fluid(product_name)
-- debug_log(__FUNC__(),"mineable_product.name" .. mineable_product.name)
local required_fluid = prodproto.mineable_properties.required_fluid
if required_fluid ~= nil
then
local fluid_amount = prodproto.mineable_properties.fluid_amount * mining_speed / mining_time
fluid_amount = fluid_amount / 10 -- prodproto.mineable_properties.fluid_amount is 10x too high
record_input(inout_data, required_fluid, fluid_amount)
end
if result_type == "fluid"
then
-- debug_log(__FUNC__(), "prod.amount is " .. prod.amount)
--
local mineable_props = prodproto.mineable_properties.result
local mineable_amt
if mineable_product.amount_min ~= nil
then
-- debug_log(__FUNC__(), " producst min is " .. mineable_product.amount_min)
mineable_amt = (mineable_product.amount_min + mineable_product.amount_max) / 2
else
mineable_amt = mineable_product.amount
end
local mining_yield = (prod.amount / prodproto.normal_resource_amount ) * mineable_amt
debug_log(__FUNC__(),"yield is " .. math.floor(mining_yield))
mining_yield = math.min(mining_yield, 100)
amount = amount * mining_yield
debug_log(__FUNC__(),amount .." = amount* "..mining_yield .. " for " .. product_name)
end
debug_log(__FUNC__(), "product_name is now " .. product_name .. " amount " .. amount)
record_output(inout_data, product_name, amount)
end
end
-- ----------------------------------------------------------------
local function calc_production(inout_data, surface, entity)
local machines_with_no_recipe = 0
local beacon_modeffects = init_modeffects()
beacon_modeffects = check_beacons(surface, entity)
if get_entity_recipe(entity) == nil
then
machines_with_no_recipe = machines_with_no_recipe + 1
end
calc_assembler(entity, inout_data, beacon_modeffects)
return machines_with_no_recipe
end
-- ----------------------------------------------------------------
local function calc_inserter(inout_data, surface, entity)
local prodproto = game.entity_prototypes[entity.name]
local esource = prodproto.electric_energy_source_prototype
if esource ~= nil
then
debug_log(__FUNC__(), "drain is " .. esource.drain)
-- debug_log(__FUNC__(), "energy_per_rotation is " .. prodproto.energy_usage)
local idle_usage = esource.drain * 60
local rot_speed = prodproto.inserter_rotation_speed
local energy_per_rot = 1 --prodproto.energy_usage
local ext_speed = prodproto.inserter_extension_speed
local energy_per_ext = 1 -- prodproto.energy_usage
local active_usage = ((energy_per_rot*rot_speed) + (ext_speed*energy_per_ext)) * 60
local max_usage = idle_usage + active_usage
max_usage = idle_usage + active_usage
debug_log(__FUNC__(), "adjusted max consumption " .. max_usage)
inout_data.max_consumption = inout_data.max_consumption + max_usage
inout_data.min_consumption = inout_data.min_consumption + idle_usage
debug_log(__FUNC__(), "total min " .. inout_data.min_consumption .. " max " .. inout_data.max_consumption)
end
end
-- ----------------------------------------------------------------
local function dump_help(prodproto, filter)
local helpText = prodproto.help()
local helpTable = helpText:split("\n")
-- debug_log(__FUNC__(), "help for " ..prodproto.name)
for hix = 1, #helpTable do
if helpTable[hix] ~= nil
then
if string.find(helpTable[hix], filter)
then
debug_log(__FUNC__(),helpTable[hix])
end
end
end
end
-- cribbed from helmod
local function getPowerExtract(temperature, heat_capac)
if temperature == nil then
temperature = 165
end
if temperature < 15 then
temperature = 25
end
if heat_capacity == nil or heat_capacity == 0 then
heat_capacity = 200
end
return (temperature-15)*heat_capacity
end
-- cribbed from helmod
local function getFluidProduction(proto)
local effectivity = proto.effectivity or 1
debug_log(__FUNC__(), "effectivity is " .. effectivity)
if(proto.effectivity == nil)
then
debug_log(__FUNC__(), "proto.effectivity is nil")
end
local fluid_filter = "water"
local fluid_prototype = game.fluid_prototypes[fluid_filter]
local heat_capacity = fluid_prototype.heat_capacity
local target_temperature = proto.target_temperature
debug_log(__FUNC__(),"target temp is " .. target_temperature .. " heat cap is " .. heat_capacity)
local power_extract = getPowerExtract(target_temperature, heat_capacity)
local energy_consumption = proto.max_energy_usage
debug_log(__FUNC__(), "energy_consumption " .. energy_consumption .. "/" .. power_extract .. " = " .. (energy_consumption / power_extract))
return energy_consumption / (effectivity * power_extract)
-- number is too high for kras by a factor of 2.5, is correct for vanilla
-- effectivity is nil
-- energy_consumption must be wrong for Kras
end
local function calc_boiler(inout_data, surface, entity)
debug_log(__FUNC__(), "boiler is " .. entity.name)
-- prodproto.fluidbox_prototypes array has .filter.name - either steam or water
-- but since consumption/production rates not exposed by factorio (as far as I know)
-- have to use hardcoded numbers
local prodproto = game.entity_prototypes[entity.name]
debug_log(__FUNC__(), "type is " .. prodproto.type)
debug_log(__FUNC__(), "max is " .. prodproto.max_energy_usage)
-- debug_log(__FUNC__(), "fluid_usage_per_tick is " .. prodproto.fluid_usage_per_tick)
local wontwork = prodproto.emissions_per_second
debug_log(__FUNC__(), "wontwork is " .. wontwork)
local energySource = prodproto.heat_energy_source_prototype
if energySource == nil
then
debug_log(__FUNC__(), "energySource is nil")
else
debug_log(__FUNC__(), "energySource is not nil")
dump_help(energySource, "eff")
debug_log(__FUNC__(), "emissions is " .. energySource.emissions)
end
-- local emissions_per_minute = energySource.emissions_per_minute
-- debug_log(__FUNC__(),"emissions per minute " .. emissions_per_minute)
-- local helpText = prodproto.help()
-- local helpTable = helpText:split("\n")
-- debug_log(__FUNC__(), "help # " .. #helpTable)
-- for hix = 1, #helpTable do
-- if helpTable[hix] ~= nil
-- then
-- if string.find(helpTable[hix], "fluid")
-- then
-- debug_log(__FUNC__(),helpTable[hix])
-- end
-- if string.find(helpTable[hix], "emiss")
-- then
-- debug_log(__FUNC__(),helpTable[hix])
-- end
-- end
-- end
dump_help(prodproto, "consumption")
dump_help(prodproto, "energy")
--debug_log(__FUNC__(), "prodproto.energy_consumption " .. (prodproto.energy_consumption or -99))
local helsanswer = getFluidProduction(prodproto)
debug_log(__FUNC__(), "helsanswer is " .. helsanswer)
-- local consumptionRate
local productionRate
-- if entity.name == "boiler"
-- then
-- consumptionRate = 20
-- productionRate = 20
-- elseif entity.name == "heat-exchanger"
-- then
-- consumptionRate = 20
-- productionRate = 20
-- else
-- game.print("Unknown boiler " .. entity.name .. ". Fluid calculations may not be accurate")
-- return
-- end
productionRate = helsanswer * 60
record_input(inout_data, "water", productionRate)
record_output(inout_data, "steam", productionRate)
end
-- ----------------------------------------------------------------
local function calc_generator(inout_data, surface, entity)
debug_log(__FUNC__(), "generator is " .. entity.name)
local genProto = game.entity_prototypes[entity.name]
debug_log(__FUNC__(), "generator fluid usage is " .. genProto.fluid_usage_per_tick)
local consumptionRate = genProto.fluid_usage_per_tick * 60
local fluid_name = "some fluid"
if #genProto.fluidbox_prototypes == 1
then
if genProto.fluidbox_prototypes[1].filter ~= nil
then
fluid_name = genProto.fluidbox_prototypes[1].filter.name
end
end
debug_log(__FUNC__(), "generator uses " .. fluid_name)
if fluid_name ~= "some fluid"
then
record_input(inout_data, fluid_name, consumptionRate)
end
end
-- ----------------------------------------------------------------
-- player has selected some machines with our tool
script.on_event(defines.events.on_player_selected_area,
function(event)
local function_name = "marc event"
-- leave if not our tool
if event.item ~= "max-rate-calculator"
then
return
end
find_belts()
find_inserters()
global.marc_selected_unit_index = global.marc_selected_unit_index or {}
local player = game.players[event.player_index]
local surface = player.surface
local inout_data = {
inputs={},
outputs = {},
machines = {},
machines_fed = {},
max_consumption = 0,
min_consumption = 0}
-- for all the machines selected, calculate consumption/production rates.
-- (note: beacons themselves don't need to be selected, if one is in range
-- of a selected machine, it will be considered)
local no_recipe_smelters = 0
local no_recipe_assemblers = 0
local count = 0
for _, entity in ipairs(event.entities)
do
debug_log(function_name,"Found entity " .. entity.name .. " type " .. entity.type .. " count " .. count )
count = count + 1
if entity.type == "assembling-machine" or entity.type == "rocket-silo"
then
no_recipe_assemblers = no_recipe_assemblers + calc_production(inout_data, surface, entity)
elseif entity.type == "furnace"
then
no_recipe_smelters = no_recipe_smelters + calc_production(inout_data, surface, entity)
elseif entity.type == "mining-drill"
then
calc_mining( inout_data, surface, player, entity)
elseif entity.type == "beacon"
then
-- beacon prototype has drain with same value as proto's energy_usage, should be zero
local prodproto = game.entity_prototypes[entity.name]
debug_log(function_name,"TODO: beacon " .. count .. " should just be prodproto.energy_usage " .. prodproto.energy_usage .. " ignore drain");
inout_data.max_consumption = inout_data.max_consumption + prodproto.energy_usage * 60
inout_data.min_consumption = inout_data.min_consumption + prodproto.energy_usage * 60
elseif entity.type == "inserter"
then
calc_inserter( inout_data, surface, entity)
-- science, rocket launch pads, radars, lasers
elseif entity.type == "boiler"
then
calc_boiler( inout_data, surface, entity)
elseif entity.type == "generator"
then
calc_generator( inout_data, surface, entity)
else
local prodproto = game.entity_prototypes[entity.name]
debug_log(function_name,"TODO maybe: " .. entity.type)
if prodproto.fixed_recipe ~= nil
then
debug_log(function_name,"TODO fixed recipe " .. prodproto.fixed_recipe)
end
if prodproto.fluid_usage_per_tick ~= nil
then
debug_log(function_name,"TODO fupt " .. prodproto.fluid_usage_per_tick)
end
end
end
-- put out a warning if we found machines with no recipes
if no_recipe_assemblers > 0 or no_recipe_smelters > 0
then
if no_recipe_assemblers > 0 and no_recipe_smelters > 0
then
player.print({"marc-gui-no-recipe-both", no_recipe_assemblers, no_recipe_smelters})
else
if no_recipe_assemblers > 0
then
player.print({"marc-gui-no-recipe-assemblers", no_recipe_assemblers})
else
player.print({"marc-gui-no-recipe-smelters", no_recipe_smelters})
end
end
end
-- save so if user changes units dropdown, we can recalculate the gui
global.marc_inout_data_by_player = global.marc_inout_data_by_player or {}
global.marc_inout_data_by_player[event.player_index] = inout_data
global.marc_inout_data = inout_data
-- now open and show the gui with the calculations
open_gui(event, inout_data)
-- throw away the max-rate-calculator item. User never gets one in their inventory (unless they click hotkey and directly put it in inventory)
local cursor_stack = player.cursor_stack
cursor_stack.clear()
end
)
-- ----------------------------------------------------------------
-- player hit the magic key, create our selection tool and put it in their hand
local function on_hotkey_main(event)
init_selected_units(event.player_index)
local player = game.players[event.player_index]
-- once in their life, a message is displayed giving a hint
global.marc_hint = global.marc_hint or 0
if global.marc_hint == 0
then
player.print({"marc-gui-select-hint"})
global.marc_hint = 1
end
-- put whatever is in the player's hand back in their inventory
-- and put our selection tool in their hand
local old_cursor_had_item = ""
if player.cursor_stack ~= nil -- RusselRaZe reported crash accessing nil cursor_stack here when riding cargo rocket in space ex mod
then
if player.cursor_stack.valid_for_read
then
old_cursor_had_item = player.cursor_stack.name
end
end
player.clear_cursor()
if player.cursor_stack ~= nil -- muppet9010 reported crash accessing nil cursor_stack here when player died
then
if old_cursor_had_item ~= "max-rate-calculator" -- if already in hand, just clear it and get out
then
local cursor_stack = player.cursor_stack
cursor_stack.clear()
cursor_stack.set_stack({name="max-rate-calculator", type="selection-tool", count = 1})
end
end
end
-- ----------------------------------------------------------------
local function max_rate_shortcut(event)
debug_print("Max Rate Shortcut")
if event.prototype_name == "max-rate-shortcut"
then
on_hotkey_main(event)
elseif event.prototype_name == "marc_calc_4func"
then
local player = game.players[event.player_index]
toggle_calculator(player)
end
end
-- ----------------------------------------------------------------
-- user has clicked somewhere. If clicked on any gui item name that starts with "marc_..."
-- hide the gui
local function on_gui_click(event)
local event_name = event.element.name
debug_print("event_name " .. event_name)
local marc_prefix = "marc_"
local possible_marc_prefix = string.sub( event_name, 1, string.len(marc_prefix) )
local player = game.players[event.player_index]
local root = get_gui_root(player)
local marcalc_prefix = "marcalc_"
local possible_marcalc_prefix = string.sub( event_name, 1, string.len(marcalc_prefix))
if possible_marcalc_prefix == marcalc_prefix
then
handle_marcalc_click(event_name, player)
return
end
if global.marc_inout_data_by_player ~= nil
then
local inout_data = global.marc_inout_data_by_player[event.player_index]
if inout_data ~= nil
then
-- debug_print("on_gui_click looking for " .. event_name .. " in clickable values")
val = inout_data.clickable_values[event_name]
if val ~= nil
then
-- debug_print("on_gui_click found " .. event_name .. " in clickable values. val = " .. val)
marcalc_clickable_value_clicked(player, val)
return
end
end
end
if possible_marc_prefix == marc_prefix
then
if event_name == "marc_calculator_button"
then
toggle_calculator(player)
return
end
if root.marc_gui_top then
if event_name == "marc_close_button"
then
destroy_marc_gui(player)
hide_calculator(player)
end
elseif event_name == "marc_close_button"
then
if player.gui.left.marc_gui_top then
player.gui.left.marc_gui_top.destroy()
hide_calculator(player)
end
end
end
end
-- ----------------------------------------------------------------
local function on_gui_selection(event)
local event_name = event.element.name
local player = game.players[event.player_index]
local root = get_gui_root(player)
if event_name == "maxrate_units"
then
local selected = root.marc_gui_top.marc_gui_upper.maxrate_units.selected_index
global.marc_selected_units[event.player_index] = selected
unit_entry = g_marc_units[selected]
debug_print("selected " .. unit_entry.name .. " " .. unit_entry.multiplier .. "/" .. unit_entry.divisor)
root.marc_gui_top.destroy()
if global.marc_inout_data_by_player == nil -- by_player is new, may not exist in old save
then
open_gui(event, global.marc_inout_data)
else
open_gui(event, global.marc_inout_data_by_player[event.player_index])
end
end
end
-- ----------------------------------------------------------------
local function on_marc_command(event)
if event.parameter == "debug"
then
global.marc_debug = true
debug_print("marc debugging is on")
elseif event.parameter == "nodebug"
then
debug_print("marc debugging is off")
global.marc_debug = false
elseif event.parameter == "showloc"
then
if global.marc_win_loc_x ~= nil
then
debug_log("event", "marc location x " .. global.marc_win_loc_x )
end
if global.marc_win_loc_y ~= nil
then
debug_log("event", "marc location y " .. global.marc_win_loc_y)
end
elseif event.parameter == "resetloc"
then
global.marc_win_loc_x = 0
global.marc_win_loc_y = 176
else
game.players[event.player_index].print("unknown marc parameter: " .. event.parameter)
end
end
-- ----------------------------------------------------------------
script.on_event( defines.events.on_gui_selection_state_changed, on_gui_selection )
script.on_event( "marc_hotkey", on_hotkey_main )
script.on_event( defines.events.on_lua_shortcut, max_rate_shortcut )
script.on_event( defines.events.on_gui_click, on_gui_click)
script.on_event( defines.events.on_gui_text_changed, marcalc_on_gui_text_changed )
commands.add_command( "marc", "Max Rate Calculator [ debug | nodebug ] ", on_marc_command )