Compare commits

...

16 Commits

Author SHA1 Message Date
CaitSith2
2524ddc075 Dark Souls 3: Use options_dataclass 2023-12-24 17:30:53 -08:00
CaitSith2
d545b78803 Clique: Use options_dataclass 2023-12-24 17:30:17 -08:00
CaitSith2
88b1c94eb2 ChecksFinder: use options_dataclass 2023-12-24 17:29:30 -08:00
CaitSith2
7742d5d804 BumperStickers: Use options_dataclass 2023-12-24 17:29:04 -08:00
CaitSith2
d3e148dcc6 Blasphemous: Use options_dataclass 2023-12-24 17:28:34 -08:00
CaitSith2
b5fccde913 Didn't mean to include this debugging line. 2023-12-24 16:50:40 -08:00
CaitSith2
55e9b0687a Factorio: Options assigned as data_class 2023-12-24 16:49:05 -08:00
CaitSith2
79e1bf351e Adventure: options assigned as data_class 2023-12-24 16:47:54 -08:00
CaitSith2
fcfea9d9aa Adventure: use options.name instead of multiworld.name[players] 2023-12-24 15:22:07 -08:00
CaitSith2
cfc5508f06 ds3: use options.name instead of multiworld.name[player] 2023-12-24 14:10:33 -08:00
CaitSith2
62cb5f1fc2 fix flake8(push) hopefully 2023-12-24 13:43:19 -08:00
CaitSith2
7e70b16656 Clique: use options.name instead of multiworld.name 2023-12-24 13:34:25 -08:00
CaitSith2
7b486b3380 BumpStick: use options.name instead of multiworld.name 2023-12-24 13:33:32 -08:00
CaitSith2
09cac0a685 Core: use options.option_name instead of multiworld.option_name 2023-12-24 12:24:31 -08:00
CaitSith2
12c583533d Blasphemous: use options.option_name instead of multiworld.option_name 2023-12-24 11:43:28 -08:00
CaitSith2
c5af28a649 Factorio: use options.option_name instead of multiworld.option_name 2023-12-24 11:41:23 -08:00
24 changed files with 422 additions and 397 deletions

View File

@@ -167,10 +167,11 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
# remove starting inventory from pool items. # remove starting inventory from pool items.
# Because some worlds don't actually create items during create_items this has to be as late as possible. # Because some worlds don't actually create items during create_items this has to be as late as possible.
if any(world.start_inventory_from_pool[player].value for player in world.player_ids): if any(getattr(world.worlds[player].options, "start_inventory_from_pool", StartInventoryPool({})).value for player in world.player_ids):
new_items: List[Item] = [] new_items: List[Item] = []
depletion_pool: Dict[int, Dict[str, int]] = { depletion_pool: Dict[int, Dict[str, int]] = {
player: world.start_inventory_from_pool[player].value.copy() for player in world.player_ids} player: getattr(world.worlds[player].options, "start_inventory_from_pool", StartInventoryPool({})).value.copy()
for player in world.player_ids}
for player, items in depletion_pool.items(): for player, items in depletion_pool.items():
player_world: AutoWorld.World = world.worlds[player] player_world: AutoWorld.World = world.worlds[player]
for count in items.values(): for count in items.values():

View File

@@ -2,7 +2,9 @@ from __future__ import annotations
from typing import Dict from typing import Dict
from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions
from dataclasses import dataclass
class FreeincarnateMax(Range): class FreeincarnateMax(Range):
@@ -224,21 +226,20 @@ class StartCastle(Choice):
default = option_yellow default = option_yellow
adventure_option_definitions: Dict[str, type(Option)] = { @dataclass
"dragon_slay_check": DragonSlayCheck, class AdventureOptions(PerGameCommonOptions):
"death_link": DeathLink, dragon_slay_check: DragonSlayCheck
"bat_logic": BatLogic, death_link: DeathLink
"freeincarnate_max": FreeincarnateMax, bat_logic: BatLogic
"dragon_rando_type": DragonRandoType, freeincarnate_max: FreeincarnateMax
"connector_multi_slot": ConnectorMultiSlot, dragon_rando_type: DragonRandoType
"yorgle_speed": YorgleStartingSpeed, connector_multi_slot: ConnectorMultiSlot
"yorgle_min_speed": YorgleMinimumSpeed, yorgle_speed: YorgleStartingSpeed
"grundle_speed": GrundleStartingSpeed, yorgle_min_speed: YorgleMinimumSpeed
"grundle_min_speed": GrundleMinimumSpeed, grundle_speed: GrundleStartingSpeed
"rhindle_speed": RhindleStartingSpeed, grundle_min_speed: GrundleMinimumSpeed
"rhindle_min_speed": RhindleMinimumSpeed, rhindle_speed: RhindleStartingSpeed
"difficulty_switch_a": DifficultySwitchA, rhindle_min_speed: RhindleMinimumSpeed
"difficulty_switch_b": DifficultySwitchB, difficulty_switch_a: DifficultySwitchA
"start_castle": StartCastle, difficulty_switch_b: DifficultySwitchB
start_castle: StartCastle
}

View File

@@ -1,5 +1,6 @@
from BaseClasses import MultiWorld, Region, Entrance, LocationProgressType from BaseClasses import MultiWorld, Region, Entrance, LocationProgressType
from .Locations import location_table, LocationData, AdventureLocation, dragon_room_to_region from .Locations import location_table, LocationData, AdventureLocation, dragon_room_to_region
from Options import PerGameCommonOptions
def connect(world: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True, def connect(world: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True,
@@ -24,7 +25,7 @@ def connect(world: MultiWorld, player: int, source: str, target: str, rule: call
connect(world, player, target, source, rule, True) connect(world, player, target, source, rule, True)
def create_regions(multiworld: MultiWorld, player: int, dragon_rooms: []) -> None: def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player: int, dragon_rooms: []) -> None:
for name, locdata in location_table.items(): for name, locdata in location_table.items():
locdata.get_position(multiworld.random) locdata.get_position(multiworld.random)
@@ -76,7 +77,7 @@ def create_regions(multiworld: MultiWorld, player: int, dragon_rooms: []) -> Non
credits_room_far_side.exits.append(Entrance(player, "CreditsFromFarSide", credits_room_far_side)) credits_room_far_side.exits.append(Entrance(player, "CreditsFromFarSide", credits_room_far_side))
multiworld.regions.append(credits_room_far_side) multiworld.regions.append(credits_room_far_side)
dragon_slay_check = multiworld.dragon_slay_check[player].value dragon_slay_check = options.dragon_slay_check.value
priority_locations = determine_priority_locations(multiworld, dragon_slay_check) priority_locations = determine_priority_locations(multiworld, dragon_slay_check)
for name, location_data in location_table.items(): for name, location_data in location_table.items():

View File

@@ -6,7 +6,8 @@ from BaseClasses import LocationProgressType
def set_rules(self) -> None: def set_rules(self) -> None:
world = self.multiworld world = self.multiworld
use_bat_logic = world.bat_logic[self.player].value == BatLogic.option_use_logic options = self.options
use_bat_logic = options.bat_logic.value == BatLogic.option_use_logic
set_rule(world.get_entrance("YellowCastlePort", self.player), set_rule(world.get_entrance("YellowCastlePort", self.player),
lambda state: state.has("Yellow Key", self.player)) lambda state: state.has("Yellow Key", self.player))
@@ -28,7 +29,7 @@ def set_rules(self) -> None:
lambda state: state.has("Bridge", self.player) or lambda state: state.has("Bridge", self.player) or
state.has("Magnet", self.player)) state.has("Magnet", self.player))
dragon_slay_check = world.dragon_slay_check[self.player].value dragon_slay_check = options.dragon_slay_check.value
if dragon_slay_check: if dragon_slay_check:
if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item: if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item:
set_rule(world.get_location("Slay Yorgle", self.player), set_rule(world.get_location("Slay Yorgle", self.player),

View File

@@ -15,7 +15,7 @@ from Options import AssembleOptions
from worlds.AutoWorld import WebWorld, World from worlds.AutoWorld import WebWorld, World
from Fill import fill_restrictive from Fill import fill_restrictive
from worlds.generic.Rules import add_rule, set_rule from worlds.generic.Rules import add_rule, set_rule
from .Options import adventure_option_definitions, DragonRandoType, DifficultySwitchA, DifficultySwitchB from .Options import AdventureOptions, DragonRandoType, DifficultySwitchA, DifficultySwitchB
from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, \ from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, \
AdventureAutoCollectLocation AdventureAutoCollectLocation
from .Items import item_table, ItemData, nothing_item_id, event_table, AdventureItem, standard_item_max from .Items import item_table, ItemData, nothing_item_id, event_table, AdventureItem, standard_item_max
@@ -109,7 +109,8 @@ class AdventureWorld(World):
game: ClassVar[str] = "Adventure" game: ClassVar[str] = "Adventure"
web: ClassVar[WebWorld] = AdventureWeb() web: ClassVar[WebWorld] = AdventureWeb()
option_definitions: ClassVar[Dict[str, AssembleOptions]] = adventure_option_definitions options = AdventureOptions
options_dataclass = AdventureOptions
settings: ClassVar[AdventureSettings] settings: ClassVar[AdventureSettings]
item_name_to_id: ClassVar[Dict[str, int]] = {name: data.id for name, data in item_table.items()} item_name_to_id: ClassVar[Dict[str, int]] = {name: data.id for name, data in item_table.items()}
location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()} location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()}
@@ -150,18 +151,18 @@ class AdventureWorld(World):
bytearray(f"ADVENTURE{__version__.replace('.', '')[:3]}_{self.player}_{self.multiworld.seed}", "utf8")[:21] bytearray(f"ADVENTURE{__version__.replace('.', '')[:3]}_{self.player}_{self.multiworld.seed}", "utf8")[:21]
self.rom_name.extend([0] * (21 - len(self.rom_name))) self.rom_name.extend([0] * (21 - len(self.rom_name)))
self.dragon_rando_type = self.multiworld.dragon_rando_type[self.player].value self.dragon_rando_type = self.options.dragon_rando_type.value
self.dragon_slay_check = self.multiworld.dragon_slay_check[self.player].value self.dragon_slay_check = self.options.dragon_slay_check.value
self.connector_multi_slot = self.multiworld.connector_multi_slot[self.player].value self.connector_multi_slot = self.options.connector_multi_slot.value
self.yorgle_speed = self.multiworld.yorgle_speed[self.player].value self.yorgle_speed = self.options.yorgle_speed.value
self.yorgle_min_speed = self.multiworld.yorgle_min_speed[self.player].value self.yorgle_min_speed = self.options.yorgle_min_speed.value
self.grundle_speed = self.multiworld.grundle_speed[self.player].value self.grundle_speed = self.options.grundle_speed.value
self.grundle_min_speed = self.multiworld.grundle_min_speed[self.player].value self.grundle_min_speed = self.options.grundle_min_speed.value
self.rhindle_speed = self.multiworld.rhindle_speed[self.player].value self.rhindle_speed = self.options.rhindle_speed.value
self.rhindle_min_speed = self.multiworld.rhindle_min_speed[self.player].value self.rhindle_min_speed = self.options.rhindle_min_speed.value
self.difficulty_switch_a = self.multiworld.difficulty_switch_a[self.player].value self.difficulty_switch_a = self.options.difficulty_switch_a.value
self.difficulty_switch_b = self.multiworld.difficulty_switch_b[self.player].value self.difficulty_switch_b = self.options.difficulty_switch_b.value
self.start_castle = self.multiworld.start_castle[self.player].value self.start_castle = self.options.start_castle.value
self.created_items = 0 self.created_items = 0
if self.dragon_slay_check == 0: if self.dragon_slay_check == 0:
@@ -228,7 +229,7 @@ class AdventureWorld(World):
extra_filler_count = num_locations - self.created_items extra_filler_count = num_locations - self.created_items
# traps would probably go here, if enabled # traps would probably go here, if enabled
freeincarnate_max = self.multiworld.freeincarnate_max[self.player].value freeincarnate_max = self.options.freeincarnate_max.value
actual_freeincarnates = min(extra_filler_count, freeincarnate_max) actual_freeincarnates = min(extra_filler_count, freeincarnate_max)
self.multiworld.itempool += [self.create_item("Freeincarnate") for _ in range(actual_freeincarnates)] self.multiworld.itempool += [self.create_item("Freeincarnate") for _ in range(actual_freeincarnates)]
self.created_items += actual_freeincarnates self.created_items += actual_freeincarnates
@@ -248,7 +249,7 @@ class AdventureWorld(World):
self.created_items += 1 self.created_items += 1
def create_regions(self) -> None: def create_regions(self) -> None:
create_regions(self.multiworld, self.player, self.dragon_rooms) create_regions(self.options, self.multiworld, self.player, self.dragon_rooms)
set_rules = set_rules set_rules = set_rules
@@ -355,7 +356,7 @@ class AdventureWorld(World):
auto_collect_locations: [AdventureAutoCollectLocation] = [] auto_collect_locations: [AdventureAutoCollectLocation] = []
local_item_to_location: {int, int} = {} local_item_to_location: {int, int} = {}
bat_no_touch_locs: [LocationData] = [] bat_no_touch_locs: [LocationData] = []
bat_logic: int = self.multiworld.bat_logic[self.player].value bat_logic: int = self.options.bat_logic.value
try: try:
rom_deltas: { int, int } = {} rom_deltas: { int, int } = {}
self.place_dragons(rom_deltas) self.place_dragons(rom_deltas)
@@ -411,7 +412,7 @@ class AdventureWorld(World):
item_position_data_start = get_item_position_data_start(unplaced_item.table_index) item_position_data_start = get_item_position_data_start(unplaced_item.table_index)
rom_deltas[item_position_data_start] = 0xff rom_deltas[item_position_data_start] = 0xff
if self.multiworld.connector_multi_slot[self.player].value: if self.options.connector_multi_slot.value:
rom_deltas[connector_port_offset] = (self.player & 0xff) rom_deltas[connector_port_offset] = (self.player & 0xff)
else: else:
rom_deltas[connector_port_offset] = 0 rom_deltas[connector_port_offset] = 0

