示例#1
0
        private static bool SharedShouldFireMore(WeaponState state)
        {
            if (!state.IsFiring)
            {
                return(false);
            }

            // is firing delay completed?
            var canStopFiring = state.DamageApplyDelaySecondsRemains <= 0;

            if (canStopFiring &&
                IsServer &&
                state.ServerLastClientReportedShotsDoneCount.HasValue)
            {
                // cannot stop firing if not all the ammo are fired yet
                if (state.ShotsDone < state.ServerLastClientReportedShotsDoneCount)
                {
                    // let's spend all the remaining ammo before stopping firing
                    canStopFiring = false;
                    //Logger.Dev("Not all shots done yet, delay stopping firing: shotsDone="
                    //           + state.ShotsDone
                    //           + " requiresShotsDone="
                    //           + state.ServerLastClientReportedShotsDoneCount);
                }
            }

            return(!canStopFiring);
        }
示例#2
0
        private static void SharedCallOnWeaponFinished(WeaponState state, ICharacter character)
        {
            if (IsServer)
            {
                ServerCheckFiredShotsMismatch(state, character);
            }

            state.IsEventWeaponStartSent = false;

            if (IsClient)
            {
                // finished firing weapon on Client-side
                WeaponSystemClientDisplay.OnWeaponFinished(character);
            }
            else // if this is Server
            {
                // notify other clients about finished firing weapon
                using (var scopedBy = Api.Shared.GetTempList <ICharacter>())
                {
                    Server.World.GetScopedByPlayers(character, scopedBy);
                    Instance.CallClient(
                        scopedBy,
                        _ => _.ClientRemote_OnWeaponFinished(character));
                }
            }
        }
示例#3
0
        public static void SharedUpdateReloading(WeaponState weaponState, ICharacter character, ref double deltaTime)
        {
            var reloadingState = weaponState.WeaponReloadingState;

            if (reloadingState == null)
            {
                return;
            }

            // process reloading
            reloadingState.SecondsToReloadRemains -= deltaTime;
            if (reloadingState.SecondsToReloadRemains > 0)
            {
                // need more time to reload
                deltaTime = 0;
                return;
            }

            // reloaded
            deltaTime = -reloadingState.SecondsToReloadRemains;
            reloadingState.SecondsToReloadRemains = 0;
            SharedProcessWeaponReload(character, weaponState);

            weaponState.ShotsDone = 0;
            weaponState.ServerLastClientReportedShotsDoneCount = null;
        }
示例#4
0
        public static void RebuildWeaponCache(
            ICharacter character,
            WeaponState weaponState)
        {
            DamageDescription damageDescription = null;
            var item      = weaponState.ActiveItemWeapon;
            var protoItem = weaponState.ActiveProtoWeapon;

            if (protoItem == null)
            {
                return;
            }

            if (protoItem.OverrideDamageDescription != null)
            {
                damageDescription = protoItem.OverrideDamageDescription;
            }
            else if (item != null)
            {
                var weaponPrivateState = item.GetPrivateState <WeaponPrivateState>();
                damageDescription = weaponPrivateState.CurrentProtoItemAmmo?.DamageDescription;
            }

            weaponState.WeaponCache = new WeaponFinalCache(
                character,
                character.SharedGetFinalStatsCache(),
                item,
                weaponState.ActiveProtoWeapon,
                damageDescription);
        }
示例#5
0
        private static void ServerCheckFiredShotsMismatch(WeaponState state, ICharacter character)
        {
            var ammoConsumptionPerShot = state.ProtoWeapon.AmmoConsumptionPerShot;

            if (ammoConsumptionPerShot == 0)
            {
                // weapon doesn't use any ammo - no problem with possible desync
                return;
            }

            if (!WeaponAmmoSystem.IsResetsShotsDoneNumberOnReload(state.ProtoWeapon))
            {
                // this weapon can keep firing after the reload on the server side
                return;
            }

            var requestedShotsCount = state.ServerLastClientReportedShotsDoneCount;

            if (!requestedShotsCount.HasValue)
            {
                return;
            }

            var extraShotsDone = (int)(state.ShotsDone - (long)requestedShotsCount.Value);

            state.ServerLastClientReportedShotsDoneCount = null;

            if (extraShotsDone == 0)
            {
                return;
            }

            if (extraShotsDone < 0)
            {
                // should never happen as server should fire as much as client requested, always
                return;
            }

            var itemWeapon = state.ItemWeapon;

            if (itemWeapon == null)
            {
                return;
            }

            Logger.Important($"Shots count mismatch: requested={requestedShotsCount} actualShotsDone={state.ShotsDone}",
                             character);
            Instance.CallClient(character,
                                _ => _.ClientRemote_FixAmmoCount(itemWeapon, extraShotsDone));
        }
示例#6
0
        private static void SharedCallOnWeaponStart(WeaponState state, ICharacter character)
        {
            Api.Assert(!state.IsEventWeaponStartSent, "Firing event must be not set");
            state.IsEventWeaponStartSent = true;

            if (IsClient)
            {
                // start firing weapon on Client-side
                WeaponSystemClientDisplay.OnWeaponStart(character);
            }
            else // if IsServer
            {
                using var scopedBy = Api.Shared.GetTempList <ICharacter>();
                Server.World.GetScopedByPlayers(character, scopedBy);
                Instance.CallClient(scopedBy,
                                    _ => _.ClientRemote_OnWeaponStart(character));
            }
        }
示例#7
0
        private static void ServerCheckFiredShotsMismatch(WeaponState state, ICharacter character)
        {
            var ammoConsumptionPerShot = state.ActiveProtoWeapon.AmmoConsumptionPerShot;

            if (ammoConsumptionPerShot == 0)
            {
                // weapon doesn't use any ammo - no problem with possible desync
                return;
            }

            var requestedShotsCount = state.ServerLastClientReportedShotsDoneCount;

            if (!requestedShotsCount.HasValue)
            {
                return;
            }

            var extraShotsDone = (int)(state.ShotsDone - (long)requestedShotsCount.Value);

            state.ServerLastClientReportedShotsDoneCount = null;

            if (extraShotsDone == 0)
            {
                return;
            }

            if (extraShotsDone < 0)
            {
                // should never happen as server should fire as much as client requested, always
                return;
            }

            var itemWeapon = state.ActiveItemWeapon;

            if (itemWeapon == null)
            {
                return;
            }

            //Logger.Dev($"Shots count mismatch: requested={requestedShotsCount} actualShotsDone={state.ShotsDone}");

            Instance.CallClient(character,
                                _ => _.ClientRemote_FixAmmoCount(itemWeapon, extraShotsDone));
        }
