Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for rotated/mirrored tiles #5652

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Resources/Locale/en-US/custom-controls.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ entity-spawn-window-override-menu-tooltip = Override placement
## TileSpawnWindow

tile-spawn-window-title = Place Tiles
tile-spawn-window-mirror-button-text = Mirror Tiles

## Console

Expand Down
48 changes: 44 additions & 4 deletions Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,50 @@ private void _updateChunkMesh(Entity<MapGridComponent> grid, MapChunk chunk, Map
var gy = y + cScaled.Y;

var vIdx = i * 4;
vertexBuffer[vIdx + 0] = new Vertex2D(gx, gy, region.Left, region.Bottom, Color.White);
vertexBuffer[vIdx + 1] = new Vertex2D(gx + 1, gy, region.Right, region.Bottom, Color.White);
vertexBuffer[vIdx + 2] = new Vertex2D(gx + 1, gy + 1, region.Right, region.Top, Color.White);
vertexBuffer[vIdx + 3] = new Vertex2D(gx, gy + 1, region.Left, region.Top, Color.White);

var rLeftBottom = (region.Left, region.Bottom);
var rRightBottom = (region.Right, region.Bottom);
var rRightTop = (region.Right, region.Top);
var rLeftTop = (region.Left, region.Top);

// Rotate the tile
for (int r = 0; r < tile.Rotation; r++)
{
(rLeftBottom, rRightBottom, rRightTop, rLeftTop) =
(rLeftTop, rLeftBottom, rRightBottom, rRightTop);
}

// Mirror on the x-axis
if (tile.Mirrored)
{
if (tile.Rotation % 2 == 0)
{
rLeftBottom = (rLeftBottom.Item1.Equals(region.Left) ? region.Right : region.Left,
rLeftBottom.Item2);
rRightBottom = (rRightBottom.Item1.Equals(region.Left) ? region.Right : region.Left,
rRightBottom.Item2);
rRightTop = (rRightTop.Item1.Equals(region.Left) ? region.Right : region.Left,
rRightTop.Item2);
rLeftTop = (rLeftTop.Item1.Equals(region.Left) ? region.Right : region.Left,
rLeftTop.Item2);
}
else
{
rLeftBottom = (rLeftBottom.Item1,
rLeftBottom.Item2.Equals(region.Bottom) ? region.Top : region.Bottom);
rRightBottom = (rRightBottom.Item1,
rRightBottom.Item2.Equals(region.Bottom) ? region.Top : region.Bottom);
rRightTop = (rRightTop.Item1,
rRightTop.Item2.Equals(region.Bottom) ? region.Top : region.Bottom);
rLeftTop = (rLeftTop.Item1,
rLeftTop.Item2.Equals(region.Bottom) ? region.Top : region.Bottom);
}
}

vertexBuffer[vIdx + 0] = new Vertex2D(gx, gy, rLeftBottom.Item1, rLeftBottom.Item2, Color.White);
vertexBuffer[vIdx + 1] = new Vertex2D(gx + 1, gy, rRightBottom.Item1, rRightBottom.Item2, Color.White);
vertexBuffer[vIdx + 2] = new Vertex2D(gx + 1, gy + 1, rRightTop.Item1, rRightTop.Item2, Color.White);
vertexBuffer[vIdx + 3] = new Vertex2D(gx, gy + 1, rLeftTop.Item1, rLeftTop.Item2, Color.White);
var nIdx = i * GetQuadBatchIndexCount();
var tIdx = (ushort)(i * 4);
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
Expand Down
12 changes: 11 additions & 1 deletion Robust.Client/Placement/IPlacementManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,20 @@ public interface IPlacementManager
Direction Direction { get; set; }

/// <summary>
/// Gets called when Direction changed (presently for EntitySpawnWindow UI)
/// Whether a tile placement should be mirrored or not.
/// </summary>
bool Mirrored { get; set; }

/// <summary>
/// Gets called when Direction changed (presently for EntitySpawnWindow/TileSpawnWindow UI)
/// </summary>
event EventHandler DirectionChanged;

/// <summary>
/// Gets called when Mirrored changed (presently for TileSpawnWindow UI)
/// </summary>
event EventHandler MirroredChanged;

/// <summary>
/// Gets called when the PlacementManager changed its build/erase mode or when the hijacks changed
/// </summary>
Expand Down
25 changes: 24 additions & 1 deletion Robust.Client/Placement/PlacementManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ public Box2 ColliderAABB

private Direction _direction = Direction.South;

private bool _mirrored;

public bool Mirrored
{
get => _mirrored;
set
{
_mirrored = value;
MirroredChanged?.Invoke(this, EventArgs.Empty);
}
}

/// <inheritdoc />
public Direction Direction
{
Expand All @@ -188,6 +200,9 @@ public Direction Direction
/// <inheritdoc />
public event EventHandler? DirectionChanged;

/// <inheritdoc />
public event EventHandler? MirroredChanged;

private PlacementOverlay _drawOverlay = default!;
private bool _isActive;

Expand Down Expand Up @@ -772,7 +787,10 @@ private void RequestPlacement(EntityCoordinates coordinates)
var grid = EntityManager.GetComponent<MapGridComponent>(gridId);

// no point changing the tile to the same thing.
if (Maps.GetTileRef(gridId, grid, coordinates).Tile.TypeId == CurrentPermission.TileType)
var tileRef = Maps.GetTileRef(gridId, grid, coordinates).Tile;
if (tileRef.TypeId == CurrentPermission.TileType &&
tileRef.Mirrored == Mirrored &&
tileRef.Rotation == Tile.DirectionToByte(Direction))
return;
}

Expand All @@ -796,9 +814,14 @@ private void RequestPlacement(EntityCoordinates coordinates)
};

