mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-07 15:13:52 -08:00
Compare commits
16 Commits
core_optim
...
use_self.o
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2524ddc075 | ||
|
|
d545b78803 | ||
|
|
88b1c94eb2 | ||
|
|
7742d5d804 | ||
|
|
d3e148dcc6 | ||
|
|
b5fccde913 | ||
|
|
55e9b0687a | ||
|
|
79e1bf351e | ||
|
|
fcfea9d9aa | ||
|
|
cfc5508f06 | ||
|
|
62cb5f1fc2 | ||
|
|
7e70b16656 | ||
|
|
7b486b3380 | ||
|
|
09cac0a685 | ||
|
|
12c583533d | ||
|
|
c5af28a649 |
5
Main.py
5
Main.py
@@ -167,10 +167,11 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||
|
||||
# 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.
|
||||
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] = []
|
||||
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():
|
||||
player_world: AutoWorld.World = world.worlds[player]
|
||||
for count in items.values():
|
||||
|
||||
@@ -2,7 +2,9 @@ from __future__ import annotations
|
||||
|
||||
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):
|
||||
@@ -224,21 +226,20 @@ class StartCastle(Choice):
|
||||
default = option_yellow
|
||||
|
||||
|
||||
adventure_option_definitions: Dict[str, type(Option)] = {
|
||||
"dragon_slay_check": DragonSlayCheck,
|
||||
"death_link": DeathLink,
|
||||
"bat_logic": BatLogic,
|
||||
"freeincarnate_max": FreeincarnateMax,
|
||||
"dragon_rando_type": DragonRandoType,
|
||||
"connector_multi_slot": ConnectorMultiSlot,
|
||||
"yorgle_speed": YorgleStartingSpeed,
|
||||
"yorgle_min_speed": YorgleMinimumSpeed,
|
||||
"grundle_speed": GrundleStartingSpeed,
|
||||
"grundle_min_speed": GrundleMinimumSpeed,
|
||||
"rhindle_speed": RhindleStartingSpeed,
|
||||
"rhindle_min_speed": RhindleMinimumSpeed,
|
||||
"difficulty_switch_a": DifficultySwitchA,
|
||||
"difficulty_switch_b": DifficultySwitchB,
|
||||
"start_castle": StartCastle,
|
||||
|
||||
}
|
||||
@dataclass
|
||||
class AdventureOptions(PerGameCommonOptions):
|
||||
dragon_slay_check: DragonSlayCheck
|
||||
death_link: DeathLink
|
||||
bat_logic: BatLogic
|
||||
freeincarnate_max: FreeincarnateMax
|
||||
dragon_rando_type: DragonRandoType
|
||||
connector_multi_slot: ConnectorMultiSlot
|
||||
yorgle_speed: YorgleStartingSpeed
|
||||
yorgle_min_speed: YorgleMinimumSpeed
|
||||
grundle_speed: GrundleStartingSpeed
|
||||
grundle_min_speed: GrundleMinimumSpeed
|
||||
rhindle_speed: RhindleStartingSpeed
|
||||
rhindle_min_speed: RhindleMinimumSpeed
|
||||
difficulty_switch_a: DifficultySwitchA
|
||||
difficulty_switch_b: DifficultySwitchB
|
||||
start_castle: StartCastle
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from BaseClasses import MultiWorld, Region, Entrance, LocationProgressType
|
||||
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,
|
||||
@@ -24,7 +25,7 @@ def connect(world: MultiWorld, player: int, source: str, target: str, rule: call
|
||||
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():
|
||||
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))
|
||||
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)
|
||||
|
||||
for name, location_data in location_table.items():
|
||||
|
||||
@@ -6,7 +6,8 @@ from BaseClasses import LocationProgressType
|
||||
|
||||
def set_rules(self) -> None:
|
||||
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),
|
||||
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
|
||||
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 self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item:
|
||||
set_rule(world.get_location("Slay Yorgle", self.player),
|
||||
|
||||
@@ -15,7 +15,7 @@ from Options import AssembleOptions
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from Fill import fill_restrictive
|
||||
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, \
|
||||
AdventureAutoCollectLocation
|
||||
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"
|
||||
web: ClassVar[WebWorld] = AdventureWeb()
|
||||
|
||||
option_definitions: ClassVar[Dict[str, AssembleOptions]] = adventure_option_definitions
|
||||
options = AdventureOptions
|
||||
options_dataclass = AdventureOptions
|
||||
settings: ClassVar[AdventureSettings]
|
||||
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()}
|
||||
@@ -150,18 +151,18 @@ class AdventureWorld(World):
|
||||
bytearray(f"ADVENTURE{__version__.replace('.', '')[:3]}_{self.player}_{self.multiworld.seed}", "utf8")[:21]
|
||||
self.rom_name.extend([0] * (21 - len(self.rom_name)))
|
||||
|
||||
self.dragon_rando_type = self.multiworld.dragon_rando_type[self.player].value
|
||||
self.dragon_slay_check = self.multiworld.dragon_slay_check[self.player].value
|
||||
self.connector_multi_slot = self.multiworld.connector_multi_slot[self.player].value
|
||||
self.yorgle_speed = self.multiworld.yorgle_speed[self.player].value
|
||||
self.yorgle_min_speed = self.multiworld.yorgle_min_speed[self.player].value
|
||||
self.grundle_speed = self.multiworld.grundle_speed[self.player].value
|
||||
self.grundle_min_speed = self.multiworld.grundle_min_speed[self.player].value
|
||||
self.rhindle_speed = self.multiworld.rhindle_speed[self.player].value
|
||||
self.rhindle_min_speed = self.multiworld.rhindle_min_speed[self.player].value
|
||||
self.difficulty_switch_a = self.multiworld.difficulty_switch_a[self.player].value
|
||||
self.difficulty_switch_b = self.multiworld.difficulty_switch_b[self.player].value
|
||||
self.start_castle = self.multiworld.start_castle[self.player].value
|
||||
self.dragon_rando_type = self.options.dragon_rando_type.value
|
||||
self.dragon_slay_check = self.options.dragon_slay_check.value
|
||||
self.connector_multi_slot = self.options.connector_multi_slot.value
|
||||
self.yorgle_speed = self.options.yorgle_speed.value
|
||||
self.yorgle_min_speed = self.options.yorgle_min_speed.value
|
||||
self.grundle_speed = self.options.grundle_speed.value
|
||||
self.grundle_min_speed = self.options.grundle_min_speed.value
|
||||
self.rhindle_speed = self.options.rhindle_speed.value
|
||||
self.rhindle_min_speed = self.options.rhindle_min_speed.value
|
||||
self.difficulty_switch_a = self.options.difficulty_switch_a.value
|
||||
self.difficulty_switch_b = self.options.difficulty_switch_b.value
|
||||
self.start_castle = self.options.start_castle.value
|
||||
self.created_items = 0
|
||||
|
||||
if self.dragon_slay_check == 0:
|
||||
@@ -228,7 +229,7 @@ class AdventureWorld(World):
|
||||
extra_filler_count = num_locations - self.created_items
|
||||
|
||||
# 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)
|
||||
self.multiworld.itempool += [self.create_item("Freeincarnate") for _ in range(actual_freeincarnates)]
|
||||
self.created_items += actual_freeincarnates
|
||||
@@ -248,7 +249,7 @@ class AdventureWorld(World):
|
||||
self.created_items += 1
|
||||
|
||||
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
|
||||
|
||||
@@ -355,7 +356,7 @@ class AdventureWorld(World):
|
||||
auto_collect_locations: [AdventureAutoCollectLocation] = []
|
||||
local_item_to_location: {int, int} = {}
|
||||
bat_no_touch_locs: [LocationData] = []
|
||||
bat_logic: int = self.multiworld.bat_logic[self.player].value
|
||||
bat_logic: int = self.options.bat_logic.value
|
||||
try:
|
||||
rom_deltas: { int, int } = {}
|
||||
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)
|
||||
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)
|
||||
else:
|
||||
rom_deltas[connector_port_offset] = 0
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -163,26 +164,26 @@ class BlasphemousDeathLink(DeathLink):
|
||||
Note that Guilt Fragments will not appear when killed by Death Link."""
|
||||
|
||||
|
||||
blasphemous_options = {
|
||||
"prie_dieu_warp": PrieDieuWarp,
|
||||
"skip_cutscenes": SkipCutscenes,
|
||||
"corpse_hints": CorpseHints,
|
||||
"difficulty": Difficulty,
|
||||
"penitence": Penitence,
|
||||
"starting_location": StartingLocation,
|
||||
"ending": Ending,
|
||||
"skip_long_quests": SkipLongQuests,
|
||||
"thorn_shuffle" : ThornShuffle,
|
||||
"dash_shuffle": DashShuffle,
|
||||
"wall_climb_shuffle": WallClimbShuffle,
|
||||
"reliquary_shuffle": ReliquaryShuffle,
|
||||
"boots_of_pleading": CustomItem1,
|
||||
"purified_hand": CustomItem2,
|
||||
"start_wheel": StartWheel,
|
||||
"skill_randomizer": SkillRando,
|
||||
"enemy_randomizer": EnemyRando,
|
||||
"enemy_groups": EnemyGroups,
|
||||
"enemy_scaling": EnemyScaling,
|
||||
"death_link": BlasphemousDeathLink,
|
||||
"start_inventory": StartInventoryPool
|
||||
}
|
||||
@dataclass
|
||||
class BlasphemousOptions(PerGameCommonOptions):
|
||||
prie_dieu_warp: PrieDieuWarp
|
||||
skip_cutscenes: SkipCutscenes
|
||||
corpse_hints: CorpseHints
|
||||
difficulty: Difficulty
|
||||
penitence: Penitence
|
||||
starting_location: StartingLocation
|
||||
ending: Ending
|
||||
skip_long_quests: SkipLongQuests
|
||||
thorn_shuffle : ThornShuffle
|
||||
dash_shuffle: DashShuffle
|
||||
wall_climb_shuffle: WallClimbShuffle
|
||||
reliquary_shuffle: ReliquaryShuffle
|
||||
boots_of_pleading: CustomItem1
|
||||
purified_hand: CustomItem2
|
||||
start_wheel: StartWheel
|
||||
skill_randomizer: SkillRando
|
||||
enemy_randomizer: EnemyRando
|
||||
enemy_groups: EnemyGroups
|
||||
enemy_scaling: EnemyScaling
|
||||
death_link: BlasphemousDeathLink
|
||||
start_inventory: StartInventoryPool
|
||||
@@ -497,8 +497,9 @@ def chalice_rooms(state: CollectionState, player: int, number: int) -> bool:
|
||||
def rules(blasphemousworld):
|
||||
world = blasphemousworld.multiworld
|
||||
player = blasphemousworld.player
|
||||
logic = world.difficulty[player].value
|
||||
enemy = world.enemy_randomizer[player].value
|
||||
options = blasphemousworld.options
|
||||
logic = options.difficulty.value
|
||||
enemy = options.enemy_randomizer.value
|
||||
|
||||
|
||||
# D01Z01S01 (The Holy Line)
|
||||
@@ -2488,7 +2489,7 @@ def rules(blasphemousworld):
|
||||
|
||||
# D04Z02S01 (Mother of Mothers)
|
||||
# Items
|
||||
if world.purified_hand[player]:
|
||||
if options.purified_hand:
|
||||
set_rule(world.get_location("MoM: Western room ledge", player),
|
||||
lambda state: (
|
||||
state.has("D04Z02S01[N]", player)
|
||||
@@ -4093,7 +4094,7 @@ def rules(blasphemousworld):
|
||||
|
||||
# D17Z01S04 (Brotherhood of the Silent Sorrow)
|
||||
# Items
|
||||
if world.boots_of_pleading[player]:
|
||||
if options.boots_of_pleading:
|
||||
set_rule(world.get_location("BotSS: 2nd meeting with Redento", player),
|
||||
lambda state: redento(state, blasphemousworld, player, 2))
|
||||
# Doors
|
||||
|
||||
@@ -7,7 +7,7 @@ from .Locations import location_table
|
||||
from .Rooms import room_table, door_table
|
||||
from .Rules import rules
|
||||
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
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ class BlasphemousWorld(World):
|
||||
location_name_to_game_id = {loc["name"]: loc["game_id"] for loc in location_table}
|
||||
|
||||
item_name_groups = group_table
|
||||
option_definitions = blasphemous_options
|
||||
options = BlasphemousOptions
|
||||
options_dataclass = BlasphemousOptions
|
||||
|
||||
required_client_version = (0, 4, 2)
|
||||
|
||||
@@ -73,60 +74,61 @@ class BlasphemousWorld(World):
|
||||
|
||||
|
||||
def generate_early(self):
|
||||
options = self.options
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
|
||||
if not world.starting_location[player].randomized:
|
||||
if world.starting_location[player].value == 6 and world.difficulty[player].value < 2:
|
||||
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {world.starting_location[player]}"
|
||||
if not options.starting_location.randomized:
|
||||
if options.starting_location.value == 6 and options.difficulty.value < 2:
|
||||
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {options.starting_location}"
|
||||
" cannot be chosen if Difficulty is lower than Hard.")
|
||||
|
||||
if (world.starting_location[player].value == 0 or world.starting_location[player].value == 6) \
|
||||
and world.dash_shuffle[player]:
|
||||
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {world.starting_location[player]}"
|
||||
if (options.starting_location.value == 0 or options.starting_location.value == 6) \
|
||||
and options.dash_shuffle:
|
||||
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {options.starting_location}"
|
||||
" cannot be chosen if Shuffle Dash is enabled.")
|
||||
|
||||
if world.starting_location[player].value == 3 and world.wall_climb_shuffle[player]:
|
||||
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {world.starting_location[player]}"
|
||||
if options.starting_location.value == 3 and options.wall_climb_shuffle:
|
||||
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {options.starting_location}"
|
||||
" cannot be chosen if Shuffle Wall Climb is enabled.")
|
||||
else:
|
||||
locations: List[int] = [ 0, 1, 2, 3, 4, 5, 6 ]
|
||||
invalid: bool = False
|
||||
|
||||
if world.difficulty[player].value < 2:
|
||||
if options.difficulty.value < 2:
|
||||
locations.remove(6)
|
||||
|
||||
if world.dash_shuffle[player]:
|
||||
if options.dash_shuffle:
|
||||
locations.remove(0)
|
||||
if 6 in locations:
|
||||
locations.remove(6)
|
||||
|
||||
if world.wall_climb_shuffle[player]:
|
||||
if options.wall_climb_shuffle:
|
||||
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
|
||||
|
||||
if (world.starting_location[player].value == 0 or world.starting_location[player].value == 6) \
|
||||
and world.dash_shuffle[player]:
|
||||
if (options.starting_location.value == 0 or options.starting_location.value == 6) \
|
||||
and options.dash_shuffle:
|
||||
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
|
||||
|
||||
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"))
|
||||
|
||||
if not world.wall_climb_shuffle[player]:
|
||||
if not options.wall_climb_shuffle:
|
||||
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:
|
||||
world.exclude_locations[player].value.add(loc)
|
||||
options.exclude_locations.value.add(loc)
|
||||
|
||||
start_rooms: Dict[int, str] = {
|
||||
0: "D17Z01S01",
|
||||
@@ -138,12 +140,12 @@ class BlasphemousWorld(World):
|
||||
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):
|
||||
options = self.options
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
|
||||
removed: int = 0
|
||||
to_remove: List[str] = [
|
||||
@@ -157,46 +159,46 @@ class BlasphemousWorld(World):
|
||||
skipped_items = []
|
||||
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):
|
||||
skipped_items.append(item)
|
||||
junk += 1
|
||||
|
||||
skipped_items.extend(unrandomized_dict.values())
|
||||
|
||||
if world.thorn_shuffle[player] == 2:
|
||||
if options.thorn_shuffle == 2:
|
||||
for i in range(8):
|
||||
skipped_items.append("Thorn Upgrade")
|
||||
|
||||
if world.dash_shuffle[player]:
|
||||
if options.dash_shuffle:
|
||||
skipped_items.append(to_remove[removed])
|
||||
removed += 1
|
||||
elif not world.dash_shuffle[player]:
|
||||
elif not options.dash_shuffle:
|
||||
skipped_items.append("Dash Ability")
|
||||
|
||||
if world.wall_climb_shuffle[player]:
|
||||
if options.wall_climb_shuffle:
|
||||
skipped_items.append(to_remove[removed])
|
||||
removed += 1
|
||||
elif not world.wall_climb_shuffle[player]:
|
||||
elif not options.wall_climb_shuffle:
|
||||
skipped_items.append("Wall Climb Ability")
|
||||
|
||||
if not world.reliquary_shuffle[player]:
|
||||
if not options.reliquary_shuffle:
|
||||
skipped_items.extend(reliquary_set)
|
||||
elif world.reliquary_shuffle[player]:
|
||||
elif options.reliquary_shuffle:
|
||||
for i in range(3):
|
||||
skipped_items.append(to_remove[removed])
|
||||
removed += 1
|
||||
|
||||
if not world.boots_of_pleading[player]:
|
||||
if not options.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")
|
||||
|
||||
if world.start_wheel[player]:
|
||||
if options.start_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())
|
||||
|
||||
counter = Counter(skipped_items)
|
||||
@@ -219,23 +221,24 @@ class BlasphemousWorld(World):
|
||||
|
||||
|
||||
def pre_fill(self):
|
||||
options = self.options
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
|
||||
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")
|
||||
|
||||
if world.start_wheel[player]:
|
||||
if options.start_wheel:
|
||||
world.get_location("Beginning gift", player)\
|
||||
.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)
|
||||
|
||||
if world.thorn_shuffle[player] == 1:
|
||||
world.local_items[player].value.add("Thorn Upgrade")
|
||||
if options.thorn_shuffle == 1:
|
||||
options.local_items.value.add("Thorn Upgrade")
|
||||
|
||||
|
||||
def place_items_from_set(self, location_set: Set[str], name: str):
|
||||
@@ -251,6 +254,7 @@ class BlasphemousWorld(World):
|
||||
|
||||
|
||||
def create_regions(self) -> None:
|
||||
options = self.options
|
||||
player = self.player
|
||||
world = self.multiworld
|
||||
|
||||
@@ -282,9 +286,9 @@ class BlasphemousWorld(World):
|
||||
})
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
region: Region = world.get_region(loc["room"], player)
|
||||
@@ -310,9 +314,9 @@ class BlasphemousWorld(World):
|
||||
victory.place_locked_item(self.create_event("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))
|
||||
elif world.ending[self.player].value == 2:
|
||||
elif options.ending.value == 2:
|
||||
set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8) and
|
||||
state.has("Holy Wound of Abnegation", player))
|
||||
|
||||
@@ -332,11 +336,12 @@ class BlasphemousWorld(World):
|
||||
locations = []
|
||||
doors: Dict[str, str] = {}
|
||||
|
||||
options = self.options
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
thorns: bool = True
|
||||
|
||||
if world.thorn_shuffle[player].value == 2:
|
||||
if options.thorn_shuffle.value == 2:
|
||||
thorns = False
|
||||
|
||||
for loc in world.get_filled_locations(player):
|
||||
@@ -354,28 +359,28 @@ class BlasphemousWorld(World):
|
||||
locations.append(data)
|
||||
|
||||
config = {
|
||||
"LogicDifficulty": world.difficulty[player].value,
|
||||
"StartingLocation": world.starting_location[player].value,
|
||||
"LogicDifficulty": options.difficulty.value,
|
||||
"StartingLocation": options.starting_location.value,
|
||||
"VersionCreated": "AP",
|
||||
|
||||
"UnlockTeleportation": bool(world.prie_dieu_warp[player].value),
|
||||
"AllowHints": bool(world.corpse_hints[player].value),
|
||||
"AllowPenitence": bool(world.penitence[player].value),
|
||||
"UnlockTeleportation": bool(options.prie_dieu_warp.value),
|
||||
"AllowHints": bool(options.corpse_hints.value),
|
||||
"AllowPenitence": bool(options.penitence.value),
|
||||
|
||||
"ShuffleReliquaries": bool(world.reliquary_shuffle[player].value),
|
||||
"ShuffleBootsOfPleading": bool(world.boots_of_pleading[player].value),
|
||||
"ShufflePurifiedHand": bool(world.purified_hand[player].value),
|
||||
"ShuffleDash": bool(world.dash_shuffle[player].value),
|
||||
"ShuffleWallClimb": bool(world.wall_climb_shuffle[player].value),
|
||||
"ShuffleReliquaries": bool(options.reliquary_shuffle.value),
|
||||
"ShuffleBootsOfPleading": bool(options.boots_of_pleading.value),
|
||||
"ShufflePurifiedHand": bool(options.purified_hand.value),
|
||||
"ShuffleDash": bool(options.dash_shuffle.value),
|
||||
"ShuffleWallClimb": bool(options.wall_climb_shuffle.value),
|
||||
|
||||
"ShuffleSwordSkills": bool(world.skill_randomizer[player].value),
|
||||
"ShuffleSwordSkills": bool(options.skill_randomizer.value),
|
||||
"ShuffleThorns": thorns,
|
||||
"JunkLongQuests": bool(world.skip_long_quests[player].value),
|
||||
"StartWithWheel": bool(world.start_wheel[player].value),
|
||||
"JunkLongQuests": bool(options.skip_long_quests.value),
|
||||
"StartWithWheel": bool(options.start_wheel.value),
|
||||
|
||||
"EnemyShuffleType": world.enemy_randomizer[player].value,
|
||||
"MaintainClass": bool(world.enemy_groups[player].value),
|
||||
"AreaScaling": bool(world.enemy_scaling[player].value),
|
||||
"EnemyShuffleType": options.enemy_randomizer.value,
|
||||
"MaintainClass": bool(options.enemy_groups.value),
|
||||
"AreaScaling": bool(options.enemy_scaling.value),
|
||||
|
||||
"BossShuffleType": 0,
|
||||
"DoorShuffleType": 0
|
||||
@@ -385,8 +390,8 @@ class BlasphemousWorld(World):
|
||||
"locations": locations,
|
||||
"doors": doors,
|
||||
"cfg": config,
|
||||
"ending": world.ending[self.player].value,
|
||||
"death_link": bool(world.death_link[self.player].value)
|
||||
"ending": options.ending.value,
|
||||
"death_link": bool(options.death_link.value)
|
||||
}
|
||||
|
||||
return slot_data
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
import typing
|
||||
from Options import Option, Range
|
||||
from Options import Option, Range, PerGameCommonOptions
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class TaskAdvances(Range):
|
||||
@@ -69,12 +70,12 @@ class KillerTrapWeight(Range):
|
||||
default = 0
|
||||
|
||||
|
||||
bumpstik_options: typing.Dict[str, type(Option)] = {
|
||||
"task_advances": TaskAdvances,
|
||||
"turners": Turners,
|
||||
"paint_cans": PaintCans,
|
||||
"trap_count": Traps,
|
||||
"rainbow_trap_weight": RainbowTrapWeight,
|
||||
"spinner_trap_weight": SpinnerTrapWeight,
|
||||
"killer_trap_weight": KillerTrapWeight
|
||||
}
|
||||
@dataclass
|
||||
class BumpStikOptions(PerGameCommonOptions):
|
||||
task_advances: TaskAdvances
|
||||
turners: Turners
|
||||
paint_cans: PaintCans
|
||||
trap_count: Traps
|
||||
rainbow_trap_weight: RainbowTrapWeight
|
||||
spinner_trap_weight: SpinnerTrapWeight
|
||||
killer_trap_weight: KillerTrapWeight
|
||||
|
||||
@@ -43,7 +43,8 @@ class BumpStikWorld(World):
|
||||
|
||||
required_client_version = (0, 3, 8)
|
||||
|
||||
option_definitions = bumpstik_options
|
||||
options = BumpStikOptions
|
||||
options_dataclass = BumpStikOptions
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super(BumpStikWorld, self).__init__(world, player)
|
||||
@@ -86,13 +87,13 @@ class BumpStikWorld(World):
|
||||
return "Nothing"
|
||||
|
||||
def generate_early(self):
|
||||
self.task_advances = self.multiworld.task_advances[self.player].value
|
||||
self.turners = self.multiworld.turners[self.player].value
|
||||
self.paint_cans = self.multiworld.paint_cans[self.player].value
|
||||
self.traps = self.multiworld.trap_count[self.player].value
|
||||
self.rainbow_trap_weight = self.multiworld.rainbow_trap_weight[self.player].value
|
||||
self.spinner_trap_weight = self.multiworld.spinner_trap_weight[self.player].value
|
||||
self.killer_trap_weight = self.multiworld.killer_trap_weight[self.player].value
|
||||
self.task_advances = self.options.task_advances.value
|
||||
self.turners = self.options.turners.value
|
||||
self.paint_cans = self.options.paint_cans.value
|
||||
self.traps = self.options.trap_count.value
|
||||
self.rainbow_trap_weight = self.options.rainbow_trap_weight.value
|
||||
self.spinner_trap_weight = self.options.spinner_trap_weight.value
|
||||
self.killer_trap_weight = self.options.killer_trap_weight.value
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.multiworld, self.player)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import typing
|
||||
from Options import Option
|
||||
from Options import Option, PerGameCommonOptions
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
checksfinder_options: typing.Dict[str, type(Option)] = {
|
||||
}
|
||||
@dataclass
|
||||
class ChecksFinderOptions(PerGameCommonOptions):
|
||||
pass
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from BaseClasses import Region, Entrance, Item, Tutorial, ItemClassification
|
||||
from .Items import ChecksFinderItem, item_table, required_items
|
||||
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 ..AutoWorld import World, WebWorld
|
||||
from dataclasses import fields
|
||||
|
||||
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!
|
||||
"""
|
||||
game: str = "ChecksFinder"
|
||||
option_definitions = checksfinder_options
|
||||
options = ChecksFinderOptions
|
||||
options_dataclass = ChecksFinderOptions
|
||||
topology_present = True
|
||||
web = ChecksFinderWeb()
|
||||
|
||||
@@ -79,8 +81,8 @@ class ChecksFinderWorld(World):
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = self._get_checksfinder_data()
|
||||
for option_name in checksfinder_options:
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
for option_name in [field.name for field in fields(ChecksFinderOptions)]:
|
||||
option = getattr(self.options, option_name)
|
||||
if slot_data.get(option_name, None) is None and type(option.value) in {str, int}:
|
||||
slot_data[option_name] = int(option.value)
|
||||
return slot_data
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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):
|
||||
@@ -10,7 +11,7 @@ class CliqueItem(Item):
|
||||
class CliqueItemData(NamedTuple):
|
||||
code: Optional[int] = None
|
||||
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] = {
|
||||
@@ -21,11 +22,11 @@ item_data_table: Dict[str, CliqueItemData] = {
|
||||
"Button Activation": CliqueItemData(
|
||||
code=69696968,
|
||||
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(
|
||||
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(
|
||||
type=ItemClassification.progression,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from typing import Callable, Dict, NamedTuple, Optional
|
||||
|
||||
from BaseClasses import Location, MultiWorld
|
||||
from BaseClasses import Location
|
||||
from .Options import CliqueOptions
|
||||
|
||||
|
||||
|
||||
class CliqueLocation(Location):
|
||||
@@ -10,7 +12,7 @@ class CliqueLocation(Location):
|
||||
class CliqueLocationData(NamedTuple):
|
||||
region: str
|
||||
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
|
||||
|
||||
|
||||
@@ -22,7 +24,7 @@ location_data_table: Dict[str, CliqueLocationData] = {
|
||||
"The Item on the Desk": CliqueLocationData(
|
||||
region="The Button Realm",
|
||||
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(
|
||||
region="The Button Realm",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from typing import Dict
|
||||
|
||||
from Options import Choice, Option, Toggle
|
||||
from Options import Choice, Option, Toggle, PerGameCommonOptions
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class HardMode(Toggle):
|
||||
@@ -25,10 +26,12 @@ class ButtonColor(Choice):
|
||||
option_black = 11
|
||||
|
||||
|
||||
clique_options: Dict[str, type(Option)] = {
|
||||
"color": ButtonColor,
|
||||
"hard_mode": HardMode,
|
||||
|
||||
@dataclass
|
||||
class CliqueOptions(PerGameCommonOptions):
|
||||
color: ButtonColor
|
||||
hard_mode: HardMode
|
||||
|
||||
# DeathLink is always on. Always.
|
||||
# "death_link": DeathLink,
|
||||
}
|
||||
# death_link: DeathLink
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
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]:
|
||||
if getattr(multiworld, "hard_mode")[player]:
|
||||
def get_button_rule(options: CliqueOptions, player: int) -> Callable[[CollectionState], bool]:
|
||||
if getattr(options, "hard_mode"):
|
||||
return lambda state: state.has("Button Activation", player)
|
||||
|
||||
return lambda state: True
|
||||
|
||||
@@ -4,7 +4,7 @@ from BaseClasses import Region, Tutorial
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from .Items import CliqueItem, item_data_table, item_table
|
||||
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 .Rules import get_button_rule
|
||||
|
||||
@@ -29,7 +29,8 @@ class CliqueWorld(World):
|
||||
game = "Clique"
|
||||
data_version = 3
|
||||
web = CliqueWebWorld()
|
||||
option_definitions = clique_options
|
||||
options = CliqueOptions
|
||||
options_dataclass = CliqueOptions
|
||||
location_name_to_id = location_table
|
||||
item_name_to_id = item_table
|
||||
|
||||
@@ -39,7 +40,7 @@ class CliqueWorld(World):
|
||||
def create_items(self) -> None:
|
||||
item_pool: List[CliqueItem] = []
|
||||
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))
|
||||
|
||||
self.multiworld.itempool += item_pool
|
||||
@@ -55,27 +56,27 @@ class CliqueWorld(World):
|
||||
region = self.multiworld.get_region(region_name, self.player)
|
||||
region.add_locations({
|
||||
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)
|
||||
region.add_exits(region_data_table[region_name].connecting_regions)
|
||||
|
||||
# Place locked locations.
|
||||
for location_name, location_data in locked_locations.items():
|
||||
# Ignore locations we never created.
|
||||
if not location_data.can_create(self.multiworld, self.player):
|
||||
if not location_data.can_create(self.options):
|
||||
continue
|
||||
|
||||
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)
|
||||
|
||||
# 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:
|
||||
return "A Cool Filler Item (No Satisfaction Guaranteed)"
|
||||
|
||||
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("In the Player's Mind", self.player).access_rule = button_rule
|
||||
|
||||
@@ -88,5 +89,5 @@ class CliqueWorld(World):
|
||||
|
||||
def fill_slot_data(self):
|
||||
return {
|
||||
"color": getattr(self.multiworld, "color")[self.player].current_key
|
||||
"color": getattr(self.options, "color").current_key
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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):
|
||||
@@ -200,36 +201,36 @@ class EnableDLCOption(Toggle):
|
||||
display_name = "Enable DLC"
|
||||
|
||||
|
||||
dark_souls_options: typing.Dict[str, Option] = {
|
||||
"enable_weapon_locations": RandomizeWeaponLocations,
|
||||
"enable_shield_locations": RandomizeShieldLocations,
|
||||
"enable_armor_locations": RandomizeArmorLocations,
|
||||
"enable_ring_locations": RandomizeRingLocations,
|
||||
"enable_spell_locations": RandomizeSpellLocations,
|
||||
"enable_key_locations": RandomizeKeyLocations,
|
||||
"enable_boss_locations": RandomizeBossSoulLocations,
|
||||
"enable_npc_locations": RandomizeNPCLocations,
|
||||
"enable_misc_locations": RandomizeMiscLocations,
|
||||
"enable_health_upgrade_locations": RandomizeHealthLocations,
|
||||
"enable_progressive_locations": RandomizeProgressiveLocationsOption,
|
||||
"pool_type": PoolTypeOption,
|
||||
"guaranteed_items": GuaranteedItemsOption,
|
||||
"auto_equip": AutoEquipOption,
|
||||
"lock_equip": LockEquipOption,
|
||||
"no_weapon_requirements": NoWeaponRequirementsOption,
|
||||
"randomize_infusion": RandomizeInfusionOption,
|
||||
"randomize_infusion_percentage": RandomizeInfusionPercentageOption,
|
||||
"randomize_weapon_level": RandomizeWeaponLevelOption,
|
||||
"randomize_weapon_level_percentage": RandomizeWeaponLevelPercentageOption,
|
||||
"min_levels_in_5": MinLevelsIn5WeaponPoolOption,
|
||||
"max_levels_in_5": MaxLevelsIn5WeaponPoolOption,
|
||||
"min_levels_in_10": MinLevelsIn10WeaponPoolOption,
|
||||
"max_levels_in_10": MaxLevelsIn10WeaponPoolOption,
|
||||
"early_banner": EarlySmallLothricBanner,
|
||||
"late_basin_of_vows": LateBasinOfVowsOption,
|
||||
"late_dlc": LateDLCOption,
|
||||
"no_spell_requirements": NoSpellRequirementsOption,
|
||||
"no_equip_load": NoEquipLoadOption,
|
||||
"death_link": DeathLink,
|
||||
"enable_dlc": EnableDLCOption,
|
||||
}
|
||||
@dataclass
|
||||
class DarkSouls3Options(PerGameCommonOptions):
|
||||
enable_weapon_locations: RandomizeWeaponLocations
|
||||
enable_shield_locations: RandomizeShieldLocations
|
||||
enable_armor_locations: RandomizeArmorLocations
|
||||
enable_ring_locations: RandomizeRingLocations
|
||||
enable_spell_locations: RandomizeSpellLocations
|
||||
enable_key_locations: RandomizeKeyLocations
|
||||
enable_boss_locations: RandomizeBossSoulLocations
|
||||
enable_npc_locations: RandomizeNPCLocations
|
||||
enable_misc_locations: RandomizeMiscLocations
|
||||
enable_health_upgrade_locations: RandomizeHealthLocations
|
||||
enable_progressive_locations: RandomizeProgressiveLocationsOption
|
||||
pool_type: PoolTypeOption
|
||||
guaranteed_items: GuaranteedItemsOption
|
||||
auto_equip: AutoEquipOption
|
||||
lock_equip: LockEquipOption
|
||||
no_weapon_requirements: NoWeaponRequirementsOption
|
||||
randomize_infusion: RandomizeInfusionOption
|
||||
randomize_infusion_percentage: RandomizeInfusionPercentageOption
|
||||
randomize_weapon_level: RandomizeWeaponLevelOption
|
||||
randomize_weapon_level_percentage: RandomizeWeaponLevelPercentageOption
|
||||
min_levels_in_5: MinLevelsIn5WeaponPoolOption
|
||||
max_levels_in_5: MaxLevelsIn5WeaponPoolOption
|
||||
min_levels_in_10: MinLevelsIn10WeaponPoolOption
|
||||
max_levels_in_10: MaxLevelsIn10WeaponPoolOption
|
||||
early_banner: EarlySmallLothricBanner
|
||||
late_basin_of_vows: LateBasinOfVowsOption
|
||||
late_dlc: LateDLCOption
|
||||
no_spell_requirements: NoSpellRequirementsOption
|
||||
no_equip_load: NoEquipLoadOption
|
||||
death_link: DeathLink
|
||||
enable_dlc: EnableDLCOption
|
||||
|
||||
@@ -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 .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):
|
||||
@@ -43,7 +43,8 @@ class DarkSouls3World(World):
|
||||
"""
|
||||
|
||||
game: str = "Dark Souls III"
|
||||
option_definitions = dark_souls_options
|
||||
options = DarkSouls3Options
|
||||
options_dataclass = DarkSouls3Options
|
||||
topology_present: bool = True
|
||||
web = DarkSouls3Web()
|
||||
data_version = 8
|
||||
@@ -72,47 +73,47 @@ class DarkSouls3World(World):
|
||||
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
if self.multiworld.early_banner[self.player] == EarlySmallLothricBanner.option_early_global:
|
||||
self.multiworld.early_items[self.player]['Small Lothric Banner'] = 1
|
||||
elif self.multiworld.early_banner[self.player] == EarlySmallLothricBanner.option_early_local:
|
||||
self.multiworld.local_early_items[self.player]['Small Lothric Banner'] = 1
|
||||
if self.multiworld.enable_boss_locations[self.player] == Toggle.option_true:
|
||||
if self.options.early_banner == EarlySmallLothricBanner.option_early_global:
|
||||
self.options.early_items['Small Lothric Banner'] = 1
|
||||
elif self.options.early_banner == EarlySmallLothricBanner.option_early_local:
|
||||
self.options.local_early_items['Small Lothric Banner'] = 1
|
||||
if self.options.enable_boss_locations == Toggle.option_true:
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
def create_regions(self):
|
||||
progressive_location_table = []
|
||||
if self.multiworld.enable_progressive_locations[self.player]:
|
||||
if self.options.enable_progressive_locations:
|
||||
progressive_location_table = [] + \
|
||||
location_tables["Progressive Items 1"] + \
|
||||
location_tables["Progressive Items 2"] + \
|
||||
location_tables["Progressive Items 3"] + \
|
||||
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"]
|
||||
|
||||
if self.multiworld.enable_health_upgrade_locations[self.player]:
|
||||
if self.options.enable_health_upgrade_locations:
|
||||
progressive_location_table += location_tables["Progressive Items Health"]
|
||||
|
||||
# Create Vanilla Regions
|
||||
@@ -146,7 +147,7 @@ class DarkSouls3World(World):
|
||||
regions["Consumed King's Garden"].locations.append(potd_location)
|
||||
|
||||
# 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 [
|
||||
"Painted World of Ariandel 1",
|
||||
"Painted World of Ariandel 2",
|
||||
@@ -192,7 +193,7 @@ class DarkSouls3World(World):
|
||||
create_connection("Consumed King's Garden", "Untended Graves")
|
||||
|
||||
# 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("Painted World of Ariandel 1", "Painted World of Ariandel 2")
|
||||
create_connection("Painted World of Ariandel 2", "Dreg Heap")
|
||||
@@ -240,7 +241,7 @@ class DarkSouls3World(World):
|
||||
|
||||
|
||||
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}
|
||||
|
||||
@@ -254,7 +255,7 @@ class DarkSouls3World(World):
|
||||
itempool_by_category[location.category].append(location.default_item_name)
|
||||
|
||||
# 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):
|
||||
candidates = [
|
||||
item.name for item
|
||||
@@ -300,7 +301,7 @@ class DarkSouls3World(World):
|
||||
# A list of items we can replace
|
||||
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:
|
||||
# Break early just in case nothing is removable (if user is trying to guarantee more
|
||||
# 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 - 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),
|
||||
lambda state: state.has("Small Lothric Banner", self.player))
|
||||
|
||||
# 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),
|
||||
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 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),
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
lambda state: state.has("Jailbreaker's Key", 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),
|
||||
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),
|
||||
lambda state: state.has("Cell Key", 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),
|
||||
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),
|
||||
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),
|
||||
lambda state: state.has("Storm Ruler", 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
|
||||
# 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),
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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 - Yhorm the Giant", 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()}
|
||||
|
||||
# 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
|
||||
max_5 = self.multiworld.max_levels_in_5[self.player]
|
||||
min_5 = min(self.multiworld.min_levels_in_5[self.player], max_5)
|
||||
max_10 = self.multiworld.max_levels_in_10[self.player]
|
||||
min_10 = min(self.multiworld.min_levels_in_10[self.player], max_10)
|
||||
weapon_level_percentage = self.multiworld.randomize_weapon_level_percentage[self.player]
|
||||
max_5 = self.options.max_levels_in_5
|
||||
min_5 = min(self.options.min_levels_in_5, max_5)
|
||||
max_10 = self.options.max_levels_in_10
|
||||
min_10 = min(self.options.min_levels_in_10, max_10)
|
||||
weapon_level_percentage = self.options.randomize_weapon_level_percentage
|
||||
|
||||
for item in item_dictionary.values():
|
||||
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)
|
||||
|
||||
# Randomize some weapon infusions
|
||||
if self.multiworld.randomize_infusion[self.player] == Toggle.option_true:
|
||||
infusion_percentage = self.multiworld.randomize_infusion_percentage[self.player]
|
||||
if self.options.randomize_infusion == Toggle.option_true:
|
||||
infusion_percentage = self.options.randomize_infusion_percentage
|
||||
for item in item_dictionary.values():
|
||||
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:
|
||||
@@ -518,22 +519,22 @@ class DarkSouls3World(World):
|
||||
|
||||
slot_data = {
|
||||
"options": {
|
||||
"enable_weapon_locations": self.multiworld.enable_weapon_locations[self.player].value,
|
||||
"enable_shield_locations": self.multiworld.enable_shield_locations[self.player].value,
|
||||
"enable_armor_locations": self.multiworld.enable_armor_locations[self.player].value,
|
||||
"enable_ring_locations": self.multiworld.enable_ring_locations[self.player].value,
|
||||
"enable_spell_locations": self.multiworld.enable_spell_locations[self.player].value,
|
||||
"enable_key_locations": self.multiworld.enable_key_locations[self.player].value,
|
||||
"enable_boss_locations": self.multiworld.enable_boss_locations[self.player].value,
|
||||
"enable_npc_locations": self.multiworld.enable_npc_locations[self.player].value,
|
||||
"enable_misc_locations": self.multiworld.enable_misc_locations[self.player].value,
|
||||
"auto_equip": self.multiworld.auto_equip[self.player].value,
|
||||
"lock_equip": self.multiworld.lock_equip[self.player].value,
|
||||
"no_weapon_requirements": self.multiworld.no_weapon_requirements[self.player].value,
|
||||
"death_link": self.multiworld.death_link[self.player].value,
|
||||
"no_spell_requirements": self.multiworld.no_spell_requirements[self.player].value,
|
||||
"no_equip_load": self.multiworld.no_equip_load[self.player].value,
|
||||
"enable_dlc": self.multiworld.enable_dlc[self.player].value
|
||||
"enable_weapon_locations": self.options.enable_weapon_locations.value,
|
||||
"enable_shield_locations": self.options.enable_shield_locations.value,
|
||||
"enable_armor_locations": self.options.enable_armor_locations.value,
|
||||
"enable_ring_locations": self.options.enable_ring_locations.value,
|
||||
"enable_spell_locations": self.options.enable_spell_locations.value,
|
||||
"enable_key_locations": self.options.enable_key_locations.value,
|
||||
"enable_boss_locations": self.options.enable_boss_locations.value,
|
||||
"enable_npc_locations": self.options.enable_npc_locations.value,
|
||||
"enable_misc_locations": self.options.enable_misc_locations.value,
|
||||
"auto_equip": self.options.auto_equip.value,
|
||||
"lock_equip": self.options.lock_equip.value,
|
||||
"no_weapon_requirements": self.options.no_weapon_requirements.value,
|
||||
"death_link": self.options.death_link.value,
|
||||
"no_spell_requirements": self.options.no_spell_requirements.value,
|
||||
"no_equip_load": self.options.no_equip_load.value,
|
||||
"enable_dlc": self.options.enable_dlc.value
|
||||
},
|
||||
"seed": self.multiworld.seed_name, # to verify the server's multiworld
|
||||
"slot": self.multiworld.player_name[self.player], # to connect to server
|
||||
|
||||
@@ -6,6 +6,8 @@ import shutil
|
||||
import threading
|
||||
import zipfile
|
||||
from typing import Optional, TYPE_CHECKING, Any, List, Callable, Tuple, Union
|
||||
from dataclasses import fields
|
||||
import datetime
|
||||
|
||||
import jinja2
|
||||
|
||||
@@ -88,6 +90,7 @@ class FactorioModFile(worlds.Files.APContainer):
|
||||
def generate_mod(world: "Factorio", output_directory: str):
|
||||
player = world.player
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
global data_final_template, locale_template, control_template, data_template, settings_template
|
||||
with template_load_lock:
|
||||
if not data_final_template:
|
||||
@@ -129,40 +132,40 @@ def generate_mod(world: "Factorio", output_directory: str):
|
||||
"base_tech_table": base_tech_table,
|
||||
"tech_to_progressive_lookup": tech_to_progressive_lookup,
|
||||
"mod_name": mod_name,
|
||||
"allowed_science_packs": multiworld.max_science_pack[player].get_allowed_packs(),
|
||||
"allowed_science_packs": options.max_science_pack.get_allowed_packs(),
|
||||
"custom_technologies": multiworld.worlds[player].custom_technologies,
|
||||
"tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites,
|
||||
"slot_name": multiworld.player_name[player], "seed_name": multiworld.seed_name,
|
||||
"slot_player": player,
|
||||
"starting_items": multiworld.starting_items[player], "recipes": recipes,
|
||||
"starting_items": options.starting_items, "recipes": recipes,
|
||||
"random": random, "flop_random": flop_random,
|
||||
"recipe_time_scale": recipe_time_scales.get(multiworld.recipe_time[player].value, None),
|
||||
"recipe_time_range": recipe_time_ranges.get(multiworld.recipe_time[player].value, None),
|
||||
"recipe_time_scale": recipe_time_scales.get(options.recipe_time.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},
|
||||
"progressive_technology_table": {tech.name: tech.progressive for tech in
|
||||
progressive_technology_table.values()},
|
||||
"custom_recipes": world.custom_recipes,
|
||||
"max_science_pack": multiworld.max_science_pack[player].value,
|
||||
"max_science_pack": options.max_science_pack.value,
|
||||
"liquids": fluids,
|
||||
"goal": multiworld.goal[player].value,
|
||||
"energy_link": multiworld.energy_link[player].value,
|
||||
"goal": options.goal.value,
|
||||
"energy_link": options.energy_link.value,
|
||||
"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"]:
|
||||
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
|
||||
|
||||
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"].update({item: 1 for item in multiworld.free_sample_blacklist[player].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: 1 for item in options.free_sample_blacklist.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")
|
||||
mod = FactorioModFile(zf_path, player=player, player_name=multiworld.player_name[player])
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from __future__ import annotations
|
||||
import typing
|
||||
import datetime
|
||||
|
||||
from Options import Choice, OptionDict, OptionSet, Option, DefaultOnToggle, Range, DeathLink, Toggle, \
|
||||
StartInventoryPool
|
||||
StartInventoryPool, PerGameCommonOptions
|
||||
from schema import Schema, Optional, And, Or
|
||||
from dataclasses import dataclass
|
||||
|
||||
# schema helpers
|
||||
FloatRange = lambda low, high: And(Or(int, float), lambda f: low <= f <= high)
|
||||
@@ -422,50 +422,44 @@ class EnergyLink(Toggle):
|
||||
display_name = "EnergyLink"
|
||||
|
||||
|
||||
factorio_options: typing.Dict[str, type(Option)] = {
|
||||
"max_science_pack": MaxSciencePack,
|
||||
"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,
|
||||
}
|
||||
|
||||
# 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"
|
||||
class ChunkShuffle(Toggle):
|
||||
"""Entrance Randomizer.
|
||||
2023 April Fool's option. Shuffles chunk border transitions.
|
||||
Only valid during the Month of April. Forced off otherwise."""
|
||||
|
||||
|
||||
if datetime.datetime.today().day > 1:
|
||||
ChunkShuffle.__doc__ += """
|
||||
2023 April Fool's option. Shuffles chunk border transitions."""
|
||||
factorio_options["chunk_shuffle"] = ChunkShuffle
|
||||
@dataclass
|
||||
class FactorioOptions(PerGameCommonOptions):
|
||||
max_science_pack: MaxSciencePack
|
||||
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
|
||||
|
||||
@@ -20,10 +20,10 @@ def _sorter(location: "FactorioScienceLocation"):
|
||||
|
||||
|
||||
def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]:
|
||||
options = factorio_world.options
|
||||
world = factorio_world.multiworld
|
||||
player = factorio_world.player
|
||||
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)
|
||||
world.random.shuffle(locations)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from worlds.LauncherComponents import Component, components, Type, launch_subpro
|
||||
from worlds.generic import Rules
|
||||
from .Locations import location_pools, location_table
|
||||
from .Mod import generate_mod
|
||||
from .Options import factorio_options, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution
|
||||
from .Options import FactorioOptions, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution, TechCostMix
|
||||
from .Shapes import get_shapes
|
||||
from .Technologies import base_tech_table, recipe_sources, base_technology_table, \
|
||||
all_ingredient_names, all_product_sources, required_technologies, get_rocket_requirements, \
|
||||
@@ -88,6 +88,9 @@ class Factorio(World):
|
||||
location_pool: typing.List[FactorioScienceLocation]
|
||||
advancement_technologies: typing.Set[str]
|
||||
|
||||
options = FactorioOptions
|
||||
options_dataclass = FactorioOptions
|
||||
|
||||
web = FactorioWeb()
|
||||
|
||||
item_name_to_id = all_items
|
||||
@@ -117,11 +120,11 @@ class Factorio(World):
|
||||
|
||||
def generate_early(self) -> None:
|
||||
# if max < min, then swap max and min
|
||||
if self.multiworld.max_tech_cost[self.player] < self.multiworld.min_tech_cost[self.player]:
|
||||
self.multiworld.min_tech_cost[self.player].value, self.multiworld.max_tech_cost[self.player].value = \
|
||||
self.multiworld.max_tech_cost[self.player].value, self.multiworld.min_tech_cost[self.player].value
|
||||
self.tech_mix = self.multiworld.tech_cost_mix[self.player]
|
||||
self.skip_silo = self.multiworld.silo[self.player].value == Silo.option_spawn
|
||||
if self.options.max_tech_cost < self.options.min_tech_cost:
|
||||
self.options.min_tech_cost.value, self.options.max_tech_cost.value = \
|
||||
self.options.max_tech_cost.value, self.options.min_tech_cost.value
|
||||
self.tech_mix = self.options.tech_cost_mix
|
||||
self.skip_silo = self.options.silo.value == Silo.option_spawn
|
||||
|
||||
def create_regions(self):
|
||||
player = self.player
|
||||
@@ -132,17 +135,17 @@ class Factorio(World):
|
||||
nauvis = Region("Nauvis", player, self.multiworld)
|
||||
|
||||
location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \
|
||||
self.multiworld.evolution_traps[player] + \
|
||||
self.multiworld.attack_traps[player] + \
|
||||
self.multiworld.teleport_traps[player] + \
|
||||
self.multiworld.grenade_traps[player] + \
|
||||
self.multiworld.cluster_grenade_traps[player] + \
|
||||
self.multiworld.atomic_rocket_traps[player] + \
|
||||
self.multiworld.artillery_traps[player]
|
||||
self.options.evolution_traps + \
|
||||
self.options.attack_traps + \
|
||||
self.options.teleport_traps + \
|
||||
self.options.grenade_traps + \
|
||||
self.options.cluster_grenade_traps + \
|
||||
self.options.atomic_rocket_traps + \
|
||||
self.options.artillery_traps
|
||||
|
||||
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])
|
||||
try:
|
||||
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, "
|
||||
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]
|
||||
distribution: TechCostDistribution = self.multiworld.tech_cost_distribution[self.player]
|
||||
min_cost = self.multiworld.min_tech_cost[self.player]
|
||||
max_cost = self.multiworld.max_tech_cost[self.player]
|
||||
distribution: TechCostDistribution = self.options.tech_cost_distribution
|
||||
min_cost = self.options.min_tech_cost
|
||||
max_cost = self.options.max_tech_cost
|
||||
if distribution == distribution.option_even:
|
||||
rand_values = (random.randint(min_cost, max_cost) for _ in self.science_locations)
|
||||
else:
|
||||
@@ -164,7 +167,7 @@ class Factorio(World):
|
||||
distribution.option_high: max_cost}[distribution.value]
|
||||
rand_values = (random.triangular(min_cost, max_cost, mode) for _ in self.science_locations)
|
||||
rand_values = sorted(rand_values)
|
||||
if self.multiworld.ramping_tech_costs[self.player]:
|
||||
if self.options.ramping_tech_costs:
|
||||
def sorter(loc: FactorioScienceLocation):
|
||||
return loc.complexity, loc.rel_cost
|
||||
else:
|
||||
@@ -179,7 +182,7 @@ class Factorio(World):
|
||||
event = FactorioItem("Victory", ItemClassification.progression, None, player)
|
||||
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)
|
||||
nauvis.locations.append(location)
|
||||
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")
|
||||
for trap_name in traps:
|
||||
self.multiworld.itempool.extend(self.create_item(f"{trap_name} Trap") for _ in
|
||||
range(getattr(self.multiworld,
|
||||
f"{trap_name.lower().replace(' ', '_')}_traps")[player]))
|
||||
range(getattr(self.options,
|
||||
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))
|
||||
|
||||
cost_sorted_locations = sorted(self.science_locations, key=lambda location: location.name)
|
||||
@@ -206,7 +209,7 @@ class Factorio(World):
|
||||
"logistics": 1,
|
||||
"rocket-silo": -1}
|
||||
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
|
||||
for loc in self.science_locations:
|
||||
loc.revealed = True
|
||||
@@ -237,10 +240,10 @@ class Factorio(World):
|
||||
player = self.player
|
||||
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)
|
||||
|
||||
if self.multiworld.recipe_ingredients[self.player]:
|
||||
if self.options.recipe_ingredients:
|
||||
custom_recipe = self.custom_recipes[ingredient]
|
||||
|
||||
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))
|
||||
|
||||
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 \
|
||||
else next(iter(all_product_sources.get("rocket-silo")))
|
||||
part_recipe = self.custom_recipes["rocket-part"]
|
||||
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 \
|
||||
else next(iter(all_product_sources.get("satellite")))
|
||||
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")
|
||||
world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player)
|
||||
for technology in
|
||||
@@ -279,12 +282,12 @@ class Factorio(World):
|
||||
world.completion_condition[player] = lambda state: state.has('Victory', player)
|
||||
|
||||
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
|
||||
# 32 bit uint
|
||||
map_basic_settings["seed"] = self.multiworld.per_slot_randoms[self.player].randint(0, 2 ** 32 - 1)
|
||||
|
||||
start_location_hints: typing.Set[str] = self.multiworld.start_location_hints[self.player].value
|
||||
start_location_hints: typing.Set[str] = self.options.start_location_hints.value
|
||||
|
||||
for loc in self.science_locations:
|
||||
# show start_location_hints ingame
|
||||
@@ -308,8 +311,6 @@ class Factorio(World):
|
||||
|
||||
return super(Factorio, self).collect_item(state, item, remove)
|
||||
|
||||
option_definitions = factorio_options
|
||||
|
||||
@classmethod
|
||||
def stage_write_spoiler(cls, world, spoiler_handle):
|
||||
factorio_players = world.get_game_players(cls.game)
|
||||
@@ -437,25 +438,25 @@ class Factorio(World):
|
||||
|
||||
def set_custom_technologies(self):
|
||||
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():
|
||||
custom_technologies[technology_name] = technology.get_custom(self.multiworld, allowed_packs, self.player)
|
||||
return custom_technologies
|
||||
|
||||
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"]
|
||||
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.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category,
|
||||
{valid_pool[x]: 10 for x in range(3 + ingredients_offset)},
|
||||
original_rocket_part.products,
|
||||
original_rocket_part.energy)}
|
||||
|
||||
if self.multiworld.recipe_ingredients[self.player]:
|
||||
if self.options.recipe_ingredients:
|
||||
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])
|
||||
self.multiworld.random.shuffle(valid_pool)
|
||||
if pack in recipes: # skips over space science pack
|
||||
@@ -463,23 +464,23 @@ class Factorio(World):
|
||||
ingredients_offset)
|
||||
self.custom_recipes[pack] = new_recipe
|
||||
|
||||
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe \
|
||||
or self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
if self.options.silo.value == Silo.option_randomize_recipe \
|
||||
or self.options.satellite.value == Satellite.option_randomize_recipe:
|
||||
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]
|
||||
|
||||
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(
|
||||
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)
|
||||
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(
|
||||
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)
|
||||
self.custom_recipes["satellite"] = new_recipe
|
||||
bridge = "ap-energy-bridge"
|
||||
@@ -487,16 +488,16 @@ class Factorio(World):
|
||||
Recipe(bridge, "crafting", {"replace_1": 1, "replace_2": 1, "replace_3": 1,
|
||||
"replace_4": 1, "replace_5": 1, "replace_6": 1},
|
||||
{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)
|
||||
for ingredient_name in new_recipe.ingredients:
|
||||
new_recipe.ingredients[ingredient_name] = self.multiworld.random.randint(50, 500)
|
||||
self.custom_recipes[bridge] = new_recipe
|
||||
|
||||
needed_recipes = self.multiworld.max_science_pack[self.player].get_allowed_packs() | {"rocket-part"}
|
||||
if self.multiworld.silo[self.player] != Silo.option_spawn:
|
||||
needed_recipes = self.options.max_science_pack.get_allowed_packs() | {"rocket-part"}
|
||||
if self.options.silo != Silo.option_spawn:
|
||||
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"}
|
||||
|
||||
for recipe in needed_recipes:
|
||||
@@ -538,7 +539,7 @@ class FactorioScienceLocation(FactorioLocation):
|
||||
ingredients: typing.Dict[str, int]
|
||||
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)
|
||||
# "AP-{Complexity}-{Cost}"
|
||||
self.complexity = int(self.name[3]) - 1
|
||||
@@ -546,7 +547,7 @@ class FactorioScienceLocation(FactorioLocation):
|
||||
|
||||
self.ingredients = {Factorio.ordered_science_packs[self.complexity]: 1}
|
||||
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
|
||||
|
||||
@property
|
||||
|
||||
@@ -15,9 +15,9 @@ else:
|
||||
|
||||
def locality_needed(world: MultiWorld) -> bool:
|
||||
for player in world.player_ids:
|
||||
if world.local_items[player].value:
|
||||
if world.worlds[player].options.local_items.value:
|
||||
return True
|
||||
if world.non_local_items[player].value:
|
||||
if world.worlds[player].options.non_local_items.value:
|
||||
return True
|
||||
|
||||
# Group
|
||||
@@ -40,12 +40,12 @@ def locality_rules(world: MultiWorld):
|
||||
forbid_data[sender][receiver].update(items)
|
||||
|
||||
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:
|
||||
for sending_player in world.player_ids:
|
||||
if receiving_player != sending_player:
|
||||
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:
|
||||
forbid(receiving_player, receiving_player, non_local_items)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user