示例#1
0
        protected override GatheringActionState SharedTryCreateState(WorldActionRequest request)
        {
            var worldObject = request.WorldObject;
            var character   = request.Character;

            var staticWorldObject = (IStaticWorldObject)worldObject;

            if (!(worldObject.ProtoGameObject is IProtoObjectGatherable protoGatherable))
            {
                throw new Exception("Not a gatherable resource: " + worldObject);
            }

            if (IsServer)
            {
                WorldObjectClaimSystem.ServerTryClaim(staticWorldObject,
                                                      character,
                                                      WorldObjectClaimDuration.RegularObjects);
            }

            var durationSeconds = protoGatherable.DurationGatheringSeconds;
            var multiplier      = protoGatherable.GetGatheringSpeedMultiplier(staticWorldObject, character);

            durationSeconds /= multiplier;

            if (CreativeModeSystem.SharedIsInCreativeMode(character))
            {
                durationSeconds = 0.1;
            }

            return(new GatheringActionState(
                       character,
                       staticWorldObject,
                       durationSeconds));
        }
        private static bool ServerTrySpawnNode(Vector2Ushort spawnPosition, ICharacter pveTagForCharacter)
        {
            var protoNode = ProtoNodeLazy.Value;

            if (!protoNode.CheckTileRequirements(spawnPosition,
                                                 character: null,
                                                 logErrors: false))
            {
                return(false);
            }

            var node = Server.World.CreateStaticWorldObject(protoNode, spawnPosition);

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

            // make this node to despawn automatically when there is no pragmium source nearby
            ObjectMineralPragmiumNode.ServerRestartDestroyTimer(
                ObjectMineralPragmiumNode.GetPrivateState(node));

            WorldObjectClaimSystem.ServerTryClaim(node,
                                                  pveTagForCharacter,
                                                  WorldObjectClaimDuration.PragmiumSourceCluster);

            return(true);
        }
        private void ServerCalculateDistanceSqrToTheClosestPragmiumSpires(
            ICharacter character,
            Vector2Ushort position,
            List <double> closestSqrDistances)
        {
            var isObjectClaimSystemEnabled = WorldObjectClaimSystem.SharedIsEnabled;

            using var tempList = Api.Shared.GetTempList <IStaticWorldObject>();
            LazyPragmiumSource.Value.GetAllGameObjects(tempList.AsList());

            foreach (var staticWorldObject in tempList.AsList())
            {
                var distanceSqr = position.TileSqrDistanceTo(staticWorldObject.TilePosition);
                if (distanceSqr >= this.MaxRange * this.MaxRange)
                {
                    // too far
                    continue;
                }

                if (isObjectClaimSystemEnabled &&
                    !WorldObjectClaimSystem.SharedIsAllowInteraction(character,
                                                                     staticWorldObject,
                                                                     showClientNotification: false))
                {
                    // this pragmium source is claimed for another player
                    continue;
                }

                closestSqrDistances.Add(distanceSqr);
            }

            closestSqrDistances.Sort();
        }
示例#4
0
        public static void OnCharacterKilled(
            ICharacter attackerCharacter,
            ICharacter targetCharacter,
            IProtoSkill protoSkill)
        {
            if (attackerCharacter is null ||
                targetCharacter is null)
            {
                return;
            }

            Api.Logger.Info("Killed " + targetCharacter, attackerCharacter);
            Api.SafeInvoke(
                () => CharacterKilled?.Invoke(attackerCharacter, targetCharacter));

            if (attackerCharacter.IsNpc ||
                !targetCharacter.IsNpc)
            {
                return;
            }

            var playerCharacterSkills = attackerCharacter.SharedGetSkills();

            // give hunting skill experience for mob kill
            var huntXP = SkillHunting.ExperienceForKill;

            huntXP *= ((IProtoCharacterMob)targetCharacter.ProtoGameObject).MobKillExperienceMultiplier;
            if (huntXP > 0)
            {
                playerCharacterSkills.ServerAddSkillExperience <SkillHunting>(huntXP);
            }

            if (protoSkill is ProtoSkillWeapons protoSkillWeapon)
            {
                // give weapon experience for kill
                protoSkillWeapon.ServerOnKill(playerCharacterSkills, killedCharacter: targetCharacter);
            }

            if (!WorldObjectClaimSystem.SharedIsEnabled)
            {
                return;
            }

            // try claim the corpse for the attacker player
            using var tempListCorpses = Api.Shared.GetTempList <IStaticWorldObject>();
            Api.GetProtoEntity <ObjectCorpse>()
            .GetAllGameObjects(tempListCorpses.AsList());
            foreach (var worldObjectCorpse in tempListCorpses.AsList())
            {
                if (targetCharacter.Id
                    == ObjectCorpse.GetPublicState(worldObjectCorpse).DeadCharacterId)
                {
                    WorldObjectClaimSystem.ServerTryClaim(worldObjectCorpse,
                                                          attackerCharacter,
                                                          WorldObjectClaimDuration.CreatureCorpse);
                    return;
                }
            }
        }
