Skip to content

Commit 4ddfb7c

Browse files
authored
The Witness: Laser Hints (#2895)
1 parent b147c5b commit 4ddfb7c

File tree

4 files changed

+69
-22
lines changed

4 files changed

+69
-22
lines changed

worlds/witness/__init__.py

+27-19
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@
33
"""
44
import dataclasses
55

6-
from typing import Dict, Optional
6+
from typing import Dict, Optional, cast
77
from BaseClasses import Region, Location, MultiWorld, Item, Entrance, Tutorial, CollectionState
88
from Options import PerGameCommonOptions, Toggle
99
from .presets import witness_option_presets
1010
from worlds.AutoWorld import World, WebWorld
1111
from .player_logic import WitnessPlayerLogic
12-
from .static_logic import StaticWitnessLogic
12+
from .static_logic import StaticWitnessLogic, ItemCategory, DoorItemDefinition
1313
from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \
1414
get_priority_hint_items, make_always_and_priority_hints, generate_joke_hints, make_area_hints, get_hintable_areas, \
15-
make_extra_location_hints, create_all_hints
15+
make_extra_location_hints, create_all_hints, make_laser_hints, make_compact_hint_data, CompactItemData
1616
from .locations import WitnessPlayerLocations, StaticWitnessLocations
1717
from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems, ItemData
1818
from .regions import WitnessRegions
1919
from .rules import set_rules
2020
from .options import TheWitnessOptions
21-
from .utils import get_audio_logs
21+
from .utils import get_audio_logs, get_laser_shuffle
2222
from logging import warning, error
2323

2424

@@ -66,7 +66,8 @@ def __init__(self, multiworld: "MultiWorld", player: int):
6666
self.items = None
6767
self.regio = None
6868

69-
self.log_ids_to_hints = None
69+
self.log_ids_to_hints: Dict[int, CompactItemData] = dict()
70+
self.laser_ids_to_hints: Dict[int, CompactItemData] = dict()
7071

7172
self.items_placed_early = []
7273
self.own_itempool = []
@@ -81,6 +82,7 @@ def _get_slot_data(self):
8182
'symbols_not_in_the_game': self.items.get_symbol_ids_not_in_pool(),
8283
'disabled_entities': [int(h, 16) for h in self.player_logic.COMPLETELY_DISABLED_ENTITIES],
8384
'log_ids_to_hints': self.log_ids_to_hints,
85+
'laser_ids_to_hints': self.laser_ids_to_hints,
8486
'progressive_item_lists': self.items.get_progressive_item_ids_in_pool(),
8587
'obelisk_side_id_to_EPs': StaticWitnessLogic.OBELISK_SIDE_ID_TO_EP_HEXES,
8688
'precompleted_puzzles': [int(h, 16) for h in self.player_logic.EXCLUDED_LOCATIONS],
@@ -100,8 +102,6 @@ def generate_early(self):
100102
)
101103
self.regio: WitnessRegions = WitnessRegions(self.locat, self)
102104

103-
self.log_ids_to_hints = dict()
104-
105105
interacts_with_multiworld = (
106106
self.options.shuffle_symbols or
107107
self.options.shuffle_doors or
@@ -272,11 +272,25 @@ def create_items(self):
272272
self.options.local_items.value.add(item_name)
273273

274274
def fill_slot_data(self) -> dict:
275+
already_hinted_locations = set()
276+
277+
# Laser hints
278+
279+
if self.options.laser_hints:
280+
laser_hints = make_laser_hints(self, StaticWitnessItems.item_groups["Lasers"])
281+
282+
for item_name, hint in laser_hints.items():
283+
item_def = cast(DoorItemDefinition, StaticWitnessLogic.all_items[item_name])
284+
self.laser_ids_to_hints[int(item_def.panel_id_hexes[0], 16)] = make_compact_hint_data(hint, self.player)
285+
already_hinted_locations.add(hint.location)
286+
287+
# Audio Log Hints
288+
275289
hint_amount = self.options.hint_amount.value
276290

277291
credits_hint = (
278292
"This Randomizer is brought to you by\n"
279-
"NewSoupVi, Jarno, blastron,\n",
293+
"NewSoupVi, Jarno, blastron,\n"
280294
"jbzdarkid, sigma144, IHNN, oddGarrett, Exempt-Medic.", -1, -1
281295
)
282296

@@ -285,25 +299,19 @@ def fill_slot_data(self) -> dict:
285299
if hint_amount:
286300
area_hints = round(self.options.area_hint_percentage / 100 * hint_amount)
287301

288-
generated_hints = create_all_hints(self, hint_amount, area_hints)
302+
generated_hints = create_all_hints(self, hint_amount, area_hints, already_hinted_locations)
289303

290304
self.random.shuffle(audio_logs)
291305

292306
duplicates = min(3, len(audio_logs) // hint_amount)
293307

294308
for hint in generated_hints:
295-
location = hint.location
296-
area_amount = hint.area_amount
297-
298-
# None if junk hint, address if location hint, area string if area hint
299-
arg_1 = location.address if location else (hint.area if hint.area else None)
300-
301-
# self.player if junk hint, player if location hint, progression amount if area hint
302-
arg_2 = area_amount if area_amount is not None else (location.player if location else self.player)
309+
hint = generated_hints.pop(0)
310+
compact_hint_data = make_compact_hint_data(hint, self.player)
303311

304312
for _ in range(0, duplicates):
305313
audio_log = audio_logs.pop()
306-
self.log_ids_to_hints[int(audio_log, 16)] = (hint.wording, arg_1, arg_2)
314+
self.log_ids_to_hints[int(audio_log, 16)] = compact_hint_data
307315

308316
if audio_logs:
309317
audio_log = audio_logs.pop()
@@ -315,7 +323,7 @@ def fill_slot_data(self) -> dict:
315323
audio_log = audio_logs.pop()
316324
self.log_ids_to_hints[int(audio_log, 16)] = joke_hints.pop()
317325

318-
# generate hints done
326+
# Options for the client & auto-tracker
319327

320328
slot_data = self._get_slot_data()
321329

worlds/witness/hints.py

+32-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import logging
22
from dataclasses import dataclass
3-
from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional
3+
from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional, Union
44
from BaseClasses import Item, ItemClassification, Location, LocationProgressType, CollectionState
55
from . import StaticWitnessLogic
66
from .utils import weighted_sample
77

88
if TYPE_CHECKING:
99
from . import WitnessWorld
1010

11+
CompactItemData = Tuple[str, Union[str, int], int]
12+
1113
joke_hints = [
1214
"Quaternions break my brain",
1315
"Eclipse has nothing, but you should do it anyway.",
@@ -634,14 +636,15 @@ def make_area_hints(world: "WitnessWorld", amount: int, already_hinted_locations
634636
return hints, unhinted_locations_per_area
635637

636638

637-
def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int) -> List[WitnessWordedHint]:
639+
def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int,
640+
already_hinted_locations: Set[Location]) -> List[WitnessWordedHint]:
638641
generated_hints: List[WitnessWordedHint] = []
639642

640643
state = CollectionState(world.multiworld)
641644

642645
# Keep track of already hinted locations. Consider early Tutorial as "already hinted"
643646

644-
already_hinted_locations = {
647+
already_hinted_locations |= {
645648
loc for loc in world.multiworld.get_reachable_locations(state, world.player)
646649
if loc.address and StaticWitnessLogic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)"
647650
}
@@ -721,3 +724,29 @@ def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int) -
721724
f"Generated {len(generated_hints)} instead.")
722725

723726
return generated_hints
727+
728+
729+
def make_compact_hint_data(hint: WitnessWordedHint, local_player_number: int) -> CompactItemData:
730+
location = hint.location
731+
area_amount = hint.area_amount
732+
733+
# None if junk hint, address if location hint, area string if area hint
734+
arg_1 = location.address if location else (hint.area if hint.area else None)
735+
736+
# self.player if junk hint, player if location hint, progression amount if area hint
737+
arg_2 = area_amount if area_amount is not None else (location.player if location else local_player_number)
738+
739+
return hint.wording, arg_1, arg_2
740+
741+
742+
def make_laser_hints(world: "WitnessWorld", laser_names: List[str]) -> Dict[str, WitnessWordedHint]:
743+
laser_hints_by_name = dict()
744+
745+
for item_name in laser_names:
746+
location_hint = hint_from_item(world, item_name, world.own_itempool)
747+
if not location_hint:
748+
continue
749+
750+
laser_hints_by_name[item_name] = word_direct_hint(world, location_hint)
751+
752+
return laser_hints_by_name

worlds/witness/options.py

+7
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@ class AreaHintPercentage(Range):
225225
default = 33
226226

227227

228+
class LaserHints(Toggle):
229+
"""If on, lasers will tell you where their items are if you walk close to them in-game.
230+
Only applies if laser shuffle is enabled."""
231+
display_name = "Laser Hints"
232+
233+
228234
class DeathLink(Toggle):
229235
"""If on: Whenever you fail a puzzle (with some exceptions), everyone who is also on Death Link dies.
230236
The effect of a "death" in The Witness is a Bonk Trap."""
@@ -264,5 +270,6 @@ class TheWitnessOptions(PerGameCommonOptions):
264270
puzzle_skip_amount: PuzzleSkipAmount
265271
hint_amount: HintAmount
266272
area_hint_percentage: AreaHintPercentage
273+
laser_hints: LaserHints
267274
death_link: DeathLink
268275
death_link_amnesty: DeathLinkAmnesty

worlds/witness/presets.py

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"puzzle_skip_amount": PuzzleSkipAmount.default,
3434
"hint_amount": HintAmount.default,
3535
"area_hint_percentage": AreaHintPercentage.default,
36+
"laser_hints": LaserHints.default,
3637
"death_link": DeathLink.default,
3738
},
3839

@@ -66,6 +67,7 @@
6667
"puzzle_skip_amount": 15,
6768
"hint_amount": HintAmount.default,
6869
"area_hint_percentage": AreaHintPercentage.default,
70+
"laser_hints": LaserHints.default,
6971
"death_link": DeathLink.default,
7072
},
7173

@@ -99,6 +101,7 @@
99101
"puzzle_skip_amount": 15,
100102
"hint_amount": HintAmount.default,
101103
"area_hint_percentage": AreaHintPercentage.default,
104+
"laser_hints": LaserHints.default,
102105
"death_link": DeathLink.default,
103106
},
104107
}

0 commit comments

Comments
 (0)