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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 294 additions and 233 deletions

View File

@ -37,7 +37,7 @@ class Version(typing.NamedTuple):
build: int
__version__ = "0.3.5"
__version__ = "0.3.6"
version_tuple = tuplize_version(__version__)
is_linux = sys.platform.startswith("linux")

View File

@ -0,0 +1,32 @@
from typing import Dict, List
from .Technologies import factorio_base_id, factorio_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
prefix: str = f"AP-{i}-"
pools[pack] = [prefix + hex(int(x * scale))[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
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
del pool

View File

@ -1,23 +1,23 @@
"""Outputs a Factorio Mod to facilitate integration with Archipelago"""
import os
import zipfile
from typing import Optional
import threading
import json
import os
import shutil
import threading
import zipfile
from typing import Optional, TYPE_CHECKING
import jinja2
import shutil
import Utils
import Patch
import worlds.AutoWorld
import worlds.Files
from . import Options
from .Technologies import tech_table, recipes, free_sample_exclusions, progressive_technology_table, \
base_tech_table, tech_to_progressive_lookup, fluids
if TYPE_CHECKING:
from . import Factorio
template_env: Optional[jinja2.Environment] = None
data_template: Optional[jinja2.Template] = None
@ -75,7 +75,7 @@ class FactorioModFile(worlds.Files.APContainer):
super(FactorioModFile, self).write_contents(opened_zipfile)
def generate_mod(world, output_directory: str):
def generate_mod(world: "Factorio", output_directory: str):
player = world.player
multiworld = world.world
global data_final_template, locale_template, control_template, data_template, settings_template
@ -95,18 +95,10 @@ def generate_mod(world, output_directory: str):
control_template = template_env.get_template("control.lua")
settings_template = template_env.get_template("settings.lua")
# get data for templates
locations = []
for location in multiworld.get_filled_locations(player):
if location.address:
locations.append((location.name, location.item.name, location.item.player, location.item.advancement))
locations = [(location, location.item)
for location in world.locations]
mod_name = f"AP-{multiworld.seed_name}-P{player}-{multiworld.get_file_safe_player_name(player)}"
tech_cost_scale = {0: 0.1,
1: 0.25,
2: 0.5,
3: 1,
4: 2,
5: 5,
6: 10}[multiworld.tech_cost[player].value]
random = multiworld.slot_seeds[player]
def flop_random(low, high, base=None):
@ -120,18 +112,19 @@ def generate_mod(world, output_directory: str):
return random.uniform(low, high)
template_data = {
"locations": locations, "player_names": multiworld.player_name, "tech_table": tech_table,
"base_tech_table": base_tech_table, "tech_to_progressive_lookup": tech_to_progressive_lookup,
"locations": locations,
"player_names": multiworld.player_name,
"tech_table": tech_table,
"base_tech_table": base_tech_table,
"tech_to_progressive_lookup": tech_to_progressive_lookup,
"mod_name": mod_name,
"allowed_science_packs": multiworld.max_science_pack[player].get_allowed_packs(),
"tech_cost_scale": tech_cost_scale,
"custom_technologies": multiworld.worlds[player].custom_technologies,
"tech_tree_layout_prerequisites": multiworld.tech_tree_layout_prerequisites[player],
"slot_name": multiworld.player_name[player], "seed_name": multiworld.seed_name,
"slot_player": player,
"starting_items": multiworld.starting_items[player], "recipes": recipes,
"random": random, "flop_random": flop_random,
"static_nodes": multiworld.worlds[player].static_nodes,
"recipe_time_scale": recipe_time_scales.get(multiworld.recipe_time[player].value, None),
"recipe_time_range": recipe_time_ranges.get(multiworld.recipe_time[player].value, None),
"free_sample_blacklist": {item: 1 for item in free_sample_exclusions},
@ -141,7 +134,7 @@ def generate_mod(world, output_directory: str):
"max_science_pack": multiworld.max_science_pack[player].value,
"liquids": fluids,
"goal": multiworld.goal[player].value,
"energy_link": multiworld.energy_link[player].value
"energy_link": multiworld.energy_link[player].value,
}
for factorio_option in Options.factorio_options:

View File

@ -41,17 +41,30 @@ class Goal(Choice):
default = 0
class TechCost(Choice):
"""How expensive are the technologies."""
display_name = "Technology Cost Scale"
option_very_easy = 0
option_easy = 1
option_kind = 2
option_normal = 3
option_hard = 4
option_very_hard = 5
option_insane = 6
default = 3
class TechCost(Range):
range_start = 1
range_end = 10000
default = 5
class MinTechCost(TechCost):
"""The cheapest a Technology can be in Science Packs."""
display_name = "Minimum Science Pack Cost"
default = 5
class MaxTechCost(TechCost):
"""The most expensive a Technology can be in Science Packs."""
display_name = "Maximum Science Pack Cost"
default = 500
class TechCostMix(Range):
"""Percent chance that a preceding Science Pack is also required.
Chance is rolled per preceding pack."""
display_name = "Science Pack Cost Mix"
range_end = 100
default = 70
class Silo(Choice):
@ -168,7 +181,7 @@ class FactorioFreeSampleWhitelist(OptionSet):
class TrapCount(Range):
range_end = 4
range_end = 25
class AttackTrapCount(TrapCount):
@ -343,7 +356,9 @@ factorio_options: typing.Dict[str, type(Option)] = {
"max_science_pack": MaxSciencePack,
"goal": Goal,
"tech_tree_layout": TechTreeLayout,
"tech_cost": TechCost,
"min_tech_cost": MinTechCost,
"max_tech_cost": MaxTechCost,
"tech_cost_mix": TechCostMix,
"silo": Silo,
"satellite": Satellite,
"free_samples": FreeSamples,

View File

@ -1,8 +1,11 @@
from typing import Dict, List, Set
from typing import Dict, List, Set, TYPE_CHECKING
from collections import deque
from .Options import TechTreeLayout
if TYPE_CHECKING:
from . import Factorio, FactorioScienceLocation
funnel_layers = {TechTreeLayout.option_small_funnels: 3,
TechTreeLayout.option_medium_funnels: 4,
TechTreeLayout.option_large_funnels: 5}
@ -12,24 +15,26 @@ funnel_slice_sizes = {TechTreeLayout.option_small_funnels: 6,
TechTreeLayout.option_large_funnels: 15}
def get_shapes(factorio_world) -> Dict[str, List[str]]:
def _sorter(location: "FactorioScienceLocation"):
return location.complexity, location.rel_cost
def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]:
world = factorio_world.world
player = factorio_world.player
prerequisites: Dict[str, Set[str]] = {}
prerequisites: Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]] = {}
layout = world.tech_tree_layout[player].value
custom_technologies = factorio_world.custom_technologies
tech_names: List[str] = list(set(custom_technologies) - world.worlds[player].static_nodes)
tech_names.sort()
world.random.shuffle(tech_names)
locations: List["FactorioScienceLocation"] = sorted(factorio_world.locations, key=lambda loc: loc.name)
world.random.shuffle(locations)
if layout == TechTreeLayout.option_single:
pass
elif layout == TechTreeLayout.option_small_diamonds:
slice_size = 4
while len(tech_names) > slice_size:
slice = tech_names[:slice_size]
tech_names = tech_names[slice_size:]
slice.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
while len(locations) > slice_size:
slice = locations[:slice_size]
locations = locations[slice_size:]
slice.sort(key=_sorter)
diamond_0, diamond_1, diamond_2, diamond_3 = slice
# 0 |
@ -40,10 +45,10 @@ def get_shapes(factorio_world) -> Dict[str, List[str]]:
elif layout == TechTreeLayout.option_medium_diamonds:
slice_size = 9
while len(tech_names) > slice_size:
slice = tech_names[:slice_size]
tech_names = tech_names[slice_size:]
slice.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
while len(locations) > slice_size:
slice = locations[:slice_size]
locations = locations[slice_size:]
slice.sort(key=_sorter)
# 0 |
# 1 2 |
@ -65,10 +70,10 @@ def get_shapes(factorio_world) -> Dict[str, List[str]]:
elif layout == TechTreeLayout.option_large_diamonds:
slice_size = 16
while len(tech_names) > slice_size:
slice = tech_names[:slice_size]
tech_names = tech_names[slice_size:]
slice.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
while len(locations) > slice_size:
slice = locations[:slice_size]
locations = locations[slice_size:]
slice.sort(key=_sorter)
# 0 |
# 1 2 |
@ -101,10 +106,10 @@ def get_shapes(factorio_world) -> Dict[str, List[str]]:
elif layout == TechTreeLayout.option_small_pyramids:
slice_size = 6
while len(tech_names) > slice_size:
slice = tech_names[:slice_size]
tech_names = tech_names[slice_size:]
slice.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
while len(locations) > slice_size:
slice = locations[:slice_size]
locations = locations[slice_size:]
slice.sort(key=_sorter)
# 0 |
# 1 2 |
@ -119,10 +124,10 @@ def get_shapes(factorio_world) -> Dict[str, List[str]]:
elif layout == TechTreeLayout.option_medium_pyramids:
slice_size = 10
while len(tech_names) > slice_size:
slice = tech_names[:slice_size]
tech_names = tech_names[slice_size:]
slice.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
while len(locations) > slice_size:
slice = locations[:slice_size]
locations = locations[slice_size:]
slice.sort(key=_sorter)
# 0 |
# 1 2 |
@ -144,10 +149,10 @@ def get_shapes(factorio_world) -> Dict[str, List[str]]:
elif layout == TechTreeLayout.option_large_pyramids:
slice_size = 15
while len(tech_names) > slice_size:
slice = tech_names[:slice_size]
tech_names = tech_names[slice_size:]
slice.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
while len(locations) > slice_size:
slice = locations[:slice_size]
locations = locations[slice_size:]
slice.sort(key=_sorter)
# 0 |
# 1 2 |
@ -176,17 +181,17 @@ def get_shapes(factorio_world) -> Dict[str, List[str]]:
elif layout in funnel_layers:
slice_size = funnel_slice_sizes[layout]
world.random.shuffle(tech_names)
world.random.shuffle(locations)
while len(tech_names) > slice_size:
tech_names = tech_names[slice_size:]
current_tech_names = tech_names[:slice_size]
while len(locations) > slice_size:
locations = locations[slice_size:]
current_locations = locations[:slice_size]
layer_size = funnel_layers[layout]
previous_slice = []
current_tech_names.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
current_locations.sort(key=_sorter)
for layer in range(funnel_layers[layout]):
slice = current_tech_names[:layer_size]
current_tech_names = current_tech_names[layer_size:]
slice = current_locations[:layer_size]
current_locations = current_locations[layer_size:]
if previous_slice:
for i, tech_name in enumerate(slice):
prerequisites.setdefault(tech_name, set()).update(previous_slice[i:i+2])
@ -202,10 +207,10 @@ def get_shapes(factorio_world) -> Dict[str, List[str]]:
# 15 |
# 16 |
slice_size = 17
while len(tech_names) > slice_size:
slice = tech_names[:slice_size]
tech_names = tech_names[slice_size:]
slice.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
while len(locations) > slice_size:
slice = locations[:slice_size]
locations = locations[slice_size:]
slice.sort(key=_sorter)
prerequisites[slice[1]] = {slice[0]}
prerequisites[slice[2]] = {slice[0]}
@ -229,13 +234,13 @@ def get_shapes(factorio_world) -> Dict[str, List[str]]:
prerequisites[slice[15]] = {slice[9], slice[10], slice[11], slice[12], slice[13], slice[14]}
prerequisites[slice[16]] = {slice[15]}
elif layout == TechTreeLayout.option_choices:
tech_names.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
current_choices = deque([tech_names[0]])
tech_names = tech_names[1:]
while len(tech_names) > 1:
locations.sort(key=_sorter)
current_choices = deque([locations[0]])
locations = locations[1:]
while len(locations) > 1:
source = current_choices.pop()
choices = tech_names[:2]
tech_names = tech_names[2:]
choices = locations[:2]
locations = locations[2:]
for choice in choices:
prerequisites[choice] = {source}
current_choices.extendleft(choices)

View File

@ -36,7 +36,7 @@ technology_table: Dict[str, Technology] = {}
always = lambda state: True
class FactorioElement():
class FactorioElement:
name: str
def __repr__(self):
@ -98,7 +98,7 @@ class CustomTechnology(Technology):
and ((ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"})
or origin.name == "rocket-silo")
self.player = player
if origin.name not in world.worlds[player].static_nodes:
if origin.name not in world.worlds[player].special_nodes:
if military_allowed:
ingredients.add("military-science-pack")
ingredients = list(ingredients)

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()]

View File

@ -1,4 +1,4 @@
{% from "macros.lua" import dict_to_recipe %}
{% from "macros.lua" import dict_to_recipe, variable_to_lua %}
-- this file gets written automatically by the Archipelago Randomizer and is in its raw form a Jinja2 Template
require('lib')
data.raw["rocket-silo"]["rocket-silo"].fluid_boxes = {
@ -50,16 +50,8 @@ data.raw["recipe"]["{{recipe_name}}"].ingredients = {{ dict_to_recipe(recipe.ing
{%- endfor %}
local technologies = data.raw["technology"]
local original_tech
local new_tree_copy
allowed_ingredients = {}
{%- for tech_name, technology in custom_technologies.items() %}
allowed_ingredients["{{ tech_name }}"] = {
{%- for ingredient in technology.ingredients %}
["{{ingredient}}"] = 1,
{%- endfor %}
}
{% endfor %}
local template_tech = table.deepcopy(technologies["automation"])
{#- ensure the copy unlocks nothing #}
template_tech.unlocks = {}
@ -87,39 +79,6 @@ template_tech.prerequisites = {}
data.raw["recipe"]["rocket-silo"].enabled = true
{% endif %}
function prep_copy(new_copy, old_tech)
old_tech.hidden = true
local ingredient_filter = allowed_ingredients[old_tech.name]
if ingredient_filter ~= nil then
if mods["science-not-invited"] then
local weights = {
["automation-science-pack"] = 0, -- Red science
["logistic-science-pack"] = 0, -- Green science
["military-science-pack"] = 0, -- Black science
["chemical-science-pack"] = 0, -- Blue science
["production-science-pack"] = 0, -- Purple science
["utility-science-pack"] = 0, -- Yellow science
["space-science-pack"] = 0 -- Space science
}
for key, value in pairs(ingredient_filter) do
weights[key] = value
end
SNI.setWeights(weights)
-- Just in case an ingredient is being added to an existing tech. Found the root cause of the 9.223e+18 problem.
-- Turns out science-not-invited was ultimately dividing by zero, due to it being unaware of there being added ingredients.
old_tech.unit.ingredients = add_ingredients(old_tech.unit.ingredients, ingredient_filter)
SNI.sendInvite(old_tech)
-- SCIENCE-not-invited could potentially make tech cost 9.223e+18.
old_tech.unit.count = math.min(100000, old_tech.unit.count)
end
new_copy.unit = table.deepcopy(old_tech.unit)
new_copy.unit.ingredients = filter_ingredients(new_copy.unit.ingredients, ingredient_filter)
new_copy.unit.ingredients = add_ingredients(new_copy.unit.ingredients, ingredient_filter)
else
new_copy.unit = table.deepcopy(old_tech.unit)
end
end
function set_ap_icon(tech)
tech.icon = "__{{ mod_name }}__/graphics/icons/ap.png"
tech.icons = nil
@ -198,38 +157,40 @@ end
data.raw["ammo"]["artillery-shell"].stack_size = 10
{# each randomized tech gets set to be invisible, with new nodes added that trigger those #}
{%- for original_tech_name, item_name, receiving_player, advancement in locations %}
original_tech = technologies["{{original_tech_name}}"]
{%- for original_tech_name in base_tech_table -%}
technologies["{{ original_tech_name }}"].hidden = true
{% endfor %}
{%- for location, item in locations %}
{#- the tech researched by the local player #}
new_tree_copy = table.deepcopy(template_tech)
new_tree_copy.name = "ap-{{ tech_table[original_tech_name] }}-"{# use AP ID #}
prep_copy(new_tree_copy, original_tech)
{% if tech_cost_scale != 1 %}
new_tree_copy.unit.count = math.max(1, math.floor(new_tree_copy.unit.count * {{ tech_cost_scale }}))
{% endif %}
{%- if (tech_tree_information == 2 or original_tech_name in static_nodes) and item_name in base_tech_table -%}
{#- copy Factorio Technology Icon -#}
copy_factorio_icon(new_tree_copy, "{{ item_name }}")
{%- if original_tech_name == "rocket-silo" and original_tech_name in static_nodes %}
new_tree_copy.name = "ap-{{ location.address }}-"{# use AP ID #}
new_tree_copy.unit.count = {{ location.count }}
new_tree_copy.unit.ingredients = {{ variable_to_lua(location.factorio_ingredients) }}
{%- if location.revealed and item.name in base_tech_table -%}
{#- copy Factorio Technology Icon #}
copy_factorio_icon(new_tree_copy, "{{ item.name }}")
{%- if item.name == "rocket-silo" and item.player == location.player %}
{%- for ingredient in custom_recipes["rocket-part"].ingredients %}
table.insert(new_tree_copy.effects, {type = "nothing", effect_description = "Ingredient {{ loop.index }}: {{ ingredient }}"})
{% endfor -%}
{% endif -%}
{%- elif (tech_tree_information == 2 or original_tech_name in static_nodes) and item_name in progressive_technology_table -%}
copy_factorio_icon(new_tree_copy, "{{ progressive_technology_table[item_name][0] }}")
{%- elif location.revealed and item.name in progressive_technology_table -%}
copy_factorio_icon(new_tree_copy, "{{ progressive_technology_table[item.name][0] }}")
{%- else -%}
{#- use default AP icon if no Factorio graphics exist -#}
{% if advancement or not tech_tree_information %}set_ap_icon(new_tree_copy){% else %}set_ap_unimportant_icon(new_tree_copy){% endif %}
{% if item.advancement or not tech_tree_information %}set_ap_icon(new_tree_copy){% else %}set_ap_unimportant_icon(new_tree_copy){% endif %}
{%- endif -%}
{#- connect Technology #}
{%- if original_tech_name in tech_tree_layout_prerequisites %}
{%- for prerequisite in tech_tree_layout_prerequisites[original_tech_name] %}
table.insert(new_tree_copy.prerequisites, "ap-{{ tech_table[prerequisite] }}-")
{%- if location in tech_tree_layout_prerequisites %}
{%- for prerequisite in tech_tree_layout_prerequisites[location] %}
table.insert(new_tree_copy.prerequisites, "ap-{{ prerequisite.address }}-")
{% endfor %}
{% endif -%}
{#- add new Technology to game #}
data:extend{new_tree_copy}
{% endfor %}
{#- Recipe Rando #}
{% if recipe_time_scale %}
{%- for recipe_name, recipe in recipes.items() %}
{%- if recipe.category not in ("basic-solid", "basic-fluid") %}

View File

@ -5,22 +5,22 @@ archipelago=Archipelago
archipelago=World preset created by the Archipelago Randomizer. World may or may not contain actual archipelagos.
[technology-name]
{% for original_tech_name, item_name, receiving_player, advancement in locations %}
{%- if tech_tree_information == 2 or original_tech_name in static_nodes %}
ap-{{ tech_table[original_tech_name] }}-={{ player_names[receiving_player] }}'s {{ item_name }}
{% for location, item in locations %}
{%- if location.revealed %}
ap-{{ location.address }}-={{ player_names[item.player] }}'s {{ item.name }} ({{ location.name }})
{%- else %}
ap-{{ tech_table[original_tech_name] }}-=An Archipelago Sendable
ap-{{ location.address }}-= {{location.name}}
{%- endif -%}
{% endfor %}
[technology-description]
{% for original_tech_name, item_name, receiving_player, advancement in locations %}
{%- if tech_tree_information == 2 or original_tech_name in static_nodes %}
ap-{{ tech_table[original_tech_name] }}-=Researching this technology sends {{ item_name }} to {{ player_names[receiving_player] }}{% if advancement %}, which is considered a logical advancement{% endif %}.
{%- elif tech_tree_information == 1 and advancement %}
ap-{{ tech_table[original_tech_name] }}-=Researching this technology sends something to someone, which is considered a logical advancement. For purposes of hints, this location is called "{{ original_tech_name }}".
{% for location, item in locations %}
{%- if location.revealed %}
ap-{{ location.address }}-=Researching this technology sends {{ item.name }} to {{ player_names[item.player] }}{% if item.advancement %}, which is considered a logical advancement{% elif item.useful %}, which is considered useful{% elif item.trap %}, which is considered fun{% endif %}.
{%- elif tech_tree_information == 1 and item.advancement %}
ap-{{ location.address }}-=Researching this technology sends something to someone, which is considered a logical advancement.
{%- else %}
ap-{{ tech_table[original_tech_name] }}-=Researching this technology sends something to someone. For purposes of hints, this location is called "{{ original_tech_name }}".
ap-{{ location.address }}-=Researching this technology sends something to someone.
{%- endif -%}
{% endfor %}

View File

@ -4,7 +4,7 @@
["{{ key }}"] = {{ variable_to_lua(value) }}{% if not loop.last %},{% endif %}
{% endfor -%}
}
{%- endmacro %}
{% endmacro %}
{% macro list_to_lua(list) -%}
{
{%- for key in list -%}