示例#8
0
        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);
        }
示例#9
0
        private static void SharedCallOnWeaponInputStop(WeaponState state, ICharacter character)
        {
            Api.Assert(state.IsEventWeaponStartSent, "Firing event must be set");
            state.IsEventWeaponStartSent = false;

            if (IsClient)
            {
                // finished firing weapon on Client-side
                WeaponSystemClientDisplay.OnWeaponInputStop(character);
            }
            else // if this is Server
            {
                // notify other clients about finished firing weapon
                using var scopedBy = Api.Shared.GetTempList <ICharacter>();
                Server.World.GetScopedByPlayers(character, scopedBy);
                Instance.CallClient(
                    scopedBy,
                    _ => _.ClientRemote_OnWeaponInputStop(character));
            }
        }
示例#10
0
        // send notification about reloading to players in scope (so they can play a sound)
        private static void ServerNotifyAboutReloading(ICharacter character, WeaponState weaponState, bool isFinished)
        {
            using var scopedBy = Api.Shared.GetTempList <ICharacter>();
            Server.World.GetScopedByPlayers(character, scopedBy);
            scopedBy.Remove(character);
            if (scopedBy.Count == 0)
            {
                return;
            }

            if (isFinished)
            {
                Instance.CallClient(scopedBy.AsList(),
                                    _ => _.ClientRemote_OnOtherCharacterReloaded(character, weaponState.ProtoWeapon));
            }
            else
            {
                Instance.CallClient(scopedBy.AsList(),
                                    _ => _.ClientRemote_OnOtherCharacterReloading(character, weaponState.ProtoWeapon));
            }
        }
示例#11
0
        public static void SharedUpdateReloading(WeaponState weaponState, ICharacter character, double deltaTime)
        {
            var reloadingState = weaponState.WeaponReloadingState;

            if (reloadingState is null)
            {
                return;
            }

            // process reloading
            reloadingState.SecondsToReloadRemains -= deltaTime;
            if (reloadingState.SecondsToReloadRemains > 0)
            {
                // need more time to reload
                return;
            }

            // reloaded
            reloadingState.SecondsToReloadRemains = 0;
            SharedProcessWeaponReload(character,
                                      weaponState,
                                      out var isAmmoTypeChanged);

            if (isAmmoTypeChanged ||
                IsResetsShotsDoneNumberOnReload(weaponState.ProtoWeapon))
            {
                weaponState.ShotsDone = 0;
                weaponState.ServerLastClientReportedShotsDoneCount = null;
                weaponState.CustomTargetPosition = null;
                //Api.Logger.Dev("Reset ServerLastClientReportedShotsDoneCount. Last value: "
                //               + weaponState.ServerLastClientReportedShotsDoneCount);
            }

            weaponState.FirePatternCooldownSecondsRemains = 0;
            weaponState.IsIdleAutoReloadingAllowed        = true;
        }
示例#12
0
        private static void SharedFireWeapon(
            ICharacter character,
            IItem weaponItem,
            IProtoItemWeapon protoWeapon,
            WeaponState weaponState)
        {
            if (!protoWeapon.SharedOnFire(character, weaponState))
            {
                return;
            }

            var playerCharacterSkills = character.SharedGetSkills();
            var protoWeaponSkill      = playerCharacterSkills != null
                                       ? protoWeapon.WeaponSkillProto
                                       : null;

            if (IsServer)
            {
                protoWeaponSkill?.ServerOnShot(playerCharacterSkills); // give experience for shot
                CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(character);
            }

            var weaponCache = weaponState.WeaponCache;

            if (weaponCache is null)
            {
                SharedRebuildWeaponCache(character, weaponState);
                weaponCache = weaponState.WeaponCache;
            }

            var characterCurrentVehicle = character.IsNpc
                                              ? null
                                              : character.SharedGetCurrentVehicle();

            var isMeleeWeapon            = protoWeapon is IProtoItemWeaponMelee;
            var characterProtoCharacter  = (IProtoCharacterCore)character.ProtoCharacter;
            var fromPosition             = characterProtoCharacter.SharedGetWeaponFireWorldPosition(character, isMeleeWeapon);
            var fireSpreadAngleOffsetDeg = protoWeapon.SharedUpdateAndGetFirePatternCurrentSpreadAngleDeg(weaponState);

            var collisionGroup = protoWeapon.CollisionGroup;

            using var allHitObjects = Shared.GetTempList <IWorldObject>();
            var shotsPerFire = weaponCache.FireScatterPreset.ProjectileAngleOffets;

            foreach (var angleOffsetDeg in shotsPerFire)
            {
                SharedShotWeaponHitscan(character,
                                        protoWeapon,
                                        fromPosition,
                                        weaponCache,
                                        weaponState.CustomTargetPosition,
                                        characterProtoCharacter,
                                        fireSpreadAngleOffsetDeg + angleOffsetDeg,
                                        collisionGroup,
                                        isMeleeWeapon,
                                        characterCurrentVehicle,
                                        protoWeaponSkill,
                                        playerCharacterSkills,
                                        allHitObjects);
            }

            if (IsServer)
            {
                protoWeapon.ServerOnShot(character, weaponItem, protoWeapon, allHitObjects.AsList());
            }
        }
