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