private void ClientDeleteInternal(IReadOnlyCollection <IStaticWorldObject> worldObjectsToDelete)
        {
            worldObjectsToDelete = worldObjectsToDelete.Distinct().ToList();

            var restoreRequests = worldObjectsToDelete.Select(o => new RestoreObjectRequest(o))
                                  .Batch(20000)
                                  .Select(b => b.ToList())
                                  .ToList();

            var tilePositions = worldObjectsToDelete
                                .GroupBy(_ => _.TilePosition)
                                .Select(g => g.Key)
                                .Batch(20000)
                                .Select(b => b.ToList())
                                .ToList();

            EditorClientSystem.DoAction(
                "Delete objects",
                onDo: () =>
            {
                foreach (var batch in tilePositions)
                {
                    this.CallServer(_ => _.ServerRemote_DeleteObjects(batch));
                }
            },
                onUndo: () =>
            {
                foreach (var batch in restoreRequests)
                {
                    this.CallServer(_ => _.ServerRemote_RestoreObjects(batch));
                }
            },
                canGroupWithPreviousAction: false);
        }
예제 #2
0
        public void ApplyClientChanges(bool forcePushChangesImmediately)
        {
            this.ValidateIsDataReceived();

            var newSnapshot = this.quadTree.SaveQuadTree();
            var diffDo      = QuadTreeDiff.Create(
                newSnapshot,
                this.lastClientSnapshot);

            if (diffDo.IsEmpty)
            {
                // quad trees are equal
                return;
            }

            var redo = false;

            EditorClientSystem.DoAction(
                "Modify zone " + this.ProtoZone.Id,
                onDo: () =>
            {
                if (redo)
                {
                    this.quadTree.ApplyDiff(diffDo);
                }

                OnDiffApplied(diffDo);
            },
                onUndo: () =>
            {
                // set flag for next "do" call to make it "redo"
                redo         = true;
                var diffRedo = diffDo.ReverseDiff();
                this.quadTree.ApplyDiff(diffRedo);
                OnDiffApplied(diffRedo);
            });

            // helper local function
            void OnDiffApplied(QuadTreeDiff appliedDiff)
            {
                this.isNeedSyncToServer   = true;
                this.lastModificationTime = Api.Client.Core.ClientRealTime;

                this.lastClientSnapshot = this.quadTree.SaveQuadTree();
                this.ZoneModified?.Invoke(appliedDiff);

                if (forcePushChangesImmediately)
                {
                    this.SyncToServer(forceImmediate: true);
                }
                else
                {
                    this.ScheduleSyncToServer();
                }
            }
        }
예제 #3
0
        public void Apply(
            long seed,
            double noiseProbability,
            BoundsUshort selectionBounds,
            IProtoTile protoTileTarget,
            IProtoTile protoTileNoise)
        {
            Api.Assert(protoTileTarget != null, "Please select target tile proto");
            Api.Assert(protoTileNoise != null, "Please select noise tile proto");
            Api.Assert(selectionBounds.Size.LengthSquared > 0, "Please select world area");
            Api.Assert(noiseProbability >= 0 || noiseProbability <= 1,
                       "Noise probability must be in range from 0 to 1 inclusive.");

            var random = new Random((int)seed);

            var world = Client.World;

            var tilesToModify = new List <Vector2Ushort>();

            for (var x = selectionBounds.MinX; x < selectionBounds.MaxX; x++)
            {
                for (var y = selectionBounds.MinY; y < selectionBounds.MaxY; y++)
                {
                    if (random.NextDouble() > noiseProbability)
                    {
                        // do not process this tile
                        continue;
                    }

                    // check tile type
                    var tilePosition = new Vector2Ushort(x, y);
                    var tile         = world.GetTile(tilePosition);
                    if (tile.ProtoTile == protoTileTarget)
                    {
                        tilesToModify.Add(tilePosition);
                    }
                }
            }

            if (tilesToModify.Count == 0)
            {
                return;
            }

            EditorClientSystem.DoAction(
                "Modify terrain tiles (noise)",
                onDo: () => tilesToModify.ChunkedInvoke(
                    5000,
                    chunk => this.CallServer(_ => _.ServerRemote_PlaceAt(chunk, protoTileNoise))),
                onUndo: () => tilesToModify.ChunkedInvoke(
                    5000,
                    chunk => this.CallServer(_ => _.ServerRemote_PlaceAt(chunk, protoTileTarget))));
        }