示例#13
0
        /// <summary>
        /// Executed when a weapon must reload (after the reloading duration is completed).
        /// </summary>
        private static void SharedProcessWeaponReload(ICharacter character, WeaponState weaponState)
        {
            var weaponReloadingState = weaponState.WeaponReloadingState;

            // remove weapon reloading state
            weaponState.WeaponReloadingState = null;

            var itemWeapon             = weaponReloadingState.Item;
            var itemWeaponProto        = (IProtoItemWeapon)itemWeapon.ProtoGameObject;
            var itemWeaponPrivateState = itemWeapon.GetPrivateState <WeaponPrivateState>();
            var weaponAmmoCount        = (int)itemWeaponPrivateState.AmmoCount;
            var weaponAmmoCapacity     = itemWeaponProto.AmmoCapacity;

            var selectedProtoItemAmmo = weaponReloadingState.ProtoItemAmmo;

            if (weaponAmmoCount > 0)
            {
                if (selectedProtoItemAmmo != itemWeaponPrivateState.CurrentProtoItemAmmo &&
                    weaponAmmoCount > 0)
                {
                    // unload current ammo
                    if (IsServer)
                    {
                        Server.Items.CreateItem(
                            toCharacter: character,
                            protoItem: itemWeaponPrivateState.CurrentProtoItemAmmo,
                            count: (ushort)weaponAmmoCount);
                    }

                    Logger.Info(
                        $"Weapon ammo unloaded: {itemWeapon} -> {weaponAmmoCount} {itemWeaponPrivateState.CurrentProtoItemAmmo})",
                        character);

                    weaponAmmoCount = 0;
                    itemWeaponPrivateState.AmmoCount = 0;
                }
                else // if the same ammo type is loaded
                if (weaponAmmoCount == weaponAmmoCapacity)
                {
                    // already completely loaded
                    Logger.Info(
                        $"Weapon reloading cancelled: {itemWeapon} - no reloading is required ({weaponAmmoCount}/{weaponAmmoCapacity} {selectedProtoItemAmmo})",
                        character);
                    return;
                }
            }
            else // if ammoCount == 0
            if (selectedProtoItemAmmo == null &&
                itemWeaponPrivateState.CurrentProtoItemAmmo == null)
            {
                Logger.Info(
                    $"Weapon reloading cancelled: {itemWeapon} - already unloaded ({weaponAmmoCount}/{weaponAmmoCapacity})",
                    character);
                return;
            }

            if (selectedProtoItemAmmo != null)
            {
                var selectedAmmoGroup = SharedGetCompatibleAmmoGroups(character, itemWeaponProto)
                                        .FirstOrDefault(g => g.Key == selectedProtoItemAmmo);

                if (selectedAmmoGroup == null)
                {
                    Logger.Warning(
                        $"Weapon reloading impossible: {itemWeapon} - no ammo of the required type ({selectedProtoItemAmmo})",
                        character);
                    return;
                }

                var ammoItems = SharedSelectAmmoItemsFromGroup(selectedAmmoGroup,
                                                               ammoCountNeed: weaponAmmoCapacity - weaponAmmoCount);
                foreach (var request in ammoItems)
                {
                    var itemAmmo = request.Item;
                    if (itemAmmo.ProtoItem != selectedProtoItemAmmo)
                    {
                        Logger.Error(
                            "Trying to load multiple ammo types in one reloading: "
                            + ammoItems.Select(a => a.Item.ProtoItem).GetJoinedString(),
                            character);
                        break;
                    }

                    int ammoToSubstract;

                    var itemAmmoCount = itemAmmo.Count;
                    if (itemAmmoCount == 0)
                    {
                        continue;
                    }

                    if (request.Count != itemAmmoCount)
                    {
                        if (request.Count < itemAmmoCount)
                        {
                            itemAmmoCount = request.Count;
                        }
                        else if (IsServer)
                        {
                            Logger.Warning(
                                $"Trying to take more ammo to reload than player have: {itemAmmo} requested {request.Count}. Will reload as much as possible only.",
                                character);
                        }
                    }

                    if (weaponAmmoCount + itemAmmoCount >= weaponAmmoCapacity)
                    {
                        // there are more than enough ammo in that item stack to fully refill the weapon
                        ammoToSubstract = weaponAmmoCapacity - weaponAmmoCount;
                        weaponAmmoCount = weaponAmmoCapacity;
                    }
                    else
                    {
                        // consume full item stack
                        ammoToSubstract  = itemAmmoCount;
                        weaponAmmoCount += itemAmmoCount;
                    }

                    // check if character owns this item
                    if (itemAmmo.Container.OwnerAsCharacter != character)
                    {
                        Logger.Error("The character doesn't own " + itemAmmo + " - cannot use it to reload",
                                     character);
                        continue;
                    }

                    // reduce ammo item count
                    if (IsServer)
                    {
                        Server.Items.SetCount(
                            itemAmmo,
                            itemAmmo.Count - ammoToSubstract,
                            byCharacter: character);
                    }

                    if (weaponAmmoCount == weaponAmmoCapacity)
                    {
                        // the weapon is fully reloaded, no need to subtract ammo from the next ammo items
                        break;
                    }
                }
            }

            if (itemWeaponPrivateState.CurrentProtoItemAmmo != selectedProtoItemAmmo)
            {
                // another ammo type selected
                itemWeaponPrivateState.CurrentProtoItemAmmo = selectedProtoItemAmmo;
                // reset weapon cache (it will be re-calculated on next fire processing)
                weaponState.WeaponCache = null;
            }

            if (weaponAmmoCount < 0 ||
                weaponAmmoCount > weaponAmmoCapacity)
            {
                Logger.Error(
                    "Something is completely wrong during reloading! Result ammo count is: " + weaponAmmoCount);
                weaponAmmoCount = 0;
            }

            itemWeaponPrivateState.AmmoCount = (ushort)weaponAmmoCount;

            Logger.Info(
                $"Weapon reloaded: {itemWeapon} - ammo {weaponAmmoCount}/{weaponAmmoCapacity} {(selectedProtoItemAmmo?.ToString() ?? "<no ammo>")}",
                character);
        }
