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:
Fabian Dill 2024-11-11 11:43:16 +01:00 committed by GitHub
parent b3e5ef876a
commit f3413e9cef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 263 additions and 200 deletions

View File

@ -304,13 +304,13 @@ def stream_factorio_output(pipe, queue, process):
async def factorio_server_watcher(ctx: FactorioContext):
savegame_name = os.path.abspath(ctx.savegame_name)
savegame_name = os.path.abspath(os.path.join(ctx.write_data_path, "saves", "Archipelago", ctx.savegame_name))
if not os.path.exists(savegame_name):
logger.info(f"Creating savegame {savegame_name}")
subprocess.run((
executable, "--create", savegame_name, "--preset", "archipelago"
))
factorio_process = subprocess.Popen((executable, "--start-server", ctx.savegame_name,
factorio_process = subprocess.Popen((executable, "--start-server", savegame_name,
*(str(elem) for elem in server_args)),
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
@ -331,7 +331,8 @@ async def factorio_server_watcher(ctx: FactorioContext):
factorio_queue.task_done()
if not ctx.rcon_client and "Starting RCON interface at IP ADDR:" in msg:
ctx.rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password)
ctx.rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password,
timeout=5)
if not ctx.server:
logger.info("Established bridge to Factorio Server. "
"Ready to connect to Archipelago via /connect")
@ -405,8 +406,7 @@ async def get_info(ctx: FactorioContext, rcon_client: factorio_rcon.RCONClient):
info = json.loads(rcon_client.send_command("/ap-rcon-info"))
ctx.auth = info["slot_name"]
ctx.seed_name = info["seed_name"]
# 0.2.0 addition, not present earlier
death_link = bool(info.get("death_link", False))
death_link = info["death_link"]
ctx.energy_link_increment = info.get("energy_link", 0)
logger.debug(f"Energy Link Increment: {ctx.energy_link_increment}")
if ctx.energy_link_increment and ctx.ui:

View File

@ -35,9 +35,11 @@ base_info = {
"author": "Berserker",
"homepage": "https://archipelago.gg",
"description": "Integration client for the Archipelago Randomizer",
"factorio_version": "1.1",
"factorio_version": "2.0",
"dependencies": [
"base >= 1.1.0",
"base >= 2.0.15",
"? quality >= 2.0.15",
"! space-age",
"? science-not-invited",
"? factory-levels"
]
@ -133,21 +135,21 @@ def generate_mod(world: "Factorio", output_directory: str):
"allowed_science_packs": world.options.max_science_pack.get_allowed_packs(),
"custom_technologies": world.custom_technologies,
"tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites,
"slot_name": world.player_name, "seed_name": multiworld.seed_name,
"slot_name": world.player_name,
"seed_name": multiworld.seed_name,
"slot_player": player,
"starting_items": world.options.starting_items, "recipes": recipes,
"random": random, "flop_random": flop_random,
"recipes": recipes,
"random": random,
"flop_random": flop_random,
"recipe_time_scale": recipe_time_scales.get(world.options.recipe_time.value, None),
"recipe_time_range": recipe_time_ranges.get(world.options.recipe_time.value, None),
"free_sample_blacklist": {item: 1 for item in free_sample_exclusions},
"free_sample_quality_name": world.options.free_samples_quality.current_key,
"progressive_technology_table": {tech.name: tech.progressive for tech in
progressive_technology_table.values()},
"custom_recipes": world.custom_recipes,
"max_science_pack": world.options.max_science_pack.value,
"liquids": fluids,
"goal": world.options.goal.value,
"energy_link": world.options.energy_link.value,
"useless_technologies": useless_technologies,
"removed_technologies": world.removed_technologies,
"chunk_shuffle": 0,
}

View File

@ -1,12 +1,11 @@
from __future__ import annotations
from dataclasses import dataclass
import datetime
import typing
from schema import Schema, Optional, And, Or
from Options import Choice, OptionDict, OptionSet, Option, DefaultOnToggle, Range, DeathLink, Toggle, \
from Options import Choice, OptionDict, OptionSet, DefaultOnToggle, Range, DeathLink, Toggle, \
StartInventoryPool, PerGameCommonOptions
# schema helpers
@ -122,6 +121,18 @@ class FreeSamples(Choice):
default = 3
class FreeSamplesQuality(Choice):
"""If free samples are on, determine the quality of the granted items.
Requires the quality mod, which is part of the Space Age DLC. Without it, normal quality is given."""
display_name = "Free Samples Quality"
option_normal = 0
option_uncommon = 1
option_rare = 2
option_epic = 3
option_legendary = 4
default = 0
class TechTreeLayout(Choice):
"""Selects how the tech tree nodes are interwoven.
Single: No dependencies
@ -284,17 +295,21 @@ class FactorioWorldGen(OptionDict):
# FIXME: do we want default be a rando-optimized default or in-game DS?
value: typing.Dict[str, typing.Dict[str, typing.Any]]
default = {
"terrain_segmentation": 0.5,
"water": 1.5,
"autoplace_controls": {
# terrain
"water": {"frequency": 1, "size": 1, "richness": 1},
"nauvis_cliff": {"frequency": 1, "size": 1, "richness": 1},
"starting_area_moisture": {"frequency": 1, "size": 1, "richness": 1},
# resources
"coal": {"frequency": 1, "size": 3, "richness": 6},
"copper-ore": {"frequency": 1, "size": 3, "richness": 6},
"crude-oil": {"frequency": 1, "size": 3, "richness": 6},
"enemy-base": {"frequency": 1, "size": 1, "richness": 1},
"iron-ore": {"frequency": 1, "size": 3, "richness": 6},
"stone": {"frequency": 1, "size": 3, "richness": 6},
"uranium-ore": {"frequency": 1, "size": 3, "richness": 6},
# misc
"trees": {"frequency": 1, "size": 1, "richness": 1},
"uranium-ore": {"frequency": 1, "size": 3, "richness": 6}
"enemy-base": {"frequency": 1, "size": 1, "richness": 1},
},
"seed": None,
"starting_area": 1,
@ -336,8 +351,6 @@ class FactorioWorldGen(OptionDict):
}
schema = Schema({
"basic": {
Optional("terrain_segmentation"): FloatRange(0.166, 6),
Optional("water"): FloatRange(0.166, 6),
Optional("autoplace_controls"): {
str: {
"frequency": FloatRange(0, 6),
@ -438,6 +451,7 @@ class FactorioOptions(PerGameCommonOptions):
silo: Silo
satellite: Satellite
free_samples: FreeSamples
free_samples_quality: FreeSamplesQuality
tech_tree_information: TechTreeInformation
starting_items: FactorioStartItems
free_sample_blacklist: FactorioFreeSampleBlacklist

View File

@ -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

View File

@ -2,10 +2,11 @@ from __future__ import annotations
import collections
import logging
import settings
import typing
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification
import Utils
import settings
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification
from worlds.AutoWorld import World, WebWorld
from worlds.LauncherComponents import Component, components, Type, launch_subprocess
from worlds.generic import Rules
@ -14,7 +15,7 @@ from .Mod import generate_mod
from .Options import FactorioOptions, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution
from .Shapes import get_shapes
from .Technologies import base_tech_table, recipe_sources, base_technology_table, \
all_ingredient_names, all_product_sources, required_technologies, get_rocket_requirements, \
all_product_sources, required_technologies, get_rocket_requirements, \
progressive_technology_table, common_tech_table, tech_to_progressive_lookup, progressive_tech_table, \
get_science_pack_pools, Recipe, recipes, technology_table, tech_table, factorio_base_id, useless_technologies, \
fluids, stacking_items, valid_ingredients, progressive_rows
@ -97,19 +98,21 @@ class Factorio(World):
item_name_groups = {
"Progressive": set(progressive_tech_table.keys()),
}
required_client_version = (0, 5, 0)
required_client_version = (0, 5, 1)
if Utils.version_tuple < required_client_version:
raise Exception(f"Update Archipelago to use this world ({game}).")
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()
tech_tree_layout_prerequisites: typing.Dict[FactorioScienceLocation, typing.Set[FactorioScienceLocation]]
tech_mix: int = 0
skip_silo: bool = False
origin_region_name = "Nauvis"
science_locations: typing.List[FactorioScienceLocation]
removed_technologies: typing.Set[str]
settings: typing.ClassVar[FactorioSettings]
def __init__(self, world, player: int):
super(Factorio, self).__init__(world, player)
self.removed_technologies = useless_technologies.copy()
self.advancement_technologies = set()
self.custom_recipes = {}
self.science_locations = []
@ -208,11 +211,9 @@ class Factorio(World):
for loc in self.science_locations:
loc.revealed = True
if self.skip_silo:
removed = useless_technologies | {"rocket-silo"}
else:
removed = useless_technologies
self.removed_technologies |= {"rocket-silo"}
for tech_name in base_tech_table:
if tech_name not in removed:
if tech_name not in self.removed_technologies:
progressive_item_name = tech_to_progressive_lookup.get(tech_name, tech_name)
want_progressive = want_progressives[progressive_item_name]
item_name = progressive_item_name if want_progressive else tech_name
@ -240,40 +241,49 @@ class Factorio(World):
custom_recipe = self.custom_recipes[ingredient]
location.access_rule = lambda state, ingredient=ingredient, custom_recipe=custom_recipe: \
(ingredient not in technology_table or state.has(ingredient, player)) and \
(not technology_table[ingredient].unlocks or state.has(ingredient, player)) and \
all(state.has(technology.name, player) for sub_ingredient in custom_recipe.ingredients
for technology in required_technologies[sub_ingredient]) and \
all(state.has(technology.name, player) for technology in required_technologies[custom_recipe.crafting_machine])
else:
location.access_rule = lambda state, ingredient=ingredient: \
all(state.has(technology.name, player) for technology in required_technologies[ingredient])
for location in self.science_locations:
Rules.set_rule(location, lambda state, ingredients=location.ingredients:
Rules.set_rule(location, lambda state, ingredients=frozenset(location.ingredients):
all(state.has(f"Automated {ingredient}", player) for ingredient in ingredients))
prerequisites = shapes.get(location)
if prerequisites:
Rules.add_rule(location, lambda state, locations=
prerequisites: all(state.can_reach(loc) for loc in locations))
Rules.add_rule(location, lambda state, locations=frozenset(prerequisites):
all(state.can_reach(loc) for loc in locations))
silo_recipe = None
cargo_pad_recipe = None
if self.options.silo == Silo.option_spawn:
silo_recipe = self.custom_recipes["rocket-silo"] if "rocket-silo" in self.custom_recipes \
else next(iter(all_product_sources.get("rocket-silo")))
silo_recipe = self.get_recipe("rocket-silo")
cargo_pad_recipe = self.get_recipe("cargo-landing-pad")
part_recipe = self.custom_recipes["rocket-part"]
satellite_recipe = None
if self.options.goal == Goal.option_satellite:
satellite_recipe = self.custom_recipes["satellite"] if "satellite" in self.custom_recipes \
else next(iter(all_product_sources.get("satellite")))
victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe)
if self.options.silo != Silo.option_spawn:
victory_tech_names.add("rocket-silo")
satellite_recipe = self.get_recipe("satellite")
victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe, cargo_pad_recipe)
if self.options.silo == Silo.option_spawn:
victory_tech_names -= {"rocket-silo"}
else:
victory_tech_names |= {"rocket-silo"}
self.get_location("Rocket Launch").access_rule = lambda state: all(state.has(technology, player)
for technology in
victory_tech_names)
for tech_name in victory_tech_names:
if not self.multiworld.get_all_state(True).has(tech_name, player):
print(tech_name)
self.multiworld.completion_condition[player] = lambda state: state.has('Victory', player)
def get_recipe(self, name: str) -> Recipe:
return self.custom_recipes[name] if name in self.custom_recipes \
else next(iter(all_product_sources.get(name)))
def generate_basic(self):
map_basic_settings = self.options.world_gen.value["basic"]
if map_basic_settings.get("seed", None) is None: # allow seed 0
@ -321,9 +331,11 @@ class Factorio(World):
def make_quick_recipe(self, original: Recipe, pool: list, allow_liquids: int = 2,
ingredients_offset: int = 0) -> Recipe:
count: int = len(original.ingredients) + ingredients_offset
assert len(pool) >= count, f"Can't pick {count} many items from pool {pool}."
new_ingredients = {}
liquids_used = 0
for _ in range(len(original.ingredients) + ingredients_offset):
for _ in range(count):
new_ingredient = pool.pop()
if new_ingredient in fluids:
while liquids_used == allow_liquids and new_ingredient in fluids:
@ -440,7 +452,8 @@ class Factorio(World):
ingredients_offset = self.options.recipe_ingredients_offset
original_rocket_part = recipes["rocket-part"]
science_pack_pools = get_science_pack_pools()
valid_pool = sorted(science_pack_pools[self.options.max_science_pack.get_max_pack()] & valid_ingredients)
valid_pool = sorted(science_pack_pools[self.options.max_science_pack.get_max_pack()]
& valid_ingredients)
self.random.shuffle(valid_pool)
self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category,
{valid_pool[x]: 10 for x in range(3 + ingredients_offset)},
@ -489,7 +502,7 @@ class Factorio(World):
needed_recipes = self.options.max_science_pack.get_allowed_packs() | {"rocket-part"}
if self.options.silo != Silo.option_spawn:
needed_recipes |= {"rocket-silo"}
needed_recipes |= {"rocket-silo", "cargo-landing-pad"}
if self.options.goal.value == Goal.option_satellite:
needed_recipes |= {"satellite"}

View File

@ -1 +1 @@
["fluid-unknown","water","crude-oil","steam","heavy-oil","light-oil","petroleum-gas","sulfuric-acid","lubricant"]
["water","steam","crude-oil","petroleum-gas","light-oil","heavy-oil","lubricant","sulfuric-acid","parameter-0","parameter-1","parameter-2","parameter-3","parameter-4","parameter-5","parameter-6","parameter-7","parameter-8","parameter-9","fluid-unknown"]

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"stone-furnace":{"smelting":true},"steel-furnace":{"smelting":true},"electric-furnace":{"smelting":true},"assembling-machine-1":{"crafting":true,"basic-crafting":true,"advanced-crafting":true},"assembling-machine-2":{"basic-crafting":true,"crafting":true,"advanced-crafting":true,"crafting-with-fluid":true},"assembling-machine-3":{"basic-crafting":true,"crafting":true,"advanced-crafting":true,"crafting-with-fluid":true},"oil-refinery":{"oil-processing":true},"chemical-plant":{"chemistry":true},"centrifuge":{"centrifuging":true},"rocket-silo":{"rocket-building":true},"character":{"crafting":true}}
{"stone-furnace":{"smelting":true},"steel-furnace":{"smelting":true},"electric-furnace":{"smelting":true},"assembling-machine-1":{"crafting":true,"basic-crafting":true,"advanced-crafting":true,"parameters":true},"assembling-machine-2":{"basic-crafting":true,"crafting":true,"advanced-crafting":true,"crafting-with-fluid":true,"parameters":true},"assembling-machine-3":{"basic-crafting":true,"crafting":true,"advanced-crafting":true,"crafting-with-fluid":true,"parameters":true},"oil-refinery":{"oil-processing":true,"parameters":true},"chemical-plant":{"chemistry":true,"parameters":true},"centrifuge":{"centrifuging":true,"parameters":true},"rocket-silo":{"rocket-building":true,"parameters":true},"character":{"crafting":true}}

View File

@ -1,9 +1,9 @@
function get_any_stack_size(name)
local item = game.item_prototypes[name]
local item = prototypes.item[name]
if item ~= nil then
return item.stack_size
end
item = game.equipment_prototypes[name]
item = prototypes.equipment[name]
if item ~= nil then
return item.stack_size
end
@ -24,7 +24,7 @@ function split(s, sep)
end
function random_offset_position(position, offset)
return {x=position.x+math.random(-offset, offset), y=position.y+math.random(-1024, 1024)}
return {x=position.x+math.random(-offset, offset), y=position.y+math.random(-offset, offset)}
end
function fire_entity_at_players(entity_name, speed)
@ -36,4 +36,4 @@ function fire_entity_at_players(entity_name, speed)
target=current_character, speed=speed}
end
end
end
end

View File

@ -143,24 +143,24 @@ function on_check_energy_link(event)
local force = "player"
local bridges = surface.find_entities_filtered({name="ap-energy-bridge", force=force})
local bridgecount = table_size(bridges)
global.forcedata[force].energy_bridges = bridgecount
if global.forcedata[force].energy == nil then
global.forcedata[force].energy = 0
storage.forcedata[force].energy_bridges = bridgecount
if storage.forcedata[force].energy == nil then
storage.forcedata[force].energy = 0
end
if global.forcedata[force].energy < ENERGY_INCREMENT * bridgecount * 5 then
if storage.forcedata[force].energy < ENERGY_INCREMENT * bridgecount * 5 then
for i, bridge in ipairs(bridges) do
if bridge.energy > ENERGY_INCREMENT*3 then
global.forcedata[force].energy = global.forcedata[force].energy + (ENERGY_INCREMENT * ENERGY_LINK_EFFICIENCY)
storage.forcedata[force].energy = storage.forcedata[force].energy + (ENERGY_INCREMENT * ENERGY_LINK_EFFICIENCY)
bridge.energy = bridge.energy - ENERGY_INCREMENT
end
end
end
for i, bridge in ipairs(bridges) do
if global.forcedata[force].energy < ENERGY_INCREMENT then
if storage.forcedata[force].energy < ENERGY_INCREMENT then
break
end
if bridge.energy < ENERGY_INCREMENT*2 and global.forcedata[force].energy > ENERGY_INCREMENT then
global.forcedata[force].energy = global.forcedata[force].energy - ENERGY_INCREMENT
if bridge.energy < ENERGY_INCREMENT*2 and storage.forcedata[force].energy > ENERGY_INCREMENT then
storage.forcedata[force].energy = storage.forcedata[force].energy - ENERGY_INCREMENT
bridge.energy = bridge.energy + ENERGY_INCREMENT
end
end
@ -186,23 +186,41 @@ function check_spawn_silo(force)
local surface = game.get_surface(1)
local spawn_position = force.get_spawn_position(surface)
spawn_entity(surface, force, "rocket-silo", spawn_position.x, spawn_position.y, 80, true, true)
spawn_entity(surface, force, "cargo-landing-pad", spawn_position.x, spawn_position.y, 80, true, true)
end
end
function check_despawn_silo(force)
if not force.players or #force.players < 1 and force.get_entity_count("rocket-silo") > 0 then
local surface = game.get_surface(1)
local spawn_position = force.get_spawn_position(surface)
local x1 = spawn_position.x - 41
local x2 = spawn_position.x + 41
local y1 = spawn_position.y - 41
local y2 = spawn_position.y + 41
local silos = surface.find_entities_filtered{area = { {x1, y1}, {x2, y2} },
name = "rocket-silo",
force = force}
for i,silo in ipairs(silos) do
silo.destructible = true
silo.destroy()
if not force.players or #force.players < 1 then
if force.get_entity_count("rocket-silo") > 0 then
local surface = game.get_surface(1)
local spawn_position = force.get_spawn_position(surface)
local x1 = spawn_position.x - 41
local x2 = spawn_position.x + 41
local y1 = spawn_position.y - 41
local y2 = spawn_position.y + 41
local silos = surface.find_entities_filtered{area = { {x1, y1}, {x2, y2} },
name = "rocket-silo",
force = force}
for i, silo in ipairs(silos) do
silo.destructible = true
silo.destroy()
end
end
if force.get_entity_count("cargo-landing-pad") > 0 then
local surface = game.get_surface(1)
local spawn_position = force.get_spawn_position(surface)
local x1 = spawn_position.x - 41
local x2 = spawn_position.x + 41
local y1 = spawn_position.y - 41
local y2 = spawn_position.y + 41
local pads = surface.find_entities_filtered{area = { {x1, y1}, {x2, y2} },
name = "cargo-landing-pad",
force = force}
for i, pad in ipairs(pads) do
pad.destructible = true
pad.destroy()
end
end
end
end
@ -214,19 +232,18 @@ function on_force_created(event)
if type(event.force) == "string" then -- should be of type LuaForce
force = game.forces[force]
end
force.research_queue_enabled = true
local data = {}
data['earned_samples'] = {{ dict_to_lua(starting_items) }}
data["victory"] = 0
data["death_link_tick"] = 0
data["energy"] = 0
data["energy_bridges"] = 0
global.forcedata[event.force] = data
storage.forcedata[event.force] = data
{%- if silo == 2 %}
check_spawn_silo(force)
{%- endif %}
{%- for tech_name in useless_technologies %}
force.technologies.{{ tech_name }}.researched = true
{%- for tech_name in removed_technologies %}
force.technologies["{{ tech_name }}"].researched = true
{%- endfor %}
end
script.on_event(defines.events.on_force_created, on_force_created)
@ -236,7 +253,7 @@ function on_force_destroyed(event)
{%- if silo == 2 %}
check_despawn_silo(event.force)
{%- endif %}
global.forcedata[event.force.name] = nil
storage.forcedata[event.force.name] = nil
end
function on_runtime_mod_setting_changed(event)
@ -267,8 +284,8 @@ function on_player_created(event)
-- FIXME: This (probably) fires before any other mod has a chance to change the player's force
-- For now, they will (probably) always be on the 'player' force when this event fires.
local data = {}
data['pending_samples'] = table.deepcopy(global.forcedata[player.force.name]['earned_samples'])
global.playerdata[player.index] = data
data['pending_samples'] = table.deepcopy(storage.forcedata[player.force.name]['earned_samples'])
storage.playerdata[player.index] = data
update_player(player.index) -- Attempt to send pending free samples, if relevant.
{%- if silo == 2 %}
check_spawn_silo(game.players[event.player_index].force)
@ -287,14 +304,14 @@ end
script.on_event(defines.events.on_player_changed_force, on_player_changed_force)
function on_player_removed(event)
global.playerdata[event.player_index] = nil
storage.playerdata[event.player_index] = nil
end
script.on_event(defines.events.on_player_removed, on_player_removed)
function on_rocket_launched(event)
if event.rocket and event.rocket.valid and global.forcedata[event.rocket.force.name]['victory'] == 0 then
if event.rocket and event.rocket.valid and storage.forcedata[event.rocket.force.name]['victory'] == 0 then
if event.rocket.get_item_count("satellite") > 0 or GOAL == 0 then
global.forcedata[event.rocket.force.name]['victory'] = 1
storage.forcedata[event.rocket.force.name]['victory'] = 1
dumpInfo(event.rocket.force)
game.set_game_state
{
@ -318,7 +335,7 @@ function update_player(index)
if not character or not character.valid then
return
end
local data = global.playerdata[index]
local data = storage.playerdata[index]
local samples = data['pending_samples']
local sent
--player.print(serpent.block(data['pending_samples']))
@ -327,14 +344,17 @@ function update_player(index)
for name, count in pairs(samples) do
stack.name = name
stack.count = count
if game.item_prototypes[name] then
if script.active_mods["quality"] then
stack.quality = "{{ free_sample_quality_name }}"
end
if prototypes.item[name] then
if character.can_insert(stack) then
sent = character.insert(stack)
else
sent = 0
end
if sent > 0 then
player.print("Received " .. sent .. "x [item=" .. name .. "]")
player.print("Received " .. sent .. "x [item=" .. name .. ",quality={{ free_sample_quality_name }}]")
data.suppress_full_inventory_message = false
end
if sent ~= count then -- Couldn't full send.
@ -372,19 +392,19 @@ function add_samples(force, name, count)
end
t[name] = (t[name] or 0) + count
end
-- Add to global table of earned samples for future new players
add_to_table(global.forcedata[force.name]['earned_samples'])
-- Add to storage table of earned samples for future new players
add_to_table(storage.forcedata[force.name]['earned_samples'])
-- Add to existing players
for _, player in pairs(force.players) do
add_to_table(global.playerdata[player.index]['pending_samples'])
add_to_table(storage.playerdata[player.index]['pending_samples'])
update_player(player.index)
end
end
script.on_init(function()
{% if not imported_blueprints %}set_permissions(){% endif %}
global.forcedata = {}
global.playerdata = {}
storage.forcedata = {}
storage.playerdata = {}
-- Fire dummy events for all currently existing forces.
local e = {}
for name, _ in pairs(game.forces) do
@ -420,12 +440,12 @@ script.on_event(defines.events.on_research_finished, function(event)
if FREE_SAMPLES == 0 then
return -- Nothing else to do
end
if not technology.effects then
if not technology.prototype.effects then
return -- No technology effects, so nothing to do.
end
for _, effect in pairs(technology.effects) do
for _, effect in pairs(technology.prototype.effects) do
if effect.type == "unlock-recipe" then
local recipe = game.recipe_prototypes[effect.recipe]
local recipe = prototypes.recipe[effect.recipe]
for _, result in pairs(recipe.products) do
if result.type == "item" and result.amount then
local name = result.name
@ -477,7 +497,7 @@ function kill_players(force)
end
function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores)
local prototype = game.entity_prototypes[name]
local prototype = prototypes.entity[name]
local args = { -- For can_place_entity and place_entity
name = prototype.name,
position = {x = x, y = y},
@ -537,7 +557,7 @@ function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores)
}
local entities = surface.find_entities_filtered {
area = collision_area,
collision_mask = prototype.collision_mask
collision_mask = prototype.collision_mask.layers
}
local can_place = true
for _, entity in pairs(entities) do
@ -560,6 +580,9 @@ function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores)
end
args.build_check_type = defines.build_check_type.script
args.create_build_effect_smoke = false
if script.active_mods["quality"] then
args.quality = "{{ free_sample_quality_name }}"
end
new_entity = surface.create_entity(args)
if new_entity then
new_entity.destructible = false
@ -585,7 +608,7 @@ script.on_event(defines.events.on_entity_died, function(event)
end
local force = event.entity.force
global.forcedata[force.name].death_link_tick = game.tick
storage.forcedata[force.name].death_link_tick = game.tick
dumpInfo(force)
kill_players(force)
end, {LuaEntityDiedEventFilter = {["filter"] = "name", ["name"] = "character"}})
@ -600,7 +623,7 @@ commands.add_command("ap-sync", "Used by the Archipelago client to get progress
force = game.players[call.player_index].force
end
local research_done = {}
local forcedata = chain_lookup(global, "forcedata", force.name)
local forcedata = chain_lookup(storage, "forcedata", force.name)
local data_collection = {
["research_done"] = research_done,
["victory"] = chain_lookup(forcedata, "victory"),
@ -616,7 +639,7 @@ commands.add_command("ap-sync", "Used by the Archipelago client to get progress
research_done[tech_name] = tech.researched
end
end
rcon.print(game.table_to_json({["slot_name"] = SLOT_NAME, ["seed_name"] = SEED_NAME, ["info"] = data_collection}))
rcon.print(helpers.table_to_json({["slot_name"] = SLOT_NAME, ["seed_name"] = SEED_NAME, ["info"] = data_collection}))
end)
commands.add_command("ap-print", "Used by the Archipelago client to print messages", function (call)
@ -655,8 +678,8 @@ end,
}
commands.add_command("ap-get-technology", "Grant a technology, used by the Archipelago Client.", function(call)
if global.index_sync == nil then
global.index_sync = {}
if storage.index_sync == nil then
storage.index_sync = {}
end
local tech
local force = game.forces["player"]
@ -680,8 +703,8 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi
end
return
elseif progressive_technologies[item_name] ~= nil then
if global.index_sync[index] ~= item_name then -- not yet received prog item
global.index_sync[index] = item_name
if storage.index_sync[index] ~= item_name then -- not yet received prog item
storage.index_sync[index] = item_name
local tech_stack = progressive_technologies[item_name]
for _, item_name in ipairs(tech_stack) do
tech = force.technologies[item_name]
@ -696,7 +719,7 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi
elseif force.technologies[item_name] ~= nil then
tech = force.technologies[item_name]
if tech ~= nil then
global.index_sync[index] = tech
storage.index_sync[index] = tech
if tech.researched ~= true then
game.print({"", "Received [technology=" .. tech.name .. "] from ", source})
game.play_sound({path="utility/research_completed"})
@ -704,8 +727,8 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi
end
end
elseif TRAP_TABLE[item_name] ~= nil then
if global.index_sync[index] ~= item_name then -- not yet received trap
global.index_sync[index] = item_name
if storage.index_sync[index] ~= item_name then -- not yet received trap
storage.index_sync[index] = item_name
game.print({"", "Received ", item_name, " from ", source})
TRAP_TABLE[item_name]()
end
@ -716,7 +739,7 @@ end)
commands.add_command("ap-rcon-info", "Used by the Archipelago client to get information", function(call)
rcon.print(game.table_to_json({
rcon.print(helpers.table_to_json({
["slot_name"] = SLOT_NAME,
["seed_name"] = SEED_NAME,
["death_link"] = DEATH_LINK,
@ -726,7 +749,7 @@ end)
{% if allow_cheats -%}
commands.add_command("ap-spawn-silo", "Attempts to spawn a silo around 0,0", function(call)
commands.add_command("ap-spawn-silo", "Attempts to spawn a silo and cargo landing pad around 0,0", function(call)
spawn_entity(game.player.surface, game.player.force, "rocket-silo", 0, 0, 80, true, true)
end)
{% endif -%}
@ -742,7 +765,7 @@ end)
commands.add_command("ap-energylink", "Used by the Archipelago client to manage Energy Link", function(call)
local change = tonumber(call.parameter or "0")
local force = "player"
global.forcedata[force].energy = global.forcedata[force].energy + change
storage.forcedata[force].energy = storage.forcedata[force].energy + change
end)
commands.add_command("energy-link", "Print the status of the Archipelago energy link.", function(call)

View File

@ -6,43 +6,46 @@ data.raw["rocket-silo"]["rocket-silo"].fluid_boxes = {
production_type = "input",
pipe_picture = assembler2pipepictures(),
pipe_covers = pipecoverspictures(),
volume = 1000,
base_area = 10,
base_level = -1,
pipe_connections = {
{ type = "input", position = { 0, 5 } },
{ type = "input", position = { 0, -5 } },
{ type = "input", position = { 5, 0 } },
{ type = "input", position = { -5, 0 } }
{ flow_direction = "input", direction = defines.direction.south, position = { 0, 4.2 } },
{ flow_direction = "input", direction = defines.direction.north, position = { 0, -4.2 } },
{ flow_direction = "input", direction = defines.direction.east, position = { 4.2, 0 } },
{ flow_direction = "input", direction = defines.direction.west, position = { -4.2, 0 } }
}
},
{
production_type = "input",
pipe_picture = assembler2pipepictures(),
pipe_covers = pipecoverspictures(),
volume = 1000,
base_area = 10,
base_level = -1,
pipe_connections = {
{ type = "input", position = { -3, 5 } },
{ type = "input", position = { -3, -5 } },
{ type = "input", position = { 5, -3 } },
{ type = "input", position = { -5, -3 } }
{ flow_direction = "input", direction = defines.direction.south, position = { -3, 4.2 } },
{ flow_direction = "input", direction = defines.direction.north, position = { -3, -4.2 } },
{ flow_direction = "input", direction = defines.direction.east, position = { 4.2, -3 } },
{ flow_direction = "input", direction = defines.direction.west, position = { -4.2, -3 } }
}
},
{
production_type = "input",
pipe_picture = assembler2pipepictures(),
pipe_covers = pipecoverspictures(),
volume = 1000,
base_area = 10,
base_level = -1,
pipe_connections = {
{ type = "input", position = { 3, 5 } },
{ type = "input", position = { 3, -5 } },
{ type = "input", position = { 5, 3 } },
{ type = "input", position = { -5, 3 } }
{ flow_direction = "input", direction = defines.direction.south, position = { 3, 4.2 } },
{ flow_direction = "input", direction = defines.direction.north, position = { 3, -4.2 } },
{ flow_direction = "input", direction = defines.direction.east, position = { 4.2, 3 } },
{ flow_direction = "input", direction = defines.direction.west, position = { -4.2, 3 } }
}
},
off_when_no_fluid_recipe = true
}
}
data.raw["rocket-silo"]["rocket-silo"].fluid_boxes_off_when_no_fluid_recipe = true
{%- for recipe_name, recipe in custom_recipes.items() %}
data.raw["recipe"]["{{recipe_name}}"].category = "{{recipe.category}}"

View File

@ -18,12 +18,9 @@ energy_bridge.energy_source.buffer_capacity = "50MJ"
energy_bridge.energy_source.input_flow_limit = "10MW"
energy_bridge.energy_source.output_flow_limit = "10MW"
tint_icon(energy_bridge, energy_bridge_tint())
energy_bridge.picture.layers[1].tint = energy_bridge_tint()
energy_bridge.picture.layers[1].hr_version.tint = energy_bridge_tint()
energy_bridge.charge_animation.layers[1].layers[1].tint = energy_bridge_tint()
energy_bridge.charge_animation.layers[1].layers[1].hr_version.tint = energy_bridge_tint()
energy_bridge.discharge_animation.layers[1].layers[1].tint = energy_bridge_tint()
energy_bridge.discharge_animation.layers[1].layers[1].hr_version.tint = energy_bridge_tint()
energy_bridge.chargable_graphics.picture.layers[1].tint = energy_bridge_tint()
energy_bridge.chargable_graphics.charge_animation.layers[1].layers[1].tint = energy_bridge_tint()
energy_bridge.chargable_graphics.discharge_animation.layers[1].layers[1].tint = energy_bridge_tint()
data.raw["accumulator"]["ap-energy-bridge"] = energy_bridge
local energy_bridge_item = table.deepcopy(data.raw["item"]["accumulator"])
@ -35,9 +32,9 @@ data.raw["item"]["ap-energy-bridge"] = energy_bridge_item
local energy_bridge_recipe = table.deepcopy(data.raw["recipe"]["accumulator"])
energy_bridge_recipe.name = "ap-energy-bridge"
energy_bridge_recipe.result = energy_bridge_item.name
energy_bridge_recipe.results = { {type = "item", name = energy_bridge_item.name, amount = 1} }
energy_bridge_recipe.energy_required = 1
energy_bridge_recipe.enabled = {{ energy_link }}
energy_bridge_recipe.enabled = {% if energy_link %}true{% else %}false{% endif %}
energy_bridge_recipe.localised_name = "Archipelago EnergyLink Bridge"
data.raw["recipe"]["ap-energy-bridge"] = energy_bridge_recipe

View File

@ -26,4 +26,4 @@
{type = {% if key in liquids %}"fluid"{% else %}"item"{% endif %}, name = "{{ key }}", amount = {{ value | safe }}}{% if not loop.last %},{% endif %}
{% endfor -%}
}
{%- endmacro %}
{%- endmacro %}

View File

@ -27,4 +27,4 @@ data:extend({
default_value = false
{% endif %}
}
})
})

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"iron-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"iron-ore":{"name":"iron-ore","amount":1}}},"copper-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"copper-ore":{"name":"copper-ore","amount":1}}},"stone":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"stone":{"name":"stone","amount":1}}},"coal":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"coal":{"name":"coal","amount":1}}},"uranium-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":2,"required_fluid":"sulfuric-acid","fluid_amount":10,"products":{"uranium-ore":{"name":"uranium-ore","amount":1}}},"crude-oil":{"minable":true,"infinite":true,"infinite_depletion":10,"category":"basic-fluid","mining_time":1,"products":{"crude-oil":{"name":"crude-oil","amount":10}}}}
{"iron-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"iron-ore":{"name":"iron-ore","amount":1}}},"copper-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"copper-ore":{"name":"copper-ore","amount":1}}},"stone":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"stone":{"name":"stone","amount":1}}},"coal":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"coal":{"name":"coal","amount":1}}},"crude-oil":{"minable":true,"infinite":true,"infinite_depletion":10,"category":"basic-fluid","mining_time":1,"products":{"crude-oil":{"name":"crude-oil","amount":10}}},"uranium-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":2,"required_fluid":"sulfuric-acid","fluid_amount":10,"products":{"uranium-ore":{"name":"uranium-ore","amount":1}}}}

File diff suppressed because one or more lines are too long