예제 #4
0
        private void ClientPlaceAt(List <Vector2Ushort> tilePositions, bool isRepeat)
        {
            this.ValidateCallback(tilePositions, out var tilePosition);

            var tile            = Client.World.GetTile(tilePosition);
            var previousIsSlope = tile.IsSlope;
            var newIsSlope      = !previousIsSlope;

            EditorClientSystem.DoAction(
                "Toggle terrain slope",
                onDo: () => this.CallServer(_ => _.ServerRemote_PlaceAt(tilePosition, newIsSlope)),
                onUndo: () => this.CallServer(_ => _.ServerRemote_PlaceAt(tilePosition, previousIsSlope)));
        }
예제 #5
0
        private void Init(ICharacter currentCharacter)
        {
            if (currentCharacter.ProtoCharacter != Api.GetProtoEntity <PlayerCharacterEditorMode>())
            {
                return;
            }

            Api.Client.UI.LayoutRootChildren.Add(new EditorHUDLayoutControl());

            ClientComponentWorldCameraZoomManager.Instance.ZoomBounds = ZoomBoundsEditorMode;
            Menu.Register <WindowEditorWorldMap>();

            this.inputContextEditorMapMenu
                = ClientInputContext
                  .Start("Editor map")
                  .HandleButtonDown(
                      GameButton.MapMenu,
                      Menu.Toggle <WindowEditorWorldMap>);

            this.inputContextEditorUndoRedo
                = ClientInputContext
                  .Start("Editor undo/redo")
                  .HandleAll(
                      () =>
            {
                var input = Client.Input;
                if (input.IsKeyDown(InputKey.Z) &&
                    input.IsKeyHeld(InputKey.Control))
                {
                    if (input.IsKeyHeld(InputKey.Shift))
                    {
                        EditorClientSystem.Redo();
                        return;
                    }

                    EditorClientSystem.Undo();
                    return;
                }

                if (input.IsKeyDown(InputKey.Y) &&
                    input.IsKeyHeld(InputKey.Control))
                {
                    EditorClientSystem.Redo();
                    return;
                }
            });
        }
예제 #6
0
        private void ClientPlaceStaticObject(
            List <Vector2Ushort> tilePositions,
            IProtoStaticWorldObject protoStaticWorldObject)
        {
            var tilePosition = tilePositions[0];

            if (Client.World.GetTile(tilePosition)
                .StaticObjects.Any(so => so.ProtoStaticWorldObject == protoStaticWorldObject))
            {
                return;
            }

            EditorClientSystem.DoAction(
                $"Place object \"{protoStaticWorldObject.Name}\"",
                onDo: () => this.CallServer(
                    _ => _.ServerRemote_PlaceStaticObject(protoStaticWorldObject, tilePosition)),
                onUndo: () => this.CallServer(_ => _.ServerRemote_Destroy(protoStaticWorldObject, tilePosition)));
        }
