Factorio: revamped location system (#1147)

This commit is contained in:
Fabian Dill
2022-10-28 21:00:06 +02:00
committed by GitHub
parent ec0389eefb
commit 53974d568b
10 changed files with 294 additions and 233 deletions

View File

@@ -1,19 +1,20 @@
from __future__ import annotations
import collections
import logging
import typing
from worlds.AutoWorld import World, WebWorld
from BaseClasses import Region, Entrance, Location, Item, RegionType, Tutorial, ItemClassification
from worlds.AutoWorld import World, WebWorld
from .Mod import generate_mod
from .Options import factorio_options, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal
from .Shapes import get_shapes
from .Technologies import base_tech_table, recipe_sources, base_technology_table, \
all_ingredient_names, all_product_sources, required_technologies, get_rocket_requirements, \
progressive_technology_table, common_tech_table, tech_to_progressive_lookup, progressive_tech_table, \
get_science_pack_pools, Recipe, recipes, technology_table, tech_table, factorio_base_id, useless_technologies, \
fluids, stacking_items, valid_ingredients
from .Shapes import get_shapes
from .Mod import generate_mod
from .Options import factorio_options, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal
import logging
fluids, stacking_items, valid_ingredients, progressive_rows
from .Locations import location_pools, location_table
class FactorioWeb(WebWorld):
@@ -43,89 +44,75 @@ class Factorio(World):
research new technologies, and become more efficient in your quest to build a rocket and return home.
"""
game: str = "Factorio"
static_nodes = {"automation", "logistics", "rocket-silo"}
special_nodes = {"automation", "logistics", "rocket-silo"}
custom_recipes: typing.Dict[str, Recipe]
location_pool: typing.List[FactorioScienceLocation]
advancement_technologies: typing.Set[str]
web = FactorioWeb()
item_name_to_id = all_items
location_name_to_id = base_tech_table
# TODO: remove base_tech_table ~ 0.3.7
location_name_to_id = {**base_tech_table, **location_table}
item_name_groups = {
"Progressive": set(progressive_tech_table.keys()),
}
data_version = 5
required_client_version = (0, 3, 0)
data_version = 6
required_client_version = (0, 3, 6)
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()
tech_mix: int = 0
skip_silo: bool = False
def __init__(self, world, player: int):
super(Factorio, self).__init__(world, player)
self.advancement_technologies = set()
self.custom_recipes = {}
def generate_basic(self):
player = self.player
want_progressives = collections.defaultdict(lambda: self.world.progressive[player].
want_progressives(self.world.random))
skip_silo = self.world.silo[player].value == Silo.option_spawn
evolution_traps_wanted = self.world.evolution_traps[player].value
attack_traps_wanted = self.world.attack_traps[player].value
traps_wanted = ["Evolution Trap"] * evolution_traps_wanted + ["Attack Trap"] * attack_traps_wanted
self.world.random.shuffle(traps_wanted)
for tech_name in base_tech_table:
if traps_wanted and tech_name in useless_technologies:
self.world.itempool.append(self.create_item(traps_wanted.pop()))
elif skip_silo and tech_name == "rocket-silo":
pass
else:
progressive_item_name = tech_to_progressive_lookup.get(tech_name, tech_name)
want_progressive = want_progressives[progressive_item_name]
item_name = progressive_item_name if want_progressive else tech_name
tech_item = self.create_item(item_name)
if tech_name in self.static_nodes:
self.world.get_location(tech_name, player).place_locked_item(tech_item)
else:
self.world.itempool.append(tech_item)
map_basic_settings = self.world.world_gen[player].value["basic"]
if map_basic_settings.get("seed", None) is None: # allow seed 0
map_basic_settings["seed"] = self.world.slot_seeds[player].randint(0, 2 ** 32 - 1) # 32 bit uint
# used to be called "sending_visible"
if self.world.tech_tree_information[player] == TechTreeInformation.option_full:
# mark all locations as pre-hinted
self.world.start_location_hints[self.player].value.update(base_tech_table)
self.locations = []
generate_output = generate_mod
def generate_early(self) -> None:
self.world.max_tech_cost[self.player] = max(self.world.max_tech_cost[self.player],
self.world.min_tech_cost[self.player])
self.tech_mix = self.world.tech_cost_mix[self.player]
self.skip_silo = self.world.silo[self.player].value == Silo.option_spawn
def create_regions(self):
player = self.player
random = self.world.random
menu = Region("Menu", RegionType.Generic, "Menu", player, self.world)
crash = Entrance(player, "Crash Land", menu)
menu.exits.append(crash)
nauvis = Region("Nauvis", RegionType.Generic, "Nauvis", player, self.world)
skip_silo = self.world.silo[self.player].value == Silo.option_spawn
for tech_name, tech_id in base_tech_table.items():
if skip_silo and tech_name == "rocket-silo":
continue
tech = Location(player, tech_name, tech_id, nauvis)
nauvis.locations.append(tech)
tech.game = "Factorio"
location = Location(player, "Rocket Launch", None, nauvis)
location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \
self.world.evolution_traps[player].value + self.world.attack_traps[player].value
location_pool = []
for pack in self.world.max_science_pack[self.player].get_allowed_packs():
location_pool.extend(location_pools[pack])
location_names = self.world.random.sample(location_pool, location_count)
self.locations = [FactorioScienceLocation(player, loc_name, self.location_name_to_id[loc_name], nauvis)
for loc_name in location_names]
rand_values = sorted(random.randint(self.world.min_tech_cost[self.player],
self.world.max_tech_cost[self.player]) for _ in self.locations)
for i, location in enumerate(sorted(self.locations, key=lambda loc: loc.rel_cost)):
location.count = rand_values[i]
del rand_values
nauvis.locations.extend(self.locations)
location = FactorioLocation(player, "Rocket Launch", None, nauvis)
nauvis.locations.append(location)
location.game = "Factorio"
event = FactorioItem("Victory", ItemClassification.progression, None, player)
event.game = "Factorio"
self.world.push_item(location, event, False)
location.event = location.locked = True
location.place_locked_item(event)
for ingredient in self.world.max_science_pack[self.player].get_allowed_packs():
location = Location(player, f"Automate {ingredient}", None, nauvis)
location.game = "Factorio"
location = FactorioLocation(player, f"Automate {ingredient}", None, nauvis)
nauvis.locations.append(location)
event = FactorioItem(f"Automated {ingredient}", ItemClassification.progression, None, player)
self.world.push_item(location, event, False)
location.event = location.locked = True
location.place_locked_item(event)
crash.connect(nauvis)
self.world.regions += [menu, nauvis]
@@ -151,17 +138,13 @@ class Factorio(World):
location.access_rule = lambda state, ingredient=ingredient: \
all(state.has(technology.name, player) for technology in required_technologies[ingredient])
skip_silo = self.world.silo[self.player].value == Silo.option_spawn
for tech_name, technology in self.custom_technologies.items():
if skip_silo and tech_name == "rocket-silo":
continue
location = world.get_location(tech_name, player)
Rules.set_rule(location, technology.build_rule(player))
prequisites = shapes.get(tech_name)
if prequisites:
locations = {world.get_location(requisite, player) for requisite in prequisites}
Rules.add_rule(location, lambda state,
locations=locations: all(state.can_reach(loc) for loc in locations))
for location in self.locations:
Rules.set_rule(location, lambda state, ingredients=location.ingredients:
all(state.has(f"Automated {ingredient}", player) for ingredient in ingredients))
prerequisites = shapes.get(location)
if prerequisites:
Rules.add_rule(location, lambda state, locations=
prerequisites: all(state.can_reach(loc) for loc in locations))
silo_recipe = None
if self.world.silo[self.player] == Silo.option_spawn:
@@ -179,6 +162,48 @@ class Factorio(World):
world.completion_condition[player] = lambda state: state.has('Victory', player)
def generate_basic(self):
player = self.player
want_progressives = collections.defaultdict(lambda: self.world.progressive[player].
want_progressives(self.world.random))
self.world.itempool.extend(self.create_item("Evolution Trap") for _ in
range(self.world.evolution_traps[player].value))
self.world.itempool.extend(self.create_item("Attack Trap") for _ in
range(self.world.attack_traps[player].value))
cost_sorted_locations = sorted(self.locations, key=lambda location: location.name)
special_index = {"automation": 0,
"logistics": 1,
"rocket-silo": -1}
loc: FactorioScienceLocation
if self.skip_silo:
removed = useless_technologies | {"rocket-silo"}
else:
removed = useless_technologies
for tech_name in base_tech_table:
if tech_name not in removed:
progressive_item_name = tech_to_progressive_lookup.get(tech_name, tech_name)
want_progressive = want_progressives[progressive_item_name]
item_name = progressive_item_name if want_progressive else tech_name
tech_item = self.create_item(item_name)
index = special_index.get(tech_name, None)
if index is None:
self.world.itempool.append(tech_item)
else:
loc = cost_sorted_locations[index]
loc.place_locked_item(tech_item)
loc.revealed = True
map_basic_settings = self.world.world_gen[player].value["basic"]
if map_basic_settings.get("seed", None) is None: # allow seed 0
map_basic_settings["seed"] = self.world.slot_seeds[player].randint(0, 2 ** 32 - 1) # 32 bit uint
if self.world.tech_tree_information[player] == TechTreeInformation.option_full:
# mark all locations as pre-hinted
self.world.start_location_hints[self.player].value.update(base_tech_table)
for loc in self.locations:
loc.revealed = True
def collect_item(self, state, item, remove=False):
if item.advancement and item.name in progressive_technology_table:
prog_table = progressive_technology_table[item.name].progressive
@@ -400,3 +425,33 @@ class Factorio(World):
ItemClassification.trap if "Trap" in name else ItemClassification.filler,
all_items[name], self.player)
return item
class FactorioLocation(Location):
game: str = Factorio.game
class FactorioScienceLocation(FactorioLocation):
complexity: int
revealed: bool = False
# Factorio technology properties:
ingredients: typing.Dict[str, int]
count: int
def __init__(self, player: int, name: str, address: int, parent: Region):
super(FactorioScienceLocation, self).__init__(player, name, address, parent)
# "AP-{Complexity}-{Cost}"
self.complexity = int(self.name[3]) - 1
self.rel_cost = int(self.name[5:], 16)
self.ingredients = {Factorio.ordered_science_packs[self.complexity]: 1}
for complexity in range(self.complexity):
if parent.world.tech_cost_mix[self.player] > parent.world.random.randint(0, 99):
self.ingredients[Factorio.ordered_science_packs[complexity]] = 1
self.count = parent.world.random.randint(parent.world.min_tech_cost[self.player],
parent.world.max_tech_cost[self.player])
@property
def factorio_ingredients(self) -> typing.List[typing.Tuple[str, int]]:
return [(name, count) for name, count in self.ingredients.items()]