Beispiel #1
0
        public static void ServerCreateBossLoot(
            Vector2Ushort epicenterPosition,
            IProtoCharacterMob protoCharacterBoss,
            ServerBossDamageTracker damageTracker,
            double bossDifficultyCoef,
            IProtoStaticWorldObject lootObjectProto,
            int lootObjectsDefaultCount,
            double lootObjectsRadius,
            int maxLootWinners)
        {
            var approximatedTotalLootCountToSpawn = (int)Math.Ceiling(lootObjectsDefaultCount * bossDifficultyCoef);

            if (approximatedTotalLootCountToSpawn < 2)
            {
                approximatedTotalLootCountToSpawn = 2;
            }

            var allCharactersByDamage = damageTracker.GetDamageByCharacter();
            var winners = ServerSelectWinnersByParticipation(
                ServerSelectWinnersByDamage(allCharactersByDamage,
                                            maxLootWinners));

            var winnerEntries = winners
                                .Select(
                c => new WinnerEntry(
                    c.Character,
                    damagePercent: (byte)Math.Max(
                        1,
                        Math.Round(c.Score * 100, MidpointRounding.AwayFromZero)),
                    lootCount: CalculateLootCountForScore(c.Score)))
                                .ToList();

            if (PveSystem.ServerIsPvE)
            {
                ServerNotifyCharactersNotEligibleForReward(
                    protoCharacterBoss,
                    allCharactersByDamage.Select(c => c.Character)
                    .Except(winners.Select(c => c.Character))
                    .ToList());

                ServerNotifyCharactersEligibleForReward(
                    protoCharacterBoss,
                    winnerEntries,
                    totalLootCount: (ushort)winnerEntries.Sum(e => (int)e.LootCount));
            }

            ServerSpawnLoot();

            // send victory announcement notification
            var winnerNamesWithClanTags = winnerEntries
                                          .Select(w => (Name: w.Character.Name,
                                                        ClanTag: PlayerCharacter.GetPublicState(w.Character).ClanTag))
                                          .ToArray();

            Instance.CallClient(
                ServerCharacters.EnumerateAllPlayerCharacters(onlyOnline: true),
                _ => _.ClientRemote_VictoryAnnouncement(protoCharacterBoss,
                                                        winnerNamesWithClanTags));

            Api.Logger.Important(
                protoCharacterBoss.ShortId
                + " boss defeated."
                + Environment.NewLine
                + "Damage by player:"
                + Environment.NewLine
                + allCharactersByDamage.Select(p => $" * {p.Character}: {p.Damage:F0}")
                .GetJoinedString(Environment.NewLine)
                + Environment.NewLine
                + "Player participation score: (only for selected winners)"
                + Environment.NewLine
                + winners.Select(p => $" * {p.Character}: {(p.Score * 100):F1}%")
                .GetJoinedString(Environment.NewLine));

            byte CalculateLootCountForScore(double score)
            {
                var result = Math.Ceiling(approximatedTotalLootCountToSpawn * score);

                if (result < 1)
                {
                    return(1);
                }

                return((byte)Math.Min(byte.MaxValue, result));
            }

            void ServerSpawnLoot()
            {
                foreach (var winnerEntry in winnerEntries)
                {
                    if (!ServerTrySpawnLootForWinner(winnerEntry.Character, winnerEntry.LootCount))
                    {
                        // spawn attempts failure as logged inside the method,
                        // abort further spawning
                        return;
                    }
                }
            }

            bool ServerTrySpawnLootForWinner(ICharacter forCharacter, double countToSpawnRemains)
            {
                var attemptsRemains = 2000;

                while (countToSpawnRemains > 0)
                {
                    attemptsRemains--;
                    if (attemptsRemains <= 0)
                    {
                        // attempts exceeded
                        Api.Logger.Error(
                            "Cannot spawn all the loot for boss - number of attempts exceeded. Cont to spawn for winner remains: "
                            + countToSpawnRemains);
                        return(false);
                    }

                    // calculate random distance from the explosion epicenter
                    var distance = RandomHelper.Range(2, lootObjectsRadius);

                    // ensure we spawn more objects closer to the epicenter
                    var spawnProbability = 1 - (distance / lootObjectsRadius);
                    spawnProbability = Math.Pow(spawnProbability, 1.25);
                    if (!RandomHelper.RollWithProbability(spawnProbability))
                    {
                        // random skip
                        continue;
                    }

                    var angle         = RandomHelper.NextDouble() * MathConstants.DoublePI;
                    var spawnPosition = new Vector2Ushort(
                        (ushort)(epicenterPosition.X + distance * Math.Cos(angle)),
                        (ushort)(epicenterPosition.Y + distance * Math.Sin(angle)));

                    if (ServerTrySpawnLootObject(spawnPosition, forCharacter))
                    {
                        // spawned successfully!
                        countToSpawnRemains--;
                    }
                }

                return(true);
            }

            bool ServerTrySpawnLootObject(Vector2Ushort spawnPosition, ICharacter forCharacter)
            {
                if (!lootObjectProto.CheckTileRequirements(spawnPosition,
                                                           character: null,
                                                           logErrors: false))
                {
                    return(false);
                }

                var lootObject = ServerWorld.CreateStaticWorldObject(lootObjectProto, spawnPosition);

                if (lootObject is null)
                {
                    return(false);
                }

                // mark the loot object for this player (works only in PvE)
                WorldObjectClaimSystem.ServerTryClaim(lootObject,
                                                      forCharacter,
                                                      WorldObjectClaimDuration.BossLoot,
                                                      claimForPartyMembers: false);
                return(true);
            }
        }
