예제 #1
0
파일: death.py 프로젝트: arkutils/Purlovia
def gather_death_data(species: PrimalDinoCharacter) -> DeathData:
    out = DeathData(
        dossierId=species.DeathGivesDossierIndex[0],
        baseXP=species.KillXPBase[0],
    )

    engrams = species.get('DeathGiveEngramClasses', fallback=None)
    if engrams:
        # Output only valid engram references.
        for engram_ref in engrams:
            if engram_ref and engram_ref.value and engram_ref.value.value:
                out.engrams.append(decode_item_name(engram_ref))

    # Export possible loot bag drop components (for use with wiki.drops).
    loot_chance = species.DeathInventoryChanceToUse[0]
    if loot_chance > 0:
        loot_templates = gather_inherited_struct_fields(species.get_source(), 'DeathInventoryTemplates',
                                                        LOOT_INVENTORY_TEMPLATES_DEFAULTS)
        weights = loot_templates['Weights']
        drop_components = loot_templates['AssociatedObjects']

        if drop_components and drop_components.values:
            # Make sure there's no more weights than drop components
            if weights:
                weights = weights.values
            else:
                weights = []
            weights = weights[:len(drop_components.values)]

            # Go through each drop component and gather the possible choices.
            choices = list()
            for ref, weight in zip_longest(drop_components, weights, fillvalue=1):
                # Skip invalid drop component references.
                if not ref or not ref.value or not ref.value.value:
                    continue

                # Skip the component if its weight is lower than zero.
                if weight <= 0:
                    continue

                # Needed due to a bug in the UE module which will be fixed in another commit.
                choices.append((weight, ref.format_for_json().format_for_json()))

            # Convert the choices list into chance
            weight_sum = sum(t[0] for t in choices)
            for weight, ref in choices:
                out.lootBags.append(ItemChancePair(chance=weight / weight_sum, item=ref))

        # Clamp the primary chance for the bag between 0 and 1.
        # If there's no valid drop components, override it to zero.
        if out.lootBags:
            # Explicit float cast to avoid a type warning over missing overloads for int+object.
            out.lootBagChance = min(1, max(0, float(loot_chance)))
        else:
            out.lootBagChance = 0

    return out
예제 #2
0
def gather_cloning_data(species: PrimalDinoCharacter) -> Optional[CloningData]:
    if not can_be_cloned(species):
        return None

    loader = species.get_source().asset.loader
    chamber_a = loader[CLONING_CHAMBER_C]
    assert chamber_a.default_export
    chamber = cast(TekCloningChamber,
                   gather_properties(chamber_a.default_export))

    cost_base = species.CloneBaseElementCost[
        0] * chamber.CloneBaseElementCostGlobalMultiplier[0]
    cost_level = species.CloneElementCostPerLevel[
        0] * chamber.CloneElementCostPerLevelGlobalMultiplier[
            0]  # skipped: CharacterLevel

    time_base = chamber.CloningTimePerElementShard[
        0] * cost_base  # skipped: BabyMatureSpeedMultiplier
    time_level = chamber.CloningTimePerElementShard[
        0] * cost_level  # skipped: BabyMatureSpeedMultiplier, CharacterLevel

    if cost_base == 0:
        # Free cloning, skip for sanity, it probably can't be obtained naturally.
        return None

    return CloningData(
        costBase=round(cost_base, 1),
        costLevel=round(cost_level, 2),
        timeBase=round(time_base, 1),
        timeLevel=round(time_level, 2),
    )