示例#14
0
        private static void SharedFireWeapon(
            ICharacter character,
            IItem weaponItem,
            IProtoItemWeapon protoWeapon,
            WeaponState weaponState)
        {
            protoWeapon.SharedOnFire(character, weaponState);

            var playerCharacterSkills = character.SharedGetSkills();
            var protoWeaponSkill      = playerCharacterSkills != null
                                       ? protoWeapon.WeaponSkillProto
                                       : null;

            if (IsServer)
            {
                // give experience for shot
                protoWeaponSkill?.ServerOnShot(playerCharacterSkills);
            }

            var weaponCache = weaponState.WeaponCache;

            if (weaponCache == null)
            {
                // calculate new weapon cache
                RebuildWeaponCache(character, weaponState);
                weaponCache = weaponState.WeaponCache;
            }

            // raycast possible victims
            var fromPosition = character.Position
                               + (0, character.ProtoCharacter.CharacterWorldWeaponOffset);

            var toPosition = fromPosition
                             + new Vector2D(weaponCache.RangeMax, 0)
                             .RotateRad(character.ProtoCharacter.SharedGetRotationAngleRad(character));

            var collisionGroup = protoWeapon is IProtoItemWeaponMelee
                                     ? CollisionGroups.HitboxMelee
                                     : CollisionGroups.HitboxRanged;

            using (var lineTestResults = character.PhysicsBody.PhysicsSpace.TestLine(
                       fromPosition: fromPosition,
                       toPosition: toPosition,
                       collisionGroup: collisionGroup))
            {
                var damageMultiplier = 1d;
                var isMeleeWeapon    = protoWeapon is IProtoItemWeaponMelee;
                var hitObjects       = new List <WeaponHitData>(isMeleeWeapon ? 1 : lineTestResults.Count);

                foreach (var testResult in lineTestResults)
                {
                    var testResultPhysicsBody = testResult.PhysicsBody;
                    var attackedProtoTile     = testResultPhysicsBody.AssociatedProtoTile;
                    if (attackedProtoTile != null)
                    {
                        if (attackedProtoTile.Kind != TileKind.Solid)
                        {
                            // non-solid obstacle - skip
                            continue;
                        }

                        // tile on the way - blocking damage ray
                        break;
                    }

                    var damagedObject = testResultPhysicsBody.AssociatedWorldObject;
                    if (damagedObject == character)
                    {
                        // ignore collision with self
                        continue;
                    }

                    if (!(damagedObject.ProtoGameObject is IDamageableProtoWorldObject damageableProto))
                    {
                        // shoot through this object
                        continue;
                    }

                    if (!damageableProto.SharedOnDamage(
                            weaponCache,
                            damagedObject,
                            damageMultiplier,
                            out var obstacleBlockDamageCoef,
                            out var damageApplied))
                    {
                        // not hit
                        continue;
                    }

                    if (IsServer)
                    {
                        weaponCache.ProtoWeapon
                        .ServerOnDamageApplied(weaponCache.Weapon, character, damagedObject, damageApplied);

                        if (damageApplied > 0 &&
                            protoWeaponSkill != null)
                        {
                            // give experience for damage
                            protoWeaponSkill.ServerOnDamageApplied(playerCharacterSkills, damagedObject, damageApplied);

                            if (damagedObject is ICharacter damagedCharacter &&
                                damagedCharacter.GetPublicState <ICharacterPublicState>().CurrentStats.HealthCurrent
                                <= 0)
                            {
                                // give weapon experience for kill
                                Logger.Info("Killed " + damagedCharacter, character);
                                protoWeaponSkill.ServerOnKill(playerCharacterSkills, killedCharacter: damagedCharacter);

                                if (damagedCharacter.ProtoCharacter is ProtoCharacterMob protoMob)
                                {
                                    // give hunting skill experience for mob kill
                                    var experience = SkillHunting.ExperienceForKill;
                                    experience *= protoMob.MobKillExperienceMultiplier;
                                    if (experience > 0)
                                    {
                                        playerCharacterSkills.ServerAddSkillExperience <SkillHunting>(experience);
                                    }
                                }
                            }
                        }
                    }

                    if (obstacleBlockDamageCoef < 0 ||
                        obstacleBlockDamageCoef > 1)
                    {
                        Logger.Error(
                            "Obstacle block damage coefficient should be >= 0 and <= 1 - wrong calculation by "
                            + damageableProto);
                        break;
                    }

                    //var hitPosition = testResultPhysicsBody.Position + testResult.Penetration;
                    hitObjects.Add(new WeaponHitData(damagedObject)); //, hitPosition));

                    if (isMeleeWeapon)
                    {
                        // currently melee weapon could attack only one object on the ray
                        break;
                    }

                    damageMultiplier = damageMultiplier * (1.0 - obstacleBlockDamageCoef);
                    if (damageMultiplier <= 0)
                    {
                        // target blocked the damage ray
                        break;
                    }
                }

                if (hitObjects.Count > 0)
                {
                    if (IsClient)
                    {
                        // display weapon shot on Client-side
                        WeaponSystemClientDisplay.OnWeaponHit(protoWeapon, hitObjects);
                    }
                    else // if server
                    {
                        // display damages on clients in scope of every damaged object
                        using (var scopedBy = Api.Shared.GetTempList <ICharacter>())
                        {
                            foreach (var hitObject in hitObjects)
                            {
                                if (hitObject.WorldObject.IsDestroyed)
                                {
                                    continue;
                                }

                                Server.World.GetScopedByPlayers(hitObject.WorldObject, scopedBy);
                                scopedBy.Remove(character);
                                if (scopedBy.Count == 0)
                                {
                                    continue;
                                }

                                Instance.CallClient(scopedBy,
                                                    _ => _.ClientRemote_OnWeaponHit(protoWeapon, hitObject));
                                scopedBy.Clear();
                            }
                        }
                    }
                }

                if (IsServer)
                {
                    protoWeapon.ServerOnShot(character, weaponItem, protoWeapon, hitObjects);
                }
            }
        }
