mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Factorio: support 2.0 update (#4110)
- removed tutorialization (Craft/Do X to unlock tech) - start with everything needed for power, electric mining drills, science lab and automation science already unlocked - updated world gen - updated mod api use - updated fluid boxes (CaitSith2) - new option: free sample quality (needs quality mod) - removed old gruft, faster gen speed, faster load time - lists space age as explicitly not supported, so it prevents the game from trying to load both - fixes Y offset of traps being wrong (way higher than intended) - client now has a 5 second timeout to communicate with the bound factorio server, so it aborts actions if the server died - savegames are now stored in write_data_directory -> saves -> Archipelago - add cargo-landing-pad handling - starting rocket silo and cargo landing pad respect free sample quality - supports Factorio 2.0 --------- Co-authored-by: CaitSith2 <d_good@caitsith2.com>
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import orjson
|
||||
import logging
|
||||
import os
|
||||
import string
|
||||
import functools
|
||||
import pkgutil
|
||||
import string
|
||||
from collections import Counter
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Dict, Set, FrozenSet, Tuple, Union, List, Any
|
||||
from typing import Dict, Set, FrozenSet, Tuple, Union, List, Any, Optional
|
||||
|
||||
import orjson
|
||||
|
||||
import Utils
|
||||
from . import Options
|
||||
@@ -32,8 +32,23 @@ items_future = pool.submit(load_json_data, "items")
|
||||
tech_table: Dict[str, int] = {}
|
||||
technology_table: Dict[str, Technology] = {}
|
||||
|
||||
start_unlocked_recipes = {
|
||||
"offshore-pump",
|
||||
"boiler",
|
||||
"steam-engine",
|
||||
"automation-science-pack",
|
||||
"inserter",
|
||||
"small-electric-pole",
|
||||
"copper-cable",
|
||||
"lab",
|
||||
"electronic-circuit",
|
||||
"electric-mining-drill",
|
||||
"pipe",
|
||||
"pipe-to-ground",
|
||||
}
|
||||
|
||||
def always(state):
|
||||
|
||||
def always(state) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@@ -50,15 +65,13 @@ class FactorioElement:
|
||||
class Technology(FactorioElement): # maybe make subclass of Location?
|
||||
has_modifier: bool
|
||||
factorio_id: int
|
||||
ingredients: Set[str]
|
||||
progressive: Tuple[str]
|
||||
unlocks: Union[Set[str], bool] # bool case is for progressive technologies
|
||||
|
||||
def __init__(self, name: str, ingredients: Set[str], factorio_id: int, progressive: Tuple[str] = (),
|
||||
def __init__(self, technology_name: str, factorio_id: int, progressive: Tuple[str] = (),
|
||||
has_modifier: bool = False, unlocks: Union[Set[str], bool] = None):
|
||||
self.name = name
|
||||
self.name = technology_name
|
||||
self.factorio_id = factorio_id
|
||||
self.ingredients = ingredients
|
||||
self.progressive = progressive
|
||||
self.has_modifier = has_modifier
|
||||
if unlocks:
|
||||
@@ -66,19 +79,6 @@ class Technology(FactorioElement): # maybe make subclass of Location?
|
||||
else:
|
||||
self.unlocks = set()
|
||||
|
||||
def build_rule(self, player: int):
|
||||
logging.debug(f"Building rules for {self.name}")
|
||||
|
||||
return lambda state: all(state.has(f"Automated {ingredient}", player)
|
||||
for ingredient in self.ingredients)
|
||||
|
||||
def get_prior_technologies(self) -> Set[Technology]:
|
||||
"""Get Technologies that have to precede this one to resolve tree connections."""
|
||||
technologies = set()
|
||||
for ingredient in self.ingredients:
|
||||
technologies |= required_technologies[ingredient] # technologies that unlock the recipes
|
||||
return technologies
|
||||
|
||||
def __hash__(self):
|
||||
return self.factorio_id
|
||||
|
||||
@@ -91,22 +91,22 @@ class Technology(FactorioElement): # maybe make subclass of Location?
|
||||
|
||||
class CustomTechnology(Technology):
|
||||
"""A particularly configured Technology for a world."""
|
||||
ingredients: Set[str]
|
||||
|
||||
def __init__(self, origin: Technology, world, allowed_packs: Set[str], player: int):
|
||||
ingredients = origin.ingredients & allowed_packs
|
||||
military_allowed = "military-science-pack" in allowed_packs \
|
||||
and ((ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"})
|
||||
or origin.name == "rocket-silo")
|
||||
ingredients = allowed_packs
|
||||
self.player = player
|
||||
if origin.name not in world.special_nodes:
|
||||
if military_allowed:
|
||||
ingredients.add("military-science-pack")
|
||||
ingredients = list(ingredients)
|
||||
ingredients.sort() # deterministic sample
|
||||
ingredients = world.random.sample(ingredients, world.random.randint(1, len(ingredients)))
|
||||
elif origin.name == "rocket-silo" and military_allowed:
|
||||
ingredients.add("military-science-pack")
|
||||
super(CustomTechnology, self).__init__(origin.name, ingredients, origin.factorio_id)
|
||||
ingredients = set(world.random.sample(list(ingredients), world.random.randint(1, len(ingredients))))
|
||||
self.ingredients = ingredients
|
||||
super(CustomTechnology, self).__init__(origin.name, origin.factorio_id)
|
||||
|
||||
def get_prior_technologies(self) -> Set[Technology]:
|
||||
"""Get Technologies that have to precede this one to resolve tree connections."""
|
||||
technologies = set()
|
||||
for ingredient in self.ingredients:
|
||||
technologies |= required_technologies[ingredient] # technologies that unlock the recipes
|
||||
return technologies
|
||||
|
||||
|
||||
class Recipe(FactorioElement):
|
||||
@@ -149,19 +149,22 @@ class Recipe(FactorioElement):
|
||||
ingredients = sum(self.ingredients.values())
|
||||
return min(ingredients / amount for product, amount in self.products.items())
|
||||
|
||||
@property
|
||||
@functools.cached_property
|
||||
def base_cost(self) -> Dict[str, int]:
|
||||
ingredients = Counter()
|
||||
for ingredient, cost in self.ingredients.items():
|
||||
if ingredient in all_product_sources:
|
||||
for recipe in all_product_sources[ingredient]:
|
||||
if recipe.ingredients:
|
||||
ingredients.update({name: amount * cost / recipe.products[ingredient] for name, amount in
|
||||
recipe.base_cost.items()})
|
||||
else:
|
||||
ingredients[ingredient] += recipe.energy * cost / recipe.products[ingredient]
|
||||
else:
|
||||
ingredients[ingredient] += cost
|
||||
try:
|
||||
for ingredient, cost in self.ingredients.items():
|
||||
if ingredient in all_product_sources:
|
||||
for recipe in all_product_sources[ingredient]:
|
||||
if recipe.ingredients:
|
||||
ingredients.update({name: amount * cost / recipe.products[ingredient] for name, amount in
|
||||
recipe.base_cost.items()})
|
||||
else:
|
||||
ingredients[ingredient] += recipe.energy * cost / recipe.products[ingredient]
|
||||
else:
|
||||
ingredients[ingredient] += cost
|
||||
except RecursionError as e:
|
||||
raise Exception(f"Infinite recursion in ingredients of {self}.") from e
|
||||
return ingredients
|
||||
|
||||
@property
|
||||
@@ -191,9 +194,12 @@ recipe_sources: Dict[str, Set[str]] = {} # recipe_name -> technology source
|
||||
|
||||
# recipes and technologies can share names in Factorio
|
||||
for technology_name, data in sorted(techs_future.result().items()):
|
||||
current_ingredients = set(data["ingredients"])
|
||||
technology = Technology(technology_name, current_ingredients, factorio_tech_id,
|
||||
has_modifier=data["has_modifier"], unlocks=set(data["unlocks"]))
|
||||
technology = Technology(
|
||||
technology_name,
|
||||
factorio_tech_id,
|
||||
has_modifier=data["has_modifier"],
|
||||
unlocks=set(data["unlocks"]) - start_unlocked_recipes,
|
||||
)
|
||||
factorio_tech_id += 1
|
||||
tech_table[technology_name] = technology.factorio_id
|
||||
technology_table[technology_name] = technology
|
||||
@@ -226,11 +232,12 @@ for recipe_name, recipe_data in raw_recipes.items():
|
||||
recipes[recipe_name] = recipe
|
||||
if set(recipe.products).isdisjoint(
|
||||
# prevents loop recipes like uranium centrifuging
|
||||
set(recipe.ingredients)) and ("empty-barrel" not in recipe.products or recipe.name == "empty-barrel") and \
|
||||
set(recipe.ingredients)) and ("barrel" not in recipe.products or recipe.name == "barrel") and \
|
||||
not recipe_name.endswith("-reprocessing"):
|
||||
for product_name in recipe.products:
|
||||
all_product_sources.setdefault(product_name, set()).add(recipe)
|
||||
|
||||
assert all(recipe_name in raw_recipes for recipe_name in start_unlocked_recipes), "Unknown Recipe defined."
|
||||
|
||||
machines: Dict[str, Machine] = {}
|
||||
|
||||
@@ -248,9 +255,7 @@ del machines_future
|
||||
|
||||
# build requirements graph for all technology ingredients
|
||||
|
||||
all_ingredient_names: Set[str] = set()
|
||||
for technology in technology_table.values():
|
||||
all_ingredient_names |= technology.ingredients
|
||||
all_ingredient_names: Set[str] = set(Options.MaxSciencePack.get_ordered_science_packs())
|
||||
|
||||
|
||||
def unlock_just_tech(recipe: Recipe, _done) -> Set[Technology]:
|
||||
@@ -319,13 +324,17 @@ required_technologies: Dict[str, FrozenSet[Technology]] = Utils.KeyedDefaultDict
|
||||
recursively_get_unlocking_technologies(ingredient_name, unlock_func=unlock)))
|
||||
|
||||
|
||||
def get_rocket_requirements(silo_recipe: Recipe, part_recipe: Recipe, satellite_recipe: Recipe) -> Set[str]:
|
||||
def get_rocket_requirements(silo_recipe: Optional[Recipe], part_recipe: Recipe,
|
||||
satellite_recipe: Optional[Recipe], cargo_landing_pad_recipe: Optional[Recipe]) -> Set[str]:
|
||||
techs = set()
|
||||
if silo_recipe:
|
||||
for ingredient in silo_recipe.ingredients:
|
||||
techs |= recursively_get_unlocking_technologies(ingredient)
|
||||
for ingredient in part_recipe.ingredients:
|
||||
techs |= recursively_get_unlocking_technologies(ingredient)
|
||||
if cargo_landing_pad_recipe:
|
||||
for ingredient in cargo_landing_pad_recipe.ingredients:
|
||||
techs |= recursively_get_unlocking_technologies(ingredient)
|
||||
if satellite_recipe:
|
||||
techs |= satellite_recipe.unlocking_technologies
|
||||
for ingredient in satellite_recipe.ingredients:
|
||||
@@ -382,15 +391,15 @@ progressive_rows["progressive-processing"] = (
|
||||
"uranium-processing", "kovarex-enrichment-process", "nuclear-fuel-reprocessing")
|
||||
progressive_rows["progressive-rocketry"] = ("rocketry", "explosive-rocketry", "atomic-bomb")
|
||||
progressive_rows["progressive-vehicle"] = ("automobilism", "tank", "spidertron")
|
||||
progressive_rows["progressive-train-network"] = ("railway", "fluid-wagon",
|
||||
"automated-rail-transportation", "rail-signals")
|
||||
progressive_rows["progressive-fluid-handling"] = ("fluid-handling", "fluid-wagon")
|
||||
progressive_rows["progressive-train-network"] = ("railway", "automated-rail-transportation")
|
||||
progressive_rows["progressive-engine"] = ("engine", "electric-engine")
|
||||
progressive_rows["progressive-armor"] = ("heavy-armor", "modular-armor", "power-armor", "power-armor-mk2")
|
||||
progressive_rows["progressive-personal-battery"] = ("battery-equipment", "battery-mk2-equipment")
|
||||
progressive_rows["progressive-energy-shield"] = ("energy-shield-equipment", "energy-shield-mk2-equipment")
|
||||
progressive_rows["progressive-wall"] = ("stone-wall", "gate")
|
||||
progressive_rows["progressive-follower"] = ("defender", "distractor", "destroyer")
|
||||
progressive_rows["progressive-inserter"] = ("fast-inserter", "stack-inserter")
|
||||
progressive_rows["progressive-inserter"] = ("fast-inserter", "bulk-inserter")
|
||||
progressive_rows["progressive-turret"] = ("gun-turret", "laser-turret")
|
||||
progressive_rows["progressive-flamethrower"] = ("flamethrower",) # leaving out flammables, as they do nothing
|
||||
progressive_rows["progressive-personal-roboport-equipment"] = ("personal-roboport-equipment",
|
||||
@@ -402,7 +411,7 @@ sorted_rows = sorted(progressive_rows)
|
||||
source_target_mapping: Dict[str, str] = {
|
||||
"progressive-braking-force": "progressive-train-network",
|
||||
"progressive-inserter-capacity-bonus": "progressive-inserter",
|
||||
"progressive-refined-flammables": "progressive-flamethrower"
|
||||
"progressive-refined-flammables": "progressive-flamethrower",
|
||||
}
|
||||
|
||||
for source, target in source_target_mapping.items():
|
||||
@@ -416,12 +425,14 @@ progressive_technology_table: Dict[str, Technology] = {}
|
||||
|
||||
for root in sorted_rows:
|
||||
progressive = progressive_rows[root]
|
||||
assert all(tech in tech_table for tech in progressive), "declared a progressive technology without base technology"
|
||||
assert all(tech in tech_table for tech in progressive), \
|
||||
(f"Declared a progressive technology ({root}) without base technology. "
|
||||
f"Missing: f{tuple(tech for tech in progressive if tech not in tech_table)}")
|
||||
factorio_tech_id += 1
|
||||
progressive_technology = Technology(root, technology_table[progressive[0]].ingredients, factorio_tech_id,
|
||||
progressive,
|
||||
progressive_technology = Technology(root, factorio_tech_id,
|
||||
tuple(progressive),
|
||||
has_modifier=any(technology_table[tech].has_modifier for tech in progressive),
|
||||
unlocks=any(technology_table[tech].unlocks for tech in progressive))
|
||||
unlocks=any(technology_table[tech].unlocks for tech in progressive),)
|
||||
progressive_tech_table[root] = progressive_technology.factorio_id
|
||||
progressive_technology_table[root] = progressive_technology
|
||||
|
||||
|
||||
Reference in New Issue
Block a user