From efeb1ab8e062b31edc05d210f4280d6446b3052a Mon Sep 17 00:00:00 2001 From: Ben Gardner Date: Tue, 5 Dec 2023 06:09:50 -0600 Subject: [PATCH] save --- control.lua | 7 + data.lua | 1 + graphics/entities/network-tank-provider.png | Bin 8439 -> 34472 bytes graphics/entities/network-tank-provider.xcf | Bin 7407 -> 63521 bytes graphics/entities/network-tank-requester.png | Bin 10134 -> 35477 bytes graphics/entities/network-tank-requester.xcf | Bin 7407 -> 63521 bytes src/GlobalState.lua | 912 +++++++------ src/NetworkChest.lua | 1193 ++---------------- src/NetworkTankGui.lua | 153 ++- src/NetworkViewUi.lua | 3 - src/NetworkViewUi_test.lua | 96 +- src/ServiceEntity.lua | 152 ++- src/constants.lua | 9 +- src/prototypes/network-tanks.lua | 8 +- src/service_alerts.lua | 104 ++ src/service_players.lua | 69 + src/service_queue.lua | 914 ++++++++++++++ 17 files changed, 2038 insertions(+), 1583 deletions(-) create mode 100644 src/service_alerts.lua create mode 100644 src/service_players.lua create mode 100644 src/service_queue.lua diff --git a/control.lua b/control.lua index bb68e3a..85cf9f8 100644 --- a/control.lua +++ b/control.lua @@ -1,7 +1,14 @@ if script.active_mods["gvv"] then require("__gvv__.gvv")() end +-- need to register for on_tick() first +require "src.GlobalState" + require "src.NetworkChest" require "src.NetworkSensor" require "src.NetworkViewUi" require "src.NetworkViewUi_test" require "src.NetworkTankGui" + +require "src.service_players" +require "src.service_alerts" +require "src.service_queue" diff --git a/data.lua b/data.lua index 9116b43..3e7906f 100644 --- a/data.lua +++ b/data.lua @@ -4,6 +4,7 @@ local Hotkeys = require "src.Hotkeys" require "src.prototypes.network-sensor" require "src.prototypes.network-chests" +require "src.prototypes.network-tanks" local M = {} diff --git a/graphics/entities/network-tank-provider.png b/graphics/entities/network-tank-provider.png index 1214bd72ddd78fe343aef1575a0033c69c9a239f..0970b9572ca6dd9255f3ef6abb68b42143e0f192 100644 GIT binary patch delta 31349 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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%0D?wKl^i{Ig%^fF@$lhQ#YbFfs;&j~b z@5ySNUnvZT&)QI*Cc1+i+`#4q>`NTm;|m<`V62(XHj>s%ygsYP$qzc1&Qjgg zJkm0oFuEOB6A#6WN$nimHHY5}DXJI?^#tjaqhcQ5yxEczlosD?&&2zUdW#OSorO2~ z{zvHOB*q5QKT-u);)lugc0b~Bz>XN2Vi=E(QD1U#Vb4-el_jn8jcgo+ zKPU`OgS$2xVM1#|U9OXjYe#rc0BfL2DXzyfv6v6a6h5QE zy~BBZ4C#Lc1v2+$bSJ# ChH8cY delta 5196 zcmV-S6tnB7jRN;Ue~C~|M-2)Z3IG5A4M|8uQUCw}00000KnMl^008w}slEUJ4pe$n zSaechcOYMQNnhuGE%fyvEk4yBU##^W-@hK;?}B;! zaiKQ?NuN7MdoOqwlKe*WhOTc!ZI1rS)=b|!m-o>4@xMsVt+vz)ze|q2x5?*`W9j76 zJkYCoJ`2@Pe?q?xeEt1(Y9^+9BuV7A$Qz~jf04!U#P2LAaAJB6iHzrq`XoM5NeoVF zU�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 F{sW0vEQA06 literal 7407 zcmeI0&rcgi6vxM4?;3-D00u%ac*jW#QDg@vBq0(ZQBhl^YSpOeD(TTNYqJ|{BeO}! ziJW_H)kCHJ1MS}v_0mgkRWJPms_AD287%Z{Ez#``8|aZThVpXRqFH zUM^RwLWme99_Tl&@33(!rlXTsa^qX|hc^5?7RQFL>4-li#PIvH(@mx>*E_A2*A8i$ zsV6qNUM=)ComYOi<$Ut+^GCX8+^^TmJKavh-}cJwTFcYHVQlo_#)i}DpqVpM`mI{i zD>u9TrcTpK`)O%6e7)^CyPa)+^J;3T(X4m2J6&hVNV(g5R&$okt9q60XQy;m9rM!N zd%HWey5DX()n!_*kF|P@@{6_~I)1ypqj4({=_5K@DGtj&(&XU^J~=3c-sqK zb-K^XVXghVveRzrPW|iCmxbwHqr#;2@4w-lUo58MdV-OjkBCylFGT!Q#CIb8Xv80j z`00qB#n<=iUp9`ux1L|`2-?@z6)di}(901&x}y;8U-6&&-a#1#NN=wAF9&^vw>|ys zlw3jyT{~CmuLI5fd7#sW`TMb7MbcdnIro~lH=s4}7NT8PM9BSOe9T8Q_m8N}MbBx#V9OFx8 zsAS$8CdrAa!`K{SI#94=ZsurnB_xE*y+Qyl8p7;cF&(WSlXVE-!TtcVNX>^91 zNs%(KENtfRQ!p#QaX}PWL&5z(Ov5+h?#I3thPZ^dgt&ybgt&ybgt&ybgt&ybgt&w_ zren5*I5}Ev32_N=#%i@B#3jTf#8FE^yr*#!hj>IS30c!0RfwB7#3jU8!%2lW@dSC+ zF@-GcIPt#f^~70+eX2s*#QO?y+6n4OntC8kofh?uP`@o94i8&G92QCXQYHp**rni> zhKYnjku?+~#NnHfhr|Pj2M`Y+9zZ;RcmVML;sL}1hzAf4Adcyn9YCBMt#$zM0OE|* zY6TDvARa&*wE~FuG;ZP$kEj(u+Vn>i;wBF10OG9Sq(Yo{f;{V(LY8)%cwhB;;;h3y zRUvKSeT6vf1ob3MJrJi(i+V?>-wq%S4?BQ3ERyu4Obp_%OTjG-6A6bRYbXQ|hi@j( z#AifaEs26EiYsbHTvg+uqB5eYM#YLsVI$(Uvc(-`iMuM%v$%MVzPyTy8I=>`YFJFE zyvU(>=E$HEY8D-$uOQNBPR$$5s)e2w#TfC7a?qH_o}n2fP5)V%RwdJaQYH6^(I>e_ zR2{RfQ4=2$N9wQ!-F{b#tX;?Zx?t)YqaIyvTFsgIGt@glJF6}k&8cZIX*f*6M03f) zNb^cz39$;#Rl_wYZV|hw#>5Sk7R%&S)P%UEro@u^^k4@X?D>N|-|X>&J%6z05BB_% zdwu_J!2e~>|B?M}aL&J|bN*ZY%0hf_&cCp8evkOzoPS~G{I@&+9h~vsJ=^!&{j+^v zFY=7PFTN4`L;R;V*zqsyl>g*__+ZDsupPfge6ZtR*pB~1Jmsmw;K|O(Cp!OyUEe(H Qf5Q`g2h-OBET^5n0bFj_*8l(j diff --git a/graphics/entities/network-tank-requester.png b/graphics/entities/network-tank-requester.png index fe52f437cebe9696043811f63f87dd3caf1c6040..3b49fd1da8080e72e7e836827e7740123c7a0fe7 100644 GIT binary patch delta 32466 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%0A>@5y`= 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 delta 6905 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 z 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%0D?*!p~v+d^cf*UBFOt9BE-)lK?_ zh=E3r2ay8Yb^I)@I_@+7pT7X+yWrOyzvPFCUp2CUANJqBIMx3aD1lt98UMdc{L4** zCqDxC<;AZOezpGI#vd+Cvi%M}_^a`6+frm3zbyRz+s_i?-*(v8CcmT|ewM1<|GiDl z^RFK^Hpnl2miE5?yF#vopTqX=|6Uk=|Cb9H*lM}nj+Qxay=luzwF9|cQEFa+E!Q7S ztgO7ED$w<_vsn4H4Q*h8tIf&>zfb=NY#kL~C+KM1F+K+(IN?%OJ}iNKd=qM^xU!vc5^rSQJ$V# z+5|$t0rW$tFg>TNy0NKyh&Uqni_)`@qN}xg*VHO@Q`1-#buH~fxI)N#!zYrOSxG`?ZA0%T9 zg_9}nPy7!mPY|1a$4>$WNj>rcppxXZ;o

5N#rp2p?Mf1#~Fpn0Lq*@GG`R+!Sgl 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 diff --git a/src/GlobalState.lua b/src/GlobalState.lua index 677d690..458e1de 100644 --- a/src/GlobalState.lua +++ b/src/GlobalState.lua @@ -3,6 +3,7 @@ local tables_have_same_keys = require("src.tables_have_same_keys") .tables_have_same_keys local constants = require "src.constants" local clog = require("src.log_console").log +local Event = require('__stdlib__/stdlib/event/event') local M = {} @@ -64,35 +65,55 @@ Everything starts at priotirty 0 when created/added. - Logistics * Always lowest priority (no rush -- let the robots scurry) -]] + + + global.mod.entity_info = {} -- key=unit_number, val=table + entity_info contents: + { + entity = LuaEntity, -- reference to the entity (which may not be valid anymore) + service_type = "network-chest", -- service type used in a service look-up table + service_tick = 111, -- last tick when this was serviced + service_delta = 222, -- tick delta at last service + service_priority = 5, -- priority at the last service + -- the rest depend on the type + requests = { ... }, -- chest requests + recent_items = { ... }, -- recent items for auto-provider chest + config = { ... }, -- network-tank config + contents = { ... }, -- last get_contents() result for logistic Storage chest + } + ]] function M.inner_setup() + global.ammo_table = nil + global.fuel_table = nil + if global.mod == nil then global.mod = { rand = game.create_random_generator(), - chests = {}, items = {}, + item_limits = {} } end + if global.service_names ~= nil then + global.service_names = nil -- undo mistake -- called name_service_map, below + end + if global.mod.entity_info == nil then -- random info that is automatically removed when the entity is destroyed global.mod.entity_info = {} -- key=unit_number, val=table end - if global.mod.scan_queues == nil then + if global.mod.scan_queues == nil or (#global.mod.scan_queues ~= constants.QUEUE_COUNT) then M.reset_queues() end + M.log_queue_info() M.remove_old_ui() if global.mod.player_info == nil then global.mod.player_info = {} end - if global.mod.network_chest_has_been_placed == nil then - global.mod.network_chest_has_been_placed = true -- (global.mod.scan_queue.size + global.mod.scan_queue_inactive.size) > 0 - end - if global.mod.fluids == nil then global.mod.fluids = {} end @@ -102,146 +123,123 @@ function M.inner_setup() if global.mod.missing_fluid == nil then global.mod.missing_fluid = {} -- missing_fluid[key][unit_number] = { game.tick, count } end - if global.mod.tanks == nil then - global.mod.tanks = {} - end - if global.mod.vehicles == nil then - global.mod.vehicles = {} -- vehicles[unit_number] = entity - M.vehicle_scan_surfaces() - end - - if global.mod.serviced == nil then - global.mod.serviced = {} -- vehicles[unit_number] = entity - M.service_scan_surfaces() - end - - if global.mod.logistic == nil then - global.mod.logistic = {} -- key=unit_number, val=entity - end if global.mod.logistic_names == nil then global.mod.logistic_names = {} -- key=item name, val=logistic_mode from prototype end - local logistic_names = M.logistic_scan_prototypes() - if not tables_have_same_keys(logistic_names, global.mod.logistic_names) then - global.mod.logistic_names = logistic_names - global.mod.logistic = {} - M.logistic_scan_surfaces() + local name_service_map = M.scan_prototypes() + if not tables_have_same_keys(name_service_map, global.name_service_map) then + global.name_service_map = name_service_map + M.scan_surfaces() end - if global.mod.alert_trans == nil then - global.mod.alert_trans = {} -- alert_trans[unit_number] = game.tick + -- major upgrade to unified entity info table + if global.mod.chests ~= nil then + M.convert_to_entity_info() end - if not global.mod.has_run_fluid_temp_conversion then - local new_fluids = {} - for fluid, count in pairs(global.mod.fluids) do - local default_temp = game.fluid_prototypes[fluid].default_temperature - new_fluids[fluid] = {} - new_fluids[fluid][default_temp] = count - end - global.mod.fluids = new_fluids - local n_tanks = 0 - for _, entity in pairs(global.mod.tanks) do - n_tanks = n_tanks + 1 - if entity.config ~= nil then - entity.config.temperature = - game.fluid_prototypes[entity.config.fluid].default_temperature - end - end - if n_tanks > 0 then - clog( - "Migrated Item Network fluids to include temperatures. Warning: If you provide a fluid at a non-default temperature (like steam), you will have to update every requester tank to use the new fluid temperature.") - end - global.mod.has_run_fluid_temp_conversion = true + if global.mod.alert_trans == nil then + global.mod.alert_trans = {} -- alert_trans[unit_number] = game.tick end if global.mod.sensors == nil then global.mod.sensors = {} end - if global.mod.item_limits == nil then - global.mod.item_limits = {} - M.limit_scan() - end - -- TEST: remove when good - --M.limit_scan() - -- TEST: reset the queues; causes desync in multiplayer --M.reset_queues() end -function M.reset_queues() - global.mod.scan_deadline = 0 -- keep doing nothing until past this tick - global.mod.scan_index = 1 -- working on the queue in this index +local function generic_add_entity(entity, info) + if entity ~= nil and entity.valid then + local service_type = M.get_service_type_for_entity(entity.name) + if service_type == nil then + --clog("created unhandled %s [%s] %s", entity.name, entity.type, entity.unit_number) + return + end - global.mod.scan_queues = {} -- list of queues to process - for idx = 1, constants.QUEUE_COUNT do - -- key=unit_nubmer, val=priority - global.mod.scan_queues[idx] = {} + clog("generic_add [%s] => %s", entity.name, serpent.line(service_type)) + if type(service_type.create) == "function" then + service_type.create(entity, info or {}) + else + clog("ERROR: no create for %s", entity.name) + end end +end - if global.mod.chests ~= nil then - for unum, _ in pairs(global.mod.chests) do - M.queue_insert(unum, 1) + +function M.convert_to_entity_info() + --[[ + { + entity = LuaEntity, -- reference to the entity (which may not be valid anymore) + service_type = "network-chest", -- service type used in a service look-up table (required) + service_tick = 111, -- last tick when this was serviced (auto) + service_delta = 222, -- tick delta at last service (auto) + service_priority = 5, -- priority at the last service (auto) + -- the rest depend on the type + requests = { ... }, -- chest requests + recent_items = { ... }, -- recent items for auto-provider chest + config = { ... }, -- network-tank config + contents = { ... }, -- last get_contents() result for logistic Storage chest + } + ]] + + global.name_service_map = M.scan_prototypes() + + -- chests need a 'service_type' field + for unum, info in pairs(global.mod.chests or {}) do + if info.entity ~= nil and info.entity.valid then + generic_add_entity(info.entity, info) end end - if global.mod.tanks ~= nil then - for unum, _ in pairs(global.mod.tanks) do - M.queue_insert(unum, 2) + global.mod.chests = nil + + -- tanks need a 'service_type' field + for unum, info in pairs(global.mod.tanks or {}) do + if info.entity ~= nil and info.entity.valid then + generic_add_entity(info.entity, info) end end - if global.mod.vehicles ~= nil then - for unum, _ in pairs(global.mod.vehicles) do - M.queue_insert(unum, 3) - end + global.mod.tanks = nil + + -- vehicles was misnamed, as it only handles the spidertron + for unum, ent in pairs(global.mod.vehicles or {}) do + generic_add_entity(ent) end - if global.mod.logistic ~= nil then - for unum, _ in pairs(global.mod.logistic) do - M.queue_insert(unum, 4) - end + global.mod.vehicles = nil + + for unum, ent in pairs(global.mod.logistic or {}) do + generic_add_entity(ent) end + global.mod.logistic = nil + + for unum, ent in pairs(global.mod.serviced or {}) do + generic_add_entity(ent) + end + global.mod.serviced = nil + + M.scan_surfaces() end --- scan existing tanks and chests and use the max "give" limit as the item limit -function M.limit_scan(item) - local limits = global.mod.item_limits - local unlimited = constants.UNLIMITED +function M.reset_queues() + print("RESET SCAN QUEUE") + global.mod.scan_deadline = 0 -- keep doing nothing until past this tick + global.mod.scan_index = 1 -- working on the queue in this index - for _, info in pairs(global.mod.chests) do - if info.requests ~= nil then - for _, req in ipairs(info.requests) do - if req.type == "give" then - local old_limit = limits[req.item] - local new_limit = req.limit - if req.no_limit == true then - new_limit = unlimited - end - if old_limit == nil or old_limit < new_limit then - limits[req.item] = new_limit - end - end - end - end + global.mod.scan_queues = {} -- list of queues to process + for idx = 1, constants.QUEUE_COUNT do + -- key=unit_number, val=priority + global.mod.scan_queues[idx] = {} end - for _, info in pairs(global.mod.tanks) do - local config = info.config - if config ~= nil then - if config.type == "give" then - if config.temperature ~= nil and config.fluid ~= nil then - local key = M.fluid_temp_key_encode(config.fluid, config.temperature) - local old_limit = limits[key] - local new_limit = config.limit - if config.no_limit == true then - new_limit = unlimited - end - if old_limit == nil or old_limit < new_limit then - limits[config.fluid] = new_limit - --game.print(string.format("updated limit %s %s", key, config.limit)) - end - end - end + -- add all entities, spreading them evenly + local pri = 0 + for unum, info in pairs(global.mod.entity_info) do + info.service_priority = pri + M.queue_insert(unum, info) + pri = pri + 1 + if pri > constants.QUEUE_COUNT - 2 then + pri = 0 end end end @@ -412,31 +410,6 @@ function M.rand_hex(len) return table.concat(chars, "") end ---[[ -function M.shuffle(list) - for i = #list, 2, -1 do - local j = global.mod.rand(i) - list[i], list[j] = list[j], list[i] - end -end -]] - --- get a table of all logistic item names that we should supply --- called once at each startup to see if the chest list changed -function M.logistic_scan_prototypes() - local info = {} -- key=name, val=logistic_mode - -- find all with type="logistic-container" and (logistic_mode="requester" or logistic_mode="buffer") - for name, prot in pairs(game.get_filtered_entity_prototypes { { - filter = "type", - type = "logistic-container", - } }) do - if prot.logistic_mode == "requester" or prot.logistic_mode == "buffer" or prot.logistic_mode == "storage" then - info[name] = prot.logistic_mode - end - end - return info -end - function M.is_logistic_entity(item_name) return global.mod.logistic_names[item_name] ~= nil end @@ -459,62 +432,81 @@ function M.logistic_scan_surfaces() end end +------------------------------------------------------------------------------- + function M.entity_info_get(unit_number) return global.mod.entity_info[unit_number] end +-- grab and filter +function M.entity_info_get_type(unit_number, service_type) + local info = global.mod.entity_info[unit_number] + if info ~= nil and info.service_type == service_type then + return info + end +end + function M.entity_info_set(unit_number, info) + info.unit_number = unit_number global.mod.entity_info[unit_number] = info end +function M.entity_info_clear(unit_number) + global.mod.entity_info[unit_number] = nil +end + +------------------------------------------------------------------------------- + function M.get_logistic_entity(unit_number) - return global.mod.logistic[unit_number] + return M.entity_info_get_type(unit_number, "logistic-chest") end function M.logistic_add_entity(entity) - if global.mod.logistic[entity.unit_number] == nil then - global.mod.logistic[entity.unit_number] = entity - M.queue_insert(entity.unit_number, 0) - --Queue.push(global.mod.scan_queue, entity.unit_number) - end + M.generic_add_entity(entity) end -function M.logistic_del(unit_number) - global.mod.logistic[unit_number] = nil -end +-- tags is a table that may contain the 'tag' field in the service_task registration +-- for example, a chest may set: tags={ requests={...} } +function M.generic_add_entity(entity, tags) + local unit_number = entity.unit_number + if M.entity_info_get(unit_number) == nil then + local info = { + service_type = M.get_service_type_for_entity(entity.name), + entity = entity, + } -function M.logistic_get_list() - return global.mod.logistic + local svc_func = M.get_service_task(info.service_type) + if svc_func == nil then + clog("genric : nothing for %s", serpent.line(info)) + end +if svc_func ~= nil and svc_func.tag ~= nil then + if tags ~= nil then + info[svc_func.tag] = tags[svc_func.tag] or {} + end + end + M.entity_info_set(unit_number, info) + M.queue_insert(unit_number, info) + end end +------------------------------------------------------------------------------- + function M.is_vehicle_entity(name) return name == "spidertron" end -function M.vehicle_scan_surfaces() +function M.spidertron_scan_surfaces() for _, surface in pairs(game.surfaces) do local entities = surface.find_entities_filtered { name = "spidertron" } for _, entity in ipairs(entities) do - M.vehicle_add_entity(entity) + M.spidertron_add_entity(entity) end end end -function M.get_vehicle_entity(unit_number) - return global.mod.vehicles[unit_number] -end - -- add a vehicle, assume the caller knows what he is doing -function M.vehicle_add_entity(entity) - if global.mod.vehicles[entity.unit_number] == nil then - global.mod.vehicles[entity.unit_number] = entity - M.queue_insert(entity.unit_number, 0) - --Queue.push(global.mod.scan_queue, entity.unit_number) - end -end - -function M.vehicle_del(unit_number) - global.mod.vehicles[unit_number] = nil +function M.spidertron_add_entity(entity) + M.generic_add_entity(entity) end ------------------------------------------------------------------------------- @@ -567,21 +559,15 @@ function M.service_scan_surfaces() end function M.get_service_entity(unit_number) - return global.mod.serviced[unit_number] + return M.entity_info_get_type(unit_number, "general-service") end function M.service_add_entity(entity) - if global.mod.serviced[entity.unit_number] == nil then - global.mod.serviced[entity.unit_number] = entity - M.queue_insert(entity.unit_number, 0) - end -end - -function M.service_del(unit_number) - global.mod.serviced[unit_number] = nil + M.generic_add_entity(entity) end ------------------------------------------------------------------------------- +-- Sensors are NOT treated the same as other entities. function M.sensor_add(entity) global.mod.sensors[entity.unit_number] = entity @@ -595,27 +581,10 @@ function M.sensor_get_list() return global.mod.sensors end -function M.register_chest_entity(entity, requests) - if requests == nil then - requests = {} - end - - if global.mod.chests[entity.unit_number] ~= nil then - return - end - - M.queue_insert(entity.unit_number, 0) - --Queue.push(global.mod.scan_queue, entity.unit_number) - global.mod.chests[entity.unit_number] = { - entity = entity, - requests = requests, - } - - global.mod.network_chest_has_been_placed = true -end +------------------------------------------------------------------------------- -function M.delete_chest_entity(unit_number) - global.mod.chests[unit_number] = nil +function M.register_chest_entity(entity, requests) + M.generic_add_entity(entity, { requests = requests or {} }) end --[[ @@ -655,27 +624,14 @@ function M.put_chest_contents_in_network(entity) M.put_inventory_in_network(entity.get_output_inventory()) end -function M.register_tank_entity(entity, config) - if global.mod.tanks[entity.unit_number] ~= nil then - return - end - if config == nil then - -- will get replaced on the first tick - config = { - type = "auto", - } - end +------------------------------------------------------------------------------- - M.queue_insert(entity.unit_number, 0) - --Queue.push(global.mod.scan_queue, entity.unit_number) - global.mod.tanks[entity.unit_number] = { - entity = entity, - config = config, - } +function M.register_tank_entity(entity, config) + M.generic_add_entity(entity, { config=config or { type="auto" } }) end function M.delete_tank_entity(unit_number) - global.mod.tanks[unit_number] = nil + M.entity_info_clear(unit_number) end function M.put_tank_contents_in_network(entity) @@ -689,28 +645,47 @@ function M.put_tank_contents_in_network(entity) entity.clear_fluid_inside() end -function M.get_chest_info(unit_number) - return global.mod.chests[unit_number] +function M.put_contents_in_network(entity) + -- drain any fluid + local fluidbox = entity.fluidbox + for idx = 1, #fluidbox do + local fluid = fluidbox[idx] + if fluid ~= nil then + M.increment_fluid_count(fluid.name, fluid.temperature, fluid.amount) + end + end + entity.clear_fluid_inside() + + -- return any inventory + for idx = 1, entity.get_max_inventory_index() do + M.put_inventory_in_network(entity.get_output_inventory(idx)) + end end -function M.get_chests() - return global.mod.chests + +-- grab the entity info and ensure that it is +function M.get_chest_info(unit_number) + return M.entity_info_get_type(unit_number, "network-chest") end function M.get_tank_info(unit_number) - return global.mod.tanks[unit_number] + return M.entity_info_get_type(unit_number, "network-tank") end function M.copy_chest_requests(source_unit_number, dest_unit_number) - -- REVISIT: creating two chests with the same requests field. Intentional? - global.mod.chests[dest_unit_number].requests = - global.mod.chests[source_unit_number].requests + local src_info = M.get_chest_info(source_unit_number) + local dst_info = M.get_chest_info(dest_unit_number) + if src_info ~= nil and dst_info ~= nil then + dst_info.requests = table.deepcopy(src_info.requests) + end end function M.copy_tank_config(source_unit_number, dest_unit_number) - -- REVISIT: creating two tanks with the same config field. Intentional? - global.mod.tanks[dest_unit_number].config = - global.mod.tanks[source_unit_number].config + local src_info = M.get_tank_info(source_unit_number) + local dst_info = M.get_tank_info(dest_unit_number) + if src_info ~= nil and dst_info ~= nil then + dst_info.config = table.deepcopy(src_info.config) + end end function M.set_chest_requests(unit_number, requests) @@ -718,9 +693,11 @@ function M.set_chest_requests(unit_number, requests) if info == nil then return end - global.mod.chests[unit_number].requests = requests + info.requests = requests end +------------------------------------------------------------------------------- + function M.get_item_count(item_name) return global.mod.items[item_name] or 0 end @@ -809,7 +786,7 @@ end M.UPDATE_STATUS = { INVALID = nil, -- causes the entity to be removed UPDATE_PRI_INC = -1, -- entity needed service - UPDATE_PRI_SAME = 0, -- entity needed service + UPDATE_PRI_SAME = 0, -- entity needed service, but current pri is OK UPDATE_PRI_DEC = 1, -- entity did not need service UPDATE_PRI_MAX = constants.QUEUE_COUNT - 1, -- aliases @@ -819,40 +796,6 @@ M.UPDATE_STATUS = { NOT_UPDATED = constants.QUEUE_COUNT - 1, } -function M.update_queue(update_entity) - local MAX_ENTITIES_TO_UPDATE = settings.global - ["item-network-number-of-entities-per-tick"] - .value - local updated_entities = {} - - local function inner_update_entity(unit_number) - if updated_entities[unit_number] ~= nil then - return M.UPDATE_STATUS.ALREADY_UPDATED - end - updated_entities[unit_number] = true - - return update_entity(unit_number) - end - - for _ = 1, MAX_ENTITIES_TO_UPDATE do - local unit_number = Queue.pop(global.mod.scan_queue) - if unit_number == nil then - break - end - - local status = inner_update_entity(unit_number) - if status == M.UPDATE_STATUS.NOT_UPDATED or - status == M.UPDATE_STATUS.UPDATED or - status == M.UPDATE_STATUS.ALREADY_UPDATED - then - Queue.push(global.mod.scan_queue, unit_number) - end - end - - -- finally, swap a random entity to the front of the queue to introduce randomness in update order. - --Queue.swap_random_to_front(global.mod.scan_queue, global.mod.rand) -end - -- translate a tile name to the item name ("stone-path" => "stone-brick") function M.resolve_name(name) if game.item_prototypes[name] ~= nil or game.fluid_prototypes[name] ~= nil then @@ -887,121 +830,35 @@ function M.resolve_name(name) return nil end -function M.update_queue_log() - clog("item-network queue sizes: active: %s inactive: %s", global.mod.scan_queue.size, global.mod.scan_queue_inactive.size) -end - -function M.update_queue_dual(update_entity) - local MAX_ENTITIES_TO_UPDATE = settings.global - ["item-network-number-of-entities-per-tick"] - .value - local updated_entities = {} - - -- peek the first entry. If we haven't processed it, then pop and return it. - local function pop_from_q(q) - local unum = Queue.get_front(q) - if unum ~= nil and updated_entities[unum] == nil then - updated_entities[unum] = true - return Queue.pop(q) - end - return nil - end +------------------------------------------------------------------------------- - local toggle = true - for _ = 1, MAX_ENTITIES_TO_UPDATE do - local unit_number - if toggle then - unit_number = pop_from_q(global.mod.scan_queue) - if unit_number == nil then - unit_number = pop_from_q(global.mod.scan_queue_inactive) - if unit_number == nil then - break - end - end - else - unit_number = pop_from_q(global.mod.scan_queue_inactive) - if unit_number == nil then - unit_number = pop_from_q(global.mod.scan_queue) - if unit_number == nil then - break - end - end - end - toggle = not toggle +M.service_tasks = {} - local status = update_entity(unit_number) - if status == M.UPDATE_STATUS.UPDATED then - Queue.push(global.mod.scan_queue, unit_number) - elseif status == M.UPDATE_STATUS.NOT_UPDATED then - Queue.push(global.mod.scan_queue_inactive, unit_number) - end +-- Register a service task. +function M.register_service_task(service_type, funcs) + if funcs == nil or funcs.create == nil or funcs.service == nil then + assert(false, string.format("register_service_task: incorrect usage %s", serpent.line(funcs))) + end + M.service_tasks[service_type] = funcs + clog("added service %s", service_type) + for k, v in pairs(funcs) do + clog(' - %s : %s', k, type(v)) end end -function M.update_queue_multi(update_entity) - local weight_fast = 10 - local weight_med = 6 - local weight_slow = 4 - local MAX_ENTITIES_TO_UPDATE = (weight_fast + weight_med + weight_slow) - local updated_entities = {} - - local function inner_update_entity(unit_number) - if updated_entities[unit_number] ~= nil then - return M.UPDATE_STATUS.ALREADY_UPDATED - end - updated_entities[unit_number] = true - return update_entity(unit_number) +function M.get_service_task(service_type) + local retval = M.service_tasks[service_type or ""] + if retval == nil then + clog("get_service_task: nothing for %s", service_type) end + return retval +end - for _ = 1, MAX_ENTITIES_TO_UPDATE do - local unit_number - if weight_slow > 0 then - weight_slow = weight_slow - 1 - unit_number = Queue.pop(global.mod.scan_queue_slow) - end - if unit_number == nil and weight_med > 0 then - weight_med = weight_med - 1 - unit_number = Queue.pop(global.mod.scan_queue_med) - end - if unit_number == nil then - unit_number = Queue.pop(global.mod.scan_queue_fast) - end - if unit_number == nil then - unit_number = Queue.pop(global.mod.scan_queue_med) - end - if unit_number == nil then - unit_number = Queue.pop(global.mod.scan_queue_slow) - end - if unit_number == nil then - break - end - - local status = inner_update_entity(unit_number) - if status == M.UPDATE_STATUS.NOT_UPDATED or - status == M.UPDATE_STATUS.UPDATED or - status == M.UPDATE_STATUS.ALREADY_UPDATED - then - -- update service_count - local service_count = global.mod.entity_service_counts[unit_number] or 0 - if status == M.UPDATE_STATUS.NOT_UPDATED then - service_count = service_count - 1 - elseif status == M.UPDATE_STATUS.UPDATED then - service_count = math.max(0, service_count) + 1 - end - global.mod.entity_service_counts[unit_number] = service_count - - if service_count >= 2 then - Queue.push(global.mod.scan_queue_fast, unit_number) - elseif service_count < -60 then - Queue.push(global.mod.scan_queue_slow, unit_number) - else - Queue.push(global.mod.scan_queue_med, unit_number) - end - end +function M.get_service_type_for_entity(name) + if global.name_service_map == nil then + error("called before init: get_service_type_for_entity") end - - -- finally, swap a random entity to the front of the queue to introduce randomness in update order. - --Queue.swap_random_to_front(global.mod.scan_queue, global.mod.rand) + return global.name_service_map[name] end --[[ @@ -1009,17 +866,19 @@ end 0 is the highest priority and will put the entity in the next queue slot. The largest value (lowest priority) is (constants.QUEUE_COUNT - 1). That will put the entity in the previous queue slot. + Uses (info.service_priority or 0) as the priority. ]] -function M.queue_insert(unit_number, priority) +function M.queue_insert(unit_number, info) -- clamp priority to 0..QUEUE_COUNT-2 (inclusive) - priority = math.max(0, math.min(priority, constants.QUEUE_COUNT - 2)) + local priority = math.max(0, math.min(info.service_priority or 0, constants.QUEUE_COUNT - 2)) -- q_idx is where to put it, Priority 0 => scan_index + 1 -- scan_index is 1-based, so we subtract 1 and then add 1 (scan_index - 1 + 1 + priority) local q_idx = 1 + (global.mod.scan_index + priority) % constants.QUEUE_COUNT - global.mod.scan_queues[q_idx][unit_number] = priority - + global.mod.scan_queues[q_idx][unit_number] = info --print(string.format("[%s] ADD q %s unum %s si=%s p=%s qx=%s", game.tick, q_idx, unit_number, global.mod.scan_index, priority, constants.QUEUE_COUNT)) + + info.service_priority = priority end --[[ @@ -1029,7 +888,7 @@ is processed on the next tick. The function is passed in to avoid circular dependencies. ]] -function M.update_queue_lists(update_entity) +function M.queue_service() local MAX_ENTITIES_TO_UPDATE = settings.global ["item-network-number-of-entities-per-tick"] .value @@ -1038,11 +897,17 @@ function M.update_queue_lists(update_entity) local q_idx = global.mod.scan_index or 1 if q_idx < 1 or q_idx > #qs then q_idx = 1 + global.mod.scan_index = q_idx + if global.mod.scan_queue_start_tick ~= nil then + local dt = game.tick - global.mod.scan_queue_start_tick + --clog("queue complete in %.1f sec (%s ticks)", dt / 60, dt) + end + global.mod.scan_queue_start_tick = game.tick end local q = qs[q_idx] for _ = 1, MAX_ENTITIES_TO_UPDATE do - local unit_number, old_pri = next(q) + local unit_number, info = next(q) if unit_number == nil then -- nothing to process in this queue. step to the next one if past the deadline if game.tick >= (global.mod.scan_deadline or 0) then @@ -1052,19 +917,87 @@ function M.update_queue_lists(update_entity) end return end + -- remove from the active queue q[unit_number] = nil --print(string.format("[%s] PROC q %s unum=%s pri=%s", game.tick, q_idx, unit_number, old_pri)) - -- the UPDATE_STATUS are actually relative priorities. - local pri_adj = update_entity(unit_number, old_pri) - -- nil means entity is invalid. - if pri_adj ~= nil then - M.queue_insert(unit_number, old_pri + pri_adj) + if type(info) == "number" then + local pri = info + info = M.entity_info_get(unit_number) + if info ~= nil then + info.service_priority = pri + end + end + + if info ~= nil and info.entity ~= nil and info.entity.valid then + if info.entity.to_be_deconstructed() then + -- we don't remove or servive to-be-deconstructed entities + info.service_priority = M.UPDATE_STATUS.NOT_UPDATED + M.queue_insert(unit_number, info) + + else + -- Would be really nice to cache this lookup, but can't store functions in save file. + local func = M.get_service_task(info.service_type) + if func == nil then + clog("service: nothing for %s", serpent.line(info)) + end + if func ~= nil and func.service ~= nil then + -- the UPDATE_STATUS values are relative priorities. + local pri_adj = func.service(info) + if pri_adj ~= nil then + if info.service_tick ~= nil then + info.service_tick_delta = game.tick - info.service_tick + end + info.service_tick = game.tick + + info.service_priority = (info.service_priority or 0) + pri_adj + M.queue_insert(unit_number, info) + end + else + clog("WARNING: no handler for [%s] - dropped", serpent.line(info)) + for k, _ in pairs(M.service_tasks) do + clog("avail: %s", k) + end + end + end end end end -M.update_queue = M.update_queue_lists +-- grab the priority for the entity from the service queues +function M.get_priority(unit_number) + for _, qq in ipairs(global.mod.scan_queues) do + local pri = qq[unit_number] + if pri ~= nil then + return pri + end + end +end + +-- create a histogram of priorities +function M.get_priority_histogram() + local h = {} -- key=pri, val=count + for _, qq in ipairs(global.mod.scan_queues) do + for _, pri in pairs(qq) do + h[pri] = (h[pri] or 0) + 1 + end + end + return h +end + +function M.log_queue_info() + local h = M.get_priority_histogram() + clog("priority hist: %s", serpent.line(h)) + + local qs = global.mod.scan_queues + local cnt = 0 + for idx = 1, #qs do + local ts = table_size(qs[idx]) + clog("queue %s: size=%s", idx, ts) + cnt = cnt + ts + end + clog("total %s", cnt) +end function M.log_entity(title, entity) if entity ~= nil then @@ -1078,6 +1011,7 @@ function M.log_entity(title, entity) end end + --[[ Automatically configure a chest to request ingredients needed for linked assemblers. local buffer_size = settings.global @@ -1170,4 +1104,224 @@ function M.auto_network_chest(entity) return requests, provides end +function M.get_fuel_table() + if global.fuel_table == nil then + local fuels = {} -- array { name=name, val=energy per stack, cat=category } + for _, prot in pairs(game.item_prototypes) do + local fc = prot.fuel_category + if fc ~= nil then + table.insert(fuels, { name=prot.name, val=prot.stack_size * prot.fuel_value, cat=fc }) + end + end + table.sort(fuels, function (a, b) return a.val > b.val end) + + print(string.format("fuel table: %s", serpent.line(fuels))) + global.fuel_table = fuels + end + return global.fuel_table +end + +function M.get_best_available_fuel(entity) + --clog("called get best fuel on %s", entity.name) + local bprot = entity.prototype.burner_prototype + if bprot ~= nil then + local fct = bprot.fuel_categories + --clog(" fuel cat: %s", serpent.line(fct)) + local ff = M.get_fuel_table() + --clog(" fuel tab: %s", serpent.line(ff)) + for _, fuel in ipairs(ff) do + if fct[fuel.cat] ~= nil then + local n_avail = M.get_item_count(fuel.name) + if n_avail > 0 then + --clog("FUEL: %s can use %s and we have %s", entity.name, fuel.name, n_avail) + return fuel.name, n_avail + end + --clog("FUEL: %s can use %s, but we are out", entity.name, fuel.name) + end + end + end + return nil +end + +local function recurse_find_damage(tab) + if tab.type == 'damage' and tab.damage ~= nil then + return tab.damage + end + for k, v in pairs(tab) do + if type(v) == 'table' then + local rv = recurse_find_damage(v) + if rv ~= nil then + return rv + end + end + end + return nil +end + +--[[ +Collect a decreasing list of ammos byte damage / type. +]] +function M.get_ammo_table() + if global.ammo_table == nil then + local ammo_list = {} + for _, prot in pairs(game.item_prototypes) do + if prot.type == "ammo" then + local at = prot.get_ammo_type() + if at ~= nil then + local damage = recurse_find_damage(at.action) + if damage ~= nil and type(damage.amount) == "number" then + local xx = ammo_list[at.category] + if xx == nil then + xx = {} + ammo_list[at.category] = xx + end + table.insert(xx, { name=prot.name, amount=damage.amount }) + end + end + end + end + for k, xx in pairs(ammo_list) do + table.sort(xx, function (a, b) return a.amount > b.amount end) + end + + -- reduce to a list of names + local ammo_table = {} + for cat, xxx in pairs(ammo_list) do + local aa = {} + ammo_table[cat] = aa + for _, row in ipairs(xxx) do + table.insert(aa, row.name) + end + end + + -- hard code some projectile stuff that I haven't figured out how to detect + ammo_table['cannon-shell'] = { + "explosive-uranium-cannon-shell", + "explosive-cannon-shell", + "cannon-shell" + } + + ammo_table['flamethrower'] = { "flamethrower-ammo" } + + print(string.format("ammo table: %s", serpent.line(ammo_table))) + global.ammo_table = ammo_table + end + return global.ammo_table +end + +--[[ +Grab the best ammo for the category. +@category is typically one of "bullet", "rocket", "flamethrower", etc +]] +function M.get_best_available_ammo(category) + -- haven't figured out a good way to do anything other than bullets + local ammo_list = M.get_ammo_table()[category] + if ammo_list ~= nil then + for _, ammo_name in ipairs(ammo_list) do + local n_avail = M.get_item_count(ammo_name) + if n_avail > 0 then + clog("AMMO: %s can use %s and we have %s", category, ammo_name, n_avail) + return ammo_name, n_avail + end + clog("AMMO: %s can use %s, but we are out", category, ammo_name) + end + else + clog("no ammo list for cat %s", category) + end +end + +function M.last_service_set(unit_number) + global.mod.last_service[unit_number] = game.tick +end + +function M.last_service_get(unit_number) + return global.mod.last_service[unit_number] or 0 +end + +function M.last_service_clear(unit_number) + global.mod.last_service[unit_number] = nil +end + +------------------------------------------------------------------------------- + +function M.scan_prototypes() + -- key=entity_name, val=service_type + local name_to_service = { + -- add built-in stuff + ["network-chest"] = "network-chest", + ["network-chest-provider"] = "network-chest-provider", + ["network-chest-requester"] = "network-chest-requester", + ["network-tank"] = "network-tank", + ["network-tank-provider"] = "network-tank-provider", + ["network-tank-requester"] = "network-tank-requester", + ["spidertron"] = "spidertron", + } + + local type_to_service = { + ["spider-vehicle"] = "spidertron", + ["car"] = "general-service", -- "car", + ["furnace"] = "general-service", -- "furnace", + ["assembling-machine"] = "general-service", -- "assembling-machine", + ["ammo-turret"] = "general-service", -- "ammo-turret", + ["artillery-turret"] = "general-service", -- "artillery-turret", + ["burner-generator"] = "general-service", -- "burner-generator", + } + + for _, prot in pairs(game.entity_prototypes) do + if prot.type == "logistic-container" then + if prot.logistic_mode == "requester" or prot.logistic_mode == "buffer" or prot.logistic_mode == "storage" then + name_to_service[prot.name] = "logistic-chest-" .. prot.logistic_mode + end + else + local ss = type_to_service[prot.type] + if ss ~= nil then + name_to_service[prot.name] = ss + else + -- check for a 'general-service' + local add_it = false + if prot.has_flag("player-creation") then + -- check for stuff that burns coal + if prot.burner_prototype ~= nil and prot.burner_prototype.fuel_categories.chemical == true then + add_it = true -- refueling + elseif prot.name == "burner-lab" or prot.name == "burner-inserter" then + add_it = true + end + end + if add_it then + name_to_service[prot.name] = "general-service" + end + end + end + end + return name_to_service +end + +-- called once at startup if scan_prototypes() returns something different +function M.scan_surfaces() + clog("IN: Scanning surface...") + local name_filter = {} + for name, _ in pairs(global.name_service_map) do + table.insert(name_filter, name) + end + for _, surface in pairs(game.surfaces) do + local entities = surface.find_entities_filtered { name = name_filter } + for _, ent in ipairs(entities) do + M.generic_add_entity(ent) + end + end +end + +------------------------------------------------------------------------------- + +-- not sure this belongs here... +Event.on_configuration_changed(function () + -- need to rescan the fuel table + global.ammo_table = nil + global.fuel_table = nil +end) + +-- need to run as soon as 'game' is available +Event.on_nth_tick(1, M.setup) +--Event.on_init(M.setup) + return M diff --git a/src/NetworkChest.lua b/src/NetworkChest.lua index 5f22ba0..fc487d8 100644 --- a/src/NetworkChest.lua +++ b/src/NetworkChest.lua @@ -5,59 +5,39 @@ local NetworkViewUi = require "src.NetworkViewUi" local UiConstants = require "src.UiConstants" local Event = require('__stdlib__/stdlib/event/event') local util = require("util") -- from core/lualib -local clog = require("src.log_console").log -local tabutils = require("src.tables_have_same_keys") local constants = require("constants") -local NetworkTankAutoConfig = require("src.NetworkTankAutoConfig") -local ServiceEntity = require("src.ServiceEntity") +local clog = require("src.log_console").log local M = {} -function M.on_init() - GlobalState.setup() -end - -function M.on_create(event, entity) - local requests = {} - - if event.tags ~= nil then - local requests_tag = event.tags.requests - if requests_tag ~= nil then - requests = requests_tag - end - end +--function M.on_init() + --GlobalState.setup() +--end - GlobalState.register_chest_entity(entity, requests) -end local function generic_create_handler(event) + --clog("generic create %s", serpent.line(event)) local entity = event.created_entity or event.entity or event.destination if entity == nil then return end - if entity.name == "network-chest" then - M.on_create(event, entity) - elseif entity.name == "network-chest-provider" then - M.on_create(event, entity) - elseif entity.name == "network-chest-requester" then - M.on_create(event, entity) - elseif constants.NETWORK_TANK_NAMES[entity.name] ~= nil then - local config = nil - if event.tags ~= nil then - local config_tag = event.tags.config - if config_tag ~= nil then - config = config_tag - end - end - GlobalState.register_tank_entity(entity, config) - elseif GlobalState.is_logistic_entity(entity.name) then - GlobalState.logistic_add_entity(entity) - elseif GlobalState.is_vehicle_entity(entity.name) then - GlobalState.vehicle_add_entity(entity) - elseif GlobalState.is_service_entity(entity.name) then - GlobalState.service_add_entity(entity) - --else - -- clog("created unhandled %s [%s] %s", entity.name, entity.type, entity.unit_number) + local service_type = GlobalState.get_service_type_for_entity(entity.name) + if service_type == nil then + clog("created unhandled %s [%s] %s", entity.name, entity.type, entity.unit_number) + return + end + + local svc_func = GlobalState.get_service_task(service_type) + if svc_func == nil then + clog("ERROR: no def for %s", service_type) + return + end + + clog("generic_create_handler [%s] => %s", entity.name, serpent.line(svc_func)) + if type(svc_func.create) == "function" then + svc_func.create(entity, event.tags) + else + clog("ERROR: no create for %s", entity.name) end end @@ -70,34 +50,39 @@ function M.script_raised_built(event) end function M.on_entity_cloned(event) - -- only handle same-type clones + -- only handle same-name clones if event.source.name ~= event.destination.name then return end local name = event.source.name - if util.string_starts_with(name, "network-chest") then - GlobalState.register_chest_entity(event.destination) - local source_info = GlobalState.get_chest_info(event.source.unit_number) - local dest_info = GlobalState.get_chest_info(event.destination.unit_number) - if source_info ~= nil and dest_info ~= nil then - dest_info.requests = source_info.requests - end - elseif constants.NETWORK_TANK_NAMES[name] ~= nil then - GlobalState.register_tank_entity(event.source) - GlobalState.register_tank_entity(event.destination) - GlobalState.copy_tank_config( - event.source.unit_number, - event.destination.unit_number - ) - elseif GlobalState.is_logistic_entity(name) then - GlobalState.logistic_add_entity(event.destination) - elseif GlobalState.is_vehicle_entity(name) then - GlobalState.vehicle_add_entity(event.destination) - elseif GlobalState.is_vehicle_entity(name) then - GlobalState.vehicle_add_entity(event.destination) - elseif GlobalState.is_service_entity(name) then - GlobalState.service_add_entity(event.destination) + -- grab the service_type for the entity name + local svc_type = GlobalState.get_service_type_for_entity(name) + if svc_type == nil then + -- entity not handled + return + end + -- grab the functions for the service_type + local svc_func = GlobalState.get_service_task(svc_type) + if svc_func == nil then + -- how did we get called? + error(string.format("No functions for service_type %s", svc_type)) + return + end + + -- create the dest, if needed + svc_func.create(event.destination) + + -- see if there is a clone method for this type + if type(svc_func.clone) ~= "function" then + return + end + + -- make sure both instances exist + local dst_info = GlobalState.entity_info_get(event.destination.unit_number) + local src_info = GlobalState.entity_info_get(event.source.unit_number) + if dst_info ~= nil and src_info ~= nil then + svc_func.clone(dst_info, src_info) end end @@ -120,37 +105,28 @@ function M.generic_destroy_handler(event, opts) return end - GlobalState.entity_info_set(unit_number, nil) - - if util.string_starts_with(entity.name, "network-chest") then - GlobalState.put_chest_contents_in_network(entity) - if not opts.do_not_delete_entity then - GlobalState.delete_chest_entity(unit_number) - end - if global.mod.network_chest_gui ~= nil and - global.mod.network_chest_gui.entity.unit_number == unit_number - then - global.mod.network_chest_gui.frame.destroy() - global.mod.network_chest_gui = nil - end - - elseif constants.NETWORK_TANK_NAMES[entity.name] ~= nil then - GlobalState.put_tank_contents_in_network(entity) - if not opts.do_not_delete_entity then - GlobalState.delete_tank_entity(unit_number) - end - - elseif GlobalState.is_logistic_entity(entity.name) then - GlobalState.put_chest_contents_in_network(entity) - GlobalState.logistic_del(unit_number) + local info = GlobalState.entity_info_get(unit_number) + if info == nil then + return + end - elseif GlobalState.is_vehicle_entity(entity.name) then - GlobalState.vehicle_del(unit_number) + -- put any fluids or items in the network + GlobalState.put_contents_in_network(entity) - elseif GlobalState.is_service_entity(entity.name) then - GlobalState.service_del(unit_number) + if not opts.do_not_delete_entity then + GlobalState.entity_info_clear(unit_number) + end + -- TODO: close the network chest main GUI + -- close the network chest GUI pop-up + if global.mod.network_chest_gui ~= nil and + global.mod.network_chest_gui.entity.unit_number == unit_number + then + global.mod.network_chest_gui.frame.destroy() + global.mod.network_chest_gui = nil end + + -- TODO: close the network tank GUI end function M.on_player_mined_entity(event) @@ -174,32 +150,25 @@ function M.on_entity_died(event) end function M.on_marked_for_deconstruction(event) - if event.entity.name == "network-chest" then - GlobalState.put_chest_contents_in_network(event.entity) - elseif constants.NETWORK_TANK_NAMES[event.entity.name] ~= nil then - GlobalState.put_tank_contents_in_network(event.entity) - end + -- put any fluids or items in the network + GlobalState.put_contents_in_network(event.entity) end function M.on_post_entity_died(event) if event.unit_number ~= nil then - GlobalState.logistic_del(event.unit_number) - - local original_entity = GlobalState.get_chest_info(event.unit_number) - if original_entity ~= nil then + local info = GlobalState.entity_info_get(event.unit_number) + if info ~= nil then if event.ghost ~= nil then - event.ghost.tags = { requests = original_entity.requests } - end - GlobalState.delete_chest_entity(event.unit_number) - else - -- it might be a tank - local tank_info = GlobalState.get_tank_info(event.unit_number) - if tank_info ~= nil then - GlobalState.delete_tank_entity(event.unit_number) - if event.ghost ~= nil then - event.ghost.tags = { config = tank_info.config } + if info.requests ~= nil then + -- network-chest + event.ghost.tags = { requests = info.requests } + elseif info.config ~= nil then + -- network-tank + event.ghost.tags = { config = info.config } end end + + GlobalState.entity_info_clear(event.unit_number) end end end @@ -284,632 +253,36 @@ end function M.on_entity_settings_pasted(event) local source = event.source local dest = event.destination - if dest.name == "network-chest" then - if source.name == "network-chest" then - GlobalState.copy_chest_requests(source.unit_number, dest.unit_number) - elseif source.type == "assembling-machine" then - local recipe = source.get_recipe() - if recipe ~= nil then - local requests = {} - local buffer_size = settings.global - ["item-network-stack-size-on-assembler-paste"].value - for _, ingredient in ipairs(recipe.ingredients) do - if ingredient.type == "item" then - local stack_size = game.item_prototypes[ingredient.name].stack_size - local buffer = math.min(buffer_size, stack_size) - table.insert(requests, { - type = "take", - item = ingredient.name, - buffer = buffer, - limit = 0, - }) - end - end - GlobalState.set_chest_requests(dest.unit_number, requests) - end - elseif source.type == "container" then - -- Turn filter slots into requests, if any. Works for 'se-rocket-launch-pad' - local inv = source.get_output_inventory() - if inv.is_filtered() then - -- clog("SOURCE: %s t=%s inv slots=%s filt=%s", source.name, source.type, #inv, inv.is_filtered()) - local requests = {} - local buffer_size = settings.global["item-network-stack-size-on-assembler-paste"].value - for idx=1,#inv do - local ff = inv.get_filter(idx) - if ff ~= nil then - local prot = game.item_prototypes[ff] - if prot ~= nil then - local stack_size = prot.stack_size - local buffer = math.min(buffer_size, stack_size) - table.insert(requests, { - type = "take", - item = ff, - buffer = buffer, - limit = 0, - }) - -- clog(" - [%s] %s", idx, ff, serpent.line(requests[#requests])) - end - end - end - if next(requests) ~= nil then - GlobalState.set_chest_requests(dest.unit_number, requests) - end - end - end - - elseif constants.NETWORK_TANK_NAMES[dest.name] ~= nil then - if source.name == dest.name then - GlobalState.copy_tank_config(source.unit_number, dest.unit_number) - end - - elseif dest.name == "network-chest-provider" then - if source.type == "assembling-machine" then - -- paste a filter slot per - local recipe = source.get_recipe() - local dinv = dest.get_output_inventory() - if recipe ~= nil and dinv ~= nil then - -- move items to the net to keep things simple - GlobalState.put_inventory_in_network(dinv) - - -- get existing filters - local filters = {} - local fidx = 1 - while fidx <= #dinv do - local cf = dinv.get_filter(fidx) - if cf ~= nil then - filters[cf] = true - end - fidx = fidx + 1 - end - - -- add new filters - for _, prod in ipairs(recipe.products) do - if prod.type == "item" then - filters[prod.name] = true - end - end - - -- update the filters - fidx = 1 - for name, _ in pairs(filters) do - dinv.set_filter(fidx, name) - fidx = fidx + 1 - end - dinv.set_bar(fidx) - - -- clear any extra filters (shouldn't happen) - while fidx <= #dinv do - dinv.set_filter(fidx, nil) - fidx = fidx + 1 - end - end - end - end -end - --- fulfill requests. entity must have request_slot_count and get_request_slot() --- useful for vehicles (spidertron) and logistic containers -function M.inventory_handle_requests(entity, inv) - if entity ~= nil and inv ~= nil and entity.request_slot_count > 0 then - local contents = inv.get_contents() - - for slot = 1, entity.request_slot_count do - local req = entity.get_request_slot(slot) - if req ~= nil and req.name ~= nil then - local current_count = contents[req.name] or 0 - local network_count = GlobalState.get_item_count(req.name) - local n_wanted = math.max(0, req.count - current_count) - local n_transfer = math.min(network_count, n_wanted) - if n_transfer > 0 then - local n_inserted = inv.insert { name = req.name, count = n_transfer } - if n_inserted > 0 then - GlobalState.set_item_count(req.name, network_count - n_inserted) - end - end - if n_transfer < n_wanted then - GlobalState.missing_item_set(req.name, entity.unit_number, n_wanted - n_transfer) - end - end - end - end - - -- logsitics are always at the back of the list - return GlobalState.UPDATE_STATUS.UPDATE_LOGISTIC -end - -function M.update_player(player, enable_logistics) - local enable_player = settings.get_player_settings(player.index) - ["item-network-enable-player-logistics"].value - local entity = player.character - if entity == nil then + -- See if we manage the destination entity + local dst_info = GlobalState.entity_info_get(dest.unit_number) + if dst_info == nil then return end - if enable_logistics and not entity.force.character_logistic_requests then - entity.force.character_logistic_requests = true - if entity.force.character_trash_slot_count < 10 then - entity.force.character_trash_slot_count = 10 - end - end - - if enable_player then - -- put all trash into network - GlobalState.put_inventory_in_network(player.get_inventory(defines.inventory.character_trash)) - - -- get contents of player inventory - local main_inv = player.get_inventory(defines.inventory.character_main) - if main_inv ~= nil then - local character = player.character - if character ~= nil and character.character_personal_logistic_requests_enabled then - local main_contents = main_inv.get_contents() - local cursor_stack = player.cursor_stack - if cursor_stack ~= nil and cursor_stack.valid_for_read then - main_contents[cursor_stack.name] = - (main_contents[cursor_stack.name] or 0) + cursor_stack.count - end - - -- scan logistic slots and transfer to character - for logistic_idx = 1, character.request_slot_count do - local param = player.get_personal_logistic_slot(logistic_idx) - if param ~= nil and param.name ~= nil then - local available_in_network = GlobalState.get_item_count(param.name) - local current_amount = main_contents[param.name] or 0 - local delta = math.min(available_in_network, - math.max(0, param.min - current_amount)) - if delta > 0 then - local n_transfered = main_inv.insert({ - name = param.name, - count = delta, - }) - GlobalState.set_item_count( - param.name, - available_in_network - n_transfered - ) - end - end - end - end - end - end -end - -function M.updatePlayers() - if not global.mod.network_chest_has_been_placed then + -- REVISIT: I think I can do this with a metatable... + -- get management functions + local dst_func = GlobalState.get_service_task(dst_info.service_type) + if dst_func == nil then return end - local enable_logistics = settings.global["item-network-force-enable-logistics"].value - - for _, player in pairs(game.players) do - M.update_player(player, enable_logistics) - end -end - -function M.update_entity_vehicle(entity) - -- only 1 logistic vehicle right now - if entity.name ~= "spidertron" then - return GlobalState.UPDATE_STATUS.INVALID - end - - if entity.vehicle_logistic_requests_enabled then - local inv_trash = entity.get_inventory(defines.inventory.spider_trash) - local inv_trunk = entity.get_inventory(defines.inventory.spider_trunk) - - GlobalState.put_inventory_in_network(inv_trash) - M.inventory_handle_requests(entity, inv_trunk) - end - return GlobalState.UPDATE_STATUS.UPDATE_PRI_MAX -end - -function M.is_request_valid(request) - return request.item ~= nil and request.buffer_size ~= nil and - request.limit ~= nil -end - --------------------------------------------------------------------------------- - --- Reset a locked inventory. --- Filters can only be before the bar, so we only clear up to that point. -local function inv_reset(inv) - inv.clear() - local bar_idx = inv.get_bar() - if bar_idx < #inv then - for idx = 1, bar_idx - 1 do - inv.set_filter(idx, nil) - end - inv.set_bar() - end -end - ---[[ - (re)lock an unconfigured chest. - We create one slot for each recent_item that isn't in leftovers. - Then we remember the first empty slot as the bar index. - Then we add all the leftovers and set the bar. -]] -local function inv_unconfigured_lock(inv, leftovers, recent_items) - inv_reset(inv) - - -- add filtered slots for recent items NOT in leftovers - local f_idx = 1 - for item, _ in pairs(recent_items) do - -- create a filtered slot for each item we have seen recently that doesn't have items - -- Limit to the most recent 8 items - if leftovers[item] == nil then - inv.set_filter(f_idx, item) - f_idx = f_idx + 1 - if f_idx > #inv - 4 then - break - end - end - end - - -- add the leftover items (no need to filter) - for item, count in pairs(leftovers) do - -- add leftovers to the chest. anything that didn't fit goes to the net - local n_sent = inv.insert({name = item, count = count}) - if count > n_sent then - GlobalState.increment_item_count(item, count - n_sent) - end - end - - -- set the bar on the first left-over - inv.set_bar(f_idx) -end - ---[[ -Updates a chest if there are no requests. -Anything in the chest is forwarded to the item-network. -If anything can't be forwarded, the chest is locked (set bar=1) and the leftovers stay in the chest. -]] -local function update_network_chest_unconfigured_unlocked(info, inv, contents) - local status = GlobalState.UPDATE_STATUS.NOT_UPDATED - - -- We will re-add anything that cannot be sent when we lock the chest - inv.clear() - - -- move everything to the network, calculating leftovers - local leftovers = {} - for item, count in pairs(contents) do - if count > 0 then - local n_free = GlobalState.get_insert_count(item) - local n_transfer = math.min(count, n_free) - if n_transfer > 0 then - info.recent_items[item] = game.tick - GlobalState.increment_item_count(item, n_transfer) - status = GlobalState.UPDATE_STATUS.UPDATED - count = count - n_transfer - end - - if count > 0 then - leftovers[item] = count - end - end - end - - -- if there are any leftovers, we put them back and lock the chest - if next(leftovers) ~= nil then - --GlobalState.log_entity("UNCONF LOCK", info.entity) - info.locked_items = leftovers - inv_unconfigured_lock(inv, leftovers, info.recent_items) - status = GlobalState.UPDATE_STATUS.UPDATED - end - return status -end - ---[[ -Updates a chest if there are no requests. -Anything in the chest is forwarded to the item-network. -If anything can't be forwarded, the chest is locked (set bar=1) and the leftovers stay in the chest. -This does not call inv.clear(), but rather pulls items using inv.remove(). -]] -local function update_network_chest_unconfigured_locked(info, inv, contents) - local status = GlobalState.UPDATE_STATUS.UPDATE_BULK - - -- NOTE: We do not clear the inventory or filters. There is one slot per item. - - -- move everything to the network, calculating leftovers - local leftovers = {} - for item, count in pairs(contents) do - if count > 0 then - local n_free = GlobalState.get_insert_count(item) - local n_transfer = math.min(count, n_free) - if n_transfer > 0 then - info.recent_items[item] = game.tick - GlobalState.increment_item_count(item, n_transfer) - inv.remove({name=item, count=n_transfer}) - status = GlobalState.UPDATE_STATUS.UPDATED - count = count - n_transfer - end - if count > 0 then - -- Since there is one slot per item, count will be <= stack_size. - leftovers[item] = count - end - end - end - - -- check if the list of locked items changed - if tabutils.tables_have_same_keys(info.locked_items, leftovers) then - -- nope! we are done. - return status - end - info.locked_items = leftovers - - -- See if we can unlock the chest (chest is empty) - if next(leftovers) == nil then - -- the chest is now empty, so unlock it - --GlobalState.log_entity("UNCONF UNLOCK", info.entity) - inv_reset(inv) - else - -- need to re-lock the chest - --GlobalState.log_entity("UNCONF RE-LOCK", info.entity) - inv_unconfigured_lock(inv, leftovers, info.recent_items) - end - - return status -end - --- get the "buffer" value for this item -local function get_request_buffer(requests, item) - for _, rr in ipairs(requests) do - if rr.item == item then - return rr.buffer, rr.type - end - end - return 0, "give" -end - -local function inv_filter_slots(inv, item, inv_idx, count) - for _ = 1, count do - inv.set_filter(inv_idx, item) - inv_idx = inv_idx + 1 - end - return inv_idx -end - ---[[ - Lock a configured chest. - - inv = the inventory (info.entity.get_output_inventory()) - contents = the remaining contents, key=item_name, val=count - info.locked_items = the items that are in the locked state - info.recent_items = the items that have been in the chest recently -]] -local function inv_configured_lock(info, inv, contents) - -- the configured chest needs to be locked, so we re-add contents - inv_reset(inv) - - -- going to sort requests into two sections: before and after the bar - local slots_unlocked = {} - local slots_locked = {} - local locked_items = info.locked_items or {} - - -- add "take" request filters - for _, req in pairs(info.requests) do - local stack_size = game.item_prototypes[req.item].stack_size - local n_slots = math.floor((req.buffer + stack_size - 1) / stack_size) - if req.type == "take" then - slots_unlocked[req.item] = n_slots - else - -- provider needs 1 slot to buffer 0 items - n_slots = math.min(1, n_slots) - - if locked_items[req.item] == nil then - slots_unlocked[req.item] = n_slots - else - slots_locked[req.item] = n_slots - end - end - end - - -- add one slot for any item we have seen, but isn't in the chest - for name, _ in pairs(info.recent_items) do - if slots_unlocked[name] == nil and slots_locked[name] == nil then - slots_unlocked[name] = 1 - end - end - - -- now set the provider filters and the bar - local inv_idx = 1 - for item, n_slots in pairs(slots_unlocked) do - inv_idx = inv_filter_slots(inv, item, inv_idx, n_slots) - end - local bar_idx = inv_idx - for item, n_slots in pairs(slots_locked) do - inv_idx = inv_filter_slots(inv, item, inv_idx, n_slots) - end - -- add the contents and set the bar to lock - for name, count in pairs(contents) do - if count > 0 then - inv.insert({name=name, count=count}) - end - end - inv.set_bar(bar_idx) -end - ---[[ - Common bit for a configured chest. - Push items to the net, then handle "take" requests - - returns GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME or GlobalState.UPDATE_STATUS.UPDATE_PRI_INC or GlobalState.UPDATE_STATUS.UPDATE_PRI_DEC -]] -local function update_network_chest_configured_common(info, inv, contents) - local status = GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME - local locked_items = {} -- key=item, val=true - - -- pass 1: Send excessive items; note which sends fail in "locked" table - -- does not affect the priority - for item, n_have in pairs(contents) do - local n_want, r_type = get_request_buffer(info.requests, item) - - -- try to send to the network if we have too many - local n_extra = n_have - n_want - if n_extra > 0 then - local n_free = GlobalState.get_insert_count(item) - local n_transfer = math.min(n_extra, n_free) - if n_transfer > 0 then - -- status = GlobalState.UPDATE_STATUS.UPDATE_PRI_INC - inv.remove({name=item, count=n_transfer}) - GlobalState.increment_item_count(item, n_transfer) - info.recent_items[item] = game.tick - n_have = n_have - n_transfer - contents[item] = n_have - end - if n_have > n_want and r_type ~= "take" then - -- add to the locked area (provider unable to provide) - locked_items[item] = true - end - end - end - - -- pass 2: satisfy requests (pull into contents) - local added_some = false - for _, req in pairs(info.requests) do - if req.type == "take" then - local n_have = contents[req.item] or 0 - local n_innet = GlobalState.get_item_count(req.item) - local n_avail = math.max(0, n_innet - (req.limit or 0)) - local n_want = req.buffer - if n_want > n_have then - local n_transfer = math.min(n_want - n_have, n_avail) - if n_transfer > 0 then - -- it may not fit in the chest due to other reasons - n_transfer = inv.insert({name=req.item, count=n_transfer}) - if n_transfer > 0 then - added_some = true - contents[req.item] = n_have + n_transfer - GlobalState.set_item_count(req.item, n_innet - n_transfer) - - --[[ If we filled the entire buffer, then we may not be requesting often enough. - If there is enough in the net for another 4*buffer, then we are probably not - requesting enough. Up the buffer size by 1. - ]] - if n_transfer == req.buffer then - status = GlobalState.UPDATE_STATUS.UPDATE_PRI_INC - if n_innet > n_transfer * 4 then - req.buffer = req.buffer + 1 - end - end - end - else - GlobalState.missing_item_set(req.item, info.entity.unit_number, n_want - n_have) - end - end - end - end - - -- update less frequently if we didn't request anything - if added_some == false then - status = GlobalState.UPDATE_STATUS.UPDATE_PRI_DEC - end - - return status, locked_items -end - ---[[ - Update the chest in the configured-unlocked state. - Transitions to locked if an item can't be pushed to the network. -]] -local function update_network_chest_configured_unlocked(info, inv, contents) - local status, locked_items = update_network_chest_configured_common(info, inv, contents) - - -- is the chest still unlocked? if so, we are done. - if next(locked_items) == nil then - info.locked_items = nil - return status - end - info.locked_items = locked_items - - inv_configured_lock(info, inv, contents) - - -- just locked, so update less often - return GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME -end - ---[[ -Process a configured, locked chest. -]] -local function update_network_chest_configured_locked(info, inv, contents) - local status, locked_items = update_network_chest_configured_common(info, inv, contents) - - -- check if the list of locked items changed - if tabutils.tables_have_same_keys(info.locked_items, locked_items) then - return status - end - - -- can we unlock the chest? - if next(locked_items) == nil then - info.locked_items = nil - -- we no longer have too much stuff, so we can unlock - for idx = 1, inv.get_bar() do - inv.set_filter(idx, nil) + -- see if we can promote this to a clone op + if source.name == dest.name and type(dst_func.clone) == "function" then + local src_info = GlobalState.entity_info_get(source.unit_number) + if src_info ~= nil then + dst_func.clone(dst_info, src_info) + return end - inv.set_bar() - else - -- the list of locked items changed, so we need to re-lock the chest - info.locked_items = locked_items - inv_configured_lock(info, inv, contents) end - return GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME -end - --- this is the handler for the "old" network-chest -local function update_network_chest(info) - local inv = info.entity.get_output_inventory() - local contents = inv.get_contents() - - -- make sure recent_items is present (can get cleared elsewhere) - if info.recent_items == nil then - info.recent_items = {} - end - - local is_locked = (inv.get_bar() < #inv) - if #info.requests == 0 then - -- fully automatic provider - if is_locked then - update_network_chest_unconfigured_locked(info, inv, contents) - else - update_network_chest_unconfigured_unlocked(info, inv, contents) - end - return GlobalState.UPDATE_STATUS.UPDATE_BULK - else -- configured - if is_locked then - return update_network_chest_configured_locked(info, inv, contents) - else - return update_network_chest_configured_unlocked(info, inv, contents) - end + -- try to paste settings + if type(dst_func.paste) == "function" then + dst_func.paste(dst_info, source) + return end end ---[[ -This is the handler for the "new" provider-only chest. -Sends everything to the network. No bars, filter, etc. -]] -local function update_network_chest_provider(info) - local status = GlobalState.UPDATE_STATUS.NOT_UPDATED - - local inv = info.entity.get_output_inventory() - local contents = inv.get_contents() - - -- move everything we can to the network - for item, count in pairs(contents) do - if count > 0 then - local n_free = GlobalState.get_insert_count(item) - local n_transfer = math.min(count, n_free) - if n_transfer > 0 then - local n_added = inv.remove({ name=item, count=n_transfer }) - if n_added > 0 then - GlobalState.increment_item_count(item, n_added) - status = GlobalState.UPDATE_STATUS.UPDATED - end - end - end - end - - return status -end --[[ This is the handler for the "new" requester-only chest. @@ -955,371 +328,9 @@ local function update_network_chest_requester(info) return status end -local function update_tank(info) - -- hook in autoconfig - if info.config.type == "auto" then - local config = NetworkTankAutoConfig.auto_config(info.entity) - if config == nil then - return GlobalState.UPDATE_STATUS.NOT_UPDATED - end - info.config = config - end - - local status = GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME - local type = info.config.type - local limit = info.config.limit or 5000 - local buffer = info.config.buffer or 1000 - local fluid = info.config.fluid - local temp = info.config.temperature - local no_limit = (info.config.no_limit == true) - - if type == "give" then - -- the network tank only has one fluidbox... - local fluidbox = info.entity.fluidbox - for idx = 1, #fluidbox do - local fluid_instance = fluidbox[idx] - if fluid_instance ~= nil then - local current_count = GlobalState.get_fluid_count( - fluid_instance.name, - fluid_instance.temperature - ) - local key = GlobalState.fluid_temp_key_encode(fluid_instance.name, fluid_instance.temperature) - local gl_limit = GlobalState.get_limit(key) - if no_limit then - limit = gl_limit - end - local n_give = math.max(0, fluid_instance.amount) - local n_take = math.max(0, math.max(limit, gl_limit) - current_count) - local n_transfer = math.floor(math.min(n_give, n_take)) - if n_transfer > 0 then - GlobalState.increment_fluid_count(fluid_instance.name, - fluid_instance.temperature, n_transfer) - local removed = info.entity.remove_fluid({ - name = fluid_instance.name, - temperature = fluid_instance.temperature, - amount = n_transfer, - }) - assert(removed == n_transfer) - - if n_transfer >= constants.MAX_TANK_SIZE * 0.9 then - -- We gave a full tank's worth, so maybe we should service more often - status = GlobalState.UPDATE_STATUS.UPDATE_PRI_INC - elseif n_transfer <= constants.MAX_TANK_SIZE * 0.1 then - -- We gave less than 10%, so we could check less often - status = GlobalState.UPDATE_STATUS.UPDATE_PRI_DEC - end - end - end - end - - else - local fluidbox = info.entity.fluidbox - local tank_fluid = nil - local tank_temp = nil - local tank_count = 0 - local n_fluid_boxes = 0 - for idx = 1, #fluidbox do - local fluid_instance = fluidbox[idx] - if fluid_instance ~= nil then - n_fluid_boxes = n_fluid_boxes + 1 - tank_fluid = fluid_instance.name - tank_temp = fluid_instance.temperature - tank_count = fluid_instance.amount - end - end - - -- only touch if there is a matching fluid - if n_fluid_boxes == 0 or (n_fluid_boxes == 1 and tank_fluid == fluid and tank_temp == temp) then - local network_count = GlobalState.get_fluid_count( - fluid, - temp - ) - -- how much we the network can give us, less the limit - local n_give = math.max(0, network_count - limit) - local n_take = math.max(0, buffer - tank_count) -- how much space we have in the tank - local n_transfer = math.floor(math.min(n_give, n_take)) - if n_transfer > 0 then - local added = info.entity.insert_fluid({ - name = fluid, - amount = n_transfer, - temperature = temp, - }) - if added > 0 then - GlobalState.increment_fluid_count(fluid, temp, -added) - if added == n_take and tank_count < (0.1 * buffer) then - -- we filled the tank and the tank was less than 10% full - status = GlobalState.UPDATE_STATUS.UPDATE_PRI_INC - elseif tank_count < (buffer * 0.9) or added < (buffer * 0.1) then - status = GlobalState.UPDATE_STATUS.UPDATE_PRI_DEC - end - end - end - if n_take > n_give then - GlobalState.missing_fluid_set(fluid, temp, info.entity.unit_number, - n_take - n_give) - end - end - end - - return status -end - -local function update_entity_chest(unit_number, info) - local entity = info.entity - if not entity.valid then - return GlobalState.UPDATE_STATUS.INVALID -- nil - end - - if entity.to_be_deconstructed() then - return GlobalState.UPDATE_STATUS.NOT_UPDATED - end - - if entity.name == "network-chest" then - return update_network_chest(info) - - elseif entity.name == "network-chest-provider" then - update_network_chest_provider(info) - return GlobalState.UPDATE_STATUS.UPDATE_BULK - - elseif entity.name == "network-chest-requester" then - update_network_chest_requester(info) - return GlobalState.UPDATE_STATUS.UPDATE_BULK - else - clog("What is this chest? %s", entity.name) - end - return GlobalState.UPDATE_STATUS.NOT_UPDATED -end - -local function update_entity_tank(unit_number, info) - local entity = info.entity - if not entity.valid then - clog("tank entity %s not valid", unit_number) - return GlobalState.UPDATE_STATUS.INVALID - end - - -- 'to_be_deconstructed()' may be temporary - if info.config == nil or entity.to_be_deconstructed() then - return GlobalState.UPDATE_STATUS.UPDATE_PRI_MAX - end - - return update_tank(info) -end - ---[[ -Updates one entity that we track. - -@unit_number is the entity unit number. It may no longer be valid. -@priority is current priority, which may be adjusted. -@returns the new priority -]] -local function update_entity(unit_number, priority) - local info - info = GlobalState.get_chest_info(unit_number) - if info ~= nil then - info.service_tick = game.tick - local retval = update_entity_chest(unit_number, info) - if retval == nil then - clog("chest drop: %s", serpent.line(info)) - end - return retval - end - - info = GlobalState.get_tank_info(unit_number) - if info ~= nil then - info.service_tick = game.tick - local retval = update_entity_tank(unit_number, info) - if retval == nil then - clog("tank drop: %s", serpent.line(info)) - end - return retval - end - - local entity = GlobalState.get_logistic_entity(unit_number) - if entity ~= nil then - local retval = M.update_entity_logistic(entity) - if retval == nil then - clog("logistic drop: %s", serpent.line(info)) - end - return retval - end - - entity = GlobalState.get_vehicle_entity(unit_number) - if entity ~= nil then - local retval = M.update_entity_vehicle(entity) - if retval == nil then - clog("vehicle drop: %s", serpent.line(info)) - end - return retval - end - - entity = GlobalState.get_service_entity(unit_number) - if entity ~= nil then - local retval = ServiceEntity.update_entity(entity) - if retval == nil then - clog("service drop: %s", serpent.line(info)) - end - return retval - end - - -- unknown/invalid unit_number (probably already removed) - return nil -end - -function M.update_queue() - GlobalState.update_queue(update_entity) -end - -function M.handle_logistic_storage(entity, inv) - local new_contents = inv.get_contents() - if next(new_contents) ~= nil then - local info = GlobalState.entity_info_get(entity.unit_number) - if info == nil then - info = { contents = new_contents, tick = game.tick } - GlobalState.entity_info_set(entity.unit_number, info) - return - end - - if tabutils.tables_have_same_counts(new_contents, info.contents) then - local tick_delta = game.tick - info.tick - -- drop content after it does not change for 1 minute - if tick_delta > 60*60 then - -- send contents to network - GlobalState.put_inventory_in_network(inv) - --clog("[%s] [%s] storage to network: %s", game.tick, entity.unit_number, serpent.line(new_contents)) - info.contents = {} -- assume it was sent - info.tick = game.tick - end - else - -- changed, so reset the timer - info.contents = new_contents - info.tick = game.tick - --clog("[%s] [%s] storage updated: %s", game.tick, entity.unit_number, serpent.line(new_contents)) - end - end -end - -function M.update_entity_logistic(entity) - -- sanity check - if not entity.valid then - return GlobalState.UPDATE_STATUS.INVALID -- nil - end - - if settings.global["item-network-enable-logistic-chest"].value and not entity.to_be_deconstructed() then - -- this might be a requester or storage - local logistic_mode = GlobalState.get_logistic_mode(entity.name) - if logistic_mode == "storage" then - M.handle_logistic_storage(entity, entity.get_output_inventory()) - else - M.inventory_handle_requests(entity, entity.get_output_inventory()) - end - end - - return GlobalState.UPDATE_STATUS.UPDATE_PRI_MAX -end - -function M.onTick() - GlobalState.setup() - M.update_queue() -end - -function M.onTick_60() - M.updatePlayers() - M.check_alerts() -end - -function M.handle_missing_material(entity, missing_name, item_count) - item_count = item_count or 1 - -- a cliff doesn't have a unit_number, so fake one based on the position - local key = entity.unit_number - if key == nil then - key = string.format("%s,%s", entity.position.x, entity.position.y) - end - - -- did we already transfer something for this ghost/upgrade? - if GlobalState.alert_transfer_get(key) == true then - return - end - - -- make sure it is something we can handle - local name, count = GlobalState.resolve_name(missing_name) - if name == nil then - return - end - count = count or item_count - - -- do we have an item to send? - local network_count = GlobalState.get_item_count(name) - if network_count < count then - GlobalState.missing_item_set(name, key, count) - return - end - - -- Find a construction network with a construction robot that covers this position - local nets = entity.surface.find_logistic_networks_by_construction_area( - entity.position, "player") - for _, net in ipairs(nets) do - if net.all_construction_robots > 0 then - local n_inserted = net.insert({ name = name, count = count }) - if n_inserted > 0 then - GlobalState.increment_item_count(name, -n_inserted) - GlobalState.alert_transfer_set(key) - return - end - end - end -end - -function M.check_alerts() - GlobalState.alert_transfer_cleanup() - - -- process all the alerts for all players - for _, player in pairs(game.players) do - local alerts = player.get_alerts { - type = defines.alert_type.no_material_for_construction } - for _, xxx in pairs(alerts) do - for _, alert_array in pairs(xxx) do - for _, alert in ipairs(alert_array) do - if alert.target ~= nil then - local entity = alert.target - -- we only care about ghosts and items that are set to upgrade - if entity.name == "entity-ghost" or entity.name == "tile-ghost" then - M.handle_missing_material(entity, entity.ghost_name) - elseif entity.name == "cliff" then - M.handle_missing_material(entity, "cliff-explosives") - elseif entity.name == "item-request-proxy" then - for k, v in pairs(entity.item_requests) do - M.handle_missing_material(entity, k, v) - end - else - local tent = entity.get_upgrade_target() - if tent ~= nil then - M.handle_missing_material(entity, tent.name) - end - end - end - end - end - end - end - - -- send repair packs - for _, player in pairs(game.players) do - local alerts = player.get_alerts { - type = defines.alert_type.not_enough_repair_packs } - for _, xxx in pairs(alerts) do - for _, alert_array in pairs(xxx) do - for _, alert in ipairs(alert_array) do - if alert.target ~= nil then - M.handle_missing_material(alert.target, "repair-pack") - end - end - end - end - end -end ------------------------------------------- --- GUI Section +-- GUI Section -- needs to move into GUI files ------------------------------------------- function M.on_gui_click(event) @@ -1511,12 +522,4 @@ Event.on_event( M.in_cancel_dialog ) -Event.on_nth_tick(1, M.onTick) -Event.on_nth_tick(60, M.onTick_60) --- Event.on_nth_tick(60 * 3, M.on_every_5_seconds) - -Event.on_init(function() - M.on_init() -end) - return M diff --git a/src/NetworkTankGui.lua b/src/NetworkTankGui.lua index 5953a4d..59c4fc4 100644 --- a/src/NetworkTankGui.lua +++ b/src/NetworkTankGui.lua @@ -20,36 +20,25 @@ local function gui_destroy(player_index) end --[[ -This is called when the system opens a diaglog for the tank. -It replaces the dialog with one of our own. + Destroys the old gui and retrieves the info for the tank. + If found, creates the new window with the preview and save/cancel buttons on the bottom. + returns the class instance, main_flow and tank_info ]] -local function network_tank_on_gui_opened(player, entity) +local function common_on_gui_opened(player, entity) -- need to start clean each time; there can be only one open at a time gui_destroy(player.index) local tank_info = GlobalState.get_tank_info(entity.unit_number) if tank_info == nil then - return + return nil end - local default_is_take = true - local default_fluid = nil - local default_buffer = nil - local default_limit = nil - local default_temp = nil - - if tank_info.config ~= nil then - default_is_take = tank_info.config.type == "take" - default_fluid = tank_info.config.fluid - default_buffer = tank_info.config.buffer - default_limit = tank_info.config.limit - default_temp = tank_info.config.temperature - end - - local self = my_mgr:create_window(player, entity.localised_name, { + local self = my_mgr:create_window(player, entity.localised_name, { window_name = UiConstants.NT_MAIN_FRAME, close_button = UiConstants.NT_CLOSE_BTN, }) + self.unit_number = entity.unit_number + local elems = self.elems local frame = elems.body @@ -73,7 +62,61 @@ local function network_tank_on_gui_opened(player, entity) entity_preview.style.height = 100 entity_preview.entity = entity - local main_flow = side_flow.add({ type = "flow", direction = "vertical" }) + local right_flow = side_flow.add({ type = "flow", direction = "vertical" }) + local main_flow = right_flow.add({ type = "flow", direction = "vertical" }) + + local save_cancel_flow = right_flow.add({ + type = "flow", + direction = "horizontal", + }) + save_cancel_flow.add({ + name = UiConstants.NT_CONFIRM_EVENT, + type = "button", + caption = "Save", + }) + save_cancel_flow.add({ + name = UiConstants.NT_CANCEL_EVENT, + type = "button", + caption = "Cancel", + }) + + return self, main_flow, tank_info +end + +--[[ +This is called when the system opens a diaglog for the tank. +It replaces the dialog with one of our own. + +@is_requester true=requester, other=net-tank +]] +local function network_tank_on_gui_opened(player, entity, is_requester) + + local self, main_flow, tank_info = common_on_gui_opened(player, entity) + if self == nil then + return + end + + local default_is_take = true + local default_fluid = nil + local default_buffer = nil + local default_limit = nil + local default_temp = nil + + if tank_info.config ~= nil then + default_is_take = tank_info.config.type == "take" + default_fluid = tank_info.config.fluid + default_buffer = tank_info.config.buffer + default_limit = tank_info.config.limit + default_temp = tank_info.config.temperature + end + + if is_requester == true then + default_is_take = true + end + + local elems = self.elems + + local frame = elems.body local auto_flow = main_flow.add({ type = "flow", direction = "horizontal" }) auto_flow.add({ @@ -86,7 +129,6 @@ local function network_tank_on_gui_opened(player, entity) auto_flow.style.vertical_align = "center" elems.type_flow = main_flow.add({ type = "flow", direction = "horizontal" }) - elems.type_flow.add({ type = "label", caption = "Type:" }) elems.choose_take_btn = elems.type_flow.add({ name = UiConstants.NT_CHOOSE_TAKE_BTN, @@ -94,6 +136,9 @@ local function network_tank_on_gui_opened(player, entity) state = default_is_take, }) elems.type_flow.add({ type = "label", caption = "Request" }) + if is_requester == true then + elems.type_flow.visible = false + end elems.choose_give_btn = elems.type_flow.add({ name = UiConstants.NT_CHOOSE_GIVE_BTN, @@ -157,6 +202,8 @@ local function network_tank_on_gui_opened(player, entity) end elems.limit_input.style.width = 100 + --[[ + add_save_cancel(main_flow) local save_cancel_flow = main_flow.add({ type = "flow", direction = "horizontal", @@ -171,9 +218,7 @@ local function network_tank_on_gui_opened(player, entity) type = "button", caption = "Cancel", }) - - -- NOTE: we could store entity, but then we'd need to check valid before accessing unit_number - self.unit_number = entity.unit_number +]] self.type = default_is_take and "take" or "give" self.fluid = default_fluid self.buffer = default_buffer @@ -183,6 +228,27 @@ local function network_tank_on_gui_opened(player, entity) M.update_input_visibility(self) end +--[[ + +]] +local function network_tank_provider_on_gui_opened(player, entity) + local self, main_flow, tank_info = common_on_gui_opened(player, entity) + if self == nil then + return + end + + local default_buffer = nil + + if tank_info.config ~= nil then + default_buffer = tank_info.config.buffer + end + + self.type = "give" + self.buffer = default_buffer + + M.update_buffer_slider(self) +end + function M.reset(self) gui_destroy(self.player.index) end @@ -231,22 +297,14 @@ end function M.get_config_from_network_tank_ui(self) local type = self.type local fluid = self.fluid - local buffer = self.buffer - local limit = self.limit - local temperature = self.temperature - - if type == "take" then - if type == nil or fluid == nil or temperature == nil or buffer == nil or limit == nil then - return nil - end + local buffer = self.buffer or 0 + local limit = self.limit or 0 + local temperature = self.temperature or 0 - if buffer <= 0 or limit < 0 then - return nil - end + buffer = math.max(0, math.min(buffer, Constants.MAX_TANK_SIZE)) + limit = math.max(0, limit) - if buffer > Constants.MAX_TANK_SIZE then - buffer = Constants.MAX_TANK_SIZE - end + if type == "take" then local config = { type = type, @@ -261,16 +319,9 @@ function M.get_config_from_network_tank_ui(self) return config else - if type == nil then - return nil - end - - if limit == nil or limit < 0 then - limit = 0 - end - + -- "give" meaning sending to the network local config = { - type = type, + type = "give", limit = limit, } if limit == 0 then @@ -402,14 +453,18 @@ Event.on_event( function (event) if event.gui_type == defines.gui_type.entity then local val = Constants.NETWORK_TANK_NAMES[event.entity.name] - if val ~= nil and val ~= false then + if val ~= nil then local entity = event.entity assert(GlobalState.get_tank_info(entity.unit_number) ~= nil) local player = game.get_player(event.player_index) if player == nil then return end - network_tank_on_gui_opened(player, entity) + if val == false then + -- network_tank_provider_on_gui_opened(player, entity, val) + else + network_tank_on_gui_opened(player, entity, val) + end end end end diff --git a/src/NetworkViewUi.lua b/src/NetworkViewUi.lua index 7848a8d..6b1344c 100644 --- a/src/NetworkViewUi.lua +++ b/src/NetworkViewUi.lua @@ -39,9 +39,6 @@ function M.open_main_frame(player_index) return end - -- log something for debug - --GlobalState.update_queue_log() - --local width = M.WIDTH local height = M.HEIGHT + 22 diff --git a/src/NetworkViewUi_test.lua b/src/NetworkViewUi_test.lua index 8bf423e..647d111 100644 --- a/src/NetworkViewUi_test.lua +++ b/src/NetworkViewUi_test.lua @@ -168,7 +168,7 @@ function M.create_gui(player_index) self.children.character_inventory.peer = self.children.network_items self.children.network_items.peer = self.children.character_inventory - -- test + --[[ test for _, info in pairs(GlobalState.get_chests()) do local entity = info.entity if entity and entity.valid and entity.name == "network-chest-requester" then @@ -176,6 +176,7 @@ function M.create_gui(player_index) break end end + ]] end function M.add_chest_inventory(self, frame) @@ -270,6 +271,89 @@ function M.on_gui_closed(event) end end +local function recurse_find_damage(tab) + if tab.type == 'damage' and tab.damage ~= nil then + return tab.damage + end + for k, v in pairs(tab) do + if type(v) == 'table' then + local rv = recurse_find_damage(v) + if rv ~= nil then + return rv + end + end + end + return nil +end + +local function log_ammo_stuff() + --local fuels = {} -- array { name, energy per stack } + local ammo_list = {} + for _, prot in pairs(game.item_prototypes) do + if prot.type == "ammo" then + print("-") + clog("ammo: %s type=%s attack=%s", prot.name, prot.type, serpent.line(prot.attack_parameters)) + + local at = prot.get_ammo_type() + if at ~= nil then + clog(" - category %s", tt, serpent.line(at.category)) + if at.category == 'bullet' then + local damage = recurse_find_damage(at.action) + if damage ~= nil and type(damage.amount) == "number" then + clog(" - damage %s", damage.amount) + + local xx = ammo_list[at.category] + if xx == nil then + xx = {} + ammo_list[at.category] = xx + end + table.insert(xx, { name=prot.name, amount=damage.amount }) + end + end + end + end + if prot.type == "gun" then + print("-") + clog("gun: %s type=%s attack=%s", prot.name, prot.type, serpent.line(prot.attack_parameters)) + + --[[ + for _, tt in ipairs({ "default", "player", "turret", "vehicle"}) do + local at = prot.get_ammo_type(tt) + if at ~= nil then + clog(" - %s => %s", tt, serpent.line(at.category)) + local xx = ammo_list[at.category] + if xx == nil then + xx = {} + ammo_list[at.category] =xx + end + xx[prot.name] = true + end + end + ]] + end + end + for k, xx in pairs(ammo_list) do + table.sort(xx, function (a, b) return a.amount > b.amount end) + end + clog("#### ammo: %s", serpent.line(ammo_list)) + + for _, prot in pairs(game.entity_prototypes) do + local guns = prot.guns + if guns ~= nil then + clog(" - %s has guns => %s", prot.name, serpent.line(guns)) + for idx, ig in pairs(prot.indexed_guns) do + local ap = ig.attack_parameters + local ac = ap.ammo_categories + clog(" ++ %s %s %s :: %s", idx, ig.name, ig.type, serpent.line(ig.attack_parameters.ammo_categories)) + --clog(" =>> ap %s", serpent.line(ap)) + for k, v in pairs(ac) do + clog(" =>> ac %s = %s", serpent.line(k), serpent.line(v)) + end + end + end + end +end + Gui.on_click(UiConstants.TV_PIN_BTN, M.on_click_pin_button) Gui.on_click(UiConstants.TV_CLOSE_BTN, M.on_click_close_button) Gui.on_click(UiConstants.TV_REFRESH_BTN, M.on_click_refresh_button) @@ -283,19 +367,17 @@ Event.on_event("in_open_test_view", function (event) Event.on_event("debug-network-item", function (event) + GlobalState.log_queue_info() + -- log_ammo_stuff() --[[ player_index, input_name, cursor_position, ]] local player = game.get_player(event.player_index) if player ~= nil and player.selected then local ent = player.selected local unum = ent.unit_number clog("EVENT %s ent=[%s] %s %s", serpent.line(event), unum, ent.name, ent.type) - local info = GlobalState.get_chest_info(unum) - if info ~= nil then - clog(" - chest: %s", serpent.line(info)) - end - info = GlobalState.get_tank_info(unum) + local info = GlobalState.entity_info_get(unum) if info ~= nil then - clog(" - tank: %s", serpent.line(info)) + clog(" - %s", serpent.line(info)) end end end) diff --git a/src/ServiceEntity.lua b/src/ServiceEntity.lua index 8858114..7411f99 100644 --- a/src/ServiceEntity.lua +++ b/src/ServiceEntity.lua @@ -33,26 +33,42 @@ local function transfer_item_to_inv(entity, inv, name, count) if n_added > 0 then GlobalState.increment_item_count(name, -n_added) --clog("[%s] %s : added %s %s", entity.unit_number, entity.name, name, n_added) - else -- if n_added < n_trans then - GlobalState.missing_item_set(name, entity.unit_number, n_trans) + elseif n_added < n_trans then + -- there was insufficient available + GlobalState.missing_item_set(name, entity.unit_number, n_trans - n_added) end + else + -- there was nothiing available + GlobalState.missing_item_set(name, entity.unit_number, count) end end end +local function transfer_item_to_inv_max(entity, inv, name) + transfer_item_to_inv(entity, inv, name, inv.get_insertable_count(name)) +end + local fuel_list = { --"processed-fuel", "coal", "wood", } -local function service_refuel(entity) - local inv = entity.get_fuel_inventory() - if inv == nil then - return - end - +local function service_refuel(entity, inv) if inv.is_empty() then + local fuel_name, n_avail = GlobalState.get_best_available_fuel(entity) + if fuel_name == nil then + return + end + --clog("best fuel for %s is %s and we have %s", entity.name, fuel_name, n_avail) + local prot = game.item_prototypes[fuel_name] + --local n_avail = GlobalState.get_item_count(fuel_name) + if n_avail > 0 then + local n_add = (#inv * prot.stack_size) -- - 12 + transfer_item_to_inv(entity, inv, fuel_name, math.min(n_avail, n_add)) + end + return + --[[ for _, fuel_name in ipairs(fuel_list) do local prot = game.item_prototypes[fuel_name] local n_avail = GlobalState.get_item_count(fuel_name) @@ -64,6 +80,7 @@ local function service_refuel(entity) return end end + ]] else -- try to top off the fuel(s) for fuel, _ in ipairs(inv.get_contents()) do @@ -112,22 +129,56 @@ local ammo_artillery_shell_types = { "artillery-shell", } -local function service_reload_ammo(entity, inv, ammo_names) - local automated_ammo_count = entity.prototype.automated_ammo_count - local content = inv.get_contents() - if next(content) == nil then - for _, ammo in ipairs(ammo_names) do - local n_avail = GlobalState.get_item_count(ammo) - if n_avail > 0 then - transfer_item_to_inv(entity, inv, ammo, math.min(automated_ammo_count, n_avail)) - return - end +local function service_reload_ammo_type(entity, inv, ammo_category) + if inv == nil then + clog("service_reload_ammo_type: %s nil inv", entity.name) + return + end + + if inv.is_empty() then + local best_ammo = GlobalState.get_best_available_ammo(ammo_category) + clog("service_reload_ammo_type: %s inv empty, cat=%s best=%s", entity.name, ammo_category, best_ammo) + if best_ammo ~= nil then + transfer_item_to_inv_max(entity, inv, best_ammo) + return end else - for name, count in pairs(content) do - if count < automated_ammo_count then - transfer_item_to_inv(entity, inv, name, automated_ammo_count - count) - return + for name, count in pairs(inv.get_contents()) do + transfer_item_to_inv_max(entity, inv, name) + end + end +end + +local function service_reload_ammo_car(entity, inv) + if inv == nil then + return + end + -- sanity check: no guns means no ammo + local prot = entity.prototype + if prot.guns == nil then + clog("weird. no guns on %s", entity.name) + return + end + local inv_cat = {} + -- figure out the ammo categories for each inventory slot + -- REVISIT: this is only needed if the inventory slot is empty + -- direct: prot.indexed_guns[idx].attack_parameters.ammo_categories[1] + -- TODO: remember this at runtime, as it can't change + for _, ig in pairs(prot.indexed_guns) do + local ac = ig.attack_parameters.ammo_categories + table.insert(inv_cat, ac[1]) + end + + for idx = 1, #inv do + local stack = inv[idx] + if stack.valid_for_read and stack.count > 0 then + -- top off the ammo + transfer_item_to_inv_max(entity, inv, stack.name) + else + -- empty: find good ammo to add + local ammo_name = GlobalState.get_best_available_ammo(inv_cat[idx]) + if ammo_name ~= nil then + transfer_item_to_inv_max(entity, inv, ammo_name) end end end @@ -137,18 +188,24 @@ end Updates the entity. @reutrn GlobalState.UPDATE_STATUS.INVALID or GlobalState.UPDATE_STATUS.UPDATE_PRI_MAX ]] -function M.update_entity(entity) - if not entity.valid then - return GlobalState.UPDATE_STATUS.INVALID - end - -- 'to_be_deconstructed()' may be temporary - if entity.to_be_deconstructed() then +function M.update_entity(info) + local entity = info.entity + + local isz = entity.get_max_inventory_index() + if isz < 1 then + clog("No inventory for %s", entity.name) return GlobalState.UPDATE_STATUS.UPDATE_PRI_MAX + --return GlobalState.UPDATE_STATUS.INVALID + end + + -- handle refueling + local f_inv = entity.get_fuel_inventory() + if f_inv ~= nil then + service_refuel(entity, f_inv) end if entity.type == "furnace" then GlobalState.items_inv_to_net_with_limits(entity.get_output_inventory()) - service_refuel(entity) -- add ore to match the previous recipe local inv_src = entity.get_inventory(defines.inventory.furnace_source) @@ -156,31 +213,42 @@ function M.update_entity(entity) service_recipe_inv(entity, entity.get_inventory(defines.inventory.furnace_source), entity.previous_recipe, 50) end - elseif entity.type == "mining-drill" then - -- mining drills dump their output directly to a network chest, so we just need to refuel - service_refuel(entity) - elseif entity.type == "assembling-machine" then --clog("Service [%s] %s status=%s", entity.unit_number, entity.name, entity.status) - service_refuel(entity) -- for 'burner-assembling-machine' - if entity.status == defines.entity_status.item_ingredient_shortage then - service_recipe_inv(entity, entity.get_inventory(defines.inventory.assembling_machine_input), entity.get_recipe(), 2) + local old_status = entity.status + + -- move output items to net + local out_inv = entity.get_output_inventory() + GlobalState.items_inv_to_net_with_limits(out_inv) + + local inp_inv = entity.get_inventory(defines.inventory.assembling_machine_input) + + -- was full and still can't send off items, so shut off, return inputs + if not out_inv.is_empty() and old_status == defines.entity_status.full_output then + -- full output, no room in net, + GlobalState.items_inv_to_net(inp_inv) + else + service_recipe_inv(entity, inp_inv, entity.get_recipe(), 2) end - GlobalState.items_inv_to_net_with_limits(entity.get_output_inventory()) -- will stop refill if status becomes "output full" - - elseif entity.name == "boiler" or entity.name == "burner-lab" or entity.name == "burner-inserter" or entity.type == "burner-generator" then - service_refuel(entity) + elseif entity.type == "car" then + service_reload_ammo_car(entity, entity.get_inventory(defines.inventory.car_ammo), ammo_bullet_types) elseif entity.type == "ammo-turret" then - service_reload_ammo(entity, entity.get_inventory(defines.inventory.turret_ammo), ammo_bullet_types) + service_reload_ammo_type(entity, entity.get_inventory(defines.inventory.turret_ammo), "bullet") elseif entity.type == "artillery-turret" then - service_reload_ammo(entity, entity.get_inventory(defines.inventory.artillery_turret_ammo), ammo_artillery_shell_types) + service_reload_ammo_type(entity, entity.get_inventory(defines.inventory.artillery_turret_ammo), "artillery-shell") end return GlobalState.UPDATE_STATUS.UPDATE_PRI_MAX end +function M.create(entity, tags) + GlobalState.service_add_entity(entity) +end + +GlobalState.register_service_task("general-service", { create=M.create, service=M.update_entity }) + return M diff --git a/src/constants.lua b/src/constants.lua index 246ed69..6447648 100644 --- a/src/constants.lua +++ b/src/constants.lua @@ -1,7 +1,7 @@ local M = {}; M.NUM_INVENTORY_SLOTS = 48 -M.TANK_AREA = 100 +M.TANK_AREA = 1000 M.TANK_HEIGHT = 1 M.MAX_TANK_SIZE = M.TANK_AREA * M.TANK_HEIGHT * 100 @@ -9,10 +9,11 @@ M.ALERT_TRANSFER_TICKS = 10 * 60 M.MAX_MISSING_TICKS = 5 * 60 -- use an array of queues, each must take a minimum of QUEUE_TICKS to process. -M.QUEUE_COUNT = 10 -M.QUEUE_TICKS = 10 -- should be like 10 +M.QUEUE_TICKS = 20 +M.QUEUE_COUNT = 32 +M.QUEUE_PERIOD_MIN = M.QUEUE_TICKS * M.QUEUE_COUNT --- has to be small enough to be in the constant combinator +-- has to be small enough to be in the constant combinator, which uses 32-bit signed integers M.UNLIMITED = 2000000000 -- "2G" M.NETWORK_TANK_NAMES = { diff --git a/src/prototypes/network-tanks.lua b/src/prototypes/network-tanks.lua index 42010cb..9fe9e25 100644 --- a/src/prototypes/network-tanks.lua +++ b/src/prototypes/network-tanks.lua @@ -31,7 +31,7 @@ local function add_network_tank(name, is_output) fluid_box = { base_area = constants.TANK_AREA, height = constants.TANK_HEIGHT, - base_level = base_level, + pipe_covers = pipecoverspictures(), pipe_connections = { { position = { 0, 1 }, type = fb_type }, @@ -42,7 +42,7 @@ local function add_network_tank(name, is_output) picture = { sheet = { filename = fname, - size = 64, + size = 128, scale = 0.5, }, }, @@ -51,8 +51,8 @@ local function add_network_tank(name, is_output) size = 1, }, fluid_background = { - filename = Paths.graphics .. "/entities/fluid-background.png", - size = { 32, 32 }, + filename = Paths.graphics .. "/empty-pixel.png", + size = {1, 1}, }, flow_sprite = { filename = Paths.graphics .. "/empty-pixel.png", diff --git a/src/service_alerts.lua b/src/service_alerts.lua new file mode 100644 index 0000000..dd75539 --- /dev/null +++ b/src/service_alerts.lua @@ -0,0 +1,104 @@ +--[[ +Scans alerts once per second and tries to insert missing material into a +logistic constgruction network that overlaps the target. +]] +local GlobalState = require "src.GlobalState" +local Event = require('__stdlib__/stdlib/event/event') +local clog = require("src.log_console").log + +--[[ +This is called to handle the lack of a material. +entity is used for the unit_number and position. +]] +local function handle_missing_material(entity, missing_name, item_count) + item_count = item_count or 1 + -- a cliff doesn't have a unit_number, so fake one based on the position + local key = entity.unit_number + if key == nil then + key = string.format("%s,%s", entity.position.x, entity.position.y) + end + + -- did we already transfer something for this ghost/upgrade? + if GlobalState.alert_transfer_get(key) == true then + return + end + + -- make sure it is something we can handle + local name, count = GlobalState.resolve_name(missing_name) + if name == nil then + return + end + count = count or item_count + + -- do we have an item to send? + local network_count = GlobalState.get_item_count(name) + if network_count < count then + GlobalState.missing_item_set(name, key, count) + return + end + + -- Find a construction network with a construction robot that covers this position + local nets = entity.surface.find_logistic_networks_by_construction_area( + entity.position, "player") + for _, net in ipairs(nets) do + if net.all_construction_robots > 0 then + local n_inserted = net.insert({ name = name, count = count }) + if n_inserted > 0 then + GlobalState.increment_item_count(name, -n_inserted) + GlobalState.alert_transfer_set(key) + return + end + end + end +end + +local function service_alerts() + GlobalState.alert_transfer_cleanup() + + -- process all the alerts for all players + for _, player in pairs(game.players) do + local alerts = player.get_alerts { + type = defines.alert_type.no_material_for_construction } + for _, xxx in pairs(alerts) do + for _, alert_array in pairs(xxx) do + for _, alert in ipairs(alert_array) do + if alert.target ~= nil then + local entity = alert.target + -- we only care about ghosts and items that are set to upgrade + if entity.name == "entity-ghost" or entity.name == "tile-ghost" then + handle_missing_material(entity, entity.ghost_name) + elseif entity.name == "cliff" then + handle_missing_material(entity, "cliff-explosives") + elseif entity.name == "item-request-proxy" then + for k, v in pairs(entity.item_requests) do + handle_missing_material(entity, k, v) + end + else + local tent = entity.get_upgrade_target() + if tent ~= nil then + handle_missing_material(entity, tent.name) + end + end + end + end + end + end + end + + -- send repair packs + for _, player in pairs(game.players) do + local alerts = player.get_alerts { + type = defines.alert_type.not_enough_repair_packs } + for _, xxx in pairs(alerts) do + for _, alert_array in pairs(xxx) do + for _, alert in ipairs(alert_array) do + if alert.target ~= nil then + handle_missing_material(alert.target, "repair-pack") + end + end + end + end + end +end + +Event.on_nth_tick(60, service_alerts) diff --git a/src/service_players.lua b/src/service_players.lua new file mode 100644 index 0000000..e3a7410 --- /dev/null +++ b/src/service_players.lua @@ -0,0 +1,69 @@ +local GlobalState = require "src.GlobalState" +local Event = require('__stdlib__/stdlib/event/event') +local clog = require("src.log_console").log + + +local function update_player(player, enable_logistics) + local enable_player = settings.get_player_settings(player.index)["item-network-enable-player-logistics"].value + + local entity = player.character + if entity == nil then + return + end + + if enable_logistics and not entity.force.character_logistic_requests then + entity.force.character_logistic_requests = true + if entity.force.character_trash_slot_count < 10 then + entity.force.character_trash_slot_count = 10 + end + end + + if enable_player then + -- put all trash into network + GlobalState.put_inventory_in_network(player.get_inventory(defines.inventory.character_trash)) + + -- get contents of player inventory + local main_inv = player.get_inventory(defines.inventory.character_main) + if main_inv ~= nil then + local character = player.character + if character ~= nil and character.character_personal_logistic_requests_enabled then + local main_contents = main_inv.get_contents() + local cursor_stack = player.cursor_stack + if cursor_stack ~= nil and cursor_stack.valid_for_read then + main_contents[cursor_stack.name] = (main_contents[cursor_stack.name] or 0) + cursor_stack.count + end + + -- scan logistic slots and transfer to character + for logistic_idx = 1, character.request_slot_count do + local param = player.get_personal_logistic_slot(logistic_idx) + if param ~= nil and param.name ~= nil then + local available_in_network = GlobalState.get_item_count(param.name) + local current_amount = main_contents[param.name] or 0 + local delta = math.min(available_in_network, + math.max(0, param.min - current_amount)) + if delta > 0 then + local n_transfered = main_inv.insert({ + name = param.name, + count = delta, + }) + GlobalState.set_item_count( + param.name, + available_in_network - n_transfered + ) + end + end + end + end + end + end +end + +local function service_players() + local enable_logistics = settings.global["item-network-force-enable-logistics"].value + + for _, player in pairs(game.players) do + update_player(player, enable_logistics) + end +end + +Event.on_nth_tick(60, service_players) diff --git a/src/service_queue.lua b/src/service_queue.lua new file mode 100644 index 0000000..5071848 --- /dev/null +++ b/src/service_queue.lua @@ -0,0 +1,914 @@ +local GlobalState = require "src.GlobalState" +local Event = require('__stdlib__/stdlib/event/event') +local tabutils = require("src.tables_have_same_keys") +local NetworkTankAutoConfig = require("src.NetworkTankAutoConfig") +local ServiceEntity = require("src.ServiceEntity") +local clog = require("src.log_console").log + + +-- fulfill requests. entity must have request_slot_count and get_request_slot() +-- useful for vehicles (spidertron) and logistic containers +local function inventory_handle_requests(entity, inv) + if entity ~= nil and inv ~= nil and entity.request_slot_count > 0 then + local contents = inv.get_contents() + + for slot = 1, entity.request_slot_count do + local req = entity.get_request_slot(slot) + if req ~= nil and req.name ~= nil then + local current_count = contents[req.name] or 0 + local network_count = GlobalState.get_item_count(req.name) + local n_wanted = math.max(0, req.count - current_count) + local n_transfer = math.min(network_count, n_wanted) + if n_transfer > 0 then + local n_inserted = inv.insert { name = req.name, count = n_transfer } + if n_inserted > 0 then + GlobalState.set_item_count(req.name, network_count - n_inserted) + end + end + if n_transfer < n_wanted then + GlobalState.missing_item_set(req.name, entity.unit_number, n_wanted - n_transfer) + end + end + end + end + + -- logsitics are always at the back of the list + return GlobalState.UPDATE_STATUS.UPDATE_LOGISTIC +end + +-------------------------------------------------------------------------------- + +local function service_spidertron(info) + local entity = info.entity + if entity.vehicle_logistic_requests_enabled then + local inv_trash = entity.get_inventory(defines.inventory.spider_trash) + local inv_trunk = entity.get_inventory(defines.inventory.spider_trunk) + + GlobalState.put_inventory_in_network(inv_trash) + inventory_handle_requests(entity, inv_trunk) + end + return GlobalState.UPDATE_STATUS.UPDATE_PRI_MAX +end + +-------------------------------------------------------------------------------- + +-- Reset a locked inventory. +-- Filters can only be before the bar, so we only clear up to that point. +local function inv_reset(inv) + inv.clear() + local bar_idx = inv.get_bar() + if bar_idx < #inv then + for idx = 1, bar_idx - 1 do + inv.set_filter(idx, nil) + end + inv.set_bar() + end +end + +--[[ + (re)lock an unconfigured chest. + We create one slot for each recent_item that isn't in leftovers. + Then we remember the first empty slot as the bar index. + Then we add all the leftovers and set the bar. +]] +local function inv_unconfigured_lock(inv, leftovers, recent_items) + inv_reset(inv) + + -- add filtered slots for recent items NOT in leftovers + local f_idx = 1 + for item, _ in pairs(recent_items) do + -- create a filtered slot for each item we have seen recently that doesn't have items + -- Limit to the most recent 8 items + if leftovers[item] == nil then + inv.set_filter(f_idx, item) + f_idx = f_idx + 1 + if f_idx > #inv - 4 then + break + end + end + end + + -- add the leftover items (no need to filter) + for item, count in pairs(leftovers) do + -- add leftovers to the chest. anything that didn't fit goes to the net + local n_sent = inv.insert({name = item, count = count}) + if count > n_sent then + GlobalState.increment_item_count(item, count - n_sent) + end + end + + -- set the bar on the first left-over + inv.set_bar(f_idx) +end + +--[[ +Updates a chest if there are no requests. +Anything in the chest is forwarded to the item-network. +If anything can't be forwarded, the chest is locked (set bar=1) and the leftovers stay in the chest. +]] +local function update_network_chest_unconfigured_unlocked(info, inv, contents) + local status = GlobalState.UPDATE_STATUS.NOT_UPDATED + + -- We will re-add anything that cannot be sent when we lock the chest + inv.clear() + + -- move everything to the network, calculating leftovers + local leftovers = {} + for item, count in pairs(contents) do + if count > 0 then + local n_free = GlobalState.get_insert_count(item) + local n_transfer = math.min(count, n_free) + if n_transfer > 0 then + info.recent_items[item] = game.tick + GlobalState.increment_item_count(item, n_transfer) + status = GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME + count = count - n_transfer + end + + if count > 0 then + leftovers[item] = count + end + end + end + + -- if there are any leftovers, we put them back and lock the chest + if next(leftovers) ~= nil then + --GlobalState.log_entity("UNCONF LOCK", info.entity) + info.locked_items = leftovers + inv_unconfigured_lock(inv, leftovers, info.recent_items) + status = GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME + end + return status +end + +--[[ +Updates a chest if there are no requests. +Anything in the chest is forwarded to the item-network. +If anything can't be forwarded, the chest is locked (set bar=1) and the leftovers stay in the chest. +This does not call inv.clear(), but rather pulls items using inv.remove(). +]] +local function update_network_chest_unconfigured_locked(info, inv, contents) + local status = GlobalState.UPDATE_STATUS.UPDATE_BULK + + -- NOTE: We do not clear the inventory or filters. There is one slot per item. + + -- move everything to the network, calculating leftovers + local leftovers = {} + for item, count in pairs(contents) do + if count > 0 then + local n_free = GlobalState.get_insert_count(item) + local n_transfer = math.min(count, n_free) + if n_transfer > 0 then + info.recent_items[item] = game.tick + GlobalState.increment_item_count(item, n_transfer) + inv.remove({name=item, count=n_transfer}) + status = GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME + count = count - n_transfer + end + if count > 0 then + -- Since there is one slot per item, count will be <= stack_size. + leftovers[item] = count + end + end + end + + -- check if the list of locked items changed + if tabutils.tables_have_same_keys(info.locked_items, leftovers) then + -- nope! we are done. + return status + end + info.locked_items = leftovers + + -- See if we can unlock the chest (chest is empty) + if next(leftovers) == nil then + -- the chest is now empty, so unlock it + --GlobalState.log_entity("UNCONF UNLOCK", info.entity) + inv_reset(inv) + else + -- need to re-lock the chest + --GlobalState.log_entity("UNCONF RE-LOCK", info.entity) + inv_unconfigured_lock(inv, leftovers, info.recent_items) + end + + return status +end + +-- get the "buffer" value for this item +local function get_request_buffer(requests, item) + for _, rr in ipairs(requests) do + if rr.item == item then + return rr.buffer, rr.type + end + end + return 0, "give" +end + +local function inv_filter_slots(inv, item, inv_idx, count) + for _ = 1, count do + inv.set_filter(inv_idx, item) + inv_idx = inv_idx + 1 + end + return inv_idx +end + +--[[ + Lock a configured chest. + + inv = the inventory (info.entity.get_output_inventory()) + contents = the remaining contents, key=item_name, val=count + info.locked_items = the items that are in the locked state + info.recent_items = the items that have been in the chest recently +]] +local function inv_configured_lock(info, inv, contents) + -- the configured chest needs to be locked, so we re-add contents + inv_reset(inv) + + -- going to sort requests into two sections: before and after the bar + local slots_unlocked = {} + local slots_locked = {} + local locked_items = info.locked_items or {} + + -- add "take" request filters + for _, req in pairs(info.requests) do + local stack_size = game.item_prototypes[req.item].stack_size + local n_slots = math.floor((req.buffer + stack_size - 1) / stack_size) + if req.type == "take" then + slots_unlocked[req.item] = n_slots + else + -- provider needs 1 slot to buffer 0 items + n_slots = math.min(1, n_slots) + + if locked_items[req.item] == nil then + slots_unlocked[req.item] = n_slots + else + slots_locked[req.item] = n_slots + end + end + end + + -- add one slot for any item we have seen, but isn't in the chest + for name, _ in pairs(info.recent_items) do + if slots_unlocked[name] == nil and slots_locked[name] == nil then + slots_unlocked[name] = 1 + end + end + + -- now set the provider filters and the bar + local inv_idx = 1 + for item, n_slots in pairs(slots_unlocked) do + inv_idx = inv_filter_slots(inv, item, inv_idx, n_slots) + end + local bar_idx = inv_idx + for item, n_slots in pairs(slots_locked) do + inv_idx = inv_filter_slots(inv, item, inv_idx, n_slots) + end + -- add the contents and set the bar to lock + for name, count in pairs(contents) do + if count > 0 then + inv.insert({name=name, count=count}) + end + end + inv.set_bar(bar_idx) +end + +--[[ + Common bit for a configured chest. + Push items to the net, then handle "take" requests + + returns GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME or GlobalState.UPDATE_STATUS.UPDATE_PRI_INC or GlobalState.UPDATE_STATUS.UPDATE_PRI_DEC +]] +local function update_network_chest_configured_common(info, inv, contents) + local status = GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME + local locked_items = {} -- key=item, val=true + + -- pass 1: Send excessive items; note which sends fail in "locked" table + -- does not affect the priority + for item, n_have in pairs(contents) do + local n_want, r_type = get_request_buffer(info.requests, item) + + -- try to send to the network if we have too many + local n_extra = n_have - n_want + if n_extra > 0 then + local n_free = GlobalState.get_insert_count(item) + local n_transfer = math.min(n_extra, n_free) + if n_transfer > 0 then + -- status = GlobalState.UPDATE_STATUS.UPDATE_PRI_INC + inv.remove({name=item, count=n_transfer}) + GlobalState.increment_item_count(item, n_transfer) + info.recent_items[item] = game.tick + n_have = n_have - n_transfer + contents[item] = n_have + end + if n_have > n_want and r_type ~= "take" then + -- add to the locked area (provider unable to provide) + locked_items[item] = true + end + end + end + + -- pass 2: satisfy requests (pull into contents) + local added_some = false + for _, req in pairs(info.requests) do + if req.type == "take" then + local n_have = contents[req.item] or 0 + local n_innet = GlobalState.get_item_count(req.item) + local n_avail = math.max(0, n_innet - (req.limit or 0)) + local n_want = req.buffer + if n_want > n_have then + local n_transfer = math.min(n_want - n_have, n_avail) + if n_transfer > 0 then + -- it may not fit in the chest due to other reasons + n_transfer = inv.insert({name=req.item, count=n_transfer}) + if n_transfer > 0 then + added_some = true + contents[req.item] = n_have + n_transfer + GlobalState.set_item_count(req.item, n_innet - n_transfer) + + --[[ If we filled the entire buffer, then we may not be requesting often enough. + If there is enough in the net for another 4*buffer, then we are probably not + requesting enough. Up the buffer size by 1. + ]] + if n_transfer == n_want then + clog('chest increasing request freq pri=%s', info.service_priority) + status = GlobalState.UPDATE_STATUS.UPDATE_PRI_INC + if info.service_priority < 2 and n_innet > n_transfer * 4 then + req.buffer = req.buffer + 1 + clog('chest increasing request buffer =%s', req.buffer) + end + end + end + else + GlobalState.missing_item_set(req.item, info.entity.unit_number, n_want - n_have) + end + end + end + end + + -- update less frequently if we didn't request anything + if added_some == false then + status = GlobalState.UPDATE_STATUS.UPDATE_PRI_DEC + end + + return status, locked_items +end + +--[[ + Update the chest in the configured-unlocked state. + Transitions to locked if an item can't be pushed to the network. +]] +local function update_network_chest_configured_unlocked(info, inv, contents) + local status, locked_items = update_network_chest_configured_common(info, inv, contents) + + -- is the chest still unlocked? if so, we are done. + if next(locked_items) == nil then + info.locked_items = nil + return status + end + info.locked_items = locked_items + + inv_configured_lock(info, inv, contents) + + -- just locked, so update less often + return GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME +end + +--[[ +Process a configured, locked chest. +]] +local function update_network_chest_configured_locked(info, inv, contents) + local status, locked_items = update_network_chest_configured_common(info, inv, contents) + + -- check if the list of locked items changed + if tabutils.tables_have_same_keys(info.locked_items, locked_items) then + return status + end + + -- can we unlock the chest? + if next(locked_items) == nil then + info.locked_items = nil + -- we no longer have too much stuff, so we can unlock + for idx = 1, inv.get_bar() do + inv.set_filter(idx, nil) + end + inv.set_bar() + else + -- the list of locked items changed, so we need to re-lock the chest + info.locked_items = locked_items + inv_configured_lock(info, inv, contents) + end + + return GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME +end + +-- this is the handler for the "old" network-chest +local function service_network_chest(info) + local inv = info.entity.get_output_inventory() + local contents = inv.get_contents() + + -- make sure recent_items is present (can get cleared elsewhere) + if info.recent_items == nil then + info.recent_items = {} + end + + local is_locked = (inv.get_bar() < #inv) + if #info.requests == 0 then + -- fully automatic provider + if is_locked then + update_network_chest_unconfigured_locked(info, inv, contents) + else + update_network_chest_unconfigured_unlocked(info, inv, contents) + end + return GlobalState.UPDATE_STATUS.UPDATE_BULK + else -- configured + if is_locked then + return update_network_chest_configured_locked(info, inv, contents) + else + return update_network_chest_configured_unlocked(info, inv, contents) + end + end +end + +--[[ +This is the handler for the "new" provider-only chest. +Sends everything to the network. No bars, filter, etc. +]] +local function update_network_chest_provider(info) + -- default to bulk if there is nothing to transfer or the network is full + local status = GlobalState.UPDATE_STATUS.UPDATE_BULK + + local inv = info.entity.get_output_inventory() + local contents = inv.get_contents() + local is_full = inv.is_full() + + -- move everything we can to the network + for item, count in pairs(contents) do + if count > 0 then + local n_free = GlobalState.get_insert_count(item) + local n_transfer = math.min(count, n_free) + if n_transfer > 0 then + local n_added = inv.remove({ name=item, count=n_transfer }) + if n_added > 0 then + GlobalState.increment_item_count(item, n_added) + if n_added == count then + -- we transferred everything (assume more room in net) + if is_full then + status = GlobalState.UPDATE_STATUS.UPDATE_PRI_INC + else + status = GlobalState.UPDATE_STATUS.UPDATE_PRI_DEC + end + end + end + else + -- network is full + status = GlobalState.UPDATE_STATUS.UPDATE_BULK + end + end + end + + return status +end + +--[[ +This is the handler for the "new" requester-only chest. +Fills the chest with one item (filter slot 1), respecting the bar. + +NOT USED RIGHT NOW +]] +local function update_network_chest_requester(info) + local status = GlobalState.UPDATE_STATUS.NOT_UPDATED + local inv = info.entity.get_output_inventory() + local contents = inv.get_contents() + + -- satisfy requests (pull into contents) + for _, req in pairs(info.requests) do + if req.type == "take" then + local n_have = contents[req.item] or 0 + local n_innet = GlobalState.get_item_count(req.item) + local n_avail = math.max(0, n_innet - (req.limit or 0)) + local n_want = req.buffer + if n_want > n_have then + local n_transfer = math.min(n_want - n_have, n_avail) + if n_transfer > 0 then + -- it may not fit in the chest due to other reasons + n_transfer = inv.insert({name=req.item, count=n_transfer}) + if n_transfer > 0 then + status = GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME + GlobalState.set_item_count(req.item, n_innet - n_transfer) + + --[[ If we filled the entire buffer AND there is enough in the net for another buffer, then + we are probably not requesting enough. Up the buffer size by 2. + ]] + if n_transfer == req.buffer and n_innet > n_transfer * 4 then + req.buffer = req.buffer + 2 + end + end + else + GlobalState.missing_item_set(req.item, info.entity.unit_number, n_want - n_have) + end + end + end + end + + return status +end + +local function service_network_chest_provider(info) + -- TODO: adjust priority. increase if was full and now empty. decrease otherwise. + return update_network_chest_provider(info) +end + +local function service_network_chest_requester(info) + -- TODO: adjust priority. increase if was empty and now full. decrease otherwise. adjust request amount if pri==0. + update_network_chest_requester(info) + return GlobalState.UPDATE_STATUS.UPDATE_BULK +end + +local function create_network_chest(entity, tags) + local requests = {} + + if tags ~= nil then + local requests_tag = tags.requests + if requests_tag ~= nil then + requests = requests_tag + end + end + + GlobalState.register_chest_entity(entity, requests) +end + +-- copy the settings from @src_info to @dst_info (same entity name) +local function clone_network_chest(dst_info, src_info) + dst_info.requests = table.deepcopy(src_info.requests) +end + +-- paste the settings from @source onto dst_info +local function paste_network_chest(dst_info, source) + if source.name == "network-chest" then + local src_info = GlobalState.get_chest_info(source.unit_number) + if src_info ~= nil then + dst_info.requests = table.deepcopy(src_info.requests) + end + + elseif source.type == "assembling-machine" then + local recipe = source.get_recipe() + if recipe ~= nil then + local requests = {} + local buffer_size = settings.global + ["item-network-stack-size-on-assembler-paste"].value + for _, ingredient in ipairs(recipe.ingredients) do + if ingredient.type == "item" then + local stack_size = game.item_prototypes[ingredient.name].stack_size + local buffer = math.min(buffer_size, stack_size) + table.insert(requests, { + type = "take", + item = ingredient.name, + buffer = buffer, + limit = 0, + }) + end + end + dst_info.requests = requests + end + + elseif source.type == "container" then + -- Turn filter slots into requests, if any. Works for 'se-rocket-launch-pad' + local inv = source.get_output_inventory() + if inv.is_filtered() then + -- clog("SOURCE: %s t=%s inv slots=%s filt=%s", source.name, source.type, #inv, inv.is_filtered()) + local requests = {} + local buffer_size = settings.global["item-network-stack-size-on-assembler-paste"].value + for idx=1,#inv do + local ff = inv.get_filter(idx) + if ff ~= nil then + local prot = game.item_prototypes[ff] + if prot ~= nil then + local stack_size = prot.stack_size + local buffer = math.min(buffer_size, stack_size) + table.insert(requests, { + type = "take", + item = ff, + buffer = buffer, + limit = 0, + }) + -- clog(" - [%s] %s", idx, ff, serpent.line(requests[#requests])) + end + end + end + -- don't change anything if there were no filtered slots + if next(requests) ~= nil then + dst_info.requests = requests + end + end + end +end + +local function paste_network_chest_provider(dst_info, source) + local dest = dst_info.entity + if source.type == "assembling-machine" then + -- paste a filter slot per + local recipe = source.get_recipe() + local dinv = dest.get_output_inventory() + if recipe ~= nil and dinv ~= nil then + -- move items to the net to keep things simple + GlobalState.put_inventory_in_network(dinv) + + -- get existing filters + local filters = {} + local fidx = 1 + while fidx <= #dinv do + local cf = dinv.get_filter(fidx) + if cf ~= nil then + filters[cf] = true + end + fidx = fidx + 1 + end + + -- add new filters + for _, prod in ipairs(recipe.products) do + if prod.type == "item" then + filters[prod.name] = true + end + end + + -- update the filters + fidx = 1 + for name, _ in pairs(filters) do + dinv.set_filter(fidx, name) + fidx = fidx + 1 + end + dinv.set_bar(fidx) + + -- clear any extra filters (shouldn't happen) + while fidx <= #dinv do + dinv.set_filter(fidx, nil) + fidx = fidx + 1 + end + end + end +end + +------------------------------------------------------------------------------- + +local function update_tank(info) + -- NOTE: the network-tank has exactly 1 fluidbox, which may have a nil fluid + + -- hook in autoconfig + if info.config.type == "auto" then + local config = NetworkTankAutoConfig.auto_config(info.entity) + if config == nil then + return GlobalState.UPDATE_STATUS.NOT_UPDATED + end + info.config = config + end + + local status = GlobalState.UPDATE_STATUS.UPDATE_PRI_SAME + local type = info.config.type + local limit = info.config.limit or 5000 + local buffer = info.config.buffer or 1000 + local fluid = info.config.fluid + local temp = info.config.temperature + local no_limit = (info.config.no_limit == true) + + local fluidbox = info.entity.fluidbox + + if type == "give" then + -- We are moving the fluid into the network. + -- the network tank will have sero or one fluidbox... + local fluid_instance = fluidbox[1] + if fluid_instance ~= nil then + local current_count = GlobalState.get_fluid_count( + fluid_instance.name, + fluid_instance.temperature + ) + local key = GlobalState.fluid_temp_key_encode(fluid_instance.name, fluid_instance.temperature) + local gl_limit = GlobalState.get_limit(key) + local n_give = math.max(0, fluid_instance.amount) -- how much we want to give + local n_take = math.max(0, gl_limit - current_count) -- how much the network can take + local n_transfer = math.floor(math.min(n_give, n_take)) + if n_transfer > 0 then + local n_removed = info.entity.remove_fluid({ + name = fluid_instance.name, + temperature = fluid_instance.temperature, + amount = n_transfer, + }) + GlobalState.increment_fluid_count(fluid_instance.name, + fluid_instance.temperature, n_removed) + + -- sent something, so raise the priority + return GlobalState.UPDATE_STATUS.UPDATE_PRI_INC + end + end + -- did not send anything, so lower the priority + return GlobalState.UPDATE_STATUS.UPDATE_PRI_DEC + end + + -- We are requesting fluid FROM the network + local tank_fluid = nil + local tank_temp = nil + local tank_count = 0 + local n_fluid_boxes = 0 + + for idx = 1, #fluidbox do + local fluid_instance = fluidbox[idx] + if fluid_instance ~= nil then + n_fluid_boxes = n_fluid_boxes + 1 + tank_fluid = fluid_instance.name + tank_temp = fluid_instance.temperature + tank_count = fluid_instance.amount + end + end + + -- assume we don't do anything + status = GlobalState.UPDATE_STATUS.UPDATE_PRI_DEC + + -- only touch if there is a matching fluid + if n_fluid_boxes == 0 or (n_fluid_boxes == 1 and tank_fluid == fluid and tank_temp == temp) then + + local network_count = GlobalState.get_fluid_count( + fluid, + temp + ) + -- how much we the network can give us, less the limit + local n_give = math.max(0, network_count - limit) + local n_take = math.max(0, buffer - tank_count) -- how much space we have in the tank + local n_transfer = math.floor(math.min(n_give, n_take)) + if n_transfer > 0 then + -- assume + local added = info.entity.insert_fluid({ + name = fluid, + amount = n_transfer, + temperature = temp, + }) + if added > 0 then + GlobalState.increment_fluid_count(fluid, temp, -added) + status = GlobalState.UPDATE_STATUS.UPDATE_PRI_INC + end + end + if n_take > n_give then + GlobalState.missing_fluid_set(fluid, temp, info.entity.unit_number, + n_take - n_give) + end + end + + return status +end + +local function service_network_tank(info) + return update_tank(info) +end + +local function service_network_tank_provider(info) + -- TODO: simplified function + return update_tank(info) +end + +local function service_network_tank_requester(info) + -- TODO: simplified function + return update_tank(info) +end + +local function create_network_tank(entity, tags) + local config = nil + if tags ~= nil then + local config_tag = tags.config + if config_tag ~= nil then + config = config_tag + end + end + GlobalState.register_tank_entity(entity, config) +end + +-- clone is called with the info for both src and dest. same entity name. +local function clone_network_tank(dst_info, src_info) + dst_info.config = table.deepcopy(src_info.config) +end + +------------------------------------------------------------------------------- + +-- handles logistic storage chests +local function service_logistic_chest_storage(info) + if not settings.global["item-network-enable-logistic-chest"].value then + return GlobalState.UPDATE_STATUS.UPDATE_PRI_MAX + end + + local entity = info.entity + local inv = entity.get_output_inventory() + local new_contents = inv.get_contents() + if next(new_contents) ~= nil then + if info.contents == nil then + info.contents = new_contents + info.contents_tick = game.tick + return + end + + if tabutils.tables_have_same_counts(new_contents, info.contents) then + local tick_delta = game.tick - (info.contents_tick or game.tick) + -- drop content after it does not change for 1 minute + if tick_delta > 60*60 then + -- send contents to network + GlobalState.put_inventory_in_network(inv) + --clog("[%s] [%s] storage to network: %s", game.tick, entity.unit_number, serpent.line(new_contents)) + info.contents = {} -- assume it was sent + info.contents_tick = game.tick + end + else + -- changed, so reset the timer + info.contents = new_contents + info.contents_tick = game.tick + --clog("[%s] [%s] storage updated: %s", game.tick, entity.unit_number, serpent.line(new_contents)) + end + end + return GlobalState.UPDATE_STATUS.UPDATE_PRI_MAX +end + +-- handles logistic buffer and requester chests +local function service_logistic_chest_requester(info) + if not settings.global["item-network-enable-logistic-chest"].value then + return GlobalState.UPDATE_STATUS.UPDATE_PRI_MAX + end + + local entity = info.entity + inventory_handle_requests(entity, entity.get_output_inventory()) + + return GlobalState.UPDATE_STATUS.UPDATE_PRI_MAX +end + + +-------------------- + +local function create_logistic_chest(entity, tags) + GlobalState.logistic_add_entity(entity) +end + +local function create_spidertron(entity, tags) + GlobalState.spidertron_add_entity(entity) +end + +------------------------------------------------------------------------------- +-- register entity handlers + +GlobalState.register_service_task("network-chest", { + service=service_network_chest, + create=create_network_chest, + paste=paste_network_chest, + clone=clone_network_chest, + tag="requests", +}) +-- provider has no extra data. just filters and a bar, which is already managed. +GlobalState.register_service_task("network-chest-provider", { + service=service_network_chest_provider, + create=create_network_chest, + paste=paste_network_chest_provider, +}) +GlobalState.register_service_task("network-chest-requester", { + service=service_network_chest_requester, + create=create_network_chest, + paste=paste_network_chest, + clone=clone_network_chest, + tag="requests", +}) + +GlobalState.register_service_task("network-tank", { + service=service_network_tank, + create=create_network_tank, + clone=clone_network_tank, + tag="config", +}) +GlobalState.register_service_task("network-tank-provider", { + service=service_network_tank_provider, + create=create_network_tank, + clone=clone_network_tank, + tag="config", +}) +GlobalState.register_service_task("network-tank-requester", { + service=service_network_tank_requester, + create=create_network_tank, + clone=clone_network_tank, + tag="config", +}) + +GlobalState.register_service_task("logistic-chest-requester", { + create=create_logistic_chest, + service=service_logistic_chest_requester +}) +GlobalState.register_service_task("logistic-chest-buffer", { + create=create_logistic_chest, + service=service_logistic_chest_requester +}) +GlobalState.register_service_task("logistic-chest-storage", { + create=create_logistic_chest, + service=service_logistic_chest_storage +}) + +GlobalState.register_service_task("spidertron", { + create=create_spidertron, + service=service_spidertron +}) + +------------------------------------------------------------------------------- + +local function service_queue() + GlobalState.queue_service() +end + +Event.on_nth_tick(1, service_queue)