]> git.somenet.org - factorio/some-autoresearch.git/blob - control.lua
RELEASE 2.0.4 - Global storage should work again.
[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         table.insert(rq, next_research)
228
229         for i=1,6 do
230             if force.research_queue[i] == nil then break end
231             if i == 1 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
240         force.research_queue = rq
241     end
242 end
243
244 function onResearchFinished(event)
245     local force = event.research.force
246     local config = getConfig(force)
247     -- remove researched stuff from prioritized_techs and deprioritized_techs
248     for i = #config.prioritized_techs, 1, -1 do
249         local tech = force.technologies[config.prioritized_techs[i]]
250         if not tech or tech.researched then
251             table.remove(config.prioritized_techs, i)
252         end
253     end
254     for i = #config.deprioritized_techs, 1, -1 do
255         local tech = force.technologies[config.deprioritized_techs[i]]
256         if not tech or tech.researched then
257             table.remove(config.deprioritized_techs, i)
258         end
259     end
260     -- announce completed research
261     if config.announce_completed and config.no_announce_this_tick ~= game.tick then
262         if config.last_research_finish_tick == game.tick then
263             config.no_announce_this_tick = game.tick
264         else
265             local level = ""
266             if event.research.research_unit_count_formula then
267                 level = (event.research.researched and event.research.level) or (event.research.level - 1)
268             end
269             force.print{"auto_research.announce_completed", event.research.localised_name, level}
270         end
271     end
272
273     startNextResearch(event.research.force)
274 end
275
276 -- user interface
277 gui = {
278     toggleGui = function(player)
279         if player.gui.top.auto_research_gui then
280             player.gui.top.auto_research_gui.destroy()
281         else
282             local force = player.force
283             local config = getConfig(force)
284             local frame = player.gui.top.add{
285                 type = "frame",
286                 name = "auto_research_gui",
287                 direction = "vertical",
288                 caption = {"auto_research_gui.title"}
289             }
290             local frameflow = frame.add{
291                 type = "flow",
292                 style = "auto_research_list_flow",
293                 name = "flow",
294                 direction = "vertical"
295             }
296
297             -- checkboxes
298             frameflow.add{type = "checkbox", name = "auto_research_enabled", caption = {"auto_research_gui.enabled"}, tooltip = {"auto_research_gui.enabled_tooltip"}, state = config.enabled or false}
299             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}
300             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}
301             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}
302             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}
303
304             -- research strategy
305             frameflow.add{
306                 type = "label",
307                 style = "auto_research_header_label",
308                 caption = {"auto_research_gui.research_strategy"}
309             }
310             local research_strategies_outer = frameflow.add{
311                 type = "flow",
312                 style = "auto_research_tech_flow",
313                 name = "research_strategies_outer",
314                 direction = "horizontal"
315             }
316             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"}
317             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"}
318             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"}
319             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"}
320             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"}
321             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"}
322
323             research_strategies_outer.style.horizontal_spacing = 6
324
325             -- allowed ingredients
326             frameflow.add{
327                 type = "label",
328                 style = "auto_research_header_label",
329                 caption = {"auto_research_gui.allowed_ingredients_label"}
330             }
331             local allowed_ingredients = frameflow.add{
332                 type = "flow",
333                 style = "auto_research_list_flow",
334                 name = "allowed_ingredients",
335                 direction = "vertical"
336             }
337             gui.updateAllowedIngredientsList(player.gui.top.auto_research_gui.flow.allowed_ingredients, player, config)
338
339             -- prioritized techs
340             frameflow.add{
341                 type = "label",
342                 style = "auto_research_header_label",
343                 caption = {"auto_research_gui.prioritized_label"}
344             }
345             local prioritized = frameflow.add{
346                 type = "scroll-pane",
347                 name = "prioritized",
348                 horizontal_scroll_policy = "never",
349                 vertical_scroll_policy = "auto"
350             }
351             prioritized.style.top_padding = 5
352             prioritized.style.bottom_padding = 5
353             prioritized.style.maximal_height = 127
354             prioritized.style.minimal_width = 440
355             -- draw prioritized tech list
356             gui.updateTechnologyList(player.gui.top.auto_research_gui.flow.prioritized, config.prioritized_techs, player, true)
357
358             -- deprioritized techs
359             frameflow.add{
360                 type = "label",
361                 style = "auto_research_header_label",
362                 caption = {"auto_research_gui.deprioritized_label"}
363             }
364             local deprioritized = frameflow.add{
365                 type = "scroll-pane",
366                 name = "deprioritized",
367                 horizontal_scroll_policy = "never",
368                 vertical_scroll_policy = "auto"
369             }
370             deprioritized.style.top_padding = 5
371             deprioritized.style.bottom_padding = 5
372             deprioritized.style.maximal_height = 127
373             deprioritized.style.minimal_width = 440
374
375             -- draw deprioritized tech list
376             gui.updateTechnologyList(player.gui.top.auto_research_gui.flow.deprioritized, config.deprioritized_techs, player)
377
378             -- search for techs
379             local searchflow = frameflow.add{
380                 type = "flow",
381                 name = "searchflow",
382                 style = "auto_research_tech_flow",
383                 direction = "horizontal"
384             }
385             searchflow.add{
386                 type = "label",
387                 style = "auto_research_header_label",
388                 caption = {"auto_research_gui.search_label"}
389             }
390             searchflow.add{
391                 type = "textfield",
392                 name = "auto_research_search_text",
393                 tooltip = {"auto_research_gui.search_tooltip"}
394             }
395             searchflow.add{
396                 type = "checkbox",
397                 name = "auto_research_ingredients_filter_search_results",
398                 caption = {"auto_research_gui.ingredients_filter_search_results"},
399                 tooltip = {"auto_research_gui.ingredients_filter_search_results_tooltip"},
400                 state = config.filter_search_results or false
401             }
402             searchflow.style.horizontal_spacing = 6
403             searchflow.style.vertical_align = "center"
404
405             local search = frameflow.add{
406                 type = "scroll-pane",
407                 name = "search",
408                 horizontal_scroll_policy = "never",
409                 vertical_scroll_policy = "auto"
410             }
411             search.style.top_padding = 5
412             search.style.bottom_padding = 5
413             search.style.maximal_height = 127
414             search.style.minimal_width = 440
415
416             -- draw search result list
417             gui.updateSearchResult(player, "")
418         end
419     end,
420
421     onCheckboxClick = function(event)
422         local player = game.players[event.player_index]
423         local force = player.force
424         local name = event.element.name
425         if name == "auto_research_enabled" then
426             setAutoResearch(force, event.element.state)
427         elseif name == "auto_research_queued_only" then
428             setQueuedOnly(force, event.element.state)
429         elseif name == "auto_research_allow_switching" then
430             setAllowSwitching(force, event.element.state)
431         elseif name == "auto_research_announce_completed" then
432             setAnnounceCompletedResearch(force, event.element.state)
433         elseif name == "auto_research_deprioritize_infinite_tech" then
434             setDeprioritizeInfiniteTech(force, event.element.state)
435         elseif name == "auto_research_ingredients_filter_search_results" then
436             local config = getConfig(force)
437             config.filter_search_results = event.element.state
438             gui.updateSearchResult(player, player.gui.top.auto_research_gui.flow.searchflow.auto_research_search_text.text)
439         end
440     end,
441
442     onClick = function(event)
443         local player = game.players[event.player_index]
444         local force = player.force
445         local config = getConfig(force)
446         local name = event.element.name
447         if name == "auto_research_search_text" then
448             if event.button == defines.mouse_button_type.right then
449                 player.gui.top.auto_research_gui.flow.searchflow.auto_research_search_text.text = ""
450                 gui.updateSearchResult(player, player.gui.top.auto_research_gui.flow.searchflow.auto_research_search_text.text)
451             end
452         elseif string.find(name, "auto_research_research") then
453             config.research_strategy = string.match(name, "^auto_research_research_(.*)$")
454             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_fast.state = (config.research_strategy == "fast")
455             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_cheap.state = (config.research_strategy == "cheap")
456             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_balanced.state = (config.research_strategy == "balanced")
457             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_slow.state = (config.research_strategy == "slow")
458             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_expensive.state = (config.research_strategy == "expensive")
459             player.gui.top.auto_research_gui.flow.research_strategies_outer.auto_research_research_random.state = (config.research_strategy == "random")
460             -- start new research
461             startNextResearch(force)
462         else
463             local prefix, name = string.match(name, "^auto_research_([^-]*)-(.*)$")
464             if prefix == "allow_ingredient" then
465                 config.allowed_ingredients[name] = not config.allowed_ingredients[name]
466                 gui.updateAllowedIngredientsList(player.gui.top.auto_research_gui.flow.allowed_ingredients, player, config)
467                 if player.gui.top.auto_research_gui.flow.searchflow.auto_research_ingredients_filter_search_results.state then
468                     gui.updateSearchResult(player, player.gui.top.auto_research_gui.flow.searchflow.auto_research_search_text.text)
469                 end
470                 startNextResearch(force)
471             elseif name and force.technologies[name] then
472                 -- remove tech from prioritized list
473                 for i = #config.prioritized_techs, 1, -1 do
474                     if config.prioritized_techs[i] == name then
475                         table.remove(config.prioritized_techs, i)
476                     end
477                 end
478                 -- and from deprioritized list
479                 for i = #config.deprioritized_techs, 1, -1 do
480                     if config.deprioritized_techs[i] == name then
481                         table.remove(config.deprioritized_techs, i)
482                     end
483                 end
484                 if prefix == "queue_top" then
485                     -- add tech to top of prioritized list
486                     table.insert(config.prioritized_techs, 1, name)
487                 elseif prefix == "queue_bottom" then
488                     -- add tech to bottom of prioritized list
489                     table.insert(config.prioritized_techs, name)
490                 elseif prefix == "blacklist" then
491                     -- add tech to list of deprioritized techs
492                     table.insert(config.deprioritized_techs, name)
493                 end
494                 gui.updateTechnologyList(player.gui.top.auto_research_gui.flow.prioritized, config.prioritized_techs, player, true)
495                 gui.updateTechnologyList(player.gui.top.auto_research_gui.flow.deprioritized, config.deprioritized_techs, player)
496                 gui.updateSearchResult(player, player.gui.top.auto_research_gui.flow.searchflow.auto_research_search_text.text)
497
498                 -- start new research
499                 startNextResearch(force)
500             end
501         end
502     end,
503
504     updateAllowedIngredientsList = function(flow, player, config)
505         local counter = 1
506         while flow["flow" .. counter] do
507             flow["flow" .. counter].destroy()
508             counter = counter + 1
509         end
510         counter = 1
511         for ingredientname, allowed in pairs(config.allowed_ingredients) do
512             local flowname = "flow" .. math.floor(counter / 10) + 1
513             local ingredientflow = flow[flowname]
514             if not ingredientflow then
515                 ingredientflow = flow.add {
516                     type = "flow",
517                     style = "auto_research_tech_flow",
518                     name = flowname,
519                     direction = "horizontal"
520                 }
521             end
522             local sprite = "auto_research_tool_" .. ingredientname
523             if not helpers.is_valid_sprite_path(sprite) then
524                 sprite = "auto_research_unknown"
525             end
526             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}
527             counter = counter + 1
528         end
529     end,
530
531     updateTechnologyList = function(scrollpane, technologies, player, show_queue_buttons)
532         if scrollpane.flow then
533             scrollpane.flow.destroy()
534         end
535         local flow = scrollpane.add{
536             type = "flow",
537             style = "auto_research_list_flow",
538             name = "flow",
539             direction = "vertical"
540         }
541         if #technologies > 0 then
542             for _, techname in pairs(technologies) do
543                 local tech = player.force.technologies[techname]
544                 if tech then
545                     local entryflow = flow.add{type = "flow", style = "auto_research_tech_flow", direction = "horizontal"}
546                     if show_queue_buttons then
547                         entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_queue_top-" .. techname, sprite = "auto_research_prioritize_top"}
548                         entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_queue_bottom-" .. techname, sprite = "auto_research_prioritize_bottom"}
549                     end
550                     entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_delete-" .. techname, sprite = "auto_research_delete"}
551                     entryflow.add{type = "label", style = "auto_research_tech_label", caption = tech.localised_name}
552                     for _, ingredient in pairs(tech.research_unit_ingredients) do
553                         local sprite = "auto_research_tool_" .. ingredient.name
554                         if not helpers.is_valid_sprite_path(sprite) then
555                             sprite = "auto_research_unknown"
556                         end
557                         entryflow.add{type = "sprite", style = "auto_research_sprite", sprite = sprite}
558                     end
559                 end
560             end
561         else
562             local entryflow = flow.add{type = "flow", direction = "horizontal"}
563             entryflow.add{type = "label", caption = {"auto_research_gui.none"}}
564         end
565     end,
566
567     updateSearchResult = function(player, text)
568         local scrollpane = player.gui.top.auto_research_gui.flow.search
569         if scrollpane.flow then
570             scrollpane.flow.destroy()
571         end
572         local flow = scrollpane.add{
573             type = "flow",
574             style = "auto_research_list_flow",
575             name = "flow",
576             direction = "vertical"
577         }
578         local ingredients_filter = player.gui.top.auto_research_gui.flow.searchflow.auto_research_ingredients_filter_search_results.state
579         local config = getConfig(player.force)
580         local shown = 0
581         text = string.lower(text)
582         -- NOTICE: localised name matching does not work at present, pending unlikely changes to Factorio API
583         for name, tech in pairs(player.force.technologies) do
584             if not tech.researched and tech.enabled then
585                 local showtech = false
586                 if string.find(string.lower(name), text, 1, true) then
587                     -- show techs that match by name
588                     showtech = true
589                 -- elseif string.find(string.lower(game.technology_prototypes[name].localised_name), text, 1, true) then
590                 --     -- show techs that match by localised name
591                 --     showtech = true
592                 else
593                     for _, effect in pairs(tech.prototype.effects) do
594                         if string.find(effect.type, text, 1, true) then
595                             -- show techs that match by effect type
596                             showtech = true
597                         elseif effect.type == "unlock-recipe" then
598                             if string.find(effect.recipe, text, 1, true) then
599                                 -- show techs that match by unlocked recipe name
600                                 showtech = true
601                             -- elseif string.find(string.lower(game.recipe_prototypes[effect.recipe].localised_name), text, 1, true) then
602                             --     -- show techs that match by unlocked recipe localised name
603                             --     showtech = true
604                             else
605                                 for _, product in pairs(prototypes.recipe[effect.recipe].products) do
606                                     if string.find(product.name, text, 1, true) then
607                                         -- show techs that match by unlocked recipe product name
608                                         showtech = true
609                                     -- elseif string.find(string.lower(game.item_prototypes[product.name].localised_name), text, 1, true) then
610                                     --     -- show techs that match by unlocked recipe product localised name
611                                     --     showtech = true
612                                     else
613                                         local prototype = prototypes.item[product.name]
614                                         if prototype then
615                                             if prototype.place_result then
616                                                 if string.find(prototype.place_result.name, text, 1, true) then
617                                                     -- show techs that match by unlocked recipe product placed entity name
618                                                     showtech = true
619                                                 -- elseif string.find(string.lower(game.entity_prototypes[prototype.place_result.name].localised_name), text, 1, true) then
620                                                 --     -- show techs that match by unlocked recipe product placed entity localised name
621                                                 --     showtech = true
622                                                 end
623                                             elseif prototype.place_as_equipment_result then
624                                                 if string.find(prototype.place_as_equipment_result.name, text, 1, true) then
625                                                     -- show techs that match by unlocked recipe product placed equipment name
626                                                     showtech = true
627                                                 -- elseif string.find(string.lower(game.equipment_prototypes[prototype.place_as_equipment_result.name].localised_name), text, 1, true) then
628                                                 --     -- show techs that match by unlocked recipe product placed equipment localised name
629                                                 --     showtech = true
630                                                 end
631                                             elseif prototype.place_as_tile_result then
632                                                 if string.find(prototype.place_as_tile_result.result.name, text, 1, true) then
633                                                     -- show techs that match by unlocked recipe product placed tile name
634                                                     showtech = true
635                                                 -- elseif string.find(string.lower(prototype.place_as_tile_result.result.localised_name), text, 1, true) then
636                                                 --     -- show techs that match by unlocked recipe product placed tile localised name
637                                                 --     showtech = true
638                                                 end
639                                             end
640                                         end
641                                     end
642                                 end
643                             end
644                         end
645                     end
646                 end
647                 if showtech and config.prioritized_techs then
648                     for _, queued_tech in pairs(config.prioritized_techs) do
649                         if name == queued_tech then
650                             showtech = false
651                             break
652                         end
653                     end
654                 end
655                 if showtech and config.deprioritized_techs then
656                     for _, blacklisted_tech in pairs(config.deprioritized_techs) do
657                         if name == blacklisted_tech then
658                             showtech = false
659                             break
660                         end
661                     end
662                 end
663                 if showtech and ingredients_filter then
664                     for _, ingredient in pairs(tech.research_unit_ingredients) do
665                         if not config.allowed_ingredients[ingredient.name] then
666                             -- filter out techs that require disallowed ingredients (optional)
667                             showtech = false
668                         end
669                     end
670                 end
671                 if showtech then
672                     shown = shown + 1
673                     local entryflow = flow.add{type = "flow", style = "auto_research_tech_flow", direction = "horizontal"}
674                     entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_queue_top-" .. name, sprite = "auto_research_prioritize_top"}
675                     entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_queue_bottom-" .. name, sprite = "auto_research_prioritize_bottom"}
676                     entryflow.add{type = "sprite-button", style = "auto_research_sprite_button", name = "auto_research_blacklist-" .. name, sprite = "auto_research_deprioritize"}
677                     entryflow.add{type = "label", style = "auto_research_tech_label", name = name, caption = tech.localised_name}
678                     for _, ingredient in pairs(tech.research_unit_ingredients) do
679                         local sprite = "auto_research_tool_" .. ingredient.name
680                         if not helpers.is_valid_sprite_path(sprite) then
681                             sprite = "auto_research_unknown"
682                         end
683                         entryflow.add{type = "sprite", style = "auto_research_sprite", sprite = sprite}
684                     end
685                 end
686             end
687         end
688     end
689 }
690
691 -- event hooks
692 script.on_configuration_changed(function()
693     for _, force in pairs(game.forces) do
694         getConfig(force, true) -- triggers initialization of force config
695     end
696 end)
697 script.on_event(defines.events.on_player_created, function(event)
698     local force = game.players[event.player_index].force
699     local config = getConfig(force) -- triggers initialization of force config
700     -- set any default queued/blacklisted techs
701     local queued_tech = settings.get_player_settings(game.players[event.player_index])["queued-tech-setting"].value
702     for tech in string.gmatch(queued_tech, "[^,$]+") do
703         tech = string.gsub(tech, "%s+", "")
704         if force.technologies[tech] and force.technologies[tech].enabled and not force.technologies[tech].researched then
705             table.insert(config.prioritized_techs, tech)
706         end
707     end
708     local blacklisted_tech = settings.get_player_settings(game.players[event.player_index])["blacklisted-tech-setting"].value
709     for tech in string.gmatch(blacklisted_tech, "[^,$]+") do
710         tech = string.gsub(tech, "%s+", "")
711         if force.technologies[tech] and force.technologies[tech].enabled and not force.technologies[tech].researched then
712             table.insert(config.deprioritized_techs, tech)
713         end
714     end
715     startNextResearch(force, true)
716 end)
717 script.on_event(defines.events.on_force_created, function(event)
718     getConfig(event.force) -- triggers initialization of force config
719 end)
720 script.on_event(defines.events.on_research_finished, onResearchFinished)
721 script.on_event(defines.events.on_gui_checked_state_changed, gui.onCheckboxClick)
722 script.on_event(defines.events.on_gui_click, gui.onClick)
723 script.on_event(defines.events.on_gui_text_changed, function(event)
724     if event.element.name ~= "auto_research_search_text" then
725         return
726     end
727     gui.updateSearchResult(game.players[event.player_index], event.element.text)
728 end)
729
730 -- keybinding hooks
731 script.on_event("auto_research_toggle", function(event)
732     local player = game.players[event.player_index]
733     gui.toggleGui(player)
734 end)
735
736 -- Add remote interfaces for enabling/disabling Auto Research
737 remote.add_interface("auto_research", {
738     enabled = function(forcename, value) setAutoResearch(game.forces[forcename], value) end,
739     queued_only = function(forcename, value) setQueuedOnly(game.forces[forcename], value) end,
740     allow_switching = function(forcename, value) setAllowSwitching(game.forces[forcename], value) end,
741     announce_completed = function(forcename, value) setAnnounceCompletedResearch(game.forces[forcename], value) end,
742     deprioritize_infinite_tech = function(forcename, value) setDeprioritizeInfiniteTech(game.forces[forcename], value) end
743 })