private static void ApplyFinalStatsCache( FinalStatsCache finalStatsCache, CharacterCurrentStats stats, bool isFirstTime) { if (Api.IsClient) { return; } // these values could be set only on the Server-side stats.ServerSetHealthMax((float)finalStatsCache[StatName.HealthMax]); if (stats is PlayerCharacterCurrentStats playerStats) { playerStats.ServerSetStaminaMax((float)finalStatsCache[StatName.StaminaMax]); playerStats.ServerSetFoodMax((float)finalStatsCache[StatName.FoodMax]); playerStats.ServerSetWaterMax((float)finalStatsCache[StatName.WaterMax]); } if (isFirstTime) { stats.ServerSetCurrentValuesToMaxValues(); } }
public static double ServerCalculateTotalDamage( WeaponFinalCache weaponFinalCache, IWorldObject targetObject, FinalStatsCache targetFinalStatsCache, double damagePreMultiplier, bool clampDefenseTo1) { if (weaponFinalCache.ProtoObjectExplosive != null && targetObject.ProtoWorldObject is IProtoStaticWorldObject targetStaticWorldObjectProto) { // special case - apply the explosive damage return(ServerCalculateTotalDamageByExplosive(weaponFinalCache.ProtoObjectExplosive, targetStaticWorldObjectProto, damagePreMultiplier)); } var damageValue = damagePreMultiplier * weaponFinalCache.DamageValue; var invertedArmorPiercingCoef = weaponFinalCache.InvertedArmorPiercingCoef; var totalDamage = 0d; // calculate total damage by summing all the damage components foreach (var damageDistribution in weaponFinalCache.DamageDistributions) { var defenseStatName = SharedGetDefenseStatName(damageDistribution.DamageType); var defenseFraction = targetFinalStatsCache[defenseStatName]; defenseFraction = MathHelper.Clamp(defenseFraction, 0, clampDefenseTo1 ? 1 : double.MaxValue); totalDamage += ServerCalculateDamageComponent( damageValue, invertedArmorPiercingCoef, damageDistribution, defenseFraction); } // multiply on final multiplier (usually used for expanding projectiles) totalDamage *= weaponFinalCache.FinalDamageMultiplier; return(totalDamage); }
public static double ServerCalculateTotalDamage( WeaponFinalCache weaponCache, IWorldObject targetObject, FinalStatsCache targetFinalStatsCache, double damagePreMultiplier, bool clampDefenseTo1) { if (targetObject is IStaticWorldObject staticWorldObject && (!RaidingProtectionSystem.SharedCanRaid(staticWorldObject, showClientNotification: false) || !LandClaimShieldProtectionSystem.SharedCanRaid(staticWorldObject, showClientNotification: false) || !PveSystem.SharedIsAllowStaticObjectDamage(weaponCache.Character, staticWorldObject, showClientNotification: false) || !NewbieProtectionSystem.SharedIsAllowStructureDamage(weaponCache.Character, staticWorldObject, showClientNotification: false))) { return(0); } if (targetObject.ProtoGameObject is IProtoVehicle && !PveSystem.SharedIsAllowVehicleDamage(weaponCache, (IDynamicWorldObject)targetObject, showClientNotification: false)) { return(0); } if (weaponCache.ProtoExplosive is not null && targetObject is IStaticWorldObject targetStaticWorldObject) { // special case - apply the explosive damage to static object return(ServerCalculateTotalDamageByExplosive(weaponCache.Character, weaponCache.ProtoExplosive, targetStaticWorldObject, damagePreMultiplier)); } if (ServerIsRestrictedPvPDamage(weaponCache, targetObject, out var isPvPcase, out var isFriendlyFireCase)) { return(0); } var damageValue = damagePreMultiplier * weaponCache.DamageValue; var invertedArmorPiercingCoef = weaponCache.InvertedArmorPiercingCoef; var totalDamage = 0d; // calculate total damage by summing all the damage components foreach (var damageDistribution in weaponCache.DamageDistributions) { var defenseStatName = SharedGetDefenseStatName(damageDistribution.DamageType); var defenseFraction = targetFinalStatsCache[defenseStatName]; defenseFraction = MathHelper.Clamp(defenseFraction, 0, clampDefenseTo1 ? 1 : double.MaxValue); totalDamage += ServerCalculateDamageComponent( damageValue, invertedArmorPiercingCoef, damageDistribution, defenseFraction); } // multiply on final multiplier (usually used for expanding projectiles) totalDamage *= weaponCache.FinalDamageMultiplier; var damagingCharacter = weaponCache.Character; if (isPvPcase) { // apply PvP damage multiplier totalDamage *= WeaponConstants.DamagePvpMultiplier; } else if (damagingCharacter is not null && !damagingCharacter.IsNpc && targetObject.ProtoGameObject is IProtoCharacterMob protoCharacterMob && !protoCharacterMob.IsBoss) { // apply PvE damage multiplier totalDamage *= WeaponConstants.DamagePveMultiplier; }
public WeaponFinalCache( ICharacter character, FinalStatsCache characterFinalStatsCache, [CanBeNull] IItem weapon, [CanBeNull] IProtoItemWeapon protoWeapon, DamageDescription damageDescription, IProtoObjectExplosive protoObjectExplosive = null) { this.Character = character; this.Weapon = weapon; this.ProtoWeapon = (IProtoItemWeapon)weapon?.ProtoItem ?? protoWeapon; this.ProtoObjectExplosive = protoObjectExplosive; if (damageDescription == null) { // TODO: it looks like not implemented yet and we should throw an exception here // fallback in case weapon don't provide damage description (such as no-ammo weapon) damageDescription = new DamageDescription( damageValue: 0, armorPiercingCoef: 0, finalDamageMultiplier: 1, rangeMax: 0, damageDistribution: new DamageDistribution()); } var descriptionDamages = damageDescription.DamageProportions; var damageDistributionsCount = descriptionDamages.Count; var resultDamageDistributions = new List <DamageProportion>(damageDistributionsCount); var totalPercents = 0d; for (var index = 0; index < damageDistributionsCount; index++) { var source = descriptionDamages[index]; var statName = this.GetProportionStatName(source.DamageType); var resultDamageProportion = source.Proportion + characterFinalStatsCache[statName]; if (resultDamageProportion <= 0) { continue; } resultDamageDistributions.Add(new DamageProportion(source.DamageType, resultDamageProportion)); totalPercents += resultDamageProportion; } if (damageDistributionsCount > 0 && Math.Abs(totalPercents - 1) > 0.001d) { throw new Exception( "Sum of all damage proportions must be exactly 1. Calculated value: " + totalPercents.ToString("F3")); } this.DamageDistributions = resultDamageDistributions; this.DamageValue = damageDescription.DamageValue * (protoWeapon?.DamageMultiplier ?? 1.0) + characterFinalStatsCache[StatName.DamageAdd]; if (protoWeapon?.WeaponSkillProto != null) { var statName = protoWeapon.WeaponSkillProto.StatNameDamageBonusMultiplier; this.DamageValue *= characterFinalStatsCache.GetMultiplier(statName); } this.RangeMax = damageDescription.RangeMax * (protoWeapon?.RangeMultipier ?? 1.0) + characterFinalStatsCache[StatName.AttackRangeMax]; var armorPiercingCoef = (1 + characterFinalStatsCache[StatName.AttackArmorPiercingMultiplier]) * (damageDescription.ArmorPiercingCoef * (protoWeapon?.ArmorPiercingMultiplier ?? 1.0) + characterFinalStatsCache[StatName.AttackArmorPiercingValue]); this.InvertedArmorPiercingCoef = 1 - armorPiercingCoef; this.FinalDamageMultiplier = damageDescription.FinalDamageMultiplier + characterFinalStatsCache[StatName.AttackFinalDamageMultiplier]; }
public WeaponFinalCache( ICharacter character, FinalStatsCache characterFinalStatsCache, [CanBeNull] IItem weapon, [CanBeNull] IProtoItemWeapon protoWeapon, [CanBeNull] IProtoItemAmmo protoAmmo, DamageDescription damageDescription, IProtoExplosive protoExplosive = null, IDynamicWorldObject objectDrone = null) { this.Character = character; this.CharacterFinalStatsCache = characterFinalStatsCache; this.Drone = objectDrone; this.Weapon = weapon; this.ProtoWeapon = (IProtoItemWeapon)weapon?.ProtoItem ?? protoWeapon; this.ProtoAmmo = protoAmmo; this.ProtoExplosive = protoExplosive; if (damageDescription is null) { // TODO: it looks like not implemented yet and we should throw an exception here // fallback in case weapon don't provide damage description (such as no-ammo weapon) damageDescription = new DamageDescription( damageValue: 0, armorPiercingCoef: 0, finalDamageMultiplier: 1, rangeMax: 0, damageDistribution: new DamageDistribution()); } var descriptionDamages = damageDescription.DamageProportions; var damageDistributionsCount = descriptionDamages.Count; var resultDamageDistributions = new List <DamageProportion>(damageDistributionsCount); var totalPercents = 0d; for (var index = 0; index < damageDistributionsCount; index++) { var source = descriptionDamages[index]; var statName = GetProportionStatName(source.DamageType); var resultDamageProportion = source.Proportion + characterFinalStatsCache[statName]; if (resultDamageProportion <= 0) { continue; } resultDamageDistributions.Add(new DamageProportion(source.DamageType, resultDamageProportion)); totalPercents += resultDamageProportion; } if (damageDistributionsCount > 0 && Math.Abs(totalPercents - 1) > 0.001d) { throw new Exception( "Sum of all damage proportions must be exactly 1. Calculated value: " + totalPercents.ToString("F3")); } this.DamageDistributions = resultDamageDistributions; this.DamageValue = damageDescription.DamageValue * (protoWeapon?.DamageMultiplier ?? 1.0) + characterFinalStatsCache[StatName.DamageAdd]; var weaponSkillProto = protoWeapon?.WeaponSkillProto; if (weaponSkillProto is not null) { var statName = protoWeapon.WeaponSkillProto.StatNameDamageBonusMultiplier; this.DamageValue *= characterFinalStatsCache.GetMultiplier(statName); } this.RangeMax = damageDescription.RangeMax * (protoWeapon?.RangeMultiplier ?? 1.0) + characterFinalStatsCache[StatName.AttackRangeMax]; var armorPiercingCoef = (1 + characterFinalStatsCache[StatName.AttackArmorPiercingMultiplier]) * (damageDescription.ArmorPiercingCoef + characterFinalStatsCache[StatName.AttackArmorPiercingValue]); this.InvertedArmorPiercingCoef = 1 - armorPiercingCoef; this.FinalDamageMultiplier = damageDescription.FinalDamageMultiplier + characterFinalStatsCache[StatName.AttackFinalDamageMultiplier]; var probability = protoWeapon?.SpecialEffectProbability ?? 0; if (weaponSkillProto is not null) { var statNameSpecialEffectChance = weaponSkillProto.StatNameSpecialEffectChanceMultiplier; probability *= characterFinalStatsCache.GetMultiplier(statNameSpecialEffectChance); } this.SpecialEffectProbability = probability; this.FireScatterPreset = protoAmmo?.OverrideFireScatterPreset ?? protoWeapon?.FireScatterPreset ?? default; var shotsPerFire = this.FireScatterPreset.ProjectileAngleOffets.Length; if (shotsPerFire > 1) { // decrease final damage and special effect multiplier on the number of shots per fire var coef = 1.0 / shotsPerFire; this.FinalDamageMultiplier *= coef; this.SpecialEffectProbability *= coef; } }
public static double ServerCalculateTotalDamage( WeaponFinalCache weaponCache, IWorldObject targetObject, FinalStatsCache targetFinalStatsCache, double damagePreMultiplier, bool clampDefenseTo1) { if (targetObject is IStaticWorldObject staticWorldObject && (!RaidingProtectionSystem.SharedCanRaid(staticWorldObject, showClientNotification: false) || !PveSystem.SharedIsAllowStructureDamage(weaponCache.Character, staticWorldObject, showClientNotification: false))) { return(0); } if (weaponCache.ProtoObjectExplosive != null && targetObject.ProtoWorldObject is IProtoStaticWorldObject targetStaticWorldObjectProto) { // special case - apply the explosive damage return(ServerCalculateTotalDamageByExplosive(weaponCache.ProtoObjectExplosive, targetStaticWorldObjectProto, damagePreMultiplier)); } // these two cases apply only if damage dealt not by a bomb if (ServerIsRestrictedPvPDamage(weaponCache, targetObject, out var isPvPcase, out var isFriendlyFireCase)) { return(0); } var damageValue = damagePreMultiplier * weaponCache.DamageValue; var invertedArmorPiercingCoef = weaponCache.InvertedArmorPiercingCoef; var totalDamage = 0d; // calculate total damage by summing all the damage components foreach (var damageDistribution in weaponCache.DamageDistributions) { var defenseStatName = SharedGetDefenseStatName(damageDistribution.DamageType); var defenseFraction = targetFinalStatsCache[defenseStatName]; defenseFraction = MathHelper.Clamp(defenseFraction, 0, clampDefenseTo1 ? 1 : double.MaxValue); totalDamage += ServerCalculateDamageComponent( damageValue, invertedArmorPiercingCoef, damageDistribution, defenseFraction); } // multiply on final multiplier (usually used for expanding projectiles) totalDamage *= weaponCache.FinalDamageMultiplier; var damagingCharacter = weaponCache.Character; if (isPvPcase) { // apply PvP damage multiplier totalDamage *= WeaponConstants.DamagePvpMultiplier; } else if (damagingCharacter != null && !damagingCharacter.IsNpc) { // apply PvE damage multiplier totalDamage *= WeaponConstants.DamagePveMultiplier; } else if (damagingCharacter != null && damagingCharacter.IsNpc) { // apply creature damage multiplier totalDamage *= WeaponConstants.DamageCreaturesMultiplier; if (targetObject is ICharacter victim && !victim.ServerIsOnline && !victim.IsNpc) { // don't deal creature damage to offline players totalDamage = 0; } } if (isFriendlyFireCase) { totalDamage *= WeaponConstants.DamageFriendlyFireMultiplier; } return(totalDamage); }