Factorio: flatten science pack curve ()

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
Fabian Dill 2023-04-09 20:58:24 +02:00 committed by GitHub
parent 6628e8c85d
commit 8e7bbb4ea8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 90 deletions

View File

@ -1,32 +1,30 @@
from typing import Dict, List
from .Technologies import factorio_base_id, factorio_id
from .Technologies import factorio_base_id
from .Options import MaxSciencePack
boundary: int = 0xff
total_locations: int = 0xff
assert total_locations <= boundary
assert factorio_base_id != factorio_id
def make_pools() -> Dict[str, List[str]]:
pools: Dict[str, List[str]] = {}
for i, pack in enumerate(MaxSciencePack.get_ordered_science_packs(), start=1):
max_needed: int = sum(divmod(boundary, i))
scale: float = boundary / max_needed
max_needed: int = 0xff
prefix: str = f"AP-{i}-"
pools[pack] = [prefix + hex(int(x * scale))[2:].upper().zfill(2) for x in range(1, max_needed + 1)]
pools[pack] = [prefix + hex(x)[2:].upper().zfill(2) for x in range(1, max_needed + 1)]
return pools
location_pools: Dict[str, List[str]] = make_pools()
location_table: Dict[str, int] = {}
end_id: int = factorio_id
end_id: int = factorio_base_id
for pool in location_pools.values():
location_table.update({name: ap_id for ap_id, name in enumerate(pool, start=end_id)})
end_id += len(pool)
assert end_id - len(location_table) == factorio_id
assert end_id - len(location_table) == factorio_base_id
del pool

View File

@ -96,7 +96,7 @@ def generate_mod(world: "Factorio", output_directory: str):
settings_template = template_env.get_template("settings.lua")
# get data for templates
locations = [(location, location.item)
for location in world.locations]
for location in world.science_locations]
mod_name = f"AP-{multiworld.seed_name}-P{player}-{multiworld.get_file_safe_player_name(player)}"
random = multiworld.per_slot_randoms[player]

View File

@ -24,7 +24,7 @@ def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Se
player = factorio_world.player
prerequisites: Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]] = {}
layout = world.tech_tree_layout[player].value
locations: List["FactorioScienceLocation"] = sorted(factorio_world.locations, key=lambda loc: loc.name)
locations: List["FactorioScienceLocation"] = sorted(factorio_world.science_locations, key=lambda loc: loc.name)
world.random.shuffle(locations)
if layout == TechTreeLayout.option_single:

View File

