2020-06-22 18:25:24 +00:00
import collections
2021-10-22 12:04:13 +00:00
from typing import Counter , Optional , Dict , Any , Tuple
2020-06-22 18:25:24 +00:00
from flask import render_template
from werkzeug . exceptions import abort
2020-06-23 12:37:41 +00:00
import datetime
2020-06-26 17:29:33 +00:00
from uuid import UUID
2020-06-22 18:25:24 +00:00
2021-05-19 19:55:18 +00:00
from worlds . alttp import Items
2020-07-11 14:59:37 +00:00
from WebHostLib import app , cache , Room
2021-04-04 01:18:19 +00:00
from Utils import restricted_loads
2021-05-13 23:25:41 +00:00
from worlds import lookup_any_item_id_to_name , lookup_any_location_id_to_name
2020-06-22 18:25:24 +00:00
2021-10-22 17:06:08 +00:00
alttp_icons = {
" Blue Shield " : r " https://www.zeldadungeon.net/wiki/images/8/85/Fighters-Shield.png " ,
" Red Shield " : r " https://www.zeldadungeon.net/wiki/images/5/55/Fire-Shield.png " ,
" Mirror Shield " : r " https://www.zeldadungeon.net/wiki/images/8/84/Mirror-Shield.png " ,
" Fighter Sword " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/4/40/SFighterSword.png?width=1920 " ,
" Master Sword " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/6/65/SMasterSword.png?width=1920 " ,
" Tempered Sword " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/9/92/STemperedSword.png?width=1920 " ,
" Golden Sword " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/2/28/SGoldenSword.png?width=1920 " ,
" Bow " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Bow_ % 26_Arrows_Sprite.png?version=5f85a70e6366bf473544ef93b274f74c " ,
" Silver Bow " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/6/65/Bow.png?width=1920 " ,
" Green Mail " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c9/SGreenTunic.png?width=1920 " ,
" Blue Mail " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/9/98/SBlueTunic.png?width=1920 " ,
" Red Mail " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/7/74/SRedTunic.png?width=1920 " ,
" Power Glove " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/f/f5/SPowerGlove.png?width=1920 " ,
" Titan Mitts " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c1/STitanMitt.png?width=1920 " ,
" Progressive Sword " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/cc/ALttP_Master_Sword_Sprite.png?version=55869db2a20e157cd3b5c8f556097725 " ,
" Pegasus Boots " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Pegasus_Shoes_Sprite.png?version=405f42f97240c9dcd2b71ffc4bebc7f9 " ,
" Progressive Glove " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c1/STitanMitt.png?width=1920 " ,
" Flippers " : r " https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/4/4c/ZoraFlippers.png?width=1920 " ,
" Moon Pearl " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Moon_Pearl_Sprite.png?version=d601542d5abcc3e006ee163254bea77e " ,
" Progressive Bow " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Bow_ % 26_Arrows_Sprite.png?version=cfb7648b3714cccc80e2b17b2adf00ed " ,
" Blue Boomerang " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c3/ALttP_Boomerang_Sprite.png?version=96127d163759395eb510b81a556d500e " ,
" Red Boomerang " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/b9/ALttP_Magical_Boomerang_Sprite.png?version=47cddce7a07bc3e4c2c10727b491f400 " ,
" Hookshot " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/24/Hookshot.png?version=c90bc8e07a52e8090377bd6ef854c18b " ,
" Mushroom " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/35/ALttP_Mushroom_Sprite.png?version=1f1acb30d71bd96b60a3491e54bbfe59 " ,
" Magic Powder " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Powder_Sprite.png?version=c24e38effbd4f80496d35830ce8ff4ec " ,
" Fire Rod " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d6/FireRod.png?version=6eabc9f24d25697e2c4cd43ddc8207c0 " ,
" Ice Rod " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d7/ALttP_Ice_Rod_Sprite.png?version=1f944148223d91cfc6a615c92286c3bc " ,
" Bombos " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/8c/ALttP_Bombos_Medallion_Sprite.png?version=f4d6aba47fb69375e090178f0fc33b26 " ,
" Ether " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/Ether.png?version=34027651a5565fcc5a83189178ab17b5 " ,
" Quake " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/56/ALttP_Quake_Medallion_Sprite.png?version=efd64d451b1831bd59f7b7d6b61b5879 " ,
" Lamp " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Lantern_Sprite.png?version=e76eaa1ec509c9a5efb2916698d5a4ce " ,
" Hammer " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d1/ALttP_Hammer_Sprite.png?version=e0adec227193818dcaedf587eba34500 " ,
" Shovel " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c4/ALttP_Shovel_Sprite.png?version=e73d1ce0115c2c70eaca15b014bd6f05 " ,
" Flute " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/db/Flute.png?version=ec4982b31c56da2c0c010905c5c60390 " ,
" Bug Catching Net " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/54/Bug-CatchingNet.png?version=4d40e0ee015b687ff75b333b968d8be6 " ,
" Book of Mudora " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/22/ALttP_Book_of_Mudora_Sprite.png?version=11e4632bba54f6b9bf921df06ac93744 " ,
" Bottle " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ef/ALttP_Magic_Bottle_Sprite.png?version=fd98ab04db775270cbe79fce0235777b " ,
" Cane of Somaria " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e1/ALttP_Cane_of_Somaria_Sprite.png?version=8cc1900dfd887890badffc903bb87943 " ,
" Cane of Byrna " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Cane_of_Byrna_Sprite.png?version=758b607c8cbe2cf1900d42a0b3d0fb54 " ,
" Cape " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1c/ALttP_Magic_Cape_Sprite.png?version=6b77f0d609aab0c751307fc124736832 " ,
" Magic Mirror " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Mirror_Sprite.png?version=e035dbc9cbe2a3bd44aa6d047762b0cc " ,
" Triforce " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/4/4e/TriforceALttPTitle.png?version=dc398e1293177581c16303e4f9d12a48 " ,
" Small Key " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/f/f1/ALttP_Small_Key_Sprite.png?version=4f35d92842f0de39d969181eea03774e " ,
" Big Key " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Big_Key_Sprite.png?version=136dfa418ba76c8b4e270f466fc12f4d " ,
" Chest " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/7/73/ALttP_Treasure_Chest_Sprite.png?version=5f530ecd98dcb22251e146e8049c0dda " ,
" Light World " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e7/ALttP_Soldier_Green_Sprite.png?version=d650d417934cd707a47e496489c268a6 " ,
" Dark World " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/9/94/ALttP_Moblin_Sprite.png?version=ebf50e33f4657c377d1606bcc0886ddc " ,
" Hyrule Castle " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d3/ALttP_Ball_and_Chain_Trooper_Sprite.png?version=1768a87c06d29cc8e7ddd80b9fa516be " ,
" Agahnims Tower " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1e/ALttP_Agahnim_Sprite.png?version=365956e61b0c2191eae4eddbe591dab5 " ,
" Desert Palace " : r " https://www.zeldadungeon.net/wiki/images/2/25/Lanmola-ALTTP-Sprite.png " ,
" Eastern Palace " : r " https://www.zeldadungeon.net/wiki/images/d/dc/RedArmosKnight.png " ,
" Tower of Hera " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/ALttP_Moldorm_Sprite.png?version=c588257bdc2543468e008a6b30f262a7 " ,
" Palace of Darkness " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Helmasaur_King_Sprite.png?version=ab8a4a1cfd91d4fc43466c56cba30022 " ,
" Swamp Palace " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/7/73/ALttP_Arrghus_Sprite.png?version=b098be3122e53f751b74f4a5ef9184b5 " ,
" Skull Woods " : r " https://alttp-wiki.net/images/6/6a/Mothula.png " ,
" Thieves Town " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/86/ALttP_Blind_the_Thief_Sprite.png?version=3833021bfcd112be54e7390679047222 " ,
" Ice Palace " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Kholdstare_Sprite.png?version=e5a1b0e8b2298e550d85f90bf97045c0 " ,
" Misery Mire " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/85/ALttP_Vitreous_Sprite.png?version=92b2e9cb0aa63f831760f08041d8d8d8 " ,
" Turtle Rock " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/9/91/ALttP_Trinexx_Sprite.png?version=0cc867d513952aa03edd155597a0c0be " ,
" Ganons Tower " : r " https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/b9/ALttP_Ganon_Sprite.png?version=956f51f054954dfff53c1a9d4f929c74 "
}
2021-09-23 11:52:32 +00:00
2021-05-14 12:38:23 +00:00
def get_alttp_id ( item_name ) :
2021-02-01 00:57:44 +00:00
return Items . item_table [ item_name ] [ 2 ]
2020-06-22 18:25:24 +00:00
2021-05-13 23:25:41 +00:00
app . jinja_env . filters [ " location_name " ] = lambda location : lookup_any_location_id_to_name . get ( location , location )
app . jinja_env . filters [ ' item_name ' ] = lambda id : lookup_any_item_id_to_name . get ( id , id )
2020-07-27 17:30:31 +00:00
2020-06-22 18:25:24 +00:00
links = { " Bow " : " Progressive Bow " ,
" Silver Arrows " : " Progressive Bow " ,
2020-06-30 07:51:11 +00:00
" Silver Bow " : " Progressive Bow " ,
2020-06-22 18:25:24 +00:00
" Progressive Bow (Alt) " : " Progressive Bow " ,
" Bottle (Red Potion) " : " Bottle " ,
" Bottle (Green Potion) " : " Bottle " ,
" Bottle (Blue Potion) " : " Bottle " ,
" Bottle (Fairy) " : " Bottle " ,
" Bottle (Bee) " : " Bottle " ,
" Bottle (Good Bee) " : " Bottle " ,
" Fighter Sword " : " Progressive Sword " ,
" Master Sword " : " Progressive Sword " ,
" Tempered Sword " : " Progressive Sword " ,
" Golden Sword " : " Progressive Sword " ,
" Power Glove " : " Progressive Glove " ,
" Titans Mitts " : " Progressive Glove "
}
2020-06-23 21:50:37 +00:00
levels = { " Fighter Sword " : 1 ,
" Master Sword " : 2 ,
" Tempered Sword " : 3 ,
" Golden Sword " : 4 ,
" Power Glove " : 1 ,
2020-06-30 07:51:11 +00:00
" Titans Mitts " : 2 ,
2020-07-12 21:27:21 +00:00
" Bow " : 1 ,
2020-06-30 07:51:11 +00:00
" Silver Bow " : 2 }
2020-06-23 21:50:37 +00:00
2021-05-14 12:38:23 +00:00
multi_items = { get_alttp_id ( name ) for name in ( " Progressive Sword " , " Progressive Bow " , " Bottle " , " Progressive Glove " ) }
links = { get_alttp_id ( key ) : get_alttp_id ( value ) for key , value in links . items ( ) }
levels = { get_alttp_id ( key ) : value for key , value in levels . items ( ) }
2020-06-22 18:25:24 +00:00
tracking_names = [ " Progressive Sword " , " Progressive Bow " , " Book of Mudora " , " Hammer " ,
" Hookshot " , " Magic Mirror " , " Flute " ,
" Pegasus Boots " , " Progressive Glove " , " Flippers " , " Moon Pearl " , " Blue Boomerang " ,
" Red Boomerang " , " Bug Catching Net " , " Cape " , " Shovel " , " Lamp " ,
" Mushroom " , " Magic Powder " ,
" Cane of Somaria " , " Cane of Byrna " , " Fire Rod " , " Ice Rod " , " Bombos " , " Ether " , " Quake " ,
2021-03-06 04:59:49 +00:00
" Bottle " , " Triforce " ]
2020-06-22 18:25:24 +00:00
2020-06-23 00:50:07 +00:00
default_locations = {
' Light World ' : { 1572864 , 1572865 , 60034 , 1572867 , 1572868 , 60037 , 1572869 , 1572866 , 60040 , 59788 , 60046 , 60175 ,
1572880 , 60049 , 60178 , 1572883 , 60052 , 60181 , 1572885 , 60055 , 60184 , 191256 , 60058 , 60187 , 1572884 ,
1572886 , 1572887 , 1572906 , 60202 , 60205 , 59824 , 166320 , 1010170 , 60208 , 60211 , 60214 , 60217 , 59836 ,
60220 , 60223 , 59839 , 1573184 , 60226 , 975299 , 1573188 , 1573189 , 188229 , 60229 , 60232 , 1573193 ,
1573194 , 60235 , 1573187 , 59845 , 59854 , 211407 , 60238 , 59857 , 1573185 , 1573186 , 1572882 , 212328 ,
59881 , 59761 , 59890 , 59770 , 193020 , 212605 } ,
' Dark World ' : { 59776 , 59779 , 975237 , 1572870 , 60043 , 1572881 , 60190 , 60193 , 60196 , 60199 , 60840 , 1573190 , 209095 ,
1573192 , 1573191 , 60241 , 60244 , 60247 , 60250 , 59884 , 59887 , 60019 , 60022 , 60028 , 60031 } ,
' Desert Palace ' : { 1573216 , 59842 , 59851 , 59791 , 1573201 , 59830 } ,
' Eastern Palace ' : { 1573200 , 59827 , 59893 , 59767 , 59833 , 59773 } ,
' Hyrule Castle ' : { 60256 , 60259 , 60169 , 60172 , 59758 , 59764 , 60025 , 60253 } ,
' Agahnims Tower ' : { 60082 , 60085 } ,
' Tower of Hera ' : { 1573218 , 59878 , 59821 , 1573202 , 59896 , 59899 } ,
' Swamp Palace ' : { 60064 , 60067 , 60070 , 59782 , 59785 , 60073 , 60076 , 60079 , 1573204 , 60061 } ,
' Thieves Town ' : { 59905 , 59908 , 59911 , 59914 , 59917 , 59920 , 59923 , 1573206 } ,
' Skull Woods ' : { 59809 , 59902 , 59848 , 59794 , 1573205 , 59800 , 59803 , 59806 } ,
' Ice Palace ' : { 59872 , 59875 , 59812 , 59818 , 59860 , 59797 , 1573207 , 59869 } ,
' Misery Mire ' : { 60001 , 60004 , 60007 , 60010 , 60013 , 1573208 , 59866 , 59998 } ,
' Turtle Rock ' : { 59938 , 59941 , 59944 , 1573209 , 59947 , 59950 , 59953 , 59956 , 59926 , 59929 , 59932 , 59935 } ,
' Palace of Darkness ' : { 59968 , 59971 , 59974 , 59977 , 59980 , 59983 , 59986 , 1573203 , 59989 , 59959 , 59992 , 59962 , 59995 ,
59965 } ,
' Ganons Tower ' : { 60160 , 60163 , 60166 , 60088 , 60091 , 60094 , 60097 , 60100 , 60103 , 60106 , 60109 , 60112 , 60115 , 60118 ,
2020-06-23 03:46:32 +00:00
60121 , 60124 , 60127 , 1573217 , 60130 , 60133 , 60136 , 60139 , 60142 , 60145 , 60148 , 60151 , 60157 } ,
' Total ' : set ( ) }
2020-06-24 14:06:19 +00:00
2020-10-27 07:47:59 +00:00
key_only_locations = {
' Light World ' : set ( ) ,
' Dark World ' : set ( ) ,
' Desert Palace ' : { 0x140031 , 0x14002b , 0x140061 , 0x140028 } ,
' Eastern Palace ' : { 0x14005b , 0x140049 } ,
' Hyrule Castle ' : { 0x140037 , 0x140034 , 0x14000d , 0x14003d } ,
' Agahnims Tower ' : { 0x140061 , 0x140052 } ,
' Tower of Hera ' : set ( ) ,
' Swamp Palace ' : { 0x140019 , 0x140016 , 0x140013 , 0x140010 , 0x14000a } ,
' Thieves Town ' : { 0x14005e , 0x14004f } ,
' Skull Woods ' : { 0x14002e , 0x14001c } ,
' Ice Palace ' : { 0x140004 , 0x140022 , 0x140025 , 0x140046 } ,
' Misery Mire ' : { 0x140055 , 0x14004c , 0x140064 } ,
' Turtle Rock ' : { 0x140058 , 0x140007 } ,
' Palace of Darkness ' : set ( ) ,
' Ganons Tower ' : { 0x140040 , 0x140043 , 0x14003a , 0x14001f } ,
' Total ' : set ( )
}
2020-06-23 00:50:07 +00:00
location_to_area = { }
for area , locations in default_locations . items ( ) :
for location in locations :
location_to_area [ location ] = area
2020-10-27 07:47:59 +00:00
for area , locations in key_only_locations . items ( ) :
for location in locations :
location_to_area [ location ] = area
2020-06-23 00:50:07 +00:00
checks_in_area = { area : len ( checks ) for area , checks in default_locations . items ( ) }
2020-06-23 03:46:32 +00:00
checks_in_area [ " Total " ] = 216
2020-06-23 00:50:07 +00:00
ordered_areas = ( ' Light World ' , ' Dark World ' , ' Hyrule Castle ' , ' Agahnims Tower ' , ' Eastern Palace ' , ' Desert Palace ' ,
' Tower of Hera ' , ' Palace of Darkness ' , ' Swamp Palace ' , ' Skull Woods ' , ' Thieves Town ' , ' Ice Palace ' ,
2020-06-23 03:46:32 +00:00
' Misery Mire ' , ' Turtle Rock ' , ' Ganons Tower ' , " Total " )
2020-06-23 00:50:07 +00:00
2020-06-22 18:25:24 +00:00
tracking_ids = [ ]
for item in tracking_names :
2021-05-14 12:38:23 +00:00
tracking_ids . append ( get_alttp_id ( item ) )
2020-06-22 18:25:24 +00:00
2020-06-24 14:06:19 +00:00
small_key_ids = { }
big_key_ids = { }
2021-03-06 21:59:04 +00:00
ids_small_key = { }
ids_big_key = { }
2020-06-24 14:06:19 +00:00
for item_name , data in Items . item_table . items ( ) :
if " Key " in item_name :
area = item_name . split ( " ( " ) [ 1 ] [ : - 1 ]
if " Small " in item_name :
2021-02-01 00:57:44 +00:00
small_key_ids [ area ] = data [ 2 ]
2021-03-06 21:59:04 +00:00
ids_small_key [ data [ 2 ] ] = area
2020-06-24 14:06:19 +00:00
else :
2021-02-01 00:57:44 +00:00
big_key_ids [ area ] = data [ 2 ]
2021-03-06 21:59:04 +00:00
ids_big_key [ data [ 2 ] ] = area
2020-06-24 14:06:19 +00:00
2021-04-04 01:18:19 +00:00
from MultiServer import get_item_name_from_id , Context
2020-06-22 18:25:24 +00:00
2020-06-23 21:50:37 +00:00
def attribute_item ( inventory , team , recipient , item ) :
target_item = links . get ( item , item )
if item in levels : # non-progressive
inventory [ team ] [ recipient ] [ target_item ] = max ( inventory [ team ] [ recipient ] [ target_item ] , levels [ item ] )
else :
inventory [ team ] [ recipient ] [ target_item ] + = 1
2020-06-24 14:06:19 +00:00
2021-02-23 05:05:31 +00:00
def attribute_item_solo ( inventory , item ) :
2021-05-13 19:57:11 +00:00
""" Adds item to inventory counter, converts everything to progressive. """
2021-02-23 05:05:31 +00:00
target_item = links . get ( item , item )
if item in levels : # non-progressive
inventory [ target_item ] = max ( inventory [ target_item ] , levels [ item ] )
else :
inventory [ target_item ] + = 1
2020-06-24 14:06:19 +00:00
@app.template_filter ( )
def render_timedelta ( delta : datetime . timedelta ) :
hours , minutes = divmod ( delta . total_seconds ( ) / 60 , 60 )
hours = str ( int ( hours ) )
minutes = str ( int ( minutes ) ) . zfill ( 2 )
return f " { hours } : { minutes } "
2020-06-28 07:28:13 +00:00
_multidata_cache = { }
2021-09-23 11:52:32 +00:00
2020-10-29 22:18:59 +00:00
def get_location_table ( checks_table : dict ) - > dict :
loc_to_area = { }
for area , locations in checks_table . items ( ) :
if area == " Total " :
continue
for location in locations :
loc_to_area [ location ] = area
return loc_to_area
2020-06-28 07:28:13 +00:00
2021-09-23 11:52:32 +00:00
2020-06-28 07:28:13 +00:00
def get_static_room_data ( room : Room ) :
result = _multidata_cache . get ( room . seed . id , None )
if result :
return result
2021-04-04 01:18:19 +00:00
multidata = Context . _decompress ( room . seed . multidata )
2020-06-28 07:28:13 +00:00
# in > 100 players this can take a bit of time and is the main reason for the cache
2021-10-22 12:04:13 +00:00
locations : Dict [ int , Dict [ int , Tuple [ int , int ] ] ] = multidata [ ' locations ' ]
names : Dict [ int , Dict [ int , str ] ] = multidata [ " names " ]
2020-10-27 07:47:59 +00:00
seed_checks_in_area = checks_in_area . copy ( )
2020-07-19 19:15:55 +00:00
use_door_tracker = False
if " tags " in multidata :
2020-07-21 21:15:19 +00:00
use_door_tracker = " DR " in multidata [ " tags " ]
2020-10-27 07:47:59 +00:00
if use_door_tracker :
for area , checks in key_only_locations . items ( ) :
seed_checks_in_area [ area ] + = len ( checks )
seed_checks_in_area [ " Total " ] = 249
2020-10-29 22:18:59 +00:00
2021-04-04 01:18:19 +00:00
player_checks_in_area = { playernumber : { areaname : len ( multidata [ " checks_in_area " ] [ playernumber ] [ areaname ] )
2021-09-23 11:52:32 +00:00
if areaname != " Total " else multidata [ " checks_in_area " ] [ playernumber ] [ " Total " ]
2021-04-04 01:18:19 +00:00
for areaname in ordered_areas }
for playernumber in range ( 1 , len ( names [ 0 ] ) + 1 ) }
player_location_to_area = { playernumber : get_location_table ( multidata [ " checks_in_area " ] [ playernumber ] )
for playernumber in range ( 1 , len ( names [ 0 ] ) + 1 ) }
2021-03-06 21:59:04 +00:00
2021-04-04 01:18:19 +00:00
result = locations , names , use_door_tracker , player_checks_in_area , player_location_to_area , \
2021-10-22 12:04:13 +00:00
multidata [ " precollected_items " ] , \
2021-05-19 19:55:18 +00:00
multidata [ " games " ]
2020-07-19 19:15:55 +00:00
_multidata_cache [ room . seed . id ] = result
return result
2020-06-28 07:28:13 +00:00
2021-03-06 04:59:49 +00:00
@app.route ( ' /tracker/<suuid:tracker>/<int:tracked_team>/<int:tracked_player> ' )
2021-05-13 19:57:11 +00:00
@cache.memoize ( timeout = 60 ) # multisave is currently created at most every minute
2021-03-06 04:59:49 +00:00
def getPlayerTracker ( tracker : UUID , tracked_team : int , tracked_player : int ) :
2021-02-23 05:05:31 +00:00
# Team and player must be positive and greater than zero
2021-03-06 04:59:49 +00:00
if tracked_team < 0 or tracked_player < 1 :
2021-02-23 05:05:31 +00:00
abort ( 404 )
2021-10-22 12:04:13 +00:00
room : Optional [ Room ] = Room . get ( tracker = tracker )
2021-02-21 05:38:53 +00:00
if not room :
abort ( 404 )
2021-02-23 05:05:31 +00:00
# Collect seed information and pare it down to a single player
2021-05-19 19:55:18 +00:00
locations , names , use_door_tracker , seed_checks_in_area , player_location_to_area , \
2021-10-22 12:04:13 +00:00
precollected_items , games = get_static_room_data ( room )
2021-03-06 04:59:49 +00:00
player_name = names [ tracked_team ] [ tracked_player - 1 ]
location_to_area = player_location_to_area [ tracked_player ]
2021-02-21 05:38:53 +00:00
inventory = collections . Counter ( )
checks_done = { loc_name : 0 for loc_name in default_locations }
2021-02-23 05:05:31 +00:00
# Add starting items to inventory
2021-05-09 19:22:21 +00:00
starting_items = precollected_items [ tracked_player ]
2021-02-23 05:05:31 +00:00
if starting_items :
for item_id in starting_items :
attribute_item_solo ( inventory , item_id )
2021-04-04 01:18:19 +00:00
if room . multisave :
2021-10-22 12:04:13 +00:00
multisave : Dict [ str , Any ] = restricted_loads ( room . multisave )
2021-04-04 01:18:19 +00:00
else :
2021-10-22 12:04:13 +00:00
multisave : Dict [ str , Any ] = { }
2021-04-04 01:18:19 +00:00
2021-02-23 05:05:31 +00:00
# Add items to player inventory
2021-05-13 19:57:11 +00:00
for ( ms_team , ms_player ) , locations_checked in multisave . get ( " location_checks " , { } ) . items ( ) :
2021-02-23 05:05:31 +00:00
# Skip teams and players not matching the request
2021-05-13 19:57:11 +00:00
player_locations = locations [ ms_player ]
2021-03-06 04:59:49 +00:00
if ms_team == tracked_team :
# If the player does not have the item, do nothing
for location in locations_checked :
2021-05-13 19:57:11 +00:00
if location in player_locations :
item , recipient = player_locations [ location ]
2021-09-23 11:52:32 +00:00
if recipient == tracked_player : # a check done for the tracked player
2021-05-13 19:57:11 +00:00
attribute_item_solo ( inventory , item )
2021-09-23 11:52:32 +00:00
if ms_player == tracked_player : # a check done by the tracked player
2021-05-13 19:57:11 +00:00
checks_done [ location_to_area [ location ] ] + = 1
checks_done [ " Total " ] + = 1
2021-10-22 12:04:13 +00:00
2021-05-19 19:55:18 +00:00
if games [ tracked_player ] == " A Link to the Past " :
2021-10-26 13:34:19 +00:00
return __renderAlttpTracker ( multisave , room , locations , inventory , tracked_team , tracked_player , player_name , \
2021-10-22 12:04:13 +00:00
seed_checks_in_area , checks_done )
2021-09-23 11:52:32 +00:00
elif games [ tracked_player ] == " Minecraft " :
2021-10-26 13:34:19 +00:00
return __renderMinecraftTracker ( multisave , room , locations , inventory , tracked_team , tracked_player , player_name )
2021-09-24 18:28:10 +00:00
elif games [ tracked_player ] == " Ocarina of Time " :
2021-10-26 13:34:19 +00:00
return __renderOoTTracker ( multisave , room , locations , inventory , tracked_team , tracked_player , player_name )
2021-10-23 22:35:41 +00:00
elif games [ tracked_player ] == " Timespinner " :
2021-10-26 13:34:19 +00:00
return __renderTimespinnerTracker ( multisave , room , locations , inventory , tracked_team , tracked_player , player_name )
2021-05-19 19:55:18 +00:00
else :
2021-10-26 13:34:19 +00:00
return __renderGenericTracker ( multisave , room , locations , inventory , tracked_team , tracked_player , player_name )
2021-10-22 12:04:13 +00:00
2021-10-26 13:34:19 +00:00
def __renderAlttpTracker ( multisave : Dict [ str , Any ] , room : Room , locations : Dict [ int , Dict [ int , Tuple [ int , int ] ] ] ,
2021-10-22 12:04:13 +00:00
inventory : Counter , team : int , player : int , playerName : str ,
seed_checks_in_area : Dict [ int , Dict [ str , int ] ] , checks_done : Dict [ str , int ] ) - > str :
# Note the presence of the triforce item
game_state = multisave . get ( " client_game_state " , { } ) . get ( ( team , player ) , 0 )
if game_state == 30 :
inventory [ 106 ] = 1 # Triforce
# Progressive items need special handling for icons and class
progressive_items = {
" Progressive Sword " : 94 ,
" Progressive Glove " : 97 ,
" Progressive Bow " : 100 ,
" Progressive Mail " : 96 ,
" Progressive Shield " : 95 ,
}
progressive_names = {
" Progressive Sword " : [ None , ' Fighter Sword ' , ' Master Sword ' , ' Tempered Sword ' , ' Golden Sword ' ] ,
" Progressive Glove " : [ None , ' Power Glove ' , ' Titan Mitts ' ] ,
" Progressive Bow " : [ None , " Bow " , " Silver Bow " ] ,
" Progressive Mail " : [ " Green Mail " , " Blue Mail " , " Red Mail " ] ,
" Progressive Shield " : [ None , " Blue Shield " , " Red Shield " , " Mirror Shield " ]
}
# Determine which icon to use
display_data = { }
for item_name , item_id in progressive_items . items ( ) :
level = min ( inventory [ item_id ] , len ( progressive_names [ item_name ] ) - 1 )
display_name = progressive_names [ item_name ] [ level ]
acquired = True
if not display_name :
acquired = False
display_name = progressive_names [ item_name ] [ level + 1 ]
base_name = item_name . split ( maxsplit = 1 ) [ 1 ] . lower ( )
display_data [ base_name + " _acquired " ] = acquired
2021-10-22 17:06:08 +00:00
display_data [ base_name + " _url " ] = alttp_icons [ display_name ]
2021-10-22 12:04:13 +00:00
# The single player tracker doesn't care about overworld, underworld, and total checks. Maybe it should?
sp_areas = ordered_areas [ 2 : 15 ]
player_big_key_locations = set ( )
player_small_key_locations = set ( )
for loc_data in locations . values ( ) :
for item_id , item_player in loc_data . values ( ) :
if item_player == player :
if item_id in ids_big_key :
player_big_key_locations . add ( ids_big_key [ item_id ] )
elif item_id in ids_small_key :
player_small_key_locations . add ( ids_small_key [ item_id ] )
return render_template ( " lttpTracker.html " , inventory = inventory ,
2021-10-22 17:06:08 +00:00
player_name = playerName , room = room , icons = alttp_icons , checks_done = checks_done ,
2021-10-22 12:04:13 +00:00
checks_in_area = seed_checks_in_area [ player ] ,
acquired_items = { lookup_any_item_id_to_name [ id ] for id in inventory } ,
small_key_ids = small_key_ids , big_key_ids = big_key_ids , sp_areas = sp_areas ,
key_locations = player_small_key_locations ,
big_key_locations = player_big_key_locations ,
* * display_data )
2021-10-26 13:34:19 +00:00
def __renderMinecraftTracker ( multisave : Dict [ str , Any ] , room : Room , locations : Dict [ int , Dict [ int , Tuple [ int , int ] ] ] ,
2021-10-22 12:04:13 +00:00
inventory : Counter , team : int , player : int , playerName : str ) - > str :
icons = {
" Wooden Pickaxe " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/d/d2/Wooden_Pickaxe_JE3_BE3.png " ,
" Stone Pickaxe " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/c/c4/Stone_Pickaxe_JE2_BE2.png " ,
" Iron Pickaxe " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/d/d1/Iron_Pickaxe_JE3_BE2.png " ,
" Diamond Pickaxe " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/e/e7/Diamond_Pickaxe_JE3_BE3.png " ,
" Wooden Sword " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/d/d5/Wooden_Sword_JE2_BE2.png " ,
" Stone Sword " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/b/b1/Stone_Sword_JE2_BE2.png " ,
" Iron Sword " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/8/8e/Iron_Sword_JE2_BE2.png " ,
" Diamond Sword " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/4/44/Diamond_Sword_JE3_BE3.png " ,
" Leather Tunic " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/b/b7/Leather_Tunic_JE4_BE2.png " ,
" Iron Chestplate " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/3/31/Iron_Chestplate_JE2_BE2.png " ,
" Diamond Chestplate " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/e/e0/Diamond_Chestplate_JE3_BE2.png " ,
" Iron Ingot " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/f/fc/Iron_Ingot_JE3_BE2.png " ,
" Block of Iron " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/7/7e/Block_of_Iron_JE4_BE3.png " ,
" Brewing Stand " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/f/fa/Brewing_Stand.png " ,
" Ender Pearl " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/f/f6/Ender_Pearl_JE3_BE2.png " ,
" Bucket " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/f/fc/Bucket_JE2_BE2.png " ,
" Bow " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/a/ab/Bow_ % 28Pull_2 % 29_JE1_BE1.png " ,
" Shield " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/c/c6/Shield_JE2_BE1.png " ,
" Red Bed " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/d/dc/Red_Bed_JE4_BE3.png " ,
" Netherite Scrap " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/3/33/Netherite_Scrap_JE2_BE1.png " ,
" Flint and Steel " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/9/94/Flint_and_Steel_JE4_BE2.png " ,
" Enchanting Table " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/3/31/Enchanting_Table.gif " ,
" Fishing Rod " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/7/7f/Fishing_Rod_JE2_BE2.png " ,
" Campfire " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/9/91/Campfire_JE2_BE2.gif " ,
" Water Bottle " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/7/75/Water_Bottle_JE2_BE2.png " ,
" Dragon Head " : " https://static.wikia.nocookie.net/minecraft_gamepedia/images/b/b6/Dragon_Head.png " ,
}
minecraft_location_ids = {
" Story " : [ 42073 , 42080 , 42081 , 42023 , 42082 , 42027 , 42039 , 42085 , 42002 , 42009 , 42010 ,
42070 , 42041 , 42049 , 42090 , 42004 , 42031 , 42025 , 42029 , 42051 , 42077 , 42089 ] ,
" Nether " : [ 42017 , 42044 , 42069 , 42058 , 42034 , 42060 , 42066 , 42076 , 42064 , 42071 , 42021 ,
42062 , 42008 , 42061 , 42033 , 42011 , 42006 , 42019 , 42000 , 42040 , 42001 , 42015 , 42014 ] ,
" The End " : [ 42052 , 42005 , 42012 , 42032 , 42030 , 42042 , 42018 , 42038 , 42046 ] ,
" Adventure " : [ 42047 , 42086 , 42087 , 42050 , 42059 , 42055 , 42072 , 42003 , 42035 , 42016 , 42020 ,
42048 , 42054 , 42068 , 42043 , 42074 , 42075 , 42024 , 42026 , 42037 , 42045 , 42056 , 42088 ] ,
" Husbandry " : [ 42065 , 42067 , 42078 , 42022 , 42007 , 42079 , 42013 , 42028 ,
42036 , 42057 , 42063 , 42053 , 42083 , 42084 , 42091 ]
}
display_data = { }
# Determine display for progressive items
progressive_items = {
" Progressive Tools " : 45013 ,
" Progressive Weapons " : 45012 ,
" Progressive Armor " : 45014 ,
" Progressive Resource Crafting " : 45001
}
progressive_names = {
" Progressive Tools " : [ " Wooden Pickaxe " , " Stone Pickaxe " , " Iron Pickaxe " , " Diamond Pickaxe " ] ,
" Progressive Weapons " : [ " Wooden Sword " , " Stone Sword " , " Iron Sword " , " Diamond Sword " ] ,
" Progressive Armor " : [ " Leather Tunic " , " Iron Chestplate " , " Diamond Chestplate " ] ,
" Progressive Resource Crafting " : [ " Iron Ingot " , " Iron Ingot " , " Block of Iron " ]
}
for item_name , item_id in progressive_items . items ( ) :
level = min ( inventory [ item_id ] , len ( progressive_names [ item_name ] ) - 1 )
display_name = progressive_names [ item_name ] [ level ]
base_name = item_name . split ( maxsplit = 1 ) [ 1 ] . lower ( ) . replace ( ' ' , ' _ ' )
display_data [ base_name + " _url " ] = icons [ display_name ]
# Multi-items
multi_items = {
" 3 Ender Pearls " : 45029 ,
" 8 Netherite Scrap " : 45015
}
for item_name , item_id in multi_items . items ( ) :
base_name = item_name . split ( ) [ - 1 ] . lower ( )
count = inventory [ item_id ]
if count > = 0 :
display_data [ base_name + " _count " ] = count
# Victory condition
game_state = multisave . get ( " client_game_state " , { } ) . get ( ( team , player ) , 0 )
display_data [ ' game_finished ' ] = game_state == 30
# Turn location IDs into advancement tab counts
checked_locations = multisave . get ( " location_checks " , { } ) . get ( ( team , player ) , set ( ) )
lookup_name = lambda id : lookup_any_location_id_to_name [ id ]
location_info = { tab_name : { lookup_name ( id ) : ( id in checked_locations ) for id in tab_locations }
for tab_name , tab_locations in minecraft_location_ids . items ( ) }
checks_done = { tab_name : len ( [ id for id in tab_locations if id in checked_locations ] )
for tab_name , tab_locations in minecraft_location_ids . items ( ) }
checks_done [ ' Total ' ] = len ( checked_locations )
checks_in_area = { tab_name : len ( tab_locations ) for tab_name , tab_locations in minecraft_location_ids . items ( ) }
checks_in_area [ ' Total ' ] = sum ( checks_in_area . values ( ) )
return render_template ( " minecraftTracker.html " ,
inventory = inventory , icons = icons ,
acquired_items = { lookup_any_item_id_to_name [ id ] for id in inventory if
id in lookup_any_item_id_to_name } ,
player = player , team = team , room = room , player_name = playerName ,
checks_done = checks_done , checks_in_area = checks_in_area , location_info = location_info ,
* * display_data )
2021-10-26 13:34:19 +00:00
def __renderOoTTracker ( multisave : Dict [ str , Any ] , room : Room , locations : Dict [ int , Dict [ int , Tuple [ int , int ] ] ] ,
2021-10-22 12:04:13 +00:00
inventory : Counter , team : int , player : int , playerName : str ) - > str :
icons = {
" Fairy Ocarina " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/9/97/OoT_Fairy_Ocarina_Icon.png " ,
" Ocarina of Time " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/4/4e/OoT_Ocarina_of_Time_Icon.png " ,
" Slingshot " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/3/32/OoT_Fairy_Slingshot_Icon.png " ,
" Boomerang " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/d/d5/OoT_Boomerang_Icon.png " ,
" Bottle " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/f/fc/OoT_Bottle_Icon.png " ,
" Rutos Letter " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/2/21/OoT_Letter_Icon.png " ,
" Bombs " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/1/11/OoT_Bomb_Icon.png " ,
" Bombchus " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/3/36/OoT_Bombchu_Icon.png " ,
" Lens of Truth " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/0/05/OoT_Lens_of_Truth_Icon.png " ,
" Bow " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/9/9a/OoT_Fairy_Bow_Icon.png " ,
" Hookshot " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/7/77/OoT_Hookshot_Icon.png " ,
" Longshot " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/a/a4/OoT_Longshot_Icon.png " ,
" Megaton Hammer " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/9/93/OoT_Megaton_Hammer_Icon.png " ,
" Fire Arrows " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/1/1e/OoT_Fire_Arrow_Icon.png " ,
" Ice Arrows " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/3/3c/OoT_Ice_Arrow_Icon.png " ,
" Light Arrows " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/7/76/OoT_Light_Arrow_Icon.png " ,
" Dins Fire " : r " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/d/da/OoT_Din %27s _Fire_Icon.png " ,
" Farores Wind " : r " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/7/7a/OoT_Farore %27s _Wind_Icon.png " ,
" Nayrus Love " : r " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/b/be/OoT_Nayru %27s _Love_Icon.png " ,
" Kokiri Sword " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/5/53/OoT_Kokiri_Sword_Icon.png " ,
" Biggoron Sword " : r " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/2/2e/OoT_Giant %27s _Knife_Icon.png " ,
" Mirror Shield " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/b/b0/OoT_Mirror_Shield_Icon_2.png " ,
" Goron Bracelet " : r " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/b/b7/OoT_Goron %27s _Bracelet_Icon.png " ,
" Silver Gauntlets " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/b/b9/OoT_Silver_Gauntlets_Icon.png " ,
" Golden Gauntlets " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/6/6a/OoT_Golden_Gauntlets_Icon.png " ,
" Goron Tunic " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/1/1c/OoT_Goron_Tunic_Icon.png " ,
" Zora Tunic " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/2/2c/OoT_Zora_Tunic_Icon.png " ,
" Silver Scale " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/4/4e/OoT_Silver_Scale_Icon.png " ,
" Gold Scale " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/9/95/OoT_Golden_Scale_Icon.png " ,
" Iron Boots " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/3/34/OoT_Iron_Boots_Icon.png " ,
" Hover Boots " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/2/22/OoT_Hover_Boots_Icon.png " ,
" Adults Wallet " : r " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/f/f9/OoT_Adult %27s _Wallet_Icon.png " ,
" Giants Wallet " : r " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/8/87/OoT_Giant %27s _Wallet_Icon.png " ,
" Small Magic " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/9/9f/OoT3D_Magic_Jar_Icon.png " ,
" Large Magic " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/3/3e/OoT3D_Large_Magic_Jar_Icon.png " ,
" Gerudo Membership Card " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/4/4e/OoT_Gerudo_Token_Icon.png " ,
" Gold Skulltula Token " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/4/47/OoT_Token_Icon.png " ,
" Triforce Piece " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/0/0b/SS_Triforce_Piece_Icon.png " ,
" Triforce " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/6/68/ALttP_Triforce_Title_Sprite.png " ,
" Zeldas Lullaby " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/2/21/Grey_Note.png " ,
" Eponas Song " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/2/21/Grey_Note.png " ,
" Sarias Song " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/2/21/Grey_Note.png " ,
" Suns Song " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/2/21/Grey_Note.png " ,
" Song of Time " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/2/21/Grey_Note.png " ,
" Song of Storms " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/2/21/Grey_Note.png " ,
" Minuet of Forest " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/e/e4/Green_Note.png " ,
" Bolero of Fire " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/f/f0/Red_Note.png " ,
" Serenade of Water " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/0/0f/Blue_Note.png " ,
" Requiem of Spirit " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/a/a4/Orange_Note.png " ,
" Nocturne of Shadow " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/9/97/Purple_Note.png " ,
" Prelude of Light " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/9/90/Yellow_Note.png " ,
" Small Key " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/e/e5/OoT_Small_Key_Icon.png " ,
" Boss Key " : " https://static.wikia.nocookie.net/zelda_gamepedia_en/images/4/40/OoT_Boss_Key_Icon.png " ,
}
display_data = { }
# Determine display for progressive items
progressive_items = {
" Progressive Hookshot " : 66128 ,
" Progressive Strength Upgrade " : 66129 ,
" Progressive Wallet " : 66133 ,
" Progressive Scale " : 66134 ,
" Magic Meter " : 66138 ,
" Ocarina " : 66139 ,
}
progressive_names = {
" Progressive Hookshot " : [ " Hookshot " , " Hookshot " , " Longshot " ] ,
" Progressive Strength Upgrade " : [ " Goron Bracelet " , " Goron Bracelet " , " Silver Gauntlets " , " Golden Gauntlets " ] ,
" Progressive Wallet " : [ " Adults Wallet " , " Adults Wallet " , " Giants Wallet " , " Giants Wallet " ] ,
" Progressive Scale " : [ " Silver Scale " , " Silver Scale " , " Gold Scale " ] ,
" Magic Meter " : [ " Small Magic " , " Small Magic " , " Large Magic " ] ,
" Ocarina " : [ " Fairy Ocarina " , " Fairy Ocarina " , " Ocarina of Time " ]
}
for item_name , item_id in progressive_items . items ( ) :
level = min ( inventory [ item_id ] , len ( progressive_names [ item_name ] ) - 1 )
display_name = progressive_names [ item_name ] [ level ]
if item_name . startswith ( " Progressive " ) :
base_name = item_name . split ( maxsplit = 1 ) [ 1 ] . lower ( ) . replace ( ' ' , ' _ ' )
else :
base_name = item_name . lower ( ) . replace ( ' ' , ' _ ' )
display_data [ base_name + " _url " ] = icons [ display_name ]
if base_name == " hookshot " :
display_data [ ' hookshot_length ' ] = { 0 : ' ' , 1 : ' H ' , 2 : ' L ' } . get ( level )
if base_name == " wallet " :
display_data [ ' wallet_size ' ] = { 0 : ' 99 ' , 1 : ' 200 ' , 2 : ' 500 ' , 3 : ' 999 ' } . get ( level )
# Determine display for bottles. Show letter if it's obtained, determine bottle count
bottle_ids = [ 66015 , 66020 , 66021 , 66140 , 66141 , 66142 , 66143 , 66144 , 66145 , 66146 , 66147 , 66148 ]
display_data [ ' bottle_count ' ] = min ( sum ( map ( lambda item_id : inventory [ item_id ] , bottle_ids ) ) , 4 )
display_data [ ' bottle_url ' ] = icons [ ' Rutos Letter ' ] if inventory [ 66021 ] > 0 else icons [ ' Bottle ' ]
# Determine bombchu display
display_data [ ' has_bombchus ' ] = any ( map ( lambda item_id : inventory [ item_id ] > 0 , [ 66003 , 66106 , 66107 , 66137 ] ) )
# Multi-items
multi_items = {
" Gold Skulltula Token " : 66091 ,
" Triforce Piece " : 66202 ,
}
for item_name , item_id in multi_items . items ( ) :
base_name = item_name . split ( ) [ - 1 ] . lower ( )
count = inventory [ item_id ]
display_data [ base_name + " _count " ] = inventory [ item_id ]
# Gather dungeon locations
area_id_ranges = {
" Overworld " : ( 67000 , 67280 ) ,
" Deku Tree " : ( 67281 , 67303 ) ,
" Dodongo ' s Cavern " : ( 67304 , 67334 ) ,
" Jabu Jabu ' s Belly " : ( 67335 , 67359 ) ,
" Bottom of the Well " : ( 67360 , 67384 ) ,
" Forest Temple " : ( 67385 , 67420 ) ,
" Fire Temple " : ( 67421 , 67457 ) ,
" Water Temple " : ( 67458 , 67484 ) ,
" Shadow Temple " : ( 67485 , 67532 ) ,
" Spirit Temple " : ( 67533 , 67582 ) ,
" Ice Cavern " : ( 67583 , 67596 ) ,
" Gerudo Training Grounds " : ( 67597 , 67635 ) ,
" Ganon ' s Castle " : ( 67636 , 67673 ) ,
}
def lookup_and_trim ( id , area ) :
full_name = lookup_any_location_id_to_name [ id ]
if id == 67673 :
return full_name [ 13 : ] # Ganons Tower Boss Key Chest
if area != ' Overworld ' :
return full_name [ len ( area ) : ] # trim dungeon name. leaves an extra space that doesn't display, or trims fully for DC/Jabu/GC
return full_name
checked_locations = multisave . get ( " location_checks " , { } ) . get ( ( team , player ) , set ( ) ) . intersection ( set ( locations [ player ] ) )
location_info = { area : { lookup_and_trim ( id , area ) : id in checked_locations for id in range ( min_id , max_id + 1 ) if id in locations [ player ] }
for area , ( min_id , max_id ) in area_id_ranges . items ( ) }
checks_done = { area : len ( list ( filter ( lambda x : x , location_info [ area ] . values ( ) ) ) ) for area in area_id_ranges }
checks_in_area = { area : len ( [ id for id in range ( min_id , max_id + 1 ) if id in locations [ player ] ] )
for area , ( min_id , max_id ) in area_id_ranges . items ( ) }
checks_done [ ' Total ' ] = sum ( checks_done . values ( ) )
checks_in_area [ ' Total ' ] = sum ( checks_in_area . values ( ) )
# Give skulltulas on non-tracked locations
non_tracked_locations = multisave . get ( " location_checks " , { } ) . get ( ( team , player ) , set ( ) ) . difference ( set ( locations [ player ] ) )
for id in non_tracked_locations :
if " GS " in lookup_and_trim ( id , ' ' ) :
display_data [ " token_count " ] + = 1
# Gather small and boss key info
small_key_counts = {
" Forest Temple " : inventory [ 66175 ] ,
" Fire Temple " : inventory [ 66176 ] ,
" Water Temple " : inventory [ 66177 ] ,
" Spirit Temple " : inventory [ 66178 ] ,
" Shadow Temple " : inventory [ 66179 ] ,
" Bottom of the Well " : inventory [ 66180 ] ,
" Gerudo Training Grounds " : inventory [ 66181 ] ,
" Ganon ' s Castle " : inventory [ 66183 ] ,
}
boss_key_counts = {
" Forest Temple " : ' ✔ ' if inventory [ 66149 ] else ' ✕ ' ,
" Fire Temple " : ' ✔ ' if inventory [ 66150 ] else ' ✕ ' ,
" Water Temple " : ' ✔ ' if inventory [ 66151 ] else ' ✕ ' ,
" Spirit Temple " : ' ✔ ' if inventory [ 66152 ] else ' ✕ ' ,
" Shadow Temple " : ' ✔ ' if inventory [ 66153 ] else ' ✕ ' ,
" Ganon ' s Castle " : ' ✔ ' if inventory [ 66154 ] else ' ✕ ' ,
}
# Victory condition
game_state = multisave . get ( " client_game_state " , { } ) . get ( ( team , player ) , 0 )
display_data [ ' game_finished ' ] = game_state == 30
return render_template ( " ootTracker.html " ,
inventory = inventory , player = player , team = team , room = room , player_name = playerName ,
icons = icons , acquired_items = { lookup_any_item_id_to_name [ id ] for id in inventory } ,
checks_done = checks_done , checks_in_area = checks_in_area , location_info = location_info ,
small_key_counts = small_key_counts , boss_key_counts = boss_key_counts ,
* * display_data )
2021-10-26 13:34:19 +00:00
def __renderTimespinnerTracker ( multisave : Dict [ str , Any ] , room : Room , locations : Dict [ int , Dict [ int , Tuple [ int , int ] ] ] ,
2021-10-23 22:35:41 +00:00
inventory : Counter , team : int , player : int , playerName : str ) - > str :
icons = {
" Timespinner Wheel " : " https://timespinnerwiki.com/mediawiki/images/7/76/Timespinner_Wheel.png " ,
" Timespinner Spindle " : " https://timespinnerwiki.com/mediawiki/images/1/1a/Timespinner_Spindle.png " ,
" Timespinner Gear 1 " : " https://timespinnerwiki.com/mediawiki/images/3/3c/Timespinner_Gear_1.png " ,
" Timespinner Gear 2 " : " https://timespinnerwiki.com/mediawiki/images/e/e9/Timespinner_Gear_2.png " ,
" Timespinner Gear 3 " : " https://timespinnerwiki.com/mediawiki/images/2/22/Timespinner_Gear_3.png " ,
" Talaria Attachment " : " https://timespinnerwiki.com/mediawiki/images/6/61/Talaria_Attachment.png " ,
" Succubus Hairpin " : " https://timespinnerwiki.com/mediawiki/images/4/49/Succubus_Hairpin.png " ,
" Lightwall " : " https://timespinnerwiki.com/mediawiki/images/0/03/Lightwall.png " ,
" Celestial Sash " : " https://timespinnerwiki.com/mediawiki/images/f/f1/Celestial_Sash.png " ,
" Twin Pyramid Key " : " https://timespinnerwiki.com/mediawiki/images/4/49/Twin_Pyramid_Key.png " ,
" Security Keycard D " : " https://timespinnerwiki.com/mediawiki/images/1/1b/Security_Keycard_D.png " ,
" Security Keycard C " : " https://timespinnerwiki.com/mediawiki/images/e/e5/Security_Keycard_C.png " ,
" Security Keycard B " : " https://timespinnerwiki.com/mediawiki/images/f/f6/Security_Keycard_B.png " ,
" Security Keycard A " : " https://timespinnerwiki.com/mediawiki/images/b/b9/Security_Keycard_A.png " ,
2021-10-27 08:20:18 +00:00
" Library Keycard V " : " https://timespinnerwiki.com/mediawiki/images/5/50/Library_Keycard_V.png " ,
2021-10-23 22:35:41 +00:00
" Tablet " : " https://timespinnerwiki.com/mediawiki/images/a/a0/Tablet.png " ,
" Elevator Keycard " : " https://timespinnerwiki.com/mediawiki/images/5/55/Elevator_Keycard.png " ,
" Oculus Ring " : " https://timespinnerwiki.com/mediawiki/images/8/8d/Oculus_Ring.png " ,
" Water Mask " : " https://timespinnerwiki.com/mediawiki/images/0/04/Water_Mask.png " ,
" Gas Mask " : " https://timespinnerwiki.com/mediawiki/images/2/2e/Gas_Mask.png " ,
" Djinn Inferno " : " https://timespinnerwiki.com/mediawiki/images/f/f6/Djinn_Inferno.png " ,
" Pyro Ring " : " https://timespinnerwiki.com/mediawiki/images/2/2c/Pyro_Ring.png " ,
" Infernal Flames " : " https://timespinnerwiki.com/mediawiki/images/1/1f/Infernal_Flames.png " ,
" Fire Orb " : " https://timespinnerwiki.com/mediawiki/images/3/3e/Fire_Orb.png " ,
" Royal Ring " : " https://timespinnerwiki.com/mediawiki/images/f/f3/Royal_Ring.png " ,
" Plasma Geyser " : " https://timespinnerwiki.com/mediawiki/images/1/12/Plasma_Geyser.png " ,
" Plasma Orb " : " https://timespinnerwiki.com/mediawiki/images/4/44/Plasma_Orb.png " ,
}
timespinner_location_ids = {
" Present " : [
1337000 , 1337001 , 1337002 , 1337003 , 1337004 , 1337005 , 1337006 , 1337007 , 1337008 , 1337009 ,
1337010 , 1337011 , 1337012 , 1337013 , 1337014 , 1337015 , 1337016 , 1337017 , 1337018 , 1337019 ,
1337020 , 1337021 , 1337022 , 1337023 , 1337024 , 1337025 , 1337026 , 1337027 , 1337028 , 1337029 ,
1337030 , 1337031 , 1337032 , 1337033 , 1337034 , 1337035 , 1337036 , 1337037 , 1337038 , 1337039 ,
1337040 , 1337041 , 1337042 , 1337043 , 1337044 , 1337045 , 1337046 , 1337047 , 1337048 , 1337049 ,
1337050 , 1337051 , 1337052 , 1337053 , 1337054 , 1337055 , 1337056 , 1337057 , 1337058 , 1337059 ,
1337060 , 1337061 , 1337062 , 1337063 , 1337064 , 1337065 , 1337066 , 1337067 , 1337068 , 1337069 ,
1337070 , 1337071 , 1337072 , 1337073 , 1337074 , 1337075 , 1337076 , 1337077 , 1337078 , 1337079 ,
1337080 , 1337081 , 1337082 , 1337083 , 1337084 , 1337085 , 1337156 , 1337157 , 1337159 ,
1337160 , 1337161 , 1337162 , 1337163 , 1337164 , 1337165 , 1337166 , 1337167 , 1337168 , 1337169 ,
1337170 ] ,
" Past " : [
1337086 , 1337087 , 1337088 , 1337089 ,
1337090 , 1337091 , 1337092 , 1337093 , 1337094 , 1337095 , 1337096 , 1337097 , 1337098 , 1337099 ,
1337100 , 1337101 , 1337102 , 1337103 , 1337104 , 1337105 , 1337106 , 1337107 , 1337108 , 1337109 ,
1337110 , 1337111 , 1337112 , 1337113 , 1337114 , 1337115 , 1337116 , 1337117 , 1337118 , 1337119 ,
1337120 , 1337121 , 1337122 , 1337123 , 1337124 , 1337125 , 1337126 , 1337127 , 1337128 , 1337129 ,
1337130 , 1337131 , 1337132 , 1337133 , 1337134 , 1337135 , 1337136 , 1337137 , 1337138 , 1337139 ,
1337140 , 1337141 , 1337142 , 1337143 , 1337144 , 1337145 , 1337146 , 1337147 , 1337148 , 1337149 ,
1337150 , 1337151 , 1337152 , 1337153 , 1337154 , 1337155 ] ,
" Ancient Pyramid " : [ 1337246 , 1337247 , 1337248 , 1337249 ]
}
display_data = { }
# Victory condition
game_state = multisave . get ( " client_game_state " , { } ) . get ( ( team , player ) , 0 )
display_data [ ' game_finished ' ] = game_state == 30
# Turn location IDs into advancement tab counts
checked_locations = multisave . get ( " location_checks " , { } ) . get ( ( team , player ) , set ( ) )
lookup_name = lambda id : lookup_any_location_id_to_name [ id ]
location_info = { tab_name : { lookup_name ( id ) : ( id in checked_locations ) for id in tab_locations }
for tab_name , tab_locations in timespinner_location_ids . items ( ) }
checks_done = { tab_name : len ( [ id for id in tab_locations if id in checked_locations ] )
for tab_name , tab_locations in timespinner_location_ids . items ( ) }
checks_done [ ' Total ' ] = len ( checked_locations )
checks_in_area = { tab_name : len ( tab_locations ) for tab_name , tab_locations in timespinner_location_ids . items ( ) }
checks_in_area [ ' Total ' ] = sum ( checks_in_area . values ( ) )
return render_template ( " timespinnerTracker.html " ,
inventory = inventory , icons = icons ,
acquired_items = { lookup_any_item_id_to_name [ id ] for id in inventory if
id in lookup_any_item_id_to_name } ,
player = player , team = team , room = room , player_name = playerName ,
checks_done = checks_done , checks_in_area = checks_in_area , location_info = location_info ,
* * display_data )
2021-10-22 12:04:13 +00:00
2021-10-26 13:34:19 +00:00
def __renderGenericTracker ( multisave : Dict [ str , Any ] , room : Room , locations : Dict [ int , Dict [ int , Tuple [ int , int ] ] ] ,
2021-10-22 12:04:13 +00:00
inventory : Counter , team : int , player : int , playerName : str ) - > str :
checked_locations = multisave . get ( " location_checks " , { } ) . get ( ( team , player ) , set ( ) )
player_received_items = { }
for order_index , networkItem in enumerate (
multisave . get ( ' received_items ' , { } ) . get ( ( team , player ) , [ ] ) ,
start = 1
) :
player_received_items [ networkItem . item ] = order_index
return render_template ( " genericTracker.html " ,
inventory = inventory ,
player = player , team = team , room = room , player_name = playerName ,
checked_locations = checked_locations ,
not_checked_locations = set ( locations [ player ] ) - checked_locations ,
received_items = player_received_items )
2021-02-21 05:38:53 +00:00
2021-09-23 11:52:32 +00:00
2020-07-27 03:04:49 +00:00
@app.route ( ' /tracker/<suuid:tracker> ' )
2021-05-13 19:57:11 +00:00
@cache.memoize ( timeout = 60 ) # multisave is currently created at most every minute
2020-12-01 02:15:47 +00:00
def getTracker ( tracker : UUID ) :
2021-10-22 12:04:13 +00:00
room : Room = Room . get ( tracker = tracker )
2020-06-22 18:25:24 +00:00
if not room :
abort ( 404 )
2021-10-22 12:04:13 +00:00
locations , names , use_door_tracker , seed_checks_in_area , player_location_to_area , \
precollected_items , games = get_static_room_data ( room )
2020-06-26 17:29:33 +00:00
inventory = { teamnumber : { playernumber : collections . Counter ( ) for playernumber in range ( 1 , len ( team ) + 1 ) }
2020-06-28 07:28:13 +00:00
for teamnumber , team in enumerate ( names ) }
2020-06-26 17:29:33 +00:00
checks_done = { teamnumber : { playernumber : { loc_name : 0 for loc_name in default_locations }
for playernumber in range ( 1 , len ( team ) + 1 ) }
2020-06-28 07:28:13 +00:00
for teamnumber , team in enumerate ( names ) }
2021-04-04 01:18:19 +00:00
2020-07-27 17:30:31 +00:00
hints = { team : set ( ) for team in range ( len ( names ) ) }
2021-04-04 01:18:19 +00:00
if room . multisave :
multisave = restricted_loads ( room . multisave )
else :
multisave = { }
if " hints " in multisave :
2021-05-13 19:57:11 +00:00
for ( team , slot ) , slot_hints in multisave [ " hints " ] . items ( ) :
hints [ team ] | = set ( slot_hints )
for ( team , player ) , locations_checked in multisave . get ( " location_checks " , { } ) . items ( ) :
player_locations = locations [ player ]
2020-06-26 17:29:33 +00:00
if precollected_items :
2021-05-09 19:22:21 +00:00
precollected = precollected_items [ player ]
2020-06-26 17:29:33 +00:00
for item_id in precollected :
attribute_item ( inventory , team , player , item_id )
for location in locations_checked :
2021-05-13 19:57:11 +00:00
if location not in player_locations or location not in player_location_to_area [ player ] :
2020-10-27 07:47:59 +00:00
continue
2021-05-13 19:57:11 +00:00
item , recipient = player_locations [ location ]
2020-06-26 17:29:33 +00:00
attribute_item ( inventory , team , recipient , item )
2020-10-29 22:18:59 +00:00
checks_done [ team ] [ player ] [ player_location_to_area [ player ] [ location ] ] + = 1
2020-06-26 17:29:33 +00:00
checks_done [ team ] [ player ] [ " Total " ] + = 1
2021-05-13 19:57:11 +00:00
for ( team , player ) , game_state in multisave . get ( " client_game_state " , { } ) . items ( ) :
if game_state == 30 :
2020-06-26 17:29:33 +00:00
inventory [ team ] [ player ] [ 106 ] = 1 # Triforce
2021-10-22 12:04:13 +00:00
player_big_key_locations = { playernumber : set ( ) for playernumber in range ( 1 , len ( names [ 0 ] ) + 1 ) }
player_small_key_locations = { playernumber : set ( ) for playernumber in range ( 1 , len ( names [ 0 ] ) + 1 ) }
for loc_data in locations . values ( ) :
for item_id , item_player in loc_data . values ( ) :
if item_id in ids_big_key :
player_big_key_locations [ item_player ] . add ( ids_big_key [ item_id ] )
elif item_id in ids_small_key :
player_small_key_locations [ item_player ] . add ( ids_small_key [ item_id ] )
2021-03-07 22:30:21 +00:00
group_big_key_locations = set ( )
group_key_locations = set ( )
for player in range ( 1 , len ( names [ 0 ] ) + 1 ) :
group_key_locations | = player_small_key_locations [ player ]
group_big_key_locations | = player_big_key_locations [ player ]
2020-06-26 17:29:33 +00:00
activity_timers = { }
now = datetime . datetime . utcnow ( )
2021-04-04 01:18:19 +00:00
for ( team , player ) , timestamp in multisave . get ( " client_activity_timers " , [ ] ) :
2020-06-26 17:29:33 +00:00
activity_timers [ team , player ] = now - datetime . datetime . utcfromtimestamp ( timestamp )
player_names = { }
2020-06-28 07:28:13 +00:00
for team , names in enumerate ( names ) :
2020-06-26 17:29:33 +00:00
for player , name in enumerate ( names , 1 ) :
player_names [ ( team , player ) ] = name
2020-07-29 19:35:31 +00:00
long_player_names = player_names . copy ( )
2021-06-05 01:54:16 +00:00
for ( team , player ) , alias in multisave . get ( " name_aliases " , { } ) . items ( ) :
2020-06-27 11:52:03 +00:00
player_names [ ( team , player ) ] = alias
2020-07-29 19:35:31 +00:00
long_player_names [ ( team , player ) ] = f " { alias } ( { long_player_names [ ( team , player ) ] } ) "
2020-06-27 11:52:03 +00:00
video = { }
2021-04-04 01:18:19 +00:00
for ( team , player ) , data in multisave . get ( " video " , [ ] ) :
2020-06-27 11:52:03 +00:00
video [ ( team , player ) ] = data
2020-06-26 17:29:33 +00:00
return render_template ( " tracker.html " , inventory = inventory , get_item_name_from_id = get_item_name_from_id ,
lookup_id_to_name = Items . lookup_id_to_name , player_names = player_names ,
2021-10-22 17:06:08 +00:00
tracking_names = tracking_names , tracking_ids = tracking_ids , room = room , icons = alttp_icons ,
2020-06-26 17:29:33 +00:00
multi_items = multi_items , checks_done = checks_done , ordered_areas = ordered_areas ,
2020-10-27 07:47:59 +00:00
checks_in_area = seed_checks_in_area , activity_timers = activity_timers ,
2021-03-07 22:30:21 +00:00
key_locations = group_key_locations , small_key_ids = small_key_ids , big_key_ids = big_key_ids ,
video = video , big_key_locations = group_big_key_locations ,
2021-09-23 11:52:32 +00:00
hints = hints , long_player_names = long_player_names )