Ejemplo n.º 1
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)
            {
                protoWeaponSkill?.ServerOnShot(playerCharacterSkills); // give experience for shot
                CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(character);
            }

            var weaponCache = weaponState.WeaponCache;

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

            // raycast possible victims
            var isMeleeWeapon = protoWeapon is IProtoItemWeaponMelee;
            var fromPosition  = character.Position
                                + (0, isMeleeWeapon
                                         ? character.ProtoCharacter.CharacterWorldWeaponOffsetMelee
                                         : character.ProtoCharacter.CharacterWorldWeaponOffsetRanged);

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

            var collisionGroup = isMeleeWeapon
                                     ? CollisionGroups.HitboxMelee
                                     : CollisionGroups.HitboxRanged;

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

            if (IsClient ||
                Api.IsEditor)
            {
                SharedEditorPhysicsDebugger.SharedVisualizeTestResults(lineTestResults, collisionGroup);
            }

            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;
                    }

                    var attackedTile = IsServer
                                           ? Server.World.GetTile((Vector2Ushort)testResultPhysicsBody.Position)
                                           : Client.World.GetTile((Vector2Ushort)testResultPhysicsBody.Position);

                    if (attackedTile.Height < characterTileHeight)
                    {
                        // attacked tile is below - ignore it
                        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;
                }

                var damagedCharacter = damagedObject as ICharacter;
                if (!ReferenceEquals(damagedCharacter, null))
                {
                    // don't allow damage is there is no direct line of sight on physical colliders layer between the two objects
                    if (SharedHasTileObstacle(character, characterTileHeight, damagedCharacter))
                    {
                        continue;
                    }
                }
                else if (damagedObject is IStaticWorldObject staticWorldObject &&
                         characterTileHeight != staticWorldObject.OccupiedTile.Height)
                {
                    // don't allow damage to static objects on a different height level
                    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 &&
                        !ReferenceEquals(damagedCharacter, null))
                    {
                        CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(damagedCharacter);
                    }

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

                        if (!ReferenceEquals(damagedCharacter, null) &&
                            (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);
                        // don't notify the attacking character
                        scopedBy.Remove(character);

                        if (hitObject.WorldObject is ICharacter damagedCharacter)
                        {
                            // notify the damaged character
                            scopedBy.Add(damagedCharacter);
                        }

                        if (scopedBy.Count == 0)
                        {
                            continue;
                        }

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

            if (IsServer)
            {
                protoWeapon.ServerOnShot(character, weaponItem, protoWeapon, hitObjects);
            }
        }
Ejemplo n.º 2
0
        private static void SharedShotWeaponHitscan(
            ICharacter character,
            IProtoItemWeapon protoWeapon,
            Vector2D fromPosition,
            WeaponFinalCache weaponCache,
            Vector2D?customTargetPosition,
            IProtoCharacterCore characterProtoCharacter,
            double fireSpreadAngleOffsetDeg,
            CollisionGroup collisionGroup,
            bool isMeleeWeapon,
            IDynamicWorldObject characterCurrentVehicle,
            ProtoSkillWeapons protoWeaponSkill,
            PlayerCharacterSkills playerCharacterSkills,
            ITempList <IWorldObject> allHitObjects)
        {
            Vector2D toPosition;
            var      rangeMax = weaponCache.RangeMax;

            if (customTargetPosition.HasValue)
            {
                var direction = customTargetPosition.Value - fromPosition;
                // ensure the max range is not exceeded
                direction  = direction.ClampMagnitude(rangeMax);
                toPosition = fromPosition + direction;
            }
            else
            {
                toPosition = fromPosition
                             + new Vector2D(rangeMax, 0)
                             .RotateRad(characterProtoCharacter.SharedGetRotationAngleRad(character)
                                        + fireSpreadAngleOffsetDeg * Math.PI / 180.0);
            }

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

            if (IsClient ||
                Api.IsEditor)
            {
                SharedEditorPhysicsDebugger.SharedVisualizeTestResults(lineTestResults, collisionGroup);
            }

            var isDamageRayStopped = false;

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

                    var attackedTile = IsServer
                                           ? Server.World.GetTile((Vector2Ushort)testResultPhysicsBody.Position)
                                           : Client.World.GetTile((Vector2Ushort)testResultPhysicsBody.Position);

                    if (attackedTile.Height < characterTileHeight)
                    {
                        // attacked tile is below - ignore it
                        continue;
                    }

                    // tile on the way - blocking damage ray
                    isDamageRayStopped = true;
                    var hitData = new WeaponHitData(testResult.PhysicsBody.Position
                                                    + SharedOffsetHitWorldPositionCloserToTileHitboxCenter(
                                                        testResultPhysicsBody,
                                                        testResult.Penetration,
                                                        isRangedWeapon: !isMeleeWeapon));
                    hitObjects.Add(hitData);

                    weaponCache.ProtoWeapon
                    .SharedOnHit(weaponCache,
                                 null,
                                 0,
                                 hitData,
                                 out _);
                    break;
                }

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

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

                // don't allow damage is there is no direct line of sight on physical colliders layer between the two objects
                if (SharedHasTileObstacle(character.Position,
                                          characterTileHeight,
                                          damagedObject,
                                          targetPosition: testResult.PhysicsBody.Position
                                          + testResult.PhysicsBody.CenterOffset))
                {
                    continue;
                }

                using (CharacterDamageContext.Create(attackerCharacter: character,
                                                     damagedObject as ICharacter,
                                                     protoWeaponSkill))
                {
                    if (!damageableProto.SharedOnDamage(
                            weaponCache,
                            damagedObject,
                            damageMultiplier,
                            damagePostMultiplier: 1.0,
                            out var obstacleBlockDamageCoef,
                            out var damageApplied))
                    {
                        // not hit
                        continue;
                    }

                    var hitData = new WeaponHitData(damagedObject,
                                                    testResult.Penetration.ToVector2F());
                    weaponCache.ProtoWeapon
                    .SharedOnHit(weaponCache,
                                 damagedObject,
                                 damageApplied,
                                 hitData,
                                 out var isDamageStop);

                    if (isDamageStop)
                    {
                        obstacleBlockDamageCoef = 1;
                    }

                    if (IsServer)
                    {
                        if (damageApplied > 0 &&
                            damagedObject is ICharacter damagedCharacter)
                        {
                            CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(damagedCharacter);
                        }

                        if (damageApplied > 0)
                        {
                            // give experience for damage
                            protoWeaponSkill?.ServerOnDamageApplied(playerCharacterSkills,
                                                                    damagedObject,
                                                                    damageApplied);
                        }
                    }

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

                    hitObjects.Add(hitData);

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

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

            var shotEndPosition = GetShotEndPosition(isDamageRayStopped,
                                                     hitObjects,
                                                     toPosition,
                                                     isRangedWeapon: !isMeleeWeapon);

            if (hitObjects.Count == 0)
            {
                protoWeapon.SharedOnMiss(weaponCache,
                                         shotEndPosition);
            }

            SharedCallOnWeaponHitOrTrace(character,
                                         protoWeapon,
                                         weaponCache.ProtoAmmo,
                                         shotEndPosition,
                                         hitObjects,
                                         endsWithHit: isDamageRayStopped);

            foreach (var entry in hitObjects)
            {
                if (!entry.IsCliffsHit &&
                    !allHitObjects.Contains(entry.WorldObject))
                {
                    allHitObjects.Add(entry.WorldObject);
                }
            }
        }