Exemple #1
0
        private IStaticWorldObject ServerCreateConstructionSite(
            Vector2Ushort tilePosition,
            IProtoObjectStructure protoStructure,
            ICharacter byCharacter)
        {
            if (protoStructure == null)
            {
                throw new ArgumentNullException(nameof(protoStructure));
            }

            var protoConstructionSite = protoStructure.ConstructionSitePrototype;
            var constructionSite      = Server.World.CreateStaticWorldObject(protoConstructionSite, tilePosition);

            var serverState = ProtoObjectConstructionSite.GetPublicState(constructionSite);

            serverState.Setup(protoStructure);

            // reinitialize to build proper physics and occupy proper layout
            constructionSite.ServerRebuildScopeAndPhysics();

            Logger.Important("Construction site created: " + constructionSite);
            protoConstructionSite.ServerOnBuilt(constructionSite, byCharacter);
            Api.SafeInvoke(() => ServerStructureBuilt?.Invoke(byCharacter, constructionSite));
            Api.SafeInvoke(() => SharedWallConstructionRefreshHelper.SharedRefreshNeighborObjects(
                               constructionSite,
                               isDestroy: false));

            return(constructionSite);
        }
Exemple #2
0
        public static void SharedRefreshNeighborObjects(IStaticWorldObject staticWorldObject, bool isDestroy)
        {
            var protoWorldObject = staticWorldObject.ProtoStaticWorldObject;

            switch (protoWorldObject)
            {
            case IProtoObjectWall _:
            case IProtoObjectDoor _:
            case ObjectWallDestroyed _:
                SharedRefreshNeighborObjects(staticWorldObject.OccupiedTile, isDestroy);
                break;

            case ProtoObjectConstructionSite _:
            {
                var constructionProto = ProtoObjectConstructionSite.GetPublicState(staticWorldObject)
                                        .ConstructionProto;
                switch (constructionProto)
                {
                case IProtoObjectWall _:
                case IProtoObjectDoor _:
                    SharedRefreshNeighborObjects(staticWorldObject.OccupiedTile, isDestroy);
                    break;
                }

                break;
            }
            }
        }
        private static bool ClientValidateCanBuildCallback(Vector2Ushort tilePosition, bool logErrors)
        {
            var protoStructure = currentSelectedProtoConstruction;

            if (Client.World.GetTile(tilePosition)
                .StaticObjects
                .Any(so => so.ProtoStaticWorldObject == protoStructure ||
                     ProtoObjectConstructionSite.SharedIsConstructionOf(so, protoStructure)))
            {
                // this building is already built here
                return(false);
            }

            if (!protoStructure.CheckTileRequirements(
                    tilePosition,
                    Client.Characters.CurrentPlayerCharacter,
                    logErrors: logErrors))
            {
                // time requirements are not valid
                return(false);
            }

            var configBuild = protoStructure.ConfigBuild;

            if (configBuild.CheckStageCanBeBuilt(Client.Characters.CurrentPlayerCharacter))
            {
                return(true);
            }

            //NotificationSystem.ClientShowNotification("Cannot build", "Not enough items.", NotificationColor.Bad);

            // close construction menu
            ClientCloseConstructionMenu();
            return(false);
        }
Exemple #4
0
        public static void SharedRefreshNeighborObjects(IStaticWorldObject staticWorldObject, bool isDestroy)
        {
            if (isProcessingQueueNow)
            {
                // don't allow recursive refreshing
                return;
            }

            var protoWorldObject = staticWorldObject.ProtoStaticWorldObject;

            switch (protoWorldObject)
            {
            case IProtoObjectWall _:
            case IProtoObjectDoor _:
            case ObjectWallDestroyed _:
                SharedRefreshNeighborObjects(staticWorldObject.OccupiedTile, isDestroy);
                break;

            case ProtoObjectConstructionSite _:
            {
                var constructionProto = ProtoObjectConstructionSite.GetPublicState(staticWorldObject)
                                        .ConstructionProto;
                switch (constructionProto)
                {
                case IProtoObjectWall _:
                case IProtoObjectDoor _:
                    SharedRefreshNeighborObjects(staticWorldObject.OccupiedTile, isDestroy);
                    break;
                }

                break;
            }
            }
        }