@ -4,6 +4,7 @@ import json
import logging
import os
import string
import pkgutil
from collections import Counter
from concurrent.futures import ThreadPoolExecutor
from typing import Dict, Set, FrozenSet, Tuple, Union, List, Any
@ -11,7 +12,7 @@ from typing import Dict, Set, FrozenSet, Tuple, Union, List, Any
import Utils
from . import Options
factorio_id = factorio_base_id = 2 ** 17
factorio_tech_id = factorio_base_id = 2 ** 17
# Factorio technologies are imported from a .json document in /data
source_folder = os.path.join(os.path.dirname(__file__), "data")
@ -19,7 +20,6 @@ pool = ThreadPoolExecutor(1)
def load_json_data(data_name: str) -> Union[List[str], Dict[str, Any]]:
import pkgutil
return json.loads(pkgutil.get_data(__name__, "data/" + data_name + ".json").decode())
@ -33,7 +33,9 @@ items_future = pool.submit(load_json_data, "items")
tech_table: Dict[str, int] = {}
technology_table: Dict[str, Technology] = {}
always = lambda state: True
def always(state):
return True
class FactorioElement:
@ -49,7 +51,6 @@ class FactorioElement:
class Technology(FactorioElement): # maybe make subclass of Location?
has_modifier: bool
factorio_id: int
name: str
ingredients: Set[str]
progressive: Tuple[str]
unlocks: Union[Set[str], bool] # bool case is for progressive technologies
@ -192,9 +193,9 @@ recipe_sources: Dict[str, Set[str]] = {} # recipe_name -> technology source
# recipes and technologies can share names in Factorio
for technology_name, data in sorted(techs_future.result().items()):
current_ingredients = set(data["ingredients"])
technology = Technology(technology_name, current_ingredients, factorio_id,
technology = Technology(technology_name, current_ingredients, factorio_tech_id,
has_modifier=data["has_modifier"], unlocks=set(data["unlocks"]))
factorio_id += 1
factorio_tech_id += 1
tech_table[technology_name] = technology.factorio_id
technology_table[technology_name] = technology
for recipe_name in technology.unlocks:
@ -391,17 +392,13 @@ progressive_rows["progressive-energy-shield"] = ("energy-shield-equipment", "ene
progressive_rows["progressive-wall"] = ("stone-wall", "gate")
progressive_rows["progressive-follower"] = ("defender", "distractor", "destroyer")
progressive_rows["progressive-inserter"] = ("fast-inserter", "stack-inserter")
sorted_rows = sorted(progressive_rows)
# to keep ID mappings the same.
# If there's a breaking change at some point, then this should be moved in with the sorted ordering
progressive_rows["progressive-turret"] = ("gun-turret", "laser-turret")
sorted_rows.append("progressive-turret")
progressive_rows["progressive-flamethrower"] = ("flamethrower",) # leaving out flammables, as they do nothing
sorted_rows.append("progressive-flamethrower")
progressive_rows["progressive-personal-roboport-equipment"] = ("personal-roboport-equipment",
"personal-roboport-mk2-equipment")
sorted_rows.append("progressive-personal-roboport-equipment")
sorted_rows = sorted(progressive_rows)
# integrate into
source_target_mapping: Dict[str, str] = {
"progressive-braking-force": "progressive-train-network",
@ -421,8 +418,8 @@ progressive_technology_table: Dict[str, Technology] = {}
for root in sorted_rows:
progressive = progressive_rows[root]
assert all(tech in tech_table for tech in progressive), "declared a progressive technology without base technology"
factorio_id += 1
progressive_technology = Technology(root, technology_table[progressive[0]].ingredients, factorio_id,
factorio_tech_id += 1
progressive_technology = Technology(root, technology_table[progressive[0]].ingredients, factorio_tech_id,
progressive,
has_modifier=any(technology_table[tech].has_modifier for tech in progressive),
unlocks=any(technology_table[tech].unlocks for tech in progressive))
@ -504,3 +501,4 @@ valid_ingredients: Set[str] = stacking_items | fluids
# cleanup async helpers
pool.shutdown()
del pool
del factorio_tech_id

View File

@ -6,6 +6,9 @@ import typing
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification
from worlds.AutoWorld import World, WebWorld
from worlds.LauncherComponents import Component, components
from worlds.generic import Rules
from .Locations import location_pools, location_table
from .Mod import generate_mod
from .Options import factorio_options, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution
from .Shapes import get_shapes
@ -14,8 +17,6 @@ from .Technologies import base_tech_table, recipe_sources, base_technology_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, \
fluids, stacking_items, valid_ingredients, progressive_rows
from .Locations import location_pools, location_table
from worlds.LauncherComponents import Component, components
components.append(Component("Factorio Client", "FactorioClient"))
@ -64,18 +65,19 @@ class Factorio(World):
item_name_groups = {
"Progressive": set(progressive_tech_table.keys()),
}
data_version = 7
required_client_version = (0, 3, 6)
data_version = 8
required_client_version = (0, 4, 0)
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()
tech_mix: int = 0
skip_silo: bool = False
science_locations: typing.List[FactorioScienceLocation]
def __init__(self, world, player: int):
super(Factorio, self).__init__(world, player)
self.advancement_technologies = set()
self.custom_recipes = {}
self.locations = []
self.science_locations = []
generate_output = generate_mod
@ -115,18 +117,18 @@ class Factorio(World):
raise Exception("Too many traps for too few locations. Either decrease the trap count, "
f"or increase the location count (higher max science pack). (Player {self.player})") from e
self.locations = [FactorioScienceLocation(player, loc_name, self.location_name_to_id[loc_name], nauvis)
self.science_locations = [FactorioScienceLocation(player, loc_name, self.location_name_to_id[loc_name], nauvis)
for loc_name in location_names]
distribution: TechCostDistribution = self.multiworld.tech_cost_distribution[self.player]
min_cost = self.multiworld.min_tech_cost[self.player]
max_cost = self.multiworld.max_tech_cost[self.player]
if distribution == distribution.option_even:
rand_values = (random.randint(min_cost, max_cost) for _ in self.locations)
rand_values = (random.randint(min_cost, max_cost) for _ in self.science_locations)
else:
mode = {distribution.option_low: min_cost,
distribution.option_middle: (min_cost+max_cost)//2,
distribution.option_high: max_cost}[distribution.value]
rand_values = (random.triangular(min_cost, max_cost, mode) for _ in self.locations)
rand_values = (random.triangular(min_cost, max_cost, mode) for _ in self.science_locations)
rand_values = sorted(rand_values)
if self.multiworld.ramping_tech_costs[self.player]:
def sorter(loc: FactorioScienceLocation):
@ -134,10 +136,10 @@ class Factorio(World):
else:
def sorter(loc: FactorioScienceLocation):
return loc.rel_cost
for i, location in enumerate(sorted(self.locations, key=sorter)):
for i, location in enumerate(sorted(self.science_locations, key=sorter)):
location.count = rand_values[i]
del rand_values
nauvis.locations.extend(self.locations)
nauvis.locations.extend(self.science_locations)
location = FactorioLocation(player, "Rocket Launch", None, nauvis)
nauvis.locations.append(location)
event = FactorioItem("Victory", ItemClassification.progression, None, player)
@ -154,20 +156,53 @@ class Factorio(World):
def create_items(self) -> None:
player = self.player
self.custom_technologies = self.set_custom_technologies()
self.set_custom_recipes()
traps = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery", "Atomic Rocket")
for trap_name in traps:
self.multiworld.itempool.extend(self.create_item(f"{trap_name} Trap") for _ in
range(getattr(self.multiworld,
f"{trap_name.lower().replace(' ', '_')}_traps")[player]))
want_progressives = collections.defaultdict(lambda: self.multiworld.progressive[player].
want_progressives(self.multiworld.random))
cost_sorted_locations = sorted(self.science_locations, key=lambda location: location.name)
special_index = {"automation": 0,
"logistics": 1,
"rocket-silo": -1}
loc: FactorioScienceLocation
if self.multiworld.tech_tree_information[player] == TechTreeInformation.option_full:
# mark all locations as pre-hinted
for loc in self.science_locations:
loc.revealed = True
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.multiworld.itempool.append(tech_item)
else:
loc = cost_sorted_locations[index]
if index >= 0:
# beginning techs - limit cost to 10
# as automation is not achievable yet and hand-crafting for hours is not fun gameplay
loc.count = min(loc.count, 10)
loc.place_locked_item(tech_item)
loc.revealed = True
def set_rules(self):
world = self.multiworld
player = self.player
self.custom_technologies = self.set_custom_technologies()
self.set_custom_recipes()
shapes = get_shapes(self)
if world.logic[player] != 'nologic':
from worlds.generic import Rules
for ingredient in self.multiworld.max_science_pack[self.player].get_allowed_packs():
location = world.get_location(f"Automate {ingredient}", player)
@ -182,7 +217,7 @@ class Factorio(World):
location.access_rule = lambda state, ingredient=ingredient: \
all(state.has(technology.name, player) for technology in required_technologies[ingredient])
for location in self.locations:
for location in self.science_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)
@ -209,48 +244,14 @@ 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.multiworld.progressive[player].
want_progressives(self.multiworld.random))
cost_sorted_locations = sorted(self.locations, key=lambda location: location.name)
special_index = {"automation": 0,
"logistics": 1,
"rocket-silo": -1}
loc: FactorioScienceLocation
if self.multiworld.tech_tree_information[player] == TechTreeInformation.option_full:
# mark all locations as pre-hinted
for loc in self.locations:
loc.revealed = True
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.multiworld.itempool.append(tech_item)
else:
loc = cost_sorted_locations[index]
if index >= 0:
# beginning techs - limit cost to 10
# as automation is not achievable yet and hand-crafting for hours is not fun gameplay
loc.count = min(loc.count, 10)
loc.place_locked_item(tech_item)
loc.revealed = True
map_basic_settings = self.multiworld.world_gen[player].value["basic"]
map_basic_settings = self.multiworld.world_gen[self.player].value["basic"]
if map_basic_settings.get("seed", None) is None: # allow seed 0
map_basic_settings["seed"] = self.multiworld.per_slot_randoms[player].randint(0, 2 ** 32 - 1) # 32 bit uint
# 32 bit uint
map_basic_settings["seed"] = self.multiworld.per_slot_randoms[self.player].randint(0, 2 ** 32 - 1)
start_location_hints: typing.Set[str] = self.multiworld.start_location_hints[self.player].value
for loc in self.locations:
for loc in self.science_locations:
# show start_location_hints ingame
if loc.name in start_location_hints:
loc.revealed = True