The Witness: Laser Hints (#2895)
This commit is contained in:
parent
b147c5bf8a
commit
4ddfb7ce8b
|
@ -3,22 +3,22 @@ Archipelago init file for The Witness
|
|||
"""
|
||||
import dataclasses
|
||||
|
||||
from typing import Dict, Optional
|
||||
from typing import Dict, Optional, cast
|
||||
from BaseClasses import Region, Location, MultiWorld, Item, Entrance, Tutorial, CollectionState
|
||||
from Options import PerGameCommonOptions, Toggle
|
||||
from .presets import witness_option_presets
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from .player_logic import WitnessPlayerLogic
|
||||
from .static_logic import StaticWitnessLogic
|
||||
from .static_logic import StaticWitnessLogic, ItemCategory, DoorItemDefinition
|
||||
from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \
|
||||
get_priority_hint_items, make_always_and_priority_hints, generate_joke_hints, make_area_hints, get_hintable_areas, \
|
||||
make_extra_location_hints, create_all_hints
|
||||
make_extra_location_hints, create_all_hints, make_laser_hints, make_compact_hint_data, CompactItemData
|
||||
from .locations import WitnessPlayerLocations, StaticWitnessLocations
|
||||
from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems, ItemData
|
||||
from .regions import WitnessRegions
|
||||
from .rules import set_rules
|
||||
from .options import TheWitnessOptions
|
||||
from .utils import get_audio_logs
|
||||
from .utils import get_audio_logs, get_laser_shuffle
|
||||
from logging import warning, error
|
||||
|
||||
|
||||
|
@ -66,7 +66,8 @@ class WitnessWorld(World):
|
|||
self.items = None
|
||||
self.regio = None
|
||||
|
||||
self.log_ids_to_hints = None
|
||||
self.log_ids_to_hints: Dict[int, CompactItemData] = dict()
|
||||
self.laser_ids_to_hints: Dict[int, CompactItemData] = dict()
|
||||
|
||||
self.items_placed_early = []
|
||||
self.own_itempool = []
|
||||
|
@ -81,6 +82,7 @@ class WitnessWorld(World):
|
|||
'symbols_not_in_the_game': self.items.get_symbol_ids_not_in_pool(),
|
||||
'disabled_entities': [int(h, 16) for h in self.player_logic.COMPLETELY_DISABLED_ENTITIES],
|
||||
'log_ids_to_hints': self.log_ids_to_hints,
|
||||
'laser_ids_to_hints': self.laser_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],
|
||||
|
@ -100,8 +102,6 @@ class WitnessWorld(World):
|
|||
)
|
||||
self.regio: WitnessRegions = WitnessRegions(self.locat, self)
|
||||
|
||||
self.log_ids_to_hints = dict()
|
||||
|
||||
interacts_with_multiworld = (
|
||||
self.options.shuffle_symbols or
|
||||
self.options.shuffle_doors or
|
||||
|
@ -272,11 +272,25 @@ class WitnessWorld(World):
|
|||
self.options.local_items.value.add(item_name)
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
already_hinted_locations = set()
|
||||
|
||||
# Laser hints
|
||||
|
||||
if self.options.laser_hints:
|
||||
laser_hints = make_laser_hints(self, StaticWitnessItems.item_groups["Lasers"])
|
||||
|
||||
for item_name, hint in laser_hints.items():
|
||||
item_def = cast(DoorItemDefinition, StaticWitnessLogic.all_items[item_name])
|
||||
self.laser_ids_to_hints[int(item_def.panel_id_hexes[0], 16)] = make_compact_hint_data(hint, self.player)
|
||||
already_hinted_locations.add(hint.location)
|
||||
|
||||
# Audio Log Hints
|
||||
|
||||
hint_amount = self.options.hint_amount.value
|
||||
|
||||
credits_hint = (
|
||||
"This Randomizer is brought to you by\n"
|
||||
"NewSoupVi, Jarno, blastron,\n",
|
||||
"NewSoupVi, Jarno, blastron,\n"
|
||||
"jbzdarkid, sigma144, IHNN, oddGarrett, Exempt-Medic.", -1, -1
|
||||
)
|
||||
|
||||
|
@ -285,25 +299,19 @@ class WitnessWorld(World):
|
|||
if hint_amount:
|
||||
area_hints = round(self.options.area_hint_percentage / 100 * hint_amount)
|
||||
|
||||
generated_hints = create_all_hints(self, hint_amount, area_hints)
|
||||
generated_hints = create_all_hints(self, hint_amount, area_hints, already_hinted_locations)
|
||||
|
||||
self.random.shuffle(audio_logs)
|
||||
|
||||
duplicates = min(3, len(audio_logs) // hint_amount)
|
||||
|
||||
for hint in generated_hints:
|
||||
location = hint.location
|
||||
area_amount = hint.area_amount
|
||||
|
||||
# None if junk hint, address if location hint, area string if area hint
|
||||
arg_1 = location.address if location else (hint.area if hint.area else None)
|
||||
|
||||
# self.player if junk hint, player if location hint, progression amount if area hint
|
||||
arg_2 = area_amount if area_amount is not None else (location.player if location else self.player)
|
||||
hint = generated_hints.pop(0)
|
||||
compact_hint_data = make_compact_hint_data(hint, self.player)
|
||||
|
||||
for _ in range(0, duplicates):
|
||||
audio_log = audio_logs.pop()
|
||||
self.log_ids_to_hints[int(audio_log, 16)] = (hint.wording, arg_1, arg_2)
|
||||
self.log_ids_to_hints[int(audio_log, 16)] = compact_hint_data
|
||||
|
||||
if audio_logs:
|
||||
audio_log = audio_logs.pop()
|
||||
|
@ -315,7 +323,7 @@ class WitnessWorld(World):
|
|||
audio_log = audio_logs.pop()
|
||||
self.log_ids_to_hints[int(audio_log, 16)] = joke_hints.pop()
|
||||
|
||||
# generate hints done
|
||||
# Options for the client & auto-tracker
|
||||
|
||||
slot_data = self._get_slot_data()
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional
|
||||
from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional, Union
|
||||
from BaseClasses import Item, ItemClassification, Location, LocationProgressType, CollectionState
|
||||
from . import StaticWitnessLogic
|
||||
from .utils import weighted_sample
|
||||
|
@ -8,6 +8,8 @@ from .utils import weighted_sample
|
|||
if TYPE_CHECKING:
|
||||
from . import WitnessWorld
|
||||
|
||||
CompactItemData = Tuple[str, Union[str, int], int]
|
||||
|
||||
joke_hints = [
|
||||
"Quaternions break my brain",
|
||||
"Eclipse has nothing, but you should do it anyway.",
|
||||
|
@ -634,14 +636,15 @@ def make_area_hints(world: "WitnessWorld", amount: int, already_hinted_locations
|
|||
return hints, unhinted_locations_per_area
|
||||
|
||||
|
||||
def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int) -> List[WitnessWordedHint]:
|
||||
def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int,
|
||||
already_hinted_locations: Set[Location]) -> List[WitnessWordedHint]:
|
||||
generated_hints: List[WitnessWordedHint] = []
|
||||
|
||||
state = CollectionState(world.multiworld)
|
||||
|
||||
# Keep track of already hinted locations. Consider early Tutorial as "already hinted"
|
||||
|
||||
already_hinted_locations = {
|
||||
already_hinted_locations |= {
|
||||
loc for loc in world.multiworld.get_reachable_locations(state, world.player)
|
||||
if loc.address and StaticWitnessLogic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)"
|
||||
}
|
||||
|
@ -721,3 +724,29 @@ def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int) -
|
|||
f"Generated {len(generated_hints)} instead.")
|
||||
|
||||
return generated_hints
|
||||
|
||||
|
||||
def make_compact_hint_data(hint: WitnessWordedHint, local_player_number: int) -> CompactItemData:
|
||||
location = hint.location
|
||||
area_amount = hint.area_amount
|
||||
|
||||
# None if junk hint, address if location hint, area string if area hint
|
||||
arg_1 = location.address if location else (hint.area if hint.area else None)
|
||||
|
||||
# self.player if junk hint, player if location hint, progression amount if area hint
|
||||
arg_2 = area_amount if area_amount is not None else (location.player if location else local_player_number)
|
||||
|
||||
return hint.wording, arg_1, arg_2
|
||||
|
||||
|
||||
def make_laser_hints(world: "WitnessWorld", laser_names: List[str]) -> Dict[str, WitnessWordedHint]:
|
||||
laser_hints_by_name = dict()
|
||||
|
||||
for item_name in laser_names:
|
||||
location_hint = hint_from_item(world, item_name, world.own_itempool)
|
||||
if not location_hint:
|
||||
continue
|
||||
|
||||
laser_hints_by_name[item_name] = word_direct_hint(world, location_hint)
|
||||
|
||||
return laser_hints_by_name
|
||||
|
|
|
@ -225,6 +225,12 @@ class AreaHintPercentage(Range):
|
|||
default = 33
|
||||
|
||||
|
||||
class LaserHints(Toggle):
|
||||
"""If on, lasers will tell you where their items are if you walk close to them in-game.
|
||||
Only applies if laser shuffle is enabled."""
|
||||
display_name = "Laser Hints"
|
||||
|
||||
|
||||
class DeathLink(Toggle):
|
||||
"""If on: Whenever you fail a puzzle (with some exceptions), everyone who is also on Death Link dies.
|
||||
The effect of a "death" in The Witness is a Bonk Trap."""
|
||||
|
@ -264,5 +270,6 @@ class TheWitnessOptions(PerGameCommonOptions):
|
|||
puzzle_skip_amount: PuzzleSkipAmount
|
||||
hint_amount: HintAmount
|
||||
area_hint_percentage: AreaHintPercentage
|
||||
laser_hints: LaserHints
|
||||
death_link: DeathLink
|
||||
death_link_amnesty: DeathLinkAmnesty
|
||||
|
|
|
@ -33,6 +33,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = {
|
|||
"puzzle_skip_amount": PuzzleSkipAmount.default,
|
||||
"hint_amount": HintAmount.default,
|
||||
"area_hint_percentage": AreaHintPercentage.default,
|
||||
"laser_hints": LaserHints.default,
|
||||
"death_link": DeathLink.default,
|
||||
},
|
||||
|
||||
|
@ -66,6 +67,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = {
|
|||
"puzzle_skip_amount": 15,
|
||||
"hint_amount": HintAmount.default,
|
||||
"area_hint_percentage": AreaHintPercentage.default,
|
||||
"laser_hints": LaserHints.default,
|
||||
"death_link": DeathLink.default,
|
||||
},
|
||||
|
||||
|
@ -99,6 +101,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = {
|
|||
"puzzle_skip_amount": 15,
|
||||
"hint_amount": HintAmount.default,
|
||||
"area_hint_percentage": AreaHintPercentage.default,
|
||||
"laser_hints": LaserHints.default,
|
||||
"death_link": DeathLink.default,
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue