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): 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): if not os.path.exists(savegame_name):
logger.info(f"Creating savegame {savegame_name}") logger.info(f"Creating savegame {savegame_name}")
subprocess.run(( subprocess.run((
executable, "--create", savegame_name, "--preset", "archipelago" 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)), *(str(elem) for elem in server_args)),
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -331,7 +331,8 @@ async def factorio_server_watcher(ctx: FactorioContext):
factorio_queue.task_done() factorio_queue.task_done()
if not ctx.rcon_client and "Starting RCON interface at IP ADDR:" in msg: 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: if not ctx.server:
logger.info("Established bridge to Factorio Server. " logger.info("Established bridge to Factorio Server. "
"Ready to connect to Archipelago via /connect") "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")) info = json.loads(rcon_client.send_command("/ap-rcon-info"))
ctx.auth = info["slot_name"] ctx.auth = info["slot_name"]
ctx.seed_name = info["seed_name"] ctx.seed_name = info["seed_name"]
# 0.2.0 addition, not present earlier death_link = info["death_link"]
death_link = bool(info.get("death_link", False))
ctx.energy_link_increment = info.get("energy_link", 0) ctx.energy_link_increment = info.get("energy_link", 0)
logger.debug(f"Energy Link Increment: {ctx.energy_link_increment}") logger.debug(f"Energy Link Increment: {ctx.energy_link_increment}")
if ctx.energy_link_increment and ctx.ui: if ctx.energy_link_increment and ctx.ui:

View File

@ -35,9 +35,11 @@ base_info = {
"author": "Berserker", "author": "Berserker",
"homepage": "https://archipelago.gg", "homepage": "https://archipelago.gg",
"description": "Integration client for the Archipelago Randomizer", "description": "Integration client for the Archipelago Randomizer",
"factorio_version": "1.1", "factorio_version": "2.0",
"dependencies": [ "dependencies": [
"base >= 1.1.0", "base >= 2.0.15",
"? quality >= 2.0.15",
"! space-age",
"? science-not-invited", "? science-not-invited",
"? factory-levels" "? 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(), "allowed_science_packs": world.options.max_science_pack.get_allowed_packs(),
"custom_technologies": world.custom_technologies, "custom_technologies": world.custom_technologies,
"tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites, "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, "slot_player": player,
"starting_items": world.options.starting_items, "recipes": recipes, "recipes": recipes,
"random": random, "flop_random": flop_random, "random": random,
"flop_random": flop_random,
"recipe_time_scale": recipe_time_scales.get(world.options.recipe_time.value, None), "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), "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_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": {tech.name: tech.progressive for tech in
progressive_technology_table.values()}, progressive_technology_table.values()},
"custom_recipes": world.custom_recipes, "custom_recipes": world.custom_recipes,
"max_science_pack": world.options.max_science_pack.value,
"liquids": fluids, "liquids": fluids,
"goal": world.options.goal.value, "removed_technologies": world.removed_technologies,
"energy_link": world.options.energy_link.value,
"useless_technologies": useless_technologies,
"chunk_shuffle": 0, "chunk_shuffle": 0,
} }

View File