示例#15
0
        /// <summary>
        /// Executed when a weapon must reload (after the reloading duration is completed).
        /// </summary>
        private static void SharedProcessWeaponReload(ICharacter character, WeaponState weaponState)
        {
            var weaponReloadingState = weaponState.WeaponReloadingState;

            // remove weapon reloading state
            weaponState.WeaponReloadingState = null;

            var itemWeapon             = weaponReloadingState.Item;
            var itemWeaponProto        = (IProtoItemWeapon)itemWeapon.ProtoGameObject;
            var itemWeaponPrivateState = itemWeapon.GetPrivateState <WeaponPrivateState>();
            var weaponAmmoCount        = (int)itemWeaponPrivateState.AmmoCount;
            var weaponAmmoCapacity     = itemWeaponProto.AmmoCapacity;

            var selectedProtoItemAmmo = weaponReloadingState.ProtoItemAmmo;
            var currentProtoItemAmmo  = itemWeaponPrivateState.CurrentProtoItemAmmo;

            if (weaponAmmoCount > 0)
            {
                if (selectedProtoItemAmmo != currentProtoItemAmmo &&
                    weaponAmmoCount > 0)
                {
                    // unload current ammo
                    if (IsServer)
                    {
                        var result = Server.Items.CreateItem(
                            toCharacter: character,
                            protoItem: currentProtoItemAmmo,
                            count: (ushort)weaponAmmoCount);

                        if (!result.IsEverythingCreated)
                        {
                            // cannot unload current ammo - no space, try to unload to the ground
                            result.Rollback();

                            var tile            = Api.Server.World.GetTile(character.TilePosition);
                            var groundContainer = ObjectGroundItemsContainer
                                                  .ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(tile);

                            if (groundContainer == null)
                            {
                                // cannot unload current ammo to the ground - no free space around character
                                Instance.CallClient(character,
                                                    _ => _.ClientRemote_NoSpaceForUnloadedAmmo(currentProtoItemAmmo));
                                return;
                            }

                            result = Server.Items.CreateItem(
                                container: groundContainer,
                                protoItem: currentProtoItemAmmo,
                                count: (ushort)weaponAmmoCount);

                            if (!result.IsEverythingCreated)
                            {
                                // cannot unload current ammo to the ground - no space in ground containers near the character
                                result.Rollback();
                                Instance.CallClient(character,
                                                    _ => _.ClientRemote_NoSpaceForUnloadedAmmo(
                                                        currentProtoItemAmmo));
                                return;
                            }

                            // notify player that there were not enough space in inventory so the items were dropped to the ground
                            NotificationSystem.ServerSendNotificationNoSpaceInInventoryItemsDroppedToGround(
                                character,
                                result.ItemAmounts.First().Key?.ProtoItem);
                        }
                    }

                    Logger.Info(
                        $"Weapon ammo unloaded: {itemWeapon} -> {weaponAmmoCount} {currentProtoItemAmmo})",
                        character);

                    weaponAmmoCount = 0;
                    itemWeaponPrivateState.AmmoCount = 0;
                }
                else // if the same ammo type is loaded
                if (weaponAmmoCount == weaponAmmoCapacity)
                {
                    // already completely loaded
                    Logger.Info(
                        $"Weapon reloading cancelled: {itemWeapon} - no reloading is required ({weaponAmmoCount}/{weaponAmmoCapacity} {selectedProtoItemAmmo})",
                        character);
                    return;
                }
            }
            else // if ammoCount == 0
            if (selectedProtoItemAmmo == null &&
                currentProtoItemAmmo == null)
            {
                Logger.Info(
                    $"Weapon reloading cancelled: {itemWeapon} - already unloaded ({weaponAmmoCount}/{weaponAmmoCapacity})",
                    character);
                return;
            }

            if (selectedProtoItemAmmo != null)
            {
                var selectedAmmoGroup = SharedGetCompatibleAmmoGroups(character, itemWeaponProto)
                                        .FirstOrDefault(g => g.Key == selectedProtoItemAmmo);

                if (selectedAmmoGroup == null)
                {
                    Logger.Warning(
                        $"Weapon reloading impossible: {itemWeapon} - no ammo of the required type ({selectedProtoItemAmmo})",
                        character);
                    return;
                }

                var ammoItems = SharedSelectAmmoItemsFromGroup(selectedAmmoGroup,
                                                               ammoCountNeed: weaponAmmoCapacity - weaponAmmoCount);
                foreach (var request in ammoItems)
                {
                    var itemAmmo = request.Item;
                    if (itemAmmo.ProtoItem != selectedProtoItemAmmo)
                    {
                        Logger.Error(
                            "Trying to load multiple ammo types in one reloading: "
                            + ammoItems.Select(a => a.Item.ProtoItem).GetJoinedString(),
                            character);
                        break;
                    }

                    int ammoToSubstract;

                    var itemAmmoCount = itemAmmo.Count;
                    if (itemAmmoCount == 0)
                    {
                        continue;
                    }

                    if (request.Count != itemAmmoCount)
                    {
                        if (request.Count < itemAmmoCount)
                        {
                            itemAmmoCount = request.Count;
                        }
                        else if (IsServer)
                        {
                            Logger.Warning(
                                $"Trying to take more ammo to reload than player have: {itemAmmo} requested {request.Count}. Will reload as much as possible only.",
                                character);
                        }
                    }

                    if (weaponAmmoCount + itemAmmoCount >= weaponAmmoCapacity)
                    {
                        // there are more than enough ammo in that item stack to fully refill the weapon
                        ammoToSubstract = weaponAmmoCapacity - weaponAmmoCount;
                        weaponAmmoCount = weaponAmmoCapacity;
                    }
                    else
                    {
                        // consume full item stack
                        ammoToSubstract  = itemAmmoCount;
                        weaponAmmoCount += itemAmmoCount;
                    }

                    // check if character owns this item
                    if (itemAmmo.Container.OwnerAsCharacter != character)
                    {
                        Logger.Error("The character doesn't own " + itemAmmo + " - cannot use it to reload",
                                     character);
                        continue;
                    }

                    // reduce ammo item count
                    if (IsServer)
                    {
                        Server.Items.SetCount(
                            itemAmmo,
                            itemAmmo.Count - ammoToSubstract,
                            byCharacter: character);
                    }

                    if (weaponAmmoCount == weaponAmmoCapacity)
                    {
                        // the weapon is fully reloaded, no need to subtract ammo from the next ammo items
                        break;
                    }
                }
            }

            if (currentProtoItemAmmo != selectedProtoItemAmmo)
            {
                // another ammo type selected
                itemWeaponPrivateState.CurrentProtoItemAmmo = selectedProtoItemAmmo;
                // reset weapon cache (it will be re-calculated on next fire processing)
                weaponState.WeaponCache = null;
            }

            if (weaponAmmoCount < 0 ||
                weaponAmmoCount > weaponAmmoCapacity)
            {
                Logger.Error(
                    "Something is completely wrong during reloading! Result ammo count is: " + weaponAmmoCount);
                weaponAmmoCount = 0;
            }

            itemWeaponPrivateState.AmmoCount = (ushort)weaponAmmoCount;

            Logger.Info(
                $"Weapon reloaded: {itemWeapon} - ammo {weaponAmmoCount}/{weaponAmmoCapacity} {selectedProtoItemAmmo?.ToString() ?? "<no ammo>"}",
                character);
        }