示例#5
0
 protected override void ServerTryClaimObject(IStaticWorldObject targetObject, ICharacter character)
 {
     if (!ObjectMineralPragmiumSource.ServerTryClaimPragmiumClusterNearCharacter(character))
     {
         WorldObjectClaimSystem.ServerTryClaim(targetObject,
                                               character,
                                               WorldObjectClaimDuration.PragmiumSourceCluster);
     }
 }
        public override void ServerOnDestroy(IItem gameObject)
        {
            base.ServerOnDestroy(gameObject);

            ICharacter character = gameObject.Container.OwnerAsCharacter;

            if (character is null)
            {
                return;
            }

            var playerPrivateState = PlayerCharacter.GetPrivateState(character);

            IItemsContainer objectGroundContainer = null;

            objectGroundContainer = ObjectPlayerLootContainer.ServerTryCreateLootContainer(character);

            if (objectGroundContainer is not null)
            {
                int slotCount = this.GetGroundSlotCount(playerPrivateState);

                // set slots count matching the total occupied slots count
                Server.Items.SetSlotsCount(objectGroundContainer,
                                           (byte)Math.Min(byte.MaxValue, objectGroundContainer.OccupiedSlotsCount + slotCount));
            }

            if (objectGroundContainer is null)
            {
                objectGroundContainer = ObjectGroundItemsContainer.ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, character.Tile);

                if (objectGroundContainer is null)
                {
                    return;
                }
            }

            for (byte i = PlayerConstants.InventorySlotsCount; i < playerPrivateState.ContainerInventory.SlotsCount; i++)
            {
                IItem itemToDrop = playerPrivateState.ContainerInventory.GetItemAtSlot(i);
                if (itemToDrop is not null)
                {
                    Server.Items.MoveOrSwapItem(itemToDrop, objectGroundContainer, out _);
                }
            }

            if (playerPrivateState.ContainerInventory.SlotsCount != PlayerConstants.InventorySlotsCount)
            {
                Api.Server.Items.SetSlotsCount(playerPrivateState.ContainerInventory, PlayerConstants.InventorySlotsCount);
            }

            WorldObjectClaimSystem.ServerTryClaim(objectGroundContainer.OwnerAsStaticObject,
                                                  character,
                                                  durationSeconds: objectGroundContainer.OwnerAsStaticObject.ProtoStaticWorldObject
                                                  is ObjectPlayerLootContainer
                               ? ObjectPlayerLootContainer.AutoDestroyTimeoutSeconds + (10 * 60)
                               : WorldObjectClaimDuration.DroppedGoods);
        }
示例#7
0
 private void SharedTryClaimObject()
 {
     if (Api.IsServer)
     {
         WorldObjectClaimSystem.ServerTryClaim(
             this.WorldObject,
             this.Character,
             durationSeconds: Math.Max(this.currentStageDurationSeconds + 5,
                                       WorldObjectClaimDuration.EventObjects));
     }
 }
示例#8
0
        public override void ServerOnDestroy(IItem gameObject)
        {
            base.ServerOnDestroy(gameObject);

            if (gameObject.Container is null)
            {
                return;
            }

            ICharacter character = gameObject.Container.OwnerAsCharacter;

            if (character is null)
            {
                return;
            }

            var playerPrivateState = PlayerCharacter.GetPrivateState(character);

            IItemsContainer objectGroundContainer = null;

            objectGroundContainer = ObjectPlayerLootContainer.ServerTryCreateLootContainer(character);

            var privateState = GetPrivateState(gameObject);

            if (objectGroundContainer is not null)
            {
                int slotCount = privateState.ItemsContainer.OccupiedSlotsCount;

                // set slots count matching the total occupied slots count
                Server.Items.SetSlotsCount(objectGroundContainer,
                                           (byte)Math.Min(byte.MaxValue, objectGroundContainer.OccupiedSlotsCount + slotCount));
            }

            if (objectGroundContainer is null)
            {
                objectGroundContainer = ObjectGroundItemsContainer.ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, character.Tile);

                if (objectGroundContainer is null)
                {
                    return;
                }
            }

            Api.Server.Items.TryMoveAllItems(privateState.ItemsContainer, objectGroundContainer);

            WorldObjectClaimSystem.ServerTryClaim(objectGroundContainer.OwnerAsStaticObject,
                                                  character,
                                                  durationSeconds: objectGroundContainer.OwnerAsStaticObject.ProtoStaticWorldObject
                                                  is ObjectPlayerLootContainer
                               ? ObjectPlayerLootContainer.AutoDestroyTimeoutSeconds + (10 * 60)
                               : WorldObjectClaimDuration.DroppedGoods);
        }
示例#9
0
        public void ServerDropDroneToGround(IDynamicWorldObject objectDrone)
        {
            var privateState     = objectDrone.GetPrivateState <DronePrivateState>();
            var storageContainer = privateState.StorageItemsContainer;

            if (storageContainer.OccupiedSlotsCount == 0)
            {
                return;
            }

            // drop all items on the ground
            IItemsContainer groundContainer = null;

            if (privateState.CharacterOwner is not null)
            {
                groundContainer = ObjectPlayerLootContainer.ServerTryCreateLootContainer(privateState.CharacterOwner,
                                                                                         objectDrone.Position);

                if (groundContainer is not null)
                {
                    // set slots count matching the total occupied slots count
                    Server.Items.SetSlotsCount(
                        groundContainer,
                        (byte)Math.Min(byte.MaxValue,
                                       groundContainer.OccupiedSlotsCount
                                       + storageContainer.OccupiedSlotsCount));
                }
            }

            groundContainer ??=
            ObjectGroundItemsContainer.ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(
                privateState.CharacterOwner,
                objectDrone.Tile);

            if (groundContainer is not null)
            {
                Server.Items.TryMoveAllItems(storageContainer, groundContainer);
                WorldObjectClaimSystem.ServerTryClaim(
                    groundContainer.OwnerAsStaticObject,
                    privateState.CharacterOwner,
                    durationSeconds: ObjectPlayerLootContainer.AutoDestroyTimeoutSeconds
                    + (10 * 60));
            }
            else
            {
                // TODO: try to create a ground container in any other ground spot
                Logger.Error("Cannot find a place to drop the drone contents on the ground - drone lost!"
                             + objectDrone);
            }
        }
示例#10
0
        public static CreateItemResult TryDropToCharacterOrGround(
            IReadOnlyDropItemsList dropItemsList,
            ICharacter toCharacter,
            Vector2Ushort tilePosition,
            bool sendNotificationWhenDropToGround,
            double probabilityMultiplier,
            DropItemContext context,
            out IItemsContainer groundContainer)
        {
            var containersProvider = new CharacterAndGroundContainersProvider(toCharacter, tilePosition);

            var result = dropItemsList.Execute(
                (protoItem, count) => Items.CreateItem(protoItem, containersProvider, count),
                context,
                probabilityMultiplier);

            groundContainer = containersProvider.GroundContainer;
            if (groundContainer is null)
            {
                return(result);
            }

            if (groundContainer.OccupiedSlotsCount == 0)
            {
                // nothing is spawned, the ground container should be destroyed
                World.DestroyObject(groundContainer.OwnerAsStaticObject);
                groundContainer = null;
            }
            else
            {
                if (sendNotificationWhenDropToGround && result.TotalCreatedCount > 0)
                {
                    // notify player that there were not enough space in inventory so the items were dropped to the ground
                    NotificationSystem.ServerSendNotificationNoSpaceInInventoryItemsDroppedToGround(
                        toCharacter,
                        protoItemForIcon: result.ItemAmounts
                        .Keys
                        .FirstOrDefault(
                            k => k.Container == containersProvider.GroundContainer)?
                        .ProtoItem);
                }

                WorldObjectClaimSystem.ServerTryClaim(groundContainer.OwnerAsStaticObject,
                                                      toCharacter,
                                                      WorldObjectClaimDuration.GroundItems);
            }

            return(result);
        }
        /// <summary>
        /// Returns a ground container for the provided tile.<br />
        /// Please note: the ground container will be automatically destroyed if (during the next update) it's empty!
        /// </summary>
        public static IItemsContainer ServerTryGetOrCreateGroundContainerAtTile(
            ICharacter forCharacter,
            Vector2Ushort tilePosition,
            bool writeWarningsToLog = true)
        {
            var tile = Server.World.GetTile(tilePosition);
            var objectGroundContainer = tile.StaticObjects.FirstOrDefault(
                so => so.ProtoGameObject is ObjectGroundItemsContainer);

            if (objectGroundContainer is null)
            {
                // No ground container found in the cell. Try to create a new ground container here.
                objectGroundContainer = TryCreateGroundContainer();
                if (objectGroundContainer is null)
                {
                    // cannot create
                    return(null);
                }
            }
            else if (!WorldObjectClaimSystem.SharedIsAllowInteraction(forCharacter,
                                                                      objectGroundContainer,
                                                                      showClientNotification: false))
            {
                // cannot interact with this ground container as it's claimed by another player
                return(null);
            }

            var result = GetPublicState(objectGroundContainer).ItemsContainer;

            ServerExpandContainerAndScheduleProcessing(result);
            return(result);

            IStaticWorldObject TryCreateGroundContainer()
            {
                if (instance.CheckTileRequirements(tilePosition, character: null, logErrors: false))
                {
                    Logger.Info("Creating ground container at " + tilePosition);
                    return(Server.World.CreateStaticWorldObject(instance, tilePosition));
                }

                if (writeWarningsToLog)
                {
                    Logger.Warning(
                        $"Cannot create ground container at {tilePosition} - tile contains something preventing it.");
                }

                return(null);
            }
        }
        public static bool ServerTryClaimPragmiumClusterNearCharacter(ICharacter character)
        {
            if (!WorldObjectClaimSystem.SharedIsEnabled ||
                character is null ||
                character.IsNpc)
            {
                return(true);
            }

            using var objectsInScope = Api.Shared.GetTempList <IWorldObject>();
            Server.World.GetWorldObjectsInView(character, objectsInScope.AsList(), sortByDistance: false);

            var hasSource = false;

            foreach (var worldObject in objectsInScope.AsList())
            {
                if (!(worldObject.ProtoGameObject is ObjectMineralPragmiumSource))
                {
                    continue;
                }

                hasSource = true;
                WorldObjectClaimSystem.ServerTryClaim(worldObject,
                                                      character,
                                                      WorldObjectClaimDuration.PragmiumSourceCluster);
                break;
            }

            if (!hasSource)
            {
                return(false);
            }

            foreach (var worldObject in objectsInScope.AsList())
            {
                if (!(worldObject.ProtoGameObject is ObjectMineralPragmiumNode))
                {
                    continue;
                }

                WorldObjectClaimSystem.ServerTryClaim(worldObject,
                                                      character,
                                                      WorldObjectClaimDuration.PragmiumSourceCluster);
            }

            return(true);
        }
        public override bool SharedCanInteract(ICharacter character, IStaticWorldObject worldObject, bool writeToLog)
        {
            // don't use the base implementation as it will not work in PvE
            // (action forbidden if player doesn't have access to the land claim)
            if (character.GetPublicState <ICharacterPublicState>().IsDead ||
                IsServer && !character.ServerIsOnline)
            {
                return(false);
            }

            if (!NewbieProtectionSystem.SharedValidateInteractionIsNotForbidden(character, worldObject, writeToLog) ||
                !WorldObjectClaimSystem.SharedIsAllowInteraction(character, worldObject, writeToLog))
            {
                return(false);
            }

            return(this.SharedIsInsideCharacterInteractionArea(character, worldObject, writeToLog));
        }
