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