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:
parent
443fc03700
commit
f0a6b5a8e4
|
@ -316,7 +316,7 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
|
|||
logger.info(f' Team #{network_player.team + 1}')
|
||||
current_team = network_player.team
|
||||
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.server_auth(args['password'])
|
||||
|
||||
|
|
160
Main.py
160
Main.py
|
@ -10,8 +10,7 @@ import pickle
|
|||
from typing import Dict
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState, Region, Item
|
||||
from worlds.alttp import ALttPLocation
|
||||
from worlds.alttp.Items import ItemFactory, item_table, item_name_groups
|
||||
from worlds.alttp.Items import ItemFactory, item_name_groups
|
||||
from worlds.alttp.Regions import create_regions, mark_light_world_regions, \
|
||||
lookup_vanilla_location_to_entrance
|
||||
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.ItemPool import generate_itempool, difficulties, fill_prizes
|
||||
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.factorio import gen_factorio, factorio_create_regions
|
||||
from worlds.factorio.Mod import generate_mod
|
||||
|
@ -492,7 +491,10 @@ def main(args, seed=None):
|
|||
for future in roms:
|
||||
rom_name = future.result()
|
||||
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
|
||||
slot, team, rom_name in rom_names}
|
||||
|
||||
|
@ -500,6 +502,7 @@ def main(args, seed=None):
|
|||
for player, name in enumerate(team, 1):
|
||||
if player not in world.alttp_player_ids:
|
||||
connect_names[name] = (i, player)
|
||||
|
||||
multidata = zlib.compress(pickle.dumps({"names": parsed_names,
|
||||
"connect_names": connect_names,
|
||||
"remote_items": {player for player in range(1, world.players + 1) if
|
||||
|
@ -544,155 +547,8 @@ def main(args, seed=None):
|
|||
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):
|
||||
"""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
|
||||
prog_locations = {location for location in world.get_filled_locations() if location.item.advancement}
|
||||
state_cache = [None]
|
||||
|
|
|
@ -110,7 +110,7 @@ class Context(Node):
|
|||
self.auto_saver_thread = None
|
||||
self.save_dirty = False
|
||||
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):
|
||||
with open(multidatapath, 'rb') as f:
|
||||
|
@ -131,10 +131,10 @@ class Context(Node):
|
|||
if mdata_ver > Utils._version_tuple:
|
||||
raise RuntimeError(f"Supplied Multidata requires a server of at least version {mdata_ver},"
|
||||
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 = {}
|
||||
for team, player, version in clients_ver:
|
||||
self.minimum_client_versions[team, player] = Utils.Version(*version)
|
||||
for player, version in clients_ver.items():
|
||||
self.minimum_client_versions[player] = Utils.Version(*version)
|
||||
|
||||
for team, names in enumerate(decoded_obj['names']):
|
||||
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.team = team
|
||||
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']:
|
||||
errors.add('IncompatibleVersion')
|
||||
|
||||
if ctx.compatibility == 1 and "AP" not in args['tags']:
|
||||
errors.add('IncompatibleVersion')
|
||||
#only exact version match allowed
|
||||
# only exact version match allowed
|
||||
elif ctx.compatibility == 0 and args['version'] != _version_tuple:
|
||||
errors.add('IncompatibleVersion')
|
||||
if errors:
|
||||
|
@ -1013,7 +1013,8 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
|
|||
"team": client.team, "slot": client.slot,
|
||||
"players": ctx.get_players_package(),
|
||||
"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)
|
||||
if items:
|
||||
reply.append({"cmd": 'ReceivedItems', "index": 0, "items": tuplize_received_items(items)})
|
||||
|
|
|
@ -536,7 +536,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
|||
if option_name in weights:
|
||||
setattr(ret, option_name, option.from_any(get_choice(option_name, weights)))
|
||||
else:
|
||||
setattr(ret, option_name, option.default)
|
||||
setattr(ret, option_name, option.from_any(option.default))
|
||||
else:
|
||||
raise Exception(f"Unsupported game {ret.game}")
|
||||
return ret
|
||||
|
|
11
Options.py
11
Options.py
|
@ -104,7 +104,9 @@ class Choice(Option):
|
|||
|
||||
@classmethod
|
||||
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):
|
||||
|
@ -273,11 +275,16 @@ class TechTreeLayout(Choice):
|
|||
option_single = 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,
|
||||
"tech_tree_layout": TechTreeLayout,
|
||||
"tech_cost": TechCost,
|
||||
"free_samples": FreeSamples}
|
||||
"free_samples": FreeSamples,
|
||||
"visibility": Visibility}
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
|
2
Utils.py
2
Utils.py
|
@ -12,7 +12,7 @@ class Version(typing.NamedTuple):
|
|||
minor: int
|
||||
build: int
|
||||
|
||||
__version__ = "0.0.2"
|
||||
__version__ = "0.0.3"
|
||||
_version_tuple = tuplize_version(__version__)
|
||||
|
||||
import builtins
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
require "lib"
|
||||
-- for testing
|
||||
-- script.on_event(defines.events.on_tick, function(event)
|
||||
-- if event.tick%600 == 0 then
|
||||
-- dumpTech(game.forces["player"])
|
||||
-- end
|
||||
-- end)
|
||||
script.on_event(defines.events.on_tick, function(event)
|
||||
if event.tick%600 == 0 then
|
||||
dumpTech(game.forces["player"])
|
||||
end
|
||||
end)
|
||||
|
||||
-- hook into researches done
|
||||
script.on_event(defines.events.on_research_finished, function(event)
|
||||
|
@ -54,7 +54,7 @@ function dumpGameInfo()
|
|||
local data_collection = {}
|
||||
local force = game.forces["player"]
|
||||
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 unlocks = {}
|
||||
tech_data["unlocks"] = unlocks
|
||||
|
|
|
@ -31,10 +31,10 @@ new_tree_copy.name = "ap-{{ tech_table[original_tech_name] }}-"{# use AP ID #}
|
|||
prep_copy(new_tree_copy, original_tech)
|
||||
{% if tech_cost != 1 %}
|
||||
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
|
||||
{% endif %}
|
||||
{% if item_name in tech_table %}
|
||||
{% if item_name in tech_table and visibility %}
|
||||
{#- copy Factorio Technology Icon #}
|
||||
new_tree_copy.icon = table.deepcopy(technologies["{{ item_name }}"].icon)
|
||||
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.icons = nil
|
||||
new_tree_copy.icon_size = 512
|
||||
|
||||
{% endif %}
|
||||
{#- add new technology to game #}
|
||||
data:extend{new_tree_copy}
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
|
||||
[technology-name]
|
||||
{% 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 }}
|
||||
{%- else %}
|
||||
ap-{{ tech_table[original_tech_name] }}-= An Archipelago Sendable
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
[technology-description]
|
||||
{% 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] }}.
|
||||
{%- else %}
|
||||
ap-{{ tech_table[original_tech_name] }}-=Researching this technology sends something to someone.
|
||||
{%- endif %}
|
||||
{% endfor %}
|
|
@ -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,
|
||||
"lookup_any_item_id_to_name": lookup_any_item_id_to_name,
|
||||
"version": 2}
|
||||
"version": 3}
|
||||
|
||||
@enum.unique
|
||||
class Games(str, enum.Enum):
|
||||
|
|
|
@ -1775,7 +1775,7 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
|
|||
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)
|
||||
restore_maseya_colors(rom, offsets_array)
|
||||
if mode == 'default':
|
||||
|
|
|
@ -9,6 +9,7 @@ import json
|
|||
import jinja2
|
||||
import Utils
|
||||
import shutil
|
||||
import Options
|
||||
from BaseClasses import MultiWorld
|
||||
from .Technologies import tech_table
|
||||
|
||||
|
@ -50,7 +51,9 @@ def generate_mod(world: MultiWorld, player: int):
|
|||
6: 10}[world.tech_cost[player].value]
|
||||
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(),
|
||||
"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)
|
||||
data_final_fixes_code = template.render(**template_data)
|
||||
|
||||
|
|
|
@ -6,12 +6,13 @@ import Utils
|
|||
factorio_id = 2 ** 17
|
||||
|
||||
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:
|
||||
raw = json.load(f)
|
||||
with open(recipe_source_file) as f:
|
||||
raw_recipes = json.load(f)
|
||||
tech_table = {}
|
||||
technology_table = {}
|
||||
requirements = {} # tech_name -> Set[required_technologies]
|
||||
|
||||
|
||||
class Technology(): # maybe make subclass of Location?
|
||||
|
@ -29,9 +30,24 @@ class Technology(): # maybe make subclass of Location?
|
|||
requirements |= recipe_sources[ingredient] # technically any, not all, need to improve later
|
||||
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):
|
||||
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
|
||||
for technology_name in sorted(raw):
|
||||
|
@ -56,3 +72,10 @@ for technology, data in raw.items():
|
|||
|
||||
del (raw)
|
||||
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"])))
|
||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
|||
|
||||
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"}
|
||||
|
||||
|
|
Loading…
Reference in New Issue