Beispiel #1
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));
                }
            }
        }
Beispiel #2
0
 private void ClientRemote_OnWeaponHit(IProtoItemWeapon protoWeapon, WeaponHitData hitObject)
 {
     using (var tempList = Api.Shared.WrapObjectInTempList(hitObject))
     {
         WeaponSystemClientDisplay.OnWeaponHit(protoWeapon, tempList);
     }
 }
Beispiel #3
0
        private static void SharedCallOnWeaponShot(ICharacter character, IProtoItemWeapon protoWeapon)
        {
            if (IsClient)
            {
                // start firing weapon on Client-side
                WeaponSystemClientDisplay.OnWeaponShot(character,
                                                       protoWeapon: protoWeapon,
                                                       fallbackProtoCharacter: character.ProtoCharacter,
                                                       fallbackPosition: character.Position);
            }
            else // if IsServer
            {
                using var observers = Api.Shared.GetTempList <ICharacter>();
                var eventNetworkRadius = (byte)Math.Max(
                    15,
                    Math.Ceiling(protoWeapon.SoundPresetWeaponDistance.max));

                Server.World.GetCharactersInRadius(character.TilePosition,
                                                   observers,
                                                   radius: eventNetworkRadius,
                                                   onlyPlayers: true);
                observers.Remove(character);

                Instance.CallClient(observers,
                                    _ => _.ClientRemote_OnWeaponShot(character,
                                                                     protoWeapon,
                                                                     character.ProtoCharacter,
                                                                     character.Position));
            }
        }
Beispiel #4
0
        private void ClientRemote_OnWeaponStart(ICharacter whoFires)
        {
            if (whoFires == null ||
                !whoFires.IsInitialized)
            {
                return;
            }

            WeaponSystemClientDisplay.OnWeaponStart(whoFires);
        }
Beispiel #5
0
        private static void ClientOnCharactersHitByExplosion(IReadOnlyList <WeaponHitData> hitCharacters)
        {
            foreach (var hitData in hitCharacters)
            {
                var protoWorldObject = hitData.FallbackProtoWorldObject;
                var objectMaterial   = hitData.FallbackObjectMaterial;
                var hitWorldObject   = hitData.WorldObject;
                if (hitWorldObject is not null &&
                    !hitWorldObject.IsInitialized)
                {
                    hitWorldObject = null;
                }

                var worldObjectPosition = CalculateWorldObjectPosition(hitWorldObject, hitData);

                // apply some volume variation
                var volume = SoundConstants.VolumeHit;
                volume *= RandomHelper.Range(0.8f, 1.0f);
                var pitch = RandomHelper.Range(0.95f, 1.05f);

                if (hitWorldObject is not null)
                {
                    SoundPresetHitExplosion.PlaySound(
                        objectMaterial,
                        hitWorldObject,
                        volume: volume,
                        pitch: pitch);
                }
                else
                {
                    SoundPresetHitExplosion.PlaySound(
                        objectMaterial,
                        protoWorldObject,
                        worldPosition: worldObjectPosition,
                        volume: volume,
                        pitch: pitch);
                }

                WeaponSystemClientDisplay.ClientAddHitSparks(
                    ExplosionHitSparksPreset,
                    hitData,
                    hitWorldObject,
                    protoWorldObject,
                    worldObjectPosition,
                    projectilesCount: 1,
                    objectMaterial: objectMaterial,
                    randomizeHitPointOffset: true,
                    rotationAngleRad: null,
                    randomRotation: true,
                    drawOrder: DrawOrder.Light);
            }
Beispiel #6
0
 private static void SharedCallOnWeaponShot(ICharacter character)
 {
     if (IsClient)
     {
         // start firing weapon on Client-side
         WeaponSystemClientDisplay.OnWeaponShot(character);
     }
     else // if IsServer
     {
         using (var scopedBy = Api.Shared.GetTempList <ICharacter>())
         {
             Server.World.GetScopedByPlayers(character, scopedBy);
             Instance.CallClient(scopedBy,
                                 _ => _.ClientRemote_OnWeaponShot(character));
         }
     }
 }
Beispiel #7
0
        private void ClientRemote_OnWeaponShot(
            ICharacter whoFires,
            IProtoItemWeapon protoWeapon,
            IProtoCharacter fallbackProtoCharacter,
            Vector2D fallbackPosition)
        {
            if (whoFires != null &&
                !whoFires.IsInitialized)
            {
                whoFires = null;
            }

            WeaponSystemClientDisplay.OnWeaponShot(whoFires,
                                                   protoWeapon,
                                                   fallbackProtoCharacter,
                                                   fallbackPosition);
        }
Beispiel #8
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));
            }
        }
