public ReloadWeaponRequest( IItem item, IProtoItemAmmo protoItemAmmo) { this.ProtoItemAmmo = protoItemAmmo; this.Item = item; }
public static ItemTooltipCompatibleWeaponsControl Create(IProtoItemAmmo protoItemAmmo) { return(new ItemTooltipCompatibleWeaponsControl() { protoItemAmmo = protoItemAmmo }); }
private void ClientRemote_NoSpaceForUnloadedAmmo(IProtoItemAmmo protoAmmo) { NotificationSystem.ClientShowNotification("Cannot unload", "No space for unloaded ammo in inventory", NotificationColor.Bad, protoAmmo.Icon); }
public ViewModelItemTooltipCompatibleWeaponsControl(IProtoItemAmmo protoItemAmmo) { this.CompatibleWeaponProtos = protoItemAmmo.CompatibleWeaponProtos .OrderBy(e => e.GetType().Namespace) .ThenBy(e => e.Name) .ToArray(); }
public override void ClientOnWeaponHitOrTrace( ICharacter firingCharacter, Vector2D worldPositionSource, IProtoItemWeapon protoWeapon, IProtoItemAmmo protoAmmo, IProtoCharacter protoCharacter, in Vector2Ushort fallbackCharacterPosition,
public WeaponReloadingState( ICharacter character, IItem item, IProtoItemWeapon itemProto, IProtoItemAmmo protoItemAmmo) { this.Item = item; this.ProtoItemAmmo = protoItemAmmo; var reloadDuration = itemProto.AmmoReloadDuration; if (reloadDuration > 0) { var statName = itemProto.WeaponSkillProto?.StatNameReloadingSpeedMultiplier; if (statName.HasValue) { reloadDuration *= character.SharedGetFinalStatMultiplier(statName.Value); } reloadDuration = Api.Shared.RoundDurationByServerFrameDuration(reloadDuration); } this.SecondsToReloadRemains = reloadDuration; //Api.Logger.WriteDev($"Weapon will be reloaded in: {this.SecondsToReloadRemains:F2} seconds"); }
public ViewModelItemTooltipCompatibleWeaponsControl(IProtoItemAmmo protoItemAmmo) { this.CompatibleWeaponProtos = protoItemAmmo.CompatibleWeaponProtos .Where(p => p.Icon != null) .OrderBy(p => p.GetType().Namespace) .ThenBy(p => p.Name) .Select(p => p.Name) .ToArray(); }
private static IReadOnlyDropItemsList ServerGetDroplistFor(IProtoItemAmmo protoAmmo) { if (ServerCachedDroplists.TryGetValue(protoAmmo, out var droplist)) { return(droplist); } droplist = new DropItemsList().Add(protoAmmo); ServerCachedDroplists[protoAmmo] = droplist; return(droplist); }
public static int SharedGetTotalAvailableAmmo(IProtoItemAmmo protoItemAmmo, ICharacter character) { int result = 0; foreach (var container in SharedGetTargetContainersForCharacterAmmo(character, isForAmmoUnloading: false)) { result += container.CountItemsOfType(protoItemAmmo); } return(result); }
private static IGrouping <IProtoItemAmmo, IItem> SharedFindNextAmmoGroup( IReadOnlyList <IProtoItemAmmo> protoWeaponCompatibleAmmoProtos, List <IGrouping <IProtoItemAmmo, IItem> > existingCompatibleAmmoGroups, IProtoItemAmmo currentProtoItemAmmo) { var ammoIndex = -1; for (var index = 0; index < protoWeaponCompatibleAmmoProtos.Count; index++) { var compatibleAmmoItem = protoWeaponCompatibleAmmoProtos[index]; if (compatibleAmmoItem == currentProtoItemAmmo) { // found current proto item, select next item prototype ammoIndex = index; break; } } if (ammoIndex < 0) { ammoIndex = -1; } // try to find next available ammo do { ammoIndex++; if (ammoIndex >= protoWeaponCompatibleAmmoProtos.Count) { // unload weapon return(null); } var requiredAmmoType = protoWeaponCompatibleAmmoProtos[ammoIndex]; foreach (var availableAmmo in existingCompatibleAmmoGroups) { if (availableAmmo.Key == requiredAmmoType) { // found required ammo return(availableAmmo); } } }while (true); }
private void ClientRemote_OnWeaponHitOrTrace( ICharacter firingCharacter, IProtoItemWeapon protoWeapon, IProtoItemAmmo protoAmmo, IProtoCharacter protoCharacter, Vector2Ushort fallbackCharacterPosition, WeaponHitData[] hitObjects, Vector2D endPosition, bool endsWithHit) { WeaponSystemClientDisplay.ClientOnWeaponHitOrTrace(firingCharacter, protoWeapon, protoAmmo, protoCharacter, fallbackCharacterPosition, hitObjects, endPosition, endsWithHit); }
public static void SharedRebuildWeaponCache( ICharacter character, WeaponState weaponState) { DamageDescription damageDescription = null; var item = weaponState.ItemWeapon; var protoItem = weaponState.ProtoWeapon; if (protoItem == null) { return; } IProtoItemAmmo protoAmmo = null; if (item != null) { var weaponPrivateState = item.GetPrivateState <WeaponPrivateState>(); protoAmmo = weaponPrivateState.CurrentProtoItemAmmo; } if (protoItem.OverrideDamageDescription != null) { damageDescription = protoItem.OverrideDamageDescription; } else if (protoAmmo is IAmmoWithCustomWeaponCacheDamageDescription customAmmo) { damageDescription = customAmmo.DamageDescriptionForWeaponCache; } else if (protoAmmo != null) { damageDescription = protoAmmo.DamageDescription; } weaponState.WeaponCache = new WeaponFinalCache( character, character.SharedGetFinalStatsCache(), item, weaponState.ProtoWeapon, protoAmmo, damageDescription); }
public static void ClientOnWeaponHitOrTrace( ICharacter firingCharacter, IProtoItemWeapon protoWeapon, IProtoItemAmmo protoAmmo, IProtoCharacter protoCharacter, Vector2Ushort fallbackCharacterPosition, IReadOnlyList <WeaponHitData> hitObjects, Vector2D endPosition, bool endsWithHit) { if (firingCharacter is not null && !firingCharacter.IsInitialized) { firingCharacter = null; } var weaponTracePreset = protoWeapon.FireTracePreset ?? protoAmmo?.FireTracePreset; var worldPositionSource = SharedCalculateWeaponShotWorldPositon( firingCharacter, protoWeapon, protoCharacter, fallbackCharacterPosition.ToVector2D(), hasTrace: weaponTracePreset?.HasTrace ?? false); protoWeapon.ClientOnWeaponHitOrTrace(firingCharacter, worldPositionSource, protoWeapon, protoAmmo, protoCharacter, fallbackCharacterPosition, hitObjects, endPosition, endsWithHit); if (weaponTracePreset?.HasTrace ?? false) { ComponentWeaponTrace.Create(weaponTracePreset, worldPositionSource, endPosition, hasHit: endsWithHit, lastHitData: hitObjects.LastOrDefault(t => t.WorldObject is not null)); } foreach (var hitData in hitObjects) { var hitWorldObject = hitData.WorldObject; if (hitWorldObject is not null && !hitWorldObject.IsInitialized) { hitWorldObject = null; } var protoWorldObject = hitData.FallbackProtoWorldObject; double delay; { var worldObjectPosition = CalculateWorldObjectPosition(hitWorldObject, hitData); delay = weaponTracePreset?.HasTrace ?? false ? SharedCalculateTimeToHit(weaponTracePreset, worldPositionSource : worldPositionSource, endPosition : worldObjectPosition + hitData.HitPoint.ToVector2D()) : 0; } ClientTimersSystem.AddAction( delay, () => { // re-calculate the world object position var worldObjectPosition = CalculateWorldObjectPosition(hitWorldObject, hitData); var fireScatterPreset = protoAmmo?.OverrideFireScatterPreset ?? protoWeapon.FireScatterPreset; var projectilesCount = fireScatterPreset.ProjectileAngleOffets.Length; var objectMaterial = hitData.FallbackObjectMaterial; if (hitWorldObject is ICharacter hitCharacter && hitCharacter.IsInitialized) { objectMaterial = ((IProtoCharacterCore)hitCharacter.ProtoCharacter) .SharedGetObjectMaterialForCharacter(hitCharacter); } protoWeapon.ClientPlayWeaponHitSound(hitWorldObject, protoWorldObject, fireScatterPreset, objectMaterial, worldObjectPosition); if (weaponTracePreset is not null) { ClientAddHitSparks(weaponTracePreset.HitSparksPreset, hitData, hitWorldObject, protoWorldObject, worldObjectPosition, projectilesCount, objectMaterial, randomizeHitPointOffset: !weaponTracePreset.HasTrace, randomRotation: !weaponTracePreset.HasTrace, drawOrder: weaponTracePreset.DrawHitSparksAsLight ? DrawOrder.Light : DrawOrder.Default); } });
public static void ClientTryReloadOrSwitchAmmoType( bool isSwitchAmmoType, bool sendToServer = true, bool?showNotificationIfNoAmmo = null) { var character = Api.Client.Characters.CurrentPlayerCharacter; var currentWeaponState = PlayerCharacter.GetPrivateState(character).WeaponState; var itemWeapon = currentWeaponState.ItemWeapon; if (itemWeapon is null) { // no active weapon to reload return; } var protoWeapon = (IProtoItemWeapon)itemWeapon.ProtoItem; if (protoWeapon.AmmoCapacity == 0) { // the item is non-reloadable return; } var itemPrivateState = itemWeapon.GetPrivateState <WeaponPrivateState>(); var ammoCountNeed = isSwitchAmmoType ? protoWeapon.AmmoCapacity : (ushort)Math.Max(0, protoWeapon.AmmoCapacity - itemPrivateState.AmmoCount); if (ammoCountNeed == 0) { Logger.Info("No need to reload the weapon " + itemWeapon, character); return; } var compatibleAmmoGroups = SharedGetCompatibleAmmoGroups(character, protoWeapon); if (compatibleAmmoGroups.Count == 0 && !isSwitchAmmoType) { if (showNotificationIfNoAmmo.HasValue && showNotificationIfNoAmmo.Value || currentWeaponState.SharedGetInputIsFiring()) { protoWeapon.SoundPresetWeapon.PlaySound(WeaponSound.Empty, character, volume: SoundConstants.VolumeWeapon); NotificationSystem.ClientShowNotification( NotificationNoAmmo_Title, NotificationNoAmmo_Message, NotificationColor.Bad, protoWeapon.Icon, playSound: false); } if (currentWeaponState.SharedGetInputIsFiring()) { // stop firing the weapon currentWeaponState.ProtoWeapon.ClientItemUseFinish(itemWeapon); } return; } IProtoItemAmmo selectedProtoItemAmmo = null; var currentReloadingState = currentWeaponState.WeaponReloadingState; if (currentReloadingState is null) { // don't have reloading state - find ammo item matching current weapon ammo type var currentProtoItemAmmo = itemPrivateState.CurrentProtoItemAmmo; if (currentProtoItemAmmo is null) { // no ammo selected in weapon selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos, compatibleAmmoGroups, currentProtoItemAmmo: null)?.Key; } else // if weapon already has ammo { if (isSwitchAmmoType) { selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos, compatibleAmmoGroups, currentProtoItemAmmo)?.Key; if (selectedProtoItemAmmo == currentProtoItemAmmo && itemPrivateState.AmmoCount >= protoWeapon.AmmoCapacity) { // this ammo type is already loaded and it's fully reloaded Logger.Info("No need to reload the weapon " + itemWeapon, character); return; } } else // simple reload requested { // try to find ammo of the same type as already loaded into the weapon var isFound = false; foreach (var ammoGroup in compatibleAmmoGroups) { if (ammoGroup.Key == currentProtoItemAmmo) { isFound = true; selectedProtoItemAmmo = currentProtoItemAmmo; break; } } if (!isFound) { // no group selected - select first isSwitchAmmoType = true; sendToServer = true; selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos, compatibleAmmoGroups, currentProtoItemAmmo: null)?.Key; } } } } else { if (!isSwitchAmmoType) { // already reloading return; } // already reloading - try select another ammo type (alternate between them) var currentReloadingProtoItemAmmo = currentReloadingState.ProtoItemAmmo; selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos, compatibleAmmoGroups, currentReloadingProtoItemAmmo)?.Key; if (selectedProtoItemAmmo == currentReloadingProtoItemAmmo) { // already reloading this ammo type return; } } if (currentReloadingState != null && currentReloadingState.ProtoItemAmmo == selectedProtoItemAmmo) { // already reloading with these ammo items return; } if (currentReloadingState is null && selectedProtoItemAmmo is null && itemPrivateState.CurrentProtoItemAmmo is null) { // already unloaded return; } // create reloading state on the Client-side var weaponReloadingState = new WeaponReloadingState( character, itemWeapon, protoWeapon, selectedProtoItemAmmo); currentWeaponState.WeaponReloadingState = weaponReloadingState; protoWeapon.SoundPresetWeapon.PlaySound(WeaponSound.Reload, character, SoundConstants.VolumeWeapon); Logger.Info( $"Weapon reloading started for {itemWeapon} reload duration: {weaponReloadingState.SecondsToReloadRemains:F2}s", character); if (weaponReloadingState.SecondsToReloadRemains <= 0) { // instant-reload weapon - perform local reloading SharedProcessWeaponReload(character, currentWeaponState, out _); } if (sendToServer || isSwitchAmmoType) { // perform reload on server var arg = new ReloadWeaponRequest(itemWeapon, selectedProtoItemAmmo); Instance.CallServer(_ => _.ServerRemote_ReloadWeapon(arg)); } }
public static void ServerTryReloadSameAmmo(ICharacter character) { var weaponState = PlayerCharacter.GetPrivateState(character).WeaponState; var item = weaponState.ItemWeapon; if (item is null) { // no active weapon to reload return; } var itemProto = (IProtoItemWeapon)item.ProtoItem; if (itemProto.AmmoCapacity == 0) { // the item is non-reloadable return; } var itemPrivateState = item.GetPrivateState <WeaponPrivateState>(); var ammoCountNeed = (ushort)Math.Max(0, itemProto.AmmoCapacity - itemPrivateState.AmmoCount); if (ammoCountNeed == 0) { return; } var compatibleAmmoGroups = SharedGetCompatibleAmmoGroups(character, itemProto); if (compatibleAmmoGroups.Count == 0) { // no ammo to reload return; } IProtoItemAmmo selectedProtoItemAmmo = null; var currentReloadingState = weaponState.WeaponReloadingState; if (currentReloadingState != null) { // already reloading return; } // don't have reloading state - find ammo item matching current weapon ammo type var currentProtoItemAmmo = itemPrivateState.CurrentProtoItemAmmo; if (currentProtoItemAmmo is null) { // no ammo selected in weapon return; } // simple reload requested // try to find ammo of the same type as already loaded into the weapon var isAmmoFound = false; foreach (var ammoGroup in compatibleAmmoGroups) { if (ammoGroup.Key == currentProtoItemAmmo) { isAmmoFound = true; selectedProtoItemAmmo = currentProtoItemAmmo; break; } } if (!isAmmoFound) { return; } // create reloading state on the Server-side var weaponReloadingState = new WeaponReloadingState( character, item, itemProto, selectedProtoItemAmmo); weaponState.WeaponReloadingState = weaponReloadingState; //Logger.Dev("Weapon started reloading without a client request " + item, character); if (weaponReloadingState.SecondsToReloadRemains <= 0) { // instant-reload weapon - perform local reloading SharedProcessWeaponReload(character, weaponState, out _); } else if (IsServer) { ServerNotifyAboutReloading(character, weaponState, isFinished: false); } }
public ProtoItemAmmoViewModel([NotNull] IProtoItemAmmo ammo) : base(ammo) { }
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; } }
private static void SharedCallOnWeaponHitOrTrace( ICharacter firingCharacter, IProtoItemWeapon protoWeapon, IProtoItemAmmo protoAmmo, Vector2D endPosition, List <WeaponHitData> hitObjects, bool endsWithHit) { if (IsClient) { // display weapon shot on Client-side WeaponSystemClientDisplay.ClientOnWeaponHitOrTrace(firingCharacter, protoWeapon, protoAmmo, firingCharacter.ProtoCharacter, firingCharacter.Position.ToVector2Ushort(), hitObjects, endPosition, endsWithHit); } else // if server { // display damages on clients in scope of every damaged object var observers = new HashSet <ICharacter>(); using var tempList = Shared.GetTempList <ICharacter>(); Server.World.GetScopedByPlayers(firingCharacter, tempList); observers.AddRange(tempList.AsList()); foreach (var hitObject in hitObjects) { if (hitObject.IsCliffsHit || hitObject.WorldObject.IsDestroyed) { continue; } if (hitObject.WorldObject is ICharacter damagedCharacter && !damagedCharacter.IsNpc) { // notify the damaged character observers.Add(damagedCharacter); } Server.World.GetScopedByPlayers(hitObject.WorldObject, tempList); tempList.Clear(); observers.AddRange(tempList.AsList()); } // add all observers within the sound radius (so they can not only hear but also see the traces) var eventNetworkRadius = (byte)Math.Max( 15, Math.Ceiling(protoWeapon.SoundPresetWeaponDistance.max)); tempList.Clear(); Server.World.GetCharactersInRadius(firingCharacter.TilePosition, tempList, radius: eventNetworkRadius, onlyPlayers: true); observers.AddRange(tempList.AsList()); // don't notify the attacking character observers.Remove(firingCharacter); if (observers.Count > 0) { Instance.CallClient(observers, _ => _.ClientRemote_OnWeaponHitOrTrace(firingCharacter, protoWeapon, protoAmmo, firingCharacter.ProtoCharacter, firingCharacter .Position.ToVector2Ushort(), hitObjects.ToArray(), endPosition, endsWithHit)); } } }