Archipelago/worlds/witness/__init__.py

284 lines
12 KiB
Python
Raw Normal View History

"""
Archipelago init file for The Witness
"""
import typing
from BaseClasses import Region, Location, MultiWorld, Item, Entrance, Tutorial
2022-10-09 02:13:52 +00:00
from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \
get_priority_hint_items, make_hints, generate_joke_hints
from worlds.AutoWorld import World, WebWorld
2022-10-09 02:13:52 +00:00
from .player_logic import WitnessPlayerLogic
from .static_logic import StaticWitnessLogic
from .locations import WitnessPlayerLocations, StaticWitnessLocations
from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems, ItemData
from .rules import set_rules
from .regions import WitnessRegions
from .Options import is_option_enabled, the_witness_options, get_option_value
from .utils import get_audio_logs
from logging import warning, error
class WitnessWebWorld(WebWorld):
theme = "jungle"
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to playing The Witness with Archipelago.",
"English",
"setup_en.md",
"setup/en",
["NewSoupVi", "Jarno"]
)]
class WitnessWorld(World):
"""
The Witness is an open-world puzzle game with dozens of locations
to explore and over 500 puzzles. Play the popular puzzle randomizer
by sigma144, with an added layer of progression randomization!
"""
game = "The Witness"
topology_present = False
Witness: Bugfixes in response to beta tests (#1473) * Make all Keep Pressure Plates logically required for the Laser Panel * Added more Tutorial checks * Added the remaining two Shipwreck Boat EPs to the exclude list for normal * Improved itempool filling system, added warning if usefuls had to be eaten * Moved creation of said warning string to utils * Fixed logic bug causing broken seeds on Mountain Floor 2 * Hints system change * Expert Logic Fix * Fixed typo * Better wording * Added missing games to junk hints * Made sure Entrance names are unique * Fixed missing Obelisk Side * Disable Non Randomized + EP Shuffle fix * Fixed disable_non_randomized precompleted EPs being 'disabled' instead of 'precompleted' * Fixed if/elif error * Tutorial Gate Open local symbol item becomes local_early_item in expert instead * Bump required client version. There is a beta client that sends 0.3.9. * Removed print statement, oops * Fixed itempool manipulation in pre_fill * Replaced string concats with fstrings * Improved make_warning_string function signature Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Improved performance on removing multiple items from multiworld itempool * Comment * Fixed errors with the code * Made removal from itempool not fail unit test for multiple references * Moved all item creation to create_items, got rid of itempool modifying system * Colored Squares is no longer a good item, that's outdated * Removed double if * React to from_pool: false by removing a junk item * Fixed warning if only Fnc Brain was removed * Make use of string truthiness instead * Made reading of plandoed items safer --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-02 23:08:24 +00:00
data_version = 13
StaticWitnessLogic()
StaticWitnessLocations()
StaticWitnessItems()
web = WitnessWebWorld()
option_definitions = the_witness_options
item_name_to_id = {
name: data.ap_code for name, data in StaticWitnessItems.item_data.items()
}
location_name_to_id = StaticWitnessLocations.ALL_LOCATIONS_TO_ID
item_name_groups = StaticWitnessItems.item_groups
Witness: Bugfixes in response to beta tests (#1473) * Make all Keep Pressure Plates logically required for the Laser Panel * Added more Tutorial checks * Added the remaining two Shipwreck Boat EPs to the exclude list for normal * Improved itempool filling system, added warning if usefuls had to be eaten * Moved creation of said warning string to utils * Fixed logic bug causing broken seeds on Mountain Floor 2 * Hints system change * Expert Logic Fix * Fixed typo * Better wording * Added missing games to junk hints * Made sure Entrance names are unique * Fixed missing Obelisk Side * Disable Non Randomized + EP Shuffle fix * Fixed disable_non_randomized precompleted EPs being 'disabled' instead of 'precompleted' * Fixed if/elif error * Tutorial Gate Open local symbol item becomes local_early_item in expert instead * Bump required client version. There is a beta client that sends 0.3.9. * Removed print statement, oops * Fixed itempool manipulation in pre_fill * Replaced string concats with fstrings * Improved make_warning_string function signature Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Improved performance on removing multiple items from multiworld itempool * Comment * Fixed errors with the code * Made removal from itempool not fail unit test for multiple references * Moved all item creation to create_items, got rid of itempool modifying system * Colored Squares is no longer a good item, that's outdated * Removed double if * React to from_pool: false by removing a junk item * Fixed warning if only Fnc Brain was removed * Make use of string truthiness instead * Made reading of plandoed items safer --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-02 23:08:24 +00:00
required_client_version = (0, 3, 9)
def __init__(self, multiworld: "MultiWorld", player: int):
super().__init__(multiworld, player)
self.player_logic = None
self.locat = None
self.items = None
self.regio = None
self.log_ids_to_hints = None
def _get_slot_data(self):
return {
'seed': self.multiworld.per_slot_randoms[self.player].randint(0, 1000000),
'victory_location': int(self.player_logic.VICTORY_LOCATION, 16),
'panelhex_to_id': self.locat.CHECK_PANELHEX_TO_ID,
'item_id_to_door_hexes': StaticWitnessItems.get_item_to_door_mappings(),
'door_hexes_in_the_pool': self.items.get_door_ids_in_pool(),
'symbols_not_in_the_game': self.items.get_symbol_ids_not_in_pool(),
'disabled_panels': list(self.player_logic.COMPLETELY_DISABLED_CHECKS),
2022-10-09 02:13:52 +00:00
'log_ids_to_hints': self.log_ids_to_hints,
'progressive_item_lists': self.items.get_progressive_item_ids_in_pool(),
'obelisk_side_id_to_EPs': StaticWitnessLogic.OBELISK_SIDE_ID_TO_EP_HEXES,
'precompleted_puzzles': [int(h, 16) for h in
self.player_logic.EXCLUDED_LOCATIONS | self.player_logic.PRECOMPLETED_LOCATIONS],
'entity_to_name': StaticWitnessLogic.ENTITY_ID_TO_NAME,
}
def generate_early(self):
disabled_locations = self.multiworld.exclude_locations[self.player].value
self.player_logic = WitnessPlayerLogic(
self.multiworld, self.player, disabled_locations, self.multiworld.start_inventory[self.player].value
)
self.locat: WitnessPlayerLocations = WitnessPlayerLocations(self.multiworld, self.player, self.player_logic)
self.items: WitnessPlayerItems = WitnessPlayerItems(self.multiworld, self.player, self.player_logic, self.locat)
self.regio: WitnessRegions = WitnessRegions(self.locat)
self.log_ids_to_hints = dict()
if not (is_option_enabled(self.multiworld, self.player, "shuffle_symbols")
or get_option_value(self.multiworld, self.player, "shuffle_doors")
or is_option_enabled(self.multiworld, self.player, "shuffle_lasers")):
if self.multiworld.players == 1:
warning("This Witness world doesn't have any progression items. Please turn on Symbol Shuffle, Door"
" Shuffle or Laser Shuffle if that doesn't seem right.")
else:
raise Exception("This Witness world doesn't have any progression items. Please turn on Symbol Shuffle,"
" Door Shuffle or Laser Shuffle.")
Witness: Bugfixes in response to beta tests (#1473) * Make all Keep Pressure Plates logically required for the Laser Panel * Added more Tutorial checks * Added the remaining two Shipwreck Boat EPs to the exclude list for normal * Improved itempool filling system, added warning if usefuls had to be eaten * Moved creation of said warning string to utils * Fixed logic bug causing broken seeds on Mountain Floor 2 * Hints system change * Expert Logic Fix * Fixed typo * Better wording * Added missing games to junk hints * Made sure Entrance names are unique * Fixed missing Obelisk Side * Disable Non Randomized + EP Shuffle fix * Fixed disable_non_randomized precompleted EPs being 'disabled' instead of 'precompleted' * Fixed if/elif error * Tutorial Gate Open local symbol item becomes local_early_item in expert instead * Bump required client version. There is a beta client that sends 0.3.9. * Removed print statement, oops * Fixed itempool manipulation in pre_fill * Replaced string concats with fstrings * Improved make_warning_string function signature Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Improved performance on removing multiple items from multiworld itempool * Comment * Fixed errors with the code * Made removal from itempool not fail unit test for multiple references * Moved all item creation to create_items, got rid of itempool modifying system * Colored Squares is no longer a good item, that's outdated * Removed double if * React to from_pool: false by removing a junk item * Fixed warning if only Fnc Brain was removed * Make use of string truthiness instead * Made reading of plandoed items safer --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-02 23:08:24 +00:00
def create_regions(self):
self.regio.create_regions(self.multiworld, self.player, self.player_logic)
def create_items(self):
# Determine pool size. Note that the dog location is included in the location list, so this needs to be -1.
pool_size: int = len(self.locat.CHECK_LOCATION_TABLE) - len(self.locat.EVENT_LOCATION_TABLE) - 1
# Fill mandatory items and remove precollected and/or starting items from the pool.
item_pool: dict[str, int] = self.items.get_mandatory_items()
for precollected_item_name in [item.name for item in self.multiworld.precollected_items[self.player]]:
if precollected_item_name in item_pool:
if item_pool[precollected_item_name] == 1:
item_pool.pop(precollected_item_name)
else:
item_pool[precollected_item_name] -= 1
for inventory_item_name in self.player_logic.STARTING_INVENTORY:
if inventory_item_name in item_pool:
if item_pool[inventory_item_name] == 1:
item_pool.pop(inventory_item_name)
else:
item_pool[inventory_item_name] -= 1
if len(item_pool) > pool_size:
error_string = "The Witness world has too few locations ({num_loc}) to place its necessary items " \
"({num_item})."
error(error_string.format(num_loc=pool_size, num_item=len(item_pool)))
return
remaining_item_slots = pool_size - sum(item_pool.values())
# Add puzzle skips.
num_puzzle_skips = get_option_value(self.multiworld, self.player, "puzzle_skip_amount")
if num_puzzle_skips > remaining_item_slots:
warning(f"The Witness world has insufficient locations to place all requested puzzle skips.")
num_puzzle_skips = remaining_item_slots
item_pool["Puzzle Skip"] = num_puzzle_skips
remaining_item_slots -= num_puzzle_skips
# Add junk items.
if remaining_item_slots > 0:
item_pool.update(self.items.get_filler_items(remaining_item_slots))
# Add event items and tie them to event locations (e.g. laser activations).
for event_location in self.locat.EVENT_LOCATION_TABLE:
item_obj = self.create_item(
self.player_logic.EVENT_ITEM_PAIRS[event_location]
)
location_obj = self.multiworld.get_location(event_location, self.player)
location_obj.place_locked_item(item_obj)
# BAD DOG GET BACK HERE WITH THAT PUZZLE SKIP YOU'RE POLLUTING THE ITEM POOL
self.multiworld.get_location("Town Pet the Dog", self.player)\
.place_locked_item(self.create_item("Puzzle Skip"))
# Pick an early item to place on the tutorial gate.
early_items = [item for item in self.items.get_early_items() if item in item_pool]
if early_items:
random_early_item = self.multiworld.random.choice(early_items)
Witness: Bugfixes in response to beta tests (#1473) * Make all Keep Pressure Plates logically required for the Laser Panel * Added more Tutorial checks * Added the remaining two Shipwreck Boat EPs to the exclude list for normal * Improved itempool filling system, added warning if usefuls had to be eaten * Moved creation of said warning string to utils * Fixed logic bug causing broken seeds on Mountain Floor 2 * Hints system change * Expert Logic Fix * Fixed typo * Better wording * Added missing games to junk hints * Made sure Entrance names are unique * Fixed missing Obelisk Side * Disable Non Randomized + EP Shuffle fix * Fixed disable_non_randomized precompleted EPs being 'disabled' instead of 'precompleted' * Fixed if/elif error * Tutorial Gate Open local symbol item becomes local_early_item in expert instead * Bump required client version. There is a beta client that sends 0.3.9. * Removed print statement, oops * Fixed itempool manipulation in pre_fill * Replaced string concats with fstrings * Improved make_warning_string function signature Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Improved performance on removing multiple items from multiworld itempool * Comment * Fixed errors with the code * Made removal from itempool not fail unit test for multiple references * Moved all item creation to create_items, got rid of itempool modifying system * Colored Squares is no longer a good item, that's outdated * Removed double if * React to from_pool: false by removing a junk item * Fixed warning if only Fnc Brain was removed * Make use of string truthiness instead * Made reading of plandoed items safer --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-02 23:08:24 +00:00
if get_option_value(self.multiworld, self.player, "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
Witness: Bugfixes in response to beta tests (#1473) * Make all Keep Pressure Plates logically required for the Laser Panel * Added more Tutorial checks * Added the remaining two Shipwreck Boat EPs to the exclude list for normal * Improved itempool filling system, added warning if usefuls had to be eaten * Moved creation of said warning string to utils * Fixed logic bug causing broken seeds on Mountain Floor 2 * Hints system change * Expert Logic Fix * Fixed typo * Better wording * Added missing games to junk hints * Made sure Entrance names are unique * Fixed missing Obelisk Side * Disable Non Randomized + EP Shuffle fix * Fixed disable_non_randomized precompleted EPs being 'disabled' instead of 'precompleted' * Fixed if/elif error * Tutorial Gate Open local symbol item becomes local_early_item in expert instead * Bump required client version. There is a beta client that sends 0.3.9. * Removed print statement, oops * Fixed itempool manipulation in pre_fill * Replaced string concats with fstrings * Improved make_warning_string function signature Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Improved performance on removing multiple items from multiworld itempool * Comment * Fixed errors with the code * Made removal from itempool not fail unit test for multiple references * Moved all item creation to create_items, got rid of itempool modifying system * Colored Squares is no longer a good item, that's outdated * Removed double if * React to from_pool: false by removing a junk item * Fixed warning if only Fnc Brain was removed * Make use of string truthiness instead * Made reading of plandoed items safer --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-02 23:08:24 +00:00
else:
# Force the item onto the tutorial gate check and remove it from our random pool.
self.multiworld.get_location("Tutorial Gate Open", self.player)\
.place_locked_item(self.create_item(random_early_item))
if item_pool[random_early_item] == 1:
item_pool.pop(random_early_item)
else:
item_pool[random_early_item] -= 1
# Generate the actual items.
for item_name, quantity in sorted(item_pool.items()):
self.multiworld.itempool += [self.create_item(item_name) for _ in range(0, quantity)]
if self.items.item_data[item_name].local_only:
self.multiworld.local_items[self.player].value.add(item_name)
def set_rules(self):
set_rules(self.multiworld, self.player, self.player_logic, self.locat)
def fill_slot_data(self) -> dict:
hint_amount = get_option_value(self.multiworld, self.player, "hint_amount")
2022-10-09 02:13:52 +00:00
credits_hint = (
"This Randomizer is brought to you by",
"NewSoupVi, Jarno, blastron,",
"jbzdarkid, sigma144, IHNN, oddGarrett.", -1
)
2022-10-09 02:13:52 +00:00
audio_logs = get_audio_logs().copy()
if hint_amount != 0:
generated_hints = make_hints(self.multiworld, self.player, hint_amount)
2022-10-09 02:13:52 +00:00
self.multiworld.per_slot_randoms[self.player].shuffle(audio_logs)
duplicates = min(3, len(audio_logs) // hint_amount)
2022-10-09 02:13:52 +00:00
for _ in range(0, hint_amount):
hint = generated_hints.pop(0)
2022-10-09 02:13:52 +00:00
for _ in range(0, duplicates):
audio_log = audio_logs.pop()
self.log_ids_to_hints[int(audio_log, 16)] = hint
if audio_logs:
audio_log = audio_logs.pop()
self.log_ids_to_hints[int(audio_log, 16)] = credits_hint
joke_hints = generate_joke_hints(self.multiworld, self.player, len(audio_logs))
2022-10-09 02:13:52 +00:00
while audio_logs:
audio_log = audio_logs.pop()
self.log_ids_to_hints[int(audio_log, 16)] = joke_hints.pop()
# generate hints done
slot_data = self._get_slot_data()
for option_name in the_witness_options:
slot_data[option_name] = get_option_value(
self.multiworld, self.player, option_name
)
return slot_data
def create_item(self, item_name: str) -> Item:
# this conditional is purely for unit tests, which need to be able to create an item before generate_early
item_data: ItemData
if hasattr(self, 'items') and self.items and item_name in self.items.item_data:
item_data = self.items.item_data[item_name]
else:
item_data = StaticWitnessItems.item_data[item_name]
return WitnessItem(item_name, item_data.classification, item_data.ap_code, player=self.player)
class WitnessLocation(Location):
"""
Archipelago Location for The Witness
"""
game: str = "The Witness"
check_hex: int = -1
def __init__(self, player: int, name: str, address: typing.Optional[int], parent, ch_hex: int = -1):
super().__init__(player, name, address, parent)
self.check_hex = ch_hex
def create_region(world: MultiWorld, player: int, name: str,
locat: WitnessPlayerLocations, region_locations=None, exits=None):
"""
Create an Archipelago Region for The Witness
"""
ret = Region(name, player, world)
if region_locations:
for location in region_locations:
loc_id = locat.CHECK_LOCATION_TABLE[location]
check_hex = -1
if location in StaticWitnessLogic.CHECKS_BY_NAME:
check_hex = int(
StaticWitnessLogic.CHECKS_BY_NAME[location]["checkHex"], 0
)
location = WitnessLocation(
player, location, loc_id, ret, check_hex
)
ret.locations.append(location)
if exits:
for single_exit in exits:
ret.exits.append(Entrance(player, single_exit, ret))
return ret