예제 #3
0
파일: xp.py 프로젝트: TigerMehMat/Purlovia
def convert_level_data(
        species: PrimalDinoCharacter,
        dcsc: DinoCharacterStatusComponent) -> Optional[LevelData]:
    if bool(species.bIsBossDino[0]):
        return None

    pgd_asset = species.get_source().asset.loader[COREMEDIA_PGD_PKG]
    assert pgd_asset.default_export
    pgd: PropertyTable = pgd_asset.default_export.properties

    result = LevelData()

    # Max experience points
    max_xp_override = species.OverrideDinoMaxExperiencePoints[0]
    if max_xp_override > 0:
        result.maxExperience = max_xp_override
    else:
        result.maxExperience = dcsc.MaxExperiencePoints[0]

    # Export ramp type and find one from PGD
    ramp_type = dcsc.LevelExperienceRampType[0].get_enum_value_name()
    if ramp_type != LevelExperienceRampType.DinoEasy.name:
        result.ramp = ramp_type

    ramp_id = LevelExperienceRampType[ramp_type].value
    ramp_prop = pgd.get_property('LevelExperienceRamps',
                                 index=ramp_id,
                                 fallback=None)
    if not ramp_prop:
        # No ramp (MAX).
        result.maxLevels = 0
    else:
        ramp = ramp_prop.values[0].value
        # Find highest level using a vanilla ramp
        for index, threshold in enumerate(ramp.values):
            if result.maxExperience >= threshold:
                result.maxLevels = index + 1
            else:
                break

    # Level cap offset
    if species.has_override('DestroyTamesOverLevelClampOffset'):
        result.capOffset = species.DestroyTamesOverLevelClampOffset[0]

    return result
예제 #4
0
def _should_skip_species(species: PrimalDinoCharacter,
                         overrides: OverrideSettings):
    if overrides.skip_export:
        return True

    if not species.has_override('DescriptiveName'):
        return True

    return False
예제 #5
0
def can_be_cloned(species: PrimalDinoCharacter) -> bool:
    """
    Requirements for cloning are:
    - FLAGS_PREVENT_CLONE are all false
    - not DCSC->FreezeStatusValues
    - does not have a rider
    - clone base element cost higher or equal to 0
    """
    for flag in FLAGS_PREVENT_CLONE:
        if species.get(flag):
            return False
    return species.CloneBaseElementCost[
        0] >= 0 and species.AutoFadeOutAfterTameTime[0] == 0
예제 #6
0
def gather_damage_mults(char_props: PrimalDinoCharacter) -> Optional[Dict[str, float]]:
    damage_adjusters = char_props.get('BoneDamageAdjusters', 0, None)
    if not damage_adjusters:
        return None

    adjusters = damage_adjusters.values

    result = dict()
    for bone_info in adjusters:
        name = str(bone_info.get_property('BoneName'))
        mult = bone_info.get_property('DamageMultiplier')
        result[name] = mult

    return result
예제 #7
0
    def extract_core(self, _: Path):
        '''Perform sanity tests on core species.'''

        # Count species by prefix (/Game/<part> or /Game/Mods/<id>)
        counter: Counter = Counter()
        for clsname in find_sub_classes(PrimalDinoCharacter.get_ue_type()):
            if not clsname.startswith('/Game'):
                continue

            parts = clsname.split('/')
            if clsname.startswith('/Game/Mods/'):
                modid = self.manager.loader.get_mod_id(clsname)
                assert modid
                parts[3] = modid
                parts = parts[:4]
            else:
                parts = parts[:3]

            counter.update(['/'.join(parts)])

        # Check counts against configured limits
        overrides = get_overrides()
        reports = list()
        for path, min_count in overrides.sanity_checks.min_species.items():
            count = counter.get(path, 0)
            logger.debug('%s = %d (need %d)', path, count, min_count)
            if count < min_count:
                reports.append((path, count, min_count))

        # If anything failed, report and turn off Git push
        if not reports:
            return None

        # Disable git push to hopefully avoid damaging commits
        self.manager.config.git.SkipPush = False

        # Put together a report message
        header = "Species count sanity check failed! (git push disabled):\n"
        lines: List[str] = []
        for path, count, min_count in reports:
            lines.append(f"    {path} expected {min_count}, found {count}\n")

        # Log it
        logger.error(''.join([header] + lines))

        # Send it to Discord
        send_to_discord(log=lines,
                        header='Purlovia failed the species sanity check:')