예제 #7
0
        private void ClientPlaceAt(List <Vector2Ushort> tilePositions, IProtoTile selectedProtoTile, bool isRepeat)
        {
            var terrainHeightMode     = this.settings.SelectedHeightMode.Value;
            var isAllowTileKindChange = this.settings.IsAllowTileKindChange;
            var isAllowTileProtoChangeOnlyOnTheSameHeight = this.settings.IsAllowTileProtoChangeOnlyOnTheSameHeight;
            var isApplyOnlyOnTheSameTileProto             = this.settings.IsApplyOnlyOnTheSameTileProto;

            if (this.settings.IsFillMode)
            {
                if (isRepeat)
                {
                    // fill doesn't support repeat
                    return;
                }

                tilePositions = EditorTileHelper.GatherAllTilePositionsOfTheSameProtoTile(
                    tilePositions[0],
                    onlyOnTheSameHeight: isAllowTileProtoChangeOnlyOnTheSameHeight,
                    ignoreCliffsAndSlopes: false);

                // don't change the tile heights in the fill mode
                terrainHeightMode = TerrainHeightMode.Keep;
            }

            var worldService = Client.World;

            byte       targetHeight = 0;
            IProtoTile targetProto  = null;

            if (isRepeat)
            {
                // use target height from previous iteration
                targetHeight = this.lastTargetHeight;
                targetProto  = this.lastTargetProto;
            }
            else
            {
                if (isApplyOnlyOnTheSameTileProto)
                {
                    targetProto = EditorTileHelper.CalculateMostFrequentTileProto(tilePositions);
                }

                switch (terrainHeightMode)
                {
                case TerrainHeightMode.Keep:
                case TerrainHeightMode.Flatten:
                    // calculate average height for all the tiles
                    targetHeight = EditorTileHelper.CalculateAverageHeight(tilePositions);
                    break;

                case TerrainHeightMode.Increase:
                    targetHeight = byte.MaxValue;
                    goto case TerrainHeightMode.Decrease;

                case TerrainHeightMode.Decrease:
                    // calculate target height
                    foreach (var tilePosition in tilePositions)
                    {
                        var tile = worldService.GetTile(tilePosition);
                        var calculatedNewTileHeight = this.CalculateNewTileHeight(tile, terrainHeightMode);

                        if (terrainHeightMode == TerrainHeightMode.Increase &&
                            calculatedNewTileHeight < targetHeight ||
                            terrainHeightMode == TerrainHeightMode.Decrease &&
                            calculatedNewTileHeight > targetHeight)
                        {
                            targetHeight = calculatedNewTileHeight;
                        }
                    }

                    break;
                }
            }

            this.lastTargetHeight = targetHeight;
            this.lastTargetProto  = targetProto;

            var tilesToModify = new List <List <TileModifyRequest> >();
            var tilesToRevert = new List <List <TileModifyRequest> >();

            // separate on groups of world chunk size
            var worldChunkSize = 10 * ScriptingConstants.WorldChunkSize;
            var groups         = tilePositions
                                 .GroupBy(g => new Vector2Ushort((ushort)(g.X / worldChunkSize),
                                                                 (ushort)(g.Y / worldChunkSize)))
                                 .ToList();

            // gather tile modifications for each group
            foreach (var group in groups)
            {
                var groupTilesToModify = new List <TileModifyRequest>();
                var groupTilesToRevert = new List <TileModifyRequest>();

                foreach (var tilePosition in group)
                {
                    var tile = worldService.GetTile(tilePosition);
                    var previousProtoTile  = tile.ProtoTile;
                    var previousTileHeight = tile.Height;
                    var previousIsSlope    = tile.IsSlope;

                    var newProtoTile = selectedProtoTile ?? previousProtoTile;
                    var newIsSlope   = tile.IsSlope;

                    if (isApplyOnlyOnTheSameTileProto &&
                        previousProtoTile != targetProto)
                    {
                        continue;
                    }

                    if (isAllowTileProtoChangeOnlyOnTheSameHeight &&
                        this.lastTargetHeight != previousTileHeight)
                    {
                        continue;
                    }

                    if (!isAllowTileKindChange &&
                        previousProtoTile.Kind != newProtoTile.Kind &&
                        previousProtoTile.Kind != TileKind.Placeholder)
                    {
                        continue;
                    }

                    var newTileHeight = previousTileHeight;
                    if (terrainHeightMode == TerrainHeightMode.Flatten)
                    {
                        if (IsValidHeightFotTile(tile, targetHeight))
                        {
                            // can set tile height to target height
                            newTileHeight = targetHeight;
                        }
                    }
                    else if (terrainHeightMode == TerrainHeightMode.Increase ||
                             terrainHeightMode == TerrainHeightMode.Decrease)
                    {
                        newTileHeight = this.CalculateNewTileHeight(tile, terrainHeightMode);
                        if (newTileHeight != targetHeight)
                        {
                            // cannot change tile height
                            newTileHeight = previousTileHeight;
                        }
                    }

                    if (previousProtoTile == newProtoTile &&
                        newTileHeight == previousTileHeight &&
                        newIsSlope == previousIsSlope)
                    {
                        // nothing to change - the tile is already as desired
                        continue;
                    }

                    groupTilesToModify.Add(new TileModifyRequest(tilePosition,
                                                                 newProtoTile.SessionIndex,
                                                                 newTileHeight,
                                                                 newIsSlope));
                    groupTilesToRevert.Add(new TileModifyRequest(tilePosition,
                                                                 previousProtoTile.SessionIndex,
                                                                 previousTileHeight,
                                                                 previousIsSlope));
                }

                if (groupTilesToModify.Count == 0)
                {
                    // nothing to modify in this group
                    continue;
                }

                tilesToModify.Add(groupTilesToModify);
                tilesToRevert.Add(groupTilesToRevert);
            }

            if (tilesToModify.Count == 0)
            {
                // nothing to modify
                return;
            }

            EditorClientSystem.DoAction(
                "Modify terrain tiles",
                onDo: () => tilesToModify.ForEach(
                    chunk => this.CallServer(_ => _.ServerRemote_PlaceAt(chunk))),
                onUndo: () => tilesToRevert.ForEach(
                    chunk => this.CallServer(_ => _.ServerRemote_PlaceAt(chunk))));
        }