def log_planet_count_dist(sys_list): planet_count_dist = {} planet_size_dist = {size: 0 for size in planets.planet_sizes} for system in sys_list: planet_count = 0 for planet in fo.sys_get_planets(system): this_size = fo.planet_get_size(planet) if this_size in planets.planet_sizes: planet_count += 1 planet_size_dist[this_size] += 1 planet_count_dist.setdefault(planet_count, [0])[0] += 1 planet_tally = sum(planet_size_dist.values()) count_distribution_table = Table( [Text('planets in system'), Text('num of systems'), Float('% of systems', precession=1)], table_name='Planet Count Distribution' ) for planet_count, sys_count in planet_count_dist.items(): count_distribution_table.add_row((planet_count, sys_count[0], 100.0 * sys_count[0] / len(sys_list))) print(count_distribution_table) print() size_distribution = Table( [Text('size'), Text('count'), Float('% of planets', precession=1)], table_name='Planet Size Distribution' ) for planet_size, planet_count in sorted(planet_size_dist.items()): size_distribution.add_row((planet_size, planet_count, 100.0 * planet_count / planet_tally)) print(size_distribution) print()
def log_planets(): universe = fo.get_universe() planets_table = Table([ Text('id'), Text('name'), Text('system'), Text('type'), Sequence('specials'), Text('species'), Sequence('buildings') ], table_name='Planets summary') # group planets by system for sid in fo.get_systems(): for pid in fo.sys_get_planets(sid): planet = universe.getPlanet(pid) planet_type = fo.planet_get_type(pid).name planet_size = fo.planet_get_size(pid).name if planet_type != planet_size: planet_type = '%s %s' % (planet_type, planet_size) buildings = [ universe.getBuilding(x).name for x in planet.buildingIDs ] planets_table.add_row([ pid, planet.name, planet.systemID, planet_type, list(planet.specials), planet.speciesName, buildings ]) # Printing too much info at once will lead to truncation of text for line in planets_table.get_table().split('\n'): print line
def log_planets(): universe = fo.get_universe() planets_table = Table( [Text('id'), Text('name'), Text('system'), Text('type'), Sequence('specials'), Text('species'), Sequence('buildings')], table_name='Planets summary') # group planets by system for sid in fo.get_systems(): for pid in fo.sys_get_planets(sid): planet = universe.getPlanet(pid) planet_type = fo.planet_get_type(pid).name planet_size = fo.planet_get_size(pid).name if planet_type != planet_size: planet_type = '%s %s' % (planet_type, planet_size) buildings = [universe.getBuilding(x).name for x in planet.buildingIDs] planets_table.add_row([ pid, planet.name, planet.systemID, planet_type, list(planet.specials), planet.speciesName, buildings ]) # Printing too much info at once will lead to truncation of text for line in planets_table.get_table().split('\n'): print line
def log_planet_count_dist(sys_list): planet_count_dist = {} planet_size_dist = {size: 0 for size in planets.planet_sizes} for system in sys_list: planet_count = 0 for planet in fo.sys_get_planets(system): this_size = fo.planet_get_size(planet) if this_size in planets.planet_sizes: planet_count += 1 planet_size_dist[this_size] += 1 planet_count_dist.setdefault(planet_count, [0])[0] += 1 planet_tally = sum(planet_size_dist.values()) count_distribution_table = Table( [Text('planets in system'), Text('num of systems'), Float('% of systems', precession=1)], table_name='Planet Count Distribution' ) for planet_count, sys_count in planet_count_dist.items(): count_distribution_table.add_row((planet_count, sys_count[0], 100.0 * sys_count[0] / len(sys_list))) print count_distribution_table print size_distribution = Table( [Text('size'), Text('count'), Float('% of planets', precession=1)], table_name='Planet Size Distribution' ) for planet_size, planet_count in sorted(planet_size_dist.items()): size_distribution.add_row((planet_size, planet_count, 100.0 * planet_count / planet_tally)) print size_distribution print
def generate_fields(systems): """ Generates stationary fields in some randomly chosen empty no star systems. """ # filter out all empty no star systems candidates = [ s for s in systems if (fo.sys_get_star_type(s) == fo.starType.noStar) and ( not fo.sys_get_planets(s)) ] # make sure we have at least one empty no star system, otherwise return without creating any fields if not candidates: print("...no empty no star systems found, no fields created") return # pick 10-15% of all empty no star systems to create stationary fields in them, but at least one accepted = sample(candidates, max(int(len(candidates) * uniform(0.1, 0.15)), 1)) for system in accepted: # randomly pick a field type field_type = choice(["FLD_NEBULA_1", "FLD_NEBULA_2", "FLD_NEBULA_3"]) # and create the field if fo.create_field_in_system(field_type, uniform(40, 120), system) == fo.invalid_object(): # create field failed, report an error report_error( "Python generate_fields: create field %s in system %d failed" % (field_type, system)) print("...fields created in %d systems out of %d empty no star systems" % (len(accepted), len(candidates)))
def compile_home_system_list(num_home_systems, systems): """ Compiles a list with a requested number of home systems. """ # if the list of systems to choose home systems from is empty, report an error and return empty list if not systems: util.report_error("Python generate_home_system_list: no systems to choose from") return [] # calculate an initial minimal number of jumps that the home systems should be apart, # based on the total number of systems to choose from and the requested number of home systems min_jumps = max(int(float(len(systems)) / float(num_home_systems * 2)), 5) # try to find the home systems, decrease the min jumps until enough systems can be found, or the min jump distance # gets reduced to 0 (meaning we don't have enough systems to choose from at all) while min_jumps > 0: print "Trying to find", num_home_systems, "home systems that are at least", min_jumps, "jumps apart" # try to find home systems... home_systems = find_systems_with_min_jumps_between(num_home_systems, systems, min_jumps) # ...check if we got enough... if len(home_systems) >= num_home_systems: # ...yes, we got what we need, so let's break out of the loop break print "Home system min jump conflict: %d systems and %d empires, tried %d min jump and failed"\ % (len(systems), num_home_systems, min_jumps) # ...no, decrease the min jump distance and try again min_jumps -= 1 # check if the loop above delivered a list with enough home systems, or if it exited because the min jump distance # has been decreased to 0 without finding enough systems # in that case, our galaxy obviously is too crowded, report an error and return an empty list if len(home_systems) < num_home_systems: util.report_error("Python generate_home_system_list: requested %d homeworlds in a galaxy with %d systems" % (num_home_systems, len(systems))) return [] # make sure all our home systems have a "real" star (that is, a star that is not a neutron star, black hole, # or even no star at all) and at least one planet in it for home_system in home_systems: # if this home system has no "real" star, change star type to a randomly selected "real" star if fo.sys_get_star_type(home_system) not in starsystems.star_types_real: star_type = random.choice(starsystems.star_types_real) print "Home system", home_system, "has star type", fo.sys_get_star_type(home_system),\ ", changing that to", star_type fo.sys_set_star_type(home_system, star_type) # if this home system has no planets, create one in a random orbit # we take random values for type and size, as these will be set to suitable values later if not fo.sys_get_planets(home_system): print "Home system", home_system, "has no planets, adding one" planet = fo.create_planet(random.choice(planets.planet_sizes_real), random.choice(planets.planet_types_real), home_system, random.randint(0, fo.sys_get_num_orbits(home_system) - 1), "") # if we couldn't create the planet, report an error and return an empty list if planet == fo.invalid_object(): util.report_error("Python generate_home_system_list: couldn't create planet in home system") return [] return home_systems
def execute_turn_events(): print "Executing turn events for turn", fo.current_turn() # creating fields systems = fo.get_systems() radius = fo.get_universe_width() / 2.0 if random() < max(0.0003 * radius, 0.03): if random() < 0.4: field_type = "FLD_MOLECULAR_CLOUD" size = 5.0 else: field_type = "FLD_ION_STORM" size = 5.0 x = y = radius dist_from_center = 0.0 while (dist_from_center < radius) or any(hypot(fo.get_x(s) - x, fo.get_y(s) - y) < 50.0 for s in systems): angle = random() * 2.0 * pi dist_from_center = radius + uniform(min(max(radius * 0.02, 10), 50.0), min(max(radius * 0.05, 20), 100.0)) x = radius + (dist_from_center * sin(angle)) y = radius + (dist_from_center * cos(angle)) print "...creating new", field_type, "field, at distance", dist_from_center, "from center" if fo.create_field(field_type, x, y, size) == fo.invalid_object(): print >> sys.stderr, "Turn events: couldn't create new field" # creating monsters gsd = fo.get_galaxy_setup_data() monster_freq = MONSTER_FREQUENCY[gsd.monsterFrequency] # monster freq ranges from 30 (= one monster per 30 systems) to 3 (= one monster per 3 systems) # (example: low monsters and 150 Systems results in 150 / 30 * 0.001 = 0.005) if monster_freq > 0 and random() < len(systems) / monster_freq * 0.001: #only spawn Krill at the moment, other monsters can follow in the future if random() < 1: monster_type = "SM_KRILL_1" else: monster_type = "SM_FLOATER" # search for systems without planets or fleets candidates = [s for s in systems if len(fo.sys_get_planets(s)) <= 0 and len(fo.sys_get_fleets(s)) <= 0] if not candidates: print >> sys.stderr, "Turn events: unable to find system for monster spawn" else: system = choice(candidates) print "...creating new", monster_type, "at", fo.get_name(system) # create monster fleet monster_fleet = fo.create_monster_fleet(system) # if fleet creation fails, report an error if monster_fleet == fo.invalid_object(): print >> sys.stderr, "Turn events: unable to create new monster fleet" else: # create monster, if creation fails, report an error monster = fo.create_monster(monster_type, monster_fleet) if monster == fo.invalid_object(): print >> sys.stderr, "Turn events: unable to create monster in fleet" return True
def log_planet_type_summary(sys_list): planet_type_summary = {k: 0 for k in planets.planet_types} for system in sys_list: for planet in fo.sys_get_planets(system): planet_type_summary[fo.planet_get_type(planet)] += 1 planet_total = sum(planet_type_summary.values()) print "Planet Type Summary for a total of %d placed planets" % planet_total for planet_type, planet_count in planet_type_summary.items(): print "%-12s %4.1f%%" % (planet_type.name, 100.0 * planet_count / planet_total)
def count_planets_in_systems(systems, planet_types_filter=HS_ACCEPTABLE_PLANET_TYPES): """ Return the total number of planets in the specified group of systems, only count the planet types specified in planet_types_filter. """ num_planets = 0 for system in systems: num_planets += len([p for p in fo.sys_get_planets(system) if fo.planet_get_type(p) in planet_types_filter]) return num_planets
def log_systems(): universe = fo.get_universe() systems_table = Table( [Text('id'), Text('name'), Sequence('planets'), Sequence('connections'), Text('star')], table_name='System summary') for sid in fo.get_systems(): system = universe.getSystem(sid) systems_table.add_row([ sid, system.name, fo.sys_get_planets(sid), fo.sys_get_starlanes(sid), system.starType.name ]) # Printing too much info at once will lead to truncation of text for line in systems_table.get_table().split('\n'): print(line)
def log_systems(): universe = fo.get_universe() systems_table = Table( [Text('id'), Text('name'), Sequence('planets'), Sequence('connections'), Text('star')], table_name='System summary') for sid in fo.get_systems(): system = universe.getSystem(sid) systems_table.add_row([ sid, system.name, fo.sys_get_planets(sid), fo.sys_get_starlanes(sid), system.starType.name ]) # Printing too much info at once will lead to truncation of text for line in systems_table.get_table().split('\n'): print line
def name_planets(system): """ Sets the names of the planets of the specified system. Planet name is system name + planet number (as roman number) unless it's an asteroid belt, in that case name is system name + 'asteroid belt' (localized). """ # iterate over all planets in the system sys_name = fo.get_name(system) for planet in fo.sys_get_planets(system): name = fo.user_string("NEW_PLANET_NAME") name = name.replace("%1%", sys_name) name = name.replace("%2%", fo.planet_cardinal_suffix(planet)) fo.set_name(planet, name)
def log_planet_type_summary(sys_list): planet_type_summary_table = {k: 0 for k in planets.planet_types} for system in sys_list: for planet in fo.sys_get_planets(system): planet_type_summary_table[fo.planet_get_type(planet)] += 1 planet_total = sum(planet_type_summary_table.values()) type_summary_table = Table( [Text('planet type', align='<'), Float('% of planets', precession=1)], table_name='Planet Type Summary for a total of %d placed planets' % planet_total ) for planet_type, planet_count in sorted(planet_type_summary_table.items()): type_summary_table.add_row((planet_type.name, 100.0 * planet_count / planet_total)) print type_summary_table print
def log_planet_type_summary(sys_list): planet_type_summary_table = {k: 0 for k in planets.planet_types} for system in sys_list: for planet in fo.sys_get_planets(system): planet_type_summary_table[fo.planet_get_type(planet)] += 1 planet_total = sum(planet_type_summary_table.values()) type_summary_table = Table( [Text('planet type', align='<'), Float('% of planets', precession=1)], table_name='Planet Type Summary for a total of %d placed planets' % planet_total ) for planet_type, planet_count in sorted(planet_type_summary_table.items()): type_summary_table.add_row((planet_type.name, 100.0 * planet_count / planet_total)) print(type_summary_table) print()
def log_planet_count_dist(sys_list): planet_count_dist = {} planet_size_dist = {size : 0 for size in planets.planet_sizes} for system in sys_list: planet_count = 0 for planet in fo.sys_get_planets(system): this_size = fo.planet_get_size(planet) if this_size in planets.planet_sizes: planet_count += 1 planet_size_dist[this_size] += 1 planet_count_dist.setdefault(planet_count, [0])[0] += 1 planet_tally = sum(planet_size_dist.values()) print "Planet Count Distribution: planets_in_system | num_systems | % of systems" for planet_count, sys_count in planet_count_dist.items(): print "\t\t\t%2d | %5d | %4.1f%%" % (planet_count, sys_count[0], 100.0 * sys_count[0] / len(sys_list)) print print "Planet Size Distribution: size | count | % of planets" for planet_size, planet_count in planet_size_dist.items(): print "\t\t%-12s | %5d | %4.1f%%" % (planet_size, planet_count, 100.0 * planet_count / planet_tally)
def log_planet_count_dist(sys_list): planet_count_dist = {} planet_size_dist = {size: 0 for size in planets.planet_sizes} for system in sys_list: planet_count = 0 for planet in fo.sys_get_planets(system): this_size = fo.planet_get_size(planet) if this_size in planets.planet_sizes: planet_count += 1 planet_size_dist[this_size] += 1 planet_count_dist.setdefault(planet_count, [0])[0] += 1 planet_tally = sum(planet_size_dist.values()) count_distribution_table = Table( Text("planets in system"), Text("num of systems"), Number("% of systems", precession=1), table_name="Planet Count Distribution", ) for planet_count, sys_count in planet_count_dist.items(): count_distribution_table.add_row( planet_count, sys_count[0], 100.0 * sys_count[0] / len(sys_list), ) count_distribution_table.print_table(print) print() size_distribution = Table( Text("size"), Text("count"), Number("% of planets", precession=1), table_name="Planet Size Distribution", ) for planet_size, planet_count in sorted(planet_size_dist.items()): size_distribution.add_row( planet_size, planet_count, 100.0 * planet_count / planet_tally, ) size_distribution.print_table(print) print()
def log_planet_type_summary(sys_list): planet_type_summary_table = {k: 0 for k in planets.planet_types} for system in sys_list: for planet in fo.sys_get_planets(system): planet_type_summary_table[fo.planet_get_type(planet)] += 1 planet_total = sum(planet_type_summary_table.values()) type_summary_table = Table( Text("planet type", align="<"), Number("% of planets", precession=1), table_name="Planet Type Summary for a total of %d placed planets" % planet_total, ) for planet_type, planet_count in sorted(planet_type_summary_table.items()): type_summary_table.add_row( planet_type.name, 100.0 * planet_count / planet_total, ) type_summary_table.print_table(print) print()
def log_planet_count_dist(sys_list): planet_count_dist = {} planet_size_dist = {size: 0 for size in planets.planet_sizes} for system in sys_list: planet_count = 0 for planet in fo.sys_get_planets(system): this_size = fo.planet_get_size(planet) if this_size in planets.planet_sizes: planet_count += 1 planet_size_dist[this_size] += 1 planet_count_dist.setdefault(planet_count, [0])[0] += 1 planet_tally = sum(planet_size_dist.values()) print "Planet Count Distribution: planets_in_system | num_systems | % of systems" for planet_count, sys_count in planet_count_dist.items(): print "\t\t\t%2d | %5d | %4.1f%%" % ( planet_count, sys_count[0], 100.0 * sys_count[0] / len(sys_list)) print print "Planet Size Distribution: size | count | % of planets" for planet_size, planet_count in planet_size_dist.items(): print "\t\t%-12s | %5d | %4.1f%%" % (planet_size, planet_count, 100.0 * planet_count / planet_tally)
def log_systems(): universe = fo.get_universe() systems_table = Table( Text("id"), Text("name"), Sequence("planets"), Sequence("connections"), Text("star"), table_name="System summary", ) for sid in fo.get_systems(): system = universe.getSystem(sid) systems_table.add_row( sid, system.name, fo.sys_get_planets(sid), fo.sys_get_starlanes(sid), system.starType.name, ) systems_table.print_table(print)
def name_planets(system): """ Sets the names of the planets of the specified system. Planet name is system name + planet number (as roman number) unless it's an asteroid belt, in that case name is system name + 'asteroid belt' (localized). """ planet_number = 1 # iterate over all planets in the system for planet in fo.sys_get_planets(system): # use different naming methods for "normal" planets and asteroid belts if fo.planet_get_type(planet) == fo.planetType.asteroids: # get localized text from stringtable name = fo.user_string("PL_ASTEROID_BELT_OF_SYSTEM") # %1% parameter in the localized string is the system name name = name.replace("%1%", fo.get_name(system)) else: # set name to system name + planet number as roman number... name = fo.get_name(system) + " " + fo.roman_number(planet_number) # ...and increase planet number planet_number += 1 # do the actual renaming fo.set_name(planet, name)
def log_planets(): universe = fo.get_universe() planets_table = Table( Text("id"), Text("name"), Text("system"), Text("type"), Sequence("specials"), Text("species"), Sequence("buildings"), table_name="Planets summary", ) # group planets by system for sid in fo.get_systems(): for pid in fo.sys_get_planets(sid): planet = universe.getPlanet(pid) planet_type = fo.planet_get_type(pid).name planet_size = fo.planet_get_size(pid).name if planet_type != planet_size: planet_type = "%s %s" % (planet_type, planet_size) buildings = [ universe.getBuilding(x).name for x in planet.buildingIDs ] planets_table.add_row( pid, planet.name, planet.systemID, planet_type, list(planet.specials), planet.speciesName, buildings, ) planets_table.print_table(print)
def generate_natives(native_freq, systems, empire_home_systems): """ Adds non-empire-affiliated native populations to planets. """ # first, calculate the chance for natives on a planet based on the native frequency that has been passed # get the corresponding value for the specified natives frequency from the universe tables native_chance = universe_tables.NATIVE_FREQUENCY[native_freq] # a value of 0 means no natives, in this case return immediately if native_chance <= 0: return # compile a list of planets where natives can be placed # select only planets sufficiently far away from player home systems # list of planets safe for natives EMPIRE_TO_NATIVE_MIN_DIST = 2 empire_exclusions = set( itertools.chain.from_iterable( fo.systems_within_jumps_unordered(EMPIRE_TO_NATIVE_MIN_DIST, [e]) for e in empire_home_systems)) native_safe_planets = set( itertools.chain.from_iterable([ fo.sys_get_planets(s) for s in systems if s not in empire_exclusions ])) print( "Number of planets far enough from players for natives to be allowed:", len(native_safe_planets)) # if there are no "native safe" planets at all, we can stop here if not native_safe_planets: return # get all native species native_species = fo.get_native_species() print("Species that can be added as natives:") print("... " + "\n... ".join(native_species)) # create a map with a list for each planet type containing the species # for which this planet type is a good environment # we will need this afterwards when picking natives for a planet natives_for_planet_type.clear() # just to be safe natives_for_planet_type.update( {planet_type: [] for planet_type in planets.planet_types}) planet_types_for_natives.clear() planet_types_for_natives.update( {species: set() for species in native_species}) # iterate over all native species we got for species in native_species: # check the planet environment for all planet types for this species for planet_type in planets.planet_types: # if this planet type is a good environment for the species, add it to the list for this planet type if fo.species_get_planet_environment( species, planet_type) == fo.planetEnvironment.good: natives_for_planet_type[planet_type].append(species) planet_types_for_natives[species].add(planet_type) # randomly add species to planets # iterate over the list of "native safe" planets we compiled earlier for candidate in native_safe_planets: # select a native species to put on this planet planet_type = fo.planet_get_type(candidate) # check if we have any native species that like this planet type if not natives_for_planet_type[planet_type]: # no, continue with next planet continue universe_statistics.potential_native_planet_summary[planet_type] += 1 # make a "roll" against the chance for natives to determine if we shall place natives on this planet if random.random() > native_chance: # no, continue with next planet continue universe_statistics.settled_native_planet_summary[planet_type] += 1 # randomly pick one of the native species available for this planet type natives = random.choice(natives_for_planet_type[planet_type]) # put the selected natives on the planet fo.planet_set_species(candidate, natives) # set planet as homeworld for that species fo.species_add_homeworld(natives, candidate) # set planet focus # check if the preferred focus for the native species is among the foci available on this planet available_foci = fo.planet_available_foci(candidate) preferred_focus = fo.species_preferred_focus(natives) if preferred_focus in available_foci: # if yes, set the planet focus to the preferred focus fo.planet_set_focus(candidate, preferred_focus) elif available_foci: # if no, and there is at least one available focus, just take the first of the list # otherwise don't set any focus fo.planet_set_focus(candidate, available_foci[0]) print("Added native", natives, "to planet", fo.get_name(candidate)) # increase the statistics counter for this native species, so a species summary can be dumped to the log later universe_statistics.species_summary[natives] += 1
def name_star_systems(system_list): # choose star types and planet sizes, before choosing names, so naming can have special handling of Deep Space star_type_assignments = {} planet_assignments = {} position_list = [] for system in system_list: star_type = fo.sys_get_star_type(system) systemxy = fo.get_pos(system) star_type_assignments[systemxy] = star_type planet_assignments[systemxy] = fo.sys_get_planets(system) position_list.append(systemxy) # will name name a portion of stars on a group basis, where the stars of each group share the same base star name, # suffixed by different (default greek) letters or characters (options at top of file) star_name_map = {} star_names = names.get_name_list("STAR_NAMES") group_names = names.get_name_list("STAR_GROUP_NAMES") potential_group_names = [] individual_names = [] stargroup_words[:] = names.get_name_list("STAR_GROUP_WORDS") stargroup_chars[:] = names.get_name_list("STAR_GROUP_CHARS") stargroup_modifiers[:] = [stargroup_words, stargroup_chars][options.STAR_GROUPS_USE_CHARS] for starname in star_names: if len(starname) > 6: # if starname is long, don't allow it for groups individual_names.append(starname) continue # any names that already have a greek letter in them can only be used for individual stars, not groups for namepart in starname.split(): if namepart in greek_letters: individual_names.append(starname) break else: potential_group_names.append(starname) if not potential_group_names: potential_group_names.append("XYZZY") # ensure at least a portion of galaxy gets individual starnames num_systems = len(system_list) choice = num_systems >= options.NAMING_LARGE_GALAXY_SIZE target_indiv_ratio = [ options.TARGET_INDIV_RATIO_SMALL, options.TARGET_INDIV_RATIO_LARGE ][choice] # TODO improve the following calc to be more likely to hit target_indiv_ratio if more or less than # 50% potential_group_names used for groups num_individual_stars = int( max( min(num_systems * target_indiv_ratio, len(individual_names) + int(0.5 * len(potential_group_names))), num_systems - 0.8 * len(stargroup_modifiers) * (len(group_names) + int(0.5 * len(potential_group_names))))) star_group_size = 1 + int( (num_systems - num_individual_stars) / (max(1, len(group_names) + int(0.5 * len(potential_group_names))))) # make group size a bit bigger than min necessary, at least a trio star_group_size = max(3, star_group_size) num_star_groups = 1 + int(num_systems / star_group_size) # initial value # first cluster all systems, then remove some to be individually named (otherwise groups can have too many # individually named systems in their middle). First remove any that are too small (only 1 or 2 systems). # The clusters with the most systems are generally the most closely spaced, and though they might make good # logical candidates for groups, their names are then prone to overlapping on the galaxy map, so after removing # small groups, remove the groups with the most systems. random.shuffle(position_list) # just to be sure it is randomized init_cluster_assgts = cluster_stars(position_list, num_star_groups) star_groups = {} for index_pos, index_group in enumerate(init_cluster_assgts): systemxy = position_list[index_pos] star_groups.setdefault(index_group, []).append(systemxy) indiv_systems = [] # remove groups with only one non-deep-system for groupindex, group_list in list(star_groups.items()): max_can_transfer = len(potential_group_names) - len(star_groups) + len( individual_names) - len(indiv_systems) if max_can_transfer <= 0: break elif max_can_transfer <= len(group_list): continue not_deep, deep_space = check_deep_space(group_list, star_type_assignments, planet_assignments) if len(not_deep) > 1: continue for systemxy in star_groups[groupindex]: indiv_systems.append(systemxy) del star_groups[groupindex] # remove tiny groups group_sizes = [(len(group), index) for index, group in star_groups.items()] group_sizes.sort() while len(indiv_systems) < num_individual_stars and len(group_sizes) > 0: groupsize, groupindex = group_sizes.pop() max_can_transfer = len(potential_group_names) - len(star_groups) + len( individual_names) - len(indiv_systems) if (max_can_transfer <= 0) or (groupsize > 2): break if max_can_transfer <= groupsize: continue for systemxy in star_groups[groupindex]: indiv_systems.append(systemxy) del star_groups[groupindex] # remove largest (likely most compact) groups while len(indiv_systems) < num_individual_stars and len(group_sizes) > 0: groupsize, groupindex = group_sizes.pop(-1) max_can_transfer = len(potential_group_names) - len(star_groups) + len( individual_names) - len(indiv_systems) if max_can_transfer <= 0: break if max_can_transfer <= groupsize: continue for systemxy in star_groups[groupindex]: indiv_systems.append(systemxy) del star_groups[groupindex] num_star_groups = len(star_groups) num_individual_stars = len(indiv_systems) random.shuffle(potential_group_names) random.shuffle(individual_names) random.shuffle(group_names) num_for_indiv = min( max( len(potential_group_names) // 2, num_individual_stars + 1 - len(individual_names)), len(potential_group_names)) individual_names.extend(potential_group_names[:num_for_indiv]) group_names.extend(potential_group_names[num_for_indiv:]) # print "sampling for %d indiv names from list of %d total indiv names" % ( # num_individual_stars, len(individual_names)) indiv_name_sample = random.sample(individual_names, num_individual_stars) # indiv_name_assignments = zip([(pos.x, pos.y) for pos in position_list[:num_individual_stars]], indiv_name_sample) star_name_map.update(zip(indiv_systems, indiv_name_sample)) # print "sampling for %d group names from list of %d total group names"%(num_star_groups, len(group_names)) if len(group_names) < num_star_groups: group_names.extend([ names.random_name(6) for _ in range(num_star_groups - len(group_names)) ]) group_name_sample = random.sample(group_names, num_star_groups) for index_group, group_list in enumerate(sorted(star_groups.values())): star_name_map.update( name_group(group_list, group_name_sample[index_group], star_type_assignments, planet_assignments)) # assign names from star_name_map to star systems for system in system_list: fo.set_name( system, star_name_map.get(fo.get_pos(system), "") or random_star_name())
def compile_home_system_list(num_home_systems, systems): """ Compiles a list with a requested number of home systems. """ print "Compile home system list:", num_home_systems, "systems requested" # if the list of systems to choose home systems from is empty, report an error and return empty list if not systems: report_error("Python generate_home_system_list: no systems to choose from") return [] # calculate an initial minimal number of jumps that the home systems should be apart, # based on the total number of systems to choose from and the requested number of home systems # Don't let min_jumps be larger than 10, because a larger number is really not at all needed and with large # galaxies an excessive amount of time can be used in failed attempts min_jumps = min(10, max(int(float(len(systems)) / float(num_home_systems * 2)), 5)) # home systems must have a certain minimum of systems in their near vicinity # we will try to select our home systems from systems that match this criteria, if that fails, we will select our # home systems from all systems and add the missing number planets to the systems in their vicinity afterwards # the minimum planet limit and the jump range that defines the "near vicinity" are controlled by the # HS_* option constants in options.py (see there) # lets start by filtering out all systems from the pool we got passed into this function that match the criteria filtered_pool = [s for s in systems if has_min_planets_in_vicinity(s)] print "Filtering out systems that meet the minimum planets in the near vicinity condition yielded",\ len(filtered_pool), "systems" print "Using this as the preferred pool for home system selection" # now try to pick the requested number of home systems by calling find_home_systems # this function takes two pools, a "complete" pool and one with preferred systems # it will try to pick the home systems from the preferred pool first, so pass our filtered pool as preferred pool home_systems = find_home_systems(num_home_systems, systems, filtered_pool, min_jumps) # check if the selection process delivered a list with enough home systems # if not, our galaxy obviously is too crowded, report an error and return an empty list if len(home_systems) < num_home_systems: report_error("Python generate_home_system_list: requested %d homeworlds in a galaxy with %d systems" % (num_home_systems, len(systems))) return [] # check if we got more home systems than we requested if len(home_systems) > num_home_systems: # yes: calculate the number of planets in the near vicinity of each system # and store that value with each system in a map hs_planets_in_vicinity_map = {s: count_planets_in_systems(get_systems_within_jumps(s, HS_VICINITY_RANGE)) for s in home_systems} # sort the home systems by the number of planets in their near vicinity using the map # now only pick the number of home systems we need, taking those with the highest number of planets home_systems = sorted(home_systems, key=hs_planets_in_vicinity_map.get, reverse=True)[:num_home_systems] # make sure all our home systems have a "real" star (that is, a star that is not a neutron star, black hole, # or even no star at all) and at least one planet in it for home_system in home_systems: # if this home system has no "real" star, change star type to a randomly selected "real" star if fo.sys_get_star_type(home_system) not in star_types_real: star_type = random.choice(star_types_real) print "Home system", home_system, "has star type", fo.sys_get_star_type(home_system),\ ", changing that to", star_type fo.sys_set_star_type(home_system, star_type) # if this home system has no planets, create one in a random orbit # we take random values for type and size, as these will be set to suitable values later if not fo.sys_get_planets(home_system): print "Home system", home_system, "has no planets, adding one" planet = fo.create_planet(random.choice(planet_sizes_real), random.choice(planet_types_real), home_system, random.randint(0, fo.sys_get_num_orbits(home_system) - 1), "") # if we couldn't create the planet, report an error and return an empty list if planet == fo.invalid_object(): report_error("Python generate_home_system_list: couldn't create planet in home system") return [] # finally, check again if all home systems meet the criteria of having the required minimum number of planets # within their near vicinity, if not, add the missing number of planets print "Checking if home systems have the required minimum of planets within the near vicinity..." for home_system in home_systems: # calculate the number of missing planets, and add them if this number is > 0 systems_in_vicinity = get_systems_within_jumps(home_system, HS_VICINITY_RANGE) num_systems_in_vicinity = len(systems_in_vicinity) num_planets_in_vicinity = count_planets_in_systems(systems_in_vicinity) num_planets_to_add = min_planets_in_vicinity_limit(num_systems_in_vicinity) - num_planets_in_vicinity print "Home system", home_system, "has", num_systems_in_vicinity, "systems and", num_planets_in_vicinity,\ "planets in the near vicinity, required minimum:", min_planets_in_vicinity_limit(num_systems_in_vicinity) if num_planets_to_add > 0: systems_in_vicinity.remove(home_system) # don't add planets to the home system, so remove it from the list add_planets_to_vicinity(systems_in_vicinity, num_planets_to_add) # as we've sorted the home system list before, lets shuffle it to ensure random order and return random.shuffle(home_systems) return home_systems
def generate_monsters(monster_freq, systems): """ Adds space monsters to systems. """ # first, calculate the basic chance for monster generation in a system # based on the monster frequency that has been passed # get the corresponding value for the specified monster frequency from the universe tables basic_chance = universe_tables.MONSTER_FREQUENCY[monster_freq] # a value of 0 means no monsters, in this case return immediately if basic_chance <= 0: return print "Default monster spawn chance:", basic_chance expectation_tally = 0.0 actual_tally = 0 # get all monster fleets that have a spawn rate and limit both > 0 and at least one monster ship design in it # (a monster fleet with no monsters in it is pointless) and store them in a list fleet_plans = fo.load_monster_fleet_plan_list() # create a map where we store a spawn counter for each monster fleet # this counter will be set to the spawn limit initially and decreased every time the monster fleet is spawned # this map (dict) needs to be separate from the list holding the fleet plans because the order in which items # are stored in a dict is undefined (can be different each time), which would result in different distribution # even when using the same seed for the RNG spawn_limits = { fp: fp.spawn_limit() for fp in fleet_plans if fp.spawn_rate() > 0.0 and fp.spawn_limit() > 0 and fp.ship_designs() } # map nests to monsters for ease of reporting nest_name_map = { "KRAKEN_NEST_SPECIAL": "SM_KRAKEN_1", "SNOWFLAKE_NEST_SPECIAL": "SM_SNOWFLAKE_1", "JUGGERNAUT_NEST_SPECIAL": "SM_JUGGERNAUT_1" } tracked_plan_tries = {name: 0 for name in nest_name_map.values()} tracked_plan_counts = {name: 0 for name in nest_name_map.values()} tracked_plan_valid_locations = { fp: 0 for fp in fleet_plans if fp.name() in tracked_plan_counts } tracked_nest_valid_locations = {nest: 0 for nest in nest_name_map} if not fleet_plans: return universe = fo.get_universe() # Fleet plans that include ships capable of altering starlanes. ## @content_tag{CAN_ALTER_STARLANES} universe_generator special handling for fleets containing a hull design with this tag. fleet_can_alter_starlanes = { fp for fp in fleet_plans if any([ universe.getGenericShipDesign(design).hull_type.hasTag( "CAN_ALTER_STARLANES") for design in fp.ship_designs() ]) } # dump a list of all monster fleets meeting these conditions and their properties to the log print "Monster fleets available for generation at game start:" for fleet_plan in fleet_plans: print "...", fleet_plan.name(), ": spawn rate", fleet_plan.spawn_rate( ), print "/ spawn limit", fleet_plan.spawn_limit(), print "/ effective chance", basic_chance * fleet_plan.spawn_rate(), if len(systems) < 100: # Note: The WithinStarlaneJumps condition in fp.location() # is the most time costly function in universe generation print "/ can be spawned at", len( [s for s in systems if fleet_plan.location(s)]), "systems" else: print # to terminate the print line if fleet_plan.name() in nest_name_map.values(): statistics.tracked_monsters_chance[ fleet_plan.name()] = basic_chance * fleet_plan.spawn_rate() # initialize a manager for monsters that can alter the map # required to prevent their placement from disjoining the map starlane_altering_monsters = StarlaneAlteringMonsters(systems) # for each system in the list that has been passed to this function, find a monster fleet that can be spawned at # the system and which hasn't already been added too many times, then attempt to add that monster fleet by # testing the spawn rate chance for system in systems: # collect info for tracked monster nest valid locations for planet in fo.sys_get_planets(system): for nest in tracked_nest_valid_locations: # print "\t tracked monster check planet: %d size: %s for nest: %20s | result: %s" # % (planet, fo.planet_get_size(planet), nest, fo.special_location(nest, planet)) if fo.special_location(nest, planet): tracked_nest_valid_locations[nest] += 1 # collect info for tracked monster valid locations for fp in tracked_plan_valid_locations: if fp.location(system): tracked_plan_valid_locations[fp] += 1 # filter out all monster fleets whose location condition allows this system and whose counter hasn't reached 0. # Note: The WithinStarlaneJumps condition in fp.location() is # the most time costly function in universe generation. suitable_fleet_plans = [ fp for fp in fleet_plans if spawn_limits[fp] and fp.location(system) and ( fp not in fleet_can_alter_starlanes or starlane_altering_monsters.can_place_at(system, fp)) ] # if there are no suitable monster fleets for this system, continue with the next if not suitable_fleet_plans: continue # randomly select one monster fleet out of the suitable ones and then test if we want to add it to this system # by making a roll against the basic chance multiplied by the spawn rate of this monster fleet expectation_tally += basic_chance * sum( [fp.spawn_rate() for fp in suitable_fleet_plans]) / len(suitable_fleet_plans) fleet_plan = random.choice(suitable_fleet_plans) if fleet_plan.name() in tracked_plan_tries: tracked_plan_tries[fleet_plan.name()] += 1 if random.random() > basic_chance * fleet_plan.spawn_rate(): print( "\t\t At system %4d rejected monster fleet %s from %d suitable fleets" % (system, fleet_plan.name(), len(suitable_fleet_plans))) # no, test failed, continue with the next system continue actual_tally += 1 if fleet_plan.name() in tracked_plan_counts: tracked_plan_counts[fleet_plan.name()] += 1 # all prerequisites and the test have been met, now spawn this monster fleet in this system # create monster fleet try: if fleet_plan in fleet_can_alter_starlanes: starlane_altering_monsters.place(system, fleet_plan) else: populate_monster_fleet(fleet_plan, system) # decrement counter for this monster fleet spawn_limits[fleet_plan] -= 1 except MapGenerationError as e: report_error(str(e)) continue print "Actual # monster fleets placed: %d; Total Placement Expectation: %.1f" % ( actual_tally, expectation_tally) # finally, compile some statistics to be dumped to the log later statistics.monsters_summary = [(fp.name(), fp.spawn_limit() - counter) for fp, counter in spawn_limits.iteritems()] statistics.tracked_monsters_tries.update(tracked_plan_tries) statistics.tracked_monsters_summary.update(tracked_plan_counts) statistics.tracked_monsters_location_summary.update([ (fp.name(), count) for fp, count in tracked_plan_valid_locations.iteritems() ]) statistics.tracked_nest_location_summary.update([ (nest_name_map[nest], count) for nest, count in tracked_nest_valid_locations.items() ])
def generate_fields(systems): """ Generates stationary fields in some randomly chosen empty no star systems. """ # filter out all empty no star systems candidates = [s for s in systems if (fo.sys_get_star_type(s) == fo.starType.noStar) and (not fo.sys_get_planets(s))] # make sure we have at least one empty no star system, otherwise return without creating any fields if not candidates: print "...no empty no star systems found, no fields created" return # pick 10-20% of all empty no star systems to create stationary fields in them, but at least one accepted = sample(candidates, max(int(len(candidates) * uniform(0.1, 0.2)), 1)) for system in accepted: # randomly pick a field type field_type = choice(["FLD_NEBULA_1", "FLD_NEBULA_2"]) # and create the field if fo.create_field_in_system(field_type, uniform(40, 120), system) == fo.invalid_object(): # create field failed, report an error report_error("Python generate_fields: create field %s in system %d failed" % (field_type, system)) print "...fields created in %d systems out of %d empty no star systems" % (len(accepted), len(candidates))
def compile_home_system_list(num_home_systems, systems, gsd): """ Compiles a list with a requested number of home systems. """ print "Compile home system list:", num_home_systems, "systems requested" # if the list of systems to choose home systems from is empty, report an error and return empty list if not systems: report_error("Python generate_home_system_list: no systems to choose from") return [] # calculate an initial minimal number of jumps that the home systems should be apart, # based on the total number of systems to choose from and the requested number of home systems # don't let min_jumps be either: # a.) larger than a defined limit, because an unreasonably large number is really not at all needed, # and with large galaxies an excessive amount of time can be used in failed attempts # b.) lower than the minimum jump distance limit that should be considered high priority (see options.py), # otherwise no attempt at all would be made to enforce the other requirements for home systems (see below) min_jumps = min(HS_MAX_JUMP_DISTANCE_LIMIT, max(int(float(len(systems)) / float(num_home_systems * 2)), HS_MIN_DISTANCE_PRIORITY_LIMIT)) # home systems must have a certain minimum of systems and planets in their near vicinity # we will try to select our home systems from systems that match this criteria, if that fails, we will select our # home systems from all systems and add the missing number planets to the systems in their vicinity afterwards # the minimum system and planet limit and the jump range that defines the "near vicinity" are controlled by the # HS_* option constants in options.py (see there) # we start by building two additional pools of systems: one that contains all systems that match the criteria # completely (meets the min systems and planets limit), and one that contains all systems that match the criteria # at least partially (meets the min systems limit) pool_matching_sys_and_planet_limit = [] pool_matching_sys_limit = [] for system in systems: systems_in_vicinity = get_systems_within_jumps(system, HS_VICINITY_RANGE) if len(systems_in_vicinity) >= HS_MIN_SYSTEMS_IN_VICINITY: pool_matching_sys_limit.append(system) if count_planets_in_systems(systems_in_vicinity) >= min_planets_in_vicinity_limit(len(systems_in_vicinity)): pool_matching_sys_and_planet_limit.append(system) print len(pool_matching_sys_and_planet_limit), "systems meet the min systems and planets in the near vicinity limit" print len(pool_matching_sys_limit), "systems meet the min systems in the near vicinity limit" # now try to pick the requested number of home systems # we will do this by calling find_home_systems, which takes a list of tuples defining the pools from which to pick # the home systems; it will use the pools in the order in which they appear in the list, so put better pools first # we will make two attempts: the first one with the filtered pools we just created, and tell find_home_systems # to start with the min_jumps jumps distance we calculated above, but not to go lower than # HS_MIN_DISTANCE_PRIORITY_LIMIT print "First attempt: trying to pick home systems from the filtered pools of preferred systems" pool_list = [ # the better pool is of course the one where all systems meet BOTH the min systems and planets limit (pool_matching_sys_and_planet_limit, "pool of systems that meet both the min systems and planets limit"), # next the less preferred pool where all systems at least meets the min systems limit # specify 0 as number of requested home systems to pick as much systems as possible (pool_matching_sys_limit, "pool of systems that meet at least the min systems limit"), ] home_systems = find_home_systems(num_home_systems, pool_list, min_jumps, HS_MIN_DISTANCE_PRIORITY_LIMIT) # check if the first attempt delivered a list with enough home systems # if not, we make our second attempt, this time disregarding the filtered pools and using all systems, starting # again with the min_jumps jump distance limit and specifying 0 as number of required home systems to pick as much # systems as possible if len(home_systems) < num_home_systems: print "Second attempt: trying to pick home systems from all systems" home_systems = find_home_systems(num_home_systems, [(systems, "complete pool")], min_jumps, 1) # check if the selection process delivered a list with enough home systems # if not, our galaxy obviously is too crowded, report an error and return an empty list if len(home_systems) < num_home_systems: report_error("Python generate_home_system_list: requested %d homeworlds in a galaxy with %d systems" % (num_home_systems, len(systems))) return [] # check if we got more home systems than we requested if len(home_systems) > num_home_systems: # yes: calculate the number of planets in the near vicinity of each system # and store that value with each system in a map hs_planets_in_vicinity_map = {s: count_planets_in_systems(get_systems_within_jumps(s, HS_VICINITY_RANGE)) for s in home_systems} # sort the home systems by the number of planets in their near vicinity using the map # now only pick the number of home systems we need, taking those with the highest number of planets home_systems = sorted(home_systems, key=hs_planets_in_vicinity_map.get, reverse=True)[:num_home_systems] # make sure all our home systems have a "real" star (that is, a star that is not a neutron star, black hole, # or even no star at all) and at least one planet in it for home_system in home_systems: # if this home system has no "real" star, change star type to a randomly selected "real" star if fo.sys_get_star_type(home_system) not in star_types_real: star_type = random.choice(star_types_real) print "Home system", home_system, "has star type", fo.sys_get_star_type(home_system),\ ", changing that to", star_type fo.sys_set_star_type(home_system, star_type) # if this home system has no planets, create one in a random orbit # we take random values for type and size, as these will be set to suitable values later if not fo.sys_get_planets(home_system): print "Home system", home_system, "has no planets, adding one" planet = fo.create_planet(random.choice(planet_sizes_real), random.choice(planet_types_real), home_system, random.randint(0, fo.sys_get_num_orbits(home_system) - 1), "") # if we couldn't create the planet, report an error and return an empty list if planet == fo.invalid_object(): report_error("Python generate_home_system_list: couldn't create planet in home system") return [] # finally, check again if all home systems meet the criteria of having the required minimum number of planets # within their near vicinity, if not, add the missing number of planets print "Checking if home systems have the required minimum of planets within the near vicinity..." for home_system in home_systems: # calculate the number of missing planets, and add them if this number is > 0 systems_in_vicinity = get_systems_within_jumps(home_system, HS_VICINITY_RANGE) num_systems_in_vicinity = len(systems_in_vicinity) num_planets_in_vicinity = count_planets_in_systems(systems_in_vicinity) num_planets_to_add = min_planets_in_vicinity_limit(num_systems_in_vicinity) - num_planets_in_vicinity print "Home system", home_system, "has", num_systems_in_vicinity, "systems and", num_planets_in_vicinity,\ "planets in the near vicinity, required minimum:", min_planets_in_vicinity_limit(num_systems_in_vicinity) if num_planets_to_add > 0: systems_in_vicinity.remove(home_system) # don't add planets to the home system, so remove it from the list add_planets_to_vicinity(systems_in_vicinity, num_planets_to_add, gsd) # as we've sorted the home system list before, lets shuffle it to ensure random order and return random.shuffle(home_systems) return home_systems
def setup_empire(empire, empire_name, home_system, starting_species, player_name): """ Sets up various aspects of an empire, like empire name, homeworld, etc. """ # set empire name, if no one is given, pick one randomly if not empire_name: print "No empire name set for player", player_name, ", picking one randomly" empire_name = next(empire_name_generator) fo.empire_set_name(empire, empire_name) print "Empire name for player", player_name, "is", empire_name # check starting species, if no one is given, pick one randomly if starting_species == "RANDOM" or not starting_species: print "Picking random starting species for player", player_name starting_species = next(starting_species_pool) print "Starting species for player", player_name, "is", starting_species statistics.empire_species[starting_species] += 1 # pick a planet from the specified home system as homeworld planet_list = fo.sys_get_planets(home_system) # if the system is empty, report an error and return false, indicating failure if not planet_list: report_error("Python setup_empire: got home system with no planets") return False homeworld = random.choice(planet_list) # set selected planet as empire homeworld with selected starting species fo.empire_set_homeworld(empire, homeworld, starting_species) # set homeworld focus # check if the preferred focus for the starting species is among # the foci available on the homeworld planet available_foci = fo.planet_available_foci(homeworld) preferred_focus = fo.species_preferred_focus(starting_species) if preferred_focus in available_foci: # if yes, set the homeworld focus to the preferred focus print "Player", player_name, ": setting preferred focus", preferred_focus, "on homeworld" fo.planet_set_focus(homeworld, preferred_focus) elif len(available_foci) > 0: # if no, and there is at least one available focus, # just take the first of the list if preferred_focus == "": print "Player", player_name, ": starting species", starting_species, "has no preferred focus, using",\ available_foci[0], "instead" else: print "Player", player_name, ": preferred focus", preferred_focus, "for starting species",\ starting_species, "not available on homeworld, using", available_foci[0], "instead" fo.planet_set_focus(homeworld, available_foci[0]) else: # if no focus is available on the homeworld, don't set any focus print "Player", player_name, ": no available foci on homeworld for starting species", starting_species # give homeworld starting buildings # use the list provided in scripting/starting_unlocks/buildings.inf print "Player", player_name, ": add starting buildings to homeworld" for building in load_string_list(os.path.join(fo.get_resource_dir(), "scripting/starting_unlocks/buildings.inf")): fo.create_building(building, homeworld, empire) # unlock starting techs, buildings, hulls, ship parts, etc. # use default content file print "Player", player_name, ": add unlocked items" for item in fo.load_item_spec_list(): fo.empire_unlock_item(empire, item.type, item.name) # add premade ship designs to empire print "Player", player_name, ": add premade ship designs" for ship_design in fo.design_get_premade_list(): fo.empire_add_ship_design(empire, ship_design) # add starting fleets to empire # use default content file print "Player", player_name, ": add starting fleets" fleet_plans = fo.load_fleet_plan_list() for fleet_plan in fleet_plans: # first, create the fleet fleet = fo.create_fleet(fleet_plan.name(), home_system, empire) # if the fleet couldn't be created, report an error and try to continue with the next fleet plan if fleet == fo.invalid_object(): report_error("Python setup empire: couldn't create fleet %s" % fleet_plan.name()) continue # second, iterate over the list of ship design names in the fleet plan for ship_design in fleet_plan.ship_designs(): # create a ship in the fleet # if the ship couldn't be created, report an error and try to continue with the next ship design if fo.create_ship("", ship_design, starting_species, fleet) == fo.invalid_object(): report_error("Python setup empire: couldn't create ship %s for fleet %s" % (ship_design, fleet_plan.name())) return True
def compile_home_system_list(num_home_systems, systems): """ Compiles a list with a requested number of home systems. """ # if the list of systems to choose home systems from is empty, report an error and return empty list if not systems: util.report_error( "Python generate_home_system_list: no systems to choose from") return [] # calculate an initial minimal number of jumps that the home systems should be apart, # based on the total number of systems to choose from and the requested number of home systems min_jumps = max(int(float(len(systems)) / float(num_home_systems * 2)), 5) # try to find the home systems, decrease the min jumps until enough systems can be found, or the min jump distance # gets reduced to 0 (meaning we don't have enough systems to choose from at all) while min_jumps > 0: print "Trying to find", num_home_systems, "home systems that are at least", min_jumps, "jumps apart" # try to find home systems... home_systems = find_systems_with_min_jumps_between( num_home_systems, systems, min_jumps) # ...check if we got enough... if len(home_systems) >= num_home_systems: # ...yes, we got what we need, so let's break out of the loop break print "Home system min jump conflict: %d systems and %d empires, tried %d min jump and failed"\ % (len(systems), num_home_systems, min_jumps) # ...no, decrease the min jump distance and try again min_jumps -= 1 # check if the loop above delivered a list with enough home systems, or if it exited because the min jump distance # has been decreased to 0 without finding enough systems # in that case, our galaxy obviously is too crowded, report an error and return an empty list if len(home_systems) < num_home_systems: util.report_error( "Python generate_home_system_list: requested %d homeworlds in a galaxy with %d systems" % (num_home_systems, len(systems))) return [] # make sure all our home systems have a "real" star (that is, a star that is not a neutron star, black hole, # or even no star at all) and at least one planet in it for home_system in home_systems: # if this home system has no "real" star, change star type to a randomly selected "real" star if fo.sys_get_star_type( home_system) not in starsystems.star_types_real: star_type = random.choice(starsystems.star_types_real) print "Home system", home_system, "has star type", fo.sys_get_star_type(home_system),\ ", changing that to", star_type fo.sys_set_star_type(home_system, star_type) # if this home system has no planets, create one in a random orbit # we take random values for type and size, as these will be set to suitable values later if not fo.sys_get_planets(home_system): print "Home system", home_system, "has no planets, adding one" planet = fo.create_planet( random.choice(planets.planet_sizes_real), random.choice(planets.planet_types_real), home_system, random.randint(0, fo.sys_get_num_orbits(home_system) - 1), "") # if we couldn't create the planet, report an error and return an empty list if planet == fo.invalid_object(): util.report_error( "Python generate_home_system_list: couldn't create planet in home system" ) return [] return home_systems
def generate_monsters(monster_freq, systems): """ Adds space monsters to systems. """ # first, calculate the basic chance for monster generation in a system # based on the monster frequency that has been passed # get the corresponding value for the specified monster frequency from the universe tables inverse_monster_chance = fo.monster_frequency(monster_freq) # as the value in the universe table is higher for a lower frequency, we have to invert it # exception: a value of 0 means no monsters, in this case return immediately if inverse_monster_chance <= 0: return basic_chance = 1.0 / float(inverse_monster_chance) print "Default monster spawn chance:", basic_chance expectation_tally = 0.0 actual_tally = 0 # get all monster fleets that have a spawn rate and limit both > 0 and at least one monster ship design in it # (a monster fleet with no monsters in it is pointless) and store them with a spawn counter in a dict # this counter will be set to the spawn limit initially and decreased every time the monster fleet is spawned fleet_plans = { fp: fp.spawn_limit() for fp in fo.load_monster_fleet_plan_list( "space_monster_spawn_fleets.txt") if fp.spawn_rate() > 0.0 and fp.spawn_limit() > 0 and fp.ship_designs() } # map nests to monsters for ease of reporting nest_name_map = dict( zip([ "KRAKEN_NEST_SPECIAL", "SNOWFLAKE_NEST_SPECIAL", "JUGGERNAUT_NEST_SPECIAL" ], ["SM_KRAKEN_1", "SM_SNOWFLAKE_1", "SM_JUGGERNAUT_1"])) tracked_plan_tries = {name: 0 for name in nest_name_map.values()} tracked_plan_counts = {name: 0 for name in nest_name_map.values()} tracked_plan_valid_locations = { fp: 0 for fp, limit in fleet_plans.iteritems() if fp.name() in tracked_plan_counts } tracked_nest_valid_locations = {nest: 0 for nest in nest_name_map} if not fleet_plans: return # dump a list of all monster fleets meeting these conditions and their properties to the log print "Monster fleets available for generation at game start:" for fleet_plan in fleet_plans: print "...", fleet_plan.name(), ": spawn rate", fleet_plan.spawn_rate( ), print "/ spawn limit", fleet_plan.spawn_limit(), print "/ effective chance", basic_chance * fleet_plan.spawn_rate(), if len(systems) < 1000: print "/ can be spawned at", len( [s for s in systems if fleet_plan.location(s)]), "systems" else: print # to terminate the print line if fleet_plan.name() in nest_name_map.values(): statistics.tracked_monsters_chance[ fleet_plan.name()] = basic_chance * fleet_plan.spawn_rate() # for each system in the list that has been passed to this function, find a monster fleet that can be spawned at # the system and which hasn't already been added too many times, then attempt to add that monster fleet by # testing the spawn rate chance for system in systems: # collect info for tracked monster nest valid locations for planet in fo.sys_get_planets(system): for nest in tracked_nest_valid_locations: #print "\t tracked monster check planet: %d size: %s for nest: %20s | result: %s" % (planet, fo.planet_get_size(planet), nest, fo.special_location(nest, planet)) if fo.special_location(nest, planet): tracked_nest_valid_locations[nest] += 1 # collect info for tracked monster valid locations for fp in tracked_plan_valid_locations: if fp.location(system): tracked_plan_valid_locations[fp] += 1 # filter out all monster fleets whose location condition allows this system and whose counter hasn't reached 0 suitable_fleet_plans = [ fp for fp, counter in fleet_plans.iteritems() if counter and fp.location(system) ] # if there are no suitable monster fleets for this system, continue with the next if not suitable_fleet_plans: continue # randomly select one monster fleet out of the suitable ones and then test if we want to add it to this system # by making a roll against the basic chance multiplied by the spawn rate of this monster fleet expectation_tally += basic_chance * sum( [fp.spawn_rate() for fp in suitable_fleet_plans]) / len(suitable_fleet_plans) fleet_plan = random.choice(suitable_fleet_plans) if fleet_plan.name() in tracked_plan_tries: tracked_plan_tries[fleet_plan.name()] += 1 if random.random() > basic_chance * fleet_plan.spawn_rate(): print "\t\t At system %4d rejected monster fleet %s from %d suitable fleets" % ( system, fleet_plan.name(), len(suitable_fleet_plans)) # no, test failed, continue with the next system continue actual_tally += 1 if fleet_plan.name() in tracked_plan_counts: tracked_plan_counts[fleet_plan.name()] += 1 # all prerequisites and the test have been met, now spawn this monster fleet in this system print "Spawn", fleet_plan.name(), "at", fo.get_name(system) # decrement counter for this monster fleet fleet_plans[fleet_plan] -= 1 # create monster fleet monster_fleet = fo.create_monster_fleet(system) # if fleet creation fails, report an error and try to continue with next system if monster_fleet == fo.invalid_object(): util.report_error( "Python generate_monsters: unable to create new monster fleet %s" % fleet_plan.name()) continue # add monsters to fleet for design in fleet_plan.ship_designs(): # create monster, if creation fails, report an error and try to continue with the next design if fo.create_monster(design, monster_fleet) == fo.invalid_object(): util.report_error( "Python generate_monsters: unable to create monster %s" % design) print "Actual # monster fleets placed: %d; Total Placement Expectation: %.1f" % ( actual_tally, expectation_tally) # finally, compile some statistics to be dumped to the log later statistics.monsters_summary = [(fp.name(), fp.spawn_limit() - counter) for fp, counter in fleet_plans.iteritems()] statistics.tracked_monsters_tries.update(tracked_plan_tries) statistics.tracked_monsters_summary.update(tracked_plan_counts) statistics.tracked_monsters_location_summary.update([ (fp.name(), count) for fp, count in tracked_plan_valid_locations.iteritems() ]) statistics.tracked_nest_location_summary.update([ (nest_name_map[nest], count) for nest, count in tracked_nest_valid_locations.items() ])
def generate_monsters(monster_freq, systems): """ Adds space monsters to systems. """ # first, calculate the basic chance for monster generation in a system # based on the monster frequency that has been passed # get the corresponding value for the specified monster frequency from the universe tables basic_chance = universe_tables.MONSTER_FREQUENCY[monster_freq] # a value of 0 means no monsters, in this case return immediately if basic_chance <= 0: return print "Default monster spawn chance:", basic_chance expectation_tally = 0.0 actual_tally = 0 # get all monster fleets that have a spawn rate and limit both > 0 and at least one monster ship design in it # (a monster fleet with no monsters in it is pointless) and store them in a list fleet_plans = fo.load_monster_fleet_plan_list() # create a map where we store a spawn counter for each monster fleet # this counter will be set to the spawn limit initially and decreased every time the monster fleet is spawned # this map (dict) needs to be separate from the list holding the fleet plans because the order in which items # are stored in a dict is undefined (can be different each time), which would result in different distribution # even when using the same seed for the RNG spawn_limits = {fp: fp.spawn_limit() for fp in fleet_plans if fp.spawn_rate() > 0.0 and fp.spawn_limit() > 0 and fp.ship_designs()} # map nests to monsters for ease of reporting nest_name_map = {"KRAKEN_NEST_SPECIAL": "SM_KRAKEN_1", "SNOWFLAKE_NEST_SPECIAL": "SM_SNOWFLAKE_1", "JUGGERNAUT_NEST_SPECIAL": "SM_JUGGERNAUT_1"} tracked_plan_tries = {name: 0 for name in nest_name_map.values()} tracked_plan_counts = {name: 0 for name in nest_name_map.values()} tracked_plan_valid_locations = {fp: 0 for fp in fleet_plans if fp.name() in tracked_plan_counts} if not fleet_plans: return universe = fo.get_universe() # Fleet plans that include ships capable of altering starlanes. # @content_tag{CAN_ALTER_STARLANES} universe_generator special handling # for fleets containing a hull design with this tag. fleet_can_alter_starlanes = {fp for fp in fleet_plans if any([universe.getGenericShipDesign(design).hull_type.hasTag("CAN_ALTER_STARLANES") for design in fp.ship_designs()])} # dump a list of all monster fleets meeting these conditions and their properties to the log print "Monster fleets available for generation at game start:" fp_location_cache = {} for fleet_plan in fleet_plans: print "...", fleet_plan.name(), ": spawn rate", fleet_plan.spawn_rate(), print "/ spawn limit", fleet_plan.spawn_limit(), print "/ effective chance", basic_chance * fleet_plan.spawn_rate(), fp_location_cache[fleet_plan] = set(fleet_plan.locations(systems)) print ("/ can be spawned at", len(fp_location_cache[fleet_plan]), "of", len(systems), "systems") if fleet_plan.name() in nest_name_map.values(): universe_statistics.tracked_monsters_chance[fleet_plan.name()] = basic_chance * fleet_plan.spawn_rate() # initialize a manager for monsters that can alter the map # required to prevent their placement from disjoining the map starlane_altering_monsters = StarlaneAlteringMonsters(systems) # collect info for tracked monster nest valid locations planets = [p for s in systems for p in fo.sys_get_planets(s)] tracked_nest_valid_locations = {nest: len(fo.special_locations(nest, planets)) for nest in nest_name_map} # for each system in the list that has been passed to this function, find a monster fleet that can be spawned at # the system and which hasn't already been added too many times, then attempt to add that monster fleet by # testing the spawn rate chance random.shuffle(systems) for system in systems: # collect info for tracked monster valid locations for fp in tracked_plan_valid_locations: if system in fp_location_cache[fp]: tracked_plan_valid_locations[fp] += 1 # filter out all monster fleets whose location condition allows this system and whose counter hasn't reached 0. suitable_fleet_plans = [fp for fp in fleet_plans if system in fp_location_cache[fp] and spawn_limits.get(fp, 0) and (fp not in fleet_can_alter_starlanes or starlane_altering_monsters.can_place_at(system, fp))] # if there are no suitable monster fleets for this system, continue with the next if not suitable_fleet_plans: continue # randomly select one monster fleet out of the suitable ones and then test if we want to add it to this system # by making a roll against the basic chance multiplied by the spawn rate of this monster fleet expectation_tally += basic_chance * sum([fp.spawn_rate() for fp in suitable_fleet_plans]) / len(suitable_fleet_plans) fleet_plan = random.choice(suitable_fleet_plans) if fleet_plan.name() in tracked_plan_tries: tracked_plan_tries[fleet_plan.name()] += 1 if random.random() > basic_chance * fleet_plan.spawn_rate(): print("\t\t At system %4d rejected monster fleet %s from %d suitable fleets" % (system, fleet_plan.name(), len(suitable_fleet_plans))) # no, test failed, continue with the next system continue actual_tally += 1 if fleet_plan.name() in tracked_plan_counts: tracked_plan_counts[fleet_plan.name()] += 1 # all prerequisites and the test have been met, now spawn this monster fleet in this system # create monster fleet try: if fleet_plan in fleet_can_alter_starlanes: starlane_altering_monsters.place(system, fleet_plan) else: populate_monster_fleet(fleet_plan, system) # decrement counter for this monster fleet spawn_limits[fleet_plan] -= 1 except MapGenerationError as err: report_error(str(err)) continue print "Actual # monster fleets placed: %d; Total Placement Expectation: %.1f" % (actual_tally, expectation_tally) # finally, compile some statistics to be dumped to the log later universe_statistics.monsters_summary = [ (fp.name(), fp.spawn_limit() - counter) for fp, counter in spawn_limits.iteritems() ] universe_statistics.tracked_monsters_tries.update(tracked_plan_tries) universe_statistics.tracked_monsters_summary.update(tracked_plan_counts) universe_statistics.tracked_monsters_location_summary.update( (fp.name(), count) for fp, count in tracked_plan_valid_locations.iteritems()) universe_statistics.tracked_nest_location_summary.update( (nest_name_map[nest], count) for nest, count in tracked_nest_valid_locations.items())
def compile_home_system_list(num_home_systems, systems): """ Compiles a list with a requested number of home systems. """ print "Compile home system list:", num_home_systems, "systems requested" # if the list of systems to choose home systems from is empty, report an error and return empty list if not systems: report_error( "Python generate_home_system_list: no systems to choose from") return [] # calculate an initial minimal number of jumps that the home systems should be apart, # based on the total number of systems to choose from and the requested number of home systems # Don't let min_jumps be larger than 10, because a larger number is really not at all needed and with large # galaxies an excessive amount of time can be used in failed attempts min_jumps = min( 10, max(int(float(len(systems)) / float(num_home_systems * 2)), 5)) # home systems must have a certain minimum of systems in their near vicinity # we will try to select our home systems from systems that match this criteria, if that fails, we will select our # home systems from all systems and add the missing number planets to the systems in their vicinity afterwards # the minimum planet limit and the jump range that defines the "near vicinity" are controlled by the # HS_* option constants in options.py (see there) # lets start by filtering out all systems from the pool we got passed into this function that match the criteria filtered_pool = [s for s in systems if has_min_planets_in_vicinity(s)] print "Filtering out systems that meet the minimum planets in the near vicinity condition yielded",\ len(filtered_pool), "systems" print "Using this as the preferred pool for home system selection" # now try to pick the requested number of home systems by calling find_home_systems # this function takes two pools, a "complete" pool and one with preferred systems # it will try to pick the home systems from the preferred pool first, so pass our filtered pool as preferred pool home_systems = find_home_systems(num_home_systems, systems, filtered_pool, min_jumps) # check if the selection process delivered a list with enough home systems # if not, our galaxy obviously is too crowded, report an error and return an empty list if len(home_systems) < num_home_systems: report_error( "Python generate_home_system_list: requested %d homeworlds in a galaxy with %d systems" % (num_home_systems, len(systems))) return [] # check if we got more home systems than we requested if len(home_systems) > num_home_systems: # yes: calculate the number of planets in the near vicinity of each system # and store that value with each system in a map hs_planets_in_vicinity_map = { s: count_planets_in_systems( get_systems_within_jumps(s, HS_VICINITY_RANGE)) for s in home_systems } # sort the home systems by the number of planets in their near vicinity using the map # now only pick the number of home systems we need, taking those with the highest number of planets home_systems = sorted(home_systems, key=hs_planets_in_vicinity_map.get, reverse=True)[:num_home_systems] # make sure all our home systems have a "real" star (that is, a star that is not a neutron star, black hole, # or even no star at all) and at least one planet in it for home_system in home_systems: # if this home system has no "real" star, change star type to a randomly selected "real" star if fo.sys_get_star_type(home_system) not in star_types_real: star_type = random.choice(star_types_real) print "Home system", home_system, "has star type", fo.sys_get_star_type(home_system),\ ", changing that to", star_type fo.sys_set_star_type(home_system, star_type) # if this home system has no planets, create one in a random orbit # we take random values for type and size, as these will be set to suitable values later if not fo.sys_get_planets(home_system): print "Home system", home_system, "has no planets, adding one" planet = fo.create_planet( random.choice(planet_sizes_real), random.choice(planet_types_real), home_system, random.randint(0, fo.sys_get_num_orbits(home_system) - 1), "") # if we couldn't create the planet, report an error and return an empty list if planet == fo.invalid_object(): report_error( "Python generate_home_system_list: couldn't create planet in home system" ) return [] # finally, check again if all home systems meet the criteria of having the required minimum number of planets # within their near vicinity, if not, add the missing number of planets print "Checking if home systems have the required minimum of planets within the near vicinity..." for home_system in home_systems: # calculate the number of missing planets, and add them if this number is > 0 systems_in_vicinity = get_systems_within_jumps(home_system, HS_VICINITY_RANGE) num_systems_in_vicinity = len(systems_in_vicinity) num_planets_in_vicinity = count_planets_in_systems(systems_in_vicinity) num_planets_to_add = min_planets_in_vicinity_limit( num_systems_in_vicinity) - num_planets_in_vicinity print "Home system", home_system, "has", num_systems_in_vicinity, "systems and", num_planets_in_vicinity,\ "planets in the near vicinity, required minimum:", min_planets_in_vicinity_limit(num_systems_in_vicinity) if num_planets_to_add > 0: systems_in_vicinity.remove( home_system ) # don't add planets to the home system, so remove it from the list add_planets_to_vicinity(systems_in_vicinity, num_planets_to_add) # as we've sorted the home system list before, lets shuffle it to ensure random order and return random.shuffle(home_systems) return home_systems
def name_star_systems(system_list): # choose star types and planet sizes, before choosing names, so naming can have special handling of Deep Space star_type_assignments = {} planet_assignments = {} position_list = [] for system in system_list: star_type = fo.sys_get_star_type(system) systemxy = fo.get_pos(system) star_type_assignments[systemxy] = star_type planet_assignments[systemxy] = fo.sys_get_planets(system) position_list.append(systemxy) # will name name a portion of stars on a group basis, where the stars of each group share the same base star name, # suffixed by different (default greek) letters or characters (options at top of file) star_name_map = {} star_names = names.get_name_list("STAR_NAMES") group_names = names.get_name_list("STAR_GROUP_NAMES") potential_group_names = [] individual_names = [] stargroup_words[:] = names.get_name_list("STAR_GROUP_WORDS") stargroup_chars[:] = names.get_name_list("STAR_GROUP_CHARS") stargroup_modifiers[:] = [stargroup_words, stargroup_chars][options.STAR_GROUPS_USE_CHARS] for starname in star_names: if len(starname) > 6: # if starname is long, don't allow it for groups individual_names.append(starname) continue # any names that already have a greek letter in them can only be used for individual stars, not groups for namepart in starname.split(): if namepart in greek_letters: individual_names.append(starname) break else: potential_group_names.append(starname) if not potential_group_names: potential_group_names.append("XYZZY") # ensure at least a portion of galaxy gets individual starnames num_systems = len(system_list) target_indiv_ratio = [options.TARGET_INDIV_RATIO_SMALL, options.TARGET_INDIV_RATIO_LARGE]\ [num_systems >= options.NAMING_LARGE_GALAXY_SIZE] # TODO improve the following calc to be more likely to hit target_indiv_ratio if more or less than # 50% potential_group_names used for groups num_individual_stars = int(max(min(num_systems * target_indiv_ratio, len(individual_names)+int(0.5 * len(potential_group_names))), num_systems - 0.8 * len(stargroup_modifiers) * (len(group_names)+int(0.5 * len(potential_group_names))))) star_group_size = 1 + int((num_systems - num_individual_stars) / (max(1, len(group_names)+int(0.5 * len(potential_group_names))))) # make group size a bit bigger than min necessary, at least a trio star_group_size = max(3, star_group_size) num_star_groups = 1 + int(num_systems/star_group_size) # initial value # first cluster all systems, then remove some to be individually named (otherwise groups can have too many # individually named systems in their middle). First remove any that are too small (only 1 or 2 systems). # The clusters with the most systems are generally the most closely spaced, and though they might make good # logical candidates for groups, their names are then prone to overlapping on the galaxy map, so after removing # small groups, remove the groups with the most systems. random.shuffle(position_list) # just to be sure it is randomized init_cluster_assgts = cluster_stars(position_list, num_star_groups) star_groups = {} for index_pos, index_group in enumerate(init_cluster_assgts): systemxy = position_list[index_pos] star_groups.setdefault(index_group, []).append(systemxy) indiv_systems = [] # remove groups with only one non-deep-system for groupindex, group_list in star_groups.items(): max_can_transfer = len(potential_group_names)-len(star_groups)+len(individual_names)-len(indiv_systems) if max_can_transfer <= 0: break elif max_can_transfer <= len(group_list): continue not_deep, deep_space = check_deep_space(group_list, star_type_assignments, planet_assignments) if len(not_deep) > 1: continue for systemxy in star_groups[groupindex]: indiv_systems.append(systemxy) del star_groups[groupindex] # remove tiny groups group_sizes = [(len(group), index) for index, group in star_groups.items()] group_sizes.sort() while len(indiv_systems) < num_individual_stars and len(group_sizes) > 0: groupsize, groupindex = group_sizes.pop() max_can_transfer = len(potential_group_names)-len(star_groups)+len(individual_names)-len(indiv_systems) if (max_can_transfer <= 0) or (groupsize > 2): break if max_can_transfer <= groupsize: continue for systemxy in star_groups[groupindex]: indiv_systems.append(systemxy) del star_groups[groupindex] # remove largest (likely most compact) groups while len(indiv_systems) < num_individual_stars and len(group_sizes) > 0: groupsize, groupindex = group_sizes.pop(-1) max_can_transfer = len(potential_group_names)-len(star_groups)+len(individual_names)-len(indiv_systems) if max_can_transfer <= 0: break if max_can_transfer <= groupsize: continue for systemxy in star_groups[groupindex]: indiv_systems.append(systemxy) del star_groups[groupindex] num_star_groups = len(star_groups) num_individual_stars = len(indiv_systems) random.shuffle(potential_group_names) random.shuffle(individual_names) random.shuffle(group_names) num_for_indiv = min(max(len(potential_group_names)/2, num_individual_stars+1-len(individual_names)), len(potential_group_names)) individual_names.extend(potential_group_names[:num_for_indiv]) group_names.extend(potential_group_names[num_for_indiv:]) #print "sampling for %d indiv names from list of %d total indiv names"%(num_individual_stars, len(individual_names)) indiv_name_sample = random.sample(individual_names, num_individual_stars) #indiv_name_assignments = zip([(pos.x, pos.y) for pos in position_list[:num_individual_stars]], indiv_name_sample) indiv_name_assignments = zip(indiv_systems, indiv_name_sample) star_name_map.update(indiv_name_assignments) #print "sampling for %d group names from list of %d total group names"%(num_star_groups, len(group_names)) if len(group_names) < num_star_groups: group_names.extend([names.random_name(6) for _ in range(num_star_groups - len(group_names))]) group_name_sample = random.sample(group_names, num_star_groups) for index_group, group_list in enumerate(sorted(star_groups.values())): star_name_map.update(name_group(group_list, group_name_sample[index_group], star_type_assignments, planet_assignments)) # assign names from star_name_map to star systems for system in system_list: fo.set_name(system, star_name_map.get(fo.get_pos(system), "") or random_star_name())
def setup_empire(empire, empire_name, home_system, starting_species, player_name): """ Sets up various aspects of an empire, like empire name, homeworld, etc. """ # set empire name, if no one is given, pick one randomly if not empire_name: print "No empire name set for player", player_name, ", picking one randomly" empire_name = next(empire_name_generator) fo.empire_set_name(empire, empire_name) print "Empire name for player", player_name, "is", empire_name # check starting species, if no one is given, pick one randomly if starting_species == "RANDOM" or not starting_species: print "Picking random starting species for player", player_name starting_species = next(starting_species_pool) print "Starting species for player", player_name, "is", starting_species universe_statistics.empire_species[starting_species] += 1 # pick a planet from the specified home system as homeworld planet_list = fo.sys_get_planets(home_system) # if the system is empty, report an error and return false, indicating failure if not planet_list: report_error("Python setup_empire: got home system with no planets") return False homeworld = random.choice(planet_list) # set selected planet as empire homeworld with selected starting species fo.empire_set_homeworld(empire, homeworld, starting_species) # set homeworld focus # check if the preferred focus for the starting species is among # the foci available on the homeworld planet available_foci = fo.planet_available_foci(homeworld) preferred_focus = fo.species_preferred_focus(starting_species) if preferred_focus in available_foci: # if yes, set the homeworld focus to the preferred focus print "Player", player_name, ": setting preferred focus", preferred_focus, "on homeworld" fo.planet_set_focus(homeworld, preferred_focus) elif len(available_foci) > 0: # if no, and there is at least one available focus, # just take the first of the list if preferred_focus == "": print "Player", player_name, ": starting species", starting_species, "has no preferred focus, using",\ available_foci[0], "instead" else: print "Player", player_name, ": preferred focus", preferred_focus, "for starting species",\ starting_species, "not available on homeworld, using", available_foci[0], "instead" fo.planet_set_focus(homeworld, available_foci[0]) else: # if no focus is available on the homeworld, don't set any focus print "Player", player_name, ": no available foci on homeworld for starting species", starting_species # give homeworld starting buildings print "Player", player_name, ": add starting buildings to homeworld" for item in fo.load_starting_buildings(): fo.create_building(item.name, homeworld, empire) # unlock starting techs, buildings, hulls, ship parts, etc. # use default content file print "Player", player_name, ": add unlocked items" for item in fo.load_item_spec_list(): fo.empire_unlock_item(empire, item.type, item.name) # add premade ship designs to empire print "Player", player_name, ": add premade ship designs" for ship_design in fo.design_get_premade_list(): fo.empire_add_ship_design(empire, ship_design) # add starting fleets to empire # use default content file print "Player", player_name, ": add starting fleets" fleet_plans = fo.load_fleet_plan_list() for fleet_plan in fleet_plans: # first, create the fleet fleet = fo.create_fleet(fleet_plan.name(), home_system, empire) # if the fleet couldn't be created, report an error and try to continue with the next fleet plan if fleet == fo.invalid_object(): report_error("Python setup empire: couldn't create fleet %s" % fleet_plan.name()) continue # second, iterate over the list of ship design names in the fleet plan for ship_design in fleet_plan.ship_designs(): # create a ship in the fleet # if the ship couldn't be created, report an error and try to continue with the next ship design if fo.create_ship("", ship_design, starting_species, fleet) == fo.invalid_object(): report_error( "Python setup empire: couldn't create ship %s for fleet %s" % (ship_design, fleet_plan.name())) return True
def execute_turn_events(): print "Executing turn events for turn", fo.current_turn() # creating fields systems = fo.get_systems() radius = fo.get_universe_width() / 2.0 if random() < max(0.0003 * radius, 0.03): if random() < 0.4: field_type = "FLD_MOLECULAR_CLOUD" size = 5.0 else: field_type = "FLD_ION_STORM" size = 5.0 x = y = radius dist_from_center = 0.0 while (dist_from_center < radius) or any( hypot(fo.get_x(s) - x, fo.get_y(s) - y) < 50.0 for s in systems): angle = random() * 2.0 * pi dist_from_center = radius + uniform( min(max(radius * 0.02, 10), 50.0), min(max(radius * 0.05, 20), 100.0)) x = radius + (dist_from_center * sin(angle)) y = radius + (dist_from_center * cos(angle)) print "...creating new", field_type, "field, at distance", dist_from_center, "from center" if fo.create_field(field_type, x, y, size) == fo.invalid_object(): print >> sys.stderr, "Turn events: couldn't create new field" # creating monsters gsd = fo.get_galaxy_setup_data() monster_freq = MONSTER_FREQUENCY[gsd.monsterFrequency] # monster freq ranges from 1/30 (= one monster per 30 systems) to 1/3 (= one monster per 3 systems) # (example: low monsters and 150 Systems results in 150 / 30 * 0.01 = 0.05) if monster_freq > 0 and random() < len(systems) * monster_freq * 0.01: #only spawn Krill at the moment, other monsters can follow in the future if random() < 1: monster_type = "SM_KRILL_1" else: monster_type = "SM_FLOATER" # search for systems without planets or fleets candidates = [ s for s in systems if len(fo.sys_get_planets(s)) <= 0 and len(fo.sys_get_fleets(s)) <= 0 ] if not candidates: print >> sys.stderr, "Turn events: unable to find system for monster spawn" else: system = choice(candidates) print "...creating new", monster_type, "at", fo.get_name(system) # create monster fleet monster_fleet = fo.create_monster_fleet(system) # if fleet creation fails, report an error if monster_fleet == fo.invalid_object(): print >> sys.stderr, "Turn events: unable to create new monster fleet" else: # create monster, if creation fails, report an error monster = fo.create_monster(monster_type, monster_fleet) if monster == fo.invalid_object(): print >> sys.stderr, "Turn events: unable to create monster in fleet" return True
def compile_home_system_list(num_home_systems, systems, gsd): """ Compiles a list with a requested number of home systems. """ print "Compile home system list:", num_home_systems, "systems requested" # if the list of systems to choose home systems from is empty, report an error and return empty list if not systems: report_error( "Python generate_home_system_list: no systems to choose from") return [] # calculate an initial minimal number of jumps that the home systems should be apart, # based on the total number of systems to choose from and the requested number of home systems # don't let min_jumps be either: # a.) larger than a defined limit, because an unreasonably large number is really not at all needed, # and with large galaxies an excessive amount of time can be used in failed attempts # b.) lower than the minimum jump distance limit that should be considered high priority (see options.py), # otherwise no attempt at all would be made to enforce the other requirements for home systems (see below) min_jumps = min( HS_MAX_JUMP_DISTANCE_LIMIT, max(int(float(len(systems)) / float(num_home_systems * 2)), HS_MIN_DISTANCE_PRIORITY_LIMIT)) # home systems must have a certain minimum of systems and planets in their near vicinity # we will try to select our home systems from systems that match this criteria, if that fails, we will select our # home systems from all systems and add the missing number planets to the systems in their vicinity afterwards # the minimum system and planet limit and the jump range that defines the "near vicinity" are controlled by the # HS_* option constants in options.py (see there) # we start by building two additional pools of systems: one that contains all systems that match the criteria # completely (meets the min systems and planets limit), and one that contains all systems that match the criteria # at least partially (meets the min systems limit) pool_matching_sys_and_planet_limit = [] pool_matching_sys_limit = [] for system in systems: systems_in_vicinity = fo.systems_within_jumps_unordered( HS_VICINITY_RANGE, [system]) if len(systems_in_vicinity) >= HS_MIN_SYSTEMS_IN_VICINITY: pool_matching_sys_limit.append(system) if count_planets_in_systems( systems_in_vicinity) >= min_planets_in_vicinity_limit( len(systems_in_vicinity)): pool_matching_sys_and_planet_limit.append(system) print( len(pool_matching_sys_and_planet_limit), "systems meet the min systems and planets in the near vicinity limit") print len(pool_matching_sys_limit ), "systems meet the min systems in the near vicinity limit" # now try to pick the requested number of home systems # we will do this by calling find_home_systems, which takes a list of tuples defining the pools from which to pick # the home systems; it will use the pools in the order in which they appear in the list, so put better pools first # we will make two attempts: the first one with the filtered pools we just created, and tell find_home_systems # to start with the min_jumps jumps distance we calculated above, but not to go lower than # HS_MIN_DISTANCE_PRIORITY_LIMIT print "First attempt: trying to pick home systems from the filtered pools of preferred systems" pool_list = [ # the better pool is of course the one where all systems meet BOTH the min systems and planets limit (pool_matching_sys_and_planet_limit, "pool of systems that meet both the min systems and planets limit"), # next the less preferred pool where all systems at least meets the min systems limit # specify 0 as number of requested home systems to pick as much systems as possible (pool_matching_sys_limit, "pool of systems that meet at least the min systems limit"), ] home_systems = find_home_systems(num_home_systems, pool_list, min_jumps, HS_MIN_DISTANCE_PRIORITY_LIMIT) # check if the first attempt delivered a list with enough home systems # if not, we make our second attempt, this time disregarding the filtered pools and using all systems, starting # again with the min_jumps jump distance limit and specifying 0 as number of required home systems to pick as much # systems as possible if len(home_systems) < num_home_systems: print "Second attempt: trying to pick home systems from all systems" home_systems = find_home_systems(num_home_systems, [(systems, "complete pool")], min_jumps, 1) # check if the selection process delivered a list with enough home systems # if not, our galaxy obviously is too crowded, report an error and return an empty list if len(home_systems) < num_home_systems: report_error( "Python generate_home_system_list: requested %d homeworlds in a galaxy with %d systems" % (num_home_systems, len(systems))) return [] # check if we got more home systems than we requested if len(home_systems) > num_home_systems: # yes: calculate the number of planets in the near vicinity of each system # and store that value with each system in a map hs_planets_in_vicinity_map = { s: calculate_home_system_merit(s) for s in home_systems } # sort the home systems by the number of planets in their near vicinity using the map # now only pick the number of home systems we need, taking those with the highest number of planets home_systems = sorted(home_systems, key=hs_planets_in_vicinity_map.get, reverse=True)[:num_home_systems] # make sure all our home systems have a "real" star (that is, a star that is not a neutron star, black hole, # or even no star at all) and at least one planet in it for home_system in home_systems: # if this home system has no "real" star, change star type to a randomly selected "real" star if fo.sys_get_star_type(home_system) not in star_types_real: star_type = random.choice(star_types_real) print "Home system", home_system, "has star type", fo.sys_get_star_type(home_system),\ ", changing that to", star_type fo.sys_set_star_type(home_system, star_type) # if this home system has no planets, create one in a random orbit # we take random values for type and size, as these will be set to suitable values later if not fo.sys_get_planets(home_system): print "Home system", home_system, "has no planets, adding one" planet = fo.create_planet( random.choice(planet_sizes_real), random.choice(planet_types_real), home_system, random.randint(0, fo.sys_get_num_orbits(home_system) - 1), "") # if we couldn't create the planet, report an error and return an empty list if planet == fo.invalid_object(): report_error( "Python generate_home_system_list: couldn't create planet in home system" ) return [] # finally, check again if all home systems meet the criteria of having the required minimum number of planets # within their near vicinity, if not, add the missing number of planets print "Checking if home systems have the required minimum of planets within the near vicinity..." for home_system in home_systems: # calculate the number of missing planets, and add them if this number is > 0 systems_in_vicinity = fo.systems_within_jumps_unordered( HS_VICINITY_RANGE, [home_system]) num_systems_in_vicinity = len(systems_in_vicinity) num_planets_in_vicinity = count_planets_in_systems(systems_in_vicinity) num_planets_to_add = min_planets_in_vicinity_limit( num_systems_in_vicinity) - num_planets_in_vicinity print "Home system", home_system, "has", num_systems_in_vicinity, "systems and", num_planets_in_vicinity,\ "planets in the near vicinity, required minimum:", min_planets_in_vicinity_limit(num_systems_in_vicinity) if num_planets_to_add > 0: systems_in_vicinity.remove( home_system ) # don't add planets to the home system, so remove it from the list # sort the systems_in_vicinity before adding, since the C++ engine doesn't guarrantee the same # platform independence as python. add_planets_to_vicinity(sorted(systems_in_vicinity), num_planets_to_add, gsd) # as we've sorted the home system list before, lets shuffle it to ensure random order and return random.shuffle(home_systems) return home_systems
def execute_turn_events(): print("Executing turn events for turn", fo.current_turn()) # creating fields systems = fo.get_systems() radius = fo.get_universe_width() / 2.0 field_types = [ "FLD_MOLECULAR_CLOUD", "FLD_ION_STORM", "FLD_NANITE_SWARM", "FLD_METEOR_BLIZZARD", "FLD_VOID_RIFT" ] if random() < max(0.00015 * radius, 0.03): field_type = choice(field_types) size = 5.0 x = y = radius dist_from_center = uniform(0.35, 1.0) * radius angle = random() * 2.0 * pi x = radius + (dist_from_center * sin(angle)) y = radius + (dist_from_center * cos(angle)) print("...creating new", field_type, "field, at distance", dist_from_center, "from center") if fo.create_field(field_type, x, y, size) == fo.invalid_object(): print("Turn events: couldn't create new field", file=sys.stderr) # creating monsters gsd = fo.get_galaxy_setup_data() monster_freq = MONSTER_FREQUENCY[gsd.monsterFrequency] # monster freq ranges from 1/30 (= one monster per 30 systems) to 1/3 (= one monster per 3 systems) # (example: low monsters and 150 Systems results in 150 / 30 * 0.01 = 0.05) if monster_freq > 0 and random() < len(systems) * monster_freq * 0.01: # only spawn Krill at the moment, other monsters can follow in the future if random() < 1: monster_type = "SM_KRILL_1" else: monster_type = "SM_FLOATER" # search for systems without planets or fleets candidates = [ s for s in systems if len(fo.sys_get_planets(s)) <= 0 and len(fo.sys_get_fleets(s)) <= 0 ] if not candidates: print("Turn events: unable to find system for monster spawn", file=sys.stderr) else: system = choice(candidates) print("...creating new", monster_type, "at", fo.get_name(system)) # create monster fleet monster_fleet = fo.create_monster_fleet(system) # if fleet creation fails, report an error if monster_fleet == fo.invalid_object(): print("Turn events: unable to create new monster fleet", file=sys.stderr) else: # create monster, if creation fails, report an error monster = fo.create_monster(monster_type, monster_fleet) if monster == fo.invalid_object(): print("Turn events: unable to create monster in fleet", file=sys.stderr) return True
def generate_natives(native_freq, systems, empire_home_systems): """ Adds non-empire-affiliated native populations to planets. """ # first, calculate the chance for natives on a planet based on the native frequency that has been passed # get the corresponding value for the specified natives frequency from the universe tables inverse_native_chance = fo.native_frequency(native_freq) # as the value in the universe table is higher for a lower frequency, we have to invert it # exception: a value of 0 means no natives, in this case return immediately if inverse_native_chance <= 0: return native_chance = 1.0 / float(inverse_native_chance) # compile a list of planets where natives can be placed # select only planets sufficiently far away from player home systems native_safe_planets = [] # list of planets safe for natives for candidate in systems: if not is_too_close_to_empire_home_systems(candidate, empire_home_systems): # this system is sufficiently far away from all player homeworlds, so add it's planets to our list native_safe_planets += fo.sys_get_planets(candidate) print "Number of planets far enough from players for natives to be allowed:", len(native_safe_planets) # if there are no "native safe" planets at all, we can stop here if not native_safe_planets: return # get all native species native_species = fo.get_native_species() print "Species that can be added as natives:" print "... " + "\n... ".join(native_species) # create a map with a list for each planet type containing the species # for which this planet type is a good environment # we will need this afterwards when picking natives for a planet natives_for_planet_type.clear() # just to be safe natives_for_planet_type.update( {planet_type: [] for planet_type in planets.planet_types} ) planet_types_for_natives.clear() planet_types_for_natives.update( {species: set() for species in native_species} ) # iterate over all native species we got for species in native_species: # check the planet environment for all planet types for this species for planet_type in planets.planet_types: # if this planet type is a good environment for the species, add it to the list for this planet type if fo.species_get_planet_environment(species, planet_type) == fo.planetEnvironment.good: natives_for_planet_type[planet_type].append(species) planet_types_for_natives[species].add(planet_type) # randomly add species to planets # iterate over the list of "native safe" planets we compiled earlier for candidate in native_safe_planets: # select a native species to put on this planet planet_type = fo.planet_get_type(candidate) # check if we have any native species that like this planet type if not natives_for_planet_type[planet_type]: # no, continue with next planet continue statistics.potential_native_planet_summary[planet_type] += 1 # make a "roll" against the chance for natives to determine if we shall place natives on this planet if random.random() > native_chance: # no, continue with next planet continue statistics.settled_native_planet_summary[planet_type] += 1 # randomly pick one of the native species available for this planet type natives = random.choice(natives_for_planet_type[planet_type]) # put the selected natives on the planet fo.planet_set_species(candidate, natives) # set planet as homeworld for that species fo.species_add_homeworld(natives, candidate) # set planet focus # check if the preferred focus for the native species is among the foci available on this planet available_foci = fo.planet_available_foci(candidate) preferred_focus = fo.species_preferred_focus(natives) if preferred_focus in available_foci: # if yes, set the planet focus to the preferred focus fo.planet_set_focus(candidate, preferred_focus) elif available_foci: # if no, and there is at least one available focus, just take the first of the list # otherwise don't set any focus fo.planet_set_focus(candidate, available_foci[0]) print "Added native", natives, "to planet", fo.get_name(candidate) # increase the statistics counter for this native species, so a species summary can be dumped to the log later statistics.species_summary[natives] += 1
def generate_monsters(monster_freq, systems): """ Adds space monsters to systems. """ # first, calculate the basic chance for monster generation in a system # based on the monster frequency that has been passed # get the corresponding value for the specified monster frequency from the universe tables basic_chance = universe_tables.MONSTER_FREQUENCY[monster_freq] # a value of 0 means no monsters, in this case return immediately if basic_chance <= 0: return print "Default monster spawn chance:", basic_chance expectation_tally = 0.0 actual_tally = 0 # get all monster fleets that have a spawn rate and limit both > 0 and at least one monster ship design in it # (a monster fleet with no monsters in it is pointless) and store them in a list fleet_plans = fo.load_monster_fleet_plan_list() # create a map where we store a spawn counter for each monster fleet # this counter will be set to the spawn limit initially and decreased every time the monster fleet is spawned # this map (dict) needs to be separate from the list holding the fleet plans because the order in which items # are stored in a dict is undefined (can be different each time), which would result in different distribution # even when using the same seed for the RNG spawn_limits = {fp: fp.spawn_limit() for fp in fleet_plans if fp.spawn_rate() > 0.0 and fp.spawn_limit() > 0 and fp.ship_designs()} # map nests to monsters for ease of reporting nest_name_map = dict(zip(["KRAKEN_NEST_SPECIAL", "SNOWFLAKE_NEST_SPECIAL", "JUGGERNAUT_NEST_SPECIAL"], ["SM_KRAKEN_1", "SM_SNOWFLAKE_1", "SM_JUGGERNAUT_1"])) tracked_plan_tries = {name: 0 for name in nest_name_map.values()} tracked_plan_counts = {name: 0 for name in nest_name_map.values()} tracked_plan_valid_locations = {fp: 0 for fp in fleet_plans if fp.name() in tracked_plan_counts} tracked_nest_valid_locations = {nest: 0 for nest in nest_name_map} if not fleet_plans: return # dump a list of all monster fleets meeting these conditions and their properties to the log print "Monster fleets available for generation at game start:" for fleet_plan in fleet_plans: print "...", fleet_plan.name(), ": spawn rate", fleet_plan.spawn_rate(), print "/ spawn limit", fleet_plan.spawn_limit(), print "/ effective chance", basic_chance * fleet_plan.spawn_rate(), if len(systems) < 1000: print "/ can be spawned at", len([s for s in systems if fleet_plan.location(s)]), "systems" else: print # to terminate the print line if fleet_plan.name() in nest_name_map.values(): statistics.tracked_monsters_chance[fleet_plan.name()] = basic_chance * fleet_plan.spawn_rate() # for each system in the list that has been passed to this function, find a monster fleet that can be spawned at # the system and which hasn't already been added too many times, then attempt to add that monster fleet by # testing the spawn rate chance for system in systems: # collect info for tracked monster nest valid locations for planet in fo.sys_get_planets(system): for nest in tracked_nest_valid_locations: #print "\t tracked monster check planet: %d size: %s for nest: %20s | result: %s" % (planet, fo.planet_get_size(planet), nest, fo.special_location(nest, planet)) if fo.special_location(nest, planet): tracked_nest_valid_locations[nest] += 1 # collect info for tracked monster valid locations for fp in tracked_plan_valid_locations: if fp.location(system): tracked_plan_valid_locations[fp] += 1 # filter out all monster fleets whose location condition allows this system and whose counter hasn't reached 0 suitable_fleet_plans = [fp for fp in fleet_plans if spawn_limits[fp] and fp.location(system)] # if there are no suitable monster fleets for this system, continue with the next if not suitable_fleet_plans: continue # randomly select one monster fleet out of the suitable ones and then test if we want to add it to this system # by making a roll against the basic chance multiplied by the spawn rate of this monster fleet expectation_tally += basic_chance * sum([fp.spawn_rate() for fp in suitable_fleet_plans]) / len(suitable_fleet_plans) fleet_plan = random.choice(suitable_fleet_plans) if fleet_plan.name() in tracked_plan_tries: tracked_plan_tries[fleet_plan.name()] += 1 if random.random() > basic_chance * fleet_plan.spawn_rate(): print "\t\t At system %4d rejected monster fleet %s from %d suitable fleets" % (system, fleet_plan.name(), len(suitable_fleet_plans)) # no, test failed, continue with the next system continue actual_tally += 1 if fleet_plan.name() in tracked_plan_counts: tracked_plan_counts[fleet_plan.name()] += 1 # all prerequisites and the test have been met, now spawn this monster fleet in this system print "Spawn", fleet_plan.name(), "at", fo.get_name(system) # decrement counter for this monster fleet spawn_limits[fleet_plan] -= 1 # create monster fleet monster_fleet = fo.create_monster_fleet(system) # if fleet creation fails, report an error and try to continue with next system if monster_fleet == fo.invalid_object(): util.report_error("Python generate_monsters: unable to create new monster fleet %s" % fleet_plan.name()) continue # add monsters to fleet for design in fleet_plan.ship_designs(): # create monster, if creation fails, report an error and try to continue with the next design if fo.create_monster(design, monster_fleet) == fo.invalid_object(): util.report_error("Python generate_monsters: unable to create monster %s" % design) print "Actual # monster fleets placed: %d; Total Placement Expectation: %.1f" % (actual_tally, expectation_tally) # finally, compile some statistics to be dumped to the log later statistics.monsters_summary = [(fp.name(), fp.spawn_limit() - counter) for fp, counter in spawn_limits.iteritems()] statistics.tracked_monsters_tries.update(tracked_plan_tries) statistics.tracked_monsters_summary.update(tracked_plan_counts) statistics.tracked_monsters_location_summary.update([(fp.name(), count) for fp, count in tracked_plan_valid_locations.iteritems()]) statistics.tracked_nest_location_summary.update([(nest_name_map[nest], count) for nest, count in tracked_nest_valid_locations.items()])