Beispiel #9
0
 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);
 }
Beispiel #10
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));
            }
        }
Beispiel #11
0
        private static void SharedCallOnWeaponShot(
            ICharacter character,
            IProtoItemWeapon protoWeapon)
        {
            if (IsClient)
            {
                // start firing weapon on Client-side
                WeaponSystemClientDisplay.ClientOnWeaponShot(character,
                                                             partyId:
                                                             0, // not relevant here as it's the current player firing the weapon
                                                             protoWeapon: protoWeapon,
                                                             protoCharacter: character.ProtoCharacter,
                                                             fallbackPosition: character.Position.ToVector2Ushort());
            }
            else // if IsServer
            {
                using var observers = Shared.GetTempList <ICharacter>();
                var eventNetworkRadius = (byte)Math.Max(
                    15,
                    Math.Ceiling(protoWeapon.SoundPresetWeaponDistance.max));

                Server.World.GetCharactersInRadius(character.TilePosition,
                                                   observers,
                                                   radius: eventNetworkRadius,
                                                   onlyPlayers: true);
                observers.Remove(character);

                if (observers.Count > 0)
                {
                    var partyId = PartySystem.ServerGetParty(character)?.Id ?? 0;

                    Instance.CallClient(observers.AsList(),
                                        _ => _.ClientRemote_OnWeaponShot(character,
                                                                         partyId,
                                                                         protoWeapon,
                                                                         character.ProtoCharacter,
                                                                         character.Position.ToVector2Ushort()));
                }
            }
        }
Beispiel #12
0
 private void ClientRemote_OnWeaponStart(ICharacter whoFires)
 {
     WeaponSystemClientDisplay.OnWeaponStart(whoFires);
 }
Beispiel #13
0
 private void ClientRemote_OnWeaponInputStop(ICharacter whoFires)
 {
     WeaponSystemClientDisplay.OnWeaponInputStop(whoFires);
 }
Beispiel #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);
                }
            }
        }
Beispiel #15
0
        public static void Create(
            WeaponFireTracePreset weaponTracePreset,
            Vector2D worldPositionSource,
            Vector2D endPosition,
            WeaponHitData lastHitData,
            bool hasHit)
        {
            if (weaponTracePreset is null)
            {
                // no weapon trace for this weapon
                return;
            }

            var deltaPos     = endPosition - worldPositionSource;
            var fireDistance = CalculateFireDistance(weaponTracePreset, deltaPos);

            if (fireDistance <= weaponTracePreset.TraceMinDistance)
            {
                return;
            }

            CalculateAngleAndDirection(deltaPos,
                                       out var angleRad,
                                       out var normalizedRay);

            // offset start position of the ray
            worldPositionSource += normalizedRay * weaponTracePreset.TraceStartWorldOffset;

            // actual trace life duration is larger when has a hit
            // (to provide a contact fade-out animation for the sprite length)
            if (!hasHit)
            {
                // otherwise it's shorter on the sprite length
                fireDistance -= weaponTracePreset.TraceWorldLength;
            }

            var totalDuration = WeaponSystemClientDisplay.SharedCalculateTimeToHit(fireDistance, weaponTracePreset);

            var sceneObject           = Api.Client.Scene.CreateSceneObject("Temp_WeaponTrace");
            var componentSpriteRender = Api.Client.Rendering.CreateSpriteRenderer(
                sceneObject,
                weaponTracePreset.TraceTexture,
                positionOffset: Vector2D.Zero,
                spritePivotPoint: (0, 0.5),
                // yes, it's actually making the weapon trace to draw in the light layer!
                drawOrder: DrawOrder.Light);

            componentSpriteRender.RotationAngleRad = (float)angleRad;
            componentSpriteRender.BlendMode        = weaponTracePreset.UseScreenBlending
                                                  ? BlendMode.Screen
                                                     // it's important to use premultiplied mode here for correct rendering
                                                  : BlendMode.AlphaBlendPremultiplied;

            sceneObject.AddComponent <ComponentWeaponTrace>()
            .Setup(weaponTracePreset,
                   componentSpriteRender,
                   worldPositionSource,
                   normalizedRay,
                   fireDistance,
                   totalDuration,
                   hasHit,
                   lastHitData);

            sceneObject.Destroy(totalDuration);
        }
Beispiel #16
0
        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));
                }
            }
        }