Exemple #5
0
        public static IComponentAttachedControl CreateAndAttach(IStaticWorldObject worldObject)
        {
            var control = new ConstructionOrRepairRequirementsTooltip();

            control.WorldObject = worldObject;

            if (worldObject.ProtoStaticWorldObject is ProtoObjectConstructionSite)
            {
                // construction
                control.Setup(
                    constructionSitePublicState: ProtoObjectConstructionSite.GetPublicState(worldObject));
            }
            else
            {
                // repair
                control.Setup(
                    objectToRepairPublicState: worldObject.GetPublicState <StaticObjectPublicState>());
            }

            var centerOffset = worldObject.ProtoStaticWorldObject.SharedGetObjectCenterWorldOffset(worldObject);

            return(Api.Client.UI.AttachControl(
                       worldObject,
                       control,
                       positionOffset: centerOffset + (0, -0.6),
                       isFocusable: true));
        }
Exemple #6
0
        private static void ClientValidateCanBuild(
            Vector2Ushort tilePosition,
            bool logErrors,
            out string errorMessage,
            out bool canPlace,
            out bool isTooFar)
        {
            var protoStructure = currentSelectedProtoConstruction;

            if (Client.World.GetTile(tilePosition)
                .StaticObjects
                .Any(so => so.ProtoStaticWorldObject == protoStructure ||
                     ProtoObjectConstructionSite.SharedIsConstructionOf(so, protoStructure)))
            {
                // this building is already built here
                canPlace     = false;
                isTooFar     = false;
                errorMessage = null;
                return;
            }

            var character = Client.Characters.CurrentPlayerCharacter;

            if (!protoStructure.CheckTileRequirements(
                    tilePosition,
                    character,
                    errorCodeOrMessage: out var errorCodeOrMessage,
                    logErrors: logErrors))
            {
                // time requirements are not valid
                canPlace     = false;
                isTooFar     = false;
                errorMessage = ConstructionSystem.SharedConvertCodeOrErrorMessageToString(errorCodeOrMessage);
                return;
            }

            errorMessage = null;

            var configBuild = protoStructure.ConfigBuild;

            if (configBuild.CheckStageCanBeBuilt(character))
            {
                canPlace = true;
                isTooFar = SharedIsTooFarToPlace(protoStructure,
                                                 tilePosition,
                                                 character,
                                                 logErrors);
                return;
            }

            // not enough items to build the stage
            // close construction menu
            ClientCloseConstructionMenu();
            canPlace = false;
            isTooFar = false;
        }
Exemple #7
0
        private void UpdateBlueprintCanBuild(Tile tile)
        {
            if (this.HideBlueprintOnOverlapWithTheSameObject)
            {
                foreach (var tileObj in tile.StaticObjects)
                {
                    if (tileObj.ProtoStaticWorldObject == this.protoStaticWorldObject ||
                        ProtoObjectConstructionSite.SharedIsConstructionOf(tileObj, this.protoStaticWorldObject))
                    {
                        this.blueprintRenderer.IsEnabled = false;
                        this.tilesBlueprint.IsEnabled    = false;
                        return;
                    }
                }
            }

            this.blueprintRenderer.IsEnabled = true;
            this.tilesBlueprint.IsEnabled    = true;
            var tilePosition = tile.Position;

            this.validateCanBuildCallback(tilePosition,
                                          logErrors: false,
                                          out var errorMessage,
                                          out var canPlace,
                                          out var isTooFar);

            if (this.blueprintRenderer is null)
            {
                // this component have been disabled during the validation callback
                return;
            }

            if (this.blueprintRenderer.IsCanBuild != canPlace ||
                this.blueprintRenderer.IsTooFar != isTooFar)
            {
                this.isCanBuildChanged = true;
            }

            this.blueprintRenderer.IsCanBuild = canPlace;
            this.tilesBlueprint.IsCanBuild    = canPlace;
            this.blueprintRenderer.IsTooFar   = isTooFar;

            if (isTooFar && string.IsNullOrEmpty(errorMessage))
            {
                errorMessage = CoreStrings.Notification_TooFar;
            }

            this.blueprintRenderer.CannotBuildReason = errorMessage;
            this.blueprintRenderer.RefreshMaterial();

            // cache check result for 0.1 second
            this.cachedTimeRemainsSeconds = 0.1;
        }
        /// <summary>
        /// Checks if the wall in other tile is compatible with this type.
        /// </summary>
        private static bool SharedIsCompatibleWallType(
            Tile tile,
            bool recognizeDestroyedWalls,
            bool recognizeConstructionSites,
            bool isHorizontal)
        {
            foreach (var worldObject in tile.StaticObjects)
            {
                if (worldObject.IsDestroyed)
                {
                    continue;
                }

                if (worldObject.ProtoWorldObject is IProtoObjectWall ||
                    recognizeDestroyedWalls && worldObject.ProtoWorldObject is ObjectWallDestroyed)
                {
                    return(true);
                }

                if (recognizeConstructionSites &&
                    ProtoObjectConstructionSite.SharedIsConstructionOf(worldObject, typeof(IProtoObjectWall)))
                {
                    return(true);
                }

                if (isHorizontal)
                {
                    if (SharedIsCompatibleDoor(worldObject, tile, recognizeConstructionSites, isHorizontal: true))
                    {
                        return(true);
                    }
                }
                // Disabled - no need for this as we cover that empty space with the hitboxes
                // and the space is too small to pass through so absence of a physical collider is fine.
                //else if (Api.IsServer)
                //{
                //    // Only on the server side we do the check for the vertical compatible door.
                //    // That's because we don't want to draw the "compatible wall adapters" on the client side.
                //    // It also means that physics is a bit different between the client and the server
                //    // in this aspect but it's barely noticeable.
                //    if (SharedIsCompatibleDoor(worldObject, tile, recognizeConstructionSites, isHorizontal: false))
                //    {
                //        return true;
                //    }
                //}
            }

            return(false);
        }
