Add chess (and mostly just tabletop backend stuff) (#4429)
* Add draggable tabletop component * Use EntityCoordinates instead * Don't send coordinates every frame * Add chessboard + verb WIP * Add documentation, verb networking works now * Work so far Need PVS refactor before being able to continue Current code is broken * viewsubscriber magic * yes * Fix map creation * Add chess pieces, attempt prediction * Add chess sprites and yml * Clamping + other stuff * fix * stuff * StopDragging() StartDragging() * add piece grabbing * Refactor dragging player to seperate event * 🤣 Who did this 🤣💯👌 * 📮 sussy 📮 * Update chessboard sprite, scale piece while dragging * yes * ye * y * Close tabletop window when player dies * Make interaction check more sane * Fix funny behaviour when stunned * Add icon * Fix rsi * Make time passed check more accurate Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Use EyeManager.PixelsPerMeter Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Add missing import * Move viewport properties to XAML * Make shared system and component abstract * Use built in EntityManager * Use RaiseNetworkEvent instead of SendSystemNetworkMessage * Cache ViewSubscriberSystem * Move unnecessary code to prototype * Delete map on component shutdown instead of round restart * Make documentation match rest of codebase * Use ComponentManager instead of TryGetComponent * Use TryGetComponent instead of GetComponent * Add nullspace check to ClampPositionToViewport() * Set world pos instead of local pos * Improve server side verification * Use visualizer * Add netsync: false to sprites using visualizer * Close window when chessboard is picked up * Update to master * Fix bug when opening window while another is opened * Use ComponentManager * Use TryGetValue Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
@@ -275,6 +275,7 @@ namespace Content.Client.Entry
|
||||
"BatteryCharger",
|
||||
"SpawnItemsOnUse",
|
||||
"AmbientOnPowered",
|
||||
"TabletopGame"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Content.Shared.Tabletop.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Client.Tabletop.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedTabletopDraggableComponent))]
|
||||
public class TabletopDraggableComponent : SharedTabletopDraggableComponent
|
||||
{
|
||||
// The player dragging the piece
|
||||
[ViewVariables]
|
||||
public NetUserId? DraggingPlayer;
|
||||
}
|
||||
}
|
||||
280
Content.Client/Tabletop/TabletopSystem.cs
Normal file
@@ -0,0 +1,280 @@
|
||||
using Content.Client.Tabletop.Components;
|
||||
using Content.Client.Tabletop.UI;
|
||||
using Content.Client.Viewport;
|
||||
using Content.Shared.Tabletop;
|
||||
using Content.Shared.Tabletop.Events;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||
|
||||
namespace Content.Client.Tabletop
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class TabletopSystem : SharedTabletopSystem
|
||||
{
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManger = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
// Time in seconds to wait until sending the location of a dragged entity to the server again
|
||||
private const float Delay = 1f / 10; // 10 Hz
|
||||
|
||||
private float _timePassed; // Time passed since last update sent to the server.
|
||||
private IEntity? _draggedEntity; // Entity being dragged
|
||||
private ScalingViewport? _viewport; // Viewport currently being used
|
||||
private SS14Window? _window; // Current open tabletop window (only allow one at a time)
|
||||
private IEntity? _table; // The table entity of the currently open game session
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
CommandBinds.Builder
|
||||
.Bind(EngineKeyFunctions.Use, new PointerInputCmdHandler(OnUse, false))
|
||||
.Register<TabletopSystem>();
|
||||
|
||||
SubscribeNetworkEvent<TabletopPlayEvent>(OnTabletopPlay);
|
||||
SubscribeLocalEvent<TabletopDraggableComponent, ComponentHandleState>(HandleComponentState);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
// If there is no player entity, return
|
||||
if (_playerManager.LocalPlayer is not { ControlledEntity: { } playerEntity }) return;
|
||||
|
||||
if (StunnedOrNoHands(playerEntity))
|
||||
{
|
||||
StopDragging();
|
||||
}
|
||||
|
||||
if (!CanSeeTable(playerEntity, _table))
|
||||
{
|
||||
StopDragging();
|
||||
_window?.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
// If no entity is being dragged or no viewport is clicked, return
|
||||
if (_draggedEntity == null || _viewport == null) return;
|
||||
|
||||
// Make sure the dragged entity has a draggable component
|
||||
if (!_draggedEntity.TryGetComponent<TabletopDraggableComponent>(out var draggableComponent)) return;
|
||||
|
||||
// If the dragged entity has another dragging player, drop the item
|
||||
// This should happen if the local player is dragging an item, and another player grabs it out of their hand
|
||||
if (draggableComponent.DraggingPlayer != null &&
|
||||
draggableComponent.DraggingPlayer != _playerManager.LocalPlayer?.Session.UserId)
|
||||
{
|
||||
StopDragging(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Map mouse position to EntityCoordinates
|
||||
var coords = _viewport.ScreenToMap(_inputManager.MouseScreenPosition.Position);
|
||||
|
||||
// Clamp coordinates to viewport
|
||||
var clampedCoords = ClampPositionToViewport(coords, _viewport);
|
||||
if (clampedCoords.Equals(MapCoordinates.Nullspace)) return;
|
||||
|
||||
// Move the entity locally every update
|
||||
_draggedEntity.Transform.WorldPosition = clampedCoords.Position;
|
||||
|
||||
// Increment total time passed
|
||||
_timePassed += frameTime;
|
||||
|
||||
// Only send new position to server when Delay is reached
|
||||
if (_timePassed >= Delay && _table != null)
|
||||
{
|
||||
RaiseNetworkEvent(new TabletopMoveEvent(_draggedEntity.Uid, clampedCoords, _table.Uid));
|
||||
_timePassed -= Delay;
|
||||
}
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
/// <summary>
|
||||
/// Runs when the player presses the "Play Game" verb on a tabletop game.
|
||||
/// Opens a viewport where they can then play the game.
|
||||
/// </summary>
|
||||
private void OnTabletopPlay(TabletopPlayEvent msg)
|
||||
{
|
||||
// Close the currently opened window, if it exists
|
||||
_window?.Close();
|
||||
|
||||
_table = EntityManager.GetEntity(msg.TableUid);
|
||||
|
||||
// Get the camera entity that the server has created for us
|
||||
var camera = EntityManager.GetEntity(msg.CameraUid);
|
||||
|
||||
if (!ComponentManager.TryGetComponent<EyeComponent>(camera.Uid, out var eyeComponent))
|
||||
{
|
||||
// If there is no eye, print error and do not open any window
|
||||
Logger.Error("Camera entity does not have eye component!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a window to contain the viewport
|
||||
_window = new TabletopWindow(eyeComponent.Eye, (msg.Size.X, msg.Size.Y))
|
||||
{
|
||||
MinWidth = 500,
|
||||
MinHeight = 436,
|
||||
Title = msg.Title
|
||||
};
|
||||
|
||||
_window.OnClose += OnWindowClose;
|
||||
}
|
||||
|
||||
private void HandleComponentState(EntityUid uid, TabletopDraggableComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not TabletopDraggableComponentState state) return;
|
||||
|
||||
component.DraggingPlayer = state.DraggingPlayer;
|
||||
}
|
||||
|
||||
private void OnWindowClose()
|
||||
{
|
||||
if (_table != null)
|
||||
{
|
||||
RaiseNetworkEvent(new TabletopStopPlayingEvent(_table.Uid));
|
||||
}
|
||||
|
||||
StopDragging();
|
||||
_window = null;
|
||||
}
|
||||
|
||||
private bool OnUse(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
return args.State switch
|
||||
{
|
||||
BoundKeyState.Down => OnMouseDown(args),
|
||||
BoundKeyState.Up => OnMouseUp(args),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private bool OnMouseDown(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
// Return if no player entity
|
||||
if (_playerManager.LocalPlayer is not { ControlledEntity: { } playerEntity }) return false;
|
||||
|
||||
// Return if can not see table or stunned/no hands
|
||||
if (!CanSeeTable(playerEntity, _table) || StunnedOrNoHands(playerEntity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the entity being dragged and the viewport under the mouse
|
||||
if (!EntityManager.TryGetEntity(args.EntityUid, out var draggedEntity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure that entity can be dragged
|
||||
if (!ComponentManager.HasComponent<TabletopDraggableComponent>(draggedEntity.Uid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to get the viewport under the cursor
|
||||
if (_uiManger.MouseGetControl(args.ScreenCoordinates) as ScalingViewport is not { } viewport)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StartDragging(draggedEntity, viewport);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool OnMouseUp(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
StopDragging();
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility
|
||||
|
||||
/// <summary>
|
||||
/// Start dragging an entity in a specific viewport.
|
||||
/// </summary>
|
||||
/// <param name="draggedEntity">The entity that we start dragging.</param>
|
||||
/// <param name="viewport">The viewport in which we are dragging.</param>
|
||||
private void StartDragging(IEntity draggedEntity, ScalingViewport viewport)
|
||||
{
|
||||
RaiseNetworkEvent(new TabletopDraggingPlayerChangedEvent(draggedEntity.Uid, _playerManager.LocalPlayer?.UserId));
|
||||
|
||||
if (draggedEntity.TryGetComponent<AppearanceComponent>(out var appearance))
|
||||
{
|
||||
appearance.SetData(TabletopItemVisuals.Scale, new Vector2(1.25f, 1.25f));
|
||||
appearance.SetData(TabletopItemVisuals.DrawDepth, (int) DrawDepth.Items + 1);
|
||||
}
|
||||
|
||||
_draggedEntity = draggedEntity;
|
||||
_viewport = viewport;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop dragging the entity.
|
||||
/// </summary>
|
||||
/// <param name="broadcast">Whether to tell other clients that we stopped dragging.</param>
|
||||
private void StopDragging(bool broadcast = true)
|
||||
{
|
||||
// Set the dragging player on the component to noone
|
||||
if (broadcast && _draggedEntity != null && _draggedEntity.HasComponent<TabletopDraggableComponent>())
|
||||
{
|
||||
RaiseNetworkEvent(new TabletopDraggingPlayerChangedEvent(_draggedEntity.Uid, null));
|
||||
}
|
||||
|
||||
_draggedEntity = null;
|
||||
_viewport = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps coordinates within a viewport. ONLY WORKS FOR 90 DEGREE ROTATIONS!
|
||||
/// </summary>
|
||||
/// <param name="coordinates">The coordinates to be clamped.</param>
|
||||
/// <param name="viewport">The viewport to clamp the coordinates to.</param>
|
||||
/// <returns>Coordinates clamped to the viewport.</returns>
|
||||
private static MapCoordinates ClampPositionToViewport(MapCoordinates coordinates, ScalingViewport viewport)
|
||||
{
|
||||
if (coordinates == MapCoordinates.Nullspace) return MapCoordinates.Nullspace;
|
||||
|
||||
var eye = viewport.Eye;
|
||||
if (eye == null) return MapCoordinates.Nullspace;
|
||||
|
||||
var size = (Vector2) viewport.ViewportSize / EyeManager.PixelsPerMeter; // Convert to tiles instead of pixels
|
||||
var eyePosition = eye.Position.Position;
|
||||
var eyeRotation = eye.Rotation;
|
||||
var eyeScale = eye.Scale;
|
||||
|
||||
var min = (eyePosition - size / 2) / eyeScale;
|
||||
var max = (eyePosition + size / 2) / eyeScale;
|
||||
|
||||
// If 90/270 degrees rotated, flip X and Y
|
||||
if (MathHelper.CloseTo(eyeRotation.Degrees % 180d, 90d) || MathHelper.CloseTo(eyeRotation.Degrees % 180d, -90d))
|
||||
{
|
||||
(min.Y, min.X) = (min.X, min.Y);
|
||||
(max.Y, max.X) = (max.X, max.Y);
|
||||
}
|
||||
|
||||
var clampedPosition = Vector2.Clamp(coordinates.Position, min, max);
|
||||
|
||||
// Use the eye's map ID, we don't want anything moving to a different map!
|
||||
return new MapCoordinates(clampedPosition, eye.Position.MapId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Content.Client/Tabletop/UI/TabletopWindow.xaml
Normal file
@@ -0,0 +1,11 @@
|
||||
<SS14Window xmlns="https://spacestation14.io"
|
||||
xmlns:viewport="clr-namespace:Content.Client.Viewport">
|
||||
<viewport:ScalingViewport Name="ScalingVp" MouseFilter="Stop" RenderScaleMode="CeilInt">
|
||||
<Button Name="FlipButton"
|
||||
Text="{ Loc 'tabletop-chess-flip' }"
|
||||
MinSize="60 30"
|
||||
MaxSize="60 30"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top" />
|
||||
</viewport:ScalingViewport>
|
||||
</SS14Window>
|
||||
38
Content.Client/Tabletop/UI/TabletopWindow.xaml.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.Tabletop.UI
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public partial class TabletopWindow : SS14Window
|
||||
{
|
||||
public TabletopWindow(IEye? eye, Vector2i size)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ScalingVp.Eye = eye;
|
||||
ScalingVp.ViewportSize = size;
|
||||
|
||||
FlipButton.OnButtonUp += Flip;
|
||||
OpenCentered();
|
||||
}
|
||||
|
||||
private void Flip(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
// Flip the view 180 degrees
|
||||
if (ScalingVp.Eye is { } eye)
|
||||
{
|
||||
eye.Rotation = eye.Rotation.Opposite();
|
||||
|
||||
// Flip alignmento of the button
|
||||
FlipButton.HorizontalAlignment = FlipButton.HorizontalAlignment == HAlignment.Right
|
||||
? HAlignment.Left
|
||||
: HAlignment.Right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Content.Shared.Tabletop;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.Tabletop.Visualizers
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class TabletopItemVisualizer : AppearanceVisualizer
|
||||
{
|
||||
public override void OnChangeData(AppearanceComponent appearance)
|
||||
{
|
||||
if (!appearance.Owner.TryGetComponent<ISpriteComponent>(out var sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: maybe this can work more nicely, by maybe only having to set the item to "being dragged", and have
|
||||
// the appearance handle the rest
|
||||
if (appearance.TryGetData<Vector2>(TabletopItemVisuals.Scale, out var scale))
|
||||
{
|
||||
sprite.Scale = scale;
|
||||
}
|
||||
|
||||
if (appearance.TryGetData<int>(TabletopItemVisuals.DrawDepth, out var drawDepth))
|
||||
{
|
||||
sprite.DrawDepth = drawDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,7 @@ namespace Content.Server.Interaction
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify user has a hand, and find what object he is currently holding in his active hand
|
||||
// Verify user has a hand, and find what object they are currently holding in their active hand
|
||||
if (!user.TryGetComponent<IHandsComponent>(out var hands))
|
||||
return;
|
||||
|
||||
@@ -393,11 +393,11 @@ namespace Content.Server.Interaction
|
||||
|
||||
private bool ValidateInteractAndFace(IEntity user, EntityCoordinates coordinates)
|
||||
{
|
||||
// Verify user is on the same map as the entity he clicked on
|
||||
// Verify user is on the same map as the entity they clicked on
|
||||
if (coordinates.GetMapId(_entityManager) != user.Transform.MapID)
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"User entity named {user.Name} clicked on a map he isn't located on");
|
||||
$"User entity named {user.Name} clicked on a map they aren't located on");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -873,7 +873,7 @@ namespace Content.Server.Interaction
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify user has a hand, and find what object he is currently holding in his active hand
|
||||
// Verify user has a hand, and find what object they are currently holding in their active hand
|
||||
if (user.TryGetComponent<IHandsComponent>(out var hands))
|
||||
{
|
||||
var item = hands.GetActiveHand?.Owner;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using Content.Shared.Tabletop.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Content.Shared.Tabletop.SharedTabletopSystem;
|
||||
|
||||
namespace Content.Server.Tabletop.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedTabletopDraggableComponent))]
|
||||
public class TabletopDraggableComponent : SharedTabletopDraggableComponent
|
||||
{
|
||||
private NetUserId? _draggingPlayer;
|
||||
|
||||
// The player dragging the piece
|
||||
[ViewVariables]
|
||||
public NetUserId? DraggingPlayer
|
||||
{
|
||||
get => _draggingPlayer;
|
||||
set
|
||||
{
|
||||
_draggingPlayer = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Content.Server/Tabletop/Components/TabletopGameComponent.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.Tabletop.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A component that makes an object playable as a tabletop game.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class TabletopGameComponent : Component
|
||||
{
|
||||
public override string Name => "TabletopGame";
|
||||
|
||||
/// <summary>
|
||||
/// A verb that allows the player to start playing a tabletop game.
|
||||
/// </summary>
|
||||
[Verb]
|
||||
public class PlayVerb : Verb<TabletopGameComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, TabletopGameComponent component, VerbData data)
|
||||
{
|
||||
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
data.Text = Loc.GetString("tabletop-verb-play-game");
|
||||
data.IconTexture = "/Textures/Interface/VerbIcons/die.svg.192dpi.png";
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, TabletopGameComponent component)
|
||||
{
|
||||
EntitySystem.Get<TabletopSystem>().OpenTable(user, component.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Content.Server/Tabletop/TabletopSession.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Tabletop
|
||||
{
|
||||
/// <summary>
|
||||
/// A struct for storing data about a running tabletop game.
|
||||
/// </summary>
|
||||
public struct TabletopSession
|
||||
{
|
||||
/// <summary>
|
||||
/// The map ID associated with this tabletop game session.
|
||||
/// </summary>
|
||||
public MapId MapId;
|
||||
|
||||
/// <summary>
|
||||
/// The set of players currently playing this tabletop game.
|
||||
/// </summary>
|
||||
private readonly HashSet<IPlayerSession> _currentPlayers;
|
||||
|
||||
/// <param name="mapId">The map ID associated with this tabletop game.</param>
|
||||
public TabletopSession(MapId mapId)
|
||||
{
|
||||
MapId = mapId;
|
||||
_currentPlayers = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given player is currently playing this tabletop game.
|
||||
/// </summary>
|
||||
public bool IsPlaying(IPlayerSession playerSession)
|
||||
{
|
||||
return _currentPlayers.Contains(playerSession);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store that this player has started playing this tabletop game. If the player was already playing, nothing
|
||||
/// happens.
|
||||
/// </summary>
|
||||
public void StartPlaying(IPlayerSession playerSession)
|
||||
{
|
||||
_currentPlayers.Add(playerSession);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store that this player has stopped playing this tabletop game. If the player was not playing, nothing
|
||||
/// happens.
|
||||
/// </summary>
|
||||
public void StopPlaying(IPlayerSession playerSession)
|
||||
{
|
||||
_currentPlayers.Remove(playerSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Content.Server/Tabletop/TabletopSystem.Chess.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Tabletop
|
||||
{
|
||||
public partial class TabletopSystem
|
||||
{
|
||||
private void SetupChessBoard(MapId mapId)
|
||||
{
|
||||
var chessboard = EntityManager.SpawnEntity("ChessBoardTabletop", new MapCoordinates(-1, 0, mapId));
|
||||
chessboard.Transform.Anchored = true;
|
||||
|
||||
SpawnPieces(new MapCoordinates(-4.5f, 3.5f, mapId));
|
||||
}
|
||||
|
||||
private void SpawnPieces(MapCoordinates topLeft, float separation = 1f)
|
||||
{
|
||||
var (mapId, x, y) = topLeft;
|
||||
|
||||
// Spawn all black pieces
|
||||
SpawnPiecesRow("Black", topLeft, separation);
|
||||
SpawnPawns("Black", new MapCoordinates(x, y - separation, mapId) , separation);
|
||||
|
||||
// Spawn all white pieces
|
||||
SpawnPawns("White", new MapCoordinates(x, y - 6 * separation, mapId) , separation);
|
||||
SpawnPiecesRow("White", new MapCoordinates(x, y - 7 * separation, mapId), separation);
|
||||
|
||||
// Extra queens
|
||||
EntityManager.SpawnEntity("BlackQueen", new MapCoordinates(x + 9 * separation + 9f / 32, y - 3 * separation, mapId));
|
||||
EntityManager.SpawnEntity("WhiteQueen", new MapCoordinates(x + 9 * separation + 9f / 32, y - 4 * separation, mapId));
|
||||
}
|
||||
|
||||
// TODO: refactor to load FEN instead
|
||||
private void SpawnPiecesRow(string color, MapCoordinates left, float separation = 1f)
|
||||
{
|
||||
const string piecesRow = "rnbqkbnr";
|
||||
|
||||
var (mapId, x, y) = left;
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
switch (piecesRow[i])
|
||||
{
|
||||
case 'r':
|
||||
EntityManager.SpawnEntity(color + "Rook", new MapCoordinates(x + i * separation, y, mapId));
|
||||
break;
|
||||
case 'n':
|
||||
EntityManager.SpawnEntity(color + "Knight", new MapCoordinates(x + i * separation, y, mapId));
|
||||
break;
|
||||
case 'b':
|
||||
EntityManager.SpawnEntity(color + "Bishop", new MapCoordinates(x + i * separation, y, mapId));
|
||||
break;
|
||||
case 'q':
|
||||
EntityManager.SpawnEntity(color + "Queen", new MapCoordinates(x + i * separation, y, mapId));
|
||||
break;
|
||||
case 'k':
|
||||
EntityManager.SpawnEntity(color + "King", new MapCoordinates(x + i * separation, y, mapId));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor to load FEN instead
|
||||
private void SpawnPawns(string color, MapCoordinates left, float separation = 1f)
|
||||
{
|
||||
var (mapId, x, y) = left;
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
EntityManager.SpawnEntity(color + "Pawn", new MapCoordinates(x + i * separation, y, mapId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
195
Content.Server/Tabletop/TabletopSystem.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Tabletop.Components;
|
||||
using Content.Shared.Tabletop;
|
||||
using Content.Shared.Tabletop.Events;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||
|
||||
namespace Content.Server.Tabletop
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public partial class TabletopSystem : SharedTabletopSystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ViewSubscriberSystem _viewSubscriberSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// All tabletop games currently in progress. Sessions are associated with an entity UID, which acts as a
|
||||
/// key, such that an entity can only have one running tabletop game session.
|
||||
/// </summary>
|
||||
private readonly Dictionary<EntityUid, TabletopSession> _gameSessions = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeNetworkEvent<TabletopMoveEvent>(OnTabletopMove);
|
||||
SubscribeNetworkEvent<TabletopDraggingPlayerChangedEvent>(OnDraggingPlayerChanged);
|
||||
SubscribeNetworkEvent<TabletopStopPlayingEvent>(OnStopPlaying);
|
||||
SubscribeLocalEvent<TabletopGameComponent, ComponentShutdown>(OnGameShutdown);
|
||||
SubscribeLocalEvent<TabletopDraggableComponent, ComponentGetState>(GetCompState);
|
||||
}
|
||||
|
||||
private void GetCompState(EntityUid uid, TabletopDraggableComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new TabletopDraggableComponentState(component.DraggingPlayer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a specific user, create a table if it does not exist yet and let the user open a UI window to play it.
|
||||
/// </summary>
|
||||
/// <param name="user">The user entity for which to open the window.</param>
|
||||
/// <param name="table">The entity with which the tabletop game session will be associated.</param>
|
||||
public void OpenTable(IEntity user, IEntity table)
|
||||
{
|
||||
if (user.PlayerSession() is not { } playerSession) return;
|
||||
|
||||
// Make sure we have a session, and add the player to it
|
||||
var session = EnsureSession(table.Uid);
|
||||
session.StartPlaying(playerSession);
|
||||
|
||||
// Create a camera for the user to use
|
||||
// TODO: set correct coordinates, depending on the piece the game was started from
|
||||
IEntity camera = CreateCamera(user, new MapCoordinates(0, 0, session.MapId));
|
||||
|
||||
// Tell the client to open a viewport for the tabletop game
|
||||
// TODO: use actual title/size from prototype, for now we assume its chess
|
||||
RaiseNetworkEvent(new TabletopPlayEvent(table.Uid, camera.Uid, "Chess", (274 + 64, 274)), playerSession.ConnectedClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a session associated to this entity UID, if it does not already exist, and return it.
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity UID to ensure a session for.</param>
|
||||
/// <returns>The created/stored tabletop game session.</returns>
|
||||
private TabletopSession EnsureSession(EntityUid uid)
|
||||
{
|
||||
// We already have a session, return it
|
||||
// TODO: if tables are connected, treat them as a single entity
|
||||
if (_gameSessions.ContainsKey(uid))
|
||||
{
|
||||
return _gameSessions[uid];
|
||||
}
|
||||
|
||||
// Session does not exist for this entity yet, create a map and create a session
|
||||
var mapId = _mapManager.CreateMap();
|
||||
|
||||
// Tabletop maps do not need lighting, turn it off
|
||||
var mapComponent = _mapManager.GetMapEntity(mapId).GetComponent<IMapComponent>();
|
||||
mapComponent.LightingEnabled = false;
|
||||
mapComponent.Dirty();
|
||||
|
||||
_gameSessions.Add(uid, new TabletopSession(mapId));
|
||||
var session = _gameSessions[uid];
|
||||
|
||||
// Since this is the first time opening this session, set up the game
|
||||
// TODO: don't assume we're playing chess
|
||||
SetupChessBoard(session.MapId);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
// Move an entity which is dragged by the user, but check if they are allowed to do so and to these coordinates
|
||||
private void OnTabletopMove(TabletopMoveEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession as IPlayerSession is not { AttachedEntity: { } playerEntity } playerSession) return;
|
||||
|
||||
// Check if player is actually playing at this table
|
||||
if (!_gameSessions.TryGetValue(msg.TableUid, out var tableUid) ||
|
||||
!tableUid.IsPlaying(playerSession)) return;
|
||||
|
||||
// Return if can not see table or stunned/no hands
|
||||
if (!EntityManager.TryGetEntity(msg.TableUid, out var table)) return;
|
||||
if (!CanSeeTable(playerEntity, table) || StunnedOrNoHands(playerEntity)) return;
|
||||
|
||||
// Check if moved entity exists and has tabletop draggable component
|
||||
if (!EntityManager.TryGetEntity(msg.MovedEntityUid, out var movedEntity)) return;
|
||||
if (!ComponentManager.HasComponent<TabletopDraggableComponent>(movedEntity.Uid)) return;
|
||||
|
||||
// TODO: some permission system, disallow movement if you're not permitted to move the item
|
||||
|
||||
// Move the entity and dirty it (we use the map ID from the entity so noone can try to be funny and move the item to another map)
|
||||
var transform = ComponentManager.GetComponent<ITransformComponent>(movedEntity.Uid);
|
||||
var entityCoordinates = new EntityCoordinates(_mapManager.GetMapEntityId(transform.MapID), msg.Coordinates.Position);
|
||||
transform.Coordinates = entityCoordinates;
|
||||
movedEntity.Dirty();
|
||||
}
|
||||
|
||||
private void OnDraggingPlayerChanged(TabletopDraggingPlayerChangedEvent msg)
|
||||
{
|
||||
var draggedEntity = EntityManager.GetEntity(msg.DraggedEntityUid);
|
||||
|
||||
if (!draggedEntity.TryGetComponent<TabletopDraggableComponent>(out var draggableComponent)) return;
|
||||
|
||||
draggableComponent.DraggingPlayer = msg.DraggingPlayer;
|
||||
|
||||
if (!draggedEntity.TryGetComponent<AppearanceComponent>(out var appearance)) return;
|
||||
|
||||
if (draggableComponent.DraggingPlayer != null)
|
||||
{
|
||||
appearance.SetData(TabletopItemVisuals.Scale, new Vector2(1.25f, 1.25f));
|
||||
appearance.SetData(TabletopItemVisuals.DrawDepth, (int) DrawDepth.Items + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
appearance.SetData(TabletopItemVisuals.Scale, Vector2.One);
|
||||
appearance.SetData(TabletopItemVisuals.DrawDepth, (int) DrawDepth.Items);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStopPlaying(TabletopStopPlayingEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (_gameSessions.ContainsKey(msg.TableUid) && args.SenderSession as IPlayerSession is { } playerSession)
|
||||
{
|
||||
_gameSessions[msg.TableUid].StopPlaying(playerSession);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: needs to be refactored such that the corresponding entity on the table gets removed, instead of the whole map
|
||||
private void OnGameShutdown(EntityUid uid, TabletopGameComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (!_gameSessions.ContainsKey(uid)) return;
|
||||
|
||||
// Delete the map and remove it from the list of sessions
|
||||
_mapManager.DeleteMap(_gameSessions[uid].MapId);
|
||||
_gameSessions.Remove(uid);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility
|
||||
|
||||
/// <summary>
|
||||
/// Create a camera entity for a user to control, and add the user to the view subscribers.
|
||||
/// </summary>
|
||||
/// <param name="user">The user entity to create this camera for and add to the view subscribers.</param>
|
||||
/// <param name="coordinates">The map coordinates to spawn this camera at.</param>
|
||||
// TODO: this can probably be generalized into a "CctvSystem" or whatever
|
||||
private IEntity CreateCamera(IEntity user, MapCoordinates coordinates)
|
||||
{
|
||||
// Spawn an empty entity at the coordinates
|
||||
var camera = EntityManager.SpawnEntity(null, coordinates);
|
||||
|
||||
// Add an eye component and disable FOV
|
||||
var eyeComponent = camera.EnsureComponent<EyeComponent>();
|
||||
eyeComponent.DrawFov = false;
|
||||
|
||||
// Add the user to the view subscribers. If there is no player session, just skip this step
|
||||
if (user.PlayerSession() is { } playerSession)
|
||||
{
|
||||
_viewSubscriberSystem.AddViewSubscriber(camera.Uid, playerSession);
|
||||
}
|
||||
|
||||
return camera;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Tabletop.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows an entity to be dragged around by the mouse. The position is updated for all player while dragging.
|
||||
/// </summary>
|
||||
[NetworkedComponent]
|
||||
public abstract class SharedTabletopDraggableComponent : Component
|
||||
{
|
||||
public override string Name => "TabletopDraggable";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Tabletop.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Event to tell other clients that we are dragging this item. Necessery to handle multiple users
|
||||
/// trying to move a single item at the same time.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class TabletopDraggingPlayerChangedEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The UID of the entity being dragged.
|
||||
/// </summary>
|
||||
public EntityUid DraggedEntityUid;
|
||||
|
||||
/// <summary>
|
||||
/// The NetUserID of the player that is now dragging the item.
|
||||
/// </summary>
|
||||
public NetUserId? DraggingPlayer;
|
||||
|
||||
public TabletopDraggingPlayerChangedEvent(EntityUid draggedEntityUid, NetUserId? draggingPlayer)
|
||||
{
|
||||
DraggedEntityUid = draggedEntityUid;
|
||||
DraggingPlayer = draggingPlayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Content.Shared/Tabletop/Events/TabletopMoveEvent.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Content.Shared.Tabletop.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Tabletop.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// An event that is sent to the server every so often by the client to tell where an entity with a
|
||||
/// <see cref="SharedTabletopDraggableComponent"/> has been moved.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class TabletopMoveEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The UID of the entity being moved.
|
||||
/// </summary>
|
||||
public EntityUid MovedEntityUid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The new coordinates of the entity being moved.
|
||||
/// </summary>
|
||||
public MapCoordinates Coordinates { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The UID of the table the entity is being moved on.
|
||||
/// </summary>
|
||||
public EntityUid TableUid { get; }
|
||||
|
||||
public TabletopMoveEvent(EntityUid movedEntityUid, MapCoordinates coordinates, EntityUid tableUid)
|
||||
{
|
||||
MovedEntityUid = movedEntityUid;
|
||||
Coordinates = coordinates;
|
||||
TableUid = tableUid;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Content.Shared/Tabletop/Events/TabletopPlayEvent.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Tabletop.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// An event sent by the server to the client to tell the client to open a tabletop game window.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class TabletopPlayEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid TableUid;
|
||||
public EntityUid CameraUid;
|
||||
public string Title;
|
||||
public Vector2i Size;
|
||||
|
||||
public TabletopPlayEvent(EntityUid tableUid, EntityUid cameraUid, string title, Vector2i size)
|
||||
{
|
||||
TableUid = tableUid;
|
||||
CameraUid = cameraUid;
|
||||
Title = title;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Content.Shared/Tabletop/Events/TabletopStopPlayingEvent.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Tabletop.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// An event ot tell the server that we have stopped playing this tabletop game.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class TabletopStopPlayingEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity UID of the table associated with this tabletop game.
|
||||
/// </summary>
|
||||
public EntityUid TableUid;
|
||||
|
||||
public TabletopStopPlayingEvent(EntityUid tableUid)
|
||||
{
|
||||
TableUid = tableUid;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Content.Shared/Tabletop/SharedTabletopSystem.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction.Helpers;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Stunnable;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Tabletop
|
||||
{
|
||||
public abstract class SharedTabletopSystem : EntitySystem
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class TabletopDraggableComponentState : ComponentState
|
||||
{
|
||||
public NetUserId? DraggingPlayer;
|
||||
|
||||
public TabletopDraggableComponentState(NetUserId? draggingPlayer)
|
||||
{
|
||||
DraggingPlayer = draggingPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
#region Utility
|
||||
|
||||
/// <summary>
|
||||
/// Whether the table exists, is in range and the player is alive.
|
||||
/// </summary>
|
||||
/// <param name="playerEntity">The player entity to check.</param>
|
||||
/// <param name="table">The table entity to check.</param>
|
||||
protected static bool CanSeeTable(IEntity playerEntity, IEntity? table)
|
||||
{
|
||||
if (table?.Transform.Parent?.Owner is not { } parent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parent.HasComponent<MapComponent>() && !parent.HasComponent<IMapGridComponent>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var alive = playerEntity.TryGetComponent<SharedMobStateComponent>(out var mob) && mob.IsAlive();
|
||||
var inRange = playerEntity.InRangeUnobstructed(table);
|
||||
|
||||
return alive && inRange;
|
||||
}
|
||||
|
||||
protected static bool StunnedOrNoHands(IEntity playerEntity)
|
||||
{
|
||||
var stunned = playerEntity.TryGetComponent<SharedStunnableComponent>(out var stun) &&
|
||||
stun.Stunned;
|
||||
var hasHand = playerEntity.TryGetComponent<SharedHandsComponent>(out var handsComponent) &&
|
||||
handsComponent.Hands.Count > 0;
|
||||
|
||||
return stunned || !hasHand;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
12
Content.Shared/Tabletop/TabletopItemVisuals.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Tabletop
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum TabletopItemVisuals : byte
|
||||
{
|
||||
Scale,
|
||||
DrawDepth
|
||||
}
|
||||
}
|
||||
5
Resources/Locale/en-US/tabletop/tabletop.ftl
Normal file
@@ -0,0 +1,5 @@
|
||||
## TabletopGameComponent
|
||||
tabletop-verb-play-game = Play Game
|
||||
|
||||
## Chess
|
||||
tabletop-chess-flip = Flip
|
||||
147
Resources/Prototypes/Entities/Objects/Fun/Tabletop/chess.yml
Normal file
@@ -0,0 +1,147 @@
|
||||
# Chessboard item (normal in game item you can hold in your hand)
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
id: ChessBoard
|
||||
name: chessboard
|
||||
description: A chessboard. Pieces included!
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chessboard.rsi
|
||||
state: chessboard
|
||||
- type: TabletopGame
|
||||
|
||||
# Chessboard tabletop item (item only visible in tabletop game)
|
||||
- type: entity
|
||||
id: ChessBoardTabletop
|
||||
name: chessboard
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chessboard_tabletop.rsi
|
||||
state: chessboard_tabletop
|
||||
noRot: false
|
||||
drawdepth: FloorTiles
|
||||
|
||||
## Chess pieces
|
||||
- type: entity
|
||||
id: BaseChessPiece
|
||||
parent: BaseItem
|
||||
abstract: true
|
||||
components:
|
||||
- type: TabletopDraggable
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
noRot: true
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: TabletopItemVisualizer
|
||||
|
||||
# White pieces
|
||||
- type: entity
|
||||
id: WhiteKing
|
||||
name: white king
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: w_king
|
||||
|
||||
- type: entity
|
||||
id: WhiteQueen
|
||||
name: white queen
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: w_queen
|
||||
|
||||
- type: entity
|
||||
id: WhiteRook
|
||||
name: white rook
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: w_rook
|
||||
|
||||
- type: entity
|
||||
id: WhiteBishop
|
||||
name: white bishop
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: w_bishop
|
||||
|
||||
- type: entity
|
||||
id: WhiteKnight
|
||||
name: white knight
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: w_knight
|
||||
|
||||
- type: entity
|
||||
id: WhitePawn
|
||||
name: white pawn
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: w_pawn
|
||||
|
||||
# Black pieces
|
||||
- type: entity
|
||||
id: BlackKing
|
||||
name: black king
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: b_king
|
||||
|
||||
- type: entity
|
||||
id: BlackQueen
|
||||
name: black queen
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: b_queen
|
||||
|
||||
- type: entity
|
||||
id: BlackRook
|
||||
name: black rook
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: b_rook
|
||||
|
||||
- type: entity
|
||||
id: BlackBishop
|
||||
name: black bishop
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: b_bishop
|
||||
|
||||
- type: entity
|
||||
id: BlackKnight
|
||||
name: black knight
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: b_knight
|
||||
|
||||
- type: entity
|
||||
id: BlackPawn
|
||||
name: black pawn
|
||||
parent: BaseChessPiece
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/Tabletop/chess_pieces.rsi
|
||||
state: b_pawn
|
||||
79
Resources/Textures/Interface/VerbIcons/die.svg
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 8.4666665 8.4666665"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="die.svg"
|
||||
inkscape:export-filename="C:\Users\vince\Documents\GitKraken\space-wizards\space-station-14\Resources\Textures\Interface\VerbIcons\die.svg.192dpi.png"
|
||||
inkscape:export-xdpi="192"
|
||||
inkscape:export-ydpi="192">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.313709"
|
||||
inkscape:cx="11.175023"
|
||||
inkscape:cy="18.632593"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
objecttolerance="20"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
scale-x="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid5090"
|
||||
originx="0"
|
||||
originy="-3.4933333e-006" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-288.53334)">
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3.90235829;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="M 8 4 C 5.7840003 4 4 5.7840252 4 8 L 4 24 C 4 26.216013 5.7840003 28 8 28 L 24 28 C 26.216 28 28 26.216013 28 24 L 28 8 C 28 5.7840252 26.216 4 24 4 L 8 4 z M 10.5 8 A 2.4999999 2.4999999 0 0 1 13 10.5 A 2.4999999 2.4999999 0 0 1 10.5 13 A 2.4999999 2.4999999 0 0 1 8 10.5 A 2.4999999 2.4999999 0 0 1 10.5 8 z M 21.5 8 A 2.4999999 2.4999999 0 0 1 24 10.5 A 2.4999999 2.4999999 0 0 1 21.5 13 A 2.4999999 2.4999999 0 0 1 19 10.5 A 2.4999999 2.4999999 0 0 1 21.5 8 z M 16 13.5 A 2.4999999 2.4999999 0 0 1 18.5 16 A 2.4999999 2.4999999 0 0 1 16 18.5 A 2.4999999 2.4999999 0 0 1 13.5 16 A 2.4999999 2.4999999 0 0 1 16 13.5 z M 10.5 19 A 2.4999999 2.4999999 0 0 1 13 21.5 A 2.4999999 2.4999999 0 0 1 10.5 24 A 2.4999999 2.4999999 0 0 1 8 21.5 A 2.4999999 2.4999999 0 0 1 10.5 19 z M 21.5 19 A 2.4999999 2.4999999 0 0 1 24 21.5 A 2.4999999 2.4999999 0 0 1 21.5 24 A 2.4999999 2.4999999 0 0 1 19 21.5 A 2.4999999 2.4999999 0 0 1 21.5 19 z "
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,288.53334)"
|
||||
id="rect5088" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
BIN
Resources/Textures/Interface/VerbIcons/die.svg.192dpi.png
Normal file
|
After Width: | Height: | Size: 755 B |
@@ -0,0 +1,2 @@
|
||||
sample:
|
||||
filter: true
|
||||
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 435 B |
|
After Width: | Height: | Size: 466 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 426 B |
|
After Width: | Height: | Size: 405 B |
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/3edffc96061f135b836bc353ee29ad9ab220fa54",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "w_pawn"
|
||||
},
|
||||
{
|
||||
"name": "w_rook"
|
||||
},
|
||||
{
|
||||
"name": "w_knight"
|
||||
},
|
||||
{
|
||||
"name": "w_bishop"
|
||||
},
|
||||
{
|
||||
"name": "w_king"
|
||||
},
|
||||
{
|
||||
"name": "w_queen"
|
||||
},
|
||||
{
|
||||
"name": "b_pawn"
|
||||
},
|
||||
{
|
||||
"name": "b_rook"
|
||||
},
|
||||
{
|
||||
"name": "b_knight"
|
||||
},
|
||||
{
|
||||
"name": "b_bishop"
|
||||
},
|
||||
{
|
||||
"name": "b_king"
|
||||
},
|
||||
{
|
||||
"name": "b_queen"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 435 B |
|
After Width: | Height: | Size: 466 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 426 B |
|
After Width: | Height: | Size: 405 B |
|
After Width: | Height: | Size: 285 B |
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Visne",
|
||||
"size": {
|
||||
"x": 18,
|
||||
"y": 18
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "chessboard"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Stanbery Trask#5343, Visne",
|
||||
"size": {
|
||||
"x": 274,
|
||||
"y": 274
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "chessboard_tabletop"
|
||||
}
|
||||
]
|
||||
}
|
||||