z_mirror/RecipeBook
git clone https://git.raiguard.me/z_mirror/RecipeBook
3fc4959
trunk
1local flib_math =require ( "__flib__.math" ) 2local flib_table =require ( "__flib__.table" ) 3 4local util =require ( "scripts.util" ) 5 6--- @alias DatabaseIDType 7--- | "entity" 8--- | "equipment" 9--- | "fluid" 10--- | "item" 11--- | "recipe" 12--- | "technology" 13--- | "tile" 14 15--- @class DatabaseID 16--- @field type DatabaseIDType 17--- @field name string 18--- @field amount number? 19--- @field amount_min number? 20--- @field amount_max number? 21--- @field time number? 22--- @field probability number? 23--- @field temperature number? 24--- @field minimum_temperature number? 25--- @field maximum_temperature number? 26--- @field extra_count_fraction number? 27 28--- @class Collectors 29local collectors ={} 30 31--- @param prototype LuaRecipePrototype 32--- @return DatabaseID[] 33function collectors . ingredients ( prototype ) 34local output ={} 35 36for _ , ingredient in pairs ( prototype . ingredients ) do 37output [ # output + 1 ] ={ 38type =ingredient . type , 39name =ingredient . name , 40amount =ingredient . amount , 41minimum_temperature =ingredient . minimum_temperature , 42maximum_temperature =ingredient . maximum_temperature , 43} 44end 45 46return output 47end 48 49--- @param prototype LuaRecipePrototype 50--- @return DatabaseID[] 51function collectors . products ( prototype ) 52local output ={} 53 54for _ , product in pairs ( prototype . products ) do 55if product . type ~= "research-progress" then 56output [ # output + 1 ] ={ 57type =product . type , 58name =product . name , 59amount =product . amount , 60amount_min =product . amount_min , 61amount_max =product . amount_max , 62temperature =product . temperature , 63probability =product . probability , 64extra_count_fraction =product . extra_count_fraction , 65} 66end 67end 68 69return output 70end 71 72--- @param prototype LuaRecipePrototype 73--- @return DatabaseID[] 74function collectors . made_in ( prototype ) 75local output =util . unique_id_array () 76 77--- @diagnostic disable-next-line unused-fields 78for _ , character in pairs ( prototypes . get_entity_filtered ( { { filter ="type" , type ="character" } } )) do 79if character . crafting_categories [ prototype . category ] then 80output [ # output + 1 ] ={ 81type ="entity" , 82name =character . name , 83time =prototype . energy , 84} 85end 86end 87 88local item_ingredients =flib_table . reduce ( prototype . ingredients , function ( accumulator , ingredient ) 89return accumulator + ( ingredient . type == "item" and 1 or 0 ) 90end , 0 ) --[[@as integer]] 91 92for _ , crafter in 93pairs ( prototypes . get_entity_filtered ( { 94--- @diagnostic disable-next-line unused-fields 95{ filter ="crafting-category" , crafting_category =prototype . category } , 96} )) 97do 98local ingredient_count =crafter . ingredient_count 99if ingredient_count == 0 or ingredient_count >= item_ingredients then 100output [ # output + 1 ] ={ 101type ="entity" , 102name =crafter . name , 103time =prototype . energy / crafter . get_crafting_speed (), -- TODO: Quality 104} 105end 106end 107 108return output 109end 110 111--- @param prototype LuaFluidPrototype|LuaItemPrototype 112--- @return DatabaseID[] 113function collectors . gathered_from ( prototype ) 114local output =util . unique_id_array () 115 116if prototype . object_name == "LuaFluidPrototype" then 117for tile_name , tile in pairs ( prototypes . tile ) do 118if tile . fluid == prototype then 119output [ # output + 1 ] ={ type ="tile" , name =tile_name } 120end 121end 122end 123 124local type =util . object_name_to_type [ prototype . object_name ] 125 126for entity_name , entity in pairs ( util . get_natural_entities ()) do 127if prototype . name ~= entity_name then 128local mineable_properties =entity . mineable_properties 129if mineable_properties . minable then 130for _ , product in pairs ( mineable_properties . products or {} ) do 131if product . type == type and product . name == prototype . name then 132output [ # output + 1 ] ={ type ="entity" , name =entity_name } 133end 134end 135end 136end 137end 138 139return output 140end 141 142--- @type table<string, string[]> 143local spoilage_from_cache ={} 144for item_name , item in pairs ( prototypes . item ) do 145local spoil_result =item . spoil_result 146if spoil_result then 147table . insert ( 148flib_table . get_or_insert ( spoilage_from_cache , spoil_result . name , util . unique_id_array ()), 149{ type ="item" , name =item_name } 150) 151end 152end 153 154--- @param prototype LuaItemPrototype 155--- @return DatabaseID[] 156function collectors . spoilage_from ( prototype ) 157return spoilage_from_cache [ prototype . name ] or {} 158end 159 160--- @param prototype LuaItemPrototype 161--- @return DatabaseID[] 162function collectors . rocket_launch_products ( prototype ) 163local output =util . unique_id_array () 164 165for _ , product in pairs ( prototype . rocket_launch_products ) do 166if product . type ~= "research-progress" then 167output [ # output + 1 ] ={ 168type =product . type , 169name =product . name , 170amount =product . amount , 171amount_min =product . amount_min , 172amount_max =product . amount_max , 173temperature =product . temperature , 174probability =product . probability , 175} 176end 177end 178 179return output 180end 181 182--- @param prototype LuaItemPrototype 183--- @return DatabaseID[] 184function collectors . rocket_launch_product_of ( prototype ) 185local output =util . unique_id_array () 186 187--- @diagnostic disable-next-line unused-fields 188for _ , other_item in pairs ( prototypes . get_item_filtered ( { { filter ="has-rocket-launch-products" } } )) do 189for _ , product in pairs ( other_item . rocket_launch_products ) do 190if product . name == prototype . name then 191output [ # output + 1 ] ={ type ="item" , name =other_item . name } 192break 193end 194end 195end 196 197return output 198end 199 200--- @param prototype LuaFluidPrototype 201--- @return DatabaseID[] 202function collectors . generated_by ( prototype ) 203local output =util . unique_id_array () 204 205for _ , boiler_prototype in pairs ( prototypes . get_entity_filtered ( { { filter ="type" , type ="boiler" } } )) do 206local generates =collectors . generated_fluid ( boiler_prototype ) 207if generates and generates . name == prototype . name then 208output [ # output + 1 ] ={ 209type ="entity" , 210name =boiler_prototype . name , 211temperature =boiler_prototype . target_temperature , 212} 213end 214end 215 216return output 217end 218 219--- @param prototype LuaFluidPrototype 220--- @return DatabaseID[] 221function collectors . mining_fluid_for ( prototype ) 222local output =util . unique_id_array () 223 224for resource_name , resource in pairs ( prototypes . get_entity_filtered ( { { filter ="type" , type ="resource" } } )) do 225local mineable =resource . mineable_properties 226if mineable . minable and mineable . required_fluid == prototype . name then 227output [ # output + 1 ] ={ type ="entity" , name =resource_name } 228end 229end 230 231return output 232end 233 234--- @param prototype LuaEntityPrototype 235--- @return DatabaseID? 236function collectors . generated_fluid ( prototype ) 237if prototype . type ~= "boiler" then 238return 239end 240 241for _ , fluidbox in pairs ( prototype . fluidbox_prototypes ) do 242if fluidbox . production_type == "output" and fluidbox . filter then 243return { type ="fluid" , name =fluidbox . filter . name , temperature =prototype . target_temperature } 244end 245end 246end 247 248--- @param prototype LuaEntityPrototype 249--- @param grouped_item LuaItemPrototype? 250--- @return DatabaseID[] 251function collectors . place_result_of ( prototype , grouped_item ) 252local output =util . unique_id_array () 253 254for _ , item_definition in pairs ( prototype . items_to_place_this or {} ) do 255if not grouped_item or grouped_item . name ~= item_definition . name then 256output [ # output + 1 ] ={ type ="item" , name =item_definition . name , amount =item_definition . count } 257end 258end 259 260return output 261end 262 263--- @param prototype LuaEntityPrototype 264--- @return DatabaseID[] 265function collectors . mined_by ( prototype ) 266local output =util . unique_id_array () 267 268local required_fluid =prototype . mineable_properties . required_fluid 269local resource_category =prototype . resource_category 270for _ , drill in pairs ( prototypes . get_entity_filtered ( { { filter ="type" , type ="mining-drill" } } )) do 271if drill . resource_categories [ resource_category ] and ( not required_fluid or drill . fluidbox_prototypes [ 1 ]) then 272output [ # output + 1 ] ={ type ="entity" , name =drill . name } 273end 274end 275 276return output 277end 278 279--- @param prototype LuaFluidPrototype|LuaItemPrototype 280--- @param grouped_recipe LuaRecipePrototype? 281--- @return DatabaseID[] 282function collectors . alternative_recipes ( prototype , grouped_recipe ) 283local output =util . unique_id_array () 284if prototype . object_name == "LuaFluidPrototype" then 285for _ , recipe in 286pairs ( prototypes . get_recipe_filtered ( { 287--- @diagnostic disable-next-line unused-fields 288{ filter ="has-product-fluid" , elem_filters ={ { filter ="name" , name =prototype . name } } } , 289} )) 290do 291if recipe ~= grouped_recipe then 292local id ={ type ="recipe" , name =recipe . name } 293for _ , product in pairs ( recipe . products ) do 294if product . name == prototype . name and product . temperature then 295id . temperature =product . temperature 296break 297end 298end 299output [ # output + 1 ] =id 300end 301end 302else 303for _ , recipe in 304pairs ( prototypes . get_recipe_filtered ( { 305--- @diagnostic disable-next-line unused-fields 306{ filter ="has-product-item" , elem_filters ={ { filter ="name" , name =prototype . name } } } , 307} )) 308do 309if recipe ~= grouped_recipe then 310output [ # output + 1 ] ={ type ="recipe" , name =recipe . name } 311end 312end 313end 314 315return output 316end 317 318--- @param prototype LuaFluidPrototype|LuaItemPrototype 319--- @param grouped_recipe LuaRecipePrototype? 320--- @return DatabaseID[] 321function collectors . used_in ( prototype , grouped_recipe ) 322local output =util . unique_id_array () 323 324if prototype . object_name == "LuaFluidPrototype" then 325for _ , recipe in 326pairs ( prototypes . get_recipe_filtered ( { 327--- @diagnostic disable-next-line unused-fields 328{ filter ="has-ingredient-fluid" , elem_filters ={ { filter ="name" , name =prototype . name } } } , 329} )) 330do 331if recipe ~= grouped_recipe then 332local id ={ type ="recipe" , name =recipe . name } 333for _ , ingredient in pairs ( recipe . ingredients ) do 334-- minimum_temperature and maximum_temperature are mutually inclusive. 335if ingredient . name == prototype . name and ingredient . minimum_temperature then 336id . minimum_temperature =ingredient . minimum_temperature 337id . maximum_temperature =ingredient . maximum_temperature 338break 339end 340end 341output [ # output + 1 ] =id 342end 343end 344else 345for _ , recipe in 346pairs ( prototypes . get_recipe_filtered ( { 347--- @diagnostic disable-next-line unused-fields 348{ filter ="has-ingredient-item" , elem_filters ={ { filter ="name" , name =prototype . name } } } , 349} )) 350do 351if recipe ~= grouped_recipe then 352output [ # output + 1 ] ={ type ="recipe" , name =recipe . name } 353end 354end 355end 356 357return output 358end 359 360--- @param prototype LuaFluidPrototype|LuaItemPrototype 361--- @return DatabaseID[] 362function collectors . burned_in ( prototype ) 363local output =util . unique_id_array () 364 365if prototype . object_name == "LuaFluidPrototype" then 366--- @diagnostic disable-next-line unused-fields 367for entity_name , entity in pairs ( prototypes . get_entity_filtered ( { { filter ="type" , type ="generator" } } )) do 368local fluid_box =entity . fluidbox_prototypes [ 1 ] 369if 370( fluid_box . filter and fluid_box . filter . name == prototype . name ) 371or ( not fluid_box . filter and prototype . fuel_value > 0 ) 372then 373output [ # output + 1 ] ={ type ="entity" , name =entity_name } 374end 375end 376--- @diagnostic disable-next-line unused-fields 377for entity_name , entity in pairs ( prototypes . get_entity_filtered ( { { filter ="type" , type ="boiler" } } )) do 378for _ , fluidbox in pairs ( entity . fluidbox_prototypes ) do 379if 380( fluidbox . production_type == "input" or fluidbox . production_type == "input-output" ) 381and fluidbox . filter 382and fluidbox . filter . name == prototype . name 383then 384output [ # output + 1 ] ={ type ="entity" , name =entity_name } 385end 386end 387end 388--- @diagnostic disable-next-line unused-fields 389for entity_name , entity in pairs ( prototypes . get_entity_filtered ( { { filter ="building" } } )) do 390local fluid_energy_source_prototype =entity . fluid_energy_source_prototype 391if fluid_energy_source_prototype then 392local filter =fluid_energy_source_prototype . fluid_box . filter 393if 394( not filter and ( not fluid_energy_source_prototype . burns_fluid or prototype . fuel_value > 0 )) 395or filter == prototype 396then 397output [ # output + 1 ] ={ type ="entity" , name =entity_name } 398end 399end 400end 401else 402if prototype . fuel_value == 0 then 403return output 404end 405local fuel_category =prototype . fuel_category 406for entity_name , entity_prototype in pairs ( prototypes . entity ) do 407local burner =entity_prototype . burner_prototype 408if burner and burner . fuel_categories [ fuel_category ] then 409output [ # output + 1 ] ={ type ="entity" , name =entity_name } 410end 411end 412for equipment_name , equipment_prototype in pairs ( prototypes . equipment ) do 413local burner =equipment_prototype . burner_prototype 414if burner and burner . fuel_categories [ fuel_category ] then 415output [ # output + 1 ] ={ type ="equipment" , name =equipment_name } 416end 417end 418end 419 420return output 421end 422 423--- @param prototype LuaEntityPrototype 424--- @return DatabaseID[] 425function collectors . can_mine ( prototype ) 426local output =util . unique_id_array () 427 428--- @type string|boolean? 429local filter 430for _ , fluidbox_prototype in pairs ( prototype . fluidbox_prototypes ) do 431local production_type =fluidbox_prototype . production_type 432if production_type ~= "output" then 433filter =fluidbox_prototype . filter and fluidbox_prototype . filter . name or true 434break 435end 436end 437local resource_categories =prototype . resource_categories or {} 438for _ , resource in pairs ( prototypes . get_entity_filtered ( { { filter ="type" , type ="resource" } } )) do 439local mineable =resource . mineable_properties 440local required_fluid =mineable . required_fluid 441if 442resource_categories [ resource . resource_category ] 443and ( not required_fluid or filter == true or filter == required_fluid ) 444then 445output [ # output + 1 ] ={ type ="entity" , name =resource . name } 446end 447end 448 449return output 450end 451 452--- @param prototype LuaEntityPrototype 453--- @return DatabaseID[] 454function collectors . can_burn ( prototype ) 455local output =util . unique_id_array () 456 457local burner =prototype . burner_prototype 458if burner then 459for category in pairs ( burner . fuel_categories ) do 460for item_name in 461--- @diagnostic disable-next-line unused-fields 462pairs ( prototypes . get_item_filtered ( { { filter ="fuel-category" , [ "fuel-category" ] =category } } )) 463do 464output [ # output + 1 ] ={ type ="item" , name =item_name } 465end 466end 467end 468local fluid_energy_source_prototype =prototype . fluid_energy_source_prototype 469if fluid_energy_source_prototype then 470local filter =fluid_energy_source_prototype . fluid_box . filter 471if filter then 472output [ # output + 1 ] ={ type ="fluid" , name =filter . name } 473else 474for fluid_name in 475--- @diagnostic disable-next-line unused-fields 476pairs ( prototypes . get_fluid_filtered ( { { filter ="fuel-value" , comparison =">" , value =0 } } )) 477do 478output [ # output + 1 ] ={ type ="fluid" , name =fluid_name } 479end 480end 481end 482if prototype . type == "generator" then 483local fluid_box =prototype . fluidbox_prototypes [ 1 ] 484if fluid_box . filter then 485output [ # output + 1 ] ={ type ="fluid" , name =fluid_box . filter . name } 486else 487for fluid_name in 488--- @diagnostic disable-next-line unused-fields 489pairs ( prototypes . get_fluid_filtered ( { { filter ="fuel-value" , comparison =">" , value =0 } } )) 490do 491output [ # output + 1 ] ={ type ="fluid" , name =fluid_name } 492end 493end 494end 495 496return output 497end 498 499local yields ={ 500[ "fish" ] =true , 501[ "resource" ] =true , 502[ "simple-entity" ] =true , 503[ "tree" ] =true , 504} 505 506--- @param prototype LuaEntityPrototype 507--- @param grouped_item LuaItemPrototype? 508--- @return DatabaseID[] 509function collectors . yields ( prototype , grouped_item ) 510local output =util . unique_id_array () 511 512if not yields [ prototype . type ] then 513return output 514end 515 516local mineable_properties =prototype . mineable_properties 517if not mineable_properties or not mineable_properties . minable then 518return output 519end 520 521local products =mineable_properties . products 522if not products then 523return output 524end 525 526if not ( # products == 1 and grouped_item and products [ 1 ]. type == "item" and products [ 1 ]. name == grouped_item . name ) then 527local products =mineable_properties . products 528if products then 529for _ , product in pairs ( products ) do 530if product . type ~= "research-progress" then 531output [ # output + 1 ] ={ 532type =product . type , 533name =product . name , 534amount =product . amount , 535amount_min =product . amount_min , 536amount_max =product . amount_max , 537temperature =product . temperature , 538probability =product . probability , 539} 540end 541end 542end 543end 544 545return output 546end 547 548--- @param prototype LuaRecipePrototype 549--- @return DatabaseID[] 550function collectors . unlocked_by ( prototype ) 551local output =util . unique_id_array () 552 553if not prototype . unlock_results or prototype . enabled then 554return output 555end 556 557for technology_name in 558--- @diagnostic disable-next-line unused-fields 559pairs ( prototypes . get_technology_filtered ( { { filter ="unlocks-recipe" , recipe =prototype . name } } )) 560do 561output [ # output + 1 ] ={ type ="technology" , name =technology_name } 562end 563 564return output 565end 566 567local crafting_entities ={ 568[ "assembling-machine" ] =true , 569[ "furnace" ] =true , 570[ "rocket-silo" ] =true , 571[ "character" ] =true , 572} 573 574--- @param prototype LuaEntityPrototype 575--- @return DatabaseID[] 576function collectors . can_craft ( prototype ) 577local output =util . unique_id_array () 578 579if not crafting_entities [ prototype . type ] then 580return {} 581end 582 583--- @type RecipePrototypeFilter[] 584local filters ={} 585for category in pairs ( prototype . crafting_categories ) do 586filters [ # filters + 1 ] ={ filter ="category" , category =category } 587end 588for _ , recipe in pairs ( prototypes . get_recipe_filtered ( filters )) do 589if not recipe . parameter then 590local item_ingredients =0 591for _ , ingredient in pairs ( recipe . ingredients ) do 592if ingredient . type == "item" then 593item_ingredients =item_ingredients + 1 594end 595end 596local ingredient_count =prototype . ingredient_count 597if not ingredient_count or ingredient_count >= item_ingredients then 598output [ # output + 1 ] ={ type ="recipe" , name =recipe . name } 599end 600end 601end 602 603return output 604end 605 606--- @param prototype LuaEntityPrototype 607--- @return DatabaseID[] 608function collectors . can_extract_from ( prototype ) 609local output =util . unique_id_array () 610 611if prototype . type ~= "offshore-pump" then 612return output 613end 614 615for _ , tile_prototype in pairs ( prototypes . tile ) do 616if tile_prototype . fluid then 617output [ # output + 1 ] ={ type ="tile" , name =tile_prototype . name } 618end 619end 620 621return output 622end 623 624--- @param prototype LuaTilePrototype 625--- @return DatabaseID[] 626function collectors . source_of ( prototype ) 627local output =util . unique_id_array () 628local fluid =prototype . fluid 629if fluid then 630output [ # output + 1 ] ={ type ="fluid" , name =fluid . name } 631end 632return output 633end 634 635--- @param prototype LuaTilePrototype 636--- @return DatabaseID[] 637function collectors . extracted_by ( prototype ) 638local output =util . unique_id_array () 639 640if not prototype . fluid then 641return output 642end 643 644for _ , offshore_pump in pairs ( prototypes . get_entity_filtered ( { { filter ="type" , type ="offshore-pump" } } )) do 645output [ # output + 1 ] ={ type ="entity" , name =offshore_pump . name } 646end 647 648return output 649end 650 651--- @param prototype LuaEntityPrototype 652--- @return DatabaseID? 653function collectors . material_consumption ( prototype ) 654if prototype . type == "boiler" and prototype . boiler_mode == "output-to-separate-pipe" then 655local input_fluid_box =prototype . fluidbox_prototypes [ 1 ] 656local input_filter =input_fluid_box . filter 657if input_filter then 658local minimum_temperature =input_fluid_box . minimum_temperature or input_filter . default_temperature 659local flow_per_tick =( prototype . target_temperature - minimum_temperature ) * input_filter . heat_capacity 660return { type ="fluid" , name =input_filter . name , amount =prototype . get_max_energy_usage () / flow_per_tick * 60 } -- TODO: Quality 661end 662elseif prototype . type == "generator" then 663local fluid_box =prototype . fluidbox_prototypes [ 1 ] 664local filter =fluid_box . filter 665if filter then 666return { 667type ="fluid" , 668name =filter . name , 669amount =prototype . fluid_usage_per_tick * 60 , 670maximum_temperature =prototype . maximum_temperature , 671} 672end 673end 674end 675 676--- @param prototype LuaEntityPrototype 677--- @return DatabaseID? 678function collectors . material_production ( prototype ) 679if prototype . type == "boiler" and prototype . boiler_mode == "output-to-separate-pipe" then 680local input_fluid_box =prototype . fluidbox_prototypes [ 1 ] 681local input_filter =input_fluid_box . filter 682if input_filter then 683local output_fluid_box =prototype . fluidbox_prototypes [ 2 ] 684local output_filter =output_fluid_box . filter 685if output_filter then 686local minimum_temperature =input_fluid_box . minimum_temperature or input_filter . default_temperature 687local flow_per_tick =( prototype . target_temperature - minimum_temperature ) * input_filter . heat_capacity 688return { 689type ="fluid" , 690name =output_filter . name , 691amount =prototype . get_max_energy_usage () / flow_per_tick * 60 , -- TODO: Quality 692temperature =prototype . target_temperature , 693} 694end 695end 696end 697end 698 699--- @param prototype LuaEntityPrototype 700--- @return {min: double, max: double}? 701function collectors . power_consumption ( prototype ) 702local electric_energy_source_prototype =prototype . electric_energy_source_prototype --[[@as LuaElectricEnergySourcePrototype]] 703if not electric_energy_source_prototype then 704return 705end 706 707-- local added_emissions = 0 708local max_energy_usage =prototype . get_max_energy_usage () or 0 -- TODO: Quality 709if max_energy_usage > 0 and max_energy_usage < flib_math . max_int53 then 710local drain =electric_energy_source_prototype . drain 711if max_energy_usage ~= drain then 712max_energy_usage =max_energy_usage + drain 713end 714return { min =drain * 60 , max =max_energy_usage * 60 } 715end 716end 717 718--- @param prototype LuaEntityPrototype 719--- @return double? 720function collectors . power_production ( prototype ) 721local output =prototype . get_max_power_output () -- TODO: Quality 722if not output then 723return 724end 725 726return output * 60 727end 728 729return collectors