]> git.somenet.org - factorio/some-autoresearch.git/blob - control.lua
ce1a090606f5ec6d010102695571b8edb773d1e1
[factorio/some-autoresearch.git] / control.lua
1
2 function getConfig(force, config_changed)
3     if not storage.auto_research_config then
4         storage.auto_research_config = {}
5     end
6
7     -- Disable Research Queue popup
8     if remote.interfaces.RQ and remote.interfaces.RQ["popup"] then
9         remote.call("RQ", "popup", false)
10     end
11
12     if not storage.auto_research_config[force.name] then
13         storage.auto_research_config[force.name] = {
14             prioritized_techs = {}, -- "prioritized" is "queued". kept for backwards compatability (because i'm lazy and don't want migration code)
15             deprioritized_techs = {} -- "deprioritized" is "blacklisted". kept for backwards compatability (because i'm lazy and don't want migration code)
16         }
17         -- Enable Auto Research
18         setAutoResearch(force, true)
19
20         -- Disable queued only
21         setQueuedOnly(force, false)
22
23         -- Allow switching research
24         setAllowSwitching(force, true)
25
26         -- Print researched technology
27         setAnnounceCompletedResearch(force, true)
28     end
29
30     -- set research strategy
31     storage.auto_research_config[force.name].research_strategy = storage.auto_research_config[force.name].research_strategy or "balanced"
32
33     if config_changed or not storage.auto_research_config[force.name].allowed_ingredients or not storage.auto_research_config[force.name].infinite_research then
34         -- remember any old ingredients
35         local old_ingredients = {}
36         if storage.auto_research_config[force.name].allowed_ingredients then
37             for name, enabled in pairs(storage.auto_research_config[force.name].allowed_ingredients) do
38                 old_ingredients[name] = enabled
39             end
40         end
41         -- find all possible tech ingredients
42         -- also scan for research that are infinite: techs that have no successor and tech.research_unit_count_formula is not nil
43         storage.auto_research_config[force.name].allowed_ingredients = {}
44         storage.auto_research_config[force.name].infinite_research = {}
45         local finite_research = {}
46         for _, tech in pairs(force.technologies) do
47             for _, ingredient in pairs(tech.research_unit_ingredients) do
48                 storage.auto_research_config[force.name].allowed_ingredients[ingredient.name] = (old_ingredients[ingredient.name] == nil or old_ingredients[ingredient.name])
49             end
50             if tech.research_unit_count_formula then
51                 storage.auto_research_config[force.name].infinite_research[tech.name] = tech
52             end
53             for _, pretech in pairs(tech.prerequisites) do
54                 if pretech.enabled and not pretech.researched then
55                     finite_research[pretech.name] = true
56                 end
57             end
58         end
59         for techname, _ in pairs(finite_research) do
60             storage.auto_research_config[force.name].infinite_research[techname] = nil
61         end
62     end
63
64     return storage.auto_research_config[force.name]
65 end
66
67 function setAutoResearch(force, enabled)
68     if not force then
69         return
70     end
71     local config = getConfig(force)
72     config.enabled = enabled
73
74     if enabled then
75         -- start new research
76         startNextResearch(force)
77     end
78 end
79
80 function setQueuedOnly(force, enabled)
81     if not force then
82         return
83     end
84     getConfig(force).prioritized_only = enabled
85
86     -- start new research
87     startNextResearch(force)
88 end
89
90 function setAllowSwitching(force, enabled)
91     if not force then
92         return
93     end
94     getConfig(force).allow_switching = enabled
95
96     -- start new research
97     startNextResearch(force)
98 end
99
100 function setAnnounceCompletedResearch(force, enabled)
101     if not force then
102         return
103     end
104     getConfig(force).announce_completed = enabled
105 end
106
107 function setDeprioritizeInfiniteTech(force, enabled)
108     if not force then
109         return
110     end
111     getConfig(force).deprioritize_infinite_tech = enabled
112
113     -- start new research
114     startNextResearch(force)
115 end
116
117 function getPretechs(tech)
118     local pretechs = {}
119     pretechs[#pretechs + 1] = tech
120     local index = 1
121     while (index <= #pretechs) do
122         for _, pretech in pairs(pretechs[index].prerequisites) do
123             if pretech.enabled and not pretech.researched then
124                 pretechs[#pretechs + 1]  = pretech
125             end
126         end
127         index = index + 1
128     end
129     return pretechs
130 end
131
132 function canResearch(force, tech, config)
133     if not tech or tech.researched or not tech.enabled or tech.prototype.hidden then
134         return false
135     end
136     for _, pretech in pairs(tech.prerequisites) do
137         if not pretech.researched then
138             return false
139         end
140     end
141     if #tech.research_unit_ingredients == 0 then
142         return false
143     end
144     for _, ingredient in pairs(tech.research_unit_ingredients) do
145         if not config.allowed_ingredients[ingredient.name] then
146             return false
147         end
148     end
149     for _, deprioritized in pairs(config.deprioritized_techs) do
150         if tech.name == deprioritized then
151             return false
152         end
153     end
154     return true
155 end
156
157 function startNextResearch(force, override_spam_detection)
158     local config = getConfig(force)
159     if not config.enabled or (force.current_research and not config.allow_switching) or (not override_spam_detection and config.last_research_finish_tick == game.tick) then
160         return
161     end
162     config.last_research_finish_tick = game.tick -- if multiple research finish same tick for same force, the user probably enabled all techs
163
164     -- function for calculating tech effort
165     local calcEffort = function(tech)
166         local ingredientCount = function(ingredients)
167             local tech_ingredients = 0
168             for _, ingredient in pairs(tech.research_unit_ingredients) do
169                 tech_ingredients = tech_ingredients + ingredient.amount
170             end
171             return tech_ingredients
172         end
173         local effort = 0
174         if config.research_strategy == "fast" then
175             effort = math.max(tech.research_unit_energy, 1) * math.max(tech.research_unit_count, 1)
176         elseif config.research_strategy == "slow" then
177             effort = math.max(tech.research_unit_energy, 1) * math.max(tech.research_unit_count, 1) * -1
178         elseif config.research_strategy == "cheap" then
179             effort = math.max(ingredientCount(tech.research_unit_ingredients), 1) * math.max(tech.research_unit_count, 1)
180         elseif config.research_strategy == "expensive" then
181             effort = math.max(ingredientCount(tech.research_unit_ingredients), 1) * math.max(tech.research_unit_count, 1) * -1
182         elseif config.research_strategy == "balanced" then
183             effort = math.max(tech.research_unit_count, 1) * math.max(tech.research_unit_energy, 1) * math.max(ingredientCount(tech.research_unit_ingredients), 1)
184         else
185             effort = math.random(1, 999)
186         end
187         if (config.deprioritize_infinite_tech and config.infinite_research[tech.name]) then
188             return effort * (effort > 0 and 1000 or -1000)
189         else
190             return effort
191         end
192     end
193
194     -- see if there are some techs we should research first
195     local next_research = nil
196     local least_effort = nil
197     for _, techname in pairs(config.prioritized_techs) do
198         local tech = force.technologies[techname]
199         if tech and not next_research then
200             local pretechs = getPretechs(tech)
201             for _, pretech in pairs(pretechs) do
202                 local effort = calcEffort(pretech)
203                 if (not least_effort or effort < least_effort) and canResearch(force, pretech, config) then
204                     next_research = pretech.name
205                     least_effort = effort
206                 end
207             end
208         end
209     end
210
211     -- if no queued tech should be researched then research the "least effort" tech not researched yet
212     if not config.prioritized_only and not next_research then
213         for techname, tech in pairs(force.technologies) do
214             if tech.enabled and not tech.researched then
215                 local effort = calcEffort(tech)
216                 if (not least_effort or effort < least_effort) and canResearch(force, tech, config) then
217                     next_research = techname
218                     least_effort = effort
219                 end
220             end
221         end
222     end
223
224     -- keep queue, just put next_research at the start.
225     if next_research then
226         local rq = {}
227
228         -- manage last element of research queue
229         for i=1,7 do
230             if force.research_queue[i] == nil then break end
231             if i == (#force.research_queue) then
232                 if not (force.current_research and config.allow_switching) then
233                     table.insert(rq, force.research_queue[i].name)
234                 end
235             else
236                 table.insert(rq, force.research_queue[i].name)
237             end
238         end
239         table.insert(rq, next_research)
240
241         force.research_queue = rq
242     end
243 end
244
245 function onResearchFinished(event)
246     local force = event.research.force
247     local config = getConfig(force)
248     -- remove researched stuff from prioritized_techs and deprioritized_techs
249     for i = #config.prioritized_techs, 1, -1 do
250         local tech = force.technologies[config.prioritized_techs[i]]
251         if not tech or tech.researched then
252             table.remove(config.prioritized_techs, i)
253         end
254     end
255     for i = #config.deprioritized_techs, 1, -1 do
256         local tech = force.technologies[config.deprioritized_techs[i]]
257         if not tech or tech.researched then
258             table.remove(config.deprioritized_techs, i)
259         end
260     end
261     -- announce completed research
262     if config.announce_completed and config.no_announce_this_tick ~= game.tick then
263         if config.last_research_finish_tick == game.tick then
264             config.no_announce_this_tick = game.tick
265         else
266             local level = ""
267             if event.research.research_unit_count_formula then
268                 level = (event.research.researched and event.research.level) or (event.research.level - 1)
269             end
270             force.print{"auto_research.announce_completed", event.research.localised_name, level}
271         end
272     end
273
274     startNextResearch(event.research.force)
275 end
276
277 -- user interface
278 gui = {
279     toggleGui = function(player)
280         if player.gui.top.auto_research_gui then
281             player.gui.top.auto_research_gui.destroy()
282         else
283             local force = player.force
284             local config = getConfig(force)
285             local frame = player.gui.top.add{
286                 type = "frame",
287                 name = "auto_research_gui",
288                 direction = "vertical",
289                 caption = {"auto_research_gui.title"}
290             }
291             local frameflow = frame.add{
292                 type = "flow",
293                 style = "auto_research_list_flow",
294                 name = "flow",
295                 direction = "vertical"
296             }
297
298             -- checkboxes
299             frameflow.add{type = "checkbox", name = "auto_research_enabled", caption = {"auto_research_gui.enabled"}, tooltip = {"auto_research_gui.enabled_tooltip"}, state = config.enabled or false}
300             frameflow.add{type = "checkbox", name = "auto_research_queued_only", caption = {"auto_research_gui.prioritized_only"}, tooltip = {"auto_research_gui.prioritized_only_tooltip"}, state = config.prioritized_only or false}
301             frameflow.add{type = "checkbox", name = "auto_research_allow_switching", caption = {"auto_research_gui.allow_switching"}, tooltip = {"auto_research_gui.allow_switching_tooltip"}, state = config.allow_switching or false}
302             frameflow.add{type = "checkbox", name = "auto_research_announce_completed", caption = {"auto_research_gui.announce_completed"}, tooltip = {"auto_research_gui.announce_completed_tooltip"}, state = config.announce_completed or false}
303             frameflow.add{type = "checkbox", name = "auto_research_deprioritize_infinite_tech", caption = {"auto_research_gui.deprioritize_infinite_tech"}, tooltip = {"auto_research_gui.deprioritize_infinite_tech_tooltip"}, state = config.deprioritize_infinite_tech or false}
304
305             -- research strategy
306             frameflow.add{
307                 type = "label",
308                 style = "auto_research_header_label",
309                 caption = {"auto_research_gui.research_strategy"}
310             }
311             local research_strategies_outer = frameflow.add{
312                 type = "flow",
313                 style = "auto_research_tech_flow",
314                 name = "research_strategies_outer",
315                 direction = "horizontal"
316             }
317             research_strategies_outer.add{type = "radiobutton", name = "auto_research_research_fast", caption = {"auto_research_gui.research_fast"}, tooltip = {"auto_research_gui.research_fast_tooltip"}, state = config.research_strategy == "fast"}
318             research_strategies_outer.add{type = "radiobutton", name = "auto_research_research_slow", caption = {"auto_research_gui.research_slow"}, tooltip = {"auto_research_gui.research_slow_tooltip"}, state = config.research_strategy == "slow"}
319             research_strategies_outer.add{type = "radiobutton", name = "auto_research_research_cheap", caption = {"auto_research_gui.research_cheap"}, tooltip = {"auto_research_gui.research_cheap_tooltip"}, state = config.research_strategy == "cheap"}
320             research_strategies_outer.add{type = "radiobutton", name = "auto_research_research_expensive", caption = {"auto_research_gui.research_expensive"}, tooltip = {"auto_research_gui.research_expensive_tooltip"}, state = config.research_strategy == "expensive"}
321             research_strategies_outer.add{type = "radiobutton", name = "auto_research_research_balanced", caption = {"auto_research_gui.research_balanced"}, tooltip = {"auto_research_gui.research_balanced_tooltip"}, state = config.research_strategy == "balanced"}
322             research_strategies_outer.add{type = "radiobutton", name = "auto_research_research_random", caption = {"auto_research_gui.research_random"}, tooltip = {"auto_research_gui.research_random_tooltip"}, state = config.research_strategy == "random"}
323
324             research_strategies_outer.style.horizontal_spacing = 6
325
326             -- allowed ingredients
327             frameflow.add{
328                 type = "label",
329                 style = "auto_research_header_label",
330                 caption = {"auto_research_gui.allowed_ingredients_label"}
331             }
332             local allowed_ingredients = frameflow.add{
333                 type = "flow",
334                 style = "auto_research_list_flow",
335                 name = "allowed_ingredients",
336                 direction = "vertical"
337             }
338             gui.updateAllowedIngredientsList(player.gui.top.auto_research_gui.flow.allowed_ingredients, player, config)
339
340             -- prioritized techs
341             frameflow.add{
342                 type = "label",
343                 style = "auto_research_header_label",
344                 caption = {"auto_research_gui.prioritized_label"}
345             }
346             local prioritized = frameflow.add{
347                 type = "scroll-pane",
348                 name = "prioritized",
349                 horizontal_scroll_policy = "never",
350                 vertical_scroll_policy = "auto"
351             }
352             prioritized.style.top_padding = 5
353             prioritized.style.bottom_padding = 5
354             prioritized.style.maximal_height = 127
355             prioritized.style.minimal_width = 440
356             -- draw prioritized tech list
357             gui.updateTechnologyList(player.gui.top.auto_research_gui.flow.prioritized, config.prioritized_techs, player, true)
358
359             -- deprioritized techs
360             frameflow.add{
361                 type = "label",
362                 style = "auto_research_header_label",
363                 caption = {"auto_research_gui.deprioritized_label"}
364             }
365             local deprioritized = frameflow.add{
366                 type = "scroll-pane",
367                 name = "deprioritized",
368                 horizontal_scroll_policy = "never",
369                 vertical_scroll_policy = "auto"
370             }
371             deprioritized.style.top_padding = 5
372             deprioritized.style.bottom_padding = 5
373             deprioritized.style.maximal_height = 127
374             deprioritized.style.minimal_width = 440
375
376             -- draw deprioritized tech list
377             gui.updateTechnologyList(player.gui.top.auto_research_gui.flow.deprioritized, config.deprioritized_techs, player)
378
379             -- search for techs
380             local searchflow = frameflow.add{
381                 type = "flow",
382                 name = "searchflow",
383                 style = "auto_research_tech_flow",
384                 direction = "horizontal"
385             }
386             searchflow.add{
387                 type = "label",
388                 style = "auto_research_header_label",
389                 caption = {"auto_research_gui.search_label"}
390             }
391             searchflow.add{
392                 type = "textfield",
393                 name = "auto_research_search_text",
394                 tooltip = {"auto_research_gui.search_tooltip"}
395             }
396             searchflow.add{
397                 type = "checkbox",
398                 name = "auto_research_ingredients_filter_search_results",
399                 caption = {"auto_research_gui.ingredients_filter_search_results"},
400                 tooltip = {"auto_research_gui.ingredients_filter_search_results_tooltip"},
401                 state = config.filter_search_results or false
402             }
403             searchflow.style.horizontal_spacing = 6
404             searchflow.style.vertical_align = "center"
405
406             local search = frameflow.add{
407                 type = "scroll-pane",
408                 name = "search",
409                 horizontal_scroll_policy = "never",
410                 vertical_scroll_policy = "auto"
411             }
412             search.style.top_padding = 5
413             search.style.bottom_padding = 5
414             search.style.maximal_height = 127
415             search.style.minimal_width = 440
416
417             -- draw search result list
418             gui.updateSearchResult(player, "")
419         end
420     end,
421
422     onCheckboxClick = function(event)
423         local player = game.players[event.player_index]
424         local force = player.force
425         local name = event.element.name
426         if name == "auto_research_enabled" then
427             setAutoResearch(force, event.element.state)
428         elseif name == "auto_research_queued_only" then
429             setQueuedOnly(force, event.element.state)
430         elseif name == "auto_research_allow_switching" then
431             setAllowSwitching(force, event.element.state)
432         elseif name == "auto_research_announce_completed" then
433             setAnnounceCompletedResearch(force, event.element.state)
434         elseif name == "auto_research_deprioritize_infinite_tech" then
435             setDeprioritizeInfiniteTech(force, event.element.state)
436         elseif name == "auto_research_ingredients_filter_search_results" then
437             local config = getConfig(force)
438             config.filter_search_results = event.element.state
439             gui.updateSearchResult(player, player.gui.top.auto_research_gui.flow.searchflow.auto_research_search_text.text)
440         end
441     end,
442
443     onClick = function(event)
444         local player = game.players[event.player_index]
445         local force = player.force
446         local config = getConfig(force)
447         local name = event.element.name
448         if name == "auto_research_search_text" then
449             if event.button == defines.mouse_button_type.right then
450                 player.gui.top.auto_research_gui.flow.searchflow.auto_research_search_text.text = ""
451                 gui.updateSearchResult(player, player.gui.top.auto_research_gui.flow.searchflow.auto_research_search_text.text)
452             end
453         elseif string.find(name, "auto_research_research") then
454             config.research_strategy = string.match(name, "^auto_research_research_(.*)$")
455             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_fast.state = (config.research_strategy == "fast")
456             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_cheap.state = (config.research_strategy == "cheap")
457             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_balanced.state = (config.research_strategy == "balanced")
458             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_slow.state = (config.research_strategy == "slow")
459             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_expensive.state = (config.research_strategy == "expensive")
460             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_random.state = (config.research_strategy == "random")
461             -- start new research
462             startNextResearch(force)
463         else
464             local prefix, name = string.match(name, "^auto_research_([^-]*)-(.*)$")
465             if prefix == "allow_ingredient" then
466                 config.allowed_ingredients[name] = not config.allowed_ingredients[name]
467                 gui.updateAllowedIngredientsList(player.gui.top.auto_research_gui.flow.allowed_ingredients, player, config)
468                 if player.gui.top.auto_research_gui.flow.searchflow.auto_research_ingredients_filter_search_results.state then
469                     gui.updateSearchResult(player, player.gui.top.auto_research_gui.flow.searchflow.auto_research_search_text.text)
470                 end
471                 startNextResearch(force)
472             elseif name and force.technologies[name] then
473                 -- remove tech from prioritized list
474                 for i = #config.prioritized_techs, 1, -1 do
475                     if config.prioritized_techs[i] == name then
476                         table.remove(config.prioritized_techs, i)
477                     end
478                 end
479                 -- and from deprioritized list
480                 for i = #config.deprioritized_techs, 1, -1 do
481                     if config.deprioritized_techs[i] == name then
482                         table.remove(config.deprioritized_techs, i)
483                     end
484                 end
485                 if prefix == "queue_top" then
486                     -- add tech to top of prioritized list
487                     table.insert(config.prioritized_techs, 1, name)
488                 elseif prefix == "queue_bottom" then
489                     -- add tech to bottom of prioritized list
490                     table.insert(config.prioritized_techs, name)
491                 elseif prefix == "blacklist" then
492                     -- add tech to list of deprioritized techs
493                     table.insert(config.deprioritized_techs, name)
494                 end
495                 gui.updateTechnologyList(player.gui.top.auto_research_gui.flow.prioritized, config.prioritized_techs, player, true)
496                 gui.updateTechnologyList(player.gui.top.auto_research_gui.flow.deprioritized, config.deprioritized_techs, player)
497                 gui.updateSearchResult(player, player.gui.top.auto_research_gui.flow.searchflow.auto_research_search_text.text)
498
499                 -- start new research
500                 startNextResearch(force)
501             end
502         end
503     end,
504
505     updateAllowedIngredientsList = function(flow, player, config)
506         local counter = 1
507         while flow["flow" .. counter] do
508             flow["flow" .. counter].destroy()
509             counter = counter + 1
510         end
511         counter = 1
512         for ingredientname, allowed in pairs(config.allowed_ingredients) do
513             local flowname = "flow" .. math.floor(counter / 10) + 1
514             local ingredientflow = flow[flowname]
515             if not ingredientflow then
516                 ingredientflow = flow.add {
517                     type = "flow",
518                     style = "auto_research_tech_flow",
519                     name = flowname,
520                     direction = "horizontal"
521                 }
522             end
523             local sprite = "auto_research_tool_" .. ingredientname
524             if not helpers.is_valid_sprite_path(sprite) then
525                 sprite = "auto_research_unknown"
526             end
527             ingredientflow.add{type = "sprite-button", style = "auto_research_sprite_button_toggle" .. (allowed and "_pressed" or ""), name = "auto_research_allow_ingredient-" .. ingredientname, tooltip = {"item-name." .. ingredientname}, sprite = sprite}
528             counter = counter + 1
529         end
530     end,
531
532     updateTechnologyList = function(scrollpane, technologies, player, show_queue_buttons)
533         if scrollpane.flow then
534             scrollpane.flow.destroy()
535         end
536         local flow = scrollpane.add{
537             type = "flow",
538             style = "auto_research_list_flow",
539             name = "flow",
540             direction = "vertical"
541         }
542         if #technologies > 0 then
543             for _, techname in pairs(technologies) do
544                 local tech = player.force.technologies[techname]
545                 if tech then
546                     local entryflow = flow.add{type = "flow", style = "auto_research_tech_flow", direction = "horizontal"}
547                     if show_queue_buttons then
548                         entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_queue_top-" .. techname, sprite = "auto_research_prioritize_top"}
549                         entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_queue_bottom-" .. techname, sprite = "auto_research_prioritize_bottom"}
550                     end
551                     entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_delete-" .. techname, sprite = "auto_research_delete"}
552                     entryflow.add{type = "label", style = "auto_research_tech_label", caption = tech.localised_name}
553                     for _, ingredient in pairs(tech.research_unit_ingredients) do
554                         local sprite = "auto_research_tool_" .. ingredient.name
555                         if not helpers.is_valid_sprite_path(sprite) then
556                             sprite = "auto_research_unknown"
557                         end
558                         entryflow.add{type = "sprite", style = "auto_research_sprite", sprite = sprite}
559                     end
560                 end
561             end
562         else
563             local entryflow = flow.add{type = "flow", direction = "horizontal"}
564             entryflow.add{type = "label", caption = {"auto_research_gui.none"}}
565         end
566     end,
567
568     updateSearchResult = function(player, text)
569         local scrollpane = player.gui.top.auto_research_gui.flow.search
570         if scrollpane.flow then
571             scrollpane.flow.destroy()
572         end
573         local flow = scrollpane.add{
574             type = "flow",
575             style = "auto_research_list_flow",
576             name = "flow",
577             direction = "vertical"
578         }
579         local ingredients_filter = player.gui.top.auto_research_gui.flow.searchflow.auto_research_ingredients_filter_search_results.state
580         local config = getConfig(player.force)
581         local shown = 0
582         text = string.lower(text)
583         -- NOTICE: localised name matching does not work at present, pending unlikely changes to Factorio API
584         for name, tech in pairs(player.force.technologies) do
585             if not tech.researched and tech.enabled then
586                 local showtech = false
587                 if string.find(string.lower(name), text, 1, true) then
588                     -- show techs that match by name
589                     showtech = true
590                 -- elseif string.find(string.lower(game.technology_prototypes[name].localised_name), text, 1, true) then
591                 --     -- show techs that match by localised name
592                 --     showtech = true
593                 else
594                     for _, effect in pairs(tech.prototype.effects) do
595                         if string.find(effect.type, text, 1, true) then
596                             -- show techs that match by effect type
597                             showtech = true
598                         elseif effect.type == "unlock-recipe" then
599                             if string.find(effect.recipe, text, 1, true) then
600                                 -- show techs that match by unlocked recipe name
601                                 showtech = true
602                             -- elseif string.find(string.lower(game.recipe_prototypes[effect.recipe].localised_name), text, 1, true) then
603                             --     -- show techs that match by unlocked recipe localised name
604                             --     showtech = true
605                             else
606                                 for _, product in pairs(prototypes.recipe[effect.recipe].products) do
607                                     if string.find(product.name, text, 1, true) then
608                                         -- show techs that match by unlocked recipe product name
609                                         showtech = true
610                                     -- elseif string.find(string.lower(game.item_prototypes[product.name].localised_name), text, 1, true) then
611                                     --     -- show techs that match by unlocked recipe product localised name
612                                     --     showtech = true
613                                     else
614                                         local prototype = prototypes.item[product.name]
615                                         if prototype then
616                                             if prototype.place_result then
617                                                 if string.find(prototype.place_result.name, text, 1, true) then
618                                                     -- show techs that match by unlocked recipe product placed entity name
619                                                     showtech = true
620                                                 -- elseif string.find(string.lower(game.entity_prototypes[prototype.place_result.name].localised_name), text, 1, true) then
621                                                 --     -- show techs that match by unlocked recipe product placed entity localised name
622                                                 --     showtech = true
623                                                 end
624                                             elseif prototype.place_as_equipment_result then
625                                                 if string.find(prototype.place_as_equipment_result.name, text, 1, true) then
626                                                     -- show techs that match by unlocked recipe product placed equipment name
627                                                     showtech = true
628                                                 -- elseif string.find(string.lower(game.equipment_prototypes[prototype.place_as_equipment_result.name].localised_name), text, 1, true) then
629                                                 --     -- show techs that match by unlocked recipe product placed equipment localised name
630                                                 --     showtech = true
631                                                 end
632                                             elseif prototype.place_as_tile_result then
633                                                 if string.find(prototype.place_as_tile_result.result.name, text, 1, true) then
634                                                     -- show techs that match by unlocked recipe product placed tile name
635                                                     showtech = true
636                                                 -- elseif string.find(string.lower(prototype.place_as_tile_result.result.localised_name), text, 1, true) then
637                                                 --     -- show techs that match by unlocked recipe product placed tile localised name
638                                                 --     showtech = true
639                                                 end
640                                             end
641                                         end
642                                     end
643                                 end
644                             end
645                         end
646                     end
647                 end
648                 if showtech and config.prioritized_techs then
649                     for _, queued_tech in pairs(config.prioritized_techs) do
650                         if name == queued_tech then
651                             showtech = false
652                             break
653                         end
654                     end
655                 end
656                 if showtech and config.deprioritized_techs then
657                     for _, blacklisted_tech in pairs(config.deprioritized_techs) do
658                         if name == blacklisted_tech then
659                             showtech = false
660                             break
661                         end
662                     end
663                 end
664                 if showtech and ingredients_filter then
665                     for _, ingredient in pairs(tech.research_unit_ingredients) do
666                         if not config.allowed_ingredients[ingredient.name] then
667                             -- filter out techs that require disallowed ingredients (optional)
668                             showtech = false
669                         end
670                     end
671                 end
672                 if showtech then
673                     shown = shown + 1
674                     local entryflow = flow.add{type = "flow", style = "auto_research_tech_flow", direction = "horizontal"}
675                     entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_queue_top-" .. name, sprite = "auto_research_prioritize_top"}
676                     entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_queue_bottom-" .. name, sprite = "auto_research_prioritize_bottom"}
677                     entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_blacklist-" .. name, sprite = "auto_research_deprioritize"}
678                     entryflow.add{type = "label", style = "auto_research_tech_label", name = name, caption = tech.localised_name}
679                     for _, ingredient in pairs(tech.research_unit_ingredients) do
680                         local sprite = "auto_research_tool_" .. ingredient.name
681                         if not helpers.is_valid_sprite_path(sprite) then
682                             sprite = "auto_research_unknown"
683                         end
684                         entryflow.add{type = "sprite", style = "auto_research_sprite", sprite = sprite}
685                     end
686                 end
687             end
688         end
689     end
690 }
691
692 -- event hooks
693 script.on_configuration_changed(function()
694     for _, force in pairs(game.forces) do
695         getConfig(force, true) -- triggers initialization of force config
696     end
697 end)
698 script.on_event(defines.events.on_player_created, function(event)
699     local force = game.players[event.player_index].force
700     local config = getConfig(force) -- triggers initialization of force config
701     -- set any default queued/blacklisted techs
702     local queued_tech = settings.get_player_settings(game.players[event.player_index])["queued-tech-setting"].value
703     for tech in string.gmatch(queued_tech, "[^,$]+") do
704         tech = string.gsub(tech, "%s+", "")
705         if force.technologies[tech] and force.technologies[tech].enabled and not force.technologies[tech].researched then
706             table.insert(config.prioritized_techs, tech)
707         end
708     end
709     local blacklisted_tech = settings.get_player_settings(game.players[event.player_index])["blacklisted-tech-setting"].value
710     for tech in string.gmatch(blacklisted_tech, "[^,$]+") do
711         tech = string.gsub(tech, "%s+", "")
712         if force.technologies[tech] and force.technologies[tech].enabled and not force.technologies[tech].researched then
713             table.insert(config.deprioritized_techs, tech)
714         end
715     end
716     startNextResearch(force, true)
717 end)
718 script.on_event(defines.events.on_force_created, function(event)
719     getConfig(event.force) -- triggers initialization of force config
720 end)
721 script.on_event(defines.events.on_research_finished, onResearchFinished)
722 script.on_event(defines.events.on_gui_checked_state_changed, gui.onCheckboxClick)
723 script.on_event(defines.events.on_gui_click, gui.onClick)
724 script.on_event(defines.events.on_gui_text_changed, function(event)
725     if event.element.name ~= "auto_research_search_text" then
726         return
727     end
728     gui.updateSearchResult(game.players[event.player_index], event.element.text)
729 end)
730
731 -- keybinding hooks
732 script.on_event("auto_research_toggle", function(event)
733     local player = game.players[event.player_index]
734     gui.toggleGui(player)
735 end)
736
737 -- Add remote interfaces for enabling/disabling Auto Research
738 remote.add_interface("auto_research", {
739     enabled = function(forcename, value) setAutoResearch(game.forces[forcename], value) end,
740     queued_only = function(forcename, value) setQueuedOnly(game.forces[forcename], value) end,
741     allow_switching = function(forcename, value) setAllowSwitching(game.forces[forcename], value) end,
742     announce_completed = function(forcename, value) setAnnounceCompletedResearch(game.forces[forcename], value) end,
743     deprioritize_infinite_tech = function(forcename, value) setDeprioritizeInfiniteTech(game.forces[forcename], value) end
744 })