示例#16
0
        public static void SharedUpdateCurrentWeapon(
            ICharacter character,
            WeaponState state,
            double deltaTime)
        {
            var protoWeapon = state.ActiveProtoWeapon;

            if (protoWeapon == null)
            {
                return;
            }

            if (state.CooldownSecondsRemains > 0)
            {
                // decrease cooldown
                state.CooldownSecondsRemains -= deltaTime;
            }

            if (!state.IsFiring)
            {
                WeaponAmmoSystem.SharedUpdateReloading(state, character, ref deltaTime);
            }

            if (deltaTime <= 0)
            {
                // the weapon reloading process is consumed the whole delta time
                return;
            }

            if (state.SharedGetInputIsFiring() &&
                !character.IsOnline)
            {
                state.SetInputIsFiring(false);
            }

            if (state.SharedGetInputIsFiring() &&
                StatusEffectDazed.SharedIsCharacterDazed(character,
                                                         StatusEffectDazed.NotificationCannotAttackWhileDazed))
            {
                state.SetInputIsFiring(false);
            }

            // check ammo (if applicable to this weapon prototype)
            var canFire = protoWeapon.SharedCanFire(character, state);

            if (state.CooldownSecondsRemains > 0)
            {
                // firing cooldown is not completed
                if (!state.SharedGetInputIsFiring() &&
                    state.IsEventWeaponStartSent)
                {
                    // not firing anymore
                    SharedCallOnWeaponInputStop(state, character);
                }

                return;
            }

            var wasFiring = state.IsFiring;

            if (!state.IsFiring)
            {
                state.IsFiring = state.SharedGetInputIsFiring();
            }
            else // if IsFiring
            {
                if (!SharedShouldFireMore(state))
                {
                    state.IsFiring = state.SharedGetInputIsFiring();
                }
            }

            if (!canFire)
            {
                // cannot fire (no ammo, etc)
                state.IsFiring = false;
            }

            if (!state.IsFiring)
            {
                if (wasFiring)
                {
                    // just stopped firing
                    SharedCallOnWeaponFinished(state, character);
                }

                // the character is not firing
                // reset delay for the next shot (it will be set when firing starts next time)
                state.DamageApplyDelaySecondsRemains = 0;
                return;
            }

            // let's process what happens when we're in the firing mode
            if (!state.IsEventWeaponStartSent)
            {
                // started firing
                SharedCallOnWeaponStart(state, character);
            }

            if (state.DamageApplyDelaySecondsRemains <= 0)
            {
                // initialize delay to next shot
                state.DamageApplyDelaySecondsRemains = protoWeapon.DamageApplyDelay;
                SharedCallOnWeaponShot(character);
            }

            // decrease the remaining time to the damage application
            state.DamageApplyDelaySecondsRemains -= deltaTime;

            if (state.DamageApplyDelaySecondsRemains > 0)
            {
                // firing delay not completed
                return;
            }

            // firing delay completed
            state.ShotsDone++;
            //Logger.Dev("Weapon fired, shots done: " + state.ShotsDone);
            SharedFireWeapon(character, state.ActiveItemWeapon, protoWeapon, state);
            state.CooldownSecondsRemains += protoWeapon.FireInterval - protoWeapon.DamageApplyDelay;

            if (!protoWeapon.IsLoopedAttackAnimation)
            {
                // we don't want to stuck this animation in the last frame
                // that's fix for the issue:
                // "Fix extended animation "stuck" issue for mobs (like limbs stuck in the end position and movement animation appears broken)"
                state.IsEventWeaponStartSent = false;
            }
        }
