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