View File

@@ -1,4 +1,5 @@
from Options import Choice, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool from Options import Choice, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool, PerGameCommonOptions
from dataclasses import dataclass
import random import random
@@ -163,26 +164,26 @@ class BlasphemousDeathLink(DeathLink):
Note that Guilt Fragments will not appear when killed by Death Link.""" Note that Guilt Fragments will not appear when killed by Death Link."""
blasphemous_options = { @dataclass
"prie_dieu_warp": PrieDieuWarp, class BlasphemousOptions(PerGameCommonOptions):
"skip_cutscenes": SkipCutscenes, prie_dieu_warp: PrieDieuWarp
"corpse_hints": CorpseHints, skip_cutscenes: SkipCutscenes
"difficulty": Difficulty, corpse_hints: CorpseHints
"penitence": Penitence, difficulty: Difficulty
"starting_location": StartingLocation, penitence: Penitence
"ending": Ending, starting_location: StartingLocation
"skip_long_quests": SkipLongQuests, ending: Ending
"thorn_shuffle" : ThornShuffle, skip_long_quests: SkipLongQuests
"dash_shuffle": DashShuffle, thorn_shuffle : ThornShuffle
"wall_climb_shuffle": WallClimbShuffle, dash_shuffle: DashShuffle
"reliquary_shuffle": ReliquaryShuffle, wall_climb_shuffle: WallClimbShuffle
"boots_of_pleading": CustomItem1, reliquary_shuffle: ReliquaryShuffle
"purified_hand": CustomItem2, boots_of_pleading: CustomItem1
"start_wheel": StartWheel, purified_hand: CustomItem2
"skill_randomizer": SkillRando, start_wheel: StartWheel
"enemy_randomizer": EnemyRando, skill_randomizer: SkillRando
"enemy_groups": EnemyGroups, enemy_randomizer: EnemyRando
"enemy_scaling": EnemyScaling, enemy_groups: EnemyGroups
"death_link": BlasphemousDeathLink, enemy_scaling: EnemyScaling
"start_inventory": StartInventoryPool death_link: BlasphemousDeathLink
} start_inventory: StartInventoryPool

View File

@@ -497,8 +497,9 @@ def chalice_rooms(state: CollectionState, player: int, number: int) -> bool:
def rules(blasphemousworld): def rules(blasphemousworld):
world = blasphemousworld.multiworld world = blasphemousworld.multiworld
player = blasphemousworld.player player = blasphemousworld.player
logic = world.difficulty[player].value options = blasphemousworld.options
enemy = world.enemy_randomizer[player].value logic = options.difficulty.value
enemy = options.enemy_randomizer.value
# D01Z01S01 (The Holy Line) # D01Z01S01 (The Holy Line)
@@ -2488,7 +2489,7 @@ def rules(blasphemousworld):
# D04Z02S01 (Mother of Mothers) # D04Z02S01 (Mother of Mothers)
# Items # Items
if world.purified_hand[player]: if options.purified_hand:
set_rule(world.get_location("MoM: Western room ledge", player), set_rule(world.get_location("MoM: Western room ledge", player),
lambda state: ( lambda state: (
state.has("D04Z02S01[N]", player) state.has("D04Z02S01[N]", player)
@@ -4093,7 +4094,7 @@ def rules(blasphemousworld):
# D17Z01S04 (Brotherhood of the Silent Sorrow) # D17Z01S04 (Brotherhood of the Silent Sorrow)
# Items # Items
if world.boots_of_pleading[player]: if options.boots_of_pleading:
set_rule(world.get_location("BotSS: 2nd meeting with Redento", player), set_rule(world.get_location("BotSS: 2nd meeting with Redento", player),
lambda state: redento(state, blasphemousworld, player, 2)) lambda state: redento(state, blasphemousworld, player, 2))
# Doors # Doors

View File

@@ -7,7 +7,7 @@ from .Locations import location_table
from .Rooms import room_table, door_table from .Rooms import room_table, door_table
from .Rules import rules from .Rules import rules
from worlds.generic.Rules import set_rule, add_rule from worlds.generic.Rules import set_rule, add_rule
from .Options import blasphemous_options from .Options import BlasphemousOptions
from .Vanilla import unrandomized_dict, junk_locations, thorn_set, skill_dict from .Vanilla import unrandomized_dict, junk_locations, thorn_set, skill_dict
@@ -39,7 +39,8 @@ class BlasphemousWorld(World):
location_name_to_game_id = {loc["name"]: loc["game_id"] for loc in location_table} location_name_to_game_id = {loc["name"]: loc["game_id"] for loc in location_table}
item_name_groups = group_table item_name_groups = group_table
option_definitions = blasphemous_options options = BlasphemousOptions
options_dataclass = BlasphemousOptions
required_client_version = (0, 4, 2) required_client_version = (0, 4, 2)
@@ -73,60 +74,61 @@ class BlasphemousWorld(World):
def generate_early(self): def generate_early(self):
options = self.options
world = self.multiworld world = self.multiworld
player = self.player player = self.player
if not world.starting_location[player].randomized: if not options.starting_location.randomized:
if world.starting_location[player].value == 6 and world.difficulty[player].value < 2: if options.starting_location.value == 6 and options.difficulty.value < 2:
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {world.starting_location[player]}" raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {options.starting_location}"
" cannot be chosen if Difficulty is lower than Hard.") " cannot be chosen if Difficulty is lower than Hard.")
if (world.starting_location[player].value == 0 or world.starting_location[player].value == 6) \ if (options.starting_location.value == 0 or options.starting_location.value == 6) \
and world.dash_shuffle[player]: and options.dash_shuffle:
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {world.starting_location[player]}" raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {options.starting_location}"
" cannot be chosen if Shuffle Dash is enabled.") " cannot be chosen if Shuffle Dash is enabled.")
if world.starting_location[player].value == 3 and world.wall_climb_shuffle[player]: if options.starting_location.value == 3 and options.wall_climb_shuffle:
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {world.starting_location[player]}" raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {options.starting_location}"
" cannot be chosen if Shuffle Wall Climb is enabled.") " cannot be chosen if Shuffle Wall Climb is enabled.")
else: else:
locations: List[int] = [ 0, 1, 2, 3, 4, 5, 6 ] locations: List[int] = [ 0, 1, 2, 3, 4, 5, 6 ]
invalid: bool = False invalid: bool = False
if world.difficulty[player].value < 2: if options.difficulty.value < 2:
locations.remove(6) locations.remove(6)
if world.dash_shuffle[player]: if options.dash_shuffle:
locations.remove(0) locations.remove(0)
if 6 in locations: if 6 in locations:
locations.remove(6) locations.remove(6)
if world.wall_climb_shuffle[player]: if options.wall_climb_shuffle:
locations.remove(3) locations.remove(3)
if world.starting_location[player].value == 6 and world.difficulty[player].value < 2: if options.starting_location.value == 6 and options.difficulty.value < 2:
invalid = True invalid = True
if (world.starting_location[player].value == 0 or world.starting_location[player].value == 6) \ if (options.starting_location.value == 0 or options.starting_location.value == 6) \
and world.dash_shuffle[player]: and options.dash_shuffle:
invalid = True invalid = True
if world.starting_location[player].value == 3 and world.wall_climb_shuffle[player]: if options.starting_location.value == 3 and options.wall_climb_shuffle:
invalid = True invalid = True
if invalid: if invalid:
world.starting_location[player].value = world.random.choice(locations) options.starting_location.value = world.random.choice(locations)
if not world.dash_shuffle[player]: if not options.dash_shuffle:
world.push_precollected(self.create_item("Dash Ability")) world.push_precollected(self.create_item("Dash Ability"))
if not world.wall_climb_shuffle[player]: if not options.wall_climb_shuffle:
world.push_precollected(self.create_item("Wall Climb Ability")) world.push_precollected(self.create_item("Wall Climb Ability"))
if world.skip_long_quests[player]: if options.skip_long_quests:
for loc in junk_locations: for loc in junk_locations:
world.exclude_locations[player].value.add(loc) options.exclude_locations.value.add(loc)
start_rooms: Dict[int, str] = { start_rooms: Dict[int, str] = {
0: "D17Z01S01", 0: "D17Z01S01",
@@ -138,12 +140,12 @@ class BlasphemousWorld(World):
6: "D20Z02S09" 6: "D20Z02S09"
} }
self.start_room = start_rooms[world.starting_location[player].value] self.start_room = start_rooms[options.starting_location.value]
def create_items(self): def create_items(self):
options = self.options
world = self.multiworld world = self.multiworld
player = self.player
removed: int = 0 removed: int = 0
to_remove: List[str] = [ to_remove: List[str] = [
@@ -157,46 +159,46 @@ class BlasphemousWorld(World):
skipped_items = [] skipped_items = []
junk: int = 0 junk: int = 0
for item, count in world.start_inventory[player].value.items(): for item, count in options.start_inventory.value.items():
for _ in range(count): for _ in range(count):
skipped_items.append(item) skipped_items.append(item)
junk += 1 junk += 1
skipped_items.extend(unrandomized_dict.values()) skipped_items.extend(unrandomized_dict.values())
if world.thorn_shuffle[player] == 2: if options.thorn_shuffle == 2:
for i in range(8): for i in range(8):
skipped_items.append("Thorn Upgrade") skipped_items.append("Thorn Upgrade")
if world.dash_shuffle[player]: if options.dash_shuffle:
skipped_items.append(to_remove[removed]) skipped_items.append(to_remove[removed])
removed += 1 removed += 1
elif not world.dash_shuffle[player]: elif not options.dash_shuffle:
skipped_items.append("Dash Ability") skipped_items.append("Dash Ability")
if world.wall_climb_shuffle[player]: if options.wall_climb_shuffle:
skipped_items.append(to_remove[removed]) skipped_items.append(to_remove[removed])
removed += 1 removed += 1
elif not world.wall_climb_shuffle[player]: elif not options.wall_climb_shuffle:
skipped_items.append("Wall Climb Ability") skipped_items.append("Wall Climb Ability")
if not world.reliquary_shuffle[player]: if not options.reliquary_shuffle:
skipped_items.extend(reliquary_set) skipped_items.extend(reliquary_set)
elif world.reliquary_shuffle[player]: elif options.reliquary_shuffle:
for i in range(3): for i in range(3):
skipped_items.append(to_remove[removed]) skipped_items.append(to_remove[removed])
removed += 1 removed += 1
if not world.boots_of_pleading[player]: if not options.boots_of_pleading:
skipped_items.append("Boots of Pleading") skipped_items.append("Boots of Pleading")
if not world.purified_hand[player]: if not options.purified_hand:
skipped_items.append("Purified Hand of the Nun") skipped_items.append("Purified Hand of the Nun")
if world.start_wheel[player]: if options.start_wheel:
skipped_items.append("The Young Mason's Wheel") skipped_items.append("The Young Mason's Wheel")
if not world.skill_randomizer[player]: if not options.skill_randomizer:
skipped_items.extend(skill_dict.values()) skipped_items.extend(skill_dict.values())
counter = Counter(skipped_items) counter = Counter(skipped_items)
@@ -219,23 +221,24 @@ class BlasphemousWorld(World):
def pre_fill(self): def pre_fill(self):
options = self.options
world = self.multiworld world = self.multiworld
player = self.player player = self.player
self.place_items_from_dict(unrandomized_dict) self.place_items_from_dict(unrandomized_dict)
if world.thorn_shuffle[player] == 2: if options.thorn_shuffle == 2:
self.place_items_from_set(thorn_set, "Thorn Upgrade") self.place_items_from_set(thorn_set, "Thorn Upgrade")
if world.start_wheel[player]: if options.start_wheel:
world.get_location("Beginning gift", player)\ world.get_location("Beginning gift", player)\
.place_locked_item(self.create_item("The Young Mason's Wheel")) .place_locked_item(self.create_item("The Young Mason's Wheel"))
if not world.skill_randomizer[player]: if not options.skill_randomizer:
self.place_items_from_dict(skill_dict) self.place_items_from_dict(skill_dict)
if world.thorn_shuffle[player] == 1: if options.thorn_shuffle == 1:
world.local_items[player].value.add("Thorn Upgrade") options.local_items.value.add("Thorn Upgrade")
def place_items_from_set(self, location_set: Set[str], name: str): def place_items_from_set(self, location_set: Set[str], name: str):
@@ -251,6 +254,7 @@ class BlasphemousWorld(World):
def create_regions(self) -> None: def create_regions(self) -> None:
options = self.options
player = self.player player = self.player
world = self.multiworld world = self.multiworld
@@ -282,9 +286,9 @@ class BlasphemousWorld(World):
}) })
for index, loc in enumerate(location_table): for index, loc in enumerate(location_table):
if not world.boots_of_pleading[player] and loc["name"] == "BotSS: 2nd meeting with Redento": if not options.boots_of_pleading and loc["name"] == "BotSS: 2nd meeting with Redento":
continue continue
if not world.purified_hand[player] and loc["name"] == "MoM: Western room ledge": if not options.purified_hand and loc["name"] == "MoM: Western room ledge":
continue continue
region: Region = world.get_region(loc["room"], player) region: Region = world.get_region(loc["room"], player)
@@ -310,9 +314,9 @@ class BlasphemousWorld(World):
victory.place_locked_item(self.create_event("Victory")) victory.place_locked_item(self.create_event("Victory"))
world.get_region("D07Z01S03", player).locations.append(victory) world.get_region("D07Z01S03", player).locations.append(victory)
if world.ending[self.player].value == 1: if options.ending.value == 1:
set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8)) set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8))
elif world.ending[self.player].value == 2: elif options.ending.value == 2:
set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8) and set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8) and
state.has("Holy Wound of Abnegation", player)) state.has("Holy Wound of Abnegation", player))
@@ -332,11 +336,12 @@ class BlasphemousWorld(World):
locations = [] locations = []
doors: Dict[str, str] = {} doors: Dict[str, str] = {}
options = self.options
world = self.multiworld world = self.multiworld
player = self.player player = self.player
thorns: bool = True thorns: bool = True
if world.thorn_shuffle[player].value == 2: if options.thorn_shuffle.value == 2:
thorns = False thorns = False
for loc in world.get_filled_locations(player): for loc in world.get_filled_locations(player):
@@ -354,28 +359,28 @@ class BlasphemousWorld(World):
locations.append(data) locations.append(data)
config = { config = {
"LogicDifficulty": world.difficulty[player].value, "LogicDifficulty": options.difficulty.value,
"StartingLocation": world.starting_location[player].value, "StartingLocation": options.starting_location.value,
"VersionCreated": "AP", "VersionCreated": "AP",
"UnlockTeleportation": bool(world.prie_dieu_warp[player].value), "UnlockTeleportation": bool(options.prie_dieu_warp.value),
"AllowHints": bool(world.corpse_hints[player].value), "AllowHints": bool(options.corpse_hints.value),
"AllowPenitence": bool(world.penitence[player].value), "AllowPenitence": bool(options.penitence.value),
"ShuffleReliquaries": bool(world.reliquary_shuffle[player].value), "ShuffleReliquaries": bool(options.reliquary_shuffle.value),
"ShuffleBootsOfPleading": bool(world.boots_of_pleading[player].value), "ShuffleBootsOfPleading": bool(options.boots_of_pleading.value),
"ShufflePurifiedHand": bool(world.purified_hand[player].value), "ShufflePurifiedHand": bool(options.purified_hand.value),
"ShuffleDash": bool(world.dash_shuffle[player].value), "ShuffleDash": bool(options.dash_shuffle.value),
"ShuffleWallClimb": bool(world.wall_climb_shuffle[player].value), "ShuffleWallClimb": bool(options.wall_climb_shuffle.value),
"ShuffleSwordSkills": bool(world.skill_randomizer[player].value), "ShuffleSwordSkills": bool(options.skill_randomizer.value),
"ShuffleThorns": thorns, "ShuffleThorns": thorns,
"JunkLongQuests": bool(world.skip_long_quests[player].value), "JunkLongQuests": bool(options.skip_long_quests.value),
"StartWithWheel": bool(world.start_wheel[player].value), "StartWithWheel": bool(options.start_wheel.value),
"EnemyShuffleType": world.enemy_randomizer[player].value, "EnemyShuffleType": options.enemy_randomizer.value,
"MaintainClass": bool(world.enemy_groups[player].value), "MaintainClass": bool(options.enemy_groups.value),
"AreaScaling": bool(world.enemy_scaling[player].value), "AreaScaling": bool(options.enemy_scaling.value),
"BossShuffleType": 0, "BossShuffleType": 0,
"DoorShuffleType": 0 "DoorShuffleType": 0
@@ -385,8 +390,8 @@ class BlasphemousWorld(World):
"locations": locations, "locations": locations,
"doors": doors, "doors": doors,
"cfg": config, "cfg": config,
"ending": world.ending[self.player].value, "ending": options.ending.value,
"death_link": bool(world.death_link[self.player].value) "death_link": bool(options.death_link.value)
} }
return slot_data return slot_data