示例#17
0
        public static void SharedUpdateCurrentWeapon(
            ICharacter character,
            WeaponState state,
            double deltaTime)
        {
            var protoWeapon = state.ProtoWeapon;

            if (protoWeapon == null)
            {
                return;
            }

            if (deltaTime > 0.4)
            {
                // too large delta time probably due to a frame skip
                deltaTime = 0.4;
            }

            if (state.CooldownSecondsRemains > 0)
            {
                state.CooldownSecondsRemains -= deltaTime;
                if (state.CooldownSecondsRemains < -0.2)
                {
                    // clamp the remaining cooldown in case of a frame skip
                    state.CooldownSecondsRemains = -0.2;
                }
            }

            if (state.ReadySecondsRemains > 0)
            {
                state.ReadySecondsRemains -= deltaTime;
            }

            if (state.FirePatternCooldownSecondsRemains > 0)
            {
                state.FirePatternCooldownSecondsRemains -= deltaTime;

                if (state.FirePatternCooldownSecondsRemains <= 0)
                {
                    state.FirePatternCurrentShotNumber = 0;
                }
            }

            // TODO: restore this condition when we redo UI countdown animation for ViewModelHotbarItemWeaponOverlayControl.ReloadDurationSeconds
            //if (state.CooldownSecondsRemains <= 0)
            //{
            WeaponAmmoSystem.SharedUpdateReloading(state, character, deltaTime);
            //}

            if (Api.IsServer &&
                !character.ServerIsOnline &&
                state.SharedGetInputIsFiring())
            {
                state.SharedSetInputIsFiring(false);
            }

            // check ammo (if applicable to this weapon prototype)
            var canFire = (Api.IsClient || character.ServerIsOnline) &&
                          state.WeaponReloadingState is null &&
                          protoWeapon.SharedCanFire(character, state);

            if (state.CooldownSecondsRemains > 0)
            {
                // firing cooldown is not completed
                if (!state.SharedGetInputIsFiring() &&
                    state.IsEventWeaponStartSent)
                {
                    // not firing anymore
                    SharedCallOnWeaponInputStop(state, character);
                }

                return;
            }

            var wasFiring = state.IsFiring;

            if (!state.IsFiring)
            {
                state.IsFiring = state.SharedGetInputIsFiring();
            }
            else // if IsFiring
            {
                if (!SharedShouldFireMore(state))
                {
                    state.IsFiring = state.SharedGetInputIsFiring();
                }
            }

            if (!canFire)
            {
                // cannot fire (no ammo, etc)
                state.IsFiring = false;
            }

            if (!state.IsFiring)
            {
                if (wasFiring)
                {
                    // just stopped firing
                    SharedCallOnWeaponFinished(state, character);
                }

                // the character is not firing
                // reset delay for the next shot (it will be set when firing starts next time)
                state.DamageApplyDelaySecondsRemains = 0;
                return;
            }

            if (state.WeaponCache is null)
            {
                SharedRebuildWeaponCache(character, state);
            }

            // let's process what happens when we're in the firing mode
            if (!state.IsEventWeaponStartSent)
            {
                // started firing
                SharedCallOnWeaponStart(state, character);
            }

            if (state.DamageApplyDelaySecondsRemains <= 0)
            {
                // initialize delay to next shot
                state.DamageApplyDelaySecondsRemains =
                    Shared.RoundDurationByServerFrameDuration(protoWeapon.DamageApplyDelay);

                SharedCallOnWeaponShot(character, protoWeapon);
            }

            // decrease the remaining time to the damage application
            state.DamageApplyDelaySecondsRemains -= deltaTime;

            if (state.DamageApplyDelaySecondsRemains > 0)
            {
                // firing delay not completed
                return;
            }

            // firing delay completed
            state.ShotsDone++;
            //Logger.Dev("Weapon fired, shots done: " + state.ShotsDone);
            SharedFireWeapon(character, state.ItemWeapon, protoWeapon, state);
            var cooldownDuration = Shared.RoundDurationByServerFrameDuration(protoWeapon.FireInterval)
                                   - Shared.RoundDurationByServerFrameDuration(protoWeapon.DamageApplyDelay);

            //Logger.Dev($"Cooldown adding: {cooldownDuration} for {protoWeapon}");

            state.CooldownSecondsRemains += cooldownDuration;

            if (!protoWeapon.IsLoopedAttackAnimation)
            {
                // we don't want to stuck this animation in the last frame
                // that's fix for the issue:
                // "Fix extended animation "stuck" issue for mobs (like limbs stuck in the end position and movement animation appears broken)"
                state.IsEventWeaponStartSent = false;
            }
        }
