Factorio:

add visibility option
fix tech_cost using the wrong variable name
fix yaml defaults not init'ing the Option class
LttP:
fix potential pathing confusion in maseya palette shuffler
Server:
Minimum version per team made no sense, removed
This commit is contained in:
Fabian Dill 2021-04-08 19:53:24 +02:00
parent 443fc03700
commit f0a6b5a8e4
14 changed files with 80 additions and 180 deletions

View File

@ -316,7 +316,7 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
logger.info(f' Team #{network_player.team + 1}') logger.info(f' Team #{network_player.team + 1}')
current_team = network_player.team current_team = network_player.team
logger.info(' %s (Player %d)' % (network_player.alias, network_player.slot)) logger.info(' %s (Player %d)' % (network_player.alias, network_player.slot))
if args["datapackage_version"] > network_data_package["version"]: if args["datapackage_version"] > network_data_package["version"] or args["datapackage_version"] == 0:
await ctx.send_msgs([{"cmd": "GetDataPackage"}]) await ctx.send_msgs([{"cmd": "GetDataPackage"}])
await ctx.server_auth(args['password']) await ctx.server_auth(args['password'])

160
Main.py
View File

@ -10,8 +10,7 @@ import pickle
from typing import Dict from typing import Dict
from BaseClasses import MultiWorld, CollectionState, Region, Item from BaseClasses import MultiWorld, CollectionState, Region, Item
from worlds.alttp import ALttPLocation from worlds.alttp.Items import ItemFactory, item_name_groups
from worlds.alttp.Items import ItemFactory, item_table, item_name_groups
from worlds.alttp.Regions import create_regions, mark_light_world_regions, \ from worlds.alttp.Regions import create_regions, mark_light_world_regions, \
lookup_vanilla_location_to_entrance lookup_vanilla_location_to_entrance
from worlds.alttp.InvertedRegions import create_inverted_regions, mark_dark_world_regions from worlds.alttp.InvertedRegions import create_inverted_regions, mark_dark_world_regions
@ -23,7 +22,7 @@ from Fill import distribute_items_restrictive, flood_items, balance_multiworld_p
from worlds.alttp.Shops import create_shops, ShopSlotFill, SHOP_ID_START, total_shop_slots, FillDisabledShopSlots from worlds.alttp.Shops import create_shops, ShopSlotFill, SHOP_ID_START, total_shop_slots, FillDisabledShopSlots
from worlds.alttp.ItemPool import generate_itempool, difficulties, fill_prizes from worlds.alttp.ItemPool import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple
from worlds.hk import gen_hollow, set_rules as set_hk_rules from worlds.hk import gen_hollow
from worlds.hk import create_regions as hk_create_regions from worlds.hk import create_regions as hk_create_regions
from worlds.factorio import gen_factorio, factorio_create_regions from worlds.factorio import gen_factorio, factorio_create_regions
from worlds.factorio.Mod import generate_mod from worlds.factorio.Mod import generate_mod
@ -492,7 +491,10 @@ def main(args, seed=None):
for future in roms: for future in roms:
rom_name = future.result() rom_name = future.result()
rom_names.append(rom_name) rom_names.append(rom_name)
minimum_versions = {"server": (0, 0, 2)} client_versions = {}
minimum_versions = {"server": (0, 0, 3), "clients": client_versions}
for slot in world.player_ids:
client_versions[slot] = (0, 0, 3)
connect_names = {base64.b64encode(rom_name).decode(): (team, slot) for connect_names = {base64.b64encode(rom_name).decode(): (team, slot) for
slot, team, rom_name in rom_names} slot, team, rom_name in rom_names}
@ -500,6 +502,7 @@ def main(args, seed=None):
for player, name in enumerate(team, 1): for player, name in enumerate(team, 1):
if player not in world.alttp_player_ids: if player not in world.alttp_player_ids:
connect_names[name] = (i, player) connect_names[name] = (i, player)
multidata = zlib.compress(pickle.dumps({"names": parsed_names, multidata = zlib.compress(pickle.dumps({"names": parsed_names,
"connect_names": connect_names, "connect_names": connect_names,
"remote_items": {player for player in range(1, world.players + 1) if "remote_items": {player for player in range(1, world.players + 1) if
@ -544,155 +547,8 @@ def main(args, seed=None):
return world return world
def copy_world(world):
# ToDo: Not good yet
# delete now?
ret = MultiWorld(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.item_functionality, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints)
ret.teams = world.teams
ret.player_names = copy.deepcopy(world.player_names)
ret.remote_items = world.remote_items.copy()
ret.required_medallions = world.required_medallions.copy()
ret.swamp_patch_required = world.swamp_patch_required.copy()
ret.ganon_at_pyramid = world.ganon_at_pyramid.copy()
ret.powder_patch_required = world.powder_patch_required.copy()
ret.ganonstower_vanilla = world.ganonstower_vanilla.copy()
ret.treasure_hunt_count = world.treasure_hunt_count.copy()
ret.treasure_hunt_icon = world.treasure_hunt_icon.copy()
ret.sewer_light_cone = world.sewer_light_cone.copy()
ret.light_world_light_cone = world.light_world_light_cone
ret.dark_world_light_cone = world.dark_world_light_cone
ret.seed = world.seed
ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge.copy()
ret.can_access_trock_front = world.can_access_trock_front.copy()
ret.can_access_trock_big_chest = world.can_access_trock_big_chest.copy()
ret.can_access_trock_middle = world.can_access_trock_middle.copy()
ret.can_take_damage = world.can_take_damage
ret.difficulty_requirements = world.difficulty_requirements.copy()
ret.fix_fake_world = world.fix_fake_world.copy()
ret.mapshuffle = world.mapshuffle.copy()
ret.compassshuffle = world.compassshuffle.copy()
ret.keyshuffle = world.keyshuffle.copy()
ret.bigkeyshuffle = world.bigkeyshuffle.copy()
ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy()
ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy()
ret.open_pyramid = world.open_pyramid.copy()
ret.boss_shuffle = world.boss_shuffle.copy()
ret.enemy_shuffle = world.enemy_shuffle.copy()
ret.enemy_health = world.enemy_health.copy()
ret.enemy_damage = world.enemy_damage.copy()
ret.beemizer = world.beemizer.copy()
ret.timer = world.timer.copy()
ret.shufflepots = world.shufflepots.copy()
ret.shuffle_prizes = world.shuffle_prizes.copy()
ret.shop_shuffle = world.shop_shuffle.copy()
ret.shop_shuffle_slots = world.shop_shuffle_slots.copy()
ret.dark_room_logic = world.dark_room_logic.copy()
ret.restrict_dungeon_item_on_boss = world.restrict_dungeon_item_on_boss.copy()
ret.game = world.game.copy()
ret.completion_condition = world.completion_condition.copy()
for player in world.alttp_player_ids:
if world.mode[player] != 'inverted':
create_regions(ret, player)
else:
create_inverted_regions(ret, player)
create_shops(ret, player)
create_dungeons(ret, player)
for player in world.hk_player_ids:
hk_create_regions(ret, player)
copy_dynamic_regions_and_locations(world, ret)
# copy bosses
for dungeon in world.dungeons:
for level, boss in dungeon.bosses.items():
ret.get_dungeon(dungeon.name, dungeon.player).bosses[level] = boss
for shop in world.shops:
copied_shop = ret.get_region(shop.region.name, shop.region.player).shop
copied_shop.inventory = copy.copy(shop.inventory)
# connect copied world
for region in world.regions:
copied_region = ret.get_region(region.name, region.player)
copied_region.is_light_world = region.is_light_world
copied_region.is_dark_world = region.is_dark_world
for exit in copied_region.exits:
old_connection = world.get_entrance(exit.name, exit.player).connected_region
exit.connect(ret.get_region(old_connection.name, old_connection.player))
# fill locations
for location in world.get_locations():
if location.item is not None:
item = Item(location.item.name, location.item.advancement, location.item.code, player = location.item.player)
ret.get_location(location.name, location.player).item = item
item.location = ret.get_location(location.name, location.player)
item.world = ret
item.type = location.item.type
item.game = location.item.game
if location.event:
ret.get_location(location.name, location.player).event = True
if location.locked:
ret.get_location(location.name, location.player).locked = True
# copy remaining itempool. No item in itempool should have an assigned location
for old_item in world.itempool:
item = Item(old_item.name, old_item.advancement, old_item.code, player = old_item.player)
item.type = old_item.type
ret.itempool.append(item)
for old_item in world.precollected_items:
item = Item(old_item.name, old_item.advancement, old_item.code, player = old_item.player)
item.type = old_item.type
ret.push_precollected(item)
# copy progress items in state
ret.state.prog_items = world.state.prog_items.copy()
ret.state.stale = {player: True for player in range(1, world.players + 1)}
for player in world.alttp_player_ids:
set_rules(ret, player)
for player in world.hk_player_ids:
set_hk_rules(ret, player)
return ret
def copy_dynamic_regions_and_locations(world, ret):
for region in world.dynamic_regions:
new_reg = Region(region.name, region.type, region.hint_text, region.player)
ret.regions.append(new_reg)
ret.initialize_regions([new_reg])
ret.dynamic_regions.append(new_reg)
# Note: ideally exits should be copied here, but the current use case (Take anys) do not require this
if region.shop:
new_reg.shop = region.shop.__class__(new_reg, region.shop.room_id, region.shop.shopkeeper_config,
region.shop.custom, region.shop.locked, region.shop.sram_offset)
ret.shops.append(new_reg.shop)
for location in world.dynamic_locations:
new_reg = ret.get_region(location.parent_region.name, location.parent_region.player)
new_loc = ALttPLocation(location.player, location.name, location.address, location.crystal, location.hint_text, new_reg)
# todo: this is potentially dangerous. later refactor so we
# can apply dynamic region rules on top of copied world like other rules
new_loc.access_rule = location.access_rule
new_loc.always_allow = location.always_allow
new_loc.item_rule = location.item_rule
new_reg.locations.append(new_loc)
ret.clear_location_cache()
def create_playthrough(world): def create_playthrough(world):
"""Destructive to the world it is run on.""" """Destructive to the world while it is run, damage gets repaired afterwards."""
# get locations containing progress items # get locations containing progress items
prog_locations = {location for location in world.get_filled_locations() if location.item.advancement} prog_locations = {location for location in world.get_filled_locations() if location.item.advancement}
state_cache = [None] state_cache = [None]

View File

@ -110,7 +110,7 @@ class Context(Node):
self.auto_saver_thread = None self.auto_saver_thread = None
self.save_dirty = False self.save_dirty = False
self.tags = ['AP'] self.tags = ['AP']
self.minimum_client_versions: typing.Dict[typing.Tuple[int, int], Utils.Version] = {} self.minimum_client_versions: typing.Dict[int, Utils.Version] = {}
def load(self, multidatapath: str, use_embedded_server_options: bool = False): def load(self, multidatapath: str, use_embedded_server_options: bool = False):
with open(multidatapath, 'rb') as f: with open(multidatapath, 'rb') as f:
@ -131,10 +131,10 @@ class Context(Node):
if mdata_ver > Utils._version_tuple: if mdata_ver > Utils._version_tuple:
raise RuntimeError(f"Supplied Multidata requires a server of at least version {mdata_ver}," raise RuntimeError(f"Supplied Multidata requires a server of at least version {mdata_ver},"
f"however this server is of version {Utils._version_tuple}") f"however this server is of version {Utils._version_tuple}")
clients_ver = decoded_obj["minimum_versions"].get("clients", []) clients_ver = decoded_obj["minimum_versions"].get("clients", {})
self.minimum_client_versions = {} self.minimum_client_versions = {}
for team, player, version in clients_ver: for player, version in clients_ver.items():
self.minimum_client_versions[team, player] = Utils.Version(*version) self.minimum_client_versions[player] = Utils.Version(*version)
for team, names in enumerate(decoded_obj['names']): for team, names in enumerate(decoded_obj['names']):
for player, name in enumerate(names, 1): for player, name in enumerate(names, 1):
@ -991,13 +991,13 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
client.name = ctx.player_names[(team, slot)] client.name = ctx.player_names[(team, slot)]
client.team = team client.team = team
client.slot = slot client.slot = slot
minver = Utils.Version(*(ctx.minimum_client_versions.get((team, slot), (0,0,0)))) minver = ctx.minimum_client_versions[slot]
if minver > args['version']: if minver > args['version']:
errors.add('IncompatibleVersion') errors.add('IncompatibleVersion')
if ctx.compatibility == 1 and "AP" not in args['tags']: if ctx.compatibility == 1 and "AP" not in args['tags']:
errors.add('IncompatibleVersion') errors.add('IncompatibleVersion')
#only exact version match allowed # only exact version match allowed
elif ctx.compatibility == 0 and args['version'] != _version_tuple: elif ctx.compatibility == 0 and args['version'] != _version_tuple:
errors.add('IncompatibleVersion') errors.add('IncompatibleVersion')
if errors: if errors:
@ -1013,7 +1013,8 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
"team": client.team, "slot": client.slot, "team": client.team, "slot": client.slot,
"players": ctx.get_players_package(), "players": ctx.get_players_package(),
"missing_locations": get_missing_checks(ctx, client), "missing_locations": get_missing_checks(ctx, client),
"checked_locations": get_checked_checks(ctx, client)}] "checked_locations": get_checked_checks(ctx, client),
}]
items = get_received_items(ctx, client.team, client.slot) items = get_received_items(ctx, client.team, client.slot)
if items: if items:
reply.append({"cmd": 'ReceivedItems', "index": 0, "items": tuplize_received_items(items)}) reply.append({"cmd": 'ReceivedItems', "index": 0, "items": tuplize_received_items(items)})

View File

@ -536,7 +536,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
if option_name in weights: if option_name in weights:
setattr(ret, option_name, option.from_any(get_choice(option_name, weights))) setattr(ret, option_name, option.from_any(get_choice(option_name, weights)))
else: else:
setattr(ret, option_name, option.default) setattr(ret, option_name, option.from_any(option.default))
else: else:
raise Exception(f"Unsupported game {ret.game}") raise Exception(f"Unsupported game {ret.game}")
return ret return ret

View File

@ -104,7 +104,9 @@ class Choice(Option):
@classmethod @classmethod
def from_any(cls, data: typing.Any): def from_any(cls, data: typing.Any):
return cls.from_text(data) if type(data) == int and data in cls.options.values():
return cls(data)
return cls.from_text(str(data))
class Logic(Choice): class Logic(Choice):
@ -273,11 +275,16 @@ class TechTreeLayout(Choice):
option_single = 0 option_single = 0
default = 0 default = 0
class Visibility(Choice):
option_none = 0
option_sending = 1
default = 0
factorio_options: typing.Dict[str, type(Option)] = {"max_science_pack": MaxSciencePack, factorio_options: typing.Dict[str, type(Option)] = {"max_science_pack": MaxSciencePack,
"tech_tree_layout": TechTreeLayout, "tech_tree_layout": TechTreeLayout,
"tech_cost": TechCost, "tech_cost": TechCost,
"free_samples": FreeSamples} "free_samples": FreeSamples,
"visibility": Visibility}
if __name__ == "__main__": if __name__ == "__main__":
import argparse import argparse

View File

@ -12,7 +12,7 @@ class Version(typing.NamedTuple):
minor: int minor: int
build: int build: int
__version__ = "0.0.2" __version__ = "0.0.3"
_version_tuple = tuplize_version(__version__) _version_tuple = tuplize_version(__version__)
import builtins import builtins

View File

@ -1,10 +1,10 @@
require "lib" require "lib"
-- for testing -- for testing
-- script.on_event(defines.events.on_tick, function(event) script.on_event(defines.events.on_tick, function(event)
-- if event.tick%600 == 0 then if event.tick%600 == 0 then
-- dumpTech(game.forces["player"]) dumpTech(game.forces["player"])
-- end end
-- end) end)
-- hook into researches done -- hook into researches done
script.on_event(defines.events.on_research_finished, function(event) script.on_event(defines.events.on_research_finished, function(event)
@ -54,7 +54,7 @@ function dumpGameInfo()
local data_collection = {} local data_collection = {}
local force = game.forces["player"] local force = game.forces["player"]
for tech_name, tech in pairs(force.technologies) do for tech_name, tech in pairs(force.technologies) do
if tech.enabled then if tech.enabled and tech.research_unit_count_formula == nil then
local tech_data = {} local tech_data = {}
local unlocks = {} local unlocks = {}
tech_data["unlocks"] = unlocks tech_data["unlocks"] = unlocks

View File

@ -31,10 +31,10 @@ new_tree_copy.name = "ap-{{ tech_table[original_tech_name] }}-"{# use AP ID #}
prep_copy(new_tree_copy, original_tech) prep_copy(new_tree_copy, original_tech)
{% if tech_cost != 1 %} {% if tech_cost != 1 %}
if new_tree_copy.unit.count then if new_tree_copy.unit.count then
new_tree_copy.unit.count = math.max(1, math.floor(new_tree_copy.unit.count * {{ tech_cost }})) new_tree_copy.unit.count = math.max(1, math.floor(new_tree_copy.unit.count * {{ tech_cost_scale }}))
end end
{% endif %} {% endif %}
{% if item_name in tech_table %} {% if item_name in tech_table and visibility %}
{#- copy Factorio Technology Icon #} {#- copy Factorio Technology Icon #}
new_tree_copy.icon = table.deepcopy(technologies["{{ item_name }}"].icon) new_tree_copy.icon = table.deepcopy(technologies["{{ item_name }}"].icon)
new_tree_copy.icons = table.deepcopy(technologies["{{ item_name }}"].icons) new_tree_copy.icons = table.deepcopy(technologies["{{ item_name }}"].icons)
@ -44,6 +44,7 @@ new_tree_copy.icon_size = table.deepcopy(technologies["{{ item_name }}"].icon_si
new_tree_copy.icon = "__{{ mod_name }}__/graphics/icons/ap.png" new_tree_copy.icon = "__{{ mod_name }}__/graphics/icons/ap.png"
new_tree_copy.icons = nil new_tree_copy.icons = nil
new_tree_copy.icon_size = 512 new_tree_copy.icon_size = 512
{% endif %} {% endif %}
{#- add new technology to game #} {#- add new technology to game #}
data:extend{new_tree_copy} data:extend{new_tree_copy}

View File

@ -1,8 +1,17 @@
[technology-name] [technology-name]
{% for original_tech_name, item_name, receiving_player in locations %} {% for original_tech_name, item_name, receiving_player in locations %}
{%- if visibility %}
ap-{{ tech_table[original_tech_name] }}-={{ player_names[receiving_player] }}'s {{ item_name }} ap-{{ tech_table[original_tech_name] }}-={{ player_names[receiving_player] }}'s {{ item_name }}
{%- else %}
ap-{{ tech_table[original_tech_name] }}-= An Archipelago Sendable
{%- endif %}
{% endfor %} {% endfor %}
[technology-description] [technology-description]
{% for original_tech_name, item_name, receiving_player in locations %} {% for original_tech_name, item_name, receiving_player in locations %}
{%- if visibility %}
ap-{{ tech_table[original_tech_name] }}-=Researching this technology sends {{ item_name }} to {{ player_names[receiving_player] }}. ap-{{ tech_table[original_tech_name] }}-=Researching this technology sends {{ item_name }} to {{ player_names[receiving_player] }}.
{%- else %}
ap-{{ tech_table[original_tech_name] }}-=Researching this technology sends something to someone.
{%- endif %}
{% endfor %} {% endfor %}

View File

@ -25,7 +25,7 @@ lookup_any_location_name_to_id = {name: id for id, name in lookup_any_location_i
network_data_package = {"lookup_any_location_id_to_name": lookup_any_location_id_to_name, network_data_package = {"lookup_any_location_id_to_name": lookup_any_location_id_to_name,
"lookup_any_item_id_to_name": lookup_any_item_id_to_name, "lookup_any_item_id_to_name": lookup_any_item_id_to_name,
"version": 2} "version": 3}
@enum.unique @enum.unique
class Games(str, enum.Enum): class Games(str, enum.Enum):

View File

@ -1775,7 +1775,7 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
option_name: True option_name: True
} }
data_dir = local_path("../../data") if is_bundled() else None data_dir = local_path("data") if is_bundled() else None
offsets_array = build_offset_collections(options, data_dir) offsets_array = build_offset_collections(options, data_dir)
restore_maseya_colors(rom, offsets_array) restore_maseya_colors(rom, offsets_array)
if mode == 'default': if mode == 'default':

View File

@ -9,6 +9,7 @@ import json
import jinja2 import jinja2
import Utils import Utils
import shutil import shutil
import Options
from BaseClasses import MultiWorld from BaseClasses import MultiWorld
from .Technologies import tech_table from .Technologies import tech_table
@ -50,7 +51,9 @@ def generate_mod(world: MultiWorld, player: int):
6: 10}[world.tech_cost[player].value] 6: 10}[world.tech_cost[player].value]
template_data = {"locations": locations, "player_names" : player_names, "tech_table": tech_table, template_data = {"locations": locations, "player_names" : player_names, "tech_table": tech_table,
"mod_name": mod_name, "allowed_science_packs": world.max_science_pack[player].get_allowed_packs(), "mod_name": mod_name, "allowed_science_packs": world.max_science_pack[player].get_allowed_packs(),
"tech_cost": tech_cost, "free_samples": world.free_samples[player].value} "tech_cost_scale": tech_cost}
for factorio_option in Options.factorio_options:
template_data[factorio_option] = getattr(world, factorio_option)[player].value
control_code = control_template.render(**template_data) control_code = control_template.render(**template_data)
data_final_fixes_code = template.render(**template_data) data_final_fixes_code = template.render(**template_data)

View File

@ -6,12 +6,13 @@ import Utils
factorio_id = 2 ** 17 factorio_id = 2 ** 17
source_file = Utils.local_path("data", "factorio", "techs.json") source_file = Utils.local_path("data", "factorio", "techs.json")
recipe_source_file = Utils.local_path("data", "factorio", "recipes.json")
with open(source_file) as f: with open(source_file) as f:
raw = json.load(f) raw = json.load(f)
with open(recipe_source_file) as f:
raw_recipes = json.load(f)
tech_table = {} tech_table = {}
technology_table = {} technology_table = {}
requirements = {} # tech_name -> Set[required_technologies]
class Technology(): # maybe make subclass of Location? class Technology(): # maybe make subclass of Location?
@ -25,13 +26,28 @@ class Technology(): # maybe make subclass of Location?
def get_required_technologies(self): def get_required_technologies(self):
requirements = set() requirements = set()
for ingredient in self.ingredients: for ingredient in self.ingredients:
if ingredient in recipe_sources: # no source likely means starting item if ingredient in recipe_sources: # no source likely means starting item
requirements |= recipe_sources[ingredient] # technically any, not all, need to improve later requirements |= recipe_sources[ingredient] # technically any, not all, need to improve later
return requirements return requirements
def build_rule(self):
ingredient_rules = []
for ingredient in self.ingredients:
if ingredient in recipe_sources:
technologies = recipe_sources[ingredient] # technologies that unlock the recipe
ingredient_rules.append(lambda state, technologies=technologies: any(state.has(technology) for technology in technologies))
ingredient_rules = frozenset(ingredient_rules)
return lambda state: all(rule(state) for rule in ingredient_rules)
def __hash__(self): def __hash__(self):
return self.factorio_id return self.factorio_id
class Recipe():
def __init__(self, name, category, ingredients, products):
self.name = name
self.category = category
self.products = ingredients
self.ingredients = products
# recipes and technologies can share names in Factorio # recipes and technologies can share names in Factorio
for technology_name in sorted(raw): for technology_name in sorted(raw):
@ -56,3 +72,10 @@ for technology, data in raw.items():
del (raw) del (raw)
lookup_id_to_name: Dict[int, str] = {item_id: item_name for item_name, item_id in tech_table.items()} lookup_id_to_name: Dict[int, str] = {item_id: item_name for item_name, item_id in tech_table.items()}
all_recipes = set()
for recipe_name, recipe_data in raw_recipes.items():
# example:
# "accumulator":{"ingredients":["iron-plate","battery"],"products":["accumulator"],"category":"crafting"}
all_recipes.add(Recipe(recipe_name, recipe_data["category"], set(recipe_data["ingredients"]), set(recipe_data["products"])))

View File

@ -2,7 +2,7 @@ import logging
from BaseClasses import Region, Entrance, Location, MultiWorld, Item from BaseClasses import Region, Entrance, Location, MultiWorld, Item
from .Technologies import tech_table, requirements, recipe_sources, technology_table from .Technologies import tech_table, recipe_sources, technology_table
static_nodes = {"automation", "logistics"} static_nodes = {"automation", "logistics"}