2024-02-28 03:44:22 +00:00
|
|
|
import logging
|
2024-08-19 23:34:40 +00:00
|
|
|
import math
|
2024-02-28 03:44:22 +00:00
|
|
|
from dataclasses import dataclass
|
2024-08-19 23:34:40 +00:00
|
|
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union, cast
|
2024-04-11 22:27:42 +00:00
|
|
|
|
2024-08-19 23:34:40 +00:00
|
|
|
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region
|
2024-04-11 22:27:42 +00:00
|
|
|
|
|
|
|
from .data import static_logic as static_witness_logic
|
|
|
|
from .data.utils import weighted_sample
|
2024-07-02 21:59:26 +00:00
|
|
|
from .player_items import WitnessItem
|
2023-11-24 05:27:03 +00:00
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from . import WitnessWorld
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-08-19 23:34:40 +00:00
|
|
|
CompactHintArgs = Tuple[Union[str, int], Union[str, int]]
|
|
|
|
CompactHintData = Tuple[str, Union[str, int], Union[str, int]]
|
2024-03-05 21:54:02 +00:00
|
|
|
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
@dataclass
|
|
|
|
class WitnessLocationHint:
|
|
|
|
location: Location
|
|
|
|
hint_came_from_location: bool
|
|
|
|
|
|
|
|
# If a hint gets added to a set twice, but once as an item hint and once as a location hint, those are the same
|
2024-04-11 22:27:42 +00:00
|
|
|
def __hash__(self) -> int:
|
2024-02-28 03:44:22 +00:00
|
|
|
return hash(self.location)
|
|
|
|
|
2024-07-02 21:59:26 +00:00
|
|
|
def __eq__(self, other: Any) -> bool:
|
|
|
|
if not isinstance(other, WitnessLocationHint):
|
|
|
|
return False
|
2024-02-28 03:44:22 +00:00
|
|
|
return self.location == other.location
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class WitnessWordedHint:
|
|
|
|
wording: str
|
|
|
|
location: Optional[Location] = None
|
|
|
|
area: Optional[str] = None
|
|
|
|
area_amount: Optional[int] = None
|
2024-08-19 23:16:35 +00:00
|
|
|
area_hunt_panels: Optional[int] = None
|
2024-08-19 23:34:40 +00:00
|
|
|
vague_location_hint: bool = False
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
|
2023-12-10 19:36:55 +00:00
|
|
|
def get_always_hint_items(world: "WitnessWorld") -> List[str]:
|
2023-06-20 22:45:26 +00:00
|
|
|
always = [
|
2022-10-09 02:13:52 +00:00
|
|
|
"Boat",
|
2023-11-24 05:27:03 +00:00
|
|
|
"Caves Shortcuts",
|
2023-02-01 20:18:07 +00:00
|
|
|
"Progressive Dots",
|
2022-10-09 02:13:52 +00:00
|
|
|
]
|
|
|
|
|
2023-11-24 05:27:03 +00:00
|
|
|
difficulty = world.options.puzzle_randomization
|
|
|
|
discards = world.options.shuffle_discarded_panels
|
|
|
|
wincon = world.options.victory_condition
|
2022-10-09 02:13:52 +00:00
|
|
|
|
|
|
|
if discards:
|
2024-09-05 15:10:09 +00:00
|
|
|
if difficulty == "sigma_expert" or difficulty == "umbra_variety":
|
2023-06-20 22:45:26 +00:00
|
|
|
always.append("Arrows")
|
2022-10-09 02:13:52 +00:00
|
|
|
else:
|
2023-06-20 22:45:26 +00:00
|
|
|
always.append("Triangles")
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-11 01:25:03 +00:00
|
|
|
if wincon == "elevator":
|
2024-02-28 03:44:22 +00:00
|
|
|
always += ["Mountain Bottom Floor Pillars Room Entry (Door)", "Mountain Bottom Floor Doors"]
|
2023-11-24 05:27:03 +00:00
|
|
|
|
2024-02-11 01:25:03 +00:00
|
|
|
if wincon == "challenge":
|
2023-11-24 05:27:03 +00:00
|
|
|
always += ["Challenge Entry (Panel)", "Caves Panels"]
|
2023-06-20 22:45:26 +00:00
|
|
|
|
|
|
|
return always
|
2022-10-09 02:13:52 +00:00
|
|
|
|
|
|
|
|
2024-01-16 12:14:38 +00:00
|
|
|
def get_always_hint_locations(world: "WitnessWorld") -> List[str]:
|
|
|
|
always = [
|
2022-10-09 02:13:52 +00:00
|
|
|
"Challenge Vault Box",
|
|
|
|
"Mountain Bottom Floor Discard",
|
2023-02-01 20:18:07 +00:00
|
|
|
"Theater Eclipse EP",
|
|
|
|
"Shipwreck Couch EP",
|
|
|
|
"Mountainside Cloud Cycle EP",
|
2023-12-10 19:36:55 +00:00
|
|
|
]
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-01-16 12:14:38 +00:00
|
|
|
# Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side
|
2024-02-28 03:44:22 +00:00
|
|
|
if "0x339B6" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
|
2024-01-16 12:14:38 +00:00
|
|
|
always.append("Town Obelisk Side 6") # Eclipse EP
|
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
if "0x3388F" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
|
2024-01-16 12:14:38 +00:00
|
|
|
always.append("Treehouse Obelisk Side 4") # Couch EP
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
if "0x335AE" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
|
|
|
|
always.append("Mountainside Obelisk Side 1") # Cloud Cycle EP.
|
2024-01-16 12:14:38 +00:00
|
|
|
|
|
|
|
return always
|
|
|
|
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2023-12-10 19:36:55 +00:00
|
|
|
def get_priority_hint_items(world: "WitnessWorld") -> List[str]:
|
2022-10-09 02:13:52 +00:00
|
|
|
priority = {
|
2023-06-20 22:45:26 +00:00
|
|
|
"Caves Mountain Shortcut (Door)",
|
|
|
|
"Caves Swamp Shortcut (Door)",
|
2022-10-09 02:13:52 +00:00
|
|
|
"Swamp Entry (Panel)",
|
|
|
|
"Swamp Laser Shortcut (Door)",
|
|
|
|
}
|
|
|
|
|
2023-11-24 05:27:03 +00:00
|
|
|
if world.options.shuffle_symbols:
|
|
|
|
symbols = [
|
|
|
|
"Progressive Dots",
|
|
|
|
"Progressive Stars",
|
|
|
|
"Shapers",
|
|
|
|
"Rotated Shapers",
|
|
|
|
"Negative Shapers",
|
|
|
|
"Arrows",
|
|
|
|
"Triangles",
|
|
|
|
"Eraser",
|
|
|
|
"Black/White Squares",
|
|
|
|
"Colored Squares",
|
|
|
|
"Sound Dots",
|
2024-01-16 14:13:04 +00:00
|
|
|
"Progressive Symmetry"
|
2023-11-24 05:27:03 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
priority.update(world.random.sample(symbols, 5))
|
|
|
|
|
|
|
|
if world.options.shuffle_lasers:
|
2023-07-18 08:18:42 +00:00
|
|
|
lasers = [
|
2022-10-09 02:13:52 +00:00
|
|
|
"Symmetry Laser",
|
|
|
|
"Town Laser",
|
|
|
|
"Keep Laser",
|
|
|
|
"Swamp Laser",
|
|
|
|
"Treehouse Laser",
|
|
|
|
"Monastery Laser",
|
|
|
|
"Jungle Laser",
|
|
|
|
"Quarry Laser",
|
|
|
|
"Bunker Laser",
|
|
|
|
"Shadows Laser",
|
2023-07-18 08:18:42 +00:00
|
|
|
]
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2023-11-24 05:27:03 +00:00
|
|
|
if world.options.shuffle_doors >= 2:
|
2022-10-09 02:13:52 +00:00
|
|
|
priority.add("Desert Laser")
|
2023-11-24 05:27:03 +00:00
|
|
|
priority.update(world.random.sample(lasers, 5))
|
2022-10-09 02:13:52 +00:00
|
|
|
|
|
|
|
else:
|
2023-07-18 08:18:42 +00:00
|
|
|
lasers.append("Desert Laser")
|
2023-11-24 05:27:03 +00:00
|
|
|
priority.update(world.random.sample(lasers, 6))
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2023-12-10 19:36:55 +00:00
|
|
|
return sorted(priority)
|
2022-10-09 02:13:52 +00:00
|
|
|
|
|
|
|
|
2024-01-16 12:14:38 +00:00
|
|
|
def get_priority_hint_locations(world: "WitnessWorld") -> List[str]:
|
|
|
|
priority = [
|
2024-02-28 03:44:22 +00:00
|
|
|
"Tutorial Patio Floor",
|
|
|
|
"Tutorial Patio Flowers EP",
|
2023-06-20 22:45:26 +00:00
|
|
|
"Swamp Purple Underwater",
|
|
|
|
"Shipwreck Vault Box",
|
2024-02-28 03:44:22 +00:00
|
|
|
"Town RGB House Upstairs Left",
|
|
|
|
"Town RGB House Upstairs Right",
|
2022-10-09 02:13:52 +00:00
|
|
|
"Treehouse Green Bridge 7",
|
|
|
|
"Treehouse Green Bridge Discard",
|
|
|
|
"Shipwreck Discard",
|
|
|
|
"Desert Vault Box",
|
|
|
|
"Mountainside Vault Box",
|
|
|
|
"Mountainside Discard",
|
2023-02-01 20:18:07 +00:00
|
|
|
"Tunnels Theater Flowers EP",
|
|
|
|
"Boat Shipwreck Green EP",
|
2023-07-09 12:21:05 +00:00
|
|
|
"Quarry Stoneworks Control Room Left",
|
2023-12-10 19:36:55 +00:00
|
|
|
]
|
2024-04-11 22:27:42 +00:00
|
|
|
|
2024-01-16 12:14:38 +00:00
|
|
|
# Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side
|
2024-02-28 03:44:22 +00:00
|
|
|
if "0x33A20" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
|
2024-01-16 12:14:38 +00:00
|
|
|
priority.append("Town Obelisk Side 6") # Theater Flowers EP
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
if "0x28B29" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
|
2024-01-16 12:14:38 +00:00
|
|
|
priority.append("Treehouse Obelisk Side 4") # Shipwreck Green EP
|
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
if "0x33600" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
|
|
|
|
priority.append("Town Obelisk Side 2") # Tutorial Patio Flowers EP.
|
|
|
|
|
2024-01-16 12:14:38 +00:00
|
|
|
return priority
|
2022-10-09 02:13:52 +00:00
|
|
|
|
|
|
|
|
2024-08-19 23:34:40 +00:00
|
|
|
def try_getting_location_group_for_location(world: "WitnessWorld", hint_loc: Location) -> Tuple[str, str]:
|
|
|
|
allow_regions = world.options.vague_hints == "experimental"
|
|
|
|
|
|
|
|
possible_location_groups = {
|
|
|
|
group_name: group_locations
|
|
|
|
for group_name, group_locations in world.multiworld.worlds[hint_loc.player].location_name_groups.items()
|
|
|
|
if hint_loc.name in group_locations
|
|
|
|
}
|
|
|
|
|
|
|
|
locations_in_that_world = {
|
|
|
|
location.name for location in world.multiworld.get_locations(hint_loc.player) if not location.is_event
|
|
|
|
}
|
|
|
|
|
|
|
|
valid_location_groups: Dict[str, int] = {}
|
|
|
|
|
|
|
|
# Find valid location groups.
|
|
|
|
for group, locations in possible_location_groups.items():
|
|
|
|
if group == "Everywhere":
|
|
|
|
continue
|
|
|
|
present_locations = sum(location in locations_in_that_world for location in locations)
|
|
|
|
valid_location_groups[group] = present_locations
|
|
|
|
|
|
|
|
# If there are valid location groups, use a random one.
|
|
|
|
if valid_location_groups:
|
|
|
|
# If there are location groups with more than 1 location, remove any that only have 1.
|
|
|
|
if any(num_locs > 1 for num_locs in valid_location_groups.values()):
|
|
|
|
valid_location_groups = {name: num_locs for name, num_locs in valid_location_groups.items() if num_locs > 1}
|
|
|
|
|
|
|
|
location_groups_with_weights = {
|
|
|
|
# Listen. Just don't worry about it. :)))
|
|
|
|
location_group: (x ** 0.6) * math.e ** (- (x / 7) ** 0.6) if x > 6 else x / 6
|
|
|
|
for location_group, x in valid_location_groups.items()
|
|
|
|
}
|
|
|
|
|
|
|
|
location_groups = list(location_groups_with_weights.keys())
|
|
|
|
weights = list(location_groups_with_weights.values())
|
|
|
|
|
|
|
|
return world.random.choices(location_groups, weights, k=1)[0], "Group"
|
|
|
|
|
|
|
|
if allow_regions:
|
|
|
|
return cast(Region, hint_loc.parent_region).name, "Region"
|
|
|
|
|
|
|
|
return "Everywhere", "Everywhere"
|
|
|
|
|
|
|
|
|
2024-04-11 22:27:42 +00:00
|
|
|
def word_direct_hint(world: "WitnessWorld", hint: WitnessLocationHint) -> WitnessWordedHint:
|
2024-02-28 03:44:22 +00:00
|
|
|
location_name = hint.location.name
|
|
|
|
if hint.location.player != world.player:
|
2024-09-09 15:36:47 +00:00
|
|
|
location_name += " (" + world.multiworld.get_player_name(hint.location.player) + ")"
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
item = hint.location.item
|
2024-07-02 21:59:26 +00:00
|
|
|
|
|
|
|
item_name = "Nothing"
|
|
|
|
if item is not None:
|
|
|
|
item_name = item.name
|
|
|
|
|
|
|
|
if item.player != world.player:
|
2024-09-09 15:36:47 +00:00
|
|
|
item_name += " (" + world.multiworld.get_player_name(item.player) + ")"
|
2024-02-28 03:44:22 +00:00
|
|
|
|
2024-08-19 23:34:40 +00:00
|
|
|
hint_text = ""
|
|
|
|
area: Optional[str] = None
|
|
|
|
|
|
|
|
if world.options.vague_hints:
|
|
|
|
chosen_group, group_type = try_getting_location_group_for_location(world, hint.location)
|
2024-02-28 03:44:22 +00:00
|
|
|
|
2024-08-19 23:34:40 +00:00
|
|
|
if hint.location.player == world.player:
|
|
|
|
area = chosen_group
|
|
|
|
|
|
|
|
# local locations should only ever return a location group, as Witness defines groups for every location.
|
|
|
|
hint_text = f"{item_name} can be found in the {area} area."
|
|
|
|
else:
|
|
|
|
player_name = world.multiworld.get_player_name(hint.location.player)
|
|
|
|
|
|
|
|
if group_type == "Everywhere":
|
|
|
|
location_name = f"a location in {player_name}'s world"
|
|
|
|
elif group_type == "Group":
|
|
|
|
location_name = f"a \"{chosen_group}\" location in {player_name}'s world"
|
|
|
|
elif group_type == "Region":
|
2024-11-09 20:10:54 +00:00
|
|
|
origin_region_name = world.multiworld.worlds[hint.location.player].origin_region_name
|
|
|
|
if chosen_group == origin_region_name:
|
|
|
|
location_name = (
|
|
|
|
f"a location in the origin region of {player_name}'s world (\"{origin_region_name}\" region)"
|
|
|
|
)
|
2024-08-19 23:34:40 +00:00
|
|
|
else:
|
|
|
|
location_name = f"a location in {player_name}'s \"{chosen_group}\" region"
|
|
|
|
|
|
|
|
if hint_text == "":
|
|
|
|
if hint.hint_came_from_location:
|
|
|
|
hint_text = f"{location_name} contains {item_name}."
|
|
|
|
else:
|
|
|
|
hint_text = f"{item_name} can be found at {location_name}."
|
|
|
|
|
|
|
|
return WitnessWordedHint(hint_text, hint.location, area=area, vague_location_hint=bool(world.options.vague_hints))
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
|
2024-07-02 21:59:26 +00:00
|
|
|
def hint_from_item(world: "WitnessWorld", item_name: str,
|
|
|
|
own_itempool: List["WitnessItem"]) -> Optional[WitnessLocationHint]:
|
|
|
|
def get_real_location(multiworld: MultiWorld, location: Location) -> Location:
|
2024-04-18 16:57:22 +00:00
|
|
|
"""If this location is from an item_link pseudo-world, get the location that the item_link item is on.
|
|
|
|
Return the original location otherwise / as a fallback."""
|
|
|
|
if location.player not in world.multiworld.groups:
|
|
|
|
return location
|
|
|
|
|
|
|
|
try:
|
2024-07-02 21:59:26 +00:00
|
|
|
if not location.item:
|
|
|
|
return location
|
2024-04-18 16:57:22 +00:00
|
|
|
return multiworld.find_item(location.item.name, location.player)
|
|
|
|
except StopIteration:
|
|
|
|
return location
|
|
|
|
|
|
|
|
locations = [
|
|
|
|
get_real_location(world.multiworld, item.location)
|
|
|
|
for item in own_itempool if item.name == item_name and item.location
|
|
|
|
]
|
2023-11-24 05:27:03 +00:00
|
|
|
|
|
|
|
if not locations:
|
|
|
|
return None
|
|
|
|
|
|
|
|
location_obj = world.random.choice(locations)
|
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
return WitnessLocationHint(location_obj, False)
|
2022-10-09 02:13:52 +00:00
|
|
|
|
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
def hint_from_location(world: "WitnessWorld", location: str) -> Optional[WitnessLocationHint]:
|
2024-07-02 21:59:26 +00:00
|
|
|
return WitnessLocationHint(world.get_location(location), True)
|
2022-10-09 02:13:52 +00:00
|
|
|
|
|
|
|
|
2024-08-19 23:34:40 +00:00
|
|
|
def get_item_and_location_names_in_random_order(world: "WitnessWorld",
|
|
|
|
own_itempool: List["WitnessItem"]) -> Tuple[List[str], List[str]]:
|
|
|
|
prog_item_names_in_this_world = [
|
2024-02-28 03:44:22 +00:00
|
|
|
item.name for item in own_itempool
|
|
|
|
if item.advancement and item.code and item.location
|
2024-08-19 23:34:40 +00:00
|
|
|
]
|
|
|
|
world.random.shuffle(prog_item_names_in_this_world)
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-08-19 23:34:40 +00:00
|
|
|
locations_in_this_world = [
|
|
|
|
location for location in world.multiworld.get_locations(world.player)
|
|
|
|
if location.item and not location.is_event and location.progress_type != LocationProgressType.EXCLUDED
|
|
|
|
]
|
2024-02-28 03:44:22 +00:00
|
|
|
world.random.shuffle(locations_in_this_world)
|
|
|
|
|
2024-08-19 23:34:40 +00:00
|
|
|
if world.options.vague_hints:
|
|
|
|
locations_in_this_world.sort(key=lambda location: cast(Item, location.item).advancement)
|
|
|
|
|
|
|
|
location_names_in_this_world = [location.name for location in locations_in_this_world]
|
|
|
|
|
|
|
|
return prog_item_names_in_this_world, location_names_in_this_world
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
|
2024-07-02 21:59:26 +00:00
|
|
|
def make_always_and_priority_hints(world: "WitnessWorld", own_itempool: List["WitnessItem"],
|
2024-02-28 03:44:22 +00:00
|
|
|
already_hinted_locations: Set[Location]
|
|
|
|
) -> Tuple[List[WitnessLocationHint], List[WitnessLocationHint]]:
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-08-19 23:34:40 +00:00
|
|
|
prog_items_in_this_world, loc_in_this_world = get_item_and_location_names_in_random_order(world, own_itempool)
|
|
|
|
|
2022-10-09 02:13:52 +00:00
|
|
|
always_items = [
|
2023-11-24 05:27:03 +00:00
|
|
|
item for item in get_always_hint_items(world)
|
2022-10-09 02:13:52 +00:00
|
|
|
if item in prog_items_in_this_world
|
|
|
|
]
|
|
|
|
priority_items = [
|
2023-11-24 05:27:03 +00:00
|
|
|
item for item in get_priority_hint_items(world)
|
2022-10-09 02:13:52 +00:00
|
|
|
if item in prog_items_in_this_world
|
|
|
|
]
|
|
|
|
|
2024-08-19 23:34:40 +00:00
|
|
|
if world.options.vague_hints:
|
|
|
|
always_locations, priority_locations = [], []
|
|
|
|
else:
|
|
|
|
always_locations = [
|
|
|
|
location for location in get_always_hint_locations(world)
|
|
|
|
if location in loc_in_this_world
|
|
|
|
]
|
|
|
|
priority_locations = [
|
|
|
|
location for location in get_priority_hint_locations(world)
|
|
|
|
if location in loc_in_this_world
|
|
|
|
]
|
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
# Get always and priority location/item hints
|
|
|
|
always_location_hints = {hint_from_location(world, location) for location in always_locations}
|
|
|
|
always_item_hints = {hint_from_item(world, item, own_itempool) for item in always_items}
|
|
|
|
priority_location_hints = {hint_from_location(world, location) for location in priority_locations}
|
|
|
|
priority_item_hints = {hint_from_item(world, item, own_itempool) for item in priority_items}
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
# Combine the sets. This will get rid of duplicates
|
|
|
|
always_hints_set = always_item_hints | always_location_hints
|
|
|
|
priority_hints_set = priority_item_hints | priority_location_hints
|
2023-02-01 20:18:07 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
# Make sure priority hints doesn't contain any hints that are already always hints.
|
|
|
|
priority_hints_set -= always_hints_set
|
2023-02-01 20:18:07 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
always_generator = [hint for hint in always_hints_set if hint and hint.location not in already_hinted_locations]
|
|
|
|
priority_generator = [hint for hint in priority_hints_set if hint and hint.location not in already_hinted_locations]
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
# Convert both hint types to list and then shuffle. Also, get rid of None and Tutorial Gate Open.
|
|
|
|
always_hints = sorted(always_generator, key=lambda h: h.location)
|
|
|
|
priority_hints = sorted(priority_generator, key=lambda h: h.location)
|
|
|
|
world.random.shuffle(always_hints)
|
|
|
|
world.random.shuffle(priority_hints)
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
return always_hints, priority_hints
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2023-02-01 20:18:07 +00:00
|
|
|
|
2024-07-02 21:59:26 +00:00
|
|
|
def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List["WitnessItem"],
|
2024-02-28 03:44:22 +00:00
|
|
|
already_hinted_locations: Set[Location], hints_to_use_first: List[WitnessLocationHint],
|
|
|
|
unhinted_locations_for_hinted_areas: Dict[str, Set[Location]]) -> List[WitnessWordedHint]:
|
2024-08-19 23:34:40 +00:00
|
|
|
prog_items_in_this_world, locations_in_this_world = get_item_and_location_names_in_random_order(world, own_itempool)
|
2023-02-01 20:18:07 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
next_random_hint_is_location = world.random.randrange(0, 2)
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-07-02 21:59:26 +00:00
|
|
|
hints: List[WitnessWordedHint] = []
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
# This is a way to reverse a Dict[a,List[b]] to a Dict[b,a]
|
2024-04-11 22:27:42 +00:00
|
|
|
area_reverse_lookup = {
|
|
|
|
unhinted_location: hinted_area
|
|
|
|
for hinted_area, unhinted_locations in unhinted_locations_for_hinted_areas.items()
|
|
|
|
for unhinted_location in unhinted_locations
|
|
|
|
}
|
2023-11-24 05:27:03 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
while len(hints) < hint_amount:
|
|
|
|
if not prog_items_in_this_world and not locations_in_this_world and not hints_to_use_first:
|
2024-08-24 00:08:46 +00:00
|
|
|
logging.warning(f"Ran out of items/locations to hint for player {world.player_name}.")
|
2024-02-28 03:44:22 +00:00
|
|
|
break
|
|
|
|
|
2024-07-02 21:59:26 +00:00
|
|
|
location_hint: Optional[WitnessLocationHint]
|
2024-02-28 03:44:22 +00:00
|
|
|
if hints_to_use_first:
|
|
|
|
location_hint = hints_to_use_first.pop()
|
|
|
|
elif next_random_hint_is_location and locations_in_this_world:
|
|
|
|
location_hint = hint_from_location(world, locations_in_this_world.pop())
|
|
|
|
elif not next_random_hint_is_location and prog_items_in_this_world:
|
|
|
|
location_hint = hint_from_item(world, prog_items_in_this_world.pop(), own_itempool)
|
|
|
|
# The list that the hint was supposed to be taken from was empty.
|
|
|
|
# Try the other list, which has to still have something, as otherwise, all lists would be empty,
|
|
|
|
# which would have triggered the guard condition above.
|
|
|
|
else:
|
|
|
|
next_random_hint_is_location = not next_random_hint_is_location
|
2023-11-24 05:27:03 +00:00
|
|
|
continue
|
|
|
|
|
2024-07-02 21:59:26 +00:00
|
|
|
if location_hint is None or location_hint.location in already_hinted_locations:
|
2024-02-28 03:44:22 +00:00
|
|
|
continue
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
# Don't hint locations in areas that are almost fully hinted out already
|
|
|
|
if location_hint.location in area_reverse_lookup:
|
|
|
|
area = area_reverse_lookup[location_hint.location]
|
|
|
|
if len(unhinted_locations_for_hinted_areas[area]) == 1:
|
|
|
|
continue
|
|
|
|
del area_reverse_lookup[location_hint.location]
|
|
|
|
unhinted_locations_for_hinted_areas[area] -= {location_hint.location}
|
2023-11-24 05:27:03 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
hints.append(word_direct_hint(world, location_hint))
|
|
|
|
already_hinted_locations.add(location_hint.location)
|
2023-02-01 20:18:07 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
next_random_hint_is_location = not next_random_hint_is_location
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
return hints
|
2022-10-09 02:13:52 +00:00
|
|
|
|
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
def choose_areas(world: "WitnessWorld", amount: int, locations_per_area: Dict[str, List[Location]],
|
|
|
|
already_hinted_locations: Set[Location]) -> Tuple[List[str], Dict[str, Set[Location]]]:
|
|
|
|
"""
|
|
|
|
Choose areas to hint.
|
|
|
|
This takes into account that some areas may already have had items hinted in them through location hints.
|
|
|
|
When this happens, they are made less likely to receive an area hint.
|
|
|
|
"""
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-07-02 21:59:26 +00:00
|
|
|
unhinted_locations_per_area = {}
|
|
|
|
unhinted_location_percentage_per_area = {}
|
2023-11-24 05:27:03 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
for area_name, locations in locations_per_area.items():
|
|
|
|
not_yet_hinted_locations = sum(location not in already_hinted_locations for location in locations)
|
|
|
|
unhinted_locations_per_area[area_name] = {loc for loc in locations if loc not in already_hinted_locations}
|
|
|
|
unhinted_location_percentage_per_area[area_name] = not_yet_hinted_locations / len(locations)
|
2023-06-20 22:45:26 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
items_per_area = {area_name: [location.item for location in locations]
|
|
|
|
for area_name, locations in locations_per_area.items()}
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
areas = sorted(area for area in items_per_area if unhinted_location_percentage_per_area[area])
|
|
|
|
weights = [unhinted_location_percentage_per_area[area] for area in areas]
|
2023-11-24 05:27:03 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
amount = min(amount, len(weights))
|
|
|
|
|
|
|
|
hinted_areas = weighted_sample(world.random, areas, weights, amount)
|
|
|
|
|
|
|
|
return hinted_areas, unhinted_locations_per_area
|
|
|
|
|
|
|
|
|
|
|
|
def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]], Dict[str, List[Item]]]:
|
2024-04-11 22:27:42 +00:00
|
|
|
potential_areas = list(static_witness_logic.ALL_AREAS_BY_NAME.keys())
|
2024-02-28 03:44:22 +00:00
|
|
|
|
2024-07-02 21:59:26 +00:00
|
|
|
locations_per_area = {}
|
|
|
|
items_per_area = {}
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
for area in potential_areas:
|
|
|
|
regions = [
|
2024-06-01 21:11:28 +00:00
|
|
|
world.get_region(region)
|
2024-04-11 22:27:42 +00:00
|
|
|
for region in static_witness_logic.ALL_AREAS_BY_NAME[area]["regions"]
|
2024-06-01 21:11:28 +00:00
|
|
|
if region in world.player_regions.created_region_names
|
2024-02-28 03:44:22 +00:00
|
|
|
]
|
2024-08-19 23:34:40 +00:00
|
|
|
locations = [location for region in regions for location in region.get_locations() if not location.is_event]
|
2023-11-24 05:27:03 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
if locations:
|
|
|
|
locations_per_area[area] = locations
|
|
|
|
items_per_area[area] = [location.item for location in locations]
|
2023-11-24 05:27:03 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
return locations_per_area, items_per_area
|
|
|
|
|
|
|
|
|
2024-08-19 23:16:35 +00:00
|
|
|
def word_area_hint(world: "WitnessWorld", hinted_area: str, area_items: List[Item]) -> Tuple[str, int, Optional[int]]:
|
2024-02-28 03:44:22 +00:00
|
|
|
"""
|
|
|
|
Word the hint for an area using natural sounding language.
|
|
|
|
This takes into account how much progression there is, how much of it is local/non-local, and whether there are
|
|
|
|
any local lasers to be found in this area.
|
|
|
|
"""
|
|
|
|
|
2024-08-19 23:16:35 +00:00
|
|
|
local_progression = sum(item.player == world.player and item.advancement for item in area_items)
|
|
|
|
non_local_progression = sum(item.player != world.player and item.advancement for item in area_items)
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
laser_names = {"Symmetry Laser", "Desert Laser", "Quarry Laser", "Shadows Laser", "Town Laser", "Monastery Laser",
|
|
|
|
"Jungle Laser", "Bunker Laser", "Swamp Laser", "Treehouse Laser", "Keep Laser", }
|
|
|
|
|
|
|
|
local_lasers = sum(
|
|
|
|
item.player == world.player and item.name in laser_names
|
2024-08-19 23:16:35 +00:00
|
|
|
for item in area_items
|
2024-02-28 03:44:22 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
total_progression = non_local_progression + local_progression
|
|
|
|
|
|
|
|
player_count = world.multiworld.players
|
|
|
|
|
|
|
|
area_progression_word = "Both" if total_progression == 2 else "All"
|
|
|
|
|
2024-08-19 23:16:35 +00:00
|
|
|
hint_string = f"In the {hinted_area} area, you will find "
|
|
|
|
|
|
|
|
hunt_panels = None
|
|
|
|
if world.options.victory_condition == "panel_hunt":
|
|
|
|
hunt_panels = sum(
|
|
|
|
static_witness_logic.ENTITIES_BY_HEX[hunt_entity]["area"]["name"] == hinted_area
|
|
|
|
for hunt_entity in world.player_logic.HUNT_ENTITIES
|
|
|
|
)
|
|
|
|
|
|
|
|
if not hunt_panels:
|
|
|
|
hint_string += "no Hunt Panels and "
|
|
|
|
|
|
|
|
elif hunt_panels == 1:
|
|
|
|
hint_string += "1 Hunt Panel and "
|
|
|
|
|
|
|
|
else:
|
|
|
|
hint_string += f"{hunt_panels} Hunt Panels and "
|
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
if not total_progression:
|
2024-08-19 23:16:35 +00:00
|
|
|
hint_string += "no progression items."
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
elif total_progression == 1:
|
2024-08-19 23:16:35 +00:00
|
|
|
hint_string += "1 progression item."
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
if player_count > 1:
|
|
|
|
if local_lasers:
|
|
|
|
hint_string += "\nThis item is a laser for this world."
|
|
|
|
elif non_local_progression:
|
|
|
|
other_player_str = "the other player" if player_count == 2 else "another player"
|
|
|
|
hint_string += f"\nThis item is for {other_player_str}."
|
|
|
|
else:
|
|
|
|
hint_string += "\nThis item is for this world."
|
2022-10-09 02:13:52 +00:00
|
|
|
else:
|
2024-02-28 03:44:22 +00:00
|
|
|
if local_lasers:
|
|
|
|
hint_string += "\nThis item is a laser."
|
|
|
|
|
|
|
|
else:
|
2024-08-19 23:16:35 +00:00
|
|
|
hint_string += f"{total_progression} progression items."
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
if local_lasers == total_progression:
|
|
|
|
sentence_end = (" for this world." if player_count > 1 else ".")
|
2024-04-11 22:27:42 +00:00
|
|
|
hint_string += "\nAll of them are lasers" + sentence_end
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
elif player_count > 1:
|
|
|
|
if local_progression and non_local_progression:
|
|
|
|
if non_local_progression == 1:
|
|
|
|
other_player_str = "the other player" if player_count == 2 else "another player"
|
|
|
|
hint_string += f"\nOne of them is for {other_player_str}."
|
|
|
|
else:
|
|
|
|
other_player_str = "the other player" if player_count == 2 else "other players"
|
|
|
|
hint_string += f"\n{non_local_progression} of them are for {other_player_str}."
|
|
|
|
elif non_local_progression:
|
|
|
|
other_players_str = "the other player" if player_count == 2 else "other players"
|
|
|
|
hint_string += f"\n{area_progression_word} of them are for {other_players_str}."
|
|
|
|
elif local_progression:
|
|
|
|
hint_string += f"\n{area_progression_word} of them are for this world."
|
|
|
|
|
|
|
|
if local_lasers == 1:
|
|
|
|
if not non_local_progression:
|
|
|
|
hint_string += "\nAlso, one of them is a laser."
|
|
|
|
else:
|
|
|
|
hint_string += "\nAlso, one of them is a laser for this world."
|
|
|
|
elif local_lasers:
|
|
|
|
if not non_local_progression:
|
|
|
|
hint_string += f"\nAlso, {local_lasers} of them are lasers."
|
|
|
|
else:
|
|
|
|
hint_string += f"\nAlso, {local_lasers} of them are lasers for this world."
|
2023-11-24 05:27:03 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
else:
|
|
|
|
if local_lasers == 1:
|
|
|
|
hint_string += "\nOne of them is a laser."
|
|
|
|
elif local_lasers:
|
|
|
|
hint_string += f"\n{local_lasers} of them are lasers."
|
2023-11-24 05:27:03 +00:00
|
|
|
|
2024-08-19 23:16:35 +00:00
|
|
|
return hint_string, total_progression, hunt_panels
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2023-11-24 05:27:03 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
def make_area_hints(world: "WitnessWorld", amount: int, already_hinted_locations: Set[Location]
|
|
|
|
) -> Tuple[List[WitnessWordedHint], Dict[str, Set[Location]]]:
|
|
|
|
locs_per_area, items_per_area = get_hintable_areas(world)
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
hinted_areas, unhinted_locations_per_area = choose_areas(world, amount, locs_per_area, already_hinted_locations)
|
|
|
|
|
|
|
|
hints = []
|
|
|
|
|
|
|
|
for hinted_area in hinted_areas:
|
2024-08-19 23:16:35 +00:00
|
|
|
hint_string, prog_amount, hunt_panels = word_area_hint(world, hinted_area, items_per_area[hinted_area])
|
2024-02-28 03:44:22 +00:00
|
|
|
|
2024-08-19 23:16:35 +00:00
|
|
|
hints.append(WitnessWordedHint(hint_string, None, f"hinted_area:{hinted_area}", prog_amount, hunt_panels))
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
if len(hinted_areas) < amount:
|
2024-08-24 00:08:46 +00:00
|
|
|
logging.warning(f"Was not able to make {amount} area hints for player {world.player_name}. "
|
2024-02-28 03:44:22 +00:00
|
|
|
f"Made {len(hinted_areas)} instead, and filled the rest with random location hints.")
|
|
|
|
|
|
|
|
return hints, unhinted_locations_per_area
|
|
|
|
|
|
|
|
|
2024-03-05 21:54:02 +00:00
|
|
|
def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int,
|
|
|
|
already_hinted_locations: Set[Location]) -> List[WitnessWordedHint]:
|
2024-02-28 03:44:22 +00:00
|
|
|
generated_hints: List[WitnessWordedHint] = []
|
|
|
|
|
|
|
|
state = CollectionState(world.multiworld)
|
|
|
|
|
|
|
|
# Keep track of already hinted locations. Consider early Tutorial as "already hinted"
|
|
|
|
|
2024-03-05 21:54:02 +00:00
|
|
|
already_hinted_locations |= {
|
2024-02-28 03:44:22 +00:00
|
|
|
loc for loc in world.multiworld.get_reachable_locations(state, world.player)
|
2024-04-11 22:27:42 +00:00
|
|
|
if loc.address and static_witness_logic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)"
|
2024-02-28 03:44:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
intended_location_hints = hint_amount - area_hints
|
|
|
|
|
|
|
|
# First, make always and priority hints.
|
|
|
|
|
|
|
|
always_hints, priority_hints = make_always_and_priority_hints(
|
|
|
|
world, world.own_itempool, already_hinted_locations
|
|
|
|
)
|
|
|
|
|
|
|
|
generated_always_hints = len(always_hints)
|
|
|
|
possible_priority_hints = len(priority_hints)
|
|
|
|
|
|
|
|
# Make as many always hints as possible
|
|
|
|
always_hints_to_use = min(intended_location_hints, generated_always_hints)
|
|
|
|
|
|
|
|
# Make up to half of the rest of the location hints priority hints, using up to half of the possibly priority hints
|
|
|
|
remaining_location_hints = intended_location_hints - always_hints_to_use
|
|
|
|
priority_hints_to_use = int(max(0.0, min(possible_priority_hints / 2, remaining_location_hints / 2)))
|
|
|
|
|
|
|
|
for _ in range(always_hints_to_use):
|
|
|
|
location_hint = always_hints.pop()
|
|
|
|
generated_hints.append(word_direct_hint(world, location_hint))
|
|
|
|
already_hinted_locations.add(location_hint.location)
|
|
|
|
|
|
|
|
for _ in range(priority_hints_to_use):
|
|
|
|
location_hint = priority_hints.pop()
|
|
|
|
generated_hints.append(word_direct_hint(world, location_hint))
|
|
|
|
already_hinted_locations.add(location_hint.location)
|
|
|
|
|
|
|
|
location_hints_created_in_round_1 = len(generated_hints)
|
|
|
|
|
2024-07-02 21:59:26 +00:00
|
|
|
unhinted_locations_per_area: Dict[str, Set[Location]] = {}
|
2024-02-28 03:44:22 +00:00
|
|
|
|
|
|
|
# Then, make area hints.
|
|
|
|
if area_hints:
|
|
|
|
generated_area_hints, unhinted_locations_per_area = make_area_hints(world, area_hints, already_hinted_locations)
|
|
|
|
generated_hints += generated_area_hints
|
|
|
|
|
|
|
|
# If we don't have enough hints yet, recalculate always and priority hints, then fill with random hints
|
|
|
|
if len(generated_hints) < hint_amount:
|
|
|
|
remaining_needed_location_hints = hint_amount - len(generated_hints)
|
|
|
|
|
|
|
|
# Save old values for used always and priority hints for later calculations
|
|
|
|
amt_of_used_always_hints = always_hints_to_use
|
|
|
|
amt_of_used_priority_hints = priority_hints_to_use
|
|
|
|
|
|
|
|
# Recalculate how many always hints and priority hints are supposed to be used
|
|
|
|
intended_location_hints = remaining_needed_location_hints + location_hints_created_in_round_1
|
|
|
|
|
|
|
|
always_hints_to_use = min(intended_location_hints, generated_always_hints)
|
|
|
|
priority_hints_to_use = int(max(0.0, min(possible_priority_hints / 2, remaining_location_hints / 2)))
|
|
|
|
|
|
|
|
# If we now need more always hints and priority hints than we thought previously, make some more.
|
|
|
|
more_always_hints = always_hints_to_use - amt_of_used_always_hints
|
|
|
|
more_priority_hints = priority_hints_to_use - amt_of_used_priority_hints
|
|
|
|
|
|
|
|
extra_always_and_priority_hints: List[WitnessLocationHint] = []
|
|
|
|
|
|
|
|
for _ in range(more_always_hints):
|
|
|
|
extra_always_and_priority_hints.append(always_hints.pop())
|
|
|
|
|
|
|
|
for _ in range(more_priority_hints):
|
|
|
|
extra_always_and_priority_hints.append(priority_hints.pop())
|
|
|
|
|
|
|
|
generated_hints += make_extra_location_hints(
|
|
|
|
world, hint_amount - len(generated_hints), world.own_itempool, already_hinted_locations,
|
|
|
|
extra_always_and_priority_hints, unhinted_locations_per_area
|
|
|
|
)
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
# If we still don't have enough for whatever reason, throw a warning, proceed with the lower amount
|
|
|
|
if len(generated_hints) != hint_amount:
|
2024-08-24 00:08:46 +00:00
|
|
|
logging.warning(f"Couldn't generate {hint_amount} hints for player {world.player_name}. "
|
2024-02-28 03:44:22 +00:00
|
|
|
f"Generated {len(generated_hints)} instead.")
|
2022-10-09 02:13:52 +00:00
|
|
|
|
2024-02-28 03:44:22 +00:00
|
|
|
return generated_hints
|
2024-03-05 21:54:02 +00:00
|
|
|
|
|
|
|
|
2024-08-19 23:16:35 +00:00
|
|
|
def get_compact_hint_args(hint: WitnessWordedHint, local_player_number: int) -> CompactHintArgs:
|
|
|
|
"""
|
|
|
|
Arg reference:
|
|
|
|
|
|
|
|
Area Hint: 1st Arg is the amount of area progression and hunt panels. 2nd Arg is the name of the area.
|
|
|
|
Location Hint: 1st Arg is the location's address, second arg is the player number the location belongs to.
|
|
|
|
Junk Hint: 1st Arg is -1, second arg is this slot's player number.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Is Area Hint
|
2024-08-19 23:34:40 +00:00
|
|
|
if hint.area_amount is not None:
|
2024-08-19 23:16:35 +00:00
|
|
|
area_amount = hint.area_amount
|
|
|
|
hunt_panels = hint.area_hunt_panels
|
|
|
|
|
|
|
|
area_and_hunt_panels = area_amount
|
|
|
|
# Encode amounts together
|
|
|
|
if hunt_panels:
|
|
|
|
area_and_hunt_panels += 0x100 * hunt_panels
|
|
|
|
|
|
|
|
return hint.area, area_and_hunt_panels
|
|
|
|
|
2024-03-05 21:54:02 +00:00
|
|
|
location = hint.location
|
|
|
|
|
2024-08-19 23:16:35 +00:00
|
|
|
# Is location hint
|
2024-07-02 21:59:26 +00:00
|
|
|
if location and location.address is not None:
|
2024-08-19 23:34:40 +00:00
|
|
|
if hint.vague_location_hint and location.player == local_player_number:
|
|
|
|
assert hint.area is not None # A local vague location hint should have an area argument
|
|
|
|
return location.address, "containing_area:" + hint.area
|
2024-08-28 16:31:49 +00:00
|
|
|
return location.address, location.player # Scouting does not matter for other players (currently)
|
2024-08-19 23:16:35 +00:00
|
|
|
|
|
|
|
# Is junk / undefined hint
|
|
|
|
return -1, local_player_number
|
|
|
|
|
2024-03-05 21:54:02 +00:00
|
|
|
|
2024-08-19 23:16:35 +00:00
|
|
|
def make_compact_hint_data(hint: WitnessWordedHint, local_player_number: int) -> CompactHintData:
|
|
|
|
compact_arg_1, compact_arg_2 = get_compact_hint_args(hint, local_player_number)
|
|
|
|
return hint.wording, compact_arg_1, compact_arg_2
|
2024-03-05 21:54:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
def make_laser_hints(world: "WitnessWorld", laser_names: List[str]) -> Dict[str, WitnessWordedHint]:
|
2024-07-02 21:59:26 +00:00
|
|
|
laser_hints_by_name = {}
|
2024-03-05 21:54:02 +00:00
|
|
|
|
|
|
|
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
|