mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-18 21:38:13 -07:00
Compare commits
16 Commits
core_alway
...
use_self.o
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2524ddc075 | ||
|
|
d545b78803 | ||
|
|
88b1c94eb2 | ||
|
|
7742d5d804 | ||
|
|
d3e148dcc6 | ||
|
|
b5fccde913 | ||
|
|
55e9b0687a | ||
|
|
79e1bf351e | ||
|
|
fcfea9d9aa | ||
|
|
cfc5508f06 | ||
|
|
62cb5f1fc2 | ||
|
|
7e70b16656 | ||
|
|
7b486b3380 | ||
|
|
09cac0a685 | ||
|
|
12c583533d | ||
|
|
c5af28a649 |
@@ -1018,9 +1018,10 @@ class Location:
|
||||
self.parent_region = parent
|
||||
|
||||
def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool:
|
||||
return ((self.progress_type != LocationProgressType.EXCLUDED or not (item.advancement or item.useful))
|
||||
and self.item_rule(item)
|
||||
and (not check_access or self.can_reach(state)))
|
||||
return ((self.always_allow(state, item) and item.name not in state.multiworld.non_local_items[item.player])
|
||||
or ((self.progress_type != LocationProgressType.EXCLUDED or not (item.advancement or item.useful))
|
||||
and self.item_rule(item)
|
||||
and (not check_access or self.can_reach(state))))
|
||||
|
||||
def can_reach(self, state: CollectionState) -> bool:
|
||||
# self.access_rule computes faster on average, so placing it first for faster abort
|
||||
@@ -1061,15 +1062,6 @@ class Location:
|
||||
return "at " + self.name.replace("_", " ").replace("-", " ")
|
||||
|
||||
|
||||
class AlwaysAllowLocation(Location):
|
||||
"""Subclass of Location that allows an always_allow item rule, which overrides all other requirements."""
|
||||
always_allow: Callable[[Item], bool] = staticmethod(lambda item, state: False)
|
||||
|
||||
def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool:
|
||||
return ((self.always_allow(state, item) and item.name not in state.multiworld.non_local_items[item.player])
|
||||
or super().can_fill(state, item, check_access))
|
||||
|
||||
|
||||
class ItemClassification(IntFlag):
|
||||
filler = 0b0000 # aka trash, as in filler items like ammo, currency etc,
|
||||
progression = 0b0001 # Item that is logically relevant
|
||||
|
||||
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():
|
||||
|
||||
@@ -11,14 +11,11 @@ from flask import request, flash, redirect, url_for, session, render_template
|
||||
from markupsafe import Markup
|
||||
from pony.orm import commit, flush, select, rollback
|
||||
from pony.orm.core import TransactionIntegrityError
|
||||
import schema
|
||||
|
||||
import MultiServer
|
||||
from NetUtils import SlotType
|
||||
from Utils import VersionException, __version__
|
||||
from worlds import GamesPackage
|
||||
from worlds.Files import AutoPatchRegister
|
||||
from worlds.AutoWorld import data_package_checksum
|
||||
from . import app
|
||||
from .models import Seed, Room, Slot, GameDataPackage
|
||||
|
||||
@@ -26,15 +23,6 @@ banned_extensions = (".sfc", ".z64", ".n64", ".nes", ".smc", ".sms", ".gb", ".gb
|
||||
allowed_options_extensions = (".yaml", ".json", ".yml", ".txt", ".zip")
|
||||
allowed_generation_extensions = (".archipelago", ".zip")
|
||||
|
||||
games_package_schema = schema.Schema({
|
||||
"item_name_groups": {str: [str]},
|
||||
"item_name_to_id": {str: int},
|
||||
"location_name_groups": {str: [str]},
|
||||
"location_name_to_id": {str: int},
|
||||
schema.Optional("checksum"): str,
|
||||
schema.Optional("version"): int,
|
||||
})
|
||||
|
||||
|
||||
def allowed_options(filename: str) -> bool:
|
||||
return filename.endswith(allowed_options_extensions)
|
||||
@@ -49,8 +37,6 @@ def banned_file(filename: str) -> bool:
|
||||
|
||||
|
||||
def process_multidata(compressed_multidata, files={}):
|
||||
game_data: GamesPackage
|
||||
|
||||
decompressed_multidata = MultiServer.Context.decompress(compressed_multidata)
|
||||
|
||||
slots: typing.Set[Slot] = set()
|
||||
@@ -59,19 +45,11 @@ def process_multidata(compressed_multidata, files={}):
|
||||
game_data_packages: typing.List[GameDataPackage] = []
|
||||
for game, game_data in decompressed_multidata["datapackage"].items():
|
||||
if game_data.get("checksum"):
|
||||
original_checksum = game_data.pop("checksum")
|
||||
game_data = games_package_schema.validate(game_data)
|
||||
game_data = {key: value for key, value in sorted(game_data.items())}
|
||||
game_data["checksum"] = data_package_checksum(game_data)
|
||||
game_data_package = GameDataPackage(checksum=game_data["checksum"],
|
||||
data=pickle.dumps(game_data))
|
||||
if original_checksum != game_data["checksum"]:
|
||||
raise Exception(f"Original checksum {original_checksum} != "
|
||||
f"calculated checksum {game_data['checksum']} "
|
||||
f"for game {game}.")
|
||||
decompressed_multidata["datapackage"][game] = {
|
||||
"version": game_data.get("version", 0),
|
||||
"checksum": game_data["checksum"],
|
||||
"checksum": game_data["checksum"]
|
||||
}
|
||||
try:
|
||||
commit() # commit game data package
|
||||
@@ -86,15 +64,14 @@ def process_multidata(compressed_multidata, files={}):
|
||||
if slot_info.type == SlotType.group:
|
||||
continue
|
||||
slots.add(Slot(data=files.get(slot, None),
|
||||
player_name=slot_info.name,
|
||||
player_id=slot,
|
||||
game=slot_info.game))
|
||||
player_name=slot_info.name,
|
||||
player_id=slot,
|
||||
game=slot_info.game))
|
||||
flush() # commit slots
|
||||
|
||||
compressed_multidata = compressed_multidata[0:1] + zlib.compress(pickle.dumps(decompressed_multidata), 9)
|
||||
return slots, compressed_multidata
|
||||
|
||||
|
||||
def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, sid=None):
|
||||
if not owner:
|
||||
owner = session["_id"]
|
||||
|
||||
@@ -380,13 +380,12 @@ Additional arguments sent in this package will also be added to the [Retrieved](
|
||||
|
||||
Some special keys exist with specific return data, all of them have the prefix `_read_`, so `hints_{team}_{slot}` is `_read_hints_{team}_{slot}`.
|
||||
|
||||
| Name | Type | Notes |
|
||||
|----------------------------------|-------------------------------|-------------------------------------------------------|
|
||||
| hints_{team}_{slot} | list\[[Hint](#Hint)\] | All Hints belonging to the requested Player. |
|
||||
| slot_data_{slot} | dict\[str, any\] | slot_data belonging to the requested slot. |
|
||||
| item_name_groups_{game_name} | dict\[str, list\[str\]\] | item_name_groups belonging to the requested game. |
|
||||
| location_name_groups_{game_name} | dict\[str, list\[str\]\] | location_name_groups belonging to the requested game. |
|
||||
| client_status_{team}_{slot} | [ClientStatus](#ClientStatus) | The current game status of the requested player. |
|
||||
| Name | Type | Notes |
|
||||
|------------------------------|-------------------------------|---------------------------------------------------|
|
||||
| hints_{team}_{slot} | list\[[Hint](#Hint)\] | All Hints belonging to the requested Player. |
|
||||
| slot_data_{slot} | dict\[str, any\] | slot_data belonging to the requested slot. |
|
||||
| item_name_groups_{game_name} | dict\[str, list\[str\]\] | item_name_groups belonging to the requested game. |
|
||||
| client_status_{team}_{slot} | [ClientStatus](#ClientStatus) | The current game status of the requested player. |
|
||||
|
||||
### Set
|
||||
Used to write data to the server's data storage, that data can then be shared across worlds or just saved for later. Values for keys in the data storage can be retrieved with a [Get](#Get) package, or monitored with a [SetNotify](#SetNotify) package.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from enum import IntEnum
|
||||
|
||||
from BaseClasses import AlwaysAllowLocation, Item, ItemClassification, Region, MultiWorld
|
||||
from BaseClasses import Location, Item, ItemClassification, Region, MultiWorld
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .Dungeons import Dungeon
|
||||
from .Regions import LTTPRegion
|
||||
|
||||
|
||||
class ALttPLocation(AlwaysAllowLocation):
|
||||
class ALttPLocation(Location):
|
||||
game: str = "A Link to the Past"
|
||||
crystal: bool
|
||||
player_address: Optional[int]
|
||||
|
||||
@@ -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)
|
||||
@@ -210,7 +210,7 @@ class RecipeIngredientsOffset(Range):
|
||||
class FactorioStartItems(OptionDict):
|
||||
"""Mapping of Factorio internal item-name to amount granted on start."""
|
||||
display_name = "Starting Items"
|
||||
default = {"burner-mining-drill": 4, "stone-furnace": 4, "raw-fish": 50}
|
||||
default = {"burner-mining-drill": 19, "stone-furnace": 19}
|
||||
|
||||
|
||||
class FactorioFreeSampleBlacklist(OptionSet):
|
||||
@@ -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)
|
||||
|
||||
@@ -168,8 +168,7 @@ def allow_self_locking_items(spot: typing.Union[Location, Region], *item_names:
|
||||
"""
|
||||
This function sets rules on the supplied spot, such that the supplied item_name(s) can possibly be placed there.
|
||||
|
||||
spot: Location or Region that the item(s) are allowed to be placed in.
|
||||
Affected Locations must subclass AlwaysAllowLocation.
|
||||
spot: Location or Region that the item(s) are allowed to be placed in
|
||||
item_names: item name or names that are allowed to be placed in the Location or Region
|
||||
"""
|
||||
player = spot.player
|
||||
|
||||
@@ -444,8 +444,6 @@ def set_rules(hylics2world):
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Alcove Medallion", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Lava Medallion", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Foglast: Under Lair Medallion", player),
|
||||
lambda state: bridge_key(state, player))
|
||||
add_rule(world.get_location("Foglast: Mid-Air Medallion", player),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from functools import cached_property
|
||||
from typing import Optional, TYPE_CHECKING, cast
|
||||
|
||||
from BaseClasses import CollectionState, Item, ItemClassification, AlwaysAllowLocation, Region
|
||||
from BaseClasses import CollectionState, Item, ItemClassification, Location, Region
|
||||
from .constants import NOTES, PHOBEKINS, PROG_ITEMS, USEFUL_ITEMS
|
||||
from .regions import MEGA_SHARDS, REGIONS, SEALS
|
||||
from .shop import FIGURINES, PROG_SHOP_ITEMS, SHOP_ITEMS, USEFUL_SHOP_ITEMS
|
||||
@@ -33,7 +33,7 @@ class MessengerRegion(Region):
|
||||
world.multiworld.regions.append(self)
|
||||
|
||||
|
||||
class MessengerLocation(AlwaysAllowLocation):
|
||||
class MessengerLocation(Location):
|
||||
game = "The Messenger"
|
||||
|
||||
def __init__(self, player: int, name: str, loc_id: Optional[int], parent: MessengerRegion) -> None:
|
||||
|
||||
@@ -35,10 +35,6 @@ class NoitaWorld(World):
|
||||
|
||||
web = NoitaWeb()
|
||||
|
||||
def generate_early(self):
|
||||
if not self.multiworld.get_player_name(self.player).isascii():
|
||||
raise Exception("Noita yaml's slot name has invalid character(s).")
|
||||
|
||||
# Returned items will be sent over to the client
|
||||
def fill_slot_data(self):
|
||||
return {name: getattr(self.multiworld, name)[self.player].value for name in self.option_definitions}
|
||||
|
||||
@@ -40,8 +40,6 @@ or try restarting your game.
|
||||
### What is a YAML and why do I need one?
|
||||
You can see the [basic multiworld setup guide](/tutorial/Archipelago/setup/en) here on the Archipelago website to learn
|
||||
about why Archipelago uses YAML files and what they're for.
|
||||
Please note that Noita only allows you to type certain characters for your slot name.
|
||||
These characters are: `` !#$%&'()+,-.0123456789;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{}~<>|\/``
|
||||
|
||||
### Where do I get a YAML?
|
||||
You can use the [game settings page for Noita](/games/Noita/player-settings) here on the Archipelago website to
|
||||
@@ -56,4 +54,4 @@ Place the unzipped pack in the `packs` folder. Then, open Poptracker and open th
|
||||
Click on the "AP" symbol at the top, then enter the desired address, slot name, and password.
|
||||
|
||||
That's all you need for it. It will provide you with a quick reference to see which checks you've done and
|
||||
which checks you still have left.
|
||||
which checks you still have left.
|
||||
@@ -1,70 +1,422 @@
|
||||
# Guide de configuration pour Ocarina of Time Archipelago
|
||||
# Guide d'installation Archipelago pour Ocarina of Time
|
||||
|
||||
## Important
|
||||
|
||||
Comme nous utilisons BizHawk, ce guide s'applique uniquement aux systèmes Windows et Linux.
|
||||
Comme nous utilisons BizHawk, ce guide ne s'applique qu'aux systèmes Windows et Linux.
|
||||
|
||||
## Logiciel requis
|
||||
|
||||
- BizHawk : [Sorties BizHawk de TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory)
|
||||
- Les versions 2.3.1 et ultérieures sont prises en charge. La version 2.7 est recommandée pour des raisons de stabilité.
|
||||
- BizHawk : [BizHawk sort de TASVideos] (https://tasvideos.org/BizHawk/ReleaseHistory)
|
||||
- Les versions 2.3.1 et ultérieures sont prises en charge. La version 2.7 est recommandée pour la stabilité.
|
||||
- Des instructions d'installation détaillées pour BizHawk peuvent être trouvées sur le lien ci-dessus.
|
||||
- Les utilisateurs Windows doivent d'abord exécuter le programme d'installation des prérequis, qui peut également être trouvé sur le lien ci-dessus.
|
||||
- Les utilisateurs Windows doivent d'abord exécuter le programme d'installation prereq, qui peut également être trouvé sur le lien ci-dessus.
|
||||
- Le client Archipelago intégré, qui peut être installé [ici](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||
(sélectionnez « Ocarina of Time Client » lors de l'installation).
|
||||
(sélectionnez `Ocarina of Time Client` lors de l'installation).
|
||||
- Une ROM Ocarina of Time v1.0.
|
||||
|
||||
## Configuration de BizHawk
|
||||
|
||||
Une fois BizHawk installé, ouvrez EmuHawk et modifiez les paramètres suivants :
|
||||
Une fois BizHawk installé, ouvrez BizHawk et modifiez les paramètres suivants :
|
||||
|
||||
- (≤ 2,8) Allez dans Config > Personnaliser. Passez à l'onglet Avancé, puis faites passer le Lua Core de "NLua+KopiLua" à
|
||||
"Lua+LuaInterface". Puis redémarrez EmuHawk. Ceci est nécessaire pour que le script Lua fonctionne correctement.
|
||||
**REMARQUE : Même si « Lua+LuaInterface » est déjà sélectionné, basculez entre les deux options et resélectionnez-la. Nouvelles installations**
|
||||
**des versions plus récentes d'EmuHawk ont tendance à afficher "Lua+LuaInterface" comme option sélectionnée par défaut mais ce pendant refait l'épate juste au dessus par précautions**
|
||||
- Sous Config > Personnaliser > Avancé, assurez-vous que la case AutoSaveRAM est cochée et cliquez sur le bouton 5s.
|
||||
Cela réduit la possibilité de perdre des données de sauvegarde en cas de crash de l'émulateur.
|
||||
- Sous Config > Personnaliser, cochez les cases « Exécuter en arrière-plan » et « Accepter la saisie en arrière-plan ». Cela vous permettra continuez à jouer en arrière-plan, même si une autre fenêtre est sélectionnée.
|
||||
- Sous Config > Hotkeys, de nombreux raccourcis clavier sont répertoriés, dont beaucoup sont liés aux touches communes du clavier. Vous voudrez probablement pour désactiver la plupart d'entre eux, ce que vous pouvez faire rapidement en utilisant « Esc ».
|
||||
- Si vous jouez avec une manette, lorsque vous associez des commandes, désactivez "P1 A Up", "P1 A Down", "P1 A Left" et "P1 A Right".
|
||||
car ceux-ci interfèrent avec la visée s’ils sont liés. Définissez plutôt l'entrée directionnelle à l'aide de l'onglet Analogique.
|
||||
- Sous N64, activez "Utiliser le connecteur d'extension". Ceci est nécessaire pour que les états de sauvegarde fonctionnent.
|
||||
- Allez dans Config > Personnaliser. Basculez vers l'onglet Avancé, puis basculez le Lua Core de "NLua+KopiLua" vers
|
||||
"Interface Lua+Lua". Redémarrez ensuite BizHawk. Ceci est nécessaire pour que le script Lua fonctionne correctement.
|
||||
**REMARQUE : Même si "Lua+LuaInterface" est déjà sélectionné, basculez entre les deux options et resélectionnez-le. Nouvelles installations**
|
||||
** des versions plus récentes de BizHawk ont tendance à afficher "Lua+LuaInterface" comme option sélectionnée par défaut mais se chargent toujours **
|
||||
**"NLua+KopiLua" jusqu'à ce que cette étape soit terminée.**
|
||||
- Sous Config > Personnaliser > Avancé, assurez-vous que la case pour AutoSaveRAM est cochée et cliquez sur le bouton 5s.
|
||||
Cela réduit la possibilité de perdre des données de sauvegarde en cas de plantage de l'émulateur.
|
||||
- Sous Config > Personnaliser, cochez les cases "Exécuter en arrière-plan" et "Accepter la saisie en arrière-plan". Cela vous permettra de
|
||||
continuer à jouer en arrière-plan, même si une autre fenêtre est sélectionnée.
|
||||
- Sous Config> Raccourcis clavier, de nombreux raccourcis clavier sont répertoriés, dont beaucoup sont liés aux touches communes du clavier. Vous voudrez probablement
|
||||
désactiver la plupart d'entre eux, ce que vous pouvez faire rapidement en utilisant `Esc`.
|
||||
- Si vous jouez avec une manette, lorsque vous liez les commandes, désactivez "P1 A Up", "P1 A Down", "P1 A Left" et "P1 A Right"
|
||||
car ceux-ci interfèrent avec la visée s'ils sont liés. Définissez l'entrée directionnelle à l'aide de l'onglet Analogique à la place.
|
||||
- Sous N64, activez "Utiliser l'emplacement d'extension". Ceci est nécessaire pour que les sauvegardes fonctionnent.
|
||||
(Le menu N64 n'apparaît qu'après le chargement d'une ROM.)
|
||||
|
||||
Il est fortement recommandé d'associer les extensions de rom N64 (\*.n64, \*.z64) à l'EmuHawk que nous venons d'installer.
|
||||
Pour ce faire, vous devez simplement rechercher n'importe quelle rom N64 que vous possédez, faire un clic droit et sélectionner "Ouvrir avec...", déplier la liste qui apparaît et sélectionnez l'option du bas "Rechercher une autre application", puis accédez au dossier BizHawk et sélectionnez EmuHawk.exe.
|
||||
Il est fortement recommandé d'associer les extensions de rom N64 (\*.n64, \*.z64) au BizHawk que nous venons d'installer.
|
||||
Pour ce faire, nous devons simplement rechercher n'importe quelle rom N64 que nous possédons, faire un clic droit et sélectionner "Ouvrir avec ...", dépliez
|
||||
la liste qui apparaît et sélectionnez l'option du bas "Rechercher une autre application", puis naviguez jusqu'au dossier BizHawk
|
||||
et sélectionnez EmuHawk.exe.
|
||||
|
||||
Un guide de configuration BizHawk alternatif ainsi que divers conseils de dépannage sont disponibles
|
||||
Un guide de configuration BizHawk alternatif ainsi que divers conseils de dépannage peuvent être trouvés
|
||||
[ici](https://wiki.ootrandomizer.com/index.php?title=Bizhawk).
|
||||
|
||||
## Créer un fichier de configuration (.yaml)
|
||||
## Configuration de votre fichier YAML
|
||||
|
||||
### Qu'est-ce qu'un fichier de configuration et pourquoi en ai-je besoin ?
|
||||
### Qu'est-ce qu'un fichier YAML et pourquoi en ai-je besoin ?
|
||||
|
||||
Consultez le guide sur la configuration d'un YAML de base lors de la configuration de l'archipel.
|
||||
guide : [Guide de configuration de base de Multiworld](/tutorial/Archipelago/setup/en)
|
||||
Votre fichier YAML contient un ensemble d'options de configuration qui fournissent au générateur des informations sur la façon dont il doit
|
||||
générer votre jeu. Chaque joueur d'un multimonde fournira son propre fichier YAML. Cette configuration permet à chaque joueur de profiter
|
||||
d'une expérience personnalisée à leur goût, et différents joueurs dans le même multimonde peuvent tous avoir des options différentes.
|
||||
|
||||
### Où puis-je obtenir un fichier de configuration (.yaml) ?
|
||||
### Où puis-je obtenir un fichier YAML ?
|
||||
|
||||
La page Paramètres du lecteur sur le site Web vous permet de configurer vos paramètres personnels et d'exporter un fichier de configuration depuis eux. Page des paramètres du joueur : [Page des paramètres du joueur d'Ocarina of Time](/games/Ocarina%20of%20Time/player-settings)
|
||||
Un yaml OoT de base ressemblera à ceci. Il y a beaucoup d'options cosmétiques qui ont été supprimées pour le plaisir de ce
|
||||
tutoriel, si vous voulez voir une liste complète, téléchargez Archipelago depuis
|
||||
la [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases) et recherchez l'exemple de fichier dans
|
||||
le dossier "Lecteurs".
|
||||
|
||||
### Vérification de votre fichier de configuration
|
||||
``` yaml
|
||||
description: Modèle par défaut d'Ocarina of Time # Utilisé pour décrire votre yaml. Utile si vous avez plusieurs fichiers
|
||||
# Votre nom dans le jeu. Les espaces seront remplacés par des underscores et il y a une limite de 16 caractères
|
||||
name: VotreNom
|
||||
game:
|
||||
Ocarina of Time: 1
|
||||
requires:
|
||||
version: 0.1.7 # Version d'Archipelago requise pour que ce yaml fonctionne comme prévu.
|
||||
# Options partagées prises en charge par tous les jeux :
|
||||
accessibility:
|
||||
items: 0 # Garantit que vous pourrez acquérir tous les articles, mais vous ne pourrez peut-être pas accéder à tous les emplacements
|
||||
locations: 50 # Garantit que vous pourrez accéder à tous les emplacements, et donc à tous les articles
|
||||
none: 0 # Garantit seulement que le jeu est battable. Vous ne pourrez peut-être pas accéder à tous les emplacements ou acquérir tous les objets
|
||||
progression_balancing: # Un système pour réduire le BK, comme dans les périodes où vous ne pouvez rien faire, en déplaçant vos éléments dans une sphère d'accès antérieure
|
||||
0: 0 # Choisissez un nombre inférieur si cela ne vous dérange pas d'avoir un multimonde plus long, ou si vous pouvez glitch / faire du hors logique.
|
||||
25: 0
|
||||
50: 50 # Faites en sorte que vous ayez probablement des choses à faire.
|
||||
99: 0 # Obtenez les éléments importants tôt et restez en tête de la progression.
|
||||
Ocarina of Time:
|
||||
logic_rules: # définit la logique utilisée pour le générateur.
|
||||
glitchless: 50
|
||||
glitched: 0
|
||||
no_logic: 0
|
||||
logic_no_night_tokens_without_suns_song: # Les skulltulas nocturnes nécessiteront logiquement le Chant du soleil.
|
||||
false: 50
|
||||
true: 0
|
||||
open_forest: # Définissez l'état de la forêt de Kokiri et du chemin vers l'arbre Mojo.
|
||||
open: 50
|
||||
closed_deku: 0
|
||||
closed: 0
|
||||
open_kakariko: # Définit l'état de la porte du village de Kakariko.
|
||||
open: 50
|
||||
zelda: 0
|
||||
closed: 0
|
||||
open_door_of_time: # Ouvre la Porte du Temps par défaut, sans le Chant du Temps.
|
||||
false: 0
|
||||
true: 50
|
||||
zora_fountain: # Définit l'état du roi Zora, bloquant le chemin vers la fontaine de Zora.
|
||||
open: 0
|
||||
adult: 0
|
||||
closed: 50
|
||||
gerudo_fortress: # Définit les conditions d'accès à la forteresse Gerudo.
|
||||
normal: 0
|
||||
fast: 50
|
||||
open: 0
|
||||
bridge: # Définit les exigences pour le pont arc-en-ciel.
|
||||
open: 0
|
||||
vanilla: 0
|
||||
stones: 0
|
||||
medallions: 50
|
||||
dungeons: 0
|
||||
tokens: 0
|
||||
trials: # Définit le nombre d'épreuves requises dans le Château de Ganon.
|
||||
# vous pouvez ajouter des valeurs supplémentaires entre minimum et maximum
|
||||
0: 50 # valeur minimale
|
||||
6: 0 # valeur maximale
|
||||
random: 0
|
||||
random-low: 0
|
||||
random-higt: 0
|
||||
starting_age: # Choisissez l'âge auquel Link commencera.
|
||||
child: 50
|
||||
adult: 0
|
||||
triforce_hunt: # Rassemblez des morceaux de la Triforce dispersés dans le monde entier pour terminer le jeu.
|
||||
false: 50
|
||||
true: 0
|
||||
triforce_goal: # Nombre de pièces Triforce nécessaires pour terminer le jeu. Nombre total placé déterminé par le paramètre Item Pool.
|
||||
# vous pouvez ajouter des valeurs supplémentaires entre minimum et maximum
|
||||
1: 0 # valeur minimale
|
||||
50: 0 # valeur maximale
|
||||
random: 0
|
||||
random-low: 0
|
||||
random-higt: 0
|
||||
20: 50
|
||||
bombchus_in_logic: # Les Bombchus sont correctement pris en compte dans la logique. Le premier pack trouvé aura 20 chus ; Kokiri Shop et Bazaar vendent des recharges ; bombchus ouvre Bombchu Bowling.
|
||||
false: 50
|
||||
true: 0
|
||||
bridge_stones: # Définissez le nombre de pierres spirituelles requises pour le pont arc-en-ciel.
|
||||
# vous pouvez ajouter des valeurs supplémentaires entre minimum et maximum
|
||||
0: 0 # valeur minimale
|
||||
3: 50 # valeur maximale
|
||||
random: 0
|
||||
random-low: 0
|
||||
random-high: 0
|
||||
bridge_medallions: # Définissez le nombre de médaillons requis pour le pont arc-en-ciel.
|
||||
# vous pouvez ajouter des valeurs supplémentaires entre minimum et maximum
|
||||
0: 0 # valeur minimale
|
||||
6: 50 # valeur maximale
|
||||
random: 0
|
||||
random-low: 0
|
||||
random-high: 0
|
||||
bridge_rewards: # Définissez le nombre de récompenses de donjon requises pour le pont arc-en-ciel.
|
||||
# vous pouvez ajouter des valeurs supplémentaires entre minimum et maximum
|
||||
0: 0 # valeur minimale
|
||||
9: 50 # valeur maximale
|
||||
random: 0
|
||||
random-low: 0
|
||||
random-high: 0
|
||||
bridge_tokens: # Définissez le nombre de jetons Gold Skulltula requis pour le pont arc-en-ciel.
|
||||
# vous pouvez ajouter des valeurs supplémentaires entre minimum et maximum
|
||||
0: 0 # valeur minimale
|
||||
100: 50 # valeur maximale
|
||||
random: 0
|
||||
random-low: 0
|
||||
random-high: 0
|
||||
shuffle_mapcompass: # Contrôle où mélanger les cartes et boussoles des donjons.
|
||||
remove: 0
|
||||
startwith: 50
|
||||
vanilla: 0
|
||||
dungeon: 0
|
||||
overworld: 0
|
||||
any_dungeon: 0
|
||||
keysanity: 0
|
||||
shuffle_smallkeys: # Contrôle où mélanger les petites clés de donjon.
|
||||
remove: 0
|
||||
vanilla: 0
|
||||
dungeon: 50
|
||||
overworld: 0
|
||||
any_dungeon: 0
|
||||
keysanity: 0
|
||||
shuffle_hideoutkeys: # Contrôle où mélanger les petites clés de la Forteresse Gerudo.
|
||||
vanilla: 50
|
||||
overworld: 0
|
||||
any_dungeon: 0
|
||||
keysanity: 0
|
||||
shuffle_bosskeys: # Contrôle où mélanger les clés du boss, à l'exception de la clé du boss du château de Ganon.
|
||||
remove: 0
|
||||
vanilla: 0
|
||||
dungeon: 50
|
||||
overworld: 0
|
||||
any_dungeon: 0
|
||||
keysanity: 0
|
||||
shuffle_ganon_bosskey: # Contrôle où mélanger la clé du patron du château de Ganon.
|
||||
remove: 50
|
||||
vanilla: 0
|
||||
dungeon: 0
|
||||
overworld: 0
|
||||
any_dungeon: 0
|
||||
keysanity: 0
|
||||
on_lacs: 0
|
||||
enhance_map_compass: # La carte indique si un donjon est vanille ou MQ. La boussole indique quelle est la récompense du donjon.
|
||||
false: 50
|
||||
true: 0
|
||||
lacs_condition: # Définissez les exigences pour la cinématique de la Flèche lumineuse dans le Temple du temps.
|
||||
vanilla: 50
|
||||
stones: 0
|
||||
medallions: 0
|
||||
dungeons: 0
|
||||
tokens: 0
|
||||
lacs_stones: # Définissez le nombre de pierres spirituelles requises pour le LACS.
|
||||
# vous pouvez ajouter des valeurs supplémentaires entre minimum et maximum
|
||||
0: 0 # valeur minimale
|
||||
3: 50 # valeur maximale
|
||||
random: 0
|
||||
random-low: 0
|
||||
random-high: 0
|
||||
lacs_medallions: # Définissez le nombre de médaillons requis pour LACS.
|
||||
# vous pouvez ajouter des valeurs supplémentaires entre minimum et maximum
|
||||
0: 0 # valeur minimale
|
||||
6: 50 # valeur maximale
|
||||
random: 0
|
||||
random-low: 0
|
||||
random-high: 0
|
||||
lacs_rewards: # Définissez le nombre de récompenses de donjon requises pour LACS.
|
||||
# vous pouvez ajouter des valeurs supplémentaires entre minimum et maximum
|
||||
0: 0 # valeur minimale
|
||||
9: 50 # valeur maximale
|
||||
random: 0
|
||||
random-low: 0
|
||||
random-high: 0
|
||||
lacs_tokens: # Définissez le nombre de jetons Gold Skulltula requis pour le LACS.
|
||||
# vous pouvez ajouter des valeurs supplémentaires entre minimum et maximum
|
||||
0: 0 # valeur minimale
|
||||
100: 50 # valeur maximale
|
||||
random: 0
|
||||
random-low: 0
|
||||
random-high: 0
|
||||
shuffle_song_items: # Définit où les chansons peuvent apparaître.
|
||||
song: 50
|
||||
dungeon: 0
|
||||
any: 0
|
||||
shopsanity: # Randomise le contenu de la boutique. Réglez sur "off" pour ne pas mélanger les magasins ; "0" mélange les magasins mais ne n'autorise pas les articles multimonde dans les magasins.
|
||||
0: 0
|
||||
1: 0
|
||||
2: 0
|
||||
3: 0
|
||||
4: 0
|
||||
random_value: 0
|
||||
off: 50
|
||||
tokensanity : # les récompenses en jetons des Skulltulas dorées sont mélangées dans la réserve.
|
||||
off: 50
|
||||
dungeons: 0
|
||||
overworld: 0
|
||||
all: 0
|
||||
shuffle_scrubs: # Mélangez les articles vendus par Business Scrubs et fixez les prix.
|
||||
off: 50
|
||||
low: 0
|
||||
regular: 0
|
||||
random_prices: 0
|
||||
shuffle_cows: # les vaches donnent des objets lorsque la chanson d'Epona est jouée.
|
||||
false: 50
|
||||
true: 0
|
||||
shuffle_kokiri_sword: # Mélangez l'épée Kokiri dans la réserve d'objets.
|
||||
false: 50
|
||||
true: 0
|
||||
shuffle_ocarinas: # Mélangez l'Ocarina des fées et l'Ocarina du temps dans la réserve d'objets.
|
||||
false: 50
|
||||
true: 0
|
||||
shuffle_weird_egg: # Mélangez l'œuf bizarre de Malon au château d'Hyrule.
|
||||
false: 50
|
||||
true: 0
|
||||
shuffle_gerudo_card: # Mélangez la carte de membre Gerudo dans la réserve d'objets.
|
||||
false: 50
|
||||
true: 0
|
||||
shuffle_beans: # Ajoute un paquet de 10 haricots au pool d'objets et change le vendeur de haricots pour qu'il vende un objet pour 60 roupies.
|
||||
false: 50
|
||||
true: 0
|
||||
shuffle_medigoron_carpet_salesman: # Mélangez les objets vendus par Medigoron et le vendeur de tapis Haunted Wasteland.
|
||||
false: 50
|
||||
true: 0
|
||||
skip_child_zelda: # le jeu commence avec la lettre de Zelda, l'objet de la berceuse de Zelda et les événements pertinents déjà terminés.
|
||||
false: 50
|
||||
true: 0
|
||||
no_escape_sequence: # Ignore la séquence d'effondrement de la tour entre les combats de Ganondorf et de Ganon.
|
||||
false: 50
|
||||
true: 0
|
||||
no_guard_stealth: # Le vide sanitaire du château d'Hyrule passe directement à Zelda.
|
||||
false: 50
|
||||
true: 0
|
||||
no_epona_race: # Epona peut toujours être invoquée avec Epona's Song.
|
||||
false: 50
|
||||
true: 0
|
||||
skip_some_minigame_phases: # Dampe Race et Horseback Archery donnent les deux récompenses si la deuxième condition est remplie lors de la première tentative.
|
||||
false: 50
|
||||
true: 0
|
||||
complete_mask_quest: # Tous les masques sont immédiatement disponibles à l'emprunt dans la boutique Happy Mask.
|
||||
false: 50
|
||||
true: 0
|
||||
useful_cutscenes: # Réactive la cinématique Poe dans le Temple de la forêt, Darunia dans le Temple du feu et l'introduction de Twinrova. Surtout utile pour les pépins.
|
||||
false: 50
|
||||
true: 0
|
||||
fast_chests: # Toutes les animations des coffres sont rapides. Si désactivé, les éléments principaux ont une animation lente.
|
||||
false: 50
|
||||
true: 0
|
||||
free_scarecrow: # Sortir l'ocarina près d'un point d'épouvantail fait apparaître Pierre sans avoir besoin de la chanson.
|
||||
false: 50
|
||||
true: 0
|
||||
fast_bunny_hood: # Bunny Hood vous permet de vous déplacer 1,5 fois plus vite comme dans Majora's Mask.
|
||||
false: 50
|
||||
true: 0
|
||||
chicken_count: # Contrôle le nombre de Cuccos pour qu'Anju donne un objet en tant qu'enfant.
|
||||
\# vous pouvez ajouter des valeurs supplémentaires entre le minimum et le maximum
|
||||
0: 0 # valeur minimale
|
||||
7: 50 # valeur maximale
|
||||
random: 0
|
||||
random-low: 0
|
||||
random-high: 0
|
||||
hints: # les pierres à potins peuvent donner des indices sur l'emplacement des objets.
|
||||
none: 0
|
||||
mask: 0
|
||||
agony: 0
|
||||
always: 50
|
||||
hint_dist: # Choisissez la distribution d'astuces à utiliser. Affecte la fréquence des indices forts, quels éléments sont toujours indiqués, etc.
|
||||
balanced: 50
|
||||
ddr: 0
|
||||
league: 0
|
||||
mw2: 0
|
||||
scrubs: 0
|
||||
strong: 0
|
||||
tournament: 0
|
||||
useless: 0
|
||||
very_strong: 0
|
||||
text_shuffle: # Randomise le texte dans le jeu pour un effet comique.
|
||||
none: 50
|
||||
except_hints: 0
|
||||
complete: 0
|
||||
damage_multiplier: # contrôle la quantité de dégâts subis par Link.
|
||||
half: 0
|
||||
normal: 50
|
||||
double: 0
|
||||
quadruple: 0
|
||||
ohko: 0
|
||||
no_collectible_hearts: # les cœurs ne tomberont pas des ennemis ou des objets.
|
||||
false: 50
|
||||
true: 0
|
||||
starting_tod: # Changer l'heure de début de la journée.
|
||||
default: 50
|
||||
sunrise: 0
|
||||
morning: 0
|
||||
noon: 0
|
||||
afternoon: 0
|
||||
sunset: 0
|
||||
evening: 0
|
||||
midnight: 0
|
||||
witching_hour: 0
|
||||
start_with_consumables: # Démarrez le jeu avec des Deku Sticks et des Deku Nuts pleins.
|
||||
false: 50
|
||||
true: 0
|
||||
start_with_rupees: # Commencez avec un portefeuille plein. Les mises à niveau de portefeuille rempliront également votre portefeuille.
|
||||
false: 50
|
||||
true: 0
|
||||
item_pool_value: # modifie le nombre d'objets disponibles dans le jeu.
|
||||
plentiful: 0
|
||||
balanced: 50
|
||||
scarce: 0
|
||||
minimal: 0
|
||||
junk_ice_traps: # Ajoute des pièges à glace au pool d'objets.
|
||||
off: 0
|
||||
normal: 50
|
||||
on: 0
|
||||
mayhem: 0
|
||||
onslaught: 0
|
||||
ice_trap_appearance: # modifie l'apparence des pièges à glace en tant qu'éléments autonomes.
|
||||
major_only: 50
|
||||
junk_only: 0
|
||||
anything: 0
|
||||
logic_earliest_adult_trade: # premier élément pouvant apparaître dans la séquence d'échange pour adultes.
|
||||
pocket_egg: 0
|
||||
pocket_cucco: 0
|
||||
cojiro: 0
|
||||
odd_mushroom: 0
|
||||
poachers_saw: 0
|
||||
broken_sword: 0
|
||||
prescription: 50
|
||||
eyeball_frog: 0
|
||||
eyedrops: 0
|
||||
claim_check: 0
|
||||
logic_latest_adult_trade: # Dernier élément pouvant apparaître dans la séquence d'échange pour adultes.
|
||||
pocket_egg: 0
|
||||
pocket_cucco: 0
|
||||
cojiro: 0
|
||||
odd_mushroom: 0
|
||||
poachers_saw: 0
|
||||
broken_sword: 0
|
||||
prescription: 0
|
||||
eyeball_frog: 0
|
||||
eyedrops: 0
|
||||
claim_check: 50
|
||||
|
||||
Si vous souhaitez valider votre fichier de configuration pour vous assurer qu'il fonctionne, vous pouvez le faire sur la page YAML Validator.
|
||||
YAML page du validateur : [page de validation YAML](/mysterycheck)
|
||||
```
|
||||
|
||||
## Rejoindre un jeu multimonde
|
||||
## Rejoindre une partie MultiWorld
|
||||
|
||||
### Obtenez votre fichier OOT modifié
|
||||
### Obtenez votre fichier de correctif OOT
|
||||
|
||||
Lorsque vous rejoignez un jeu multimonde, il vous sera demandé de fournir votre fichier YAML à celui qui l'héberge. Une fois cela fait, l'hébergeur vous fournira soit un lien pour télécharger votre fichier de données, soit un fichier zip contenant les données de chacun des dossiers. Votre fichier de données doit avoir une extension « .apz5 ».
|
||||
Lorsque vous rejoignez un jeu multimonde, il vous sera demandé de fournir votre fichier YAML à l'hébergeur. Une fois que c'est Fini,
|
||||
l'hébergeur vous fournira soit un lien pour télécharger votre fichier de données, soit un fichier zip contenant les données de chacun
|
||||
des dossiers. Votre fichier de données doit avoir une extension `.apz5`.
|
||||
|
||||
Double-cliquez sur votre fichier « .apz5 » pour démarrer votre client et démarrer le processus de correctif ROM. Une fois le processus terminé (cela peut prendre un certain temps), le client et l'émulateur seront automatiquement démarrés (si vous avez associé l'extension à l'émulateur comme recommandé).
|
||||
Double-cliquez sur votre fichier `.apz5` pour démarrer votre client et démarrer le processus de patch ROM. Une fois le processus terminé
|
||||
(cela peut prendre un certain temps), le client et l'émulateur seront lancés automatiquement (si vous avez associé l'extension
|
||||
à l'émulateur comme recommandé).
|
||||
|
||||
### Connectez-vous au multiserveur
|
||||
|
||||
Une fois le client et l'émulateur démarrés, vous devez les connecter. Accédez à votre dossier d'installation Archipelago, puis vers `data/lua`, et faites glisser et déposez le script `connector_oot.lua` sur la fenêtre principale d'EmuHawk. (Vous pourrez plutôt ouvrir depuis la console Lua manuellement, cliquez sur `Script` 〉 `Open Script` et accédez à `connector_oot.lua` avec le sélecteur de fichiers.)
|
||||
Une fois le client et l'émulateur démarrés, vous devez les connecter. Dans l'émulateur, cliquez sur "Outils"
|
||||
menu et sélectionnez "Console Lua". Cliquez sur le bouton du dossier ou appuyez sur Ctrl+O pour ouvrir un script Lua.
|
||||
|
||||
Pour connecter le client au multiserveur, mettez simplement `<adresse>:<port>` dans le champ de texte en haut et appuyez sur Entrée (si le serveur utilise un mot de passe, tapez dans le champ de texte inférieur `/connect <adresse>:<port> [mot de passe]`)
|
||||
Accédez à votre dossier d'installation Archipelago et ouvrez `data/lua/connector_oot.lua`.
|
||||
|
||||
Vous êtes maintenant prêt à commencer votre aventure dans Hyrule.
|
||||
Pour connecter le client au multiserveur, mettez simplement `<adresse>:<port>` dans le champ de texte en haut et appuyez sur Entrée (si le
|
||||
le serveur utilise un mot de passe, saisissez dans le champ de texte inférieur `/connect <adresse>:<port> [mot de passe]`)
|
||||
|
||||
Vous êtes maintenant prêt à commencer votre aventure à Hyrule.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Pokémon Emerald
|
||||
|
||||
## Where is the options page?
|
||||
## Where is the settings page?
|
||||
|
||||
You can read through all the options and generate a YAML [here](../player-options).
|
||||
You can read through all the settings and generate a YAML [here](../player-settings).
|
||||
|
||||
## What does randomization do to this game?
|
||||
|
||||
This randomizer handles both item randomization and pokémon randomization. Badges, HMs, gifts from NPCs, and items on
|
||||
the ground can all be randomized. There are also many options for randomizing wild pokémon, starters, opponent pokémon,
|
||||
abilities, types, etc… You can even change a percentage of single battles into double battles. Check the
|
||||
[options page](../player-options) for a more comprehensive list of what can be changed.
|
||||
[settings page](../player-settings) for a more comprehensive list of what can be changed.
|
||||
|
||||
## What items and locations get randomized?
|
||||
|
||||
@@ -28,7 +28,7 @@ randomizer. Here are some of the more important ones:
|
||||
- You can have both bikes simultaneously
|
||||
- You can run or bike (almost) anywhere
|
||||
- The Wally catching tutorial is skipped
|
||||
- All text is instant and, with an option, can be automatically progressed by holding A
|
||||
- All text is instant, and with a setting it can be automatically progressed by holding A
|
||||
- When a Repel runs out, you will be prompted to use another
|
||||
- Many more minor improvements…
|
||||
|
||||
@@ -44,7 +44,7 @@ your inventory.
|
||||
## When the player receives an item, what happens?
|
||||
|
||||
You will only receive items while in the overworld and not during battles. Depending on your `Receive Item Messages`
|
||||
option, the received item will either be silently added to your bag or you will be shown a text box with the item's
|
||||
setting, the received item will either be silently added to your bag or you will be shown a text box with the item's
|
||||
name and the item will be added to your bag while a fanfare plays.
|
||||
|
||||
## Can I play offline?
|
||||
|
||||
@@ -26,8 +26,8 @@ clear it.
|
||||
|
||||
## Generating and Patching a Game
|
||||
|
||||
1. Create your options file (YAML). You can make one on the
|
||||
[Pokémon Emerald options page](../../../games/Pokemon%20Emerald/player-options).
|
||||
1. Create your settings file (YAML). You can make one on the
|
||||
[Pokémon Emerald settings page](../../../games/Pokemon%20Emerald/player-settings).
|
||||
2. Follow the general Archipelago instructions for [generating a game](../../Archipelago/setup/en#generating-a-game).
|
||||
This will generate an output file for you. Your patch file will have the `.apemerald` file extension.
|
||||
3. Open `ArchipelagoLauncher.exe`
|
||||
|
||||
@@ -281,20 +281,18 @@ class PokemonRedBlueWorld(World):
|
||||
self.multiworld.itempool.remove(badge)
|
||||
progitempool.remove(badge)
|
||||
for _ in range(5):
|
||||
badgelocs = [
|
||||
self.multiworld.get_location(loc, self.player) for loc in [
|
||||
"Pewter Gym - Brock Prize", "Cerulean Gym - Misty Prize",
|
||||
"Vermilion Gym - Lt. Surge Prize", "Celadon Gym - Erika Prize",
|
||||
"Fuchsia Gym - Koga Prize", "Saffron Gym - Sabrina Prize",
|
||||
"Cinnabar Gym - Blaine Prize", "Viridian Gym - Giovanni Prize"
|
||||
] if self.multiworld.get_location(loc, self.player).item is None]
|
||||
badgelocs = [self.multiworld.get_location(loc, self.player) for loc in [
|
||||
"Pewter Gym - Brock Prize", "Cerulean Gym - Misty Prize",
|
||||
"Vermilion Gym - Lt. Surge Prize", "Celadon Gym - Erika Prize",
|
||||
"Fuchsia Gym - Koga Prize", "Saffron Gym - Sabrina Prize",
|
||||
"Cinnabar Gym - Blaine Prize", "Viridian Gym - Giovanni Prize"]]
|
||||
state = self.multiworld.get_all_state(False)
|
||||
self.multiworld.random.shuffle(badges)
|
||||
self.multiworld.random.shuffle(badgelocs)
|
||||
badgelocs_copy = badgelocs.copy()
|
||||
# allow_partial so that unplaced badges aren't lost, for debugging purposes
|
||||
fill_restrictive(self.multiworld, state, badgelocs_copy, badges, True, True, allow_partial=True)
|
||||
if len(badges) > 8 - len(badgelocs):
|
||||
if badges:
|
||||
for location in badgelocs:
|
||||
if location.item:
|
||||
badges.append(location.item)
|
||||
@@ -304,7 +302,6 @@ class PokemonRedBlueWorld(World):
|
||||
for location in badgelocs:
|
||||
if location.item:
|
||||
fill_locations.remove(location)
|
||||
progitempool += badges
|
||||
break
|
||||
else:
|
||||
raise FillError(f"Failed to place badges for player {self.player}")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
from BaseClasses import AlwaysAllowLocation
|
||||
from BaseClasses import Location
|
||||
from .rom_addresses import rom_addresses
|
||||
from . import poke_data
|
||||
loc_id_start = 172000000
|
||||
@@ -2767,7 +2767,7 @@ for mon in poke_data.evolves_from:
|
||||
location_data.append(LocationData("Evolution", mon, mon, event=True))
|
||||
|
||||
|
||||
class PokemonRBLocation(AlwaysAllowLocation):
|
||||
class PokemonRBLocation(Location):
|
||||
game = "Pokemon Red and Blue"
|
||||
|
||||
def __init__(self, player, name, address, rom_address, type, level, level_address):
|
||||
|
||||
@@ -61,7 +61,7 @@ class SMSNIClient(SNIClient):
|
||||
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
|
||||
|
||||
rom_name = await snes_read(ctx, SM_ROMNAME_START, ROMNAME_SIZE)
|
||||
if rom_name is None or rom_name == bytes([0] * ROMNAME_SIZE) or rom_name[:2] != b"SM" or rom_name[2] not in b"1234567890":
|
||||
if rom_name is None or rom_name == bytes([0] * ROMNAME_SIZE) or rom_name[:2] != b"SM" or rom_name[:3] == b"SMW":
|
||||
return False
|
||||
|
||||
ctx.game = self.game
|
||||
|
||||
@@ -21,7 +21,7 @@ def fix_reg(entrance_map: dict, entrance: SM64Levels, invalid_regions: set,
|
||||
def set_rules(world, player: int, area_connections: dict):
|
||||
randomized_level_to_paintings = sm64_level_to_paintings.copy()
|
||||
randomized_level_to_secrets = sm64_level_to_secrets.copy()
|
||||
if world.AreaRandomizer[player].value >= 1: # Some randomization is happening, randomize Courses
|
||||
if world.AreaRandomizer[player].value == 1: # Some randomization is happening, randomize Courses
|
||||
randomized_level_to_paintings = shuffle_dict_keys(world,sm64_level_to_paintings)
|
||||
if world.AreaRandomizer[player].value == 2: # Randomize Secrets as well
|
||||
randomized_level_to_secrets = shuffle_dict_keys(world,sm64_level_to_secrets)
|
||||
|
||||
@@ -69,7 +69,7 @@ class SMZ3SNIClient(SNIClient):
|
||||
ctx.finished_game = True
|
||||
return
|
||||
|
||||
data = await snes_read(ctx, SMZ3_RECV_PROGRESS_ADDR + 0xD3C, 4)
|
||||
data = await snes_read(ctx, SMZ3_RECV_PROGRESS_ADDR + 0x680, 4)
|
||||
if data is None:
|
||||
return
|
||||
|
||||
@@ -77,14 +77,14 @@ class SMZ3SNIClient(SNIClient):
|
||||
recv_item = data[2] | (data[3] << 8)
|
||||
|
||||
while (recv_index < recv_item):
|
||||
item_address = recv_index * 2
|
||||
message = await snes_read(ctx, SMZ3_RECV_PROGRESS_ADDR + 0xDA0 + item_address, 2)
|
||||
is_z3_item = ((message[1] & 0x80) != 0)
|
||||
masked_part = (message[1] & 0x7F) if is_z3_item else message[1]
|
||||
item_index = ((message[0] | (masked_part << 8)) >> 3) + (256 if is_z3_item else 0)
|
||||
item_address = recv_index * 8
|
||||
message = await snes_read(ctx, SMZ3_RECV_PROGRESS_ADDR + 0x700 + item_address, 8)
|
||||
is_z3_item = ((message[5] & 0x80) != 0)
|
||||
masked_part = (message[5] & 0x7F) if is_z3_item else message[5]
|
||||
item_index = ((message[4] | (masked_part << 8)) >> 3) + (256 if is_z3_item else 0)
|
||||
|
||||
recv_index += 1
|
||||
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + 0xD3C, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + 0x680, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||
|
||||
from .TotalSMZ3.Location import locations_start_id
|
||||
from . import convertLocSMZ3IDToAPID
|
||||
@@ -95,7 +95,7 @@ class SMZ3SNIClient(SNIClient):
|
||||
snes_logger.info(f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||
|
||||
data = await snes_read(ctx, SMZ3_RECV_PROGRESS_ADDR + 0xD36, 4)
|
||||
data = await snes_read(ctx, SMZ3_RECV_PROGRESS_ADDR + 0x600, 4)
|
||||
if data is None:
|
||||
return
|
||||
|
||||
@@ -106,10 +106,10 @@ class SMZ3SNIClient(SNIClient):
|
||||
item = ctx.items_received[item_out_ptr]
|
||||
item_id = item.item - items_start_id
|
||||
|
||||
player_id = item.player if item.player < SMZ3_ROM_PLAYER_LIMIT else 0
|
||||
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + item_out_ptr * 2, bytes([player_id, item_id]))
|
||||
player_id = item.player if item.player <= SMZ3_ROM_PLAYER_LIMIT else 0
|
||||
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + item_out_ptr * 4, bytes([player_id & 0xFF, (player_id >> 8) & 0xFF, item_id & 0xFF, (item_id >> 8) & 0xFF]))
|
||||
item_out_ptr += 1
|
||||
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + 0xD38, bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
||||
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + 0x602, bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names[item.item], 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names[item.location], item_out_ptr, len(ctx.items_received)))
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import copy
|
||||
import os
|
||||
@@ -7,7 +5,7 @@ import random
|
||||
import threading
|
||||
from typing import Dict, Set, TextIO
|
||||
|
||||
from BaseClasses import Region, Entrance, AlwaysAllowLocation, MultiWorld, Item, ItemClassification, CollectionState, \
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, CollectionState, \
|
||||
Tutorial
|
||||
from worlds.generic.Rules import set_rule
|
||||
from .TotalSMZ3.Item import ItemType
|
||||
@@ -82,12 +80,11 @@ class SMZ3World(World):
|
||||
locationNamesGT: Set[str] = {loc.Name for loc in GanonsTower(None, None).Locations}
|
||||
|
||||
# first added for 0.2.6
|
||||
# optimized message queues for 0.4.4
|
||||
required_client_version = (0, 4, 4)
|
||||
required_client_version = (0, 2, 6)
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
self.rom_name_available_event = threading.Event()
|
||||
self.locations: Dict[str, SMZ3Location] = {}
|
||||
self.locations: Dict[str, Location] = {}
|
||||
self.unreachable = []
|
||||
super().__init__(world, player)
|
||||
|
||||
@@ -654,7 +651,7 @@ class SMZ3World(World):
|
||||
return ret
|
||||
|
||||
|
||||
class SMZ3Location(AlwaysAllowLocation):
|
||||
class SMZ3Location(Location):
|
||||
game: str = "SMZ3"
|
||||
|
||||
def __init__(self, player: int, name: str, address=None, parent=None):
|
||||
|
||||
Binary file not shown.
@@ -93,11 +93,7 @@ def get_pool_core(world):
|
||||
|
||||
# Starting Weapon
|
||||
start_weapon_locations = starting_weapon_locations.copy()
|
||||
final_starting_weapons = [weapon for weapon in starting_weapons
|
||||
if weapon not in world.multiworld.non_local_items[world.player]]
|
||||
if not final_starting_weapons:
|
||||
final_starting_weapons = starting_weapons
|
||||
starting_weapon = random.choice(final_starting_weapons)
|
||||
starting_weapon = random.choice(starting_weapons)
|
||||
if world.multiworld.StartingPosition[world.player] == StartingPosition.option_safe:
|
||||
placed_items[start_weapon_locations[0]] = starting_weapon
|
||||
elif world.multiworld.StartingPosition[world.player] in \
|
||||
|
||||
@@ -200,17 +200,15 @@ class TLoZWorld(World):
|
||||
for i in range(0, 0x7F):
|
||||
item = rom_data[first_quest_dungeon_items_early + i]
|
||||
if item & 0b00100000:
|
||||
item = item & 0b11011111
|
||||
item = item | 0b01000000
|
||||
rom_data[first_quest_dungeon_items_early + i] = item
|
||||
rom_data[first_quest_dungeon_items_early + i] = item & 0b11011111
|
||||
rom_data[first_quest_dungeon_items_early + i] = item | 0b01000000
|
||||
if item & 0b00011111 == 0b00000011: # Change all Item 03s to Item 3F, the proper "nothing"
|
||||
rom_data[first_quest_dungeon_items_early + i] = item | 0b00111111
|
||||
|
||||
item = rom_data[first_quest_dungeon_items_late + i]
|
||||
if item & 0b00100000:
|
||||
item = item & 0b11011111
|
||||
item = item | 0b01000000
|
||||
rom_data[first_quest_dungeon_items_late + i] = item
|
||||
rom_data[first_quest_dungeon_items_late + i] = item & 0b11011111
|
||||
rom_data[first_quest_dungeon_items_late + i] = item | 0b01000000
|
||||
if item & 0b00011111 == 0b00000011:
|
||||
rom_data[first_quest_dungeon_items_late + i] = item | 0b00111111
|
||||
return rom_data
|
||||
|
||||
@@ -143,7 +143,7 @@ class WitnessWorld(World):
|
||||
# Pick an early item to place on the tutorial gate.
|
||||
early_items = [item for item in self.items.get_early_items() if item in self.items.get_mandatory_items()]
|
||||
if early_items:
|
||||
random_early_item = self.random.choice(early_items)
|
||||
random_early_item = self.multiworld.random.choice(early_items)
|
||||
if self.options.puzzle_randomization == 1:
|
||||
# In Expert, only tag the item as early, rather than forcing it onto the gate.
|
||||
self.multiworld.local_early_items[self.player][random_early_item] = 1
|
||||
|
||||
Reference in New Issue
Block a user