View File

@@ -4,7 +4,8 @@
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
import typing import typing
from Options import Option, Range from Options import Option, Range, PerGameCommonOptions
from dataclasses import dataclass
class TaskAdvances(Range): class TaskAdvances(Range):
@@ -69,12 +70,12 @@ class KillerTrapWeight(Range):
default = 0 default = 0
bumpstik_options: typing.Dict[str, type(Option)] = { @dataclass
"task_advances": TaskAdvances, class BumpStikOptions(PerGameCommonOptions):
"turners": Turners, task_advances: TaskAdvances
"paint_cans": PaintCans, turners: Turners
"trap_count": Traps, paint_cans: PaintCans
"rainbow_trap_weight": RainbowTrapWeight, trap_count: Traps
"spinner_trap_weight": SpinnerTrapWeight, rainbow_trap_weight: RainbowTrapWeight
"killer_trap_weight": KillerTrapWeight spinner_trap_weight: SpinnerTrapWeight
} killer_trap_weight: KillerTrapWeight

View File

@@ -43,7 +43,8 @@ class BumpStikWorld(World):
required_client_version = (0, 3, 8) required_client_version = (0, 3, 8)
option_definitions = bumpstik_options options = BumpStikOptions
options_dataclass = BumpStikOptions
def __init__(self, world: MultiWorld, player: int): def __init__(self, world: MultiWorld, player: int):
super(BumpStikWorld, self).__init__(world, player) super(BumpStikWorld, self).__init__(world, player)
@@ -86,13 +87,13 @@ class BumpStikWorld(World):
return "Nothing" return "Nothing"
def generate_early(self): def generate_early(self):
self.task_advances = self.multiworld.task_advances[self.player].value self.task_advances = self.options.task_advances.value
self.turners = self.multiworld.turners[self.player].value self.turners = self.options.turners.value
self.paint_cans = self.multiworld.paint_cans[self.player].value self.paint_cans = self.options.paint_cans.value
self.traps = self.multiworld.trap_count[self.player].value self.traps = self.options.trap_count.value
self.rainbow_trap_weight = self.multiworld.rainbow_trap_weight[self.player].value self.rainbow_trap_weight = self.options.rainbow_trap_weight.value
self.spinner_trap_weight = self.multiworld.spinner_trap_weight[self.player].value self.spinner_trap_weight = self.options.spinner_trap_weight.value
self.killer_trap_weight = self.multiworld.killer_trap_weight[self.player].value self.killer_trap_weight = self.options.killer_trap_weight.value
def create_regions(self): def create_regions(self):
create_regions(self.multiworld, self.player) create_regions(self.multiworld, self.player)

View File

@@ -1,6 +1,7 @@
import typing import typing
from Options import Option from Options import Option, PerGameCommonOptions
from dataclasses import dataclass
@dataclass
checksfinder_options: typing.Dict[str, type(Option)] = { class ChecksFinderOptions(PerGameCommonOptions):
} pass

View File

@@ -1,9 +1,10 @@
from BaseClasses import Region, Entrance, Item, Tutorial, ItemClassification from BaseClasses import Region, Entrance, Item, Tutorial, ItemClassification
from .Items import ChecksFinderItem, item_table, required_items from .Items import ChecksFinderItem, item_table, required_items
from .Locations import ChecksFinderAdvancement, advancement_table, exclusion_table from .Locations import ChecksFinderAdvancement, advancement_table, exclusion_table
from .Options import checksfinder_options from .Options import ChecksFinderOptions
from .Rules import set_rules, set_completion_rules from .Rules import set_rules, set_completion_rules
from ..AutoWorld import World, WebWorld from ..AutoWorld import World, WebWorld
from dataclasses import fields
client_version = 7 client_version = 7
@@ -26,7 +27,8 @@ class ChecksFinderWorld(World):
with the mines! You win when you get all your items and beat the board! with the mines! You win when you get all your items and beat the board!
""" """
game: str = "ChecksFinder" game: str = "ChecksFinder"
option_definitions = checksfinder_options options = ChecksFinderOptions
options_dataclass = ChecksFinderOptions
topology_present = True topology_present = True
web = ChecksFinderWeb() web = ChecksFinderWeb()
@@ -79,8 +81,8 @@ class ChecksFinderWorld(World):
def fill_slot_data(self): def fill_slot_data(self):
slot_data = self._get_checksfinder_data() slot_data = self._get_checksfinder_data()
for option_name in checksfinder_options: for option_name in [field.name for field in fields(ChecksFinderOptions)]:
option = getattr(self.multiworld, option_name)[self.player] option = getattr(self.options, option_name)
if slot_data.get(option_name, None) is None and type(option.value) in {str, int}: if slot_data.get(option_name, None) is None and type(option.value) in {str, int}:
slot_data[option_name] = int(option.value) slot_data[option_name] = int(option.value)
return slot_data return slot_data

View File