Exemple #9
0
        /// <summary>
        /// Checks if the wall in other tile is compatible with this type.
        /// </summary>
        private static bool SharedIsCompatibleWallType(
            Tile tile,
            bool recognizeDestroyedWalls,
            bool recognizeConstructionSites,
            bool isHorizontal)
        {
            foreach (var worldObject in tile.StaticObjects)
            {
                if (worldObject.IsDestroyed)
                {
                    continue;
                }

                if (worldObject.ProtoWorldObject is IProtoObjectWall ||
                    recognizeDestroyedWalls && worldObject.ProtoWorldObject is ObjectWallDestroyed)
                {
                    return(true);
                }

                if (recognizeConstructionSites &&
                    ProtoObjectConstructionSite.SharedIsConstructionOf(worldObject, typeof(IProtoObjectWall)))
                {
                    return(true);
                }

                if (isHorizontal)
                {
                    if (SharedIsCompatibleDoor(worldObject, tile, recognizeConstructionSites, isHorizontal: true))
                    {
                        return(true);
                    }
                }
                // TODO: restore this. Commented out as it's preventing from moving via diagonal doors.
                //else if (Api.IsServer)
                //{
                //    // Only on the server side we do the check for the vertical compatible door.
                //    // That's because we don't want to draw the "compatible wall adapters" on the client side.
                //    // It also means that physics is a bit different between the client and the server
                //    // in this aspect but it's barely noticeable.
                //    if (SharedIsCompatibleDoor(worldObject, tile, recognizeConstructionSites, isHorizontal: false))
                //    {
                //        return true;
                //    }
                //}
            }

            return(false);
        }
        private void UpdateBlueprintCanBuild(Tile tile)
        {
            foreach (var tileObj in tile.StaticObjects)
            {
                if (tileObj.ProtoStaticWorldObject == this.protoStaticWorldObject ||
                    ProtoObjectConstructionSite.SharedIsConstructionOf(tileObj, this.protoStaticWorldObject))
                {
                    this.blueprintRenderer.IsEnabled = false;
                    this.tilesBlueprint.IsEnabled    = false;
                    return;
                }
            }

            this.blueprintRenderer.IsEnabled = true;
            this.tilesBlueprint.IsEnabled    = true;
            var tilePosition = tile.Position;

            this.validateCanBuildCallback(tilePosition,
                                          logErrors: false,
                                          out var canPlace,
                                          out var isTooFar);

            if (this.blueprintRenderer == null)
            {
                // this component have been disabled during the validation callback
                return;
            }

            if (this.blueprintRenderer.IsCanBuild != canPlace ||
                this.blueprintRenderer.IsTooFar != isTooFar)
            {
                this.isCanBuildChanged = true;
            }

            this.blueprintRenderer.IsCanBuild = canPlace;
            this.tilesBlueprint.IsCanBuild    = canPlace;
            this.blueprintRenderer.IsTooFar   = isTooFar;
            this.blueprintRenderer.RefreshMaterial();

            // cache check result for 0.1 second
            this.cachedTimeRemainsSeconds = 0.1d;
        }
