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