1import random 2 3import freeorion as fo 4 5import universe_statistics 6from names import get_name_list, random_name 7from options import (HS_ACCEPTABLE_PLANET_SIZES, HS_ACCEPTABLE_PLANET_TYPES, HS_MAX_JUMP_DISTANCE_LIMIT, 8 HS_MIN_DISTANCE_PRIORITY_LIMIT, HS_MIN_PLANETS_IN_VICINITY_PER_SYSTEM, 9 HS_MIN_PLANETS_IN_VICINITY_TOTAL, HS_MIN_SYSTEMS_IN_VICINITY, HS_VICINITY_RANGE) 10from planets import calc_planet_size, calc_planet_type, planet_sizes_real, planet_types_real 11from starsystems import pick_star_type, star_types_real 12from util import report_error, unique_product 13 14 15def get_empire_name_generator(): 16 """ 17 String generator, return random empire name from string list, 18 if string list is empty generate random name. 19 """ 20 empire_names = get_name_list("EMPIRE_NAMES") 21 random.shuffle(empire_names) 22 while True: 23 if empire_names: 24 yield empire_names.pop() 25 else: 26 yield random_name(5) 27 28 29# generate names for empires, use next(empire_name_generator) to get next name. 30empire_name_generator = get_empire_name_generator() 31 32 33def get_starting_species_pool(): 34 """ 35 Empire species pool generator, return random empire species and ensure somewhat even distribution 36 """ 37 # fill the initial pool with two sets of all playable species 38 # this way we have somewhat, but not absolutely strict even distribution of starting species at least when there 39 # is only a few number of players (some species can occur twice at max while others not at all) 40 pool = fo.get_playable_species() * 2 41 42 # randomize order in initial pool so we don't get the same species all the time 43 random.shuffle(pool) 44 # generator loop 45 while True: 46 # if our pool is exhausted (because we have more players than species instances in our initial pool) 47 # refill the pool with one set of all playable species 48 if not pool: 49 pool = fo.get_playable_species() 50 # again, randomize order in refilled pool so we don't get the same species all the time 51 random.shuffle(pool) 52 # pick and return next species, and remove it from our pool 53 yield pool.pop() 54 55 56# generates starting species for empires, use next(starting_species_pool) to get next species 57starting_species_pool = get_starting_species_pool() 58 59 60def count_planets_in_systems(systems, planet_types_filter=HS_ACCEPTABLE_PLANET_TYPES): 61 """ 62 Return the total number of planets in the specified group of systems, 63 only count the planet types specified in planet_types_filter. 64 """ 65 num_planets = 0 66 for system in systems: 67 num_planets += len([p for p in fo.sys_get_planets(system) if fo.planet_get_type(p) in planet_types_filter]) 68 return num_planets 69 70 71def calculate_home_system_merit(system): 72 """Calculate the system's merit as the number of planets within HS_VICINTIY_RANGE.""" 73 return count_planets_in_systems(fo.systems_within_jumps_unordered(HS_VICINITY_RANGE, [system])) 74 75 76def min_planets_in_vicinity_limit(num_systems): 77 """ 78 Calculates the minimum planet limit for the specified number of systems. 79 This limit is the lower of HS_MIN_PLANETS_IN_VICINITY_TOTAL or HS_MIN_PLANETS_IN_VICINITY_PER_SYSTEM 80 planets per system. 81 """ 82 return min(HS_MIN_PLANETS_IN_VICINITY_TOTAL, num_systems * HS_MIN_PLANETS_IN_VICINITY_PER_SYSTEM) 83 84 85class HomeSystemFinder: 86 """Finds a set of home systems with a least ''num_home_systems'' systems.""" 87 def __init__(self, _num_home_systems): 88 # cache of sytem merits 89 self.system_merit = {} 90 self.num_home_systems = _num_home_systems 91 92 def find_home_systems_for_min_jump_distance(self, systems_pool, min_jumps): 93 """ 94 Return a good list of home systems or an empty list if there are fewer than num_home_systems in the pool. 95 96 A good list of home systems are at least the specified minimum number of jumps apart, 97 with the best minimum system merit of all such lists picked randomly from the ''systems_pool''. 98 99 Algorithm: 100 Make several attempts to find systems that match the condition 101 of being at least min_jumps apart. 102 Use the minimum merit of the best num_home_system systems found 103 to compare the candidate with the current best set of systems. 104 On each attempt use the minimum merit of the current best set of home 105 systems to truncate the pool of candidates. 106 """ 107 108 # precalculate the system merits 109 for system in systems_pool: 110 if system not in self.system_merit: 111 self.system_merit[system] = calculate_home_system_merit(system) 112 113 # The list of merits and systems sorted in descending order by merit. 114 all_merit_system = sorted([(self.system_merit[s], s) 115 for s in systems_pool], reverse=True) 116 117 current_merit_lower_bound = 0 118 best_candidate = [] 119 120 # Cap the number of attempts when the found number of systems is less than the target 121 # num_home_systems because this indicates that the min_jumps is too large and/or the 122 # systems_pool is too small to ever succeed. 123 124 # From experimentation with cluster and 3 arm spiral galaxies, with low, med and high 125 # starlane density and (number of systems, number of home systems) pairs of (9999, 399), 126 # (999, 39) and (199, 19) the following was observered. The distribution of candidate 127 # length is a normal random variable with standard deviation approximately equal to 128 129 # expected_len_candidate_std = (len(systems) ** (1.0/2.0)) * 0.03 130 131 # which is about 1 for 1000 systems. It is likely that anylen(candidate) is within 1 132 # standard deviation of the expected len(candidate) 133 134 # If we are within the MISS_THRESHOLD of the target then try up to num_complete_misses more times. 135 MISS_THRESHOLD = 3 136 num_complete_misses_remaining = 4 137 138 # Cap the number of attempts to the smaller of the number of systems in the pool, or 100 139 attempts = min(100, len(systems_pool)) 140 141 while attempts and num_complete_misses_remaining: 142 # use a local pool of all candidate systems better than the worst threshold merit 143 all_merit_system = [(m, s) for (m, s) in all_merit_system if m > current_merit_lower_bound] 144 local_pool = {s for (m, s) in all_merit_system} 145 146 if len(local_pool) < self.num_home_systems: 147 if not best_candidate: 148 print("Failing in find_home_systems_for_min_jump_distance because " 149 "current_merit_lower_bound = {} trims local pool to {} systems " 150 "which is less than num_home_systems {}.".format( 151 current_merit_lower_bound, len(local_pool), self.num_home_systems)) 152 break 153 154 attempts = min(attempts - 1, len(local_pool)) 155 156 candidate = [] 157 while local_pool: 158 member = random.choice(list(local_pool)) 159 candidate.append(member) 160 161 # remove all neighbors from the local pool 162 local_pool -= set(fo.systems_within_jumps_unordered(min_jumps, [member])) 163 164 # Count complete misses when number of candidates is not close to the target. 165 if len(candidate) < (self.num_home_systems - MISS_THRESHOLD): 166 num_complete_misses_remaining -= 1 167 168 if len(candidate) < self.num_home_systems: 169 continue 170 171 # Calculate the merit of the current attempt. If it is the best so far 172 # keep it and update the merit_threshold 173 merit_system = sorted([(self.system_merit[s], s) 174 for s in candidate], reverse=True)[:self.num_home_systems] 175 176 (merit, system) = merit_system[-1] 177 178 # If we have a better candidate, set the new lower bound and try for a better candidate. 179 if merit > current_merit_lower_bound: 180 print("Home system set merit lower bound improved from {} to " 181 "{}".format(current_merit_lower_bound, merit)) 182 current_merit_lower_bound = merit 183 best_candidate = [s for (_, s) in merit_system] 184 185 # Quit successfully if the lowest merit system meets the minimum threshold 186 if merit >= min_planets_in_vicinity_limit( 187 len(fo.systems_within_jumps_unordered(HS_VICINITY_RANGE, [system]))): 188 break 189 190 return best_candidate 191 192 193def find_home_systems(num_home_systems, pool_list, jump_distance, min_jump_distance): 194 """ 195 Tries to find a specified number of home systems which are as far apart from each other as possible. 196 Starts with the specified jump distance and reduces that limit until enough systems can be found or the 197 jump distance drops below the specified minimum jump distance limit (in this case fail). 198 For each jump distance several attempts are made: one for each pool passed in pool_list. 199 This parameter contains a list of tuples, each tuple has a pool of systems as first element and a description 200 of the pool for logging purposes as second element. 201 """ 202 203 finder = HomeSystemFinder(num_home_systems) 204 # try to find home systems, decrease the min jumps until enough systems can be found, or the jump distance drops 205 # below the specified minimum jump distance (which means failure) 206 while jump_distance >= min_jump_distance: 207 print("Trying to find", num_home_systems, "home systems that are at least", jump_distance, "jumps apart...") 208 209 # try to pick our home systems by iterating over the pools we got 210 for pool, pool_label in pool_list: 211 print("...use", pool_label) 212 213 # check if the pool has enough systems to pick from 214 if len(pool) <= num_home_systems: 215 # no, the pool has less systems than home systems requested, so just skip trying using that pool 216 print("...pool only has", len(pool), "systems, skip trying to use it") 217 continue 218 219 # try to pick home systems 220 home_systems = finder.find_home_systems_for_min_jump_distance(pool, jump_distance) 221 # check if we got enough 222 if len(home_systems) >= num_home_systems: 223 # yes, we got what we need, return the home systems we found 224 print("...", len(home_systems), "systems found") 225 return home_systems 226 else: 227 # no, try next pool 228 print("...only", len(home_systems), "systems found") 229 230 # we did not find enough home systems with the current jump distance requirement, 231 # so decrease the jump distance and try again 232 jump_distance -= 1 233 234 # all attempts came up with too few systems, return empty list to indicate failure 235 return [] 236 237 238def add_planets_to_vicinity(vicinity, num_planets, gsd): 239 """ 240 Adds the specified number of planets to the specified systems. 241 """ 242 print("Adding", num_planets, "planets to the following systems:", vicinity) 243 244 # first, compile a list containing all the free orbits in the specified systems 245 # begin with adding the free orbits of all systems that have a real star (that is, no neutron star, black hole, 246 # and not no star), if that isn't enough, also one, by one, add the free orbits of neutron star, black hole and 247 # no star systems (in that order) until we have enough free orbits 248 249 # for that, we use this list of tuples 250 # the first tuple contains all real star types, the following tuples the neutron, black hole and no star types, 251 # so we can iterate over this list and only add the free orbits of systems that match the respective star types 252 # each step 253 # this way we can prioritize the systems we want to add planets to by star type 254 acceptable_star_types_list = [ 255 star_types_real, 256 (fo.starType.noStar,), 257 (fo.starType.neutron,), 258 (fo.starType.blackHole,) 259 ] 260 261 # store the free orbits as a list of tuples of (system, orbit) 262 free_orbits_map = [] 263 264 # now, iterate over the list of acceptable star types 265 for acceptable_star_types in acceptable_star_types_list: 266 # check all systems in the list of systems we got passed into this function 267 for system in vicinity: 268 # if this system has a star type we want to accept in this step, add its free orbits to our list 269 if fo.sys_get_star_type(system) in acceptable_star_types: 270 free_orbits_map.extend([(system, orbit) for orbit in fo.sys_free_orbits(system)]) 271 # check if we got enough free orbits after completing this step 272 # we want 4 times as much free orbits as planets we want to add, that means each system shouldn't get more 273 # than 2-3 additional planets on average 274 if len(free_orbits_map) > (num_planets * 4): 275 break 276 277 # if we got less free orbits than planets that should be added, something is wrong 278 # in that case abort and log an error 279 if len(free_orbits_map) < num_planets: 280 report_error("Python add_planets_to_vicinity: less free orbits than planets to add - cancelled") 281 282 print("...free orbits available:", free_orbits_map) 283 # as we will pop the free orbits from this list afterwards, shuffle it to randomize the order of the orbits 284 random.shuffle(free_orbits_map) 285 286 # add the requested number of planets 287 while num_planets > 0: 288 # get the next free orbit from the list we just compiled 289 system, orbit = free_orbits_map.pop() 290 291 # check the star type of the system containing the orbit we got 292 star_type = fo.sys_get_star_type(system) 293 if star_type in [fo.starType.noStar, fo.starType.blackHole]: 294 # if it is a black hole or has no star, change the star type 295 # pick a star type, continue until we get a real star 296 # don't accept neutron, black hole or no star 297 print("...system picked to add a planet has star type", star_type) 298 while star_type not in star_types_real: 299 star_type = pick_star_type(gsd.age) 300 print("...change that to", star_type) 301 fo.sys_set_star_type(system, star_type) 302 303 # pick a planet size, continue until we get a size that matches the HS_ACCEPTABLE_PLANET_SIZES option 304 planet_size = fo.planetSize.unknown 305 while planet_size not in HS_ACCEPTABLE_PLANET_SIZES: 306 planet_size = calc_planet_size(star_type, orbit, fo.galaxySetupOption.high, gsd.shape) 307 308 # pick an according planet type 309 planet_type = calc_planet_type(star_type, orbit, planet_size) 310 311 # finally, create the planet in the system and orbit we got 312 print("...adding", planet_size, planet_type, "planet to system", system) 313 if fo.create_planet(planet_size, planet_type, system, orbit, "") == fo.invalid_object(): 314 report_error("Python add_planets_to_vicinity: create planet in system %d failed" % system) 315 316 # continue with next planet 317 num_planets -= 1 318 319 320def compile_home_system_list(num_home_systems, systems, gsd): 321 """ 322 Compiles a list with a requested number of home systems. 323 """ 324 print("Compile home system list:", num_home_systems, "systems requested") 325 326 # if the list of systems to choose home systems from is empty, report an error and return empty list 327 if not systems: 328 report_error("Python generate_home_system_list: no systems to choose from") 329 return [] 330 331 # calculate an initial minimal number of jumps that the home systems should be apart, 332 # based on the total number of systems to choose from and the requested number of home systems 333 # don't let min_jumps be either: 334 # a.) larger than a defined limit, because an unreasonably large number is really not at all needed, 335 # and with large galaxies an excessive amount of time can be used in failed attempts 336 # b.) lower than the minimum jump distance limit that should be considered high priority (see options.py), 337 # otherwise no attempt at all would be made to enforce the other requirements for home systems (see below) 338 min_jumps = min(HS_MAX_JUMP_DISTANCE_LIMIT, max(int(len(systems) / (num_home_systems * 2)), 339 HS_MIN_DISTANCE_PRIORITY_LIMIT)) 340 341 # home systems must have a certain minimum of systems and planets in their near vicinity 342 # we will try to select our home systems from systems that match this criteria, if that fails, we will select our 343 # home systems from all systems and add the missing number planets to the systems in their vicinity afterwards 344 # the minimum system and planet limit and the jump range that defines the "near vicinity" are controlled by the 345 # HS_* option constants in options.py (see there) 346 347 # we start by building two additional pools of systems: one that contains all systems that match the criteria 348 # completely (meets the min systems and planets limit), and one that contains all systems that match the criteria 349 # at least partially (meets the min systems limit) 350 pool_matching_sys_and_planet_limit = [] 351 pool_matching_sys_limit = [] 352 for system in systems: 353 systems_in_vicinity = fo.systems_within_jumps_unordered(HS_VICINITY_RANGE, [system]) 354 if len(systems_in_vicinity) >= HS_MIN_SYSTEMS_IN_VICINITY: 355 pool_matching_sys_limit.append(system) 356 if count_planets_in_systems(systems_in_vicinity) >= min_planets_in_vicinity_limit(len(systems_in_vicinity)): 357 pool_matching_sys_and_planet_limit.append(system) 358 print(len(pool_matching_sys_and_planet_limit), 359 "systems meet the min systems and planets in the near vicinity limit") 360 print(len(pool_matching_sys_limit), "systems meet the min systems in the near vicinity limit") 361 362 # now try to pick the requested number of home systems 363 # we will do this by calling find_home_systems, which takes a list of tuples defining the pools from which to pick 364 # the home systems; it will use the pools in the order in which they appear in the list, so put better pools first 365 366 # we will make two attempts: the first one with the filtered pools we just created, and tell find_home_systems 367 # to start with the min_jumps jumps distance we calculated above, but not to go lower than 368 # HS_MIN_DISTANCE_PRIORITY_LIMIT 369 370 print("First attempt: trying to pick home systems from the filtered pools of preferred systems") 371 pool_list = [ 372 # the better pool is of course the one where all systems meet BOTH the min systems and planets limit 373 (pool_matching_sys_and_planet_limit, "pool of systems that meet both the min systems and planets limit"), 374 # next the less preferred pool where all systems at least meets the min systems limit 375 # specify 0 as number of requested home systems to pick as much systems as possible 376 (pool_matching_sys_limit, "pool of systems that meet at least the min systems limit"), 377 ] 378 home_systems = find_home_systems(num_home_systems, pool_list, min_jumps, HS_MIN_DISTANCE_PRIORITY_LIMIT) 379 380 # check if the first attempt delivered a list with enough home systems 381 # if not, we make our second attempt, this time disregarding the filtered pools and using all systems, starting 382 # again with the min_jumps jump distance limit and specifying 0 as number of required home systems to pick as much 383 # systems as possible 384 if len(home_systems) < num_home_systems: 385 print("Second attempt: trying to pick home systems from all systems") 386 home_systems = find_home_systems(num_home_systems, [(systems, "complete pool")], min_jumps, 1) 387 388 # check if the selection process delivered a list with enough home systems 389 # if not, our galaxy obviously is too crowded, report an error and return an empty list 390 if len(home_systems) < num_home_systems: 391 report_error("Python generate_home_system_list: requested %d homeworlds in a galaxy with %d systems" 392 % (num_home_systems, len(systems))) 393 return [] 394 395 # check if we got more home systems than we requested 396 if len(home_systems) > num_home_systems: 397 # yes: calculate the number of planets in the near vicinity of each system 398 # and store that value with each system in a map 399 hs_planets_in_vicinity_map = {s: calculate_home_system_merit(s) for s in home_systems} 400 # sort the home systems by the number of planets in their near vicinity using the map 401 # now only pick the number of home systems we need, taking those with the highest number of planets 402 home_systems = sorted(home_systems, key=hs_planets_in_vicinity_map.get, reverse=True)[:num_home_systems] 403 404 # make sure all our home systems have a "real" star (that is, a star that is not a neutron star, black hole, 405 # or even no star at all) and at least one planet in it 406 for home_system in home_systems: 407 # if this home system has no "real" star, change star type to a randomly selected "real" star 408 if fo.sys_get_star_type(home_system) not in star_types_real: 409 star_type = random.choice(star_types_real) 410 print("Home system", home_system, "has star type", fo.sys_get_star_type(home_system), ", changing that to", 411 star_type) 412 fo.sys_set_star_type(home_system, star_type) 413 414 # if this home system has no planets, create one in a random orbit 415 # we take random values for type and size, as these will be set to suitable values later 416 if not fo.sys_get_planets(home_system): 417 print("Home system", home_system, "has no planets, adding one") 418 planet = fo.create_planet(random.choice(planet_sizes_real), 419 random.choice(planet_types_real), 420 home_system, random.randint(0, fo.sys_get_num_orbits(home_system) - 1), "") 421 # if we couldn't create the planet, report an error and return an empty list 422 if planet == fo.invalid_object(): 423 report_error("Python generate_home_system_list: couldn't create planet in home system") 424 return [] 425 426 # finally, check again if all home systems meet the criteria of having the required minimum number of planets 427 # within their near vicinity, if not, add the missing number of planets 428 print("Checking if home systems have the required minimum of planets within the near vicinity...") 429 for home_system in home_systems: 430 # calculate the number of missing planets, and add them if this number is > 0 431 systems_in_vicinity = fo.systems_within_jumps_unordered(HS_VICINITY_RANGE, [home_system]) 432 num_systems_in_vicinity = len(systems_in_vicinity) 433 num_planets_in_vicinity = count_planets_in_systems(systems_in_vicinity) 434 num_planets_to_add = min_planets_in_vicinity_limit(num_systems_in_vicinity) - num_planets_in_vicinity 435 print("Home system", home_system, "has", num_systems_in_vicinity, "systems and", num_planets_in_vicinity, 436 "planets in the near vicinity, required minimum:", min_planets_in_vicinity_limit(num_systems_in_vicinity)) 437 if num_planets_to_add > 0: 438 systems_in_vicinity.remove(home_system) # don't add planets to the home system, so remove it from the list 439 # sort the systems_in_vicinity before adding, since the C++ engine doesn't guarrantee the same 440 # platform independence as python. 441 add_planets_to_vicinity(sorted(systems_in_vicinity), num_planets_to_add, gsd) 442 443 # as we've sorted the home system list before, lets shuffle it to ensure random order and return 444 random.shuffle(home_systems) 445 return home_systems 446 447 448def setup_empire(empire, empire_name, home_system, starting_species, player_name): 449 """ 450 Sets up various aspects of an empire, like empire name, homeworld, etc. 451 """ 452 453 # set empire name, if no one is given, pick one randomly 454 if not empire_name: 455 print("No empire name set for player", player_name, ", picking one randomly") 456 empire_name = next(empire_name_generator) 457 fo.empire_set_name(empire, empire_name) 458 print("Empire name for player", player_name, "is", empire_name) 459 460 # check starting species, if no one is given, pick one randomly 461 if starting_species == "RANDOM" or not starting_species: 462 print("Picking random starting species for player", player_name) 463 starting_species = next(starting_species_pool) 464 print("Starting species for player", player_name, "is", starting_species) 465 universe_statistics.empire_species[starting_species] += 1 466 467 # pick a planet from the specified home system as homeworld 468 planet_list = fo.sys_get_planets(home_system) 469 # if the system is empty, report an error and return false, indicating failure 470 if not planet_list: 471 report_error("Python setup_empire: got home system with no planets") 472 return False 473 homeworld = random.choice(planet_list) 474 475 # set selected planet as empire homeworld with selected starting species 476 fo.empire_set_homeworld(empire, homeworld, starting_species) 477 478 # set homeworld focus 479 # check if the preferred focus for the starting species is among 480 # the foci available on the homeworld planet 481 available_foci = fo.planet_available_foci(homeworld) 482 preferred_focus = fo.species_preferred_focus(starting_species) 483 if preferred_focus in available_foci: 484 # if yes, set the homeworld focus to the preferred focus 485 print("Player", player_name, ": setting preferred focus", preferred_focus, "on homeworld") 486 fo.planet_set_focus(homeworld, preferred_focus) 487 elif len(available_foci) > 0: 488 # if no, and there is at least one available focus, 489 # just take the first of the list 490 if preferred_focus == "": 491 print("Player", player_name, ": starting species", starting_species, "has no preferred focus, using", 492 available_foci[0], "instead") 493 else: 494 print("Player", player_name, ": preferred focus", preferred_focus, "for starting species", starting_species, 495 "not available on homeworld, using", available_foci[0], "instead") 496 fo.planet_set_focus(homeworld, available_foci[0]) 497 else: 498 # if no focus is available on the homeworld, don't set any focus 499 print("Player", player_name, ": no available foci on homeworld for starting species", starting_species) 500 501 # give homeworld starting buildings 502 print("Player", player_name, ": add starting buildings to homeworld") 503 for item in fo.load_starting_buildings(): 504 fo.create_building(item.name, homeworld, empire) 505 506 # unlock starting techs, buildings, hulls, ship parts, etc. 507 # use default content file 508 print("Player", player_name, ": add unlocked items") 509 for item in fo.load_unlockable_item_list(): 510 fo.empire_unlock_item(empire, item.type, item.name) 511 512 # add premade ship designs to empire 513 print("Player", player_name, ": add premade ship designs") 514 for ship_design in fo.design_get_premade_list(): 515 fo.empire_add_ship_design(empire, ship_design) 516 517 # add starting fleets to empire 518 # use default content file 519 print("Player", player_name, ": add starting fleets") 520 fleet_plans = fo.load_fleet_plan_list() 521 for fleet_plan in fleet_plans: 522 # should fleet be aggressive? check if any ships are armed. 523 should_be_aggressive = False 524 for ship_design in fleet_plan.ship_designs(): 525 design = fo.getPredefinedShipDesign(ship_design) 526 if design is None: 527 report_error("Looked up null design with name %s", ship_design) 528 elif design.isArmed: 529 should_be_aggressive = True 530 break 531 532 # first, create the fleet 533 fleet = fo.create_fleet(fleet_plan.name(), home_system, empire, should_be_aggressive) 534 # if the fleet couldn't be created, report an error and try to continue with the next fleet plan 535 if fleet == fo.invalid_object(): 536 report_error("Python setup empire: couldn't create fleet %s" % fleet_plan.name()) 537 continue 538 539 # second, iterate over the list of ship design names in the fleet plan 540 for ship_design in fleet_plan.ship_designs(): 541 # create a ship in the fleet 542 # if the ship couldn't be created, report an error and try to continue with the next ship design 543 if fo.create_ship("", ship_design, starting_species, fleet) == fo.invalid_object(): 544 report_error("Python setup empire: couldn't create ship of design %s for fleet %s" 545 % (ship_design, fleet_plan.name())) 546 547 return True 548 549 550def home_system_layout(home_systems, systems): 551 """ 552 Home systems layout generation to place teams. 553 Returns map from home system to neighbor home systems. 554 """ 555 # for each system found nearest home systems 556 # maybe multiple if home worlds placed on the same jump distnace 557 system_hs = {} 558 for system in systems: 559 nearest_hs = set() 560 nearest_dist = None 561 for hs in home_systems: 562 dist = fo.jump_distance(system, hs) 563 if nearest_dist is None or nearest_dist > dist: 564 nearest_dist = dist 565 nearest_hs = {hs} 566 elif nearest_dist == dist: 567 nearest_hs.add(hs) 568 system_hs[system] = nearest_hs 569 570 # homeworld is connected to the other 571 # if both are nearest for some system 572 # if each of them is nearest for systems on the starline ends 573 home_system_connections = {} 574 for system, connected_home_systems in system_hs.items(): 575 if len(connected_home_systems) >= 2: 576 for hs1, hs2 in unique_product(connected_home_systems, connected_home_systems): 577 home_system_connections.setdefault(hs1, set()).add(hs2) 578 home_system_connections.setdefault(hs2, set()).add(hs1) 579 580 for system, connected_home_systems in system_hs.items(): 581 adj_systems = fo.systems_within_jumps_unordered(1, [system]) 582 for system2 in adj_systems: 583 connected_home_systems2 = system_hs.get(system2, set()) 584 for hs1, hs2 in unique_product(connected_home_systems, connected_home_systems2): 585 home_system_connections.setdefault(hs1, set()).add(hs2) 586 home_system_connections.setdefault(hs2, set()).add(hs1) 587 return home_system_connections 588