Beispiel #2
0
        public static void ServerSpawnMinions(
            ICharacter characterBoss,
            Vector2D characterBossCenterPosition,
            IProtoCharacterMob protoMinion,
            List <ICharacter> minionsList,
            double spawnCheckDistanceSqr,
            ServerBossDamageTracker bossDamageTracker,
            double minionsPerPlayer,
            int minionsTotalMin,
            int minionsTotalMax,
            int?minionsSpawnPerIterationLimit,
            double baseMinionsNumber,
            double spawnNoObstaclesCircleRadius,
            double spawnDistanceMin,
            double spawnDistanceMax)
        {
            // calculate how many minions required
            var minionsRequired = baseMinionsNumber;

            using var tempListCharacters = Api.Shared.GetTempList <ICharacter>();
            Api.Server.World.GetScopedByPlayers(characterBoss, tempListCharacters);

            foreach (var player in tempListCharacters.AsList())
            {
                if (player.Position.DistanceSquaredTo(characterBossCenterPosition)
                    <= spawnCheckDistanceSqr)
                {
                    minionsRequired += minionsPerPlayer;
                }
            }

            minionsRequired = MathHelper.Clamp(minionsRequired,
                                               minionsTotalMin,
                                               minionsTotalMax);

            if (minionsSpawnPerIterationLimit.HasValue)
            {
                minionsRequired = Math.Min(minionsRequired, minionsSpawnPerIterationLimit.Value);
            }

            ServerProcessMinions(characterBoss,
                                 protoMinion,
                                 minionsList,
                                 out var spawnedMinionsCount,
                                 despawnDistanceSqr: spawnCheckDistanceSqr);

            //Logger.Dev($"Minions required: {minionsRequired} minions have: {minionsHave}");
            minionsRequired -= spawnedMinionsCount;
            if (minionsRequired <= 0)
            {
                return;
            }

            // spawn minions
            var attemptsRemains = 300;
            var physicsSpace    = characterBoss.PhysicsBody.PhysicsSpace;

            while (minionsRequired > 0)
            {
                attemptsRemains--;
                if (attemptsRemains <= 0)
                {
                    // attempts exceeded
                    return;
                }

                var spawnDistance = spawnDistanceMin
                                    + RandomHelper.NextDouble() * (spawnDistanceMax - spawnDistanceMin);
                var angle         = RandomHelper.NextDouble() * MathConstants.DoublePI;
                var spawnPosition = new Vector2Ushort(
                    (ushort)(characterBossCenterPosition.X + spawnDistance * Math.Cos(angle)),
                    (ushort)(characterBossCenterPosition.Y + spawnDistance * Math.Sin(angle)));

                if (ServerTrySpawnMinion(spawnPosition) is { } spawnedMinion)
                {
                    // spawned successfully!
                    minionsRequired--;
                    minionsList.Add(spawnedMinion);
                }
            }

            ICharacter ServerTrySpawnMinion(Vector2Ushort spawnPosition)
            {
                var worldPosition = spawnPosition.ToVector2D();

                if (physicsSpace.TestCircle(worldPosition,
                                            spawnNoObstaclesCircleRadius,
                                            CollisionGroups.Default,
                                            sendDebugEvent: true).EnumerateAndDispose().Any())
                {
                    // obstacles nearby
                    return(null);
                }

                var spawnedCharacter = Api.Server.Characters.SpawnCharacter(protoMinion, worldPosition);

                if (spawnedCharacter is null)
                {
                    return(null);
                }

                // write this boss' damage tracker into the minion character
                // so any damage dealt to it will be counted in the winners ranking
                var privateState = spawnedCharacter.GetPrivateState <ICharacterPrivateStateWithBossDamageTracker>();

                privateState.DamageTracker = bossDamageTracker;

                // start spawn animation
                if (spawnedCharacter.ProtoGameObject is IProtoCharacterMob protoCharacterMob)
                {
                    protoCharacterMob.ServerSetSpawnState(spawnedCharacter,
                                                          MobSpawnState.Spawning);
                }

                return(spawnedCharacter);
            }
        }