Advertisement
WildBraas

control.lua

Jul 25th, 2019
212
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.23 KB | None | 0 0
  1. -- Code mostly reused from folk
  2. -- Original Mod: https://mods.factorio.com/mods/folk/folk-radar
  3.  
  4. -------------------------------------------------------------------------------
  5. -- CONFIG
  6. --
  7.  
  8. -- On on_configuration_changed, if we detect any entities of type "radar"
  9. -- that are NOT included in this table, it means we need to track radars
  10. -- being .destroy()'d and such in ontick, which is expensive.
  11.  
  12. -- So, to be clear: we only track radar destruction if we find any
  13. -- radar-type entities that are NOT in this table.
  14. -- We also check if the radar has a max_distance_of_sector_revealed property larger than zero.
  15. -- And if it does not, we do not track that either.
  16.  
  17. -- Regardless, we only scan for instantly-replaced entities one time per on_configuration_changed.
  18. -- Otherwise we depend on on_built_entity/on_robot_built_entity.
  19. local CONFIG_TRACK_IGNORE = {
  20.     radar = true,
  21.     ["smart-display-radar"] = true, -- Smart Display
  22.     ["vehicle-deployer-belt"] = true, -- AAI
  23.     ["train-tracker"] = true, -- Vehicle Radar
  24.     ["vehicular-tracker"] = true, -- Vehicle Radar
  25. }
  26. -- ZZZ needs to be the same as in data-final-fixes.lua
  27. local CONFIG_REFERENCE_STRING = "zzz radar-signals [[name]]"
  28.  
  29. -------------------------------------------------------------------------------
  30. -- UTILITY
  31. --
  32.  
  33. -- true/false
  34. local function isValidRadar(radar)
  35.     local reference = game.item_prototypes[CONFIG_REFERENCE_STRING:gsub("%[name%]", tostring(radar.name))]
  36.     if type(reference) == "table" then return true end
  37.     return false
  38. end
  39.  
  40. -- Can return nil
  41. local function findRadar(cc)
  42.     local radars = cc.surface.find_entities_filtered({
  43.         area = { { cc.position.x - 1, cc.position.y - 1 }, { cc.position.x + 1, cc.position.y + 1 } },
  44.         type = "radar",
  45.         force = cc.force,
  46.     })
  47.     if type(radars) == "table" then
  48.         for _, radar in next, radars do
  49.             if isValidRadar(radar) then
  50.                 return radar
  51.             end
  52.         end
  53.     end
  54. end
  55.  
  56. -------------------------------------------------------------------------------
  57. -- SCANNING FOR UNITS
  58. --
  59.  
  60. local scan
  61. do
  62.     -- ZZZ
  63.     -- This map probably can grow quite large during a play session, but
  64.     -- I dont really care.
  65.     local radarCoverageMap = {}
  66.     -- We presume that radars dont move!
  67.     local function cacheCoverage(radar)
  68.         local name = radar.name or "radar"
  69.         local reference = game.item_prototypes[CONFIG_REFERENCE_STRING:gsub("%[name%]", tostring(name))]
  70.         local chunks = reference and reference.order and tonumber(reference.order) or 14
  71.         local tiles = ((chunks / 4) * 32) + 3 -- One chunk is 32 tiles
  72.         --print("Radar coverage for " .. tostring(name) .. " is " .. tostring(chunks) .. "/" .. tostring(tiles))
  73.         local x, y = radar.position.x, radar.position.y
  74.         radarCoverageMap[radar.unit_number] = {
  75.             area = { { x - tiles, y - tiles }, { x + tiles, y + tiles } },
  76.             type = "unit",
  77.         }
  78.         --print(serpent.block(radarCoverageMap[radar.unit_number]))
  79.     end
  80.  
  81.     local params = {
  82.         parameters = {
  83.             {
  84.                 index = 1,
  85.                 count = 0,
  86.                 signal = {
  87.                     type = "virtual",
  88.                     name = "signal-radar-signals-units"
  89.                 }
  90.             },
  91.             {   index = 2,
  92.                 count = 0,
  93.                 signal = {
  94.                     type = "virtual",
  95.                     name = "signal-radar-signals-worms"
  96.                 }
  97.             },
  98.             {   index = 3,
  99.                 count = 0,
  100.                 signal = {
  101.                     type = "virtual",
  102.                     name = "signal-radar-signals-spawners"
  103.                 }
  104.             },
  105.         }
  106.     }
  107.     scan = function(radar, control)
  108.         -- Cached
  109.         if not radarCoverageMap[radar.unit_number] then cacheCoverage(radar) end
  110.        
  111.         local scanUnits = -1
  112.         local scanWorms = -1
  113.         local scanSpawners = -1        
  114.                    
  115.         for i=1, control.signals_count do
  116.             local sig = control.get_signal( i )
  117.             if sig and sig.signal and sig.signal.name then
  118.                 if sig.signal.name == "signal-radar-signals-units" then
  119.                     scanUnits = i
  120.                    
  121.                 elseif sig.signal.name == "signal-radar-signals-worms" then
  122.                     scanWorms = i
  123.                    
  124.                 elseif sig.signal.name == "signal-radar-signals-spawners" then
  125.                     scanSpawners = i
  126.                    
  127.                 end
  128.             end
  129.         end
  130.        
  131.         if (scanUnits >= 0 or scanWorms >= 0 or scanSpawners >= 0) then
  132.             local units, worms, bases = 0, 0, 0
  133.            
  134.             if scanUnits >= 0 then
  135.                 local entities = radar.surface.find_entities_filtered({
  136.                     area = radarCoverageMap[radar.unit_number].area,
  137.                     type = {"unit", "player", "car", "locomotive"},
  138.                 })
  139.                
  140.                 if entities and #entities > 0 then
  141.                     for _, ent in next, entities do
  142.                         if ent and ent.valid and ent.force then
  143.                             if ent.force == radar.force then
  144.                                 units = units + 0
  145.                             else
  146.                                 units = units + 1
  147.                             end
  148.                         end
  149.                     end
  150.                 end
  151.                
  152.                 control.set_signal( scanUnits, {
  153.                     count = units,
  154.                     signal = {
  155.                         type = "virtual",
  156.                         name = "signal-radar-signals-units"
  157.                     }
  158.                 })
  159.             end
  160.            
  161.             if scanWorms >= 0 then
  162.                 local enemyWorms = radar.surface.find_entities_filtered({
  163.                     area = radarCoverageMap[radar.unit_number].area,
  164.                     type = {"turret", "artillery-turret", "ammo-turret", "electric-turret", "fluid-turret", "artillery-wagon"},
  165.                 })
  166.                
  167.                 if enemyWorms and #enemyWorms > 0 then
  168.                     for _, ent in next, enemyWorms do
  169.                         if ent and ent.valid and ent.force then
  170.                             if ent.force == radar.force then
  171.                                 worms = worms + 0
  172.                             else
  173.                                 worms = worms + 1
  174.                             end
  175.                         end
  176.                     end
  177.                 end
  178.                
  179.                 control.set_signal( scanWorms, {
  180.                     count = worms,
  181.                     signal = {
  182.                         type = "virtual",
  183.                         name = "signal-radar-signals-worms"
  184.                     }
  185.                 })
  186.             end
  187.            
  188.             if scanSpawners >= 0 then
  189.                 local enemyBases = radar.surface.find_entities_filtered({
  190.                     area = radarCoverageMap[radar.unit_number].area,
  191.                     type = "unit-spawner",
  192.                 })
  193.                
  194.                 if enemyBases and #enemyBases > 0 then
  195.                     for _, ent in next, enemyBases do
  196.                         if ent and ent.valid and ent.force then
  197.                             if ent.force == radar.force then
  198.                                 bases = bases + 0
  199.                             else
  200.                                 bases = bases + 1
  201.                             end
  202.                         end
  203.                     end
  204.                 end
  205.                
  206.                 control.set_signal( scanSpawners, {
  207.                     count = bases,
  208.                     signal = {
  209.                         type = "virtual",
  210.                         name = "signal-radar-signals-spawners"
  211.                     }
  212.                 })
  213.             end
  214.        
  215.         end
  216.     end
  217. end
  218.  
  219. local function Execute()
  220.     -- Instead using table.remove while moving reporters items to reportersDone here used a runner
  221.     global.reportersOffset = global.reportersOffset + 1
  222.    
  223.     if global.reportersOffset > #global.reporters then
  224.         -- game.print("Index out of range")
  225.         return
  226.     end
  227.     local m = global.reporters[global.reportersOffset]
  228.                    
  229.     if m and m.cc and m.cc.valid then
  230.         if global.needToTrackRadars and (not m.radar or not m.radar.valid) and not m.ignore then
  231.             local newRadar = findRadar(m.cc)
  232.             if newRadar then
  233.                 m.radar = newRadar
  234.             else
  235.                 -- We didnt find any radar. Assume that there just isnt one there, and dont scan again.
  236.                 -- Until on_configuration_changed.
  237.                 m.ignore = true
  238.             end
  239.         end
  240.         if m.radar and m.radar.valid and m.radar.energy > 0 then
  241.             scan(m.radar, m.cb)
  242.         end
  243.         -- This combinator is still executable, save from next gigatick
  244.         global.reportersDone[#global.reportersDone + 1] = m;
  245.     end
  246. end
  247. -------------------------------------------------------------------------------
  248. -- TICK HANDLER
  249. -- Fired every 240 ticks (4 seconds?)
  250. --
  251.  
  252. local tick
  253. do
  254.     -- Every 4 seconds, so lets be careful!
  255.     tick = function(event)
  256.         if not global.reporters then return end
  257.  
  258.         if not global.reportersDone then global.reportersDone = {} end
  259.  
  260.         -- I dont know how works with nil integers, maybe this codepart is silly
  261.         if type(global.reportersOffset) ~= "number" then global.reportersOffset = 0; end
  262.  
  263.         local totalTicks = 240; -- Maybe do a map option?
  264.  
  265.         local leaseTicks = totalTicks - 1 - event.tick % totalTicks;
  266.        
  267.         if leaseTicks == 0 then
  268.             if #global.reporters > global.reportersOffset then
  269.                 -- Strange! But sometimes in first zero-tick this happens. Calc least repos.
  270.                
  271.                 -- game.print("Unexecuted " .. (#global.reporters - global.reportersOffset + 1) .. " items")
  272.                 while #global.reporters > global.reportersOffset do
  273.                     Execute()
  274.                 end
  275.                
  276.             end
  277.             -- swapping tablesses
  278.             global.reporters = global.reportersDone
  279.             -- Until not possible to use
  280.             -- lua_createtable(global.reportersDone, #global.reporters, 0)
  281.             -- Which set hashtable stack to whole array we have little sagging in first parts of gigatick
  282.             global.reportersDone = {} -- Daddy GC will care unlinked table itself
  283.             global.reportersOffset = 0;
  284.  
  285.             -- game.print("Done worked with " .. #global.reporters .. " radar combinators")
  286.         else
  287.             local currentReportersCount = 0;
  288.             if #global.reporters >= totalTicks then
  289.                 currentReportersCount = math.floor((#global.reporters - global.reportersOffset) / leaseTicks)
  290.             else
  291.                 local unFloor = totalTicks / #global.reporters
  292.                 if(unFloor >= 2) then
  293.                     -- uniform distribution
  294.                     local floor = math.floor(unFloor)
  295.                     if (leaseTicks % floor) == 0 then
  296.                         currentReportersCount = 1
  297.                     else
  298.                         currentReportersCount = 0
  299.                         -- game.print("LT " .. leaseTicks .. ": Nit, floor is " .. floor)
  300.                     end
  301.                 else
  302.                     --uniform dedistribution
  303.                     local floor = math.floor(totalTicks / (totalTicks - #global.reporters))
  304.                     if (leaseTicks % floor) == 0 then
  305.                         currentReportersCount = 0
  306.                         -- game.print("LT " .. leaseTicks .. ": Nit, defloor is " .. floor)
  307.                     else
  308.                         currentReportersCount = 1
  309.                     end
  310.                 end
  311.             end
  312.            
  313.             -- if currentReportersCount > 0 then
  314.                 -- game.print("LT " .. leaseTicks .. ": " .. currentReportersCount .. " items")
  315.             -- end
  316.            
  317.             for _ = 1, currentReportersCount do
  318.                 Execute()
  319.             end
  320.         end
  321.     end
  322. end
  323.  
  324. -------------------------------------------------------------------------------
  325. -- ON BUILT HANDLER
  326. --
  327.  
  328. do
  329.     local function onBuilt(event)
  330.         local e = event.created_entity
  331.         if event and e then
  332.             if e.name == "radar-signals" then
  333.                 if not global.reporters then global.reporters = {} end
  334.                 if not global.reportersDone then global.reportersDone = {} end
  335.  
  336.                 if #global.reporters == 0 then
  337.                     script.on_event(defines.events.on_tick, tick)
  338.                 end
  339.  
  340.                 -- Prevents clicking it and using it like a normal constant combinator
  341.                 e.operable = true
  342.                 --e.get_control_behavior().parameters = nil
  343.  
  344.                 -- find out if we are next to a radar
  345.                 local newRadar = findRadar(e)
  346.                 -- Nothing to do for now, but lets wait for a radar
  347.                 if newRadar then
  348.                     global.reporters[#global.reporters + 1] = { cc = e, cb = e.get_control_behavior(), radar = newRadar }
  349.                 else
  350.                     global.reporters[#global.reporters + 1] = { cc = e, cb = e.get_control_behavior() }
  351.                 end
  352.             elseif e.type == "radar" then
  353.                 -- find out if there is a reporter next to this radar
  354.                 local cc = e.surface.find_entities_filtered({
  355.                     area = { { e.position.x - 2.3, e.position.y - 2.3 }, { e.position.x + 2.3, e.position.y + 2.3 } },
  356.                     name = "radar-signals",
  357.                     force = e.force,
  358.                     limit = 1,
  359.                 })
  360.                 if type(cc) ~= "table" or #cc == 0 then return end
  361.  
  362.                 for _, m in next, global.reporters do
  363.                     if m and m.cc and m.cc.valid and m.cc.unit_number == cc[1].unit_number then
  364.                         m.radar = e
  365.                         m.ignore = nil
  366.                         --m.cb.parameters = nil
  367.                         break
  368.                     end
  369.                 end
  370.             end
  371.         end
  372.     end
  373.     script.on_event(defines.events.on_built_entity, onBuilt)
  374.     script.on_event(defines.events.on_robot_built_entity, onBuilt)
  375. end
  376.  
  377. -------------------------------------------------------------------------------
  378. -- CONFIGURATION HANDLING
  379. -- Mostly checks if there are 3rd party mods that add custom radars.
  380. -- If there is, we presume that they instantly destroy() and create_entity()
  381. -- new radar types, which does NOT trigger any events.
  382. --
  383. -- So if we find any custom radar types in the data, we set a flag that enables
  384. -- each CC-box to look around itself on the next tick (4 seconds) for any
  385. -- randomly-appearing radars.
  386. --
  387. -- If it cant find any on that tick, it wont look again until the next
  388. -- on_configuration_changed, but will obviously react to one that is built next
  389. -- to it.
  390. --
  391.  
  392. do
  393.     local function checkForWeirdRadars()
  394.         local found = false
  395.         for name, ent in pairs(game.entity_prototypes) do
  396.             if ent.type == "radar" and not CONFIG_TRACK_IGNORE[name] then
  397.                 -- Check if this radar type has a max_distance_of_sector_revealed bigger than zero
  398.                 if isValidRadar(ent) then
  399.                     global.needToTrackRadars = true
  400.                     found = true
  401.                     break
  402.                 end
  403.             end
  404.         end
  405.         if not found then global.needToTrackRadars = false end
  406.  
  407.         -- Mark CCs for re-scanning for radar entities that are instantly replaced
  408.         if global.reporters and #global.reporters > 0 then
  409.             for _, m in next, global.reporters do
  410.                 m.ignore = nil
  411.             end
  412.         end
  413.         if global.reportersDone and #global.reportersDone > 0 then
  414.             for _, m in next, global.reportersDone do
  415.                 m.ignore = nil
  416.             end
  417.         end
  418.     end
  419.  
  420.     script.on_configuration_changed(checkForWeirdRadars)
  421.  
  422.     script.on_load(function()
  423.         if #global.reporters > 0 then
  424.             script.on_event(defines.events.on_tick, tick)
  425.         end
  426.        
  427.     end)
  428.  
  429.     script.on_init(function()
  430.         global.reporters = global.reporters or {}
  431.         global.reportersDone = global.reportersDone or {}
  432.         global.reportersOffset = global.reportersOffset or 0 -- Because table.remove is very expensively operation we use offset
  433.         global.needToTrackRadars = global.needToTrackRadars or false
  434.  
  435.         checkForWeirdRadars()
  436.     end)
  437. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement