def explore_path(path: str, loader: AssetLoader, excludes: Iterable[str], verbose=False, disable_debug=False): '''Run hierarchy discovery over every matching asset within the given path.''' excludes = set(excludes) logger.info('Discovering hierarchy in path: %s', path) n = 0 with ue_parsing_context(properties=False): asset_iterator = loader.find_assetnames('.*', path, exclude=excludes, extension=asset_extensions, return_extension=True) for (assetname, ext) in asset_iterator: n += 1 if verbose and n % 200 == 0: logger.info(assetname) try: asset = loader.load_asset(assetname, quiet=disable_debug) except AssetLoadException: logger.warning("Failed to load asset: %s", assetname) continue try: _ingest_asset(asset, loader, ext) except AssetLoadException: logger.warning("Failed to check parentage of %s", assetname) except MissingParent as ex: logger.exception("Missing parent for %s", assetname) raise MissingParent from ex # Remove maps from the cache immediately as they are large and cannot be inherited from if ext == '.umap': loader.cache.remove(assetname)
def gather_results(loader: AssetLoader, path: str, **kwargs) -> Tuple[Set[str], Set[str]]: kwargs.setdefault('extension', ['.txt']) if 'invert' in kwargs: raise ValueError('invert cannot appear in kwargs') normal = set(loader.find_assetnames(path, **kwargs)) inverted = set(loader.find_assetnames(path, **kwargs, invert=True)) return (normal, inverted)
def test_defaults(loader: AssetLoader): loader.wipe_cache() asset = loader[TEST_PGD_PKG] assert asset.is_linked assert asset.has_properties assert not asset.has_bulk_data loader.wipe_cache() with ue_parsing_context(): asset = loader[TEST_PGD_PKG] assert asset.is_linked assert asset.has_properties assert not asset.has_bulk_data
def test_bulk_data(loader: AssetLoader): loader.wipe_cache() with ue_parsing_context(bulk_data=False): asset = loader[TEST_PGD_PKG] assert not asset.has_bulk_data # Check asset is re-parsed when more data is requested with ue_parsing_context(bulk_data=True): asset = loader[TEST_PGD_PKG] assert asset.has_bulk_data loader.wipe_cache() with ue_parsing_context(bulk_data=True): asset = loader[TEST_PGD_PKG] assert asset.has_bulk_data
def test_properties(loader: AssetLoader): loader.wipe_cache() with ue_parsing_context(properties=False): asset = loader[TEST_PGD_PKG] assert not asset.has_properties # Check asset is re-parsed when more data is requested with ue_parsing_context(properties=True): asset = loader[TEST_PGD_PKG] assert asset.has_properties loader.wipe_cache() with ue_parsing_context(properties=True): asset = loader[TEST_PGD_PKG] assert asset.has_properties
def test_linking(loader: AssetLoader): loader.wipe_cache() with ue_parsing_context(link=False): asset = loader[TEST_PGD_PKG] assert not asset.is_linked # Check asset is re-parsed when more data is requested with ue_parsing_context(link=True): asset = loader[TEST_PGD_PKG] assert asset.is_linked loader.wipe_cache() with ue_parsing_context(link=True): asset = loader[TEST_PGD_PKG] assert asset.is_linked
def gather_breeding_data(props, loader: AssetLoader) -> Dict[str, Any]: data: Dict[str, Any] = dict(gestationTime=0, incubationTime=0) gestation_breeding = stat_value(props, 'bUseBabyGestation', 0, False) has_eggs = False if props['FertilizedEggItemsToSpawn'][0] and props[ 'FertilizedEggItemsToSpawn'][0][-1].values: # eggs = list(filter(lambda v: v and str(v) != 'None', props['FertilizedEggItemsToSpawn'][0][-1].values)) eggs = [ egg for egg in props['FertilizedEggItemsToSpawn'][0][-1].values if str(egg) != 'None' ] has_eggs = bool(eggs) if gestation_breeding: gestation_speed = stat_value(props, 'BabyGestationSpeed', 0, BABYGESTATIONSPEED_DEFAULT) extra_gestation_speed_m = stat_value( props, 'ExtraBabyGestationSpeedMultiplier', 0, 1.0) # TODO: Verify if these should really default to 1 - seems odd gestation_speed = gestation_speed or 1 extra_gestation_speed_m = extra_gestation_speed_m or 1 # 'gestationTime' = 1 / (Baby Gestation Speed × Extra Baby Gestation Speed Multiplier) data['gestationTime'] = ( 1 / gestation_speed / extra_gestation_speed_m ) if gestation_speed and extra_gestation_speed_m else None elif has_eggs: fert_egg_asset = loader.load_related(eggs[0]) fert_egg_props = ark.mod.gather_properties(fert_egg_asset) egg_decay = stat_value(fert_egg_props, 'EggLoseDurabilityPerSecond', 0, 1) extra_egg_decay_m = stat_value( fert_egg_props, 'ExtraEggLoseDurabilityPerSecondMultiplier', 0, 1) # 'incubationTime' = 100 / (Egg Lose Durability Per Second × Extra Egg Lose Durability Per Second Multiplier) data['incubationTime'] = ( 100 / egg_decay / extra_egg_decay_m) if egg_decay and extra_egg_decay_m else None data['eggTempMin'] = stat_value(fert_egg_props, 'EggMinTemperature', 0) data['eggTempMax'] = stat_value(fert_egg_props, 'EggMaxTemperature', 0) # 'maturationTime' = 1 / (Baby Age Speed × Extra Baby Age Speed Multiplier) baby_age_speed = stat_value(props, 'BabyAgeSpeed', 0, 1) extra_baby_age_speed_m = stat_value(props, 'ExtraBabyAgeSpeedMultiplier', 0, 1) data['maturationTime'] = ( 1 / baby_age_speed / extra_baby_age_speed_m ) if baby_age_speed and extra_baby_age_speed_m else None data['matingCooldownMin'] = stat_value( props, 'NewFemaleMinTimeBetweenMating', 0, FEMALE_MINTIMEBETWEENMATING_DEFAULT) data['matingCooldownMax'] = stat_value( props, 'NewFemaleMaxTimeBetweenMating', 0, FEMALE_MAXTIMEBETWEENMATING_DEFAULT) return data
def gather_pgd_colors(props: PriorityPropDict, loader: AssetLoader, require_override=True) -> Tuple[Optional[Sequence[ColorEntry]], Optional[Sequence[ColorEntry]]]: '''Gather color and dye definitions from a PrimalGameData asset.''' colors: Optional[List[ColorEntry]] = list() dyes: Optional[List[ColorEntry]] = list() # Collect the color definitions color_def_overrides = props['ColorDefinitions'][0] if require_override and len(color_def_overrides) == 1: colors = None else: color_defs = color_def_overrides[-1].values colors = list() for definition in ((entry.as_dict() for entry in color_defs)): name = definition.get('ColorName', None) or '~~unset~~' value = definition.get('ColorValue', None) color = value.values[0].as_tuple() if value else None colors.append((str(name), color)) # Collect the dye definitions dye_def_overrides = props['MasterDyeList'][0] if require_override and len(dye_def_overrides) == 1: dyes = None else: dye_defs = props['MasterDyeList'][0][-1].values dyes = list() for dye_asset in (loader.load_related(entry) for entry in dye_defs): dye_props = gather_properties(dye_asset) name = stat_value(dye_props, 'DescriptiveNameBase', 0, None) or '~~unset~~' value = dye_props['DyeColor'][0][-1] color = value.values[0].as_tuple() if value else None dyes.append((str(name), color)) return (colors, dyes)
def fixture_simple_loader() -> AssetLoader: loader = AssetLoader( assetpath=DATA_PATH, modresolver=MockModResolver(), cache_manager=MockCacheManager(), rewrites={}, mod_aliases={}, ) return loader
def get_species_from_mod(asset: UAsset, loader: AssetLoader = None) -> list: loader = loader or asset.loader assert loader and asset.assetname this_mod = loader.get_mod_name(asset.assetname) mod_species = set() # Gather species from the remapped NPCs list for _, toPkg in get_mod_remapped_npcs(asset): to_mod = loader.get_mod_name(toPkg) if to_mod == this_mod: mod_species.add(toPkg) # Gather species from the remapped spawn zones for _, toPkgs in get_mod_remapped_spawners(asset): for toPkg in toPkgs: to_mod = loader.get_mod_name(toPkg) if to_mod == this_mod: mod_species.add(toPkg) return sorted(list(mod_species))
def _fetchModTitleFromPGD(self, moddata): resolver = FixedModResolver({moddata['name']: moddata['id']}) loader = AssetLoader(resolver, self.asset_path) pkg = moddata['package'] if pkg: pgd_asset = loader[moddata['package']] title = pgd_asset.default_export.properties.get_property( 'ModName').value else: title = moddata['id'] return title
def _fetch_mod_title_from_pgd(self, moddata): resolver = FixedModResolver({moddata['name']: moddata['id']}) loader = AssetLoader(modresolver=resolver, assetpath=self.asset_path) pkg = moddata['package'] if pkg: pgd_asset = loader[moddata['package']] title = pgd_asset.default_export.properties.get_property('ModName', fallback=None) if title: return str(title) return None
def createLoader(self) -> AssetLoader: '''Create an asset loader pointing at the managed game install.''' rewrites = get_overrides().rewrites.assets or dict() mod_aliases = self.config.combine_mods.src_to_aliases modresolver = ManagedModResolver(self) loader = AssetLoader( modresolver=modresolver, assetpath=self.asset_path, rewrites=rewrites, mod_aliases=mod_aliases, ) return loader
def _ingest_export(export: ExportTableItem, loader: AssetLoader): current_cls: ExportTableItem = export segment: Optional[Node[str]] = None fullname = current_cls.fullname assert fullname # We may have already covered this while traversing parents if fullname in tree: return while True: # Extend unsaved segment old_segment = segment segment = Node(fullname) if old_segment: segment.add(old_segment) # Get name of parent class parent_name = _get_parent_cls(current_cls) if not parent_name: raise MissingParent(f'Unable to find parent of {fullname}') # Is the parent present in the tree? anchor_point = tree.get(parent_name, None) # If we've risen outside /Game but didn't find a match, add it to the root and complain if not anchor_point and not parent_name.startswith('/Game'): logger.warning( f'Internal class {parent_name} missing from pre-defined hierarchy' ) tree.add(ROOT_NAME, parent_name) anchor_point = tree.get(parent_name, None) if anchor_point: # Insert segment and finish tree.insert_segment(parent_name, segment) return # Load parent class and replace current parent_cls = loader.load_class(parent_name) current_cls = parent_cls fullname = current_cls.fullname assert fullname
def gather_pgd_colors( asset: UAsset, props: PrimalGameData, loader: AssetLoader, require_override=True ) -> Tuple[Optional[Sequence[ColorEntry]], Optional[Sequence[ColorEntry]]]: '''Gather color and dye definitions from a PrimalGameData asset.''' colors: Optional[List[ColorEntry]] = list() dyes: Optional[List[ColorEntry]] = list() # Collect the color definitions color_def_overrides = props.ColorDefinitions[0] if require_override and color_def_overrides.asset != asset: colors = None else: color_defs = color_def_overrides.values colors = list() for definition in ((entry.as_dict() for entry in color_defs)): name = definition.get('ColorName', None) or '~~unset~~' value = definition.get('ColorValue', None) color = value.values[0].as_tuple() if value else None colors.append((str(name), color)) # Collect the dye definitions dye_def_overrides = props.MasterDyeList[0] if require_override and dye_def_overrides.asset != asset: dyes = None else: dye_defs = dye_def_overrides.values dyes = list() for dye_asset in (loader.load_related(entry) for entry in dye_defs): assert dye_asset and dye_asset.default_export dye_props: PrimalItem_Dye = ue.gathering.gather_properties( dye_asset.default_export) name = dye_props.DescriptiveNameBase[0] or '~~unset~~' value = dye_props.DyeColor[0] color = value.values[0].as_tuple() if value else None dyes.append((str(name), color)) return (colors, dyes)
def gather_breeding_data(char_props: PrimalDinoCharacter, loader: AssetLoader) -> Dict[str, Any]: data: Dict[str, Any] = dict(gestationTime=0, incubationTime=0) gestation_breeding = char_props.bUseBabyGestation[0] fert_eggs = char_props.get('FertilizedEggItemsToSpawn', 0, None) fert_egg_weights = char_props.get('FertilizedEggWeightsToSpawn', 0, None) if gestation_breeding: gestation_speed = char_props.BabyGestationSpeed[0].rounded_value extra_gestation_speed_m = char_props.ExtraBabyGestationSpeedMultiplier[ 0].rounded_value try: data['gestationTime'] = cd(1 / gestation_speed / extra_gestation_speed_m) except ZeroDivisionError: logger.warning( f"Species {char_props.get_source().asset.assetname} tried dividing by zero for its gestationTime" ) elif fert_eggs and fert_eggs.values: eggs: List[Tuple[ObjectProperty, float]] = [] for index, egg in enumerate(fert_eggs.values): weight = fert_egg_weights.values[index] if fert_egg_weights else 1 # Verify the egg is a valid Object and that weight isn't 0 if str(egg) == 'None' or weight == 0: continue eggs.append((egg, weight)) # Sort eggs by highest weighting eggs.sort(reverse=True, key=itemgetter(1)) if eggs: # We only provide the highest possibility egg to ASB fert_egg_asset = loader.load_related(eggs[0][0]) assert fert_egg_asset.default_export egg_props: PrimalItem = ue.gathering.gather_properties( fert_egg_asset.default_export) egg_decay = egg_props.EggLoseDurabilityPerSecond[0].rounded_value extra_egg_decay_m = egg_props.ExtraEggLoseDurabilityPerSecondMultiplier[ 0].rounded_value # 'incubationTime' = 100 / (Egg Lose Durability Per Second × Extra Egg Lose Durability Per Second Multiplier) try: data['incubationTime'] = cd(100 / egg_decay / extra_egg_decay_m) except ZeroDivisionError: logger.warning( f"Species {char_props.get_source().asset.assetname} tried dividing by zero for its incubationTime" ) data['eggTempMin'] = egg_props.EggMinTemperature[0] data['eggTempMax'] = egg_props.EggMaxTemperature[0] # 'maturationTime' = 1 / (Baby Age Speed × Extra Baby Age Speed Multiplier) baby_age_speed = char_props.BabyAgeSpeed[0].rounded_value extra_baby_age_speed_m = char_props.ExtraBabyAgeSpeedMultiplier[ 0].rounded_value try: data['maturationTime'] = cd(1 / baby_age_speed / extra_baby_age_speed_m) except ZeroDivisionError: logger.warning( f"Species {char_props.get_source().asset.assetname} tried dividing by zero for its maturationTime" ) data['matingCooldownMin'] = char_props.NewFemaleMinTimeBetweenMating[0] data['matingCooldownMax'] = char_props.NewFemaleMaxTimeBetweenMating[0] return data
def test_no_properties_without_link(loader: AssetLoader): loader.wipe_cache() with ue_parsing_context(link=False, properties=True): asset = loader[TEST_PGD_PKG] assert not asset.has_properties
def createLoader(self) -> AssetLoader: '''Create an asset loader pointing at the managed game install.''' modresolver = ManagedModResolver(self) loader = AssetLoader(modresolver, self.asset_path) return loader