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