Exemple #11
0
        private static bool SharedIsCompatibleDoor(
            IStaticWorldObject worldObject,
            Tile tile,
            bool isConsiderConstructionSites,
            bool isHorizontal)
        {
            if (worldObject.ProtoWorldObject is IProtoObjectDoor &&
                isHorizontal == worldObject.GetPublicState <ObjectDoorPublicState>().IsHorizontalDoor)
            {
                return(true);
            }

            if (isConsiderConstructionSites &&
                ProtoObjectConstructionSite.SharedIsConstructionOf(worldObject, typeof(IProtoObjectDoor)) &&
                isHorizontal == DoorHelper.IsHorizontalDoorNeeded(tile, checkExistingDoor: true))
            {
                return(true);
            }

            return(false);
        }
Exemple #12
0
        private void SharedOnStageCompleted()
        {
            if (!this.ValidateRequiredItemsAvailable())
            {
                // don't have required items - cannot do building/repairing action
                this.AbortAction();
                return;
            }

            if (Api.IsServer)
            {
                // items are removing only on the Server-side
                this.Config.ServerDestroyRequiredItems(this.Character);

                // notify tool was used
                ServerItemUseObserver.NotifyItemUsed(this.Character, this.ItemConstructionTool);

                // reduce tool durability
                ItemDurabilitySystem.ServerModifyDurability(this.ItemConstructionTool, delta: -1);
            }

            this.currentStageDurationSeconds     = this.CalculateStageDurationSeconds(this.Character, isFirstStage: false);
            this.currentStageTimeRemainsSeconds += this.currentStageDurationSeconds;

            var currentStructurePoints = this.ObjectPublicState.StructurePointsCurrent;
            var newStructurePoints     = currentStructurePoints
                                         + this.stageStructureAddValue;

            // Please note: as we're using floating number (StructurePointsCurrent) to track the construction progress
            // it might cause some inaccuracy. In order to avoid this inaccuracy we're adding some tolerance.
            // The tolerance is also needed to handle the case when the blueprint was damaged only slightly.
            var completionTolerance = this.stageStructureAddValue / 2.0;

            // Please note: client will simply always construct until finished
            // Server will keep building stages until the completion tolerance reached.
            if ((Api.IsClient && currentStructurePoints < this.structurePointsMax) ||
                (Api.IsServer && newStructurePoints + completionTolerance < this.structurePointsMax))
            {
                // repairing/building is still possible - more stages are available
                if (Api.IsServer)
                {
                    this.ObjectPublicState.StructurePointsCurrent = (float)newStructurePoints;
                    Api.Logger.Important(
                        $"Building/repairing progressed: {this.WorldObject} structure points: {newStructurePoints}/{this.structurePointsMax}; by {this.Character}");

                    if (this.IsRepair)
                    {
                        ((IProtoObjectStructure)this.WorldObject.ProtoStaticWorldObject)
                        .ServerOnRepairStageFinished(this.WorldObject, this.Character);
                    }
                }

                this.UpdateProgress();

                if (!this.ValidateRequiredItemsAvailable())
                {
                    // don't have enough required items - cannot continue building/repairing action
                    this.AbortAction();
                    return;
                }

                if (Api.IsServer &&
                    this.ItemConstructionTool.IsDestroyed)
                {
                    // tool was destroyed (durability 0)
                    this.AbortAction();
                    return;
                }

                return;
            }

            // repairing/building is completed
            if (Api.IsServer)
            {
                newStructurePoints = this.structurePointsMax;
                Api.Logger.Important(
                    $"Building/repairing completed: {this.WorldObject} structure points: {newStructurePoints}/{this.structurePointsMax}; by {this.Character}");
                this.ObjectPublicState.StructurePointsCurrent = (float)newStructurePoints;

                if (this.IsRepair)
                {
                    ((IProtoObjectStructure)this.WorldObject.ProtoStaticWorldObject)
                    .ServerOnRepairStageFinished(this.WorldObject, this.Character);
                }

                if (this.ObjectPublicState is ConstructionSitePublicState constructionSiteState)
                {
                    constructionSiteState.LastBuildActionDoneByCharacter = this.Character;
                    var constructionProto = ProtoObjectConstructionSite.SharedGetConstructionProto(this.WorldObject);

                    // add skill experience for building
                    this.CharacterPrivateState.Skills.ServerAddSkillExperience <SkillBuilding>(
                        SkillBuilding.ExperienceAddWhenBuildingFinished
                        * constructionProto.BuildingSkillExperienceMultiplier);
                }
                else
                {
                    // add skill experience for repair
                    this.CharacterPrivateState.Skills.ServerAddSkillExperience <SkillBuilding>(
                        SkillBuilding.ExperienceAddWhenRepairFinished
                        * ((IProtoObjectStructure)this.WorldObject.ProtoGameObject).BuildingSkillExperienceMultiplier);
                }
            }

            this.SetCompleted(isCancelled: false);
            ConstructionSystem.SharedActionCompleted(this.Character, this);
        }
        public static bool CheckCanInteractForConstruction(
            ICharacter character,
            IStaticWorldObject worldObject,
            bool writeToLog,
            bool checkRaidblock)
        {
            var characterPosition      = character.Position;
            var canInteract            = false;
            var staticWorldObjectProto = ProtoObjectConstructionSite.SharedGetConstructionProto(worldObject)
                                         ?? worldObject.ProtoStaticWorldObject;

            var startTilePosition = worldObject.TilePosition;

            foreach (var tileOffset in staticWorldObjectProto.Layout.TileOffsets)
            {
                var tilePosition = startTilePosition + tileOffset;

                if (characterPosition.DistanceSquaredTo(
                        new Vector2D(tilePosition.X + 0.5, tilePosition.Y + 0.5))
                    <= MaxDistanceForBuildRepairAction * MaxDistanceForBuildRepairAction)
                {
                    canInteract = true;
                    break;
                }
            }

            if (!canInteract)
            {
                canInteract = CreativeModeSystem.SharedIsInCreativeMode(character);
            }

            if (!canInteract)
            {
                if (writeToLog)
                {
                    Logger.Warning(
                        $"Character cannot interact with {worldObject} for (de)construction - too far.",
                        character);

                    if (IsClient)
                    {
                        CannotInteractMessageDisplay.ClientOnCannotInteract(worldObject,
                                                                            CoreStrings.Notification_TooFar,
                                                                            isOutOfRange: true);
                    }
                }

                return(false);
            }

            if (checkRaidblock &&
                LandClaimSystem.SharedIsUnderRaidBlock(character, worldObject))
            {
                // the building is in an area under the raid
                if (writeToLog)
                {
                    LandClaimSystem.SharedSendNotificationActionForbiddenUnderRaidblock(character);
                }

                return(false);
            }

            return(true);
        }