@@ -1,6 +1,7 @@
from typing import Callable, Dict, NamedTuple, Optional from typing import Callable, Dict, NamedTuple, Optional
from BaseClasses import Item, ItemClassification, MultiWorld from BaseClasses import Item, ItemClassification
from .Options import CliqueOptions
class CliqueItem(Item): class CliqueItem(Item):
@@ -10,7 +11,7 @@ class CliqueItem(Item):
class CliqueItemData(NamedTuple): class CliqueItemData(NamedTuple):
code: Optional[int] = None code: Optional[int] = None
type: ItemClassification = ItemClassification.filler type: ItemClassification = ItemClassification.filler
can_create: Callable[[MultiWorld, int], bool] = lambda multiworld, player: True can_create: Callable[[CliqueOptions], bool] = lambda options: True
item_data_table: Dict[str, CliqueItemData] = { item_data_table: Dict[str, CliqueItemData] = {
@@ -21,11 +22,11 @@ item_data_table: Dict[str, CliqueItemData] = {
"Button Activation": CliqueItemData( "Button Activation": CliqueItemData(
code=69696968, code=69696968,
type=ItemClassification.progression, type=ItemClassification.progression,
can_create=lambda multiworld, player: bool(getattr(multiworld, "hard_mode")[player]), can_create=lambda options: bool(getattr(options, "hard_mode")),
), ),
"A Cool Filler Item (No Satisfaction Guaranteed)": CliqueItemData( "A Cool Filler Item (No Satisfaction Guaranteed)": CliqueItemData(
code=69696967, code=69696967,
can_create=lambda multiworld, player: False # Only created from `get_filler_item_name`. can_create=lambda options: False # Only created from `get_filler_item_name`.
), ),
"The Urge to Push": CliqueItemData( "The Urge to Push": CliqueItemData(
type=ItemClassification.progression, type=ItemClassification.progression,

View File

@@ -1,6 +1,8 @@
from typing import Callable, Dict, NamedTuple, Optional from typing import Callable, Dict, NamedTuple, Optional
from BaseClasses import Location, MultiWorld from BaseClasses import Location
from .Options import CliqueOptions
class CliqueLocation(Location): class CliqueLocation(Location):
@@ -10,7 +12,7 @@ class CliqueLocation(Location):
class CliqueLocationData(NamedTuple): class CliqueLocationData(NamedTuple):
region: str region: str
address: Optional[int] = None address: Optional[int] = None
can_create: Callable[[MultiWorld, int], bool] = lambda multiworld, player: True can_create: Callable[[CliqueOptions], bool] = lambda options: True
locked_item: Optional[str] = None locked_item: Optional[str] = None
@@ -22,7 +24,7 @@ location_data_table: Dict[str, CliqueLocationData] = {
"The Item on the Desk": CliqueLocationData( "The Item on the Desk": CliqueLocationData(
region="The Button Realm", region="The Button Realm",
address=69696968, address=69696968,
can_create=lambda multiworld, player: bool(getattr(multiworld, "hard_mode")[player]), can_create=lambda options: bool(getattr(options, "hard_mode")),
), ),
"In the Player's Mind": CliqueLocationData( "In the Player's Mind": CliqueLocationData(
region="The Button Realm", region="The Button Realm",

View File

@@ -1,6 +1,7 @@
from typing import Dict from typing import Dict
from Options import Choice, Option, Toggle from Options import Choice, Option, Toggle, PerGameCommonOptions
from dataclasses import dataclass
class HardMode(Toggle): class HardMode(Toggle):
@@ -25,10 +26,12 @@ class ButtonColor(Choice):
option_black = 11 option_black = 11
clique_options: Dict[str, type(Option)] = {
"color": ButtonColor, @dataclass
"hard_mode": HardMode, class CliqueOptions(PerGameCommonOptions):
color: ButtonColor
hard_mode: HardMode
# DeathLink is always on. Always. # DeathLink is always on. Always.
# "death_link": DeathLink, # death_link: DeathLink
}

View File

@@ -1,10 +1,11 @@
from typing import Callable from typing import Callable
from BaseClasses import CollectionState, MultiWorld from BaseClasses import CollectionState
from .Options import CliqueOptions
def get_button_rule(multiworld: MultiWorld, player: int) -> Callable[[CollectionState], bool]: def get_button_rule(options: CliqueOptions, player: int) -> Callable[[CollectionState], bool]:
if getattr(multiworld, "hard_mode")[player]: if getattr(options, "hard_mode"):
return lambda state: state.has("Button Activation", player) return lambda state: state.has("Button Activation", player)
return lambda state: True return lambda state: True

View File

@@ -4,7 +4,7 @@ from BaseClasses import Region, Tutorial
from worlds.AutoWorld import WebWorld, World from worlds.AutoWorld import WebWorld, World
from .Items import CliqueItem, item_data_table, item_table from .Items import CliqueItem, item_data_table, item_table
from .Locations import CliqueLocation, location_data_table, location_table, locked_locations from .Locations import CliqueLocation, location_data_table, location_table, locked_locations
from .Options import clique_options from .Options import CliqueOptions
from .Regions import region_data_table from .Regions import region_data_table
from .Rules import get_button_rule from .Rules import get_button_rule
@@ -29,7 +29,8 @@ class CliqueWorld(World):
game = "Clique" game = "Clique"
data_version = 3 data_version = 3
web = CliqueWebWorld() web = CliqueWebWorld()
option_definitions = clique_options options = CliqueOptions
options_dataclass = CliqueOptions
location_name_to_id = location_table location_name_to_id = location_table
item_name_to_id = item_table item_name_to_id = item_table
@@ -39,7 +40,7 @@ class CliqueWorld(World):
def create_items(self) -> None: def create_items(self) -> None:
item_pool: List[CliqueItem] = [] item_pool: List[CliqueItem] = []
for name, item in item_data_table.items(): for name, item in item_data_table.items():
if item.code and item.can_create(self.multiworld, self.player): if item.code and item.can_create(self.options):
item_pool.append(self.create_item(name)) item_pool.append(self.create_item(name))
self.multiworld.itempool += item_pool self.multiworld.itempool += item_pool
@@ -55,27 +56,27 @@ class CliqueWorld(World):
region = self.multiworld.get_region(region_name, self.player) region = self.multiworld.get_region(region_name, self.player)
region.add_locations({ region.add_locations({
location_name: location_data.address for location_name, location_data in location_data_table.items() location_name: location_data.address for location_name, location_data in location_data_table.items()
if location_data.region == region_name and location_data.can_create(self.multiworld, self.player) if location_data.region == region_name and location_data.can_create(self.options)
}, CliqueLocation) }, CliqueLocation)
region.add_exits(region_data_table[region_name].connecting_regions) region.add_exits(region_data_table[region_name].connecting_regions)
# Place locked locations. # Place locked locations.
for location_name, location_data in locked_locations.items(): for location_name, location_data in locked_locations.items():
# Ignore locations we never created. # Ignore locations we never created.
if not location_data.can_create(self.multiworld, self.player): if not location_data.can_create(self.options):
continue continue
locked_item = self.create_item(location_data_table[location_name].locked_item) locked_item = self.create_item(location_data_table[location_name].locked_item)
self.multiworld.get_location(location_name, self.player).place_locked_item(locked_item) self.multiworld.get_location(location_name, self.player).place_locked_item(locked_item)
# Set priority location for the Big Red Button! # Set priority location for the Big Red Button!
self.multiworld.priority_locations[self.player].value.add("The Big Red Button") self.options.priority_locations.value.add("The Big Red Button")
def get_filler_item_name(self) -> str: def get_filler_item_name(self) -> str:
return "A Cool Filler Item (No Satisfaction Guaranteed)" return "A Cool Filler Item (No Satisfaction Guaranteed)"
def set_rules(self) -> None: def set_rules(self) -> None:
button_rule = get_button_rule(self.multiworld, self.player) button_rule = get_button_rule(self.options, self.player)
self.multiworld.get_location("The Big Red Button", self.player).access_rule = button_rule self.multiworld.get_location("The Big Red Button", self.player).access_rule = button_rule
self.multiworld.get_location("In the Player's Mind", self.player).access_rule = button_rule self.multiworld.get_location("In the Player's Mind", self.player).access_rule = button_rule
@@ -88,5 +89,5 @@ class CliqueWorld(World):
def fill_slot_data(self): def fill_slot_data(self):
return { return {
"color": getattr(self.multiworld, "color")[self.player].current_key "color": getattr(self.options, "color").current_key
} }

View File

@@ -1,6 +1,7 @@
import typing import typing
from Options import Toggle, DefaultOnToggle, Option, Range, Choice, ItemDict, DeathLink from Options import Toggle, DefaultOnToggle, Option, Range, Choice, ItemDict, DeathLink, PerGameCommonOptions
from dataclasses import dataclass
class RandomizeWeaponLocations(DefaultOnToggle): class RandomizeWeaponLocations(DefaultOnToggle):
@@ -200,36 +201,36 @@ class EnableDLCOption(Toggle):
display_name = "Enable DLC" display_name = "Enable DLC"
dark_souls_options: typing.Dict[str, Option] = { @dataclass
"enable_weapon_locations": RandomizeWeaponLocations, class DarkSouls3Options(PerGameCommonOptions):
"enable_shield_locations": RandomizeShieldLocations, enable_weapon_locations: RandomizeWeaponLocations
"enable_armor_locations": RandomizeArmorLocations, enable_shield_locations: RandomizeShieldLocations
"enable_ring_locations": RandomizeRingLocations, enable_armor_locations: RandomizeArmorLocations
"enable_spell_locations": RandomizeSpellLocations, enable_ring_locations: RandomizeRingLocations
"enable_key_locations": RandomizeKeyLocations, enable_spell_locations: RandomizeSpellLocations
"enable_boss_locations": RandomizeBossSoulLocations, enable_key_locations: RandomizeKeyLocations
"enable_npc_locations": RandomizeNPCLocations, enable_boss_locations: RandomizeBossSoulLocations
"enable_misc_locations": RandomizeMiscLocations, enable_npc_locations: RandomizeNPCLocations
"enable_health_upgrade_locations": RandomizeHealthLocations, enable_misc_locations: RandomizeMiscLocations
"enable_progressive_locations": RandomizeProgressiveLocationsOption, enable_health_upgrade_locations: RandomizeHealthLocations
"pool_type": PoolTypeOption, enable_progressive_locations: RandomizeProgressiveLocationsOption
"guaranteed_items": GuaranteedItemsOption, pool_type: PoolTypeOption
"auto_equip": AutoEquipOption, guaranteed_items: GuaranteedItemsOption
"lock_equip": LockEquipOption, auto_equip: AutoEquipOption
"no_weapon_requirements": NoWeaponRequirementsOption, lock_equip: LockEquipOption
"randomize_infusion": RandomizeInfusionOption, no_weapon_requirements: NoWeaponRequirementsOption
"randomize_infusion_percentage": RandomizeInfusionPercentageOption, randomize_infusion: RandomizeInfusionOption
"randomize_weapon_level": RandomizeWeaponLevelOption, randomize_infusion_percentage: RandomizeInfusionPercentageOption
"randomize_weapon_level_percentage": RandomizeWeaponLevelPercentageOption, randomize_weapon_level: RandomizeWeaponLevelOption
"min_levels_in_5": MinLevelsIn5WeaponPoolOption, randomize_weapon_level_percentage: RandomizeWeaponLevelPercentageOption
"max_levels_in_5": MaxLevelsIn5WeaponPoolOption, min_levels_in_5: MinLevelsIn5WeaponPoolOption
"min_levels_in_10": MinLevelsIn10WeaponPoolOption, max_levels_in_5: MaxLevelsIn5WeaponPoolOption
"max_levels_in_10": MaxLevelsIn10WeaponPoolOption, min_levels_in_10: MinLevelsIn10WeaponPoolOption
"early_banner": EarlySmallLothricBanner, max_levels_in_10: MaxLevelsIn10WeaponPoolOption
"late_basin_of_vows": LateBasinOfVowsOption, early_banner: EarlySmallLothricBanner
"late_dlc": LateDLCOption, late_basin_of_vows: LateBasinOfVowsOption
"no_spell_requirements": NoSpellRequirementsOption, late_dlc: LateDLCOption
"no_equip_load": NoEquipLoadOption, no_spell_requirements: NoSpellRequirementsOption
"death_link": DeathLink, no_equip_load: NoEquipLoadOption
"enable_dlc": EnableDLCOption, death_link: DeathLink
} enable_dlc: EnableDLCOption

View File

@@ -9,7 +9,7 @@ from worlds.generic.Rules import set_rule, add_rule, add_item_rule
from .Items import DarkSouls3Item, DS3ItemCategory, item_dictionary, key_item_names, item_descriptions from .Items import DarkSouls3Item, DS3ItemCategory, item_dictionary, key_item_names, item_descriptions
from .Locations import DarkSouls3Location, DS3LocationCategory, location_tables, location_dictionary from .Locations import DarkSouls3Location, DS3LocationCategory, location_tables, location_dictionary
from .Options import RandomizeWeaponLevelOption, PoolTypeOption, EarlySmallLothricBanner, dark_souls_options from .Options import RandomizeWeaponLevelOption, PoolTypeOption, EarlySmallLothricBanner, DarkSouls3Options
class DarkSouls3Web(WebWorld): class DarkSouls3Web(WebWorld):
@@ -43,7 +43,8 @@ class DarkSouls3World(World):
""" """
game: str = "Dark Souls III" game: str = "Dark Souls III"
option_definitions = dark_souls_options options = DarkSouls3Options
options_dataclass = DarkSouls3Options
topology_present: bool = True topology_present: bool = True
web = DarkSouls3Web() web = DarkSouls3Web()
data_version = 8 data_version = 8
@@ -72,47 +73,47 @@ class DarkSouls3World(World):
def generate_early(self): def generate_early(self):
if self.multiworld.enable_weapon_locations[self.player] == Toggle.option_true: if self.options.enable_weapon_locations == Toggle.option_true:
self.enabled_location_categories.add(DS3LocationCategory.WEAPON) self.enabled_location_categories.add(DS3LocationCategory.WEAPON)
if self.multiworld.enable_shield_locations[self.player] == Toggle.option_true: if self.options.enable_shield_locations == Toggle.option_true:
self.enabled_location_categories.add(DS3LocationCategory.SHIELD) self.enabled_location_categories.add(DS3LocationCategory.SHIELD)
if self.multiworld.enable_armor_locations[self.player] == Toggle.option_true: if self.options.enable_armor_locations == Toggle.option_true:
self.enabled_location_categories.add(DS3LocationCategory.ARMOR) self.enabled_location_categories.add(DS3LocationCategory.ARMOR)
if self.multiworld.enable_ring_locations[self.player] == Toggle.option_true: if self.options.enable_ring_locations == Toggle.option_true:
self.enabled_location_categories.add(DS3LocationCategory.RING) self.enabled_location_categories.add(DS3LocationCategory.RING)
if self.multiworld.enable_spell_locations[self.player] == Toggle.option_true: if self.options.enable_spell_locations == Toggle.option_true:
self.enabled_location_categories.add(DS3LocationCategory.SPELL) self.enabled_location_categories.add(DS3LocationCategory.SPELL)
if self.multiworld.enable_npc_locations[self.player] == Toggle.option_true: if self.options.enable_npc_locations == Toggle.option_true:
self.enabled_location_categories.add(DS3LocationCategory.NPC) self.enabled_location_categories.add(DS3LocationCategory.NPC)
if self.multiworld.enable_key_locations[self.player] == Toggle.option_true: if self.options.enable_key_locations == Toggle.option_true:
self.enabled_location_categories.add(DS3LocationCategory.KEY) self.enabled_location_categories.add(DS3LocationCategory.KEY)
if self.multiworld.early_banner[self.player] == EarlySmallLothricBanner.option_early_global: if self.options.early_banner == EarlySmallLothricBanner.option_early_global:
self.multiworld.early_items[self.player]['Small Lothric Banner'] = 1 self.options.early_items['Small Lothric Banner'] = 1
elif self.multiworld.early_banner[self.player] == EarlySmallLothricBanner.option_early_local: elif self.options.early_banner == EarlySmallLothricBanner.option_early_local:
self.multiworld.local_early_items[self.player]['Small Lothric Banner'] = 1 self.options.local_early_items['Small Lothric Banner'] = 1
if self.multiworld.enable_boss_locations[self.player] == Toggle.option_true: if self.options.enable_boss_locations == Toggle.option_true:
self.enabled_location_categories.add(DS3LocationCategory.BOSS) self.enabled_location_categories.add(DS3LocationCategory.BOSS)
if self.multiworld.enable_misc_locations[self.player] == Toggle.option_true: if self.options.enable_misc_locations == Toggle.option_true:
self.enabled_location_categories.add(DS3LocationCategory.MISC) self.enabled_location_categories.add(DS3LocationCategory.MISC)
if self.multiworld.enable_health_upgrade_locations[self.player] == Toggle.option_true: if self.options.enable_health_upgrade_locations == Toggle.option_true:
self.enabled_location_categories.add(DS3LocationCategory.HEALTH) self.enabled_location_categories.add(DS3LocationCategory.HEALTH)
if self.multiworld.enable_progressive_locations[self.player] == Toggle.option_true: if self.options.enable_progressive_locations == Toggle.option_true:
self.enabled_location_categories.add(DS3LocationCategory.PROGRESSIVE_ITEM) self.enabled_location_categories.add(DS3LocationCategory.PROGRESSIVE_ITEM)
def create_regions(self): def create_regions(self):
progressive_location_table = [] progressive_location_table = []
if self.multiworld.enable_progressive_locations[self.player]: if self.options.enable_progressive_locations:
progressive_location_table = [] + \ progressive_location_table = [] + \
location_tables["Progressive Items 1"] + \ location_tables["Progressive Items 1"] + \
location_tables["Progressive Items 2"] + \ location_tables["Progressive Items 2"] + \
location_tables["Progressive Items 3"] + \ location_tables["Progressive Items 3"] + \
location_tables["Progressive Items 4"] location_tables["Progressive Items 4"]
if self.multiworld.enable_dlc[self.player].value: if self.options.enable_dlc.value:
progressive_location_table += location_tables["Progressive Items DLC"] progressive_location_table += location_tables["Progressive Items DLC"]
if self.multiworld.enable_health_upgrade_locations[self.player]: if self.options.enable_health_upgrade_locations:
progressive_location_table += location_tables["Progressive Items Health"] progressive_location_table += location_tables["Progressive Items Health"]
# Create Vanilla Regions # Create Vanilla Regions
@@ -146,7 +147,7 @@ class DarkSouls3World(World):
regions["Consumed King's Garden"].locations.append(potd_location) regions["Consumed King's Garden"].locations.append(potd_location)
# Create DLC Regions # Create DLC Regions
if self.multiworld.enable_dlc[self.player]: if self.options.enable_dlc:
regions.update({region_name: self.create_region(region_name, location_tables[region_name]) for region_name in [ regions.update({region_name: self.create_region(region_name, location_tables[region_name]) for region_name in [
"Painted World of Ariandel 1", "Painted World of Ariandel 1",
"Painted World of Ariandel 2", "Painted World of Ariandel 2",
@@ -192,7 +193,7 @@ class DarkSouls3World(World):
create_connection("Consumed King's Garden", "Untended Graves") create_connection("Consumed King's Garden", "Untended Graves")
# Connect DLC Regions # Connect DLC Regions
if self.multiworld.enable_dlc[self.player]: if self.options.enable_dlc:
create_connection("Cathedral of the Deep", "Painted World of Ariandel 1") create_connection("Cathedral of the Deep", "Painted World of Ariandel 1")
create_connection("Painted World of Ariandel 1", "Painted World of Ariandel 2") create_connection("Painted World of Ariandel 1", "Painted World of Ariandel 2")
create_connection("Painted World of Ariandel 2", "Dreg Heap") create_connection("Painted World of Ariandel 2", "Dreg Heap")
@@ -240,7 +241,7 @@ class DarkSouls3World(World):
def create_items(self): def create_items(self):
dlc_enabled = self.multiworld.enable_dlc[self.player] == Toggle.option_true dlc_enabled = self.options.enable_dlc == Toggle.option_true
itempool_by_category = {category: [] for category in self.enabled_location_categories} itempool_by_category = {category: [] for category in self.enabled_location_categories}
@@ -254,7 +255,7 @@ class DarkSouls3World(World):
itempool_by_category[location.category].append(location.default_item_name) itempool_by_category[location.category].append(location.default_item_name)
# Replace each item category with a random sample of items of those types # Replace each item category with a random sample of items of those types
if self.multiworld.pool_type[self.player] == PoolTypeOption.option_various: if self.options.pool_type == PoolTypeOption.option_various:
def create_random_replacement_list(item_categories: Set[DS3ItemCategory], num_items: int): def create_random_replacement_list(item_categories: Set[DS3ItemCategory], num_items: int):
candidates = [ candidates = [
item.name for item item.name for item
@@ -300,7 +301,7 @@ class DarkSouls3World(World):
# A list of items we can replace # A list of items we can replace
removable_items = [item for item in itempool if item.classification != ItemClassification.progression] removable_items = [item for item in itempool if item.classification != ItemClassification.progression]
guaranteed_items = self.multiworld.guaranteed_items[self.player].value guaranteed_items = self.options.guaranteed_items.value
for item_name in guaranteed_items: for item_name in guaranteed_items:
# Break early just in case nothing is removable (if user is trying to guarantee more # Break early just in case nothing is removable (if user is trying to guarantee more
# items than the pool can hold, for example) # items than the pool can hold, for example)
@@ -384,22 +385,22 @@ class DarkSouls3World(World):
state.has("Cinders of a Lord - Aldrich", self.player) and state.has("Cinders of a Lord - Aldrich", self.player) and
state.has("Cinders of a Lord - Lothric Prince", self.player)) state.has("Cinders of a Lord - Lothric Prince", self.player))
if self.multiworld.late_basin_of_vows[self.player] == Toggle.option_true: if self.options.late_basin_of_vows == Toggle.option_true:
add_rule(self.multiworld.get_entrance("Go To Lothric Castle", self.player), add_rule(self.multiworld.get_entrance("Go To Lothric Castle", self.player),
lambda state: state.has("Small Lothric Banner", self.player)) lambda state: state.has("Small Lothric Banner", self.player))
# DLC Access Rules Below # DLC Access Rules Below
if self.multiworld.enable_dlc[self.player]: if self.options.enable_dlc:
set_rule(self.multiworld.get_entrance("Go To Ringed City", self.player), set_rule(self.multiworld.get_entrance("Go To Ringed City", self.player),
lambda state: state.has("Small Envoy Banner", self.player)) lambda state: state.has("Small Envoy Banner", self.player))
# If key items are randomized, must have contraption key to enter second half of Ashes DLC # If key items are randomized, must have contraption key to enter second half of Ashes DLC
# If key items are not randomized, Contraption Key is guaranteed to be accessible before it is needed # If key items are not randomized, Contraption Key is guaranteed to be accessible before it is needed
if self.multiworld.enable_key_locations[self.player] == Toggle.option_true: if self.options.enable_key_locations == Toggle.option_true:
add_rule(self.multiworld.get_entrance("Go To Painted World of Ariandel 2", self.player), add_rule(self.multiworld.get_entrance("Go To Painted World of Ariandel 2", self.player),
lambda state: state.has("Contraption Key", self.player)) lambda state: state.has("Contraption Key", self.player))
if self.multiworld.late_dlc[self.player] == Toggle.option_true: if self.options.late_dlc == Toggle.option_true:
add_rule(self.multiworld.get_entrance("Go To Painted World of Ariandel 1", self.player), add_rule(self.multiworld.get_entrance("Go To Painted World of Ariandel 1", self.player),
lambda state: state.has("Small Doll", self.player)) lambda state: state.has("Small Doll", self.player))
@@ -407,7 +408,7 @@ class DarkSouls3World(World):
set_rule(self.multiworld.get_location("PC: Cinders of a Lord - Yhorm the Giant", self.player), set_rule(self.multiworld.get_location("PC: Cinders of a Lord - Yhorm the Giant", self.player),
lambda state: state.has("Storm Ruler", self.player)) lambda state: state.has("Storm Ruler", self.player))
if self.multiworld.enable_ring_locations[self.player] == Toggle.option_true: if self.options.enable_ring_locations == Toggle.option_true:
set_rule(self.multiworld.get_location("ID: Bellowing Dragoncrest Ring", self.player), set_rule(self.multiworld.get_location("ID: Bellowing Dragoncrest Ring", self.player),
lambda state: state.has("Jailbreaker's Key", self.player)) lambda state: state.has("Jailbreaker's Key", self.player))
set_rule(self.multiworld.get_location("ID: Covetous Gold Serpent Ring", self.player), set_rule(self.multiworld.get_location("ID: Covetous Gold Serpent Ring", self.player),
@@ -415,7 +416,7 @@ class DarkSouls3World(World):
set_rule(self.multiworld.get_location("UG: Hornet Ring", self.player), set_rule(self.multiworld.get_location("UG: Hornet Ring", self.player),
lambda state: state.has("Small Lothric Banner", self.player)) lambda state: state.has("Small Lothric Banner", self.player))
if self.multiworld.enable_npc_locations[self.player] == Toggle.option_true: if self.options.enable_npc_locations == Toggle.option_true:
set_rule(self.multiworld.get_location("HWL: Greirat's Ashes", self.player), set_rule(self.multiworld.get_location("HWL: Greirat's Ashes", self.player),
lambda state: state.has("Cell Key", self.player)) lambda state: state.has("Cell Key", self.player))
set_rule(self.multiworld.get_location("HWL: Blue Tearstone Ring", self.player), set_rule(self.multiworld.get_location("HWL: Blue Tearstone Ring", self.player),
@@ -431,11 +432,11 @@ class DarkSouls3World(World):
set_rule(self.multiworld.get_location("ID: Karla's Trousers", self.player), set_rule(self.multiworld.get_location("ID: Karla's Trousers", self.player),
lambda state: state.has("Jailer's Key Ring", self.player)) lambda state: state.has("Jailer's Key Ring", self.player))
if self.multiworld.enable_misc_locations[self.player] == Toggle.option_true: if self.options.enable_misc_locations == Toggle.option_true:
set_rule(self.multiworld.get_location("ID: Prisoner Chief's Ashes", self.player), set_rule(self.multiworld.get_location("ID: Prisoner Chief's Ashes", self.player),
lambda state: state.has("Jailer's Key Ring", self.player)) lambda state: state.has("Jailer's Key Ring", self.player))
if self.multiworld.enable_boss_locations[self.player] == Toggle.option_true: if self.options.enable_boss_locations == Toggle.option_true:
set_rule(self.multiworld.get_location("PC: Soul of Yhorm the Giant", self.player), set_rule(self.multiworld.get_location("PC: Soul of Yhorm the Giant", self.player),
lambda state: state.has("Storm Ruler", self.player)) lambda state: state.has("Storm Ruler", self.player))
set_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player), set_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player),
@@ -443,7 +444,7 @@ class DarkSouls3World(World):
# Lump Soul of the Dancer in with LC for locations that should not be reachable # Lump Soul of the Dancer in with LC for locations that should not be reachable
# before having access to US. (Prevents requiring getting Basin to fight Dancer to get SLB to go to US) # before having access to US. (Prevents requiring getting Basin to fight Dancer to get SLB to go to US)
if self.multiworld.late_basin_of_vows[self.player] == Toggle.option_true: if self.options.late_basin_of_vows == Toggle.option_true:
add_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player), add_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player),
lambda state: state.has("Small Lothric Banner", self.player)) lambda state: state.has("Small Lothric Banner", self.player))
@@ -453,10 +454,10 @@ class DarkSouls3World(World):
set_rule(self.multiworld.get_location("LC: Grand Archives Key", self.player), gotthard_corpse_rule) set_rule(self.multiworld.get_location("LC: Grand Archives Key", self.player), gotthard_corpse_rule)
if self.multiworld.enable_weapon_locations[self.player] == Toggle.option_true: if self.options.enable_weapon_locations == Toggle.option_true:
set_rule(self.multiworld.get_location("LC: Gotthard Twinswords", self.player), gotthard_corpse_rule) set_rule(self.multiworld.get_location("LC: Gotthard Twinswords", self.player), gotthard_corpse_rule)
self.multiworld.completion_condition[self.player] = lambda state: \ self.options.completion_condition = lambda state: \
state.has("Cinders of a Lord - Abyss Watcher", self.player) and \ state.has("Cinders of a Lord - Abyss Watcher", self.player) and \
state.has("Cinders of a Lord - Yhorm the Giant", self.player) and \ state.has("Cinders of a Lord - Yhorm the Giant", self.player) and \
state.has("Cinders of a Lord - Aldrich", self.player) and \ state.has("Cinders of a Lord - Aldrich", self.player) and \
@@ -470,13 +471,13 @@ class DarkSouls3World(World):
name_to_ds3_code = {item.name: item.ds3_code for item in item_dictionary.values()} name_to_ds3_code = {item.name: item.ds3_code for item in item_dictionary.values()}
# Randomize some weapon upgrades # Randomize some weapon upgrades
if self.multiworld.randomize_weapon_level[self.player] != RandomizeWeaponLevelOption.option_none: if self.options.randomize_weapon_level != RandomizeWeaponLevelOption.option_none:
# if the user made an error and set a min higher than the max we default to the max # if the user made an error and set a min higher than the max we default to the max
max_5 = self.multiworld.max_levels_in_5[self.player] max_5 = self.options.max_levels_in_5
min_5 = min(self.multiworld.min_levels_in_5[self.player], max_5) min_5 = min(self.options.min_levels_in_5, max_5)
max_10 = self.multiworld.max_levels_in_10[self.player] max_10 = self.options.max_levels_in_10
min_10 = min(self.multiworld.min_levels_in_10[self.player], max_10) min_10 = min(self.options.min_levels_in_10, max_10)
weapon_level_percentage = self.multiworld.randomize_weapon_level_percentage[self.player] weapon_level_percentage = self.options.randomize_weapon_level_percentage
for item in item_dictionary.values(): for item in item_dictionary.values():
if self.multiworld.per_slot_randoms[self.player].randint(0, 99) < weapon_level_percentage: if self.multiworld.per_slot_randoms[self.player].randint(0, 99) < weapon_level_percentage:
@@ -486,8 +487,8 @@ class DarkSouls3World(World):
name_to_ds3_code[item.name] += self.multiworld.per_slot_randoms[self.player].randint(min_10, max_10) name_to_ds3_code[item.name] += self.multiworld.per_slot_randoms[self.player].randint(min_10, max_10)
# Randomize some weapon infusions # Randomize some weapon infusions
if self.multiworld.randomize_infusion[self.player] == Toggle.option_true: if self.options.randomize_infusion == Toggle.option_true:
infusion_percentage = self.multiworld.randomize_infusion_percentage[self.player] infusion_percentage = self.options.randomize_infusion_percentage
for item in item_dictionary.values(): for item in item_dictionary.values():
if item.category in {DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE, DS3ItemCategory.SHIELD_INFUSIBLE}: if item.category in {DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE, DS3ItemCategory.SHIELD_INFUSIBLE}:
if self.multiworld.per_slot_randoms[self.player].randint(0, 99) < infusion_percentage: if self.multiworld.per_slot_randoms[self.player].randint(0, 99) < infusion_percentage:
@@ -518,22 +519,22 @@ class DarkSouls3World(World):
slot_data = { slot_data = {
"options": { "options": {
"enable_weapon_locations": self.multiworld.enable_weapon_locations[self.player].value, "enable_weapon_locations": self.options.enable_weapon_locations.value,
"enable_shield_locations": self.multiworld.enable_shield_locations[self.player].value, "enable_shield_locations": self.options.enable_shield_locations.value,
"enable_armor_locations": self.multiworld.enable_armor_locations[self.player].value, "enable_armor_locations": self.options.enable_armor_locations.value,
"enable_ring_locations": self.multiworld.enable_ring_locations[self.player].value, "enable_ring_locations": self.options.enable_ring_locations.value,
"enable_spell_locations": self.multiworld.enable_spell_locations[self.player].value, "enable_spell_locations": self.options.enable_spell_locations.value,
"enable_key_locations": self.multiworld.enable_key_locations[self.player].value, "enable_key_locations": self.options.enable_key_locations.value,
"enable_boss_locations": self.multiworld.enable_boss_locations[self.player].value, "enable_boss_locations": self.options.enable_boss_locations.value,
"enable_npc_locations": self.multiworld.enable_npc_locations[self.player].value, "enable_npc_locations": self.options.enable_npc_locations.value,
"enable_misc_locations": self.multiworld.enable_misc_locations[self.player].value, "enable_misc_locations": self.options.enable_misc_locations.value,
"auto_equip": self.multiworld.auto_equip[self.player].value, "auto_equip": self.options.auto_equip.value,
"lock_equip": self.multiworld.lock_equip[self.player].value, "lock_equip": self.options.lock_equip.value,
"no_weapon_requirements": self.multiworld.no_weapon_requirements[self.player].value, "no_weapon_requirements": self.options.no_weapon_requirements.value,
"death_link": self.multiworld.death_link[self.player].value, "death_link": self.options.death_link.value,
"no_spell_requirements": self.multiworld.no_spell_requirements[self.player].value, "no_spell_requirements": self.options.no_spell_requirements.value,
"no_equip_load": self.multiworld.no_equip_load[self.player].value, "no_equip_load": self.options.no_equip_load.value,
"enable_dlc": self.multiworld.enable_dlc[self.player].value "enable_dlc": self.options.enable_dlc.value
}, },
"seed": self.multiworld.seed_name, # to verify the server's multiworld "seed": self.multiworld.seed_name, # to verify the server's multiworld
"slot": self.multiworld.player_name[self.player], # to connect to server "slot": self.multiworld.player_name[self.player], # to connect to server

View File

@@ -6,6 +6,8 @@ import shutil
import threading import threading
import zipfile import zipfile
from typing import Optional, TYPE_CHECKING, Any, List, Callable, Tuple, Union from typing import Optional, TYPE_CHECKING, Any, List, Callable, Tuple, Union
from dataclasses import fields
import datetime
import jinja2 import jinja2
@@ -88,6 +90,7 @@ class FactorioModFile(worlds.Files.APContainer):
def generate_mod(world: "Factorio", output_directory: str): def generate_mod(world: "Factorio", output_directory: str):
player = world.player player = world.player
multiworld = world.multiworld multiworld = world.multiworld
options = world.options
global data_final_template, locale_template, control_template, data_template, settings_template global data_final_template, locale_template, control_template, data_template, settings_template
with template_load_lock: with template_load_lock:
if not data_final_template: if not data_final_template:
@@ -129,40 +132,40 @@ def generate_mod(world: "Factorio", output_directory: str):
"base_tech_table": base_tech_table, "base_tech_table": base_tech_table,
"tech_to_progressive_lookup": tech_to_progressive_lookup, "tech_to_progressive_lookup": tech_to_progressive_lookup,
"mod_name": mod_name, "mod_name": mod_name,
"allowed_science_packs": multiworld.max_science_pack[player].get_allowed_packs(), "allowed_science_packs": options.max_science_pack.get_allowed_packs(),
"custom_technologies": multiworld.worlds[player].custom_technologies, "custom_technologies": multiworld.worlds[player].custom_technologies,
"tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites, "tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites,
"slot_name": multiworld.player_name[player], "seed_name": multiworld.seed_name, "slot_name": multiworld.player_name[player], "seed_name": multiworld.seed_name,
"slot_player": player, "slot_player": player,
"starting_items": multiworld.starting_items[player], "recipes": recipes, "starting_items": options.starting_items, "recipes": recipes,
"random": random, "flop_random": flop_random, "random": random, "flop_random": flop_random,
"recipe_time_scale": recipe_time_scales.get(multiworld.recipe_time[player].value, None), "recipe_time_scale": recipe_time_scales.get(options.recipe_time.value, None),
"recipe_time_range": recipe_time_ranges.get(multiworld.recipe_time[player].value, None), "recipe_time_range": recipe_time_ranges.get(options.recipe_time.value, None),
"free_sample_blacklist": {item: 1 for item in free_sample_exclusions}, "free_sample_blacklist": {item: 1 for item in free_sample_exclusions},
"progressive_technology_table": {tech.name: tech.progressive for tech in "progressive_technology_table": {tech.name: tech.progressive for tech in
progressive_technology_table.values()}, progressive_technology_table.values()},
"custom_recipes": world.custom_recipes, "custom_recipes": world.custom_recipes,
"max_science_pack": multiworld.max_science_pack[player].value, "max_science_pack": options.max_science_pack.value,
"liquids": fluids, "liquids": fluids,
"goal": multiworld.goal[player].value, "goal": options.goal.value,
"energy_link": multiworld.energy_link[player].value, "energy_link": options.energy_link.value,
"useless_technologies": useless_technologies, "useless_technologies": useless_technologies,
"chunk_shuffle": multiworld.chunk_shuffle[player].value if hasattr(multiworld, "chunk_shuffle") else 0, "chunk_shuffle": options.chunk_shuffle.value if datetime.datetime.today().month == 4 else 0,
} }
for factorio_option in Options.factorio_options: for factorio_option in [field.name for field in fields(Options.FactorioOptions)]:
if factorio_option in ["free_sample_blacklist", "free_sample_whitelist"]: if factorio_option in ["free_sample_blacklist", "free_sample_whitelist"]:
continue continue
template_data[factorio_option] = getattr(multiworld, factorio_option)[player].value template_data[factorio_option] = getattr(options, factorio_option).value
if getattr(multiworld, "silo")[player].value == Options.Silo.option_randomize_recipe: if getattr(options, "silo").value == Options.Silo.option_randomize_recipe:
template_data["free_sample_blacklist"]["rocket-silo"] = 1 template_data["free_sample_blacklist"]["rocket-silo"] = 1
if getattr(multiworld, "satellite")[player].value == Options.Satellite.option_randomize_recipe: if getattr(options, "satellite").value == Options.Satellite.option_randomize_recipe:
template_data["free_sample_blacklist"]["satellite"] = 1 template_data["free_sample_blacklist"]["satellite"] = 1
template_data["free_sample_blacklist"].update({item: 1 for item in multiworld.free_sample_blacklist[player].value}) template_data["free_sample_blacklist"].update({item: 1 for item in options.free_sample_blacklist.value})
template_data["free_sample_blacklist"].update({item: 0 for item in multiworld.free_sample_whitelist[player].value}) template_data["free_sample_blacklist"].update({item: 0 for item in options.free_sample_whitelist.value})
zf_path = os.path.join(output_directory, versioned_mod_name + ".zip") zf_path = os.path.join(output_directory, versioned_mod_name + ".zip")
mod = FactorioModFile(zf_path, player=player, player_name=multiworld.player_name[player]) mod = FactorioModFile(zf_path, player=player, player_name=multiworld.player_name[player])

View File

@@ -1,10 +1,10 @@
from __future__ import annotations from __future__ import annotations
import typing import typing
import datetime
from Options import Choice, OptionDict, OptionSet, Option, DefaultOnToggle, Range, DeathLink, Toggle, \ from Options import Choice, OptionDict, OptionSet, Option, DefaultOnToggle, Range, DeathLink, Toggle, \
StartInventoryPool StartInventoryPool, PerGameCommonOptions
from schema import Schema, Optional, And, Or from schema import Schema, Optional, And, Or
from dataclasses import dataclass
# schema helpers # schema helpers
FloatRange = lambda low, high: And(Or(int, float), lambda f: low <= f <= high) FloatRange = lambda low, high: And(Or(int, float), lambda f: low <= f <= high)
@@ -422,50 +422,44 @@ class EnergyLink(Toggle):
display_name = "EnergyLink" display_name = "EnergyLink"
factorio_options: typing.Dict[str, type(Option)] = { class ChunkShuffle(Toggle):
"max_science_pack": MaxSciencePack, """Entrance Randomizer.
"goal": Goal, 2023 April Fool's option. Shuffles chunk border transitions.
"tech_tree_layout": TechTreeLayout, Only valid during the Month of April. Forced off otherwise."""
"min_tech_cost": MinTechCost,
"max_tech_cost": MaxTechCost,
"tech_cost_distribution": TechCostDistribution,
"tech_cost_mix": TechCostMix,
"ramping_tech_costs": RampingTechCosts,
"silo": Silo,
"satellite": Satellite,
"free_samples": FreeSamples,
"tech_tree_information": TechTreeInformation,
"starting_items": FactorioStartItems,
"free_sample_blacklist": FactorioFreeSampleBlacklist,
"free_sample_whitelist": FactorioFreeSampleWhitelist,
"recipe_time": RecipeTime,
"recipe_ingredients": RecipeIngredients,
"recipe_ingredients_offset": RecipeIngredientsOffset,
"imported_blueprints": ImportedBlueprint,
"world_gen": FactorioWorldGen,
"progressive": Progressive,
"teleport_traps": TeleportTrapCount,
"grenade_traps": GrenadeTrapCount,
"cluster_grenade_traps": ClusterGrenadeTrapCount,
"artillery_traps": ArtilleryTrapCount,
"atomic_rocket_traps": AtomicRocketTrapCount,
"attack_traps": AttackTrapCount,
"evolution_traps": EvolutionTrapCount,
"evolution_trap_increase": EvolutionTrapIncrease,
"death_link": DeathLink,
"energy_link": EnergyLink,
"start_inventory_from_pool": StartInventoryPool,
}
# spoilers below. If you spoil it for yourself, please at least don't spoil it for anyone else.
if datetime.datetime.today().month == 4:
class ChunkShuffle(Toggle):
"""Entrance Randomizer."""
display_name = "Chunk Shuffle"
if datetime.datetime.today().day > 1: @dataclass
ChunkShuffle.__doc__ += """ class FactorioOptions(PerGameCommonOptions):
2023 April Fool's option. Shuffles chunk border transitions.""" max_science_pack: MaxSciencePack
factorio_options["chunk_shuffle"] = ChunkShuffle goal: Goal
tech_tree_layout: TechTreeLayout
min_tech_cost: MinTechCost
max_tech_cost: MaxTechCost
tech_cost_distribution: TechCostDistribution
tech_cost_mix: TechCostMix
ramping_tech_costs: RampingTechCosts
silo: Silo
satellite: Satellite
free_samples: FreeSamples
tech_tree_information: TechTreeInformation
starting_items: FactorioStartItems
free_sample_blacklist: FactorioFreeSampleBlacklist
free_sample_whitelist: FactorioFreeSampleWhitelist
recipe_time: RecipeTime
recipe_ingredients: RecipeIngredients
recipe_ingredients_offset: RecipeIngredientsOffset
imported_blueprints: ImportedBlueprint
world_gen: FactorioWorldGen
progressive: Progressive
teleport_traps: TeleportTrapCount
grenade_traps: GrenadeTrapCount
cluster_grenade_traps: ClusterGrenadeTrapCount
artillery_traps: ArtilleryTrapCount
atomic_rocket_traps: AtomicRocketTrapCount
attack_traps: AttackTrapCount
evolution_traps: EvolutionTrapCount
evolution_trap_increase: EvolutionTrapIncrease
death_link: DeathLink
energy_link: EnergyLink
start_inventory_from_pool: StartInventoryPool
chunk_shuffle: ChunkShuffle

View File

@@ -20,10 +20,10 @@ def _sorter(location: "FactorioScienceLocation"):
def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]: def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]:
options = factorio_world.options
world = factorio_world.multiworld world = factorio_world.multiworld
player = factorio_world.player
prerequisites: Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]] = {} prerequisites: Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]] = {}
layout = world.tech_tree_layout[player].value layout = options.tech_tree_layout.value
locations: List["FactorioScienceLocation"] = sorted(factorio_world.science_locations, key=lambda loc: loc.name) locations: List["FactorioScienceLocation"] = sorted(factorio_world.science_locations, key=lambda loc: loc.name)
world.random.shuffle(locations) world.random.shuffle(locations)

View File

@@ -11,7 +11,7 @@ from worlds.LauncherComponents import Component, components, Type, launch_subpro
from worlds.generic import Rules from worlds.generic import Rules
from .Locations import location_pools, location_table from .Locations import location_pools, location_table
from .Mod import generate_mod from .Mod import generate_mod
from .Options import factorio_options, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution from .Options import FactorioOptions, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution, TechCostMix
from .Shapes import get_shapes from .Shapes import get_shapes
from .Technologies import base_tech_table, recipe_sources, base_technology_table, \ from .Technologies import base_tech_table, recipe_sources, base_technology_table, \
all_ingredient_names, all_product_sources, required_technologies, get_rocket_requirements, \ all_ingredient_names, all_product_sources, required_technologies, get_rocket_requirements, \
@@ -88,6 +88,9 @@ class Factorio(World):
location_pool: typing.List[FactorioScienceLocation] location_pool: typing.List[FactorioScienceLocation]
advancement_technologies: typing.Set[str] advancement_technologies: typing.Set[str]
options = FactorioOptions
options_dataclass = FactorioOptions
web = FactorioWeb() web = FactorioWeb()
item_name_to_id = all_items item_name_to_id = all_items
@@ -117,11 +120,11 @@ class Factorio(World):
def generate_early(self) -> None: def generate_early(self) -> None:
# if max < min, then swap max and min # if max < min, then swap max and min
if self.multiworld.max_tech_cost[self.player] < self.multiworld.min_tech_cost[self.player]: if self.options.max_tech_cost < self.options.min_tech_cost:
self.multiworld.min_tech_cost[self.player].value, self.multiworld.max_tech_cost[self.player].value = \ self.options.min_tech_cost.value, self.options.max_tech_cost.value = \
self.multiworld.max_tech_cost[self.player].value, self.multiworld.min_tech_cost[self.player].value self.options.max_tech_cost.value, self.options.min_tech_cost.value
self.tech_mix = self.multiworld.tech_cost_mix[self.player] self.tech_mix = self.options.tech_cost_mix
self.skip_silo = self.multiworld.silo[self.player].value == Silo.option_spawn self.skip_silo = self.options.silo.value == Silo.option_spawn
def create_regions(self): def create_regions(self):
player = self.player player = self.player
@@ -132,17 +135,17 @@ class Factorio(World):
nauvis = Region("Nauvis", player, self.multiworld) nauvis = Region("Nauvis", player, self.multiworld)
location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \ location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \
self.multiworld.evolution_traps[player] + \ self.options.evolution_traps + \
self.multiworld.attack_traps[player] + \ self.options.attack_traps + \
self.multiworld.teleport_traps[player] + \ self.options.teleport_traps + \
self.multiworld.grenade_traps[player] + \ self.options.grenade_traps + \
self.multiworld.cluster_grenade_traps[player] + \ self.options.cluster_grenade_traps + \
self.multiworld.atomic_rocket_traps[player] + \ self.options.atomic_rocket_traps + \
self.multiworld.artillery_traps[player] self.options.artillery_traps
location_pool = [] location_pool = []
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()): for pack in sorted(self.options.max_science_pack.get_allowed_packs()):
location_pool.extend(location_pools[pack]) location_pool.extend(location_pools[pack])
try: try:
location_names = self.multiworld.random.sample(location_pool, location_count) location_names = self.multiworld.random.sample(location_pool, location_count)
@@ -151,11 +154,11 @@ class Factorio(World):
raise Exception("Too many traps for too few locations. Either decrease the trap count, " 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 f"or increase the location count (higher max science pack). (Player {self.player})") from e
self.science_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, self.options.tech_cost_mix)
for loc_name in location_names] for loc_name in location_names]
distribution: TechCostDistribution = self.multiworld.tech_cost_distribution[self.player] distribution: TechCostDistribution = self.options.tech_cost_distribution
min_cost = self.multiworld.min_tech_cost[self.player] min_cost = self.options.min_tech_cost
max_cost = self.multiworld.max_tech_cost[self.player] max_cost = self.options.max_tech_cost
if distribution == distribution.option_even: if distribution == distribution.option_even:
rand_values = (random.randint(min_cost, max_cost) for _ in self.science_locations) rand_values = (random.randint(min_cost, max_cost) for _ in self.science_locations)
else: else:
@@ -164,7 +167,7 @@ class Factorio(World):
distribution.option_high: max_cost}[distribution.value] distribution.option_high: max_cost}[distribution.value]
rand_values = (random.triangular(min_cost, max_cost, mode) for _ in self.science_locations) rand_values = (random.triangular(min_cost, max_cost, mode) for _ in self.science_locations)
rand_values = sorted(rand_values) rand_values = sorted(rand_values)
if self.multiworld.ramping_tech_costs[self.player]: if self.options.ramping_tech_costs:
def sorter(loc: FactorioScienceLocation): def sorter(loc: FactorioScienceLocation):
return loc.complexity, loc.rel_cost return loc.complexity, loc.rel_cost
else: else:
@@ -179,7 +182,7 @@ class Factorio(World):
event = FactorioItem("Victory", ItemClassification.progression, None, player) event = FactorioItem("Victory", ItemClassification.progression, None, player)
location.place_locked_item(event) location.place_locked_item(event)
for ingredient in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()): for ingredient in sorted(self.options.max_science_pack.get_allowed_packs()):
location = FactorioLocation(player, f"Automate {ingredient}", None, nauvis) location = FactorioLocation(player, f"Automate {ingredient}", None, nauvis)
nauvis.locations.append(location) nauvis.locations.append(location)
event = FactorioItem(f"Automated {ingredient}", ItemClassification.progression, None, player) event = FactorioItem(f"Automated {ingredient}", ItemClassification.progression, None, player)
@@ -195,10 +198,10 @@ class Factorio(World):
traps = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery", "Atomic Rocket") traps = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery", "Atomic Rocket")
for trap_name in traps: for trap_name in traps:
self.multiworld.itempool.extend(self.create_item(f"{trap_name} Trap") for _ in self.multiworld.itempool.extend(self.create_item(f"{trap_name} Trap") for _ in
range(getattr(self.multiworld, range(getattr(self.options,
f"{trap_name.lower().replace(' ', '_')}_traps")[player])) f"{trap_name.lower().replace(' ', '_')}_traps")))
want_progressives = collections.defaultdict(lambda: self.multiworld.progressive[player]. want_progressives = collections.defaultdict(lambda: self.options.progressive.
want_progressives(self.multiworld.random)) want_progressives(self.multiworld.random))
cost_sorted_locations = sorted(self.science_locations, key=lambda location: location.name) cost_sorted_locations = sorted(self.science_locations, key=lambda location: location.name)
@@ -206,7 +209,7 @@ class Factorio(World):
"logistics": 1, "logistics": 1,
"rocket-silo": -1} "rocket-silo": -1}
loc: FactorioScienceLocation loc: FactorioScienceLocation
if self.multiworld.tech_tree_information[player] == TechTreeInformation.option_full: if self.options.tech_tree_information == TechTreeInformation.option_full:
# mark all locations as pre-hinted # mark all locations as pre-hinted
for loc in self.science_locations: for loc in self.science_locations:
loc.revealed = True loc.revealed = True
@@ -237,10 +240,10 @@ class Factorio(World):
player = self.player player = self.player
shapes = get_shapes(self) shapes = get_shapes(self)
for ingredient in self.multiworld.max_science_pack[self.player].get_allowed_packs(): for ingredient in self.options.max_science_pack.get_allowed_packs():
location = world.get_location(f"Automate {ingredient}", player) location = world.get_location(f"Automate {ingredient}", player)
if self.multiworld.recipe_ingredients[self.player]: if self.options.recipe_ingredients:
custom_recipe = self.custom_recipes[ingredient] custom_recipe = self.custom_recipes[ingredient]
location.access_rule = lambda state, ingredient=ingredient, custom_recipe=custom_recipe: \ location.access_rule = lambda state, ingredient=ingredient, custom_recipe=custom_recipe: \
@@ -261,16 +264,16 @@ class Factorio(World):
prerequisites: all(state.can_reach(loc) for loc in locations)) prerequisites: all(state.can_reach(loc) for loc in locations))
silo_recipe = None silo_recipe = None
if self.multiworld.silo[self.player] == Silo.option_spawn: if self.options.silo == Silo.option_spawn:
silo_recipe = self.custom_recipes["rocket-silo"] if "rocket-silo" in self.custom_recipes \ silo_recipe = self.custom_recipes["rocket-silo"] if "rocket-silo" in self.custom_recipes \
else next(iter(all_product_sources.get("rocket-silo"))) else next(iter(all_product_sources.get("rocket-silo")))
part_recipe = self.custom_recipes["rocket-part"] part_recipe = self.custom_recipes["rocket-part"]
satellite_recipe = None satellite_recipe = None
if self.multiworld.goal[self.player] == Goal.option_satellite: if self.options.goal == Goal.option_satellite:
satellite_recipe = self.custom_recipes["satellite"] if "satellite" in self.custom_recipes \ satellite_recipe = self.custom_recipes["satellite"] if "satellite" in self.custom_recipes \
else next(iter(all_product_sources.get("satellite"))) else next(iter(all_product_sources.get("satellite")))
victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe) victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe)
if self.multiworld.silo[self.player] != Silo.option_spawn: if self.options.silo != Silo.option_spawn:
victory_tech_names.add("rocket-silo") victory_tech_names.add("rocket-silo")
world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player) world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player)
for technology in for technology in
@@ -279,12 +282,12 @@ class Factorio(World):
world.completion_condition[player] = lambda state: state.has('Victory', player) world.completion_condition[player] = lambda state: state.has('Victory', player)
def generate_basic(self): def generate_basic(self):
map_basic_settings = self.multiworld.world_gen[self.player].value["basic"] map_basic_settings = self.options.world_gen.value["basic"]
if map_basic_settings.get("seed", None) is None: # allow seed 0 if map_basic_settings.get("seed", None) is None: # allow seed 0
# 32 bit uint # 32 bit uint
map_basic_settings["seed"] = self.multiworld.per_slot_randoms[self.player].randint(0, 2 ** 32 - 1) 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 start_location_hints: typing.Set[str] = self.options.start_location_hints.value
for loc in self.science_locations: for loc in self.science_locations:
# show start_location_hints ingame # show start_location_hints ingame
@@ -308,8 +311,6 @@ class Factorio(World):
return super(Factorio, self).collect_item(state, item, remove) return super(Factorio, self).collect_item(state, item, remove)
option_definitions = factorio_options
@classmethod @classmethod
def stage_write_spoiler(cls, world, spoiler_handle): def stage_write_spoiler(cls, world, spoiler_handle):
factorio_players = world.get_game_players(cls.game) factorio_players = world.get_game_players(cls.game)
@@ -437,25 +438,25 @@ class Factorio(World):
def set_custom_technologies(self): def set_custom_technologies(self):
custom_technologies = {} custom_technologies = {}
allowed_packs = self.multiworld.max_science_pack[self.player].get_allowed_packs() allowed_packs = self.options.max_science_pack.get_allowed_packs()
for technology_name, technology in base_technology_table.items(): for technology_name, technology in base_technology_table.items():
custom_technologies[technology_name] = technology.get_custom(self.multiworld, allowed_packs, self.player) custom_technologies[technology_name] = technology.get_custom(self.multiworld, allowed_packs, self.player)
return custom_technologies return custom_technologies
def set_custom_recipes(self): def set_custom_recipes(self):
ingredients_offset = self.multiworld.recipe_ingredients_offset[self.player] ingredients_offset = self.options.recipe_ingredients_offset
original_rocket_part = recipes["rocket-part"] original_rocket_part = recipes["rocket-part"]
science_pack_pools = get_science_pack_pools() science_pack_pools = get_science_pack_pools()
valid_pool = sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_max_pack()] & valid_ingredients) valid_pool = sorted(science_pack_pools[self.options.max_science_pack.get_max_pack()] & valid_ingredients)
self.multiworld.random.shuffle(valid_pool) self.multiworld.random.shuffle(valid_pool)
self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category, self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category,
{valid_pool[x]: 10 for x in range(3 + ingredients_offset)}, {valid_pool[x]: 10 for x in range(3 + ingredients_offset)},
original_rocket_part.products, original_rocket_part.products,
original_rocket_part.energy)} original_rocket_part.energy)}
if self.multiworld.recipe_ingredients[self.player]: if self.options.recipe_ingredients:
valid_pool = [] valid_pool = []
for pack in self.multiworld.max_science_pack[self.player].get_ordered_science_packs(): for pack in self.options.max_science_pack.get_ordered_science_packs():
valid_pool += sorted(science_pack_pools[pack]) valid_pool += sorted(science_pack_pools[pack])
self.multiworld.random.shuffle(valid_pool) self.multiworld.random.shuffle(valid_pool)
if pack in recipes: # skips over space science pack if pack in recipes: # skips over space science pack
@@ -463,23 +464,23 @@ class Factorio(World):
ingredients_offset) ingredients_offset)
self.custom_recipes[pack] = new_recipe self.custom_recipes[pack] = new_recipe
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe \ if self.options.silo.value == Silo.option_randomize_recipe \
or self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe: or self.options.satellite.value == Satellite.option_randomize_recipe:
valid_pool = set() valid_pool = set()
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()): for pack in sorted(self.options.max_science_pack.get_allowed_packs()):
valid_pool |= science_pack_pools[pack] valid_pool |= science_pack_pools[pack]
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe: if self.options.silo.value == Silo.option_randomize_recipe:
new_recipe = self.make_balanced_recipe( new_recipe = self.make_balanced_recipe(
recipes["rocket-silo"], valid_pool, recipes["rocket-silo"], valid_pool,
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7, factor=(self.options.max_science_pack.value + 1) / 7,
ingredients_offset=ingredients_offset) ingredients_offset=ingredients_offset)
self.custom_recipes["rocket-silo"] = new_recipe self.custom_recipes["rocket-silo"] = new_recipe
if self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe: if self.options.satellite.value == Satellite.option_randomize_recipe:
new_recipe = self.make_balanced_recipe( new_recipe = self.make_balanced_recipe(
recipes["satellite"], valid_pool, recipes["satellite"], valid_pool,
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7, factor=(self.options.max_science_pack.value + 1) / 7,
ingredients_offset=ingredients_offset) ingredients_offset=ingredients_offset)
self.custom_recipes["satellite"] = new_recipe self.custom_recipes["satellite"] = new_recipe
bridge = "ap-energy-bridge" bridge = "ap-energy-bridge"
@@ -487,16 +488,16 @@ class Factorio(World):
Recipe(bridge, "crafting", {"replace_1": 1, "replace_2": 1, "replace_3": 1, Recipe(bridge, "crafting", {"replace_1": 1, "replace_2": 1, "replace_3": 1,
"replace_4": 1, "replace_5": 1, "replace_6": 1}, "replace_4": 1, "replace_5": 1, "replace_6": 1},
{bridge: 1}, 10), {bridge: 1}, 10),
sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_ordered_science_packs()[0]]), sorted(science_pack_pools[self.options.max_science_pack.get_ordered_science_packs()[0]]),
ingredients_offset=ingredients_offset) ingredients_offset=ingredients_offset)
for ingredient_name in new_recipe.ingredients: for ingredient_name in new_recipe.ingredients:
new_recipe.ingredients[ingredient_name] = self.multiworld.random.randint(50, 500) new_recipe.ingredients[ingredient_name] = self.multiworld.random.randint(50, 500)
self.custom_recipes[bridge] = new_recipe self.custom_recipes[bridge] = new_recipe
needed_recipes = self.multiworld.max_science_pack[self.player].get_allowed_packs() | {"rocket-part"} needed_recipes = self.options.max_science_pack.get_allowed_packs() | {"rocket-part"}
if self.multiworld.silo[self.player] != Silo.option_spawn: if self.options.silo != Silo.option_spawn:
needed_recipes |= {"rocket-silo"} needed_recipes |= {"rocket-silo"}
if self.multiworld.goal[self.player].value == Goal.option_satellite: if self.options.goal.value == Goal.option_satellite:
needed_recipes |= {"satellite"} needed_recipes |= {"satellite"}
for recipe in needed_recipes: for recipe in needed_recipes:
@@ -538,7 +539,7 @@ class FactorioScienceLocation(FactorioLocation):
ingredients: typing.Dict[str, int] ingredients: typing.Dict[str, int]
count: int = 0 count: int = 0
def __init__(self, player: int, name: str, address: int, parent: Region): def __init__(self, player: int, name: str, address: int, parent: Region, tech_cost_mix: TechCostMix):
super(FactorioScienceLocation, self).__init__(player, name, address, parent) super(FactorioScienceLocation, self).__init__(player, name, address, parent)
# "AP-{Complexity}-{Cost}" # "AP-{Complexity}-{Cost}"
self.complexity = int(self.name[3]) - 1 self.complexity = int(self.name[3]) - 1
@@ -546,7 +547,7 @@ class FactorioScienceLocation(FactorioLocation):
self.ingredients = {Factorio.ordered_science_packs[self.complexity]: 1} self.ingredients = {Factorio.ordered_science_packs[self.complexity]: 1}
for complexity in range(self.complexity): for complexity in range(self.complexity):
if parent.multiworld.tech_cost_mix[self.player] > parent.multiworld.random.randint(0, 99): if tech_cost_mix > parent.multiworld.random.randint(0, 99):
self.ingredients[Factorio.ordered_science_packs[complexity]] = 1 self.ingredients[Factorio.ordered_science_packs[complexity]] = 1
@property @property

