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