@ -1,12 +1,11 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
import datetime
import typing import typing
from schema import Schema, Optional, And, Or 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 StartInventoryPool, PerGameCommonOptions
# schema helpers # schema helpers
@ -122,6 +121,18 @@ class FreeSamples(Choice):
default = 3 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): class TechTreeLayout(Choice):
"""Selects how the tech tree nodes are interwoven. """Selects how the tech tree nodes are interwoven.
Single: No dependencies Single: No dependencies
@ -284,17 +295,21 @@ class FactorioWorldGen(OptionDict):
# FIXME: do we want default be a rando-optimized default or in-game DS? # FIXME: do we want default be a rando-optimized default or in-game DS?
value: typing.Dict[str, typing.Dict[str, typing.Any]] value: typing.Dict[str, typing.Dict[str, typing.Any]]
default = { default = {
"terrain_segmentation": 0.5,
"water": 1.5,
"autoplace_controls": { "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}, "coal": {"frequency": 1, "size": 3, "richness": 6},
"copper-ore": {"frequency": 1, "size": 3, "richness": 6}, "copper-ore": {"frequency": 1, "size": 3, "richness": 6},
"crude-oil": {"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}, "iron-ore": {"frequency": 1, "size": 3, "richness": 6},
"stone": {"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}, "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, "seed": None,
"starting_area": 1, "starting_area": 1,
@ -336,8 +351,6 @@ class FactorioWorldGen(OptionDict):
} }
schema = Schema({ schema = Schema({
"basic": { "basic": {
Optional("terrain_segmentation"): FloatRange(0.166, 6),
Optional("water"): FloatRange(0.166, 6),
Optional("autoplace_controls"): { Optional("autoplace_controls"): {
str: { str: {
"frequency": FloatRange(0, 6), "frequency": FloatRange(0, 6),
@ -438,6 +451,7 @@ class FactorioOptions(PerGameCommonOptions):
silo: Silo silo: Silo
satellite: Satellite satellite: Satellite
free_samples: FreeSamples free_samples: FreeSamples
free_samples_quality: FreeSamplesQuality
tech_tree_information: TechTreeInformation tech_tree_information: TechTreeInformation
starting_items: FactorioStartItems starting_items: FactorioStartItems
free_sample_blacklist: FactorioFreeSampleBlacklist free_sample_blacklist: FactorioFreeSampleBlacklist

View File

@ -1,13 +1,13 @@
from __future__ import annotations from __future__ import annotations
import orjson import functools
import logging
import os
import string
import pkgutil import pkgutil
import string
from collections import Counter from collections import Counter
from concurrent.futures import ThreadPoolExecutor 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 import Utils
from . import Options from . import Options
@ -32,8 +32,23 @@ items_future = pool.submit(load_json_data, "items")
tech_table: Dict[str, int] = {} tech_table: Dict[str, int] = {}
technology_table: Dict[str, Technology] = {} 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 return True
@ -50,15 +65,13 @@ class FactorioElement:
class Technology(FactorioElement): # maybe make subclass of Location? class Technology(FactorioElement): # maybe make subclass of Location?
has_modifier: bool has_modifier: bool
factorio_id: int factorio_id: int
ingredients: Set[str]
progressive: Tuple[str] progressive: Tuple[str]
unlocks: Union[Set[str], bool] # bool case is for progressive technologies 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): has_modifier: bool = False, unlocks: Union[Set[str], bool] = None):
self.name = name self.name = technology_name
self.factorio_id = factorio_id self.factorio_id = factorio_id
self.ingredients = ingredients
self.progressive = progressive self.progressive = progressive
self.has_modifier = has_modifier self.has_modifier = has_modifier
if unlocks: if unlocks:
@ -66,19 +79,6 @@ class Technology(FactorioElement): # maybe make subclass of Location?
else: else:
self.unlocks = set() 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): def __hash__(self):
return self.factorio_id return self.factorio_id
@ -91,22 +91,22 @@ class Technology(FactorioElement): # maybe make subclass of Location?
class CustomTechnology(Technology): class CustomTechnology(Technology):
"""A particularly configured Technology for a world.""" """A particularly configured Technology for a world."""
ingredients: Set[str]
def __init__(self, origin: Technology, world, allowed_packs: Set[str], player: int): def __init__(self, origin: Technology, world, allowed_packs: Set[str], player: int):
ingredients = origin.ingredients & allowed_packs 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")
self.player = player self.player = player
if origin.name not in world.special_nodes: if origin.name not in world.special_nodes:
if military_allowed: ingredients = set(world.random.sample(list(ingredients), world.random.randint(1, len(ingredients))))
ingredients.add("military-science-pack") self.ingredients = ingredients
ingredients = list(ingredients) super(CustomTechnology, self).__init__(origin.name, origin.factorio_id)
ingredients.sort() # deterministic sample
ingredients = world.random.sample(ingredients, world.random.randint(1, len(ingredients))) def get_prior_technologies(self) -> Set[Technology]:
elif origin.name == "rocket-silo" and military_allowed: """Get Technologies that have to precede this one to resolve tree connections."""
ingredients.add("military-science-pack") technologies = set()
super(CustomTechnology, self).__init__(origin.name, ingredients, origin.factorio_id) for ingredient in self.ingredients:
technologies |= required_technologies[ingredient] # technologies that unlock the recipes
return technologies
class Recipe(FactorioElement): class Recipe(FactorioElement):
@ -149,19 +149,22 @@ class Recipe(FactorioElement):
ingredients = sum(self.ingredients.values()) ingredients = sum(self.ingredients.values())
return min(ingredients / amount for product, amount in self.products.items()) return min(ingredients / amount for product, amount in self.products.items())
@property @functools.cached_property
def base_cost(self) -> Dict[str, int]: def base_cost(self) -> Dict[str, int]:
ingredients = Counter() ingredients = Counter()
for ingredient, cost in self.ingredients.items(): try:
if ingredient in all_product_sources: for ingredient, cost in self.ingredients.items():
for recipe in all_product_sources[ingredient]: if ingredient in all_product_sources:
if recipe.ingredients: for recipe in all_product_sources[ingredient]:
ingredients.update({name: amount * cost / recipe.products[ingredient] for name, amount in if recipe.ingredients:
recipe.base_cost.items()}) ingredients.update({name: amount * cost / recipe.products[ingredient] for name, amount in
else: recipe.base_cost.items()})
ingredients[ingredient] += recipe.energy * cost / recipe.products[ingredient] else:
else: ingredients[ingredient] += recipe.energy * cost / recipe.products[ingredient]
ingredients[ingredient] += cost else:
ingredients[ingredient] += cost
except RecursionError as e:
raise Exception(f"Infinite recursion in ingredients of {self}.") from e
return ingredients return ingredients
@property @property
@ -191,9 +194,12 @@ recipe_sources: Dict[str, Set[str]] = {} # recipe_name -> technology source
# recipes and technologies can share names in Factorio # recipes and technologies can share names in Factorio
for technology_name, data in sorted(techs_future.result().items()): for technology_name, data in sorted(techs_future.result().items()):
current_ingredients = set(data["ingredients"]) technology = Technology(
technology = Technology(technology_name, current_ingredients, factorio_tech_id, technology_name,
has_modifier=data["has_modifier"], unlocks=set(data["unlocks"])) factorio_tech_id,
has_modifier=data["has_modifier"],
unlocks=set(data["unlocks"]) - start_unlocked_recipes,
)
factorio_tech_id += 1 factorio_tech_id += 1
tech_table[technology_name] = technology.factorio_id tech_table[technology_name] = technology.factorio_id
technology_table[technology_name] = technology technology_table[technology_name] = technology
@ -226,11 +232,12 @@ for recipe_name, recipe_data in raw_recipes.items():
recipes[recipe_name] = recipe recipes[recipe_name] = recipe
if set(recipe.products).isdisjoint( if set(recipe.products).isdisjoint(
# prevents loop recipes like uranium centrifuging # 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"): not recipe_name.endswith("-reprocessing"):
for product_name in recipe.products: for product_name in recipe.products:
all_product_sources.setdefault(product_name, set()).add(recipe) 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] = {} machines: Dict[str, Machine] = {}
@ -248,9 +255,7 @@ del machines_future
# build requirements graph for all technology ingredients # build requirements graph for all technology ingredients
all_ingredient_names: Set[str] = set() all_ingredient_names: Set[str] = set(Options.MaxSciencePack.get_ordered_science_packs())
for technology in technology_table.values():
all_ingredient_names |= technology.ingredients
def unlock_just_tech(recipe: Recipe, _done) -> Set[Technology]: 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))) 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() techs = set()
if silo_recipe: if silo_recipe:
for ingredient in silo_recipe.ingredients: for ingredient in silo_recipe.ingredients:
techs |= recursively_get_unlocking_technologies(ingredient) techs |= recursively_get_unlocking_technologies(ingredient)
for ingredient in part_recipe.ingredients: for ingredient in part_recipe.ingredients:
techs |= recursively_get_unlocking_technologies(ingredient) 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: if satellite_recipe:
techs |= satellite_recipe.unlocking_technologies techs |= satellite_recipe.unlocking_technologies
for ingredient in satellite_recipe.ingredients: for ingredient in satellite_recipe.ingredients:
@ -382,15 +391,15 @@ progressive_rows["progressive-processing"] = (
"uranium-processing", "kovarex-enrichment-process", "nuclear-fuel-reprocessing") "uranium-processing", "kovarex-enrichment-process", "nuclear-fuel-reprocessing")
progressive_rows["progressive-rocketry"] = ("rocketry", "explosive-rocketry", "atomic-bomb") progressive_rows["progressive-rocketry"] = ("rocketry", "explosive-rocketry", "atomic-bomb")
progressive_rows["progressive-vehicle"] = ("automobilism", "tank", "spidertron") progressive_rows["progressive-vehicle"] = ("automobilism", "tank", "spidertron")
progressive_rows["progressive-train-network"] = ("railway", "fluid-wagon", progressive_rows["progressive-fluid-handling"] = ("fluid-handling", "fluid-wagon")
"automated-rail-transportation", "rail-signals") progressive_rows["progressive-train-network"] = ("railway", "automated-rail-transportation")
progressive_rows["progressive-engine"] = ("engine", "electric-engine") progressive_rows["progressive-engine"] = ("engine", "electric-engine")
progressive_rows["progressive-armor"] = ("heavy-armor", "modular-armor", "power-armor", "power-armor-mk2") 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-personal-battery"] = ("battery-equipment", "battery-mk2-equipment")
progressive_rows["progressive-energy-shield"] = ("energy-shield-equipment", "energy-shield-mk2-equipment") progressive_rows["progressive-energy-shield"] = ("energy-shield-equipment", "energy-shield-mk2-equipment")
progressive_rows["progressive-wall"] = ("stone-wall", "gate") progressive_rows["progressive-wall"] = ("stone-wall", "gate")
progressive_rows["progressive-follower"] = ("defender", "distractor", "destroyer") 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-turret"] = ("gun-turret", "laser-turret")
progressive_rows["progressive-flamethrower"] = ("flamethrower",) # leaving out flammables, as they do nothing progressive_rows["progressive-flamethrower"] = ("flamethrower",) # leaving out flammables, as they do nothing
progressive_rows["progressive-personal-roboport-equipment"] = ("personal-roboport-equipment", progressive_rows["progressive-personal-roboport-equipment"] = ("personal-roboport-equipment",
@ -402,7 +411,7 @@ sorted_rows = sorted(progressive_rows)
source_target_mapping: Dict[str, str] = { source_target_mapping: Dict[str, str] = {
"progressive-braking-force": "progressive-train-network", "progressive-braking-force": "progressive-train-network",
"progressive-inserter-capacity-bonus": "progressive-inserter", "progressive-inserter-capacity-bonus": "progressive-inserter",
"progressive-refined-flammables": "progressive-flamethrower" "progressive-refined-flammables": "progressive-flamethrower",
} }
for source, target in source_target_mapping.items(): for source, target in source_target_mapping.items():
@ -416,12 +425,14 @@ progressive_technology_table: Dict[str, Technology] = {}
for root in sorted_rows: for root in sorted_rows:
progressive = progressive_rows[root] 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 factorio_tech_id += 1
progressive_technology = Technology(root, technology_table[progressive[0]].ingredients, factorio_tech_id, progressive_technology = Technology(root, factorio_tech_id,
progressive, tuple(progressive),
has_modifier=any(technology_table[tech].has_modifier for tech in 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_tech_table[root] = progressive_technology.factorio_id
progressive_technology_table[root] = progressive_technology progressive_technology_table[root] = progressive_technology

View File

@ -2,10 +2,11 @@ from __future__ import annotations
import collections import collections
import logging import logging
import settings
import typing 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.AutoWorld import World, WebWorld
from worlds.LauncherComponents import Component, components, Type, launch_subprocess from worlds.LauncherComponents import Component, components, Type, launch_subprocess
from worlds.generic import Rules 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 .Options import FactorioOptions, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution
from .Shapes import get_shapes from .Shapes import get_shapes
from .Technologies import base_tech_table, recipe_sources, base_technology_table, \ 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, \ 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, \ get_science_pack_pools, Recipe, recipes, technology_table, tech_table, factorio_base_id, useless_technologies, \
fluids, stacking_items, valid_ingredients, progressive_rows fluids, stacking_items, valid_ingredients, progressive_rows
@ -97,19 +98,21 @@ class Factorio(World):
item_name_groups = { item_name_groups = {
"Progressive": set(progressive_tech_table.keys()), "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() ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()
tech_tree_layout_prerequisites: typing.Dict[FactorioScienceLocation, typing.Set[FactorioScienceLocation]] tech_tree_layout_prerequisites: typing.Dict[FactorioScienceLocation, typing.Set[FactorioScienceLocation]]
tech_mix: int = 0 tech_mix: int = 0
skip_silo: bool = False skip_silo: bool = False
origin_region_name = "Nauvis" origin_region_name = "Nauvis"
science_locations: typing.List[FactorioScienceLocation] science_locations: typing.List[FactorioScienceLocation]
removed_technologies: typing.Set[str]
settings: typing.ClassVar[FactorioSettings] settings: typing.ClassVar[FactorioSettings]
def __init__(self, world, player: int): def __init__(self, world, player: int):
super(Factorio, self).__init__(world, player) super(Factorio, self).__init__(world, player)
self.removed_technologies = useless_technologies.copy()
self.advancement_technologies = set() self.advancement_technologies = set()
self.custom_recipes = {} self.custom_recipes = {}
self.science_locations = [] self.science_locations = []
@ -208,11 +211,9 @@ class Factorio(World):
for loc in self.science_locations: for loc in self.science_locations:
loc.revealed = True loc.revealed = True
if self.skip_silo: if self.skip_silo:
removed = useless_technologies | {"rocket-silo"} self.removed_technologies |= {"rocket-silo"}
else:
removed = useless_technologies
for tech_name in base_tech_table: 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) progressive_item_name = tech_to_progressive_lookup.get(tech_name, tech_name)
want_progressive = want_progressives[progressive_item_name] want_progressive = want_progressives[progressive_item_name]
item_name = progressive_item_name if want_progressive else tech_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] custom_recipe = self.custom_recipes[ingredient]
location.access_rule = lambda state, ingredient=ingredient, custom_recipe=custom_recipe: \ 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 all(state.has(technology.name, player) for sub_ingredient in custom_recipe.ingredients
for technology in required_technologies[sub_ingredient]) and \ for technology in required_technologies[sub_ingredient]) and \
all(state.has(technology.name, player) for technology in required_technologies[custom_recipe.crafting_machine]) all(state.has(technology.name, player) for technology in required_technologies[custom_recipe.crafting_machine])
else: else:
location.access_rule = lambda state, ingredient=ingredient: \ location.access_rule = lambda state, ingredient=ingredient: \
all(state.has(technology.name, player) for technology in required_technologies[ingredient]) all(state.has(technology.name, player) for technology in required_technologies[ingredient])
for location in self.science_locations: 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)) all(state.has(f"Automated {ingredient}", player) for ingredient in ingredients))
prerequisites = shapes.get(location) prerequisites = shapes.get(location)
if prerequisites: if prerequisites:
Rules.add_rule(location, lambda state, locations= Rules.add_rule(location, lambda state, locations=frozenset(prerequisites):
prerequisites: all(state.can_reach(loc) for loc in locations)) all(state.can_reach(loc) for loc in locations))
silo_recipe = None silo_recipe = None
cargo_pad_recipe = None
if self.options.silo == Silo.option_spawn: if self.options.silo == Silo.option_spawn:
silo_recipe = self.custom_recipes["rocket-silo"] if "rocket-silo" in self.custom_recipes \ silo_recipe = self.get_recipe("rocket-silo")
else next(iter(all_product_sources.get("rocket-silo"))) cargo_pad_recipe = self.get_recipe("cargo-landing-pad")
part_recipe = self.custom_recipes["rocket-part"] part_recipe = self.custom_recipes["rocket-part"]
satellite_recipe = None satellite_recipe = None
if self.options.goal == Goal.option_satellite: if self.options.goal == Goal.option_satellite:
satellite_recipe = self.custom_recipes["satellite"] if "satellite" in self.custom_recipes \ satellite_recipe = self.get_recipe("satellite")
else next(iter(all_product_sources.get("satellite"))) victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe, cargo_pad_recipe)
victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe) if self.options.silo == Silo.option_spawn:
if self.options.silo != Silo.option_spawn: victory_tech_names -= {"rocket-silo"}
victory_tech_names.add("rocket-silo") else:
victory_tech_names |= {"rocket-silo"}
self.get_location("Rocket Launch").access_rule = lambda state: all(state.has(technology, player) self.get_location("Rocket Launch").access_rule = lambda state: all(state.has(technology, player)
for technology in for technology in
victory_tech_names) 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) 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): def generate_basic(self):
map_basic_settings = self.options.world_gen.value["basic"] map_basic_settings = self.options.world_gen.value["basic"]
if map_basic_settings.get("seed", None) is None: # allow seed 0 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, def make_quick_recipe(self, original: Recipe, pool: list, allow_liquids: int = 2,
ingredients_offset: int = 0) -> Recipe: 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 = {} new_ingredients = {}
liquids_used = 0 liquids_used = 0
for _ in range(len(original.ingredients) + ingredients_offset): for _ in range(count):
new_ingredient = pool.pop() new_ingredient = pool.pop()
if new_ingredient in fluids: if new_ingredient in fluids:
while liquids_used == allow_liquids and 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 ingredients_offset = self.options.recipe_ingredients_offset
original_rocket_part = recipes["rocket-part"] original_rocket_part = recipes["rocket-part"]
science_pack_pools = get_science_pack_pools() 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.random.shuffle(valid_pool)
self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category, self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category,
{valid_pool[x]: 10 for x in range(3 + ingredients_offset)}, {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"} needed_recipes = self.options.max_science_pack.get_allowed_packs() | {"rocket-part"}
if self.options.silo != Silo.option_spawn: 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: if self.options.goal.value == Goal.option_satellite:
needed_recipes |= {"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) function get_any_stack_size(name)
local item = game.item_prototypes[name] local item = prototypes.item[name]
if item ~= nil then if item ~= nil then
return item.stack_size return item.stack_size
end end
item = game.equipment_prototypes[name] item = prototypes.equipment[name]
if item ~= nil then if item ~= nil then
return item.stack_size return item.stack_size
end end
@ -24,7 +24,7 @@ function split(s, sep)
end end
function random_offset_position(position, offset) 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 end
function fire_entity_at_players(entity_name, speed) 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} target=current_character, speed=speed}
end end
end end
end end

View File

@ -143,24 +143,24 @@ function on_check_energy_link(event)
local force = "player" local force = "player"
local bridges = surface.find_entities_filtered({name="ap-energy-bridge", force=force}) local bridges = surface.find_entities_filtered({name="ap-energy-bridge", force=force})
local bridgecount = table_size(bridges) local bridgecount = table_size(bridges)
global.forcedata[force].energy_bridges = bridgecount storage.forcedata[force].energy_bridges = bridgecount
if global.forcedata[force].energy == nil then if storage.forcedata[force].energy == nil then
global.forcedata[force].energy = 0 storage.forcedata[force].energy = 0
end 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 for i, bridge in ipairs(bridges) do
if bridge.energy > ENERGY_INCREMENT*3 then 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 bridge.energy = bridge.energy - ENERGY_INCREMENT
end end
end end
end end
for i, bridge in ipairs(bridges) do for i, bridge in ipairs(bridges) do
if global.forcedata[force].energy < ENERGY_INCREMENT then if storage.forcedata[force].energy < ENERGY_INCREMENT then
break break
end end
if bridge.energy < ENERGY_INCREMENT*2 and global.forcedata[force].energy > ENERGY_INCREMENT then if bridge.energy < ENERGY_INCREMENT*2 and storage.forcedata[force].energy > ENERGY_INCREMENT then
global.forcedata[force].energy = global.forcedata[force].energy - ENERGY_INCREMENT storage.forcedata[force].energy = storage.forcedata[force].energy - ENERGY_INCREMENT
bridge.energy = bridge.energy + ENERGY_INCREMENT bridge.energy = bridge.energy + ENERGY_INCREMENT
end end
end end
@ -186,23 +186,41 @@ function check_spawn_silo(force)
local surface = game.get_surface(1) local surface = game.get_surface(1)
local spawn_position = force.get_spawn_position(surface) 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, "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
end end
function check_despawn_silo(force) function check_despawn_silo(force)
if not force.players or #force.players < 1 and force.get_entity_count("rocket-silo") > 0 then if not force.players or #force.players < 1 then
local surface = game.get_surface(1) if force.get_entity_count("rocket-silo") > 0 then
local spawn_position = force.get_spawn_position(surface) local surface = game.get_surface(1)
local x1 = spawn_position.x - 41 local spawn_position = force.get_spawn_position(surface)
local x2 = spawn_position.x + 41 local x1 = spawn_position.x - 41
local y1 = spawn_position.y - 41 local x2 = spawn_position.x + 41
local y2 = spawn_position.y + 41 local y1 = spawn_position.y - 41
local silos = surface.find_entities_filtered{area = { {x1, y1}, {x2, y2} }, local y2 = spawn_position.y + 41
name = "rocket-silo", local silos = surface.find_entities_filtered{area = { {x1, y1}, {x2, y2} },
force = force} name = "rocket-silo",
for i,silo in ipairs(silos) do force = force}
silo.destructible = true for i, silo in ipairs(silos) do
silo.destroy() 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 end
end end
@ -214,19 +232,18 @@ function on_force_created(event)
if type(event.force) == "string" then -- should be of type LuaForce if type(event.force) == "string" then -- should be of type LuaForce
force = game.forces[force] force = game.forces[force]
end end
force.research_queue_enabled = true
local data = {} local data = {}
data['earned_samples'] = {{ dict_to_lua(starting_items) }} data['earned_samples'] = {{ dict_to_lua(starting_items) }}
data["victory"] = 0 data["victory"] = 0
data["death_link_tick"] = 0 data["death_link_tick"] = 0
data["energy"] = 0 data["energy"] = 0
data["energy_bridges"] = 0 data["energy_bridges"] = 0
global.forcedata[event.force] = data storage.forcedata[event.force] = data
{%- if silo == 2 %} {%- if silo == 2 %}
check_spawn_silo(force) check_spawn_silo(force)
{%- endif %} {%- endif %}
{%- for tech_name in useless_technologies %} {%- for tech_name in removed_technologies %}
force.technologies.{{ tech_name }}.researched = true force.technologies["{{ tech_name }}"].researched = true
{%- endfor %} {%- endfor %}
end end
script.on_event(defines.events.on_force_created, on_force_created) script.on_event(defines.events.on_force_created, on_force_created)
@ -236,7 +253,7 @@ function on_force_destroyed(event)
{%- if silo == 2 %} {%- if silo == 2 %}
check_despawn_silo(event.force) check_despawn_silo(event.force)
{%- endif %} {%- endif %}
global.forcedata[event.force.name] = nil storage.forcedata[event.force.name] = nil
end end
function on_runtime_mod_setting_changed(event) 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 -- 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. -- For now, they will (probably) always be on the 'player' force when this event fires.
local data = {} local data = {}
data['pending_samples'] = table.deepcopy(global.forcedata[player.force.name]['earned_samples']) data['pending_samples'] = table.deepcopy(storage.forcedata[player.force.name]['earned_samples'])
global.playerdata[player.index] = data storage.playerdata[player.index] = data
update_player(player.index) -- Attempt to send pending free samples, if relevant. update_player(player.index) -- Attempt to send pending free samples, if relevant.
{%- if silo == 2 %} {%- if silo == 2 %}
check_spawn_silo(game.players[event.player_index].force) 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) script.on_event(defines.events.on_player_changed_force, on_player_changed_force)
function on_player_removed(event) function on_player_removed(event)
global.playerdata[event.player_index] = nil storage.playerdata[event.player_index] = nil
end end
script.on_event(defines.events.on_player_removed, on_player_removed) script.on_event(defines.events.on_player_removed, on_player_removed)
function on_rocket_launched(event) 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 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) dumpInfo(event.rocket.force)
game.set_game_state game.set_game_state
{ {
@ -318,7 +335,7 @@ function update_player(index)
if not character or not character.valid then if not character or not character.valid then
return return
end end
local data = global.playerdata[index] local data = storage.playerdata[index]
local samples = data['pending_samples'] local samples = data['pending_samples']
local sent local sent
--player.print(serpent.block(data['pending_samples'])) --player.print(serpent.block(data['pending_samples']))
@ -327,14 +344,17 @@ function update_player(index)
for name, count in pairs(samples) do for name, count in pairs(samples) do
stack.name = name stack.name = name
stack.count = count 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 if character.can_insert(stack) then
sent = character.insert(stack) sent = character.insert(stack)
else else
sent = 0 sent = 0
end end
if sent > 0 then 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 data.suppress_full_inventory_message = false
end end
if sent ~= count then -- Couldn't full send. if sent ~= count then -- Couldn't full send.
@ -372,19 +392,19 @@ function add_samples(force, name, count)
end end
t[name] = (t[name] or 0) + count t[name] = (t[name] or 0) + count
end end
-- Add to global table of earned samples for future new players -- Add to storage table of earned samples for future new players
add_to_table(global.forcedata[force.name]['earned_samples']) add_to_table(storage.forcedata[force.name]['earned_samples'])
-- Add to existing players -- Add to existing players
for _, player in pairs(force.players) do 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) update_player(player.index)
end end
end end
script.on_init(function() script.on_init(function()
{% if not imported_blueprints %}set_permissions(){% endif %} {% if not imported_blueprints %}set_permissions(){% endif %}
global.forcedata = {} storage.forcedata = {}
global.playerdata = {} storage.playerdata = {}
-- Fire dummy events for all currently existing forces. -- Fire dummy events for all currently existing forces.
local e = {} local e = {}
for name, _ in pairs(game.forces) do 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 if FREE_SAMPLES == 0 then
return -- Nothing else to do return -- Nothing else to do
end end
if not technology.effects then if not technology.prototype.effects then
return -- No technology effects, so nothing to do. return -- No technology effects, so nothing to do.
end end
for _, effect in pairs(technology.effects) do for _, effect in pairs(technology.prototype.effects) do
if effect.type == "unlock-recipe" then 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 for _, result in pairs(recipe.products) do
if result.type == "item" and result.amount then if result.type == "item" and result.amount then
local name = result.name local name = result.name
@ -477,7 +497,7 @@ function kill_players(force)
end end
function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores) 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 local args = { -- For can_place_entity and place_entity
name = prototype.name, name = prototype.name,
position = {x = x, y = y}, 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 { local entities = surface.find_entities_filtered {
area = collision_area, area = collision_area,
collision_mask = prototype.collision_mask collision_mask = prototype.collision_mask.layers
} }
local can_place = true local can_place = true
for _, entity in pairs(entities) do for _, entity in pairs(entities) do
@ -560,6 +580,9 @@ function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores)
end end
args.build_check_type = defines.build_check_type.script args.build_check_type = defines.build_check_type.script
args.create_build_effect_smoke = false 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) new_entity = surface.create_entity(args)
if new_entity then if new_entity then
new_entity.destructible = false new_entity.destructible = false
@ -585,7 +608,7 @@ script.on_event(defines.events.on_entity_died, function(event)
end end
local force = event.entity.force 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) dumpInfo(force)
kill_players(force) kill_players(force)
end, {LuaEntityDiedEventFilter = {["filter"] = "name", ["name"] = "character"}}) 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 force = game.players[call.player_index].force
end end
local research_done = {} local research_done = {}
local forcedata = chain_lookup(global, "forcedata", force.name) local forcedata = chain_lookup(storage, "forcedata", force.name)
local data_collection = { local data_collection = {
["research_done"] = research_done, ["research_done"] = research_done,
["victory"] = chain_lookup(forcedata, "victory"), ["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 research_done[tech_name] = tech.researched
end end
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) end)
commands.add_command("ap-print", "Used by the Archipelago client to print messages", function (call) 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) commands.add_command("ap-get-technology", "Grant a technology, used by the Archipelago Client.", function(call)
if global.index_sync == nil then if storage.index_sync == nil then
global.index_sync = {} storage.index_sync = {}
end end
local tech local tech
local force = game.forces["player"] local force = game.forces["player"]
@ -680,8 +703,8 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi
end end
return return
elseif progressive_technologies[item_name] ~= nil then elseif progressive_technologies[item_name] ~= nil then
if global.index_sync[index] ~= item_name then -- not yet received prog item if storage.index_sync[index] ~= item_name then -- not yet received prog item
global.index_sync[index] = item_name storage.index_sync[index] = item_name
local tech_stack = progressive_technologies[item_name] local tech_stack = progressive_technologies[item_name]
for _, item_name in ipairs(tech_stack) do for _, item_name in ipairs(tech_stack) do
tech = force.technologies[item_name] 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 elseif force.technologies[item_name] ~= nil then
tech = force.technologies[item_name] tech = force.technologies[item_name]
if tech ~= nil then if tech ~= nil then
global.index_sync[index] = tech storage.index_sync[index] = tech
if tech.researched ~= true then if tech.researched ~= true then
game.print({"", "Received [technology=" .. tech.name .. "] from ", source}) game.print({"", "Received [technology=" .. tech.name .. "] from ", source})
game.play_sound({path="utility/research_completed"}) 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
end end
elseif TRAP_TABLE[item_name] ~= nil then elseif TRAP_TABLE[item_name] ~= nil then
if global.index_sync[index] ~= item_name then -- not yet received trap if storage.index_sync[index] ~= item_name then -- not yet received trap
global.index_sync[index] = item_name storage.index_sync[index] = item_name
game.print({"", "Received ", item_name, " from ", source}) game.print({"", "Received ", item_name, " from ", source})
TRAP_TABLE[item_name]() TRAP_TABLE[item_name]()
end end
@ -716,7 +739,7 @@ end)
commands.add_command("ap-rcon-info", "Used by the Archipelago client to get information", function(call) 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, ["slot_name"] = SLOT_NAME,
["seed_name"] = SEED_NAME, ["seed_name"] = SEED_NAME,
["death_link"] = DEATH_LINK, ["death_link"] = DEATH_LINK,
@ -726,7 +749,7 @@ end)
{% if allow_cheats -%} {% 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) spawn_entity(game.player.surface, game.player.force, "rocket-silo", 0, 0, 80, true, true)
end) end)
{% endif -%} {% endif -%}
@ -742,7 +765,7 @@ end)
commands.add_command("ap-energylink", "Used by the Archipelago client to manage Energy Link", function(call) commands.add_command("ap-energylink", "Used by the Archipelago client to manage Energy Link", function(call)
local change = tonumber(call.parameter or "0") local change = tonumber(call.parameter or "0")
local force = "player" local force = "player"
global.forcedata[force].energy = global.forcedata[force].energy + change storage.forcedata[force].energy = storage.forcedata[force].energy + change
end) end)
commands.add_command("energy-link", "Print the status of the Archipelago energy link.", function(call) 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", production_type = "input",
pipe_picture = assembler2pipepictures(), pipe_picture = assembler2pipepictures(),
pipe_covers = pipecoverspictures(), pipe_covers = pipecoverspictures(),
volume = 1000,
base_area = 10, base_area = 10,
base_level = -1, base_level = -1,
pipe_connections = { pipe_connections = {
{ type = "input", position = { 0, 5 } }, { flow_direction = "input", direction = defines.direction.south, position = { 0, 4.2 } },
{ type = "input", position = { 0, -5 } }, { flow_direction = "input", direction = defines.direction.north, position = { 0, -4.2 } },
{ type = "input", position = { 5, 0 } }, { flow_direction = "input", direction = defines.direction.east, position = { 4.2, 0 } },
{ type = "input", position = { -5, 0 } } { flow_direction = "input", direction = defines.direction.west, position = { -4.2, 0 } }
} }
}, },
{ {
production_type = "input", production_type = "input",
pipe_picture = assembler2pipepictures(), pipe_picture = assembler2pipepictures(),
pipe_covers = pipecoverspictures(), pipe_covers = pipecoverspictures(),
volume = 1000,
base_area = 10, base_area = 10,
base_level = -1, base_level = -1,
pipe_connections = { pipe_connections = {
{ type = "input", position = { -3, 5 } }, { flow_direction = "input", direction = defines.direction.south, position = { -3, 4.2 } },
{ type = "input", position = { -3, -5 } }, { flow_direction = "input", direction = defines.direction.north, position = { -3, -4.2 } },
{ type = "input", position = { 5, -3 } }, { flow_direction = "input", direction = defines.direction.east, position = { 4.2, -3 } },
{ type = "input", position = { -5, -3 } } { flow_direction = "input", direction = defines.direction.west, position = { -4.2, -3 } }
} }
}, },
{ {
production_type = "input", production_type = "input",
pipe_picture = assembler2pipepictures(), pipe_picture = assembler2pipepictures(),
pipe_covers = pipecoverspictures(), pipe_covers = pipecoverspictures(),
volume = 1000,
base_area = 10, base_area = 10,
base_level = -1, base_level = -1,
pipe_connections = { pipe_connections = {
{ type = "input", position = { 3, 5 } }, { flow_direction = "input", direction = defines.direction.south, position = { 3, 4.2 } },
{ type = "input", position = { 3, -5 } }, { flow_direction = "input", direction = defines.direction.north, position = { 3, -4.2 } },
{ type = "input", position = { 5, 3 } }, { flow_direction = "input", direction = defines.direction.east, position = { 4.2, 3 } },
{ type = "input", position = { -5, 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() %} {%- for recipe_name, recipe in custom_recipes.items() %}
data.raw["recipe"]["{{recipe_name}}"].category = "{{recipe.category}}" 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.input_flow_limit = "10MW"
energy_bridge.energy_source.output_flow_limit = "10MW" energy_bridge.energy_source.output_flow_limit = "10MW"
tint_icon(energy_bridge, energy_bridge_tint()) tint_icon(energy_bridge, energy_bridge_tint())
energy_bridge.picture.layers[1].tint = energy_bridge_tint() energy_bridge.chargable_graphics.picture.layers[1].tint = energy_bridge_tint()
energy_bridge.picture.layers[1].hr_version.tint = energy_bridge_tint() energy_bridge.chargable_graphics.charge_animation.layers[1].layers[1].tint = energy_bridge_tint()
energy_bridge.charge_animation.layers[1].layers[1].tint = energy_bridge_tint() energy_bridge.chargable_graphics.discharge_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()
data.raw["accumulator"]["ap-energy-bridge"] = energy_bridge data.raw["accumulator"]["ap-energy-bridge"] = energy_bridge
local energy_bridge_item = table.deepcopy(data.raw["item"]["accumulator"]) 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"]) local energy_bridge_recipe = table.deepcopy(data.raw["recipe"]["accumulator"])
energy_bridge_recipe.name = "ap-energy-bridge" 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.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" energy_bridge_recipe.localised_name = "Archipelago EnergyLink Bridge"
data.raw["recipe"]["ap-energy-bridge"] = energy_bridge_recipe 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 %} {type = {% if key in liquids %}"fluid"{% else %}"item"{% endif %}, name = "{{ key }}", amount = {{ value | safe }}}{% if not loop.last %},{% endif %}
{% endfor -%} {% endfor -%}
} }
{%- endmacro %} {%- endmacro %}

View File

@ -27,4 +27,4 @@ data:extend({
default_value = false default_value = false
{% endif %} {% 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