示例#14
0
 private bool IsValidObject(IStaticWorldObject staticWorldObject)
 {
     return((EnabledMineralList.Contains(staticWorldObject.ProtoStaticWorldObject) ||
             EnabledTreeList.Contains(staticWorldObject.ProtoStaticWorldObject)) &&
            CharacterDroneControlSystem.SharedIsValidStartLocation(
                CurrentCharacter,
                staticWorldObject.TilePosition,
                out bool hasObstacle) &&
            WorldObjectClaimSystem.SharedIsAllowInteraction(
                CurrentCharacter,
                staticWorldObject,
                showClientNotification: false) &&
            !(staticWorldObject.ProtoStaticWorldObject is IProtoObjectTree tree &&
              tree.SharedGetGrowthProgress(staticWorldObject) < AllowedTreeGrowthFractionLevel) &&
            !CharacterDroneControlSystem.SharedIsTargetAlreadyScheduledForAnyActiveDrone(
                CurrentCharacter,
                staticWorldObject.TilePosition,
                logError: false));
 }
示例#15
0
        public static CreateItemResult TryDropToGround(
            IReadOnlyDropItemsList dropItemsList,
            Vector2Ushort tilePosition,
            double probabilityMultiplier,
            DropItemContext context,
            [CanBeNull] out IItemsContainer groundContainer)
        {
            var character = context.HasCharacter ? context.Character : null;

            // obtain the ground container to drop the items into
            var tile = World.GetTile(tilePosition);

            groundContainer = ObjectGroundItemsContainer
                              .ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, tile);

            if (groundContainer is null)
            {
                // cannot drop because there are no free space available on the ground
                return(new CreateItemResult()
                {
                    IsEverythingCreated = false
                });
            }

            var result = dropItemsList.TryDropToContainer(groundContainer, context, probabilityMultiplier);

            if (groundContainer.OccupiedSlotsCount == 0)
            {
                // nothing is spawned, the ground container should be destroyed
                World.DestroyObject(groundContainer.OwnerAsStaticObject);
                groundContainer = null;
                return(result);
            }

            WorldObjectClaimSystem.ServerTryClaim(groundContainer.OwnerAsStaticObject,
                                                  character,
                                                  WorldObjectClaimDuration.GroundItems);

            return(result);
        }