if (CurrentPermission.IsTile)
{
message.TileType = CurrentPermission.TileType;
message.Mirrored = Mirrored;
}
else
{
message.EntityTemplateName = CurrentPermission.EntityType;
}

// world x and y
message.NetCoordinates = EntityManager.GetNetCoordinates(coordinates);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ public sealed class TileSpawningUIController : UIController
private readonly List<ITileDefinition> _shownTiles = new();
private bool _clearingTileSelections;
private bool _eraseTile;
private bool _mirroredTile;

public override void Initialize()
{
DebugTools.Assert(_init == false);
_init = true;
_placement.PlacementChanged += ClearTileSelection;
_placement.DirectionChanged += OnDirectionChanged;
_placement.MirroredChanged += OnMirroredChanged;
}

private void StartTilePlacement(int tileType)
Expand Down Expand Up @@ -67,6 +70,21 @@ private void OnTileEraseToggled(ButtonToggledEventArgs args)
args.Button.Pressed = args.Pressed;
}

private void OnTileMirroredToggled(ButtonToggledEventArgs args)
{
if (_window == null || _window.Disposed)
return;

if (args.Pressed)
_placement.Mirrored = true;
else
_placement.Mirrored = false;

_mirroredTile = _placement.Mirrored;

args.Button.Pressed = args.Pressed;
}

public void ToggleWindow()
{
EnsureWindow();
Expand All @@ -78,6 +96,9 @@ public void ToggleWindow()
else
{
_window.Open();
UpdateEntityDirectionLabel();
UpdateMirroredButton();
_window.SearchBar.GrabKeyboardFocus();
}
}

Expand All @@ -94,6 +115,8 @@ private void EnsureWindow()
_window.TileList.OnItemDeselected += OnTileItemDeselected;
_window.EraseButton.Pressed = _eraseTile;
_window.EraseButton.OnToggled += OnTileEraseToggled;
_window.MirroredButton.Pressed = _mirroredTile;
_window.MirroredButton.OnToggled += OnTileMirroredToggled;
BuildTileList();
}

Expand All @@ -111,6 +134,7 @@ private void ClearTileSelection(object? sender, EventArgs e)
_window.TileList.ClearSelected();
_clearingTileSelections = false;
_window.EraseButton.Pressed = false;
_window.MirroredButton.Pressed = _placement.Mirrored;
}

private void OnTileClearPressed(ButtonEventArgs args)
Expand Down Expand Up @@ -150,6 +174,33 @@ private void OnTileItemDeselected(ItemList.ItemListDeselectedEventArgs args)
_placement.Clear();
}

private void OnDirectionChanged(object? sender, EventArgs e)
{
UpdateEntityDirectionLabel();
}

private void UpdateEntityDirectionLabel()
{
if (_window == null || _window.Disposed)
return;

_window.RotationLabel.Text = _placement.Direction.ToString();
}

private void OnMirroredChanged(object? sender, EventArgs e)
{
UpdateMirroredButton();
}

private void UpdateMirroredButton()
{
if (_window == null || _window.Disposed)
return;

_mirroredTile = _placement.Mirrored;
_window.MirroredButton.Pressed = _mirroredTile;
}

private void BuildTileList(string? searchStr = null)
{
if (_window == null || _window.Disposed) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
</BoxContainer>
<ItemList Name="TileList" Access="Public" VerticalExpand="True"/>
<BoxContainer Orientation="Horizontal">
<Button Name="MirroredButton" Access="Public" ToggleMode="True" Text="{Loc tile-spawn-window-mirror-button-text}"/>
<Button Name="EraseButton" Access="Public" ToggleMode="True" Text="{Loc window-erase-button-text}"/>
</BoxContainer>
<Label Name="RotationLabel" Access="Public"/>
</BoxContainer>
</TileSpawnWindow>
48 changes: 37 additions & 11 deletions Robust.Server/Maps/MapChunkSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,43 @@ public MapChunk Read(ISerializationManager serializationManager, MappingDataNode
node.TryGetValue(new ValueDataNode("version"), out var versionNode);
var version = ((ValueDataNode?) versionNode)?.AsInt() ?? 1;

for (ushort y = 0; y < chunk.ChunkSize; y++)
// Old map files will throw an error if they do not have a rotation/mirror byte stored.
if (version >= 7)
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
for (ushort y = 0; y < chunk.ChunkSize; y++)
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var id = version < 6 ? reader.ReadUInt16() : reader.ReadInt32();
var flags = (TileRenderFlag)reader.ReadByte();
var variant = reader.ReadByte();
var rotation = reader.ReadByte();
var mirrored = System.Convert.ToBoolean(reader.ReadByte());

var defName = tileMap[id];
id = tileDefinitionManager[defName].TileId;

var tile = new Tile(id, flags, variant, rotation, mirrored);
chunk.TrySetTile(x, y, tile, out _, out _);
}
}
}
else
{
for (ushort y = 0; y < chunk.ChunkSize; y++)
{
var id = version < 6 ? reader.ReadUInt16() : reader.ReadInt32();
var flags = (TileRenderFlag)reader.ReadByte();
var variant = reader.ReadByte();
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var id = version < 6 ? reader.ReadUInt16() : reader.ReadInt32();
var flags = (TileRenderFlag)reader.ReadByte();
var variant = reader.ReadByte();

var defName = tileMap[id];
id = tileDefinitionManager[defName].TileId;
var defName = tileMap[id];
id = tileDefinitionManager[defName].TileId;

var tile = new Tile(id, flags, variant);
chunk.TrySetTile(x, y, tile, out _, out _);
var tile = new Tile(id, flags, variant);
chunk.TrySetTile(x, y, tile, out _, out _);
}
}
}

Expand All @@ -102,7 +126,7 @@ public DataNode Write(ISerializationManager serializationManager, MapChunk value
var gridNode = new ValueDataNode();
root.Add("tiles", gridNode);

root.Add("version", new ValueDataNode("6"));
root.Add("version", new ValueDataNode("7"));

Dictionary<int, int>? tileWriteMap = null;
if (context is MapSerializationContext mapContext)
Expand All @@ -116,7 +140,7 @@ public DataNode Write(ISerializationManager serializationManager, MapChunk value
private static string SerializeTiles(MapChunk chunk, Dictionary<int, int>? tileWriteMap)
{
// number of bytes written per tile, because sizeof(Tile) is useless.
const int structSize = 6;
const int structSize = 8;

var nTiles = chunk.ChunkSize * chunk.ChunkSize * structSize;
var barr = new byte[nTiles];
Expand All @@ -136,6 +160,8 @@ private static string SerializeTiles(MapChunk chunk, Dictionary<int, int>? tileW
writer.Write(typeId);
writer.Write((byte)tile.Flags);
writer.Write(tile.Variant);
writer.Write(tile.Rotation);
writer.Write(System.Convert.ToByte(tile.Mirrored));
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions Robust.Server/Placement/PlacementManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,11 @@ public void HandlePlacementRequest(MsgPlacement msg)
}
else
{
PlaceNewTile(tileType, coordinates, msg.MsgChannel.UserId);
PlaceNewTile(tileType, coordinates, msg.MsgChannel.UserId, Tile.DirectionToByte(dirRcv), msg.Mirrored);
}
}

private void PlaceNewTile(int tileType, EntityCoordinates coordinates, NetUserId placingUserId)
private void PlaceNewTile(int tileType, EntityCoordinates coordinates, NetUserId placingUserId, byte direction, bool mirrored)
{
if (!coordinates.IsValid(_entityManager)) return;

Expand All @@ -193,7 +193,7 @@ private void PlaceNewTile(int tileType, EntityCoordinates coordinates, NetUserId
if (_entityManager.TryGetComponent(coordinates.EntityId, out grid)
|| _mapManager.TryFindGridAt(_xformSystem.ToMapCoordinates(coordinates), out gridId, out grid))
{
_maps.SetTile(gridId, grid, coordinates, new Tile(tileType));
_maps.SetTile(gridId, grid, coordinates, new Tile(tileType, rotation: direction, mirrored: mirrored));

var placementEraseEvent = new PlacementTileEvent(tileType, coordinates, placingUserId);
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
Expand All @@ -207,7 +207,7 @@ private void PlaceNewTile(int tileType, EntityCoordinates coordinates, NetUserId

_xformSystem.SetWorldPosition(newGridXform, coordinates.Position - newGrid.Comp.TileSizeHalfVector); // assume bottom left tile origin
var tilePos = _maps.WorldToTile(newGrid.Owner, newGrid.Comp, coordinates.Position);
_maps.SetTile(newGrid.Owner, newGrid.Comp, tilePos, new Tile(tileType));
_maps.SetTile(newGrid.Owner, newGrid.Comp, tilePos, new Tile(tileType, rotation: direction, mirrored: mirrored));

var placementEraseEvent = new PlacementTileEvent(tileType, coordinates, placingUserId);
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
Expand Down
Loading
Loading