@@ -185,169 +185,166 @@ def main(args, seed=None):
185
185
logger .info (f'Beginning output...' )
186
186
outfilebase = 'AP_' + world .seed_name
187
187
188
- pool = concurrent .futures .ThreadPoolExecutor ()
189
-
190
188
output = tempfile .TemporaryDirectory ()
191
189
with output as temp_dir :
192
- check_accessibility_task = pool .submit (world .fulfills_accessibility )
193
-
194
- output_file_futures = []
190
+ with concurrent .futures .ThreadPoolExecutor () as pool :
191
+ check_accessibility_task = pool .submit (world .fulfills_accessibility )
192
+
193
+ output_file_futures = []
194
+
195
+ for player in world .player_ids :
196
+ # skip starting a thread for methods that say "pass".
197
+ if AutoWorld .World .generate_output .__code__ is not world .worlds [player ].generate_output .__code__ :
198
+ output_file_futures .append (pool .submit (AutoWorld .call_single , world , "generate_output" , player , temp_dir ))
199
+ output_file_futures .append (pool .submit (AutoWorld .call_stage , world , "generate_output" , temp_dir ))
200
+
201
+ def get_entrance_to_region (region : Region ):
202
+ for entrance in region .entrances :
203
+ if entrance .parent_region .type in (RegionType .DarkWorld , RegionType .LightWorld , RegionType .Generic ):
204
+ return entrance
205
+ for entrance in region .entrances : # BFS might be better here, trying DFS for now.
206
+ return get_entrance_to_region (entrance .parent_region )
207
+
208
+ # collect ER hint info
209
+ er_hint_data = {player : {} for player in world .get_game_players ("A Link to the Past" ) if
210
+ world .shuffle [player ] != "vanilla" or world .retro [player ]}
211
+
212
+ for region in world .regions :
213
+ if region .player in er_hint_data and region .locations :
214
+ main_entrance = get_entrance_to_region (region )
215
+ for location in region .locations :
216
+ if type (location .address ) == int : # skips events and crystals
217
+ if lookup_vanilla_location_to_entrance [location .address ] != main_entrance .name :
218
+ er_hint_data [region .player ][location .address ] = main_entrance .name
219
+
220
+ ordered_areas = ('Light World' , 'Dark World' , 'Hyrule Castle' , 'Agahnims Tower' , 'Eastern Palace' , 'Desert Palace' ,
221
+ 'Tower of Hera' , 'Palace of Darkness' , 'Swamp Palace' , 'Skull Woods' , 'Thieves Town' , 'Ice Palace' ,
222
+ 'Misery Mire' , 'Turtle Rock' , 'Ganons Tower' , "Total" )
223
+
224
+ checks_in_area = {player : {area : list () for area in ordered_areas }
225
+ for player in range (1 , world .players + 1 )}
226
+
227
+ for player in range (1 , world .players + 1 ):
228
+ checks_in_area [player ]["Total" ] = 0
195
229
196
- for player in world .player_ids :
197
- # skip starting a thread for methods that say "pass".
198
- if AutoWorld .World .generate_output .__code__ is not world .worlds [player ].generate_output .__code__ :
199
- output_file_futures .append (pool .submit (AutoWorld .call_single , world , "generate_output" , player , temp_dir ))
200
- output_file_futures .append (pool .submit (AutoWorld .call_stage , world , "generate_output" , temp_dir ))
201
-
202
- def get_entrance_to_region (region : Region ):
203
- for entrance in region .entrances :
204
- if entrance .parent_region .type in (RegionType .DarkWorld , RegionType .LightWorld , RegionType .Generic ):
205
- return entrance
206
- for entrance in region .entrances : # BFS might be better here, trying DFS for now.
207
- return get_entrance_to_region (entrance .parent_region )
208
-
209
- # collect ER hint info
210
- er_hint_data = {player : {} for player in world .get_game_players ("A Link to the Past" ) if
211
- world .shuffle [player ] != "vanilla" or world .retro [player ]}
212
-
213
- for region in world .regions :
214
- if region .player in er_hint_data and region .locations :
215
- main_entrance = get_entrance_to_region (region )
216
- for location in region .locations :
217
- if type (location .address ) == int : # skips events and crystals
218
- if lookup_vanilla_location_to_entrance [location .address ] != main_entrance .name :
219
- er_hint_data [region .player ][location .address ] = main_entrance .name
220
-
221
- ordered_areas = ('Light World' , 'Dark World' , 'Hyrule Castle' , 'Agahnims Tower' , 'Eastern Palace' , 'Desert Palace' ,
222
- 'Tower of Hera' , 'Palace of Darkness' , 'Swamp Palace' , 'Skull Woods' , 'Thieves Town' , 'Ice Palace' ,
223
- 'Misery Mire' , 'Turtle Rock' , 'Ganons Tower' , "Total" )
224
-
225
- checks_in_area = {player : {area : list () for area in ordered_areas }
226
- for player in range (1 , world .players + 1 )}
227
-
228
- for player in range (1 , world .players + 1 ):
229
- checks_in_area [player ]["Total" ] = 0
230
-
231
- for location in world .get_filled_locations ():
232
- if type (location .address ) is int :
233
- main_entrance = get_entrance_to_region (location .parent_region )
234
- if location .game != "A Link to the Past" :
235
- checks_in_area [location .player ]["Light World" ].append (location .address )
236
- elif location .parent_region .dungeon :
237
- dungeonname = {'Inverted Agahnims Tower' : 'Agahnims Tower' ,
238
- 'Inverted Ganons Tower' : 'Ganons Tower' } \
239
- .get (location .parent_region .dungeon .name , location .parent_region .dungeon .name )
240
- checks_in_area [location .player ][dungeonname ].append (location .address )
241
- elif main_entrance .parent_region .type == RegionType .LightWorld :
242
- checks_in_area [location .player ]["Light World" ].append (location .address )
243
- elif main_entrance .parent_region .type == RegionType .DarkWorld :
244
- checks_in_area [location .player ]["Dark World" ].append (location .address )
245
- checks_in_area [location .player ]["Total" ] += 1
246
-
247
- oldmancaves = []
248
- takeanyregions = ["Old Man Sword Cave" , "Take-Any #1" , "Take-Any #2" , "Take-Any #3" , "Take-Any #4" ]
249
- for index , take_any in enumerate (takeanyregions ):
250
- for region in [world .get_region (take_any , player ) for player in range (1 , world .players + 1 ) if
251
- world .retro [player ]]:
252
- item = world .create_item (region .shop .inventory [(0 if take_any == "Old Man Sword Cave" else 1 )]['item' ],
253
- region .player )
254
- player = region .player
255
- location_id = SHOP_ID_START + total_shop_slots + index
256
-
257
- main_entrance = get_entrance_to_region (region )
258
- if main_entrance .parent_region .type == RegionType .LightWorld :
259
- checks_in_area [player ]["Light World" ].append (location_id )
260
- else :
261
- checks_in_area [player ]["Dark World" ].append (location_id )
262
- checks_in_area [player ]["Total" ] += 1
263
-
264
- er_hint_data [player ][location_id ] = main_entrance .name
265
- oldmancaves .append (((location_id , player ), (item .code , player )))
266
-
267
- FillDisabledShopSlots (world )
268
-
269
- def write_multidata ():
270
- import NetUtils
271
- slot_data = {}
272
- client_versions = {}
273
- minimum_versions = {"server" : (0 , 1 , 1 ), "clients" : client_versions }
274
- games = {}
275
- for slot in world .player_ids :
276
- client_versions [slot ] = world .worlds [slot ].get_required_client_version ()
277
- games [slot ] = world .game [slot ]
278
- precollected_items = {player : [] for player in range (1 , world .players + 1 )}
279
- for item in world .precollected_items :
280
- precollected_items [item .player ].append (item .code )
281
- precollected_hints = {player : set () for player in range (1 , world .players + 1 )}
282
- # for now special case Factorio tech_tree_information
283
- sending_visible_players = set ()
284
- for player in world .get_game_players ("Factorio" ):
285
- if world .tech_tree_information [player ].value == 2 :
286
- sending_visible_players .add (player )
287
-
288
- for slot in world .player_ids :
289
- slot_data [slot ] = world .worlds [slot ].fill_slot_data ()
290
-
291
- locations_data : Dict [int , Dict [int , Tuple [int , int ]]] = {player : {} for player in world .player_ids }
292
230
for location in world .get_filled_locations ():
293
- if type (location .address ) == int :
294
- # item code None should be event, location.address should then also be None
295
- assert location .item .code is not None
296
- locations_data [location .player ][location .address ] = location .item .code , location .item .player
297
- if location .player in sending_visible_players and location .item .player != location .player :
298
- hint = NetUtils .Hint (location .item .player , location .player , location .address ,
299
- location .item .code , False )
300
- precollected_hints [location .player ].add (hint )
301
- precollected_hints [location .item .player ].add (hint )
302
- elif location .item .name in args .start_hints [location .item .player ]:
303
- hint = NetUtils .Hint (location .item .player , location .player , location .address ,
304
- location .item .code , False ,
305
- er_hint_data .get (location .player , {}).get (location .address , "" ))
306
- precollected_hints [location .player ].add (hint )
307
- precollected_hints [location .item .player ].add (hint )
308
-
309
- multidata = {
310
- "slot_data" : slot_data ,
311
- "games" : games ,
312
- "names" : [[name for player , name in sorted (world .player_name .items ())]],
313
- "connect_names" : {name : (0 , player ) for player , name in world .player_name .items ()},
314
- "remote_items" : {player for player in world .player_ids if
315
- world .worlds [player ].remote_items },
316
- "locations" : locations_data ,
317
- "checks_in_area" : checks_in_area ,
318
- "server_options" : get_options ()["server_options" ],
319
- "er_hint_data" : er_hint_data ,
320
- "precollected_items" : precollected_items ,
321
- "precollected_hints" : precollected_hints ,
322
- "version" : tuple (version_tuple ),
323
- "tags" : ["AP" ],
324
- "minimum_versions" : minimum_versions ,
325
- "seed_name" : world .seed_name
326
- }
327
- AutoWorld .call_all (world , "modify_multidata" , multidata )
328
-
329
- multidata = zlib .compress (pickle .dumps (multidata ), 9 )
330
-
331
- with open (os .path .join (temp_dir , f'{ outfilebase } .archipelago' ), 'wb' ) as f :
332
- f .write (bytes ([1 ])) # version of format
333
- f .write (multidata )
334
-
335
- multidata_task = pool .submit (write_multidata )
336
- if not check_accessibility_task .result ():
337
- if not world .can_beat_game ():
338
- raise Exception ("Game appears as unbeatable. Aborting." )
339
- else :
340
- logger .warning ("Location Accessibility requirements not fulfilled." )
341
-
342
- # retrieve exceptions via .result() if they occured.
343
- if multidata_task :
344
- multidata_task .result ()
345
- for i , future in enumerate (concurrent .futures .as_completed (output_file_futures )):
346
- if i % 10 == 0 :
347
- logger .info (f'Generating output files ({ i } /{ len (output_file_futures )} ).' )
348
- future .result ()
349
-
350
- pool .shutdown () # wait for all queued tasks to complete
231
+ if type (location .address ) is int :
232
+ main_entrance = get_entrance_to_region (location .parent_region )
233
+ if location .game != "A Link to the Past" :
234
+ checks_in_area [location .player ]["Light World" ].append (location .address )
235
+ elif location .parent_region .dungeon :
236
+ dungeonname = {'Inverted Agahnims Tower' : 'Agahnims Tower' ,
237
+ 'Inverted Ganons Tower' : 'Ganons Tower' } \
238
+ .get (location .parent_region .dungeon .name , location .parent_region .dungeon .name )
239
+ checks_in_area [location .player ][dungeonname ].append (location .address )
240
+ elif main_entrance .parent_region .type == RegionType .LightWorld :
241
+ checks_in_area [location .player ]["Light World" ].append (location .address )
242
+ elif main_entrance .parent_region .type == RegionType .DarkWorld :
243
+ checks_in_area [location .player ]["Dark World" ].append (location .address )
244
+ checks_in_area [location .player ]["Total" ] += 1
245
+
246
+ oldmancaves = []
247
+ takeanyregions = ["Old Man Sword Cave" , "Take-Any #1" , "Take-Any #2" , "Take-Any #3" , "Take-Any #4" ]
248
+ for index , take_any in enumerate (takeanyregions ):
249
+ for region in [world .get_region (take_any , player ) for player in range (1 , world .players + 1 ) if
250
+ world .retro [player ]]:
251
+ item = world .create_item (region .shop .inventory [(0 if take_any == "Old Man Sword Cave" else 1 )]['item' ],
252
+ region .player )
253
+ player = region .player
254
+ location_id = SHOP_ID_START + total_shop_slots + index
255
+
256
+ main_entrance = get_entrance_to_region (region )
257
+ if main_entrance .parent_region .type == RegionType .LightWorld :
258
+ checks_in_area [player ]["Light World" ].append (location_id )
259
+ else :
260
+ checks_in_area [player ]["Dark World" ].append (location_id )
261
+ checks_in_area [player ]["Total" ] += 1
262
+
263
+ er_hint_data [player ][location_id ] = main_entrance .name
264
+ oldmancaves .append (((location_id , player ), (item .code , player )))
265
+
266
+ FillDisabledShopSlots (world )
267
+
268
+ def write_multidata ():
269
+ import NetUtils
270
+ slot_data = {}
271
+ client_versions = {}
272
+ minimum_versions = {"server" : (0 , 1 , 1 ), "clients" : client_versions }
273
+ games = {}
274
+ for slot in world .player_ids :
275
+ client_versions [slot ] = world .worlds [slot ].get_required_client_version ()
276
+ games [slot ] = world .game [slot ]
277
+ precollected_items = {player : [] for player in range (1 , world .players + 1 )}
278
+ for item in world .precollected_items :
279
+ precollected_items [item .player ].append (item .code )
280
+ precollected_hints = {player : set () for player in range (1 , world .players + 1 )}
281
+ # for now special case Factorio tech_tree_information
282
+ sending_visible_players = set ()
283
+ for player in world .get_game_players ("Factorio" ):
284
+ if world .tech_tree_information [player ].value == 2 :
285
+ sending_visible_players .add (player )
286
+
287
+ for slot in world .player_ids :
288
+ slot_data [slot ] = world .worlds [slot ].fill_slot_data ()
289
+
290
+ locations_data : Dict [int , Dict [int , Tuple [int , int ]]] = {player : {} for player in world .player_ids }
291
+ for location in world .get_filled_locations ():
292
+ if type (location .address ) == int :
293
+ # item code None should be event, location.address should then also be None
294
+ assert location .item .code is not None
295
+ locations_data [location .player ][location .address ] = location .item .code , location .item .player
296
+ if location .player in sending_visible_players and location .item .player != location .player :
297
+ hint = NetUtils .Hint (location .item .player , location .player , location .address ,
298
+ location .item .code , False )
299
+ precollected_hints [location .player ].add (hint )
300
+ precollected_hints [location .item .player ].add (hint )
301
+ elif location .item .name in args .start_hints [location .item .player ]:
302
+ hint = NetUtils .Hint (location .item .player , location .player , location .address ,
303
+ location .item .code , False ,
304
+ er_hint_data .get (location .player , {}).get (location .address , "" ))
305
+ precollected_hints [location .player ].add (hint )
306
+ precollected_hints [location .item .player ].add (hint )
307
+
308
+ multidata = {
309
+ "slot_data" : slot_data ,
310
+ "games" : games ,
311
+ "names" : [[name for player , name in sorted (world .player_name .items ())]],
312
+ "connect_names" : {name : (0 , player ) for player , name in world .player_name .items ()},
313
+ "remote_items" : {player for player in world .player_ids if
314
+ world .worlds [player ].remote_items },
315
+ "locations" : locations_data ,
316
+ "checks_in_area" : checks_in_area ,
317
+ "server_options" : get_options ()["server_options" ],
318
+ "er_hint_data" : er_hint_data ,
319
+ "precollected_items" : precollected_items ,
320
+ "precollected_hints" : precollected_hints ,
321
+ "version" : tuple (version_tuple ),
322
+ "tags" : ["AP" ],
323
+ "minimum_versions" : minimum_versions ,
324
+ "seed_name" : world .seed_name
325
+ }
326
+ AutoWorld .call_all (world , "modify_multidata" , multidata )
327
+
328
+ multidata = zlib .compress (pickle .dumps (multidata ), 9 )
329
+
330
+ with open (os .path .join (temp_dir , f'{ outfilebase } .archipelago' ), 'wb' ) as f :
331
+ f .write (bytes ([1 ])) # version of format
332
+ f .write (multidata )
333
+
334
+ multidata_task = pool .submit (write_multidata )
335
+ if not check_accessibility_task .result ():
336
+ if not world .can_beat_game ():
337
+ raise Exception ("Game appears as unbeatable. Aborting." )
338
+ else :
339
+ logger .warning ("Location Accessibility requirements not fulfilled." )
340
+
341
+ # retrieve exceptions via .result() if they occured.
342
+ if multidata_task :
343
+ multidata_task .result ()
344
+ for i , future in enumerate (concurrent .futures .as_completed (output_file_futures )):
345
+ if i % 10 == 0 :
346
+ logger .info (f'Generating output files ({ i } /{ len (output_file_futures )} ).' )
347
+ future .result ()
351
348
352
349
if not args .skip_playthrough :
353
350
logger .info ('Calculating playthrough.' )
0 commit comments