示例#16
0
        protected override void SharedValidateRequest(WorldActionRequest request)
        {
            var worldObject = (IStaticWorldObject)request.WorldObject;

            if (!(worldObject?.ProtoWorldObject is IProtoObjectGatherable protoGatherable))
            {
                throw new Exception("The world object must be gatherable");
            }

            if (!worldObject.ProtoWorldObject.SharedCanInteract(request.Character, worldObject, true))
            {
                throw new Exception("Cannot interact with " + worldObject);
            }

            if (!WorldObjectClaimSystem.SharedIsAllowInteraction(request.Character,
                                                                 worldObject,
                                                                 showClientNotification: true))
            {
                throw new Exception("Locked for another player: " + worldObject);
            }

            if (protoGatherable.SharedIsCanGather(worldObject))
            {
                return;
            }

            Logger.Warning("Cannot gather now: " + worldObject, request.Character);
            if (Api.IsClient)
            {
                CannotInteractMessageDisplay.ShowOn(worldObject, NotificationNothingToHarvestYet);
                worldObject.ProtoWorldObject.SharedGetObjectSoundPreset()
                .PlaySound(ObjectSound.InteractFail);
            }

            throw new Exception("Cannot gather now: " + worldObject);
        }
示例#17
0
 protected virtual void ServerTryClaimObject(IStaticWorldObject targetObject, ICharacter character)
 {
     WorldObjectClaimSystem.ServerTryClaim(targetObject,
                                           character,
                                           WorldObjectClaimDuration.RegularObjects);
 }
        private static void ServerTrySpawnNodesAround(IStaticWorldObject worldObject)
        {
            // calculate how many nodes are nearby
            var nodesAroundCount = 0;
            var neighborTiles    = worldObject.OccupiedTiles
                                   .SelectMany(t => t.EightNeighborTiles)
                                   .Distinct()
                                   .ToList();

            foreach (var neighborTile in neighborTiles)
            {
                foreach (var otherObject in neighborTile.StaticObjects)
                {
                    if (!(otherObject.ProtoStaticWorldObject is ObjectMineralPragmiumNode))
                    {
                        continue;
                    }

                    nodesAroundCount++;
                    if (nodesAroundCount >= NodesCountLimit)
                    {
                        // there are already enough nodes around
                        return;
                    }
                }
            }

            // spawn node(s) nearby
            var countToSpawn = NodesCountLimit - nodesAroundCount;

            if (countToSpawn <= 0)
            {
                return;
            }

            var tag = GetPublicState(worldObject).WorldObjectClaim;
            var pveTagForCharacter = WorldObjectClaimSystem.ServerGetCharacterByClaim(tag);

            var attempts = neighborTiles.Count * 4;

            countToSpawn = Math.Min(countToSpawn, ServerSpawnNodesMaxCountPerIteration);

            while (attempts-- > 0)
            {
                var neighborTile = neighborTiles.TakeByRandom();
                if (neighborTile.StaticObjects.Count > 0)
                {
                    // cannot spawn there
                    continue;
                }

                if (!ServerTrySpawnNode(neighborTile.Position, pveTagForCharacter))
                {
                    // cannot spawn there
                    continue;
                }

                countToSpawn--;
                if (countToSpawn == 0)
                {
                    return;
                }
            }
        }