View File

@@ -15,9 +15,9 @@ else:
def locality_needed(world: MultiWorld) -> bool: def locality_needed(world: MultiWorld) -> bool:
for player in world.player_ids: for player in world.player_ids:
if world.local_items[player].value: if world.worlds[player].options.local_items.value:
return True return True
if world.non_local_items[player].value: if world.worlds[player].options.non_local_items.value:
return True return True
# Group # Group
@@ -40,12 +40,12 @@ def locality_rules(world: MultiWorld):
forbid_data[sender][receiver].update(items) forbid_data[sender][receiver].update(items)
for receiving_player in world.player_ids: for receiving_player in world.player_ids:
local_items: typing.Set[str] = world.local_items[receiving_player].value local_items: typing.Set[str] = world.worlds[receiving_player].options.local_items.value
if local_items: if local_items:
for sending_player in world.player_ids: for sending_player in world.player_ids:
if receiving_player != sending_player: if receiving_player != sending_player:
forbid(sending_player, receiving_player, local_items) forbid(sending_player, receiving_player, local_items)
non_local_items: typing.Set[str] = world.non_local_items[receiving_player].value non_local_items: typing.Set[str] = world.worlds[receiving_player].options.non_local_items.value
if non_local_items: if non_local_items:
forbid(receiving_player, receiving_player, non_local_items) forbid(receiving_player, receiving_player, non_local_items)