2024-02-28 03:44:22 +00:00
import logging
from dataclasses import dataclass
2024-03-05 21:54:02 +00:00
from typing import Tuple , List , TYPE_CHECKING , Set , Dict , Optional , Union
2024-02-28 03:44:22 +00:00
from BaseClasses import Item , ItemClassification , Location , LocationProgressType , CollectionState
from . import StaticWitnessLogic
from . utils import weighted_sample
2023-11-24 05:27:03 +00:00
if TYPE_CHECKING :
from . import WitnessWorld
2022-10-09 02:13:52 +00:00
2024-03-05 21:54:02 +00:00
CompactItemData = Tuple [ str , Union [ str , int ] , int ]
2022-10-09 02:13:52 +00:00
joke_hints = [
2023-03-02 23:08:24 +00:00
" Quaternions break my brain " ,
" Eclipse has nothing, but you should do it anyway. " ,
" Beep " ,
" Putting in custom subtitles shouldn ' t have been as hard as it was... " ,
" BK mode is right around the corner. " ,
" You can do it! " ,
" I believe in you! " ,
" The person playing is cute. <3 " ,
Witness: Fix one of the hints not being a Haiku (seriously) (#2123)
I hope this gets a prize for "Most irrelevant PR in AP history"
Explanation:
When changing the hint system on the client side to be able to auto-wrap, decisions were made about which line breaks were still explicitly important, with most of them being removed.
This hint was somewhat devalued in the process.
-. --- - .... .. -. --. translates to "Nothing", which I thought was the entirety of the joke.
However, the line breaks were actually also important, because:
dash dot, dash dash dash,
dash, dot dot dot dot, dot dot,
dash dot, dash dash dot
is a Haiku! And the hint's creator (oddGarrett I believe) said this was specifically part of the creative vision for this joke hint. They said it's fine, I don't need to change it, but I couldn't let that stand.
So, the explicit line breaks for this joke hint are back.
2023-08-29 18:56:40 +00:00
" dash dot, dash dash dash, \n dash, dot dot dot dot, dot dot, \n dash dot, dash dash dot " ,
2023-03-02 23:08:24 +00:00
" When you think about it, there are actually a lot of bubbles in a stream. " ,
" Never gonna give you up \n Never gonna let you down \n Never gonna run around and desert you " ,
" Thanks to the Archipelago developers for making this possible. " ,
" Have you tried ChecksFinder? \n If you like puzzles, you might enjoy it! " ,
" Have you tried Dark Souls III? \n A tough game like this feels better when friends are helping you! " ,
" Have you tried Donkey Kong Country 3? \n A legendary game from a golden age of platformers! " ,
" Have you tried Factorio? \n Alone in an unknown multiworld. Sound familiar? " ,
" Have you tried Final Fantasy? \n Experience a classic game improved to fit modern standards! " ,
" Have you tried Hollow Knight? \n Another independent hit revolutionising a genre! " ,
" Have you tried A Link to the Past? \n The Archipelago game that started it all! " ,
" Have you tried Meritous? \n You should know that obscure games are often groundbreaking! " ,
" Have you tried Ocarina of Time? \n One of the biggest randomizers, big inspiration for this one ' s features! " ,
" Have you tried Raft? \n Haven ' t you always wanted to explore the ocean surrounding this island? " ,
" Have you tried Risk of Rain 2? \n I haven ' t either. But I hear it ' s incredible! " ,
2023-07-19 02:19:58 +00:00
" Have you tried Rogue Legacy? \n After solving so many puzzles it ' s the perfect way to rest your \" thinking \" brain. " ,
" Have you tried Secret of Evermore? \n I haven ' t either. But I hear it ' s great! " ,
2023-03-02 23:08:24 +00:00
" Have you tried Slay the Spire? \n Experience the thrill of combat without needing fast fingers! " ,
" Have you tried SMZ3? \n Why play one incredible game when you can play 2 at once? " ,
" Have you tried Starcraft 2? \n Use strategy and management to crush your enemies! " ,
" Have you tried Super Mario 64? \n 3-dimensional games like this owe everything to that game. " ,
" Have you tried Super Metroid? \n A classic game, yet still one of the best in the genre. " ,
" Have you tried Timespinner? \n Everyone who plays it ends up loving it! " ,
" Have you tried VVVVVV? \n Experience the essence of gaming distilled into its purest form! " ,
" Have you tried The Witness? \n Oh. I guess you already have. Thanks for playing! " ,
" Have you tried Super Mario World? \n I don ' t think I need to tell you that it is beloved by many. " ,
" Have you tried Overcooked 2? \n When you ' re done relaxing with puzzles, use your energy to yell at your friends. " ,
" Have you tried Zillion? \n Me neither. But it looks fun. So, let ' s try something new together? " ,
" Have you tried Hylics 2? \n Stop motion might just be the epitome of unique art styles. " ,
" Have you tried Pokemon Red&Blue? \n A cute pet collecting game that fascinated an entire generation. " ,
" Have you tried Lufia II? \n Roguelites are not just a 2010s phenomenon, turns out. " ,
" Have you tried Minecraft? \n I have recently learned this is a question that needs to be asked. " ,
" Have you tried Subnautica? \n If you like this game ' s lonely atmosphere, I would suggest you try it. " ,
" Have you tried Sonic Adventure 2? \n If the silence on this island is getting to you, "
" there aren ' t many games more energetic. " ,
" Waiting to get your items? \n Try BK Sudoku! Make progress even while stuck. " ,
2023-07-19 02:19:58 +00:00
" Have you tried Adventure? \n ...Holy crud, that game is 17 years older than me. " ,
" Have you tried Muse Dash? \n Rhythm game with cute girls! \n (Maybe skip if you don ' t like the Jungle panels) " ,
" Have you tried Clique? \n It ' s certainly a lot less complicated than this game! " ,
" Have you tried Bumper Stickers? \n Decades after its inception, people are still inventing unique twists on the match-3 genre. " ,
" Have you tried DLC Quest? \n I know you all like parody games. \n I got way too many requests to make a randomizer for \" The Looker \" . " ,
" Have you tried Doom? \n I wonder if a smart fridge can connect to Archipelago. " ,
" Have you tried Kingdom Hearts II? \n I ' ll wait for you to name a more epic crossover. " ,
" Have you tried Link ' s Awakening DX? \n Hopefully, Link won ' t be obsessed with circles when he wakes up. " ,
" Have you tried The Messenger? \n Old ideas made new again. It ' s how all art is made. " ,
" Have you tried Mega Man Battle Network 3? \n It ' s a Mega Man RPG. How could you not want to try that? " ,
" Have you tried Noita? \n If you like punishing yourself, you will like it. " ,
" Have you tried Stardew Valley? \n The Farming game that gave a damn. It ' s so easy to lose hours and days to it... " ,
" Have you tried The Legend of Zelda? \n In some sense, it was the starting point of \" adventure \" in video games. " ,
" Have you tried Undertale? \n I hope I ' m not the 10th person to ask you that. But it ' s, like, really good. " ,
" Have you tried Wargroove? \n I ' m glad that for every abandoned series, enough people are yearning for its return that one of them will know how to code. " ,
" Have you tried Blasphemous? \n You haven ' t? Blasphemy! \n ...Sorry. You should try it, though! " ,
2023-11-24 19:38:46 +00:00
" Have you tried Doom II? \n Got a good game on your hands? Just make it bigger and better. " ,
" Have you tried Lingo? \n It ' s an open world puzzle game. It features panels with non-verbally explained mechanics. \n If you like this game, you ' ll like Lingo too. " ,
" (Middle Yellow) \n YOU AILED OVERNIGHT \n H--- --- ----- -----? " ,
" Have you tried Bumper Stickers? \n Maybe after spending so much time on this island, you are longing for a simpler puzzle game. " ,
" Have you tried Pokemon Emerald? \n I ' m going to say it: 10/10, just the right amount of water. " ,
" Have you tried Terraria? \n A prime example of a survival sandbox game that beats the \" Wide as an ocean, deep as a puddle \" allegations. " ,
2024-01-31 20:07:07 +00:00
" Have you tried Final Fantasy Mystic Quest? \n Apparently, it was made in an attempt to simplify Final Fantasy for the western market. \n They were right, I suck at RPGs. " ,
" Have you tried Shivers? \n Witness 2 should totally feature a haunted Museum. " ,
" Have you tried Heretic? \n Wait, there is a Doom Engine game where you can look UP AND DOWN??? " ,
2023-07-19 02:19:58 +00:00
2023-03-02 23:08:24 +00:00
" One day I was fascinated by the subject of generation of waves by wind. " ,
" I don ' t like sandwiches. Why would you think I like sandwiches? Have you ever seen me with a sandwich? " ,
" Where are you right now? \n I ' m at soup! \n What do you mean you ' re at soup? " ,
" Remember to ask in the Archipelago Discord what the Functioning Brain does. " ,
" Don ' t use your puzzle skips, you might need them later. " ,
" For an extra challenge, try playing blindfolded. " ,
" Go to the top of the mountain and see if you can see your house. " ,
" Yellow = Red + Green \n Cyan = Green + Blue \n Magenta = Red + Blue " ,
" Maybe that panel really is unsolvable. " ,
" Did you make sure it was plugged in? " ,
" Do not look into laser with remaining eye. " ,
" Try pressing Space to jump. " ,
" The Witness is a Doom clone. \n Just replace the demons with puzzles " ,
" Test Hint please ignore " ,
" Shapers can never be placed outside the panel boundaries, even if subtracted. " ,
" The Keep laser panels use the same trick on both sides! " ,
" Can ' t get past a door? Try going around. Can ' t go around? Try building a nether portal. " ,
" We ' ve been trying to reach you about your car ' s extended warranty. " ,
" I hate this game. I hate this game. I hate this game. \n - Chess player Bobby Fischer " ,
" Dear Mario, \n Please come to the castle. I ' ve baked a cake for you! " ,
" Have you tried waking up? \n Yeah, me neither. " ,
" Why do they call it The Witness, when wit game the player view play of with the game. " ,
" THE WIND FISH IN NAME ONLY, FOR IT IS NEITHER " ,
" Like this game? \n Try The Wit.nes, Understand, INSIGHT, Taiji What the Witness?, and Tametsi. " ,
" In a race, It ' s survival of the Witnesst. " ,
" This hint has been removed. We apologize for your inconvenience. " ,
" O----------- " ,
" Circle is draw \n Square is separate \n Line is win " ,
" Circle is draw \n Star is pair \n Line is win " ,
" Circle is draw \n Circle is copy \n Line is win " ,
" Circle is draw \n Dot is eat \n Line is win " ,
" Circle is start \n Walk is draw \n Line is win " ,
" Circle is start \n Line is win \n Witness is you " ,
" Can ' t find any items? \n Consider a relaxing boat trip around the island! " ,
" Don ' t forget to like, comment, and subscribe. " ,
" Ah crap, gimme a second. \n [papers rustling] \n Sorry, nothing. " ,
" Trying to get a hint? Too bad. " ,
" Here ' s a hint: Get good at the game. " ,
" I ' m still not entirely sure what we ' re witnessing here. " ,
" Have you found a red page yet? No? Then have you found a blue page? " ,
" And here we see the Witness player, seeking answers where there are none- \n Did someone turn on the loudspeaker? " ,
2023-11-24 19:38:46 +00:00
" Be quiet. I can ' t hear the elevator. " ,
" Witness me. \n - The famous last words of John Witness. " ,
" It ' s okay, I always have to skip the Rotated Shaper puzzles too. " ,
" Alan please add hint. " ,
" Rumor has it there ' s an audio log with a hint nearby. " ,
" In the future, war will break out between obelisk_sides and individual EP players. \n Which side are you on? " ,
" Droplets: Low, High, Mid. \n Ambience: Mid, Low, Mid, High. " ,
" Name a better game involving lines. I ' ll wait. " ,
" \" You have to draw a line in the sand. \" \n - Arin \" Egoraptor \" Hanson " ,
" Have you tried? \n The puzzles tend to get easier if you do. " ,
" Sorry, I accidentally left my phone in the Jungle. \n And also all my fragile dishes. " ,
" Winner of the \" Most Irrelevant PR in AP History \" award! " ,
" I bet you wish this was a real hint :) " ,
" \" This hint is an impostor. \" - Junk hint submitted by T1mshady. \n ...wait, I ' m not supposed to say that part? " ,
" Wouldn ' t you like to know, weather buoy? " ,
" Give me a few minutes, I should have better material by then. " ,
" Just pet the doggy! You know you want to!!! " ,
" ceci n ' est pas une metroidvania " ,
" HINT is MELT \n YOU is HOT " ,
" Who ' s that behind you? " ,
" :3 " ,
" ^v ^^v> >>^>v \n ^^v>v ^v>> v>^> v>v^ " ,
" Statement #0162601, regarding a strange island that-- \n Oh, wait, sorry. I ' m not supposed to be here. " ,
" Hollow Bastion has 6 progression items. \n Or maybe it doesn ' t. \n I wouldn ' t know. " ,
" Set your hint count lower so I can tell you more jokes next time. " ,
" A non-edge start point is similar to a cat. \n It must be either inside or outside, it can ' t be both. " ,
" What if we kissed on the Bunker Laser Platform? \n Jk... unless? " ,
" You don ' t have Boat? Invisible boat time! \n You do have boat? Boat clipping time! " ,
" Cet indice est en français. Nous nous excusons de tout inconvénients engendrés par cela. " ,
" How many of you have personally witnessed a total solar eclipse? " ,
2024-01-31 20:07:07 +00:00
" In the Treehouse area, you will find 69 progression items. \n Nice. \n (Source: Just trust me) " ,
2023-11-24 19:38:46 +00:00
" Lingo \n Lingoing \n Lingone " ,
" The name of the captain was Albert Einstein. " ,
" Panel impossible Sigma plz fix " ,
" Welcome Back! (: " ,
" R R R U L L U L U R U R D R D R U U " ,
" Have you tried checking your tracker? " ,
" Hints suggested by: \n IHNN, Beaker, MrPokemon11, Ember, TheM8, NewSoupVi, Jasper Bird, T1mshady, "
" KF, Yoshi348, Berserker, BowlinJim, oddGarrett, Pink Switch, Rever, Ishigh, snolid. " ,
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
def __hash__ ( self ) :
return hash ( self . location )
def __eq__ ( self , other ) :
return self . location == other . location
@dataclass
class WitnessWordedHint :
wording : str
location : Optional [ Location ] = None
area : Optional [ str ] = None
area_amount : Optional [ int ] = None
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-02-11 01:25:03 +00:00
if difficulty == " sigma_expert " :
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-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-02-28 03:44:22 +00:00
def word_direct_hint ( world : " WitnessWorld " , hint : WitnessLocationHint ) :
location_name = hint . location . name
if hint . location . player != world . player :
location_name + = " ( " + world . multiworld . get_player_name ( hint . location . player ) + " ) "
item = hint . location . item
item_name = item . name
if item . player != world . player :
item_name + = " ( " + world . multiworld . get_player_name ( item . player ) + " ) "
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 )
def hint_from_item ( world : " WitnessWorld " , item_name : str , own_itempool : List [ Item ] ) - > Optional [ WitnessLocationHint ] :
2023-11-24 05:27:03 +00:00
locations = [ item . location for item in own_itempool if item . name == item_name and item . location ]
if not locations :
return None
location_obj = world . random . choice ( locations )
2022-10-09 02:13:52 +00:00
location_name = location_obj . name
2023-11-24 05:27:03 +00:00
if location_obj . player != world . player :
location_name + = " ( " + world . multiworld . get_player_name ( location_obj . player ) + " ) "
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 ] :
2023-11-24 05:27:03 +00:00
location_obj = world . multiworld . get_location ( location , world . player )
item_obj = world . multiworld . get_location ( location , world . player ) . item
2022-10-09 02:13:52 +00:00
item_name = item_obj . name
2023-11-24 05:27:03 +00:00
if item_obj . player != world . player :
item_name + = " ( " + world . multiworld . get_player_name ( item_obj . player ) + " ) "
2022-10-09 02:13:52 +00:00
2024-02-28 03:44:22 +00:00
return WitnessLocationHint ( location_obj , True )
2022-10-09 02:13:52 +00:00
2024-02-28 03:44:22 +00:00
def get_items_and_locations_in_random_order ( world : " WitnessWorld " , own_itempool : List [ Item ] ) :
prog_items_in_this_world = sorted (
item . name for item in own_itempool
if item . advancement and item . code and item . location
)
locations_in_this_world = sorted (
location . name for location in world . multiworld . get_locations ( world . player )
if location . address and location . progress_type != LocationProgressType . EXCLUDED
)
2022-10-09 02:13:52 +00:00
2024-02-28 03:44:22 +00:00
world . random . shuffle ( prog_items_in_this_world )
world . random . shuffle ( locations_in_this_world )
return prog_items_in_this_world , locations_in_this_world
def make_always_and_priority_hints ( world : " WitnessWorld " , own_itempool : List [ Item ] ,
already_hinted_locations : Set [ Location ]
) - > Tuple [ List [ WitnessLocationHint ] , List [ WitnessLocationHint ] ] :
prog_items_in_this_world , loc_in_this_world = get_items_and_locations_in_random_order ( world , own_itempool )
2022-10-09 02:13:52 +00:00
always_locations = [
2023-11-24 05:27:03 +00:00
location for location in get_always_hint_locations ( world )
2022-10-09 02:13:52 +00:00
if location in loc_in_this_world
]
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_locations = [
2023-11-24 05:27:03 +00:00
location for location in get_priority_hint_locations ( world )
2022-10-09 02:13:52 +00:00
if location in loc_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-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-02-28 03:44:22 +00:00
def make_extra_location_hints ( world : " WitnessWorld " , hint_amount : int , own_itempool : List [ Item ] ,
already_hinted_locations : Set [ Location ] , hints_to_use_first : List [ WitnessLocationHint ] ,
unhinted_locations_for_hinted_areas : Dict [ str , Set [ Location ] ] ) - > List [ WitnessWordedHint ] :
prog_items_in_this_world , locations_in_this_world = get_items_and_locations_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-02-28 03:44:22 +00:00
hints = [ ]
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]
area_reverse_lookup = { v : k for k , l in unhinted_locations_for_hinted_areas . items ( ) for v in l }
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 :
player_name = world . multiworld . get_player_name ( world . player )
logging . warning ( f " Ran out of items/locations to hint for player { player_name } . " )
break
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-02-28 03:44:22 +00:00
if not location_hint or location_hint . location in already_hinted_locations :
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 generate_joke_hints ( world : " WitnessWorld " , amount : int ) - > List [ Tuple [ str , int , int ] ] :
return [ ( x , - 1 , - 1 ) for x in world . random . sample ( joke_hints , amount ) ]
2023-06-20 22:45:26 +00:00
2023-11-24 05:27:03 +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-02-28 03:44:22 +00:00
unhinted_locations_per_area = dict ( )
unhinted_location_percentage_per_area = dict ( )
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 ] ] ] :
potential_areas = list ( StaticWitnessLogic . ALL_AREAS_BY_NAME . keys ( ) )
locations_per_area = dict ( )
items_per_area = dict ( )
for area in potential_areas :
regions = [
world . regio . created_regions [ region ]
for region in StaticWitnessLogic . ALL_AREAS_BY_NAME [ area ] [ " regions " ]
if region in world . regio . created_regions
]
locations = [ location for region in regions for location in region . get_locations ( ) if location . address ]
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
def word_area_hint ( world : " WitnessWorld " , hinted_area : str , corresponding_items : List [ Item ] ) - > Tuple [ str , int ] :
"""
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 .
"""
local_progression = sum ( item . player == world . player and item . advancement for item in corresponding_items )
non_local_progression = sum ( item . player != world . player and item . advancement for item in corresponding_items )
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
for item in corresponding_items
)
total_progression = non_local_progression + local_progression
player_count = world . multiworld . players
area_progression_word = " Both " if total_progression == 2 else " All "
if not total_progression :
hint_string = f " In the { hinted_area } area, you will find no progression items. "
elif total_progression == 1 :
hint_string = f " In the { hinted_area } area, you will find 1 progression item. "
if player_count > 1 :
if local_lasers :
hint_string + = " \n This 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 " \n This item is for { other_player_str } . "
else :
hint_string + = " \n This 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 + = " \n This item is a laser. "
else :
hint_string = f " In the { hinted_area } area, you will find { total_progression } progression items. "
if local_lasers == total_progression :
sentence_end = ( " for this world. " if player_count > 1 else " . " )
hint_string + = f " \n All of them are lasers " + sentence_end
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 " \n One 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 + = " \n Also, one of them is a laser. "
else :
hint_string + = " \n Also, one of them is a laser for this world. "
elif local_lasers :
if not non_local_progression :
hint_string + = f " \n Also, { local_lasers } of them are lasers. "
else :
hint_string + = f " \n Also, { 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 + = " \n One 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-02-28 03:44:22 +00:00
return hint_string , total_progression
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 :
hint_string , prog_amount = word_area_hint ( world , hinted_area , items_per_area [ hinted_area ] )
hints . append ( WitnessWordedHint ( hint_string , None , f " hinted_area: { hinted_area } " , prog_amount ) )
if len ( hinted_areas ) < amount :
player_name = world . multiworld . get_player_name ( world . player )
logging . warning ( f " Was not able to make { amount } area hints for player { player_name } . "
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 )
if loc . address and StaticWitnessLogic . ENTITIES_BY_NAME [ loc . name ] [ " area " ] [ " name " ] == " Tutorial (Inside) "
}
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 )
unhinted_locations_per_area : Dict [ str , Set [ Location ] ] = dict ( )
# 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 :
player_name = world . multiworld . get_player_name ( world . player )
logging . warning ( f " Couldn ' t generate { hint_amount } hints for player { player_name } . "
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
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