示例#18
0
        /// <summary>
        /// Executed when a weapon must reload (after the reloading duration is completed).
        /// </summary>
        private static void SharedProcessWeaponReload(
            ICharacter character,
            WeaponState weaponState,
            out bool isAmmoTypeChanged)
        {
            var weaponReloadingState = weaponState.WeaponReloadingState;

            // remove weapon reloading state
            weaponState.WeaponReloadingState = null;

            var itemWeapon             = weaponReloadingState.Item;
            var itemWeaponProto        = (IProtoItemWeapon)itemWeapon.ProtoGameObject;
            var itemWeaponPrivateState = itemWeapon.GetPrivateState <WeaponPrivateState>();
            var weaponAmmoCount        = (int)itemWeaponPrivateState.AmmoCount;
            var weaponAmmoCapacity     = itemWeaponProto.AmmoCapacity;

            isAmmoTypeChanged = false;

            var selectedProtoItemAmmo = weaponReloadingState.ProtoItemAmmo;
            var currentProtoItemAmmo  = itemWeaponPrivateState.CurrentProtoItemAmmo;

            if (weaponAmmoCount > 0)
            {
                if (selectedProtoItemAmmo != currentProtoItemAmmo &&
                    weaponAmmoCount > 0)
                {
                    // unload current ammo
                    if (IsServer)
                    {
                        var targetContainers =
                            SharedGetTargetContainersForCharacterAmmo(character, isForAmmoUnloading: true);
                        var result = Server.Items.CreateItem(
                            protoItem: currentProtoItemAmmo,
                            new AggregatedItemsContainers(targetContainers),
                            count: (ushort)weaponAmmoCount);

                        if (!result.IsEverythingCreated)
                        {
                            // cannot unload current ammo - no space, try to unload to the ground
                            result.Rollback();

                            var tile            = Api.Server.World.GetTile(character.TilePosition);
                            var groundContainer = ObjectGroundItemsContainer
                                                  .ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, tile);

                            if (groundContainer is null)
                            {
                                // cannot unload current ammo to the ground - no free space around character
                                NotificationSystem.ServerSendNotificationNoSpaceInInventory(character);
                                return;
                            }

                            result = Server.Items.CreateItem(
                                container: groundContainer,
                                protoItem: currentProtoItemAmmo,
                                count: (ushort)weaponAmmoCount);

                            if (!result.IsEverythingCreated)
                            {
                                // cannot unload current ammo to the ground - no space in ground containers near the character
                                result.Rollback();
                                NotificationSystem.ServerSendNotificationNoSpaceInInventory(character);
                                return;
                            }

                            // notify player that there were not enough space in inventory so the items were dropped to the ground
                            NotificationSystem.ServerSendNotificationNoSpaceInInventoryItemsDroppedToGround(
                                character,
                                result.ItemAmounts.First().Key?.ProtoItem);
                        }
                    }

                    Logger.Info(
                        $"Weapon ammo unloaded: {itemWeapon} -> {weaponAmmoCount} {currentProtoItemAmmo})",
                        character);

                    weaponAmmoCount = 0;
                    itemWeaponPrivateState.SetAmmoCount(0);
                }
                else // if the same ammo type is loaded
                if (weaponAmmoCount == weaponAmmoCapacity)
                {
                    // already completely loaded
                    Logger.Info(
                        $"Weapon reloading cancelled: {itemWeapon} - no reloading is required ({weaponAmmoCount}/{weaponAmmoCapacity} {selectedProtoItemAmmo})",
                        character);
                    return;
                }
            }
            else // if ammoCount == 0
            if (selectedProtoItemAmmo is null &&
                currentProtoItemAmmo is null)
            {
                Logger.Info(
                    $"Weapon reloading cancelled: {itemWeapon} - already unloaded ({weaponAmmoCount}/{weaponAmmoCapacity})",
                    character);
                return;
            }

            if (selectedProtoItemAmmo != null)
            {
                var selectedAmmoGroup = SharedGetCompatibleAmmoGroups(character, itemWeaponProto)
                                        .FirstOrDefault(g => g.Key == selectedProtoItemAmmo);

                if (selectedAmmoGroup is null)
                {
                    Logger.Warning(
                        $"Weapon reloading impossible: {itemWeapon} - no ammo of the required type ({selectedProtoItemAmmo})",
                        character);
                    return;
                }

                var ammoItems = SharedSelectAmmoItemsFromGroup(selectedAmmoGroup,
                                                               ammoCountNeed: weaponAmmoCapacity - weaponAmmoCount);
                foreach (var request in ammoItems)
                {
                    var itemAmmo = request.Item;
                    Api.Assert(itemAmmo.ProtoItem == selectedProtoItemAmmo, "Sanity check");

                    int ammoToSubstract;

                    var itemAmmoCount = itemAmmo.Count;
                    if (itemAmmoCount == 0)
                    {
                        continue;
                    }

                    if (request.Count != itemAmmoCount)
                    {
                        if (request.Count < itemAmmoCount)
                        {
                            itemAmmoCount = request.Count;
                        }
                        else if (IsServer)
                        {
                            Logger.Warning(
                                $"Trying to take more ammo to reload than player have: {itemAmmo} requested {request.Count}. Will reload as much as possible only.",
                                character);
                        }
                    }

                    if (weaponAmmoCount + itemAmmoCount >= weaponAmmoCapacity)
                    {
                        // there are more than enough ammo in that item stack to fully refill the weapon
                        ammoToSubstract = weaponAmmoCapacity - weaponAmmoCount;
                        weaponAmmoCount = weaponAmmoCapacity;
                    }
                    else
                    {
                        // consume full item stack
                        ammoToSubstract  = itemAmmoCount;
                        weaponAmmoCount += itemAmmoCount;
                    }

                    // reduce ammo item count
                    if (IsServer)
                    {
                        Server.Items.SetCount(
                            itemAmmo,
                            itemAmmo.Count - ammoToSubstract,
                            byCharacter: character);
                    }

                    if (weaponAmmoCount == weaponAmmoCapacity)
                    {
                        // the weapon is fully reloaded, no need to subtract ammo from the next ammo items
                        break;
                    }
                }
            }

            if (currentProtoItemAmmo != selectedProtoItemAmmo)
            {
                // another ammo type selected
                itemWeaponPrivateState.CurrentProtoItemAmmo = selectedProtoItemAmmo;
                // reset weapon cache (it will be re-calculated on next fire processing)
                weaponState.WeaponCache = null;
                isAmmoTypeChanged       = true;
            }

            if (weaponAmmoCount < 0 ||
                weaponAmmoCount > weaponAmmoCapacity)
            {
                Logger.Error(
                    "Something is completely wrong during reloading! Result ammo count is: " + weaponAmmoCount);
                weaponAmmoCount = 0;
            }

            itemWeaponPrivateState.SetAmmoCount((ushort)weaponAmmoCount);

            if (weaponAmmoCount == 0)
            {
                // weapon unloaded - and the log entry about this is already written (see above)
                return;
            }

            Logger.Info(
                $"Weapon reloaded: {itemWeapon} - ammo {weaponAmmoCount}/{weaponAmmoCapacity} {selectedProtoItemAmmo?.ToString() ?? "<no ammo>"}",
                character);

            if (IsServer)
            {
                ServerNotifyAboutReloading(character, weaponState, isFinished: true);
            }
            else
            {
                weaponState.ProtoWeapon.SoundPresetWeapon
                .PlaySound(WeaponSound.ReloadFinished,
                           character,
                           SoundConstants.VolumeWeapon);
            }
        }
示例#19
0
        public static void SharedUpdateReloading(
            WeaponState weaponState,
            ICharacter character,
            double deltaTime,
            out bool isReloadingNow)
        {
            var reloadingState = weaponState.WeaponReloadingState;

            if (reloadingState is null)
            {
                isReloadingNow = false;
                return;
            }

            if (reloadingState.Item != weaponState.ItemWeapon)
            {
                Logger.Info("Can reload only the current weapon. Reloading aborted");
                SharedTryAbortReloading(character, reloadingState.Item);
                isReloadingNow = false;
                return;
            }

            if (IsServer &&
                weaponState.SharedGetInputIsFiring() &&
                weaponState.ItemWeapon is not null &&
                weaponState.ItemWeapon.GetPrivateState <WeaponPrivateState>().AmmoCount > 0)
            {
                var shotsRemains = (long)weaponState.ServerLastClientReportedShotsDoneCount - weaponState.ShotsDone;
                if (shotsRemains > 0 &&
                    weaponState.ServerLastClientReportedShotsDoneCount > 0)
                {
                    // sometimes the client reloading requests are received by the server
                    // too early (before the server fired all the shots) so the server should fire them first
                    //Logger.Dev("Server cannot reload while client is firing. Shot remains: " + shotsRemains);
                    isReloadingNow = false;
                    return;
                }
            }

            // process reloading
            reloadingState.SecondsToReloadRemains -= deltaTime;
            if (reloadingState.SecondsToReloadRemains > 0)
            {
                // need more time to reload
                isReloadingNow = true;
                return;
            }

            // reloaded
            reloadingState.SecondsToReloadRemains = 0;
            SharedProcessWeaponReload(character,
                                      weaponState,
                                      out var isAmmoTypeChanged);

            if (isAmmoTypeChanged)
            {
                weaponState.ClearFiringStateData();
                //Api.Logger.Dev("Reset ServerLastClientReportedShotsDoneCount. Last value: "
                //               + weaponState.ServerLastClientReportedShotsDoneCount);
            }

            weaponState.FirePatternCooldownSecondsRemains = 0;
            weaponState.IsIdleAutoReloadingAllowed        = true;
            isReloadingNow = false;
        }