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