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