示例#19
0
        public static void ServerCreateBossLoot(
            Vector2Ushort bossPosition,
            IProtoCharacterMob protoCharacterBoss,
            ServerBossDamageTracker damageTracker,
            double bossDifficultyCoef,
            IProtoStaticWorldObject lootObjectProto,
            int lootObjectsDefaultCount,
            double lootObjectsRadius,
            double learningPointsBonusPerLootObject,
            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: FactionSystem.SharedGetClanTag(w.Character)))
                                          .ToArray();

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

            foreach (var entry in winnerEntries)
            {
                // provide bonus LP
                entry.Character.SharedGetTechnologies()
                .ServerAddLearningPoints(
                    learningPointsBonusPerLootObject * entry.LootCount,
                    allowModifyingByStatsAndRates: false);
            }

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

            Api.SafeInvoke(
                () => BossDefeated?.Invoke(protoCharacterBoss,
                                           bossPosition,
                                           winnerEntries));

            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)(bossPosition.X + distance * Math.Cos(angle)),
                        (ushort)(bossPosition.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);
            }
        }
        protected override bool ClientItemUseFinish(ClientItemData data)
        {
            var character             = ClientCurrentCharacterHelper.Character;
            var characterTilePosition = character.TilePosition;
            var mouseTilePosition     = Client.Input.MousePointedTilePosition;
            var dronesNumberToLaunch  = Api.Client.Input.IsKeyHeld(InputKey.Shift, evenIfHandled: true)
                                           ? this.MaxDronesToControl
                                           : 1;

            using var tempExceptDrones  = Api.Shared.GetTempList <IItem>();
            using var tempExceptTargets = Api.Shared.GetTempList <Vector2Ushort>();

            for (var index = 0; index < dronesNumberToLaunch; index++)
            {
                var showErrorNotification = index == 0;
                var itemDrone             = CharacterDroneControlSystem.ClientSelectNextDrone(tempExceptDrones.AsList());
                if (itemDrone is null)
                {
                    if (CharacterDroneControlSystem.SharedIsMaxDronesToControlNumberExceeded(
                            character,
                            clientShowErrorNotification: showErrorNotification))
                    {
                        break;
                    }

                    if (showErrorNotification)
                    {
                        CannotInteractMessageDisplay.ClientOnCannotInteract(
                            character,
                            CharacterDroneControlSystem.Notification_ErrorNoDrones_Title,
                            isOutOfRange: false);
                    }

                    break;
                }

                tempExceptDrones.Add(itemDrone);
                Vector2Ushort targetPosition;

                if (index == 0)
                {
                    targetPosition = mouseTilePosition;
                    var targetObject = CharacterDroneControlSystem
                                       .SharedGetCompatibleTarget(character,
                                                                  mouseTilePosition,
                                                                  out var hasIncompatibleTarget,
                                                                  out var isPveActionForbidden);
                    if (targetObject is null)
                    {
                        if (showErrorNotification)
                        {
                            if (isPveActionForbidden)
                            {
                                PveSystem.ClientShowNotificationActionForbidden();
                            }

                            CannotInteractMessageDisplay.ClientOnCannotInteract(
                                character,
                                hasIncompatibleTarget
                                    ? CharacterDroneControlSystem.Notification_CannotMineThat
                                    : CharacterDroneControlSystem.Notification_NothingToMineThere,
                                isOutOfRange: false);
                        }

                        return(false);
                    }

                    if (!WorldObjectClaimSystem.SharedIsAllowInteraction(character,
                                                                         targetObject,
                                                                         showClientNotification: showErrorNotification))
                    {
                        return(false);
                    }

                    if (CharacterDroneControlSystem.SharedIsTargetAlreadyScheduledForAnyActiveDrone(
                            character,
                            mouseTilePosition,
                            logError: false))
                    {
                        // already scheduled a drone mining there...try find another target of the same type
                        targetPosition = TryGetNextTargetPosition();
                        if (targetPosition == default)
                        {
                            // no further targets
                            CannotInteractMessageDisplay.ClientOnCannotInteract(
                                character,
                                CharacterDroneControlSystem.Notification_DroneAlreadySent,
                                isOutOfRange: false);
                            return(false);
                        }
                    }
                }
                else
                {
                    targetPosition = TryGetNextTargetPosition();
                    if (targetPosition == default)
                    {
                        // no further targets
                        break;
                    }
                }

                if (!CharacterDroneControlSystem.ClientTryStartDrone(itemDrone,
                                                                     targetPosition,
                                                                     showErrorNotification: showErrorNotification))
                {
                    break;
                }

                tempExceptTargets.Add(targetPosition);
            }

            // always return false as we don't want to play any device sounds
            return(false);

            Vector2Ushort TryGetNextTargetPosition()
            {
                var targetObjectProto = CharacterDroneControlSystem
                                        .SharedGetCompatibleTarget(character,
                                                                   mouseTilePosition,
                                                                   out _,
                                                                   out _)?
                                        .ProtoWorldObject;

                if (targetObjectProto is null)
                {
                    return(default);
示例#21
0
        private static void ServerOnDroneReturnedToPlayer(IDynamicWorldObject worldObject)
        {
            var privateState = GetPrivateState(worldObject);
            var character    = privateState.CharacterOwner;

            CharacterDroneControlSystem.ServerDespawnDrone(worldObject, isReturnedToPlayer: true);

            var storageContainer = privateState.StorageItemsContainer;

            if (storageContainer.OccupiedSlotsCount == 0)
            {
                return;
            }

            // drop storage container contents to player
            // but first, move the drone item to its original slot (if possible)
            var characterContainerInventory = character.SharedGetPlayerContainerInventory();
            var characterContainerHotbar    = character.SharedGetPlayerContainerHotbar();

            var itemInFirstSlot = storageContainer.GetItemAtSlot(0);

            if (itemInFirstSlot is not null)
            {
                // item in the first slot is the drone's associated item
                // it could be destroyed in case the drone's HP dropped <= 1
                Server.Items.MoveOrSwapItem(itemInFirstSlot,
                                            privateState.IsStartedFromHotbarContainer
                                                ? characterContainerHotbar
                                                : characterContainerInventory,
                                            slotId: privateState.StartedFromSlotIndex,
                                            movedCount: out _);
            }

            var result = Server.Items.TryMoveAllItems(storageContainer, characterContainerInventory);

            try
            {
                if (storageContainer.OccupiedSlotsCount == 0)
                {
                    // all items moved from drone to player
                    return;
                }

                // try move remaining items to hotbar
                var resultToHotbar = Server.Items.TryMoveAllItems(storageContainer, characterContainerHotbar);
                result.MergeWith(resultToHotbar,
                                 areAllItemsMoved: resultToHotbar.AreAllItemMoved);

                if (storageContainer.OccupiedSlotsCount == 0)
                {
                    // all items moved from drone to player
                    return;
                }
            }
            finally
            {
                if (result.MovedItems.Count > 0)
                {
                    // notify player about the received items
                    NotificationSystem.ServerSendItemsNotification(
                        character,
                        result.MovedItems
                        .GroupBy(p => p.Key.ProtoItem)
                        .Where(p => !(p.Key is TItemDrone))
                        .ToDictionary(p => p.Key, p => p.Sum(v => v.Value)));
                }
            }

            // try to drop the remaining items on the ground
            var groundContainer = ObjectGroundItemsContainer
                                  .ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, character.Tile);

            if (groundContainer is not null)
            {
                var result2 = Server.Items.TryMoveAllItems(storageContainer, groundContainer);
                if (result2.MovedItems.Count > 0)
                {
                    var protoItemForIcon = result2.MovedItems.First().Key.ProtoItem;

                    NotificationSystem.ServerSendNotificationNoSpaceInInventoryItemsDroppedToGround(
                        character,
                        protoItemForIcon);

                    // ensure that these items could be lifted only by their owner in PvE
                    WorldObjectClaimSystem.ServerTryClaim(groundContainer.OwnerAsStaticObject,
                                                          character,
                                                          WorldObjectClaimDuration.DroppedGoods);
                }
            }

            if (storageContainer.OccupiedSlotsCount == 0)
            {
                return;
            }

            Logger.Error("Not all items dropped on the ground from the drone storage: "
                         + worldObject
                         + " slots occupied: "
                         + storageContainer.OccupiedSlotsCount);
        }
示例#22
0
        protected override void ServerUpdate(ServerUpdateData data)
        {
            base.ServerUpdate(data);

            var objectDrone  = data.GameObject;
            var privateState = data.PrivateState;
            var publicState  = data.PublicState;

            if (privateState.AssociatedItem is null)
            {
                // incorrectly configured drone
                Server.World.DestroyObject(objectDrone);
                return;
            }

            if (privateState.IsDespawned)
            {
                this.ServerSetUpdateRate(objectDrone, isRare: true);
                return;
            }

            UpdateWeaponCooldown();

            Vector2D destinationCoordinate;
            var      characterOwner = privateState.CharacterOwner;

            var hasMiningTargets = publicState.TargetObjectPosition.HasValue;

            if (hasMiningTargets &&
                !(CharacterDroneControlSystem.SharedIsValidDroneOperationDistance(objectDrone.TilePosition,
                                                                                  characterOwner.TilePosition) &&
                  objectDrone.Tile.Height == characterOwner.Tile.Height))
            {
                Logger.Info("The drone is beyond operation distance or has different tile height and will be recalled: "
                            + objectDrone);
                CharacterDroneControlSystem.ServerRecallDrone(objectDrone);
                hasMiningTargets = false;
            }

            if (hasMiningTargets)
            {
                // go to the next waypoint
                destinationCoordinate = publicState.TargetDronePosition
                                        ?? publicState.TargetObjectPosition.Value.ToVector2D();

                if (!CharacterDroneControlSystem.ServerIsMiningAllowed(
                        publicState.TargetObjectPosition.Value,
                        objectDrone))
                {
                    // cannot mine as it's already mined by another drone
                    publicState.ResetTargetPosition();
                    return;
                }
            }
            else
            {
                // should return to the player to despawn
                if (characterOwner is null ||
                    characterOwner.GetPublicState <ICharacterPublicState>().IsDead ||
                    CharacterDroneControlSystem.SharedIsBeyondDroneAbandonDistance(
                        objectDrone.TilePosition,
                        characterOwner.TilePosition))
                {
                    CharacterDroneControlSystem.ServerDeactivateDrone(objectDrone);
                    return;
                }

                destinationCoordinate = characterOwner.Position;
            }

            RefreshMovement(isToMineral: hasMiningTargets,
                            destinationCoordinate,
                            out var isDestinationReached);
            if (!isDestinationReached)
            {
                return;
            }

            if (hasMiningTargets)
            {
                PerformMining();
            }
            else
            {
                // we were going to the player and reached its location, despawn
                ServerOnDroneReturnedToPlayer(objectDrone);
            }

            void RefreshMovement(
                bool isToMineral,
                Vector2D toPosition,
                out bool isDestinationReached)
            {
                var positionDelta       = toPosition - objectDrone.Position;
                var positionDeltaLength = positionDelta.Length;

                double targetVelocity;

                if (positionDeltaLength
                    > (isToMineral
                           ? DistanceThresholdToMineral
                           : DistanceThresholdToPlayer))
                {
                    // fly towards that object
                    var moveSpeed = this.StatMoveSpeed;

                    targetVelocity       = moveSpeed;
                    isDestinationReached = false;

                    if (isToMineral)
                    {
                        // reduce speed when too close to the mineral
                        var distanceCoef = positionDeltaLength / (0.333 * targetVelocity);
                        if (distanceCoef < 1)
                        {
                            targetVelocity *= Math.Pow(distanceCoef, 0.5);
                            // ensure it cannot drop lower than 5% of the original move speed
                            targetVelocity = Math.Max(0.05 * moveSpeed, targetVelocity);
                        }
                    }
                }
                else
                {
                    isDestinationReached = true;

                    // stop
                    if (Server.World.GetDynamicObjectMoveSpeed(objectDrone) == 0)
                    {
                        // already stopped
                        return;
                    }

                    targetVelocity = 0;
                }

                var movementDirectionNormalized = positionDelta.Normalized;
                var moveAcceleration            = movementDirectionNormalized * this.PhysicsBodyAccelerationCoef * targetVelocity;

                Server.World.SetDynamicObjectMoveSpeed(objectDrone, targetVelocity);

                Server.World.SetDynamicObjectPhysicsMovement(objectDrone,
                                                             moveAcceleration,
                                                             targetVelocity: targetVelocity);
                objectDrone.PhysicsBody.Friction = this.PhysicsBodyFriction;
            }

            void PerformMining()
            {
                var targetObject = CharacterDroneControlSystem.SharedGetCompatibleTarget(
                    characterOwner,
                    publicState.TargetObjectPosition.Value,
                    out _,
                    out _);

                if (targetObject is null ||
                    !WorldObjectClaimSystem.SharedIsAllowInteraction(characterOwner,
                                                                     targetObject,
                                                                     showClientNotification: false))
                {
                    // nothing to mine there, or finished mining, or cannot mine
                    CharacterDroneControlSystem.ServerUnregisterCurrentMining(
                        publicState.TargetObjectPosition.Value,
                        objectDrone);

                    publicState.ResetTargetPosition();
                    return;
                }

                if (privateState.WeaponCooldownSecondsRemains > 0)
                {
                    return;
                }

                if (!CharacterDroneControlSystem.ServerTryRegisterCurrentMining(
                        publicState.TargetObjectPosition.Value,
                        objectDrone))
                {
                    // cannot mine as it's already mined by another drone
                    publicState.ResetTargetPosition();
                    return;
                }

                publicState.IsMining = true;

                var protoMiningTool = this.ProtoItemMiningTool;

                privateState.WeaponCooldownSecondsRemains +=
                    Api.Shared.RoundDurationByServerFrameDuration(protoMiningTool.FireInterval);

                var characterFinalStatsCache = characterOwner.SharedGetFinalStatsCache();

                var weaponFinalCache = privateState.WeaponFinalCache;

                if (weaponFinalCache is null ||
                    !privateState.LastCharacterOwnerFinalStatsCache.CustomEquals(characterFinalStatsCache))
                {
                    weaponFinalCache = ServerCreateWeaponFinalCacheForDrone(characterOwner,
                                                                            protoMiningTool,
                                                                            objectDrone);

                    privateState.WeaponFinalCache = weaponFinalCache;
                    privateState.LastCharacterOwnerFinalStatsCache = characterFinalStatsCache;
                }

                var protoWorldObject = (IDamageableProtoWorldObject)targetObject.ProtoGameObject;

                protoWorldObject.SharedOnDamage(
                    weaponFinalCache,
                    targetObject,
                    damagePreMultiplier: 1,
                    damagePostMultiplier: 1,
                    obstacleBlockDamageCoef: out _,
                    damageApplied: out var damageApplied);

                // reduce drone durability on 1 unit (reflected as HP when it's a world object)
                // but ensure the new HP cannot drop to exact 0 (to prevent destruction while mining)
                var newHP = publicState.StructurePointsCurrent
                            - 1 * LazyProtoItemDrone.Value.DurabilityToStructurePointsConversionCoefficient;

                publicState.StructurePointsCurrent = Math.Max(float.Epsilon, (float)newHP);

                if (damageApplied <= 0)
                {
                    // cannot mine there for whatever reason, recall the drone
                    publicState.ResetTargetPosition();
                }

                this.ServerSendMiningSoundCue(objectDrone, characterOwner);
            }

            void UpdateWeaponCooldown()
            {
                if (privateState.WeaponCooldownSecondsRemains <= 0)
                {
                    return;
                }

                // process weapon cooldown
                var deltaTime = data.DeltaTime;

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

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