Exemple #14
0
        /// <summary>
        /// Find neighbor objects of type wall or construction site (for wall) and refresh their renderers
        /// according to their neighbors.
        /// </summary>
        public static void SharedRefreshNeighborObjects(Tile tile, bool isDestroy)
        {
            if (isRefreshing)
            {
                // don't allow recursive refreshing
                return;
            }

            try
            {
                isRefreshing = true;

                foreach (var neighborTile in tile.EightNeighborTiles)
                {
                    foreach (var obj in neighborTile.StaticObjects)
                    {
                        var protoWorldObject = obj.ProtoWorldObject;
                        switch (protoWorldObject)
                        {
                        case IProtoObjectWall _:
                        case IProtoObjectDoor _:
                        case ObjectWallDestroyed _:
                            RefreshWorldObject();
                            break;

                        case ProtoObjectConstructionSite _:
                        {
                            var constructionProto = ProtoObjectConstructionSite.GetPublicState(obj)
                                                    .ConstructionProto;
                            switch (constructionProto)
                            {
                            case IProtoObjectWall _:
                            case IProtoObjectDoor _:
                                RefreshWorldObject();
                                break;
                            }

                            break;
                        }
                        }

                        // helper local function
                        void RefreshWorldObject()
                        {
                            if (Api.IsClient)
                            {
                                obj.ClientInitialize();
                            }
                            else
                            {
                                obj.ServerInitialize();
                            }
                        }
                    }
                }
            }
            finally
            {
                isRefreshing = false;
            }
        }