예제 #8
0
파일: xp.py 프로젝트: arkutils/Purlovia
def convert_level_data(species: PrimalDinoCharacter,
                       dcsc: DinoCharacterStatusComponent) -> LevelData:
    pgd_asset = species.get_source().asset.loader[COREMEDIA_PGD_PKG]
    assert pgd_asset.default_export
    pgd: PropertyTable = pgd_asset.default_export.properties

    result = LevelData()

    # Max experience points
    max_xp_override = species.OverrideDinoMaxExperiencePoints[0]
    if max_xp_override > 0:
        result.maxExperience = max_xp_override
    else:
        result.maxExperience = dcsc.MaxExperiencePoints[0]

    # Export ramp type and find one from PGD
    ramp_type = dcsc.LevelExperienceRampType[0].get_enum_value_name()
    if ramp_type != LevelExperienceRampType.DinoEasy.name:
        result.xpRamp = ramp_type

    # Calculate max levels out of max XP.
    ramp_id = LevelExperienceRampType[ramp_type].value
    ramp_prop = pgd.get_property('LevelExperienceRamps',
                                 index=ramp_id,
                                 fallback=None)
    if not ramp_prop:
        # No ramp (MAX). Tame level ups not possible.
        result.maxTameLevels = 0
    else:
        ramp = ramp_prop.values[0].value
        # Find highest level using a vanilla ramp
        for index, threshold in enumerate(ramp.values):
            if result.maxExperience >= threshold:
                result.maxTameLevels = index + 1
            else:
                break

    # Official servers' level cap
    if species.has_override('DestroyTamesOverLevelClampOffset'):
        result.levelCapOffset = species.DestroyTamesOverLevelClampOffset[0]

    # Wild spawn levels
    base_level_override = species.AbsoluteBaseLevel[0]
    fixed_level = species.bUseFixedSpawnLevel[0]
    if base_level_override != 0 or fixed_level:
        # Forced base level. Does not scale with difficulty.
        result.wildForcedLevel = base_level_override or 1
        result.ignoresLevelModifiers = fixed_level

        # If level modifiers are enabled, multiply the level by final level multiplier.
        if not fixed_level:
            result.wildForcedLevel *= species.FinalNPCLevelMultiplier[0]
    else:
        # Weighed level range table.
        level_weights = species.get('DinoBaseLevelWeightEntries',
                                    fallback=None)
        out_level_table = list()

        if level_weights and level_weights.values:
            weight_sum = 0
            entries = list()
            final_mult = species.FinalNPCLevelMultiplier[0]

            # Gather the data into a temporary list, with weight and range.
            for entry in level_weights:
                d = entry.as_dict()
                entries.append(
                    (d['EntryWeight'], d['BaseLevelMinRange'] * final_mult,
                     d['BaseLevelMaxRange'] * final_mult))
                weight_sum += d['EntryWeight']

            # Pack gathered data into MinMaxChanceRanges with calculated chances. The properties cannot be modified through INI
            # configs.
            for weight, min_lvl, max_lvl in entries:
                out_level_table.append(
                    MinMaxChanceRange(chance=clean_float(weight / weight_sum),
                                      min=clean_float(min_lvl),
                                      max=clean_float(max_lvl)))

        result.wildLevelTable = out_level_table

    # Calculate required amount of Mutagen needed to use it.
    # Formula extracted from PrimalItemConsumable_Mutagen_C's scripts
    # Vultures cannot use Mutagen as it can't enter their inventories.
    # IsRobot and IsVehicle banned from Mutagen in v329.51.
    if species.DinoNameTag[0] != 'Vulture' and not species.bIsRobot[
            0] and not species.bIsVehicle[0]:
        mutagen_base = species.MutagenBaseCost[0]
        if mutagen_base <= 0:
            mutagen_base = species.CloneBaseElementCost[0]
        if mutagen_base > 0:
            cost = 1 + 99 * mutagen_base

            # Round up 0.5 to conform with in-game calculations.
            if (cost % 1) >= 0.5:
                result.mutagenCost = ceil(cost)
            else:
                result.mutagenCost = round(cost)

    return result
예제 #9
0
 def get_ue_type(self):
     return PrimalDinoCharacter.get_ue_type()
예제 #10
0
def _gather_flags(species: PrimalDinoCharacter) -> List[str]:
    result = [
        _clean_flag_name(field) for field in OUTPUT_FLAGS
        if species.get(field, fallback=False)
    ]
    return result
예제 #11
0
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