Merge branch 'master-upstream' into expl_int_analyzer

This commit is contained in:
Paul
2021-01-23 19:09:18 +01:00
2256 changed files with 23129 additions and 16027 deletions

7
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
contact_links:
- name: Report a Security Exploit
url: https://discord.gg/MwDDf6t
about: Please report serious security exploits and vulnerabilities to @PJB3005. (PJB#3482 on discord).
- name: Request a Feature
url: https://hackmd.io/@ss14/docs
about: Submit feature requests on our [discord server](https://discord.gg/ZBFGnb3) or checkout our design documentation on HackMD.

19
.github/ISSUE_TEMPLATE/issue_report.md vendored Normal file
View File

@@ -0,0 +1,19 @@
---
name: Report an Issue
about: "..."
title: ''
labels: ''
assignees: ''
---
<!-- To automatically tag this issue, add the uppercase label(s) surrounded by brackets below, for example: [LABEL] -->
## Description
<!-- Explain your issue in detail, including the steps to reproduce it if applicable. Issues without proper explanation are liable to be closed by maintainers.-->
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem. -->
**Additional context**
<!-- Add any other context about the problem here. -->

8
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,8 @@
<!-- The text between the arrows are comments - they will not be visible on your PR. -->
<!-- To automatically tag this PR, add the uppercase label(s) surrounded by brackets below, for example: [LABEL] -->
## About the PR <!-- Describe the Pull Request here. What does it change? What other things could this impact? -->
**Screenshots**
<!-- If applicable, add screenshots to showcase your PR. If your PR is a visual change, add
screenshots or it's liable to be closed by maintainers. -->

47
.github/keylabeler.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
# Determines if we search the title (optional). Defaults to true.
matchTitle: false
# Determines if we search the body (optional). Defaults to true.
matchBody: true
# Determines if label matching is case sensitive (optional). Defaults to true.
caseSensitive: false
# Explicit keyword mappings to labels. Form of match:label. Required.
labelMappings:
"[ATMOS]": "Feature: Atmospherics"
"[AUDIO]": "Feature: Audio"
"[CONSTRUCTION]": "Feature: Construction"
"[ENTITIES]": "Feature: Entities"
"[ENTITY]": "Feature: Entities"
"[ENTITY AI]": "Feature: Entity AI"
"[EVENTS]": "Feature: Events"
"[EVENT]": "Feature: Events"
"[INTERACTION]": "Feature: Interaction"
"[MEDICAL]": "Feature: Medical"
"[PHYSICS]": "Feature: Physics"
"[POWER]": "Feature: Power"
"[SPRITES]": "Feature: Sprites"
"[SPRITE]": "Feature: Sprites"
"[UI]": "Feature: UI"
"[BUG]": "Type: Bug"
"[CLEANUP]": "Type: Cleanup"
"[CLEAN]": "Type: Cleanup"
"[CLEANLINESS]": "Type: Cleanup"
"[DISCUSSION]": "Type: Discussion"
"[DISCUSS]": "Type: Discussion"
"[FEATURE]": "Type: Feature"
"[FEAT]": "Type: Feature"
"[IMPROVEMENT]": "Type: Improvement"
"[IMPROVE]": "Type: Improvement"
"[PERFORMANCE]": "Type: Performance"
"[PERF]": "Type: Performance"
"[REFACTOR]": "Type: Refactor"
"[HELP WANTED]": "Status: Help Wanted"
"[DO NOT MERGE]": "Status: DO NOT MERGE"
"[DNM]": "Status: DO NOT MERGE"
"[RESOURCES]": "Resources (No code)"
"[NOCODE]": "Resources (No code)"

View File

@@ -458,7 +458,7 @@ namespace Content.Client.Arcade
return grid; return grid;
} }
protected override void FocusExited() protected override void KeyboardFocusExited()
{ {
if (!IsOpen) return; if (!IsOpen) return;
if(_gameOver) return; if(_gameOver) return;

View File

@@ -23,6 +23,7 @@ namespace Content.Client.Chat
public Button LocalButton { get; } public Button LocalButton { get; }
public Button OOCButton { get; } public Button OOCButton { get; }
public Button AdminButton { get; } public Button AdminButton { get; }
public Button DeadButton { get; }
/// <summary> /// <summary>
/// Default formatting string for the ClientChatConsole. /// Default formatting string for the ClientChatConsole.
@@ -103,13 +104,23 @@ namespace Content.Client.Chat
Visible = false Visible = false
}; };
DeadButton = new Button
{
Text = Loc.GetString("Dead"),
Name = "Dead",
ToggleMode = true,
Visible = false
};
AllButton.OnToggled += OnFilterToggled; AllButton.OnToggled += OnFilterToggled;
LocalButton.OnToggled += OnFilterToggled; LocalButton.OnToggled += OnFilterToggled;
OOCButton.OnToggled += OnFilterToggled; OOCButton.OnToggled += OnFilterToggled;
AdminButton.OnToggled += OnFilterToggled; AdminButton.OnToggled += OnFilterToggled;
DeadButton.OnToggled += OnFilterToggled;
hBox.AddChild(AllButton); hBox.AddChild(AllButton);
hBox.AddChild(LocalButton); hBox.AddChild(LocalButton);
hBox.AddChild(DeadButton);
hBox.AddChild(OOCButton); hBox.AddChild(OOCButton);
hBox.AddChild(AdminButton); hBox.AddChild(AdminButton);

View File

@@ -1,11 +1,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Client.Administration; using Content.Client.Administration;
using Content.Client.GameObjects.Components.Observer;
using Content.Client.Interfaces.Chat; using Content.Client.Interfaces.Chat;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Chat; using Content.Shared.Chat;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.UserInterface; using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Player;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -25,6 +27,8 @@ namespace Content.Client.Chat
{ {
internal sealed class ChatManager : IChatManager, IPostInjectInit internal sealed class ChatManager : IChatManager, IPostInjectInit
{ {
[Dependency] private IPlayerManager _playerManager = default!;
private struct SpeechBubbleData private struct SpeechBubbleData
{ {
public string Message; public string Message;
@@ -61,13 +65,14 @@ namespace Content.Client.Chat
private const char MeAlias = '@'; private const char MeAlias = '@';
private const char AdminChatAlias = ']'; private const char AdminChatAlias = ']';
private readonly List<StoredChatMessage> filteredHistory = new(); private readonly List<StoredChatMessage> _filteredHistory = new();
// Filter Button States // Filter Button States
private bool _allState; private bool _allState;
private bool _localState; private bool _localState;
private bool _oocState; private bool _oocState;
private bool _adminState; private bool _adminState;
private bool _deadState;
// Flag Enums for holding filtered channels // Flag Enums for holding filtered channels
private ChatChannel _filteredChannels; private ChatChannel _filteredChannels;
@@ -98,8 +103,8 @@ namespace Content.Client.Chat
public void Initialize() public void Initialize()
{ {
_netManager.RegisterNetMessage<MsgChatMessage>(MsgChatMessage.NAME, _onChatMessage); _netManager.RegisterNetMessage<MsgChatMessage>(MsgChatMessage.NAME, OnChatMessage);
_netManager.RegisterNetMessage<ChatMaxMsgLengthMessage>(ChatMaxMsgLengthMessage.NAME, _onMaxLengthReceived); _netManager.RegisterNetMessage<ChatMaxMsgLengthMessage>(ChatMaxMsgLengthMessage.NAME, OnMaxLengthReceived);
_speechBubbleRoot = new LayoutContainer(); _speechBubbleRoot = new LayoutContainer();
LayoutContainer.SetAnchorPreset(_speechBubbleRoot, LayoutContainer.LayoutPreset.Wide); LayoutContainer.SetAnchorPreset(_speechBubbleRoot, LayoutContainer.LayoutPreset.Wide);
@@ -152,24 +157,25 @@ namespace Content.Client.Chat
{ {
if (_currentChatBox != null) if (_currentChatBox != null)
{ {
_currentChatBox.TextSubmitted -= _onChatBoxTextSubmitted; _currentChatBox.TextSubmitted -= OnChatBoxTextSubmitted;
_currentChatBox.FilterToggled -= _onFilterButtonToggled; _currentChatBox.FilterToggled -= OnFilterButtonToggled;
} }
_currentChatBox = chatBox; _currentChatBox = chatBox;
if (_currentChatBox != null) if (_currentChatBox != null)
{ {
_currentChatBox.TextSubmitted += _onChatBoxTextSubmitted; _currentChatBox.TextSubmitted += OnChatBoxTextSubmitted;
_currentChatBox.FilterToggled += _onFilterButtonToggled; _currentChatBox.FilterToggled += OnFilterButtonToggled;
_currentChatBox.AllButton.Pressed = !_allState; _currentChatBox.AllButton.Pressed = !_allState;
_currentChatBox.LocalButton.Pressed = !_localState; _currentChatBox.LocalButton.Pressed = !_localState;
_currentChatBox.OOCButton.Pressed = !_oocState; _currentChatBox.OOCButton.Pressed = !_oocState;
_currentChatBox.AdminButton.Pressed = !_adminState; _currentChatBox.AdminButton.Pressed = !_adminState;
_currentChatBox.DeadButton.Pressed = !_deadState;
AdminStatusUpdated(); AdminStatusUpdated();
} }
RepopulateChat(filteredHistory); RepopulateChat(_filteredHistory);
} }
public void RemoveSpeechBubble(EntityUid entityUid, SpeechBubble bubble) public void RemoveSpeechBubble(EntityUid entityUid, SpeechBubble bubble)
@@ -224,7 +230,7 @@ namespace Content.Client.Chat
_currentChatBox?.AddLine(messageText, message.Channel, color); _currentChatBox?.AddLine(messageText, message.Channel, color);
} }
private void _onChatBoxTextSubmitted(ChatBox chatBox, string text) private void OnChatBoxTextSubmitted(ChatBox chatBox, string text)
{ {
DebugTools.Assert(chatBox == _currentChatBox); DebugTools.Assert(chatBox == _currentChatBox);
@@ -295,7 +301,7 @@ namespace Content.Client.Chat
} }
} }
private void _onFilterButtonToggled(ChatBox chatBox, BaseButton.ButtonToggledEventArgs e) private void OnFilterButtonToggled(ChatBox chatBox, BaseButton.ButtonToggledEventArgs e)
{ {
switch (e.Button.Name) switch (e.Button.Name)
{ {
@@ -336,6 +342,13 @@ namespace Content.Client.Chat
_filteredChannels &= ~ChatChannel.AdminChat; _filteredChannels &= ~ChatChannel.AdminChat;
break; break;
} }
case "Dead":
_deadState = !_deadState;
if (_deadState)
_filteredChannels |= ChatChannel.Dead;
else
_filteredChannels &= ~ChatChannel.Dead;
break;
case "ALL": case "ALL":
chatBox.LocalButton.Pressed ^= true; chatBox.LocalButton.Pressed ^= true;
@@ -346,7 +359,7 @@ namespace Content.Client.Chat
break; break;
} }
RepopulateChat(filteredHistory); RepopulateChat(_filteredHistory);
} }
private void RepopulateChat(IEnumerable<StoredChatMessage> filteredMessages) private void RepopulateChat(IEnumerable<StoredChatMessage> filteredMessages)
@@ -364,11 +377,11 @@ namespace Content.Client.Chat
} }
} }
private void _onChatMessage(MsgChatMessage msg) private void OnChatMessage(MsgChatMessage msg)
{ {
// Log all incoming chat to repopulate when filter is un-toggled // Log all incoming chat to repopulate when filter is un-toggled
var storedMessage = new StoredChatMessage(msg); var storedMessage = new StoredChatMessage(msg);
filteredHistory.Add(storedMessage); _filteredHistory.Add(storedMessage);
WriteChatMessage(storedMessage); WriteChatMessage(storedMessage);
// Local messages that have an entity attached get a speech bubble. // Local messages that have an entity attached get a speech bubble.
@@ -378,7 +391,13 @@ namespace Content.Client.Chat
switch (msg.Channel) switch (msg.Channel)
{ {
case ChatChannel.Local: case ChatChannel.Local:
AddSpeechBubble(msg, SpeechBubble.SpeechType.Say);
break;
case ChatChannel.Dead: case ChatChannel.Dead:
if (!_playerManager.LocalPlayer?.ControlledEntity?.HasComponent<GhostComponent>() ?? true)
break;
AddSpeechBubble(msg, SpeechBubble.SpeechType.Say); AddSpeechBubble(msg, SpeechBubble.SpeechType.Say);
break; break;
@@ -388,7 +407,7 @@ namespace Content.Client.Chat
} }
} }
private void _onMaxLengthReceived(ChatMaxMsgLengthMessage msg) private void OnMaxLengthReceived(ChatMaxMsgLengthMessage msg)
{ {
_maxMessageLength = msg.MaxMessageLength; _maxMessageLength = msg.MaxMessageLength;
} }
@@ -522,6 +541,18 @@ namespace Content.Client.Chat
if (_currentChatBox != null) if (_currentChatBox != null)
{ {
_currentChatBox.AdminButton.Visible = _adminMgr.HasFlag(AdminFlags.Admin); _currentChatBox.AdminButton.Visible = _adminMgr.HasFlag(AdminFlags.Admin);
_currentChatBox.DeadButton.Visible = _adminMgr.HasFlag(AdminFlags.Admin);
}
}
public void ToggleDeadChatButtonVisibility(bool visibility)
{
if (_currentChatBox != null)
{
// If the user is an admin and returned to body, don't set the flag as null
if (!visibility && _adminMgr.HasFlag(AdminFlags.Admin))
return;
_currentChatBox.DeadButton.Visible = visibility;
} }
} }

View File

@@ -1,7 +1,9 @@
#nullable enable #nullable enable
using System.Collections.Generic;
using Content.Client.GameObjects.Components.Construction; using Content.Client.GameObjects.Components.Construction;
using Content.Client.GameObjects.EntitySystems; using Content.Client.GameObjects.EntitySystems;
using Content.Shared.Construction; using Content.Shared.Construction;
using Robust.Client.Graphics;
using Robust.Client.Placement; using Robust.Client.Placement;
using Robust.Client.Utility; using Robust.Client.Utility;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
@@ -49,7 +51,15 @@ namespace Content.Client.Construction
{ {
base.StartHijack(manager); base.StartHijack(manager);
manager.CurrentBaseSprite = _prototype?.Icon.DirFrame0(); var frame = _prototype?.Icon.DirFrame0();
if (frame == null)
{
manager.CurrentTextures = null;
}
else
{
manager.CurrentTextures = new List<IDirectionalTextureProvider> {frame};
}
} }
} }
} }

View File

@@ -81,7 +81,6 @@ namespace Content.Client
prototypes.RegisterIgnore("seed"); // Seeds prototypes are server-only. prototypes.RegisterIgnore("seed"); // Seeds prototypes are server-only.
prototypes.RegisterIgnore("barSign"); prototypes.RegisterIgnore("barSign");
prototypes.RegisterIgnore("objective"); prototypes.RegisterIgnore("objective");
prototypes.RegisterIgnore("dataset");
ClientContentIoC.Register(); ClientContentIoC.Register();

View File

@@ -1,45 +0,0 @@
using Content.Shared.GameObjects.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
namespace Content.Client.GameObjects.Components
{
[UsedImplicitly]
public class AcceptCloningBoundUserInterface : BoundUserInterface
{
public AcceptCloningBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
private AcceptCloningWindow _window;
protected override void Open()
{
base.Open();
_window = new AcceptCloningWindow();
_window.OnClose += Close;
_window.DenyButton.OnPressed += _ => _window.Close();
_window.ConfirmButton.OnPressed += _ =>
{
SendMessage(
new SharedAcceptCloningComponent.UiButtonPressedMessage(
SharedAcceptCloningComponent.UiButton.Accept));
_window.Close();
};
_window.OpenCentered();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window?.Dispose();
}
}
}
}

View File

@@ -21,7 +21,6 @@ namespace Content.Client.GameObjects.Components.Arcade
base.Open(); base.Open();
_menu = new BlockGameMenu(this); _menu = new BlockGameMenu(this);
_menu.OnClose += () => SendMessage(new BlockGameMessages.BlockGameUserUnregisterMessage());
_menu.OnClose += Close; _menu.OnClose += Close;
_menu.OpenCentered(); _menu.OpenCentered();
} }

View File

@@ -0,0 +1,51 @@
#nullable enable
using Content.Shared.GameObjects.Components.Atmos;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Atmos
{
[UsedImplicitly]
public class GasFilterVisualizer : AppearanceVisualizer
{
private string _filterEnabledState = default!;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
var serializer = YamlObjectSerializer.NewReader(node);
serializer.DataField(ref _filterEnabledState, "filterEnabledState", "gasFilterOn");
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
if (!entity.TryGetComponent<ISpriteComponent>(out var sprite)) return;
sprite.LayerMapReserveBlank(Layer.FilterEnabled);
var filterEnabledLayer = sprite.LayerMapGet(Layer.FilterEnabled);
sprite.LayerSetState(filterEnabledLayer, _filterEnabledState);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!component.Owner.TryGetComponent<ISpriteComponent>(out var sprite)) return;
if (!component.TryGetData(FilterVisuals.VisualState, out FilterVisualState filterVisualState)) return;
var filterEnabledLayer = sprite.LayerMapGet(Layer.FilterEnabled);
sprite.LayerSetVisible(filterEnabledLayer, filterVisualState.Enabled);
}
public enum Layer : byte
{
FilterEnabled,
}
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
using Content.Shared.GameObjects.Components.Atmos; using Content.Shared.GameObjects.Components.Atmos;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
@@ -10,6 +10,7 @@ using Robust.Shared.GameObjects.Components.Renderable;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log; using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel; using YamlDotNet.RepresentationModel;
@@ -18,14 +19,18 @@ namespace Content.Client.GameObjects.Components.Atmos
[UsedImplicitly] [UsedImplicitly]
public class PipeVisualizer : AppearanceVisualizer public class PipeVisualizer : AppearanceVisualizer
{ {
private string _rsiString;
private RSI _pipeRSI; private RSI _pipeRSI;
public override void LoadData(YamlMappingNode node) public override void LoadData(YamlMappingNode node)
{ {
base.LoadData(node); base.LoadData(node);
var rsiString = node.GetNode("pipeRSI").ToString(); var serializer = YamlObjectSerializer.NewReader(node);
var rsiPath = SharedSpriteComponent.TextureRoot / rsiString; serializer.DataField(ref _rsiString, "rsiString", "Constructible/Atmos/pipe.rsi");
var rsiPath = SharedSpriteComponent.TextureRoot / _rsiString;
try try
{ {
var resourceCache = IoCManager.Resolve<IResourceCache>(); var resourceCache = IoCManager.Resolve<IResourceCache>();
@@ -62,7 +67,6 @@ namespace Content.Client.GameObjects.Components.Atmos
{ {
var stateId = "pipe"; var stateId = "pipe";
stateId += pipeVisualState.PipeDirection.PipeDirectionToPipeShape().ToString(); stateId += pipeVisualState.PipeDirection.PipeDirectionToPipeShape().ToString();
stateId += (int) pipeVisualState.ConduitLayer;
return stateId; return stateId;
} }

View File

@@ -1,9 +1,9 @@
using Content.Shared.GameObjects.Components.Atmos; using Content.Shared.GameObjects.Components.Atmos;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components; using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Utility; using Robust.Shared.Serialization;
using YamlDotNet.RepresentationModel; using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Atmos namespace Content.Client.GameObjects.Components.Atmos
@@ -16,7 +16,9 @@ namespace Content.Client.GameObjects.Components.Atmos
public override void LoadData(YamlMappingNode node) public override void LoadData(YamlMappingNode node)
{ {
base.LoadData(node); base.LoadData(node);
_pumpEnabledState = node.GetNode("pumpEnabledState").ToString();
var serializer = YamlObjectSerializer.NewReader(node);
serializer.DataField(ref _pumpEnabledState, "pumpEnabledState", "pumpPressureOn");
} }
public override void InitializeEntity(IEntity entity) public override void InitializeEntity(IEntity entity)

View File

@@ -0,0 +1,51 @@
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Content.Shared.GameObjects.Components.Atmos;
using YamlDotNet.RepresentationModel;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Client.GameObjects.Components.Atmos
{
[UsedImplicitly]
public class SiphonVisualizer : AppearanceVisualizer
{
private string _siphonOnState;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
var serializer = YamlObjectSerializer.NewReader(node);
serializer.DataField(ref _siphonOnState, "siphonOnState", "scrubOn");
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
if (!entity.TryGetComponent(out ISpriteComponent sprite)) return;
sprite.LayerMapReserveBlank(Layer.SiphonEnabled);
var layer = sprite.LayerMapGet(Layer.SiphonEnabled);
sprite.LayerSetState(layer, _siphonOnState);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite)) return;
if (!component.TryGetData(SiphonVisuals.VisualState, out SiphonVisualState siphonVisualState)) return;
var layer = sprite.LayerMapGet(Layer.SiphonEnabled);
sprite.LayerSetVisible(layer, siphonVisualState.SiphonEnabled);
}
private enum Layer : byte
{
SiphonEnabled,
}
}
}

View File

@@ -0,0 +1,51 @@
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Content.Shared.GameObjects.Components.Atmos;
using YamlDotNet.RepresentationModel;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Client.GameObjects.Components.Atmos
{
[UsedImplicitly]
public class VentVisualizer : AppearanceVisualizer
{
private string _ventOnstate;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
var serializer = YamlObjectSerializer.NewReader(node);
serializer.DataField(ref _ventOnstate, "ventOnState", "ventOn");
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
if (!entity.TryGetComponent(out ISpriteComponent sprite)) return;
sprite.LayerMapReserveBlank(Layer.VentEnabled);
var layer = sprite.LayerMapGet(Layer.VentEnabled);
sprite.LayerSetState(layer, _ventOnstate);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite)) return;
if (!component.TryGetData(VentVisuals.VisualState, out VentVisualState ventVisualState)) return;
var layer = sprite.LayerMapGet(Layer.VentEnabled);
sprite.LayerSetVisible(layer, ventVisualState.VentEnabled);
}
private enum Layer : byte
{
VentEnabled,
}
}
}

View File

@@ -1,72 +0,0 @@
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects.Components.Renderable;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using System;
using Content.Shared.GameObjects.Components.Atmos;
using YamlDotNet.RepresentationModel;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.Components.Atmos
{
[UsedImplicitly]
public class SiphonVisualizer : AppearanceVisualizer
{
private RSI _siphonRSI;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
var rsiString = node.GetNode("siphonRSI").ToString();
var rsiPath = SharedSpriteComponent.TextureRoot / rsiString;
try
{
var resourceCache = IoCManager.Resolve<IResourceCache>();
var resource = resourceCache.GetResource<RSIResource>(rsiPath);
_siphonRSI = resource.RSI;
}
catch (Exception e)
{
Logger.ErrorS("go.siphonvisualizer", "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
if (!entity.TryGetComponent(out ISpriteComponent sprite)) return;
sprite.LayerMapReserveBlank(Layer.SiphonBase);
var pipeBaseLayer = sprite.LayerMapGet(Layer.SiphonBase);
sprite.LayerSetRSI(pipeBaseLayer, _siphonRSI);
sprite.LayerSetVisible(pipeBaseLayer, true);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite)) return;
if (!component.TryGetData(SiphonVisuals.VisualState, out SiphonVisualState siphonVisualState)) return;
var siphonBaseState = "scrub";
siphonBaseState += siphonVisualState.SiphonEnabled ? "On" : "Off";
var baseSiphonLayer = sprite.LayerMapGet(Layer.SiphonBase);
sprite.LayerSetRSI(baseSiphonLayer, _siphonRSI);
sprite.LayerSetState(baseSiphonLayer, siphonBaseState);
sprite.LayerSetVisible(baseSiphonLayer, true);
}
private enum Layer : byte
{
SiphonBase,
}
}
}

View File

@@ -1,72 +0,0 @@
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects.Components.Renderable;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using System;
using Content.Shared.GameObjects.Components.Atmos;
using YamlDotNet.RepresentationModel;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.Components.Atmos
{
[UsedImplicitly]
public class VentVisualizer : AppearanceVisualizer
{
private RSI _ventRSI;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
var rsiString = node.GetNode("ventRSI").ToString();
var rsiPath = SharedSpriteComponent.TextureRoot / rsiString;
try
{
var resourceCache = IoCManager.Resolve<IResourceCache>();
var resource = resourceCache.GetResource<RSIResource>(rsiPath);
_ventRSI = resource.RSI;
}
catch (Exception e)
{
Logger.ErrorS("go.ventvisualizer", "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
if (!entity.TryGetComponent(out ISpriteComponent sprite)) return;
sprite.LayerMapReserveBlank(Layer.VentBase);
var pipeBaseLayer = sprite.LayerMapGet(Layer.VentBase);
sprite.LayerSetRSI(pipeBaseLayer, _ventRSI);
sprite.LayerSetVisible(pipeBaseLayer, true);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite)) return;
if (!component.TryGetData(VentVisuals.VisualState, out VentVisualState ventVisualState)) return;
var ventBaseState = "vent";
ventBaseState += ventVisualState.VentEnabled ? "On" : "Off";
var baseVentLayer = sprite.LayerMapGet(Layer.VentBase);
sprite.LayerSetRSI(baseVentLayer, _ventRSI);
sprite.LayerSetState(baseVentLayer, ventBaseState);
sprite.LayerSetVisible(baseVentLayer, true);
}
private enum Layer : byte
{
VentBase,
}
}
}

View File

@@ -1,6 +1,4 @@
#nullable enable #nullable enable
using Content.Client.GameObjects.Components.Disposal;
using Content.Client.GameObjects.Components.MedicalScanner;
using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -11,16 +9,14 @@ namespace Content.Client.GameObjects.Components.Body
[ComponentReference(typeof(IBody))] [ComponentReference(typeof(IBody))]
public class BodyComponent : SharedBodyComponent, IDraggable public class BodyComponent : SharedBodyComponent, IDraggable
{ {
public bool CanDrop(CanDropEventArgs eventArgs) bool IDraggable.CanStartDrag(StartDragDropEventArgs args)
{
if (eventArgs.Target.HasComponent<DisposalUnitComponent>() ||
eventArgs.Target.HasComponent<MedicalScannerComponent>() ||
eventArgs.Target.HasComponent<DisposalMailingUnitComponent>())
{ {
return true; return true;
} }
return false; public bool CanDrop(CanDropEventArgs args)
{
return true;
} }
} }
} }

View File

@@ -6,6 +6,7 @@ using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.Components.Buckle namespace Content.Client.GameObjects.Components.Buckle
{ {
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(SharedBuckleComponent))]
public class BuckleComponent : SharedBuckleComponent public class BuckleComponent : SharedBuckleComponent
{ {
private bool _buckled; private bool _buckled;

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Linq; using System.Linq;
using Content.Client.UserInterface.Stylesheets; using Content.Client.UserInterface.Stylesheets;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
@@ -378,7 +378,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster
bufferHBox.AddChild(bufferLabel); bufferHBox.AddChild(bufferLabel);
var bufferVol = new Label var bufferVol = new Label
{ {
Text = $"{state.BufferCurrentVolume}/{state.BufferMaxVolume}", Text = $"{state.BufferCurrentVolume}",
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor} StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
}; };
bufferHBox.AddChild(bufferVol); bufferHBox.AddChild(bufferVol);

View File

@@ -1,4 +1,4 @@
using Content.Shared.Chemistry; #nullable enable
using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.Components.Chemistry;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -8,22 +8,6 @@ namespace Content.Client.GameObjects.Components.Chemistry
[ComponentReference(typeof(SharedSolutionContainerComponent))] [ComponentReference(typeof(SharedSolutionContainerComponent))]
public class SolutionContainerComponent : SharedSolutionContainerComponent public class SolutionContainerComponent : SharedSolutionContainerComponent
{ {
public override bool CanAddSolution(Solution solution)
{
// TODO CLIENT
return false;
}
public override bool TryAddSolution(Solution solution, bool skipReactionCheck = false, bool skipColor = false)
{
// TODO CLIENT
return false;
}
public override bool TryRemoveReagent(string reagentId, ReagentUnit quantity)
{
// TODO CLIENT
return false;
}
} }
} }

View File

@@ -87,6 +87,8 @@ namespace Content.Client.GameObjects.Components
{ {
foreach (var layer in sprite.AllLayers) foreach (var layer in sprite.AllLayers)
{ {
if (!layer.Visible) continue;
if (layer.Texture != null) if (layer.Texture != null)
{ {
if (_clickMapManager.IsOccluding(layer.Texture, if (_clickMapManager.IsOccluding(layer.Texture,

View File

@@ -0,0 +1,56 @@
#nullable enable
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
using System.Collections.Generic;
namespace Content.Client.GameObjects.Components
{
/// <summary>
/// Spawns a set of entities on the client only, and removes them when this component is removed.
/// </summary>
[RegisterComponent]
public class ClientEntitySpawnerComponent : Component
{
public override string Name => "ClientEntitySpawner";
private List<string> _prototypes = default!;
private List<IEntity> _entity = new();
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _prototypes, "prototypes", new List<string> { "HVDummyWire" });
}
public override void Initialize()
{
base.Initialize();
SpawnEntities();
}
public override void OnRemove()
{
RemoveEntities();
base.OnRemove();
}
private void SpawnEntities()
{
foreach (var proto in _prototypes)
{
var entity = Owner.EntityManager.SpawnEntity(proto, Owner.Transform.Coordinates);
_entity.Add(entity);
}
}
private void RemoveEntities()
{
foreach (var entity in _entity)
{
Owner.EntityManager.DeleteEntity(entity);
}
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.GameObjects.Components.Disposal; using Content.Shared.GameObjects.Components.Disposal;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Disposal namespace Content.Client.GameObjects.Components.Disposal
@@ -7,5 +8,9 @@ namespace Content.Client.GameObjects.Components.Disposal
[ComponentReference(typeof(SharedDisposalMailingUnitComponent))] [ComponentReference(typeof(SharedDisposalMailingUnitComponent))]
public class DisposalMailingUnitComponent : SharedDisposalMailingUnitComponent public class DisposalMailingUnitComponent : SharedDisposalMailingUnitComponent
{ {
public override bool DragDropOn(DragDropEventArgs eventArgs)
{
return false;
}
} }
} }

View File

@@ -1,4 +1,5 @@
using Content.Shared.GameObjects.Components.Disposal; using Content.Shared.GameObjects.Components.Disposal;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Disposal namespace Content.Client.GameObjects.Components.Disposal
@@ -7,5 +8,9 @@ namespace Content.Client.GameObjects.Components.Disposal
[ComponentReference(typeof(SharedDisposalUnitComponent))] [ComponentReference(typeof(SharedDisposalUnitComponent))]
public class DisposalUnitComponent : SharedDisposalUnitComponent public class DisposalUnitComponent : SharedDisposalUnitComponent
{ {
public override bool DragDropOn(DragDropEventArgs eventArgs)
{
return false;
}
} }
} }

View File

@@ -0,0 +1,40 @@
using Content.Shared.GameObjects.Components.Explosion;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Explosion
{
[UsedImplicitly]
// ReSharper disable once InconsistentNaming
public class ClusterFlashVisualizer : AppearanceVisualizer
{
private string _state;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
if (node.TryGetNode("state", out var state))
{
_state = state.AsString();
}
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!component.Owner.TryGetComponent<ISpriteComponent>(out var sprite))
{
return;
}
if (component.TryGetData(ClusterFlashVisuals.GrenadesCounter, out int grenadesCounter))
{
sprite.LayerSetState(0, $"{_state}-{grenadesCounter}");
}
}
}
}

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Client.GameObjects.Components.Clothing; using Content.Client.GameObjects.Components.Clothing;
using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.Preferences.Appearance; using Content.Shared.Preferences.Appearance;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components; using Robust.Client.Interfaces.GameObjects.Components;
@@ -78,6 +79,46 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
return item != null && _slots.Values.Any(e => e == item); return item != null && _slots.Values.Any(e => e == item);
} }
public override float WalkSpeedModifier
{
get
{
var mod = 1f;
foreach (var slot in _slots.Values)
{
if (slot != null)
{
foreach (var modifier in slot.GetAllComponents<IMoveSpeedModifier>())
{
mod *= modifier.WalkSpeedModifier;
}
}
}
return mod;
}
}
public override float SprintSpeedModifier
{
get
{
var mod = 1f;
foreach (var slot in _slots.Values)
{
if (slot != null)
{
foreach (var modifier in slot.GetAllComponents<IMoveSpeedModifier>())
{
mod *= modifier.SprintSpeedModifier;
}
}
}
return mod;
}
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{ {
base.HandleComponentState(curState, nextState); base.HandleComponentState(curState, nextState);
@@ -117,6 +158,11 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
_slots.Remove(slot); _slots.Remove(slot);
} }
} }
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? mod))
{
mod.RefreshMovementSpeedModifiers();
}
} }
private void _setSlot(Slots slot, IEntity entity) private void _setSlot(Slots slot, IEntity entity)

View File

@@ -30,7 +30,8 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
private ItemSlotButton _hudButtonBelt; private ItemSlotButton _hudButtonBelt;
private ItemSlotButton _hudButtonBack; private ItemSlotButton _hudButtonBack;
private ItemSlotButton _hudButtonId; private ItemSlotButton _hudButtonId;
private Control _quickButtonsContainer; private Control _rightQuickButtonsContainer;
private Control _leftQuickButtonsContainer;
public HumanInventoryInterfaceController(ClientInventoryComponent owner) : base(owner) public HumanInventoryInterfaceController(ClientInventoryComponent owner) : base(owner)
{ {
@@ -69,16 +70,26 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
AddButton(out _hudButtonBelt, Slots.BELT, "belt"); AddButton(out _hudButtonBelt, Slots.BELT, "belt");
AddButton(out _hudButtonId, Slots.IDCARD, "id"); AddButton(out _hudButtonId, Slots.IDCARD, "id");
_quickButtonsContainer = new HBoxContainer _leftQuickButtonsContainer = new HBoxContainer
{ {
Children = Children =
{ {
_hudButtonId, _hudButtonId,
_hudButtonBelt,
_hudButtonBack, _hudButtonBack,
_hudButtonBelt,
},
SeparationOverride = 5
};
_rightQuickButtonsContainer = new HBoxContainer
{
Children =
{
_hudButtonPocket1, _hudButtonPocket1,
_hudButtonPocket2, _hudButtonPocket2,
} // keeps this "balanced" with the left, so the hands will appear perfectly in the center
new Control{CustomMinimumSize = (64, 64)}
},
SeparationOverride = 5
}; };
} }
@@ -161,7 +172,8 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
{ {
base.PlayerAttached(); base.PlayerAttached();
GameHud.InventoryQuickButtonContainer.AddChild(_quickButtonsContainer); GameHud.RightInventoryQuickButtonContainer.AddChild(_rightQuickButtonsContainer);
GameHud.LeftInventoryQuickButtonContainer.AddChild(_leftQuickButtonsContainer);
// Update all the buttons to make sure they check out. // Update all the buttons to make sure they check out.
@@ -183,7 +195,8 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
{ {
base.PlayerDetached(); base.PlayerDetached();
GameHud.InventoryQuickButtonContainer.RemoveChild(_quickButtonsContainer); GameHud.RightInventoryQuickButtonContainer.RemoveChild(_rightQuickButtonsContainer);
GameHud.LeftInventoryQuickButtonContainer.RemoveChild(_leftQuickButtonsContainer);
foreach (var (slot, list) in _inventoryButtons) foreach (var (slot, list) in _inventoryButtons)
{ {
@@ -197,7 +210,7 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
private class HumanInventoryWindow : SS14Window private class HumanInventoryWindow : SS14Window
{ {
private const int ButtonSize = 64; private const int ButtonSize = 64;
private const int ButtonSeparation = 2; private const int ButtonSeparation = 4;
private const int RightSeparation = 2; private const int RightSeparation = 2;
public IReadOnlyDictionary<Slots, ItemSlotButton> Buttons { get; } public IReadOnlyDictionary<Slots, ItemSlotButton> Buttons { get; }

View File

@@ -0,0 +1,22 @@
using Content.Shared.GameObjects.Components;
using Robust.Shared.GameObjects;
#nullable enable
namespace Content.Client.GameObjects.Components
{
[RegisterComponent]
public sealed class MagbootsComponent : SharedMagbootsComponent
{
public override bool On { get; set; }
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if (curState is not MagbootsComponentState compState)
return;
On = compState.On;
OnChanged();
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.GameObjects.Components.Medical; using Content.Shared.GameObjects.Components.Medical;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.MedicalScanner namespace Content.Client.GameObjects.Components.MedicalScanner
@@ -7,5 +8,9 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
[ComponentReference(typeof(SharedMedicalScannerComponent))] [ComponentReference(typeof(SharedMedicalScannerComponent))]
public class MedicalScannerComponent : SharedMedicalScannerComponent public class MedicalScannerComponent : SharedMedicalScannerComponent
{ {
public override bool DragDropOn(DragDropEventArgs eventArgs)
{
return false;
}
} }
} }

View File

@@ -24,12 +24,12 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
{ {
switch (status) switch (status)
{ {
case Off: return "scanner_off"; case Off: return "closed";
case Open: return "scanner_open"; case Open: return "open";
case Red: return "scanner_red"; case Red: return "closed";
case Death: return "scanner_death"; case Death: return "closed";
case Green: return "scanner_green"; case Green: return "occupied";
case Yellow: return "scanner_yellow"; case Yellow: return "closed";
default: default:
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus"); throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus");
} }
@@ -39,12 +39,12 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
{ {
switch (status) switch (status)
{ {
case Off: return "scanner_terminal_off"; case Off: return "off_unlit";
case Open: return "scanner_terminal_blue"; case Open: return "idle_unlit";
case Red: return "scanner_terminal_red"; case Red: return "red_unlit";
case Death: return "scanner_terminal_dead"; case Death: return "red_unlit";
case Green: return "scanner_terminal_green"; case Green: return "idle_unlit";
case Yellow: return "scanner_terminal_blue"; case Yellow: return "maint_unlit";
default: default:
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus"); throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus");
} }

View File

@@ -30,8 +30,7 @@ namespace Content.Client.GameObjects.Components.Mobs
private Vector2 _currentKick; private Vector2 _currentKick;
private float _lastKickTime; private float _lastKickTime;
[ComponentDependency] [ComponentDependency] private readonly EyeComponent? _eye = null;
private readonly EyeComponent? _eye;
// Basically I needed a way to chain this effect for the attack lunge animation. // Basically I needed a way to chain this effect for the attack lunge animation.
// Sorry! // Sorry!

View File

@@ -184,8 +184,10 @@ namespace Content.Client.GameObjects.Components.Mobs
// only do something for actual target-based actions // only do something for actual target-based actions
if (_ui?.SelectingTargetFor?.Action == null || if (_ui?.SelectingTargetFor?.Action == null ||
(_ui.SelectingTargetFor.Action.BehaviorType != BehaviorType.TargetEntity && (!_ui.SelectingTargetFor.Action.IsTargetAction)) return false;
_ui.SelectingTargetFor.Action.BehaviorType != BehaviorType.TargetPoint)) return false;
// do nothing if we know it's on cooldown
if (_ui.SelectingTargetFor.IsOnCooldown) return false;
var attempt = _ui.SelectingTargetFor.ActionAttempt(); var attempt = _ui.SelectingTargetFor.ActionAttempt();
if (attempt == null) if (attempt == null)
@@ -217,6 +219,13 @@ namespace Content.Client.GameObjects.Components.Mobs
} }
return true; return true;
} }
// we are supposed to target an entity but we didn't click it
case BehaviorType.TargetEntity when args.EntityUid == EntityUid.Invalid:
{
if (attempt.Action.DeselectWhenEntityNotClicked)
_ui.StopTargeting();
return false;
}
default: default:
_ui.StopTargeting(); _ui.StopTargeting();
return false; return false;

View File

@@ -12,12 +12,6 @@ namespace Content.Client.GameObjects.Components.Mobs
[ComponentReference(typeof(SharedStunnableComponent))] [ComponentReference(typeof(SharedStunnableComponent))]
public class StunnableComponent : SharedStunnableComponent public class StunnableComponent : SharedStunnableComponent
{ {
protected override void OnInteractHand()
{
EntitySystem.Get<AudioSystem>()
.Play("/Audio/Effects/thudswoosh.ogg", Owner, AudioHelpers.WithVariation(0.25f));
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{ {
base.HandleComponentState(curState, nextState); base.HandleComponentState(curState, nextState);

View File

@@ -1,5 +1,8 @@
using Content.Shared.GameObjects.Components.Movement; using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.Components.Movement namespace Content.Client.GameObjects.Components.Movement
{ {
@@ -7,6 +10,22 @@ namespace Content.Client.GameObjects.Components.Movement
[ComponentReference(typeof(IClimbable))] [ComponentReference(typeof(IClimbable))]
public class ClimbableComponent : SharedClimbableComponent public class ClimbableComponent : SharedClimbableComponent
{ {
public override bool CanDragDropOn(DragDropEventArgs eventArgs)
{
if (!base.CanDragDropOn(eventArgs))
return false;
var user = eventArgs.User;
var target = eventArgs.Target;
var dragged = eventArgs.Dragged;
bool Ignored(IEntity entity) => entity == target || entity == user || entity == dragged;
return user.InRangeUnobstructed(target, Range, predicate: Ignored) && user.InRangeUnobstructed(dragged, Range, predicate: Ignored);
}
public override bool DragDropOn(DragDropEventArgs eventArgs)
{
return false;
}
} }
} }

View File

@@ -4,6 +4,7 @@ using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Movement namespace Content.Client.GameObjects.Components.Movement
{ {
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(SharedClimbingComponent))]
public class ClimbingComponent : SharedClimbingComponent public class ClimbingComponent : SharedClimbingComponent
{ {
public override void HandleComponentState(ComponentState curState, ComponentState nextState) public override void HandleComponentState(ComponentState curState, ComponentState nextState)

View File

@@ -0,0 +1,66 @@
using Content.Shared.GameObjects.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Nutrition
{
[UsedImplicitly]
public class BurnStateVisualizer : AppearanceVisualizer
{
private string _burntIcon = "burnt-icon";
private string _litIcon = "lit-icon";
private string _unlitIcon = "icon";
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
if (node.TryGetNode("unlitIcon", out var unlitIcon))
{
_unlitIcon = unlitIcon.AsString();
}
if (node.TryGetNode("litIcon", out var litIcon))
{
_litIcon = litIcon.AsString();
}
if (node.TryGetNode("burntIcon", out var burntIcon))
{
_burntIcon = burntIcon.AsString();
}
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.TryGetData<SharedBurningStates>(SmokingVisuals.Smoking, out var smoking))
{
SetState(component, smoking);
}
}
private void SetState(AppearanceComponent component, SharedBurningStates burnState)
{
if (component.Owner.TryGetComponent<ISpriteComponent>(out var sprite))
{
switch (burnState)
{
case SharedBurningStates.Lit:
sprite.LayerSetState(0, _litIcon);
break;
case SharedBurningStates.Burnt:
sprite.LayerSetState(0, _burntIcon);
break;
default:
sprite.LayerSetState(0, _unlitIcon);
break;
}
}
}
}
}

View File

@@ -0,0 +1,41 @@
using Content.Client.Eui;
using Content.Shared.GameObjects.Components.Medical;
using Content.Shared.GameObjects.Components.Observer;
using JetBrains.Annotations;
namespace Content.Client.GameObjects.Components.Observer
{
[UsedImplicitly]
public class AcceptCloningEui : BaseEui
{
private readonly AcceptCloningWindow _window;
public AcceptCloningEui()
{
_window = new AcceptCloningWindow();
_window.DenyButton.OnPressed += _ =>
{
SendMessage(new AcceptCloningChoiceMessage(AcceptCloningUiButton.Deny));
_window.Close();
};
_window.AcceptButton.OnPressed += _ =>
{
SendMessage(new AcceptCloningChoiceMessage(AcceptCloningUiButton.Accept));
_window.Close();
};
}
public override void Opened()
{
_window.OpenCentered();
}
public override void Closed()
{
_window.Close();
}
}
}

View File

@@ -1,14 +1,15 @@
#nullable enable #nullable enable
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization; using Robust.Shared.Localization;
namespace Content.Client.GameObjects.Components namespace Content.Client.GameObjects.Components.Observer
{ {
public sealed class AcceptCloningWindow : SS14Window public sealed class AcceptCloningWindow : SS14Window
{ {
public readonly Button DenyButton; public readonly Button DenyButton;
public readonly Button ConfirmButton; public readonly Button AcceptButton;
public AcceptCloningWindow() public AcceptCloningWindow()
{ {
@@ -23,18 +24,25 @@ namespace Content.Client.GameObjects.Components
{ {
Children = Children =
{ {
(new Label (new Label()
{ {
Text = Loc.GetString("You are being cloned! Transfer your soul to the clone body?") Text = Loc.GetString("You are being cloned!\nTransfer your soul to the clone body?")
}), }),
new HBoxContainer new HBoxContainer
{ {
Align = BoxContainer.AlignMode.Center,
Children = Children =
{ {
(ConfirmButton = new Button (AcceptButton = new Button
{ {
Text = Loc.GetString("Yes"), Text = Loc.GetString("Yes"),
}), }),
(new Control()
{
CustomMinimumSize = (20, 0)
}),
(DenyButton = new Button (DenyButton = new Button
{ {
Text = Loc.GetString("No"), Text = Loc.GetString("No"),

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Client.Interfaces.Chat;
using Content.Client.UserInterface; using Content.Client.UserInterface;
using Content.Shared.GameObjects.Components.Observer; using Content.Shared.GameObjects.Components.Observer;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
@@ -19,6 +20,7 @@ namespace Content.Client.GameObjects.Components.Observer
[Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IComponentManager _componentManager = default!; [Dependency] private readonly IComponentManager _componentManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
public List<string> WarpNames = new(); public List<string> WarpNames = new();
public Dictionary<EntityUid,string> PlayerNames = new(); public Dictionary<EntityUid,string> PlayerNames = new();
@@ -83,6 +85,7 @@ namespace Content.Client.GameObjects.Components.Observer
_gameHud.HandsContainer.AddChild(_gui); _gameHud.HandsContainer.AddChild(_gui);
SetGhostVisibility(true); SetGhostVisibility(true);
_isAttached = true; _isAttached = true;
_chatManager.ToggleDeadChatButtonVisibility(true);
break; break;
@@ -90,6 +93,7 @@ namespace Content.Client.GameObjects.Components.Observer
_gui!.Parent?.RemoveChild(_gui); _gui!.Parent?.RemoveChild(_gui);
SetGhostVisibility(false); SetGhostVisibility(false);
_isAttached = false; _isAttached = false;
_chatManager.ToggleDeadChatButtonVisibility(false);
break; break;
} }
} }

View File

@@ -1,6 +1,7 @@
#nullable enable #nullable enable
using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Client.GameObjects.Components namespace Content.Client.GameObjects.Components
{ {
@@ -9,6 +10,8 @@ namespace Content.Client.GameObjects.Components
public class PlaceableSurfaceComponent : SharedPlaceableSurfaceComponent public class PlaceableSurfaceComponent : SharedPlaceableSurfaceComponent
{ {
private bool _isPlaceable; private bool _isPlaceable;
private bool _placeCentered;
private Vector2 _positionOffset;
public override bool IsPlaceable public override bool IsPlaceable
{ {
@@ -22,7 +25,36 @@ namespace Content.Client.GameObjects.Components
_isPlaceable = value; _isPlaceable = value;
Dirty(); }
}
public override bool PlaceCentered
{
get => _placeCentered;
set
{
if (_placeCentered == value)
{
return;
}
_placeCentered = value;
}
}
public override Vector2 PositionOffset
{
get => _positionOffset;
set
{
if (_positionOffset.EqualsApprox(value))
{
return;
}
_positionOffset = value;
} }
} }
@@ -36,6 +68,8 @@ namespace Content.Client.GameObjects.Components
} }
_isPlaceable = state.IsPlaceable; _isPlaceable = state.IsPlaceable;
_placeCentered = state.PlaceCentered;
_positionOffset = state.PositionOffset;
} }
} }
} }

View File

@@ -1,5 +1,6 @@
#nullable enable #nullable enable
using Content.Shared.GameObjects.Components.Strap; using Content.Shared.GameObjects.Components.Strap;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Strap namespace Content.Client.GameObjects.Components.Strap
@@ -8,5 +9,9 @@ namespace Content.Client.GameObjects.Components.Strap
[ComponentReference(typeof(SharedStrapComponent))] [ComponentReference(typeof(SharedStrapComponent))]
public class StrapComponent : SharedStrapComponent public class StrapComponent : SharedStrapComponent
{ {
public override bool DragDropOn(DragDropEventArgs eventArgs)
{
return false;
}
} }
} }

View File

@@ -6,14 +6,11 @@ using Content.Shared.GameObjects.Components.Suspicion;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.Graphics.Overlays; using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.Interfaces.ResourceManagement;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Players; using Robust.Shared.ViewVariables;
using Robust.Shared.Prototypes;
namespace Content.Client.GameObjects.Components.Suspicion namespace Content.Client.GameObjects.Components.Suspicion
{ {
@@ -28,6 +25,7 @@ namespace Content.Client.GameObjects.Components.Suspicion
private SuspicionGui? _gui; private SuspicionGui? _gui;
private string? _role; private string? _role;
private bool? _antagonist; private bool? _antagonist;
private bool _overlayActive;
public string? Role public string? Role
{ {
@@ -67,37 +65,8 @@ namespace Content.Client.GameObjects.Components.Suspicion
} }
} }
public HashSet<EntityUid> Allies { get; } = new(); [ViewVariables]
public List<(string name, EntityUid uid)> Allies { get; } = new();
private bool AddAlly(EntityUid ally)
{
if (!Allies.Add(ally))
{
return false;
}
if (!_overlayManager.TryGetOverlay<TraitorOverlay>(nameof(TraitorOverlay), out var overlay))
{
return false;
}
return overlay.AddAlly(ally);
}
private bool RemoveAlly(EntityUid ally)
{
if (!Allies.Remove(ally))
{
return false;
}
if (!_overlayManager.TryGetOverlay<TraitorOverlay>(nameof(TraitorOverlay), out var overlay))
{
return false;
}
return overlay.RemoveAlly(ally);
}
private void AddTraitorOverlay() private void AddTraitorOverlay()
{ {
@@ -106,12 +75,18 @@ namespace Content.Client.GameObjects.Components.Suspicion
return; return;
} }
var overlay = new TraitorOverlay(Owner, Owner.EntityManager, _resourceCache, _eyeManager); _overlayActive = true;
var overlay = new TraitorOverlay(Owner.EntityManager, _resourceCache, _eyeManager);
_overlayManager.AddOverlay(overlay); _overlayManager.AddOverlay(overlay);
} }
private void RemoveTraitorOverlay() private void RemoveTraitorOverlay()
{ {
if (!_overlayActive)
{
return;
}
_overlayManager.RemoveOverlay(nameof(TraitorOverlay)); _overlayManager.RemoveOverlay(nameof(TraitorOverlay));
} }
@@ -126,6 +101,8 @@ namespace Content.Client.GameObjects.Components.Suspicion
Role = state.Role; Role = state.Role;
Antagonist = state.Antagonist; Antagonist = state.Antagonist;
Allies.Clear();
Allies.AddRange(state.Allies);
} }
public override void HandleMessage(ComponentMessage message, IComponent? component) public override void HandleMessage(ComponentMessage message, IComponent? component)
@@ -160,36 +137,6 @@ namespace Content.Client.GameObjects.Components.Suspicion
} }
} }
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, netChannel, session);
switch (message)
{
case SuspicionAlliesMessage msg:
{
Allies.Clear();
foreach (var uid in msg.Allies)
{
AddAlly(uid);
}
break;
}
case SuspicionAllyAddedMessage msg:
{
AddAlly(msg.Ally);
break;
}
case SuspicionAllyRemovedMessage msg:
{
RemoveAlly(msg.Ally);
break;
}
}
}
public override void OnRemove() public override void OnRemove()
{ {
base.OnRemove(); base.OnRemove();

View File

@@ -1,14 +1,14 @@
using System.Collections.Generic;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Drawing;
using Robust.Client.Graphics.Overlays; using Robust.Client.Graphics.Overlays;
using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.Player;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Maths; using Robust.Shared.Maths;
@@ -18,37 +18,25 @@ namespace Content.Client.GameObjects.Components.Suspicion
{ {
private readonly IEntityManager _entityManager; private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager; private readonly IEyeManager _eyeManager;
private readonly IPlayerManager _playerManager;
public override OverlaySpace Space => OverlaySpace.ScreenSpace; public override OverlaySpace Space => OverlaySpace.ScreenSpace;
private readonly Font _font; private readonly Font _font;
private readonly IEntity _user;
private readonly HashSet<EntityUid> _allies = new();
private readonly string _traitorText = Loc.GetString("Traitor"); private readonly string _traitorText = Loc.GetString("Traitor");
public TraitorOverlay( public TraitorOverlay(
IEntity user,
IEntityManager entityManager, IEntityManager entityManager,
IResourceCache resourceCache, IResourceCache resourceCache,
IEyeManager eyeManager) IEyeManager eyeManager)
: base(nameof(TraitorOverlay)) : base(nameof(TraitorOverlay))
{ {
_playerManager = IoCManager.Resolve<IPlayerManager>();
_entityManager = entityManager; _entityManager = entityManager;
_eyeManager = eyeManager; _eyeManager = eyeManager;
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10); _font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
_user = user;
}
public bool AddAlly(EntityUid ally)
{
return _allies.Add(ally);
}
public bool RemoveAlly(EntityUid ally)
{
return _allies.Remove(ally);
} }
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
@@ -65,23 +53,29 @@ namespace Content.Client.GameObjects.Components.Suspicion
{ {
var viewport = _eyeManager.GetWorldViewport(); var viewport = _eyeManager.GetWorldViewport();
foreach (var uid in _allies) var ent = _playerManager.LocalPlayer?.ControlledEntity;
if (ent == null || ent.TryGetComponent(out SuspicionRoleComponent sus) != true)
{
return;
}
foreach (var (_, uid) in sus.Allies)
{ {
// Otherwise the entity can not exist yet // Otherwise the entity can not exist yet
if (!_entityManager.TryGetEntity(uid, out var ally)) if (!_entityManager.TryGetEntity(uid, out var ally))
{ {
return; continue;
} }
if (!ally.TryGetComponent(out IPhysicsComponent physics)) if (!ally.TryGetComponent(out IPhysicsComponent physics))
{ {
return; continue;
} }
if (!ExamineSystemShared.InRangeUnOccluded(_user.Transform.MapPosition, ally.Transform.MapPosition, 15, if (!ExamineSystemShared.InRangeUnOccluded(ent.Transform.MapPosition, ally.Transform.MapPosition, 15,
entity => entity == _user || entity == ally)) entity => entity == ent || entity == ally))
{ {
return; continue;
} }
// all entities have a TransformComponent // all entities have a TransformComponent

View File

@@ -0,0 +1,26 @@
#nullable enable
using Content.Shared.GameObjects.Components.Watercloset;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
namespace Content.Client.GameObjects.Components.Watercloset
{
public class ToiletVisualizer : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite)) return;
if (!component.TryGetData(ToiletVisuals.LidOpen, out bool lidOpen)) lidOpen = false;
if (!component.TryGetData(ToiletVisuals.SeatUp, out bool seatUp)) seatUp = false;
var state = string.Format("{0}_toilet_{1}",
lidOpen ? "open" : "closed",
seatUp ? "seat_up" : "seat_down");
sprite.LayerSetState(0, state);
}
}
}

View File

@@ -0,0 +1,9 @@
using Content.Shared.GameObjects.EntitySystems;
namespace Content.Client.GameObjects.EntitySystems.NewFolder
{
public class ChemicalReactionSystem : SharedChemicalReactionSystem
{
}
}

View File

@@ -149,6 +149,16 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
if (doAfters.Count == 0) if (doAfters.Count == 0)
return; return;
if (_eyeManager.CurrentMap != AttachedEntity.Transform.MapID)
{
Visible = false;
return;
}
else
{
Visible = true;
}
// Set position ready for 2nd+ frames. // Set position ready for 2nd+ frames.
var screenCoordinates = _eyeManager.CoordinatesToScreen(AttachedEntity.Transform.Coordinates); var screenCoordinates = _eyeManager.CoordinatesToScreen(AttachedEntity.Transform.Coordinates);
_playerPosition = new ScreenCoordinates(screenCoordinates.X / UIScale, screenCoordinates.Y / UIScale); _playerPosition = new ScreenCoordinates(screenCoordinates.X / UIScale, screenCoordinates.Y / UIScale);

View File

@@ -1,24 +1,32 @@
#nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Content.Client.State; using Content.Client.State;
using Content.Client.Utility; using Content.Client.Utility;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.EntitySystemMessages; using Content.Shared.GameObjects.EntitySystemMessages;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.GameObjects.EntitySystems; using Robust.Client.GameObjects.EntitySystems;
using Robust.Client.Graphics.Shaders; using Robust.Client.Graphics.Shaders;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.State; using Robust.Client.Interfaces.State;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Input; using Robust.Shared.Input;
using Robust.Shared.Input.Binding; using Robust.Shared.Input.Binding;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log; using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using DrawDepth = Content.Shared.GameObjects.DrawDepth;
namespace Content.Client.GameObjects.EntitySystems namespace Content.Client.GameObjects.EntitySystems
{ {
@@ -29,7 +37,9 @@ namespace Content.Client.GameObjects.EntitySystems
public class DragDropSystem : EntitySystem public class DragDropSystem : EntitySystem
{ {
[Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
// how often to recheck possible targets (prevents calling expensive // how often to recheck possible targets (prevents calling expensive
@@ -45,9 +55,11 @@ namespace Content.Client.GameObjects.EntitySystems
private const string ShaderDropTargetOutOfRange = "SelectionOutline"; private const string ShaderDropTargetOutOfRange = "SelectionOutline";
// entity performing the drag action // entity performing the drag action
private IEntity _dragger;
private IEntity? _dragger;
private readonly List<IDraggable> _draggables = new(); private readonly List<IDraggable> _draggables = new();
private IEntity _dragShadow; private IEntity? _dragShadow;
// time since mouse down over the dragged entity // time since mouse down over the dragged entity
private float _mouseDownTime; private float _mouseDownTime;
// how much time since last recheck of all possible targets // how much time since last recheck of all possible targets
@@ -58,14 +70,14 @@ namespace Content.Client.GameObjects.EntitySystems
// can ignore any events sent to this system // can ignore any events sent to this system
private bool _isReplaying; private bool _isReplaying;
private DragDropHelper<IEntity> _dragDropHelper; private DragDropHelper<IEntity> _dragDropHelper = default!;
private ShaderInstance _dropTargetInRangeShader; private ShaderInstance? _dropTargetInRangeShader;
private ShaderInstance _dropTargetOutOfRangeShader; private ShaderInstance? _dropTargetOutOfRangeShader;
private SharedInteractionSystem _interactionSystem; private SharedInteractionSystem _interactionSystem = default!;
private InputSystem _inputSystem; private InputSystem _inputSystem = default!;
private readonly List<SpriteComponent> _highlightedSprites = new(); private readonly List<ISpriteComponent> _highlightedSprites = new();
public override void Initialize() public override void Initialize()
{ {
@@ -112,7 +124,7 @@ namespace Content.Client.GameObjects.EntitySystems
private bool OnUseMouseDown(in PointerInputCmdHandler.PointerInputCmdArgs args) private bool OnUseMouseDown(in PointerInputCmdHandler.PointerInputCmdArgs args)
{ {
var dragger = args.Session.AttachedEntity; var dragger = args.Session?.AttachedEntity;
// cancel any current dragging if there is one (shouldn't be because they would've had to have lifted // cancel any current dragging if there is one (shouldn't be because they would've had to have lifted
// the mouse, canceling the drag, but just being cautious) // the mouse, canceling the drag, but just being cautious)
_dragDropHelper.EndDrag(); _dragDropHelper.EndDrag();
@@ -130,7 +142,7 @@ namespace Content.Client.GameObjects.EntitySystems
var canDrag = false; var canDrag = false;
foreach (var draggable in entity.GetAllComponents<IDraggable>()) foreach (var draggable in entity.GetAllComponents<IDraggable>())
{ {
var dragEventArgs = new StartDragDropEventArgs(args.Session.AttachedEntity, entity); var dragEventArgs = new StartDragDropEventArgs(dragger, entity);
if (draggable.CanStartDrag(dragEventArgs)) if (draggable.CanStartDrag(dragEventArgs))
{ {
_draggables.Add(draggable); _draggables.Add(draggable);
@@ -156,7 +168,6 @@ namespace Content.Client.GameObjects.EntitySystems
return false; return false;
} }
private bool OnBeginDrag() private bool OnBeginDrag()
{ {
if (_dragDropHelper.Dragged == null || _dragDropHelper.Dragged.Deleted) if (_dragDropHelper.Dragged == null || _dragDropHelper.Dragged.Deleted)
@@ -183,6 +194,7 @@ namespace Content.Client.GameObjects.EntitySystems
} }
HighlightTargets(); HighlightTargets();
EntityManager.EventBus.RaiseEvent(EventSource.Local, new OutlineToggleMessage(false));
// drag initiated // drag initiated
return true; return true;
@@ -209,6 +221,9 @@ namespace Content.Client.GameObjects.EntitySystems
var mousePos = _eyeManager.ScreenToMap(_dragDropHelper.MouseScreenPosition); var mousePos = _eyeManager.ScreenToMap(_dragDropHelper.MouseScreenPosition);
// TODO: would use MapPosition instead if it had a setter, but it has no setter. // TODO: would use MapPosition instead if it had a setter, but it has no setter.
// is that intentional, or should we add a setter for Transform.MapPosition? // is that intentional, or should we add a setter for Transform.MapPosition?
if (_dragShadow == null)
return false;
_dragShadow.Transform.WorldPosition = mousePos.Position; _dragShadow.Transform.WorldPosition = mousePos.Position;
_targetRecheckTime += frameTime; _targetRecheckTime += frameTime;
@@ -229,6 +244,7 @@ namespace Content.Client.GameObjects.EntitySystems
EntityManager.DeleteEntity(_dragShadow); EntityManager.DeleteEntity(_dragShadow);
} }
EntityManager.EventBus.RaiseEvent(EventSource.Local, new OutlineToggleMessage(true));
_dragShadow = null; _dragShadow = null;
_draggables.Clear(); _draggables.Clear();
_dragger = null; _dragger = null;
@@ -238,7 +254,7 @@ namespace Content.Client.GameObjects.EntitySystems
private bool OnUseMouseUp(in PointerInputCmdHandler.PointerInputCmdArgs args) private bool OnUseMouseUp(in PointerInputCmdHandler.PointerInputCmdArgs args)
{ {
if (!_dragDropHelper.IsDragging) if (_dragDropHelper.IsDragging == false)
{ {
// haven't started the drag yet, quick mouseup, definitely treat it as a normal click by // haven't started the drag yet, quick mouseup, definitely treat it as a normal click by
// replaying the original cmd // replaying the original cmd
@@ -251,40 +267,68 @@ namespace Content.Client.GameObjects.EntitySystems
var adjustedInputMsg = new FullInputCmdMessage(args.OriginalMessage.Tick, args.OriginalMessage.SubTick, var adjustedInputMsg = new FullInputCmdMessage(args.OriginalMessage.Tick, args.OriginalMessage.SubTick,
replayMsg.InputFunctionId, replayMsg.State, replayMsg.Coordinates, replayMsg.ScreenCoordinates, replayMsg.Uid); replayMsg.InputFunctionId, replayMsg.State, replayMsg.Coordinates, replayMsg.ScreenCoordinates, replayMsg.Uid);
_inputSystem.HandleInputCommand(savedValue.Session, EngineKeyFunctions.Use, if (savedValue.Session != null)
adjustedInputMsg, true); {
_inputSystem.HandleInputCommand(savedValue.Session, EngineKeyFunctions.Use, adjustedInputMsg, true);
}
_isReplaying = false; _isReplaying = false;
} }
_dragDropHelper.EndDrag(); _dragDropHelper.EndDrag();
return false; return false;
} }
if (_dragger == null)
{
_dragDropHelper.EndDrag();
return false;
}
// now when ending the drag, we will not replay the click because // now when ending the drag, we will not replay the click because
// by this time we've determined the input was actually a drag attempt // by this time we've determined the input was actually a drag attempt
var range = (args.Coordinates.ToMapPos(EntityManager) - _dragger.Transform.MapPosition.Position).Length + 0.01f;
// tell the server we are dropping if we are over a valid drop target in range. // tell the server we are dropping if we are over a valid drop target in range.
// We don't use args.EntityUid here because drag interactions generally should // We don't use args.EntityUid here because drag interactions generally should
// work even if there's something "on top" of the drop target // work even if there's something "on top" of the drop target
if (!_interactionSystem.InRangeUnobstructed(_dragger, if (!_interactionSystem.InRangeUnobstructed(_dragger,
args.Coordinates, ignoreInsideBlocker: true)) args.Coordinates, range, ignoreInsideBlocker: true))
{ {
_dragDropHelper.EndDrag(); _dragDropHelper.EndDrag();
return false; return false;
} }
var entities = GameScreenBase.GetEntitiesUnderPosition(_stateManager, args.Coordinates); var entities = GameScreenBase.GetEntitiesUnderPosition(_stateManager, args.Coordinates);
var outOfRange = false;
foreach (var entity in entities) foreach (var entity in entities)
{ {
// check if it's able to be dropped on by current dragged entity // check if it's able to be dropped on by current dragged entity
var dropArgs = new DragDropEventArgs(_dragger, args.Coordinates, _dragDropHelper.Dragged, entity); var dropArgs = new DragDropEventArgs(_dragger, args.Coordinates, _dragDropHelper.Dragged, entity);
var valid = true;
var anyDragDrop = false;
var dragDropOn = new List<IDragDropOn>();
foreach (var comp in entity.GetAllComponents<IDragDropOn>())
{
anyDragDrop = true;
if (!comp.CanDragDropOn(dropArgs))
{
valid = false;
dragDropOn.Add(comp);
}
}
if (!valid || !anyDragDrop) continue;
if (!dropArgs.InRangeUnobstructed(ignoreInsideBlocker: true))
{
outOfRange = true;
continue;
}
foreach (var draggable in _draggables) foreach (var draggable in _draggables)
{ {
if (!draggable.CanDrop(dropArgs)) if (!draggable.CanDrop(dropArgs)) continue;
{
continue;
}
// tell the server about the drop attempt // tell the server about the drop attempt
RaiseNetworkEvent(new DragDropMessage(args.Coordinates, _dragDropHelper.Dragged.Uid, RaiseNetworkEvent(new DragDropMessage(args.Coordinates, _dragDropHelper.Dragged.Uid,
@@ -292,11 +336,22 @@ namespace Content.Client.GameObjects.EntitySystems
draggable.Drop(dropArgs); draggable.Drop(dropArgs);
// Don't fail if it isn't handled as server may do something with it
foreach (var comp in dragDropOn)
{
if (!comp.DragDropOn(dropArgs)) continue;
}
_dragDropHelper.EndDrag(); _dragDropHelper.EndDrag();
return true; return true;
} }
} }
if (outOfRange)
{
_playerManager.LocalPlayer?.ControlledEntity?.PopupMessage(Loc.GetString("You can't reach there!"));
}
_dragDropHelper.EndDrag(); _dragDropHelper.EndDrag();
return false; return false;
} }
@@ -304,7 +359,9 @@ namespace Content.Client.GameObjects.EntitySystems
private void HighlightTargets() private void HighlightTargets()
{ {
if (_dragDropHelper.Dragged == null || if (_dragDropHelper.Dragged == null ||
_dragDropHelper.Dragged.Deleted || _dragShadow == null || _dragShadow.Deleted) _dragDropHelper.Dragged.Deleted ||
_dragShadow == null ||
_dragShadow.Deleted)
{ {
Logger.Warning("Programming error. Can't highlight drag and drop targets, not currently " + Logger.Warning("Programming error. Can't highlight drag and drop targets, not currently " +
"dragging anything or dragged entity / shadow was deleted."); "dragging anything or dragged entity / shadow was deleted.");
@@ -319,30 +376,44 @@ namespace Content.Client.GameObjects.EntitySystems
// find possible targets on screen even if not reachable // find possible targets on screen even if not reachable
// TODO: Duplicated in SpriteSystem // TODO: Duplicated in SpriteSystem
var pvsBounds = _eyeManager.GetWorldViewport().Enlarged(5); var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition).Position;
var pvsEntities = EntityManager.GetEntitiesIntersecting(_eyeManager.CurrentMap, pvsBounds, true); var bounds = new Box2(mousePos - 1.5f, mousePos + 1.5f);
var pvsEntities = EntityManager.GetEntitiesIntersecting(_eyeManager.CurrentMap, bounds, true);
foreach (var pvsEntity in pvsEntities) foreach (var pvsEntity in pvsEntities)
{ {
if (pvsEntity.TryGetComponent<SpriteComponent>(out var inRangeSprite)) if (!pvsEntity.TryGetComponent(out ISpriteComponent? inRangeSprite)) continue;
{
// can't highlight if there's no sprite or it's not visible // can't highlight if there's no sprite or it's not visible
if (inRangeSprite.Visible == false) continue; if (inRangeSprite.Visible == false) continue;
var valid = (bool?) null;
// check if it's able to be dropped on by current dragged entity // check if it's able to be dropped on by current dragged entity
var canDropArgs = new CanDropEventArgs(_dragger, _dragDropHelper.Dragged, pvsEntity); var dropArgs = new DragDropEventArgs(_dragger, pvsEntity.Transform.Coordinates, _dragDropHelper.Dragged, pvsEntity);
var anyValidDraggable = _draggables.Any(draggable => draggable.CanDrop(canDropArgs));
if (anyValidDraggable) foreach (var comp in pvsEntity.GetAllComponents<IDragDropOn>())
{ {
valid = comp.CanDragDropOn(dropArgs);
if (valid.Value)
break;
}
// Can't do anything so no highlight
if (!valid.HasValue)
continue;
// We'll do a final check given server-side does this before any dragdrop can take place.
if (valid.Value)
{
valid = dropArgs.InRangeUnobstructed(ignoreInsideBlocker: true);
}
// highlight depending on whether its in or out of range // highlight depending on whether its in or out of range
var inRange = _interactionSystem.InRangeUnobstructed(_dragger, pvsEntity); inRangeSprite.PostShader = valid.Value ? _dropTargetInRangeShader : _dropTargetOutOfRangeShader;
inRangeSprite.PostShader = inRange ? _dropTargetInRangeShader : _dropTargetOutOfRangeShader;
inRangeSprite.RenderOrder = EntityManager.CurrentTick.Value; inRangeSprite.RenderOrder = EntityManager.CurrentTick.Value;
_highlightedSprites.Add(inRangeSprite); _highlightedSprites.Add(inRangeSprite);
} }
} }
}
}
private void RemoveHighlights() private void RemoveHighlights()
{ {
@@ -351,6 +422,7 @@ namespace Content.Client.GameObjects.EntitySystems
highlightedSprite.PostShader = null; highlightedSprite.PostShader = null;
highlightedSprite.RenderOrder = 0; highlightedSprite.RenderOrder = 0;
} }
_highlightedSprites.Clear(); _highlightedSprites.Clear();
} }

View File

@@ -28,6 +28,7 @@ namespace Content.Client.GameObjects.EntitySystems
public override void Initialize() public override void Initialize()
{ {
SubscribeNetworkEvent<PlayMeleeWeaponAnimationMessage>(PlayWeaponArc); SubscribeNetworkEvent<PlayMeleeWeaponAnimationMessage>(PlayWeaponArc);
SubscribeNetworkEvent<PlayLungeAnimationMessage>(PlayLunge);
} }
public override void FrameUpdate(float frameTime) public override void FrameUpdate(float frameTime)
@@ -50,6 +51,8 @@ namespace Content.Client.GameObjects.EntitySystems
var attacker = EntityManager.GetEntity(msg.Attacker); var attacker = EntityManager.GetEntity(msg.Attacker);
if (!attacker.Deleted)
{
var lunge = attacker.EnsureComponent<MeleeLungeComponent>(); var lunge = attacker.EnsureComponent<MeleeLungeComponent>();
lunge.SetData(msg.Angle); lunge.SetData(msg.Angle);
@@ -79,10 +82,11 @@ namespace Content.Client.GameObjects.EntitySystems
}; };
sys.CreateEffect(effect); sys.CreateEffect(effect);
} }
}
foreach (var uid in msg.Hits) foreach (var uid in msg.Hits)
{ {
if (!EntityManager.TryGetEntity(uid, out var hitEntity)) if (!EntityManager.TryGetEntity(uid, out var hitEntity) || hitEntity.Deleted)
{ {
continue; continue;
} }
@@ -106,5 +110,13 @@ namespace Content.Client.GameObjects.EntitySystems
}); });
} }
} }
private void PlayLunge(PlayLungeAnimationMessage msg)
{
EntityManager
.GetEntity(msg.Source)
.EnsureComponent<MeleeLungeComponent>()
.SetData(msg.Angle);
}
} }
} }

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using Content.Client.Interfaces; using Content.Client.Interfaces;
using Content.Client.State; using Content.Client.State;
using Content.Client.UserInterface; using Content.Client.UserInterface;
using Content.Shared;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Network.NetMessages; using Content.Shared.Network.NetMessages;
using Robust.Client.Interfaces.Graphics; using Robust.Client.Interfaces.Graphics;
@@ -11,6 +10,7 @@ using Robust.Client.Interfaces.State;
using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -28,7 +28,7 @@ namespace Content.Client.GameTicking
[ViewVariables] public bool IsGameStarted { get; private set; } [ViewVariables] public bool IsGameStarted { get; private set; }
[ViewVariables] public bool DisallowedLateJoin { get; private set; } [ViewVariables] public bool DisallowedLateJoin { get; private set; }
[ViewVariables] public string ServerInfoBlob { get; private set; } [ViewVariables] public string ServerInfoBlob { get; private set; }
[ViewVariables] public DateTime StartTime { get; private set; } [ViewVariables] public TimeSpan StartTime { get; private set; }
[ViewVariables] public bool Paused { get; private set; } [ViewVariables] public bool Paused { get; private set; }
[ViewVariables] public Dictionary<NetUserId, PlayerStatus> Status { get; private set; } [ViewVariables] public Dictionary<NetUserId, PlayerStatus> Status { get; private set; }
[ViewVariables] public IReadOnlyList<string> JobsAvailable => _jobsAvailable; [ViewVariables] public IReadOnlyList<string> JobsAvailable => _jobsAvailable;

View File

@@ -169,6 +169,9 @@ namespace Content.Client
"Flammable", "Flammable",
"CreamPie", "CreamPie",
"CreamPied", "CreamPied",
"Smoking",
"Matchstick",
"Matchbox",
"BlockGameArcade", "BlockGameArcade",
"KitchenSpike", "KitchenSpike",
"Butcherable", "Butcherable",
@@ -228,7 +231,15 @@ namespace Content.Client
"BiologicalSurgeryData", "BiologicalSurgeryData",
"CargoTelepad", "CargoTelepad",
"TraitorDeathMatchRedemption", "TraitorDeathMatchRedemption",
"GlassBeaker" "GlassBeaker",
"SliceableFood",
"DamageOtherOnHit",
"DamageOnLand",
"GasFilter",
"Recyclable",
"SecretStash",
"Toilet",
"ClusterFlash"
}; };
} }
} }

View File

@@ -13,5 +13,7 @@ namespace Content.Client.Interfaces.Chat
void SetChatBox(ChatBox chatBox); void SetChatBox(ChatBox chatBox);
void RemoveSpeechBubble(EntityUid entityUid, SpeechBubble bubble); void RemoveSpeechBubble(EntityUid entityUid, SpeechBubble bubble);
void ToggleDeadChatButtonVisibility(bool visibility);
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Timing;
using static Content.Shared.GameTicking.SharedGameTicker; using static Content.Shared.GameTicking.SharedGameTicker;
namespace Content.Client.Interfaces namespace Content.Client.Interfaces
@@ -11,7 +12,7 @@ namespace Content.Client.Interfaces
string ServerInfoBlob { get; } string ServerInfoBlob { get; }
bool AreWeReady { get; } bool AreWeReady { get; }
bool DisallowedLateJoin { get; } bool DisallowedLateJoin { get; }
DateTime StartTime { get; } TimeSpan StartTime { get; }
bool Paused { get; } bool Paused { get; }
Dictionary<NetUserId, PlayerStatus> Status { get; } Dictionary<NetUserId, PlayerStatus> Status { get; }
IReadOnlyList<string> JobsAvailable { get; } IReadOnlyList<string> JobsAvailable { get; }

View File

@@ -33,10 +33,12 @@ namespace Content.Client.Sandbox
public readonly Button ShowMarkersButton; //Shows spawn points public readonly Button ShowMarkersButton; //Shows spawn points
public readonly Button ShowBbButton; //Shows bounding boxes public readonly Button ShowBbButton; //Shows bounding boxes
public readonly Button MachineLinkingButton; // Enables/disables machine linking mode. public readonly Button MachineLinkingButton; // Enables/disables machine linking mode.
private readonly IGameHud _gameHud;
public SandboxWindow() public SandboxWindow()
{ {
Resizable = false; Resizable = false;
_gameHud = IoCManager.Resolve<IGameHud>();
Title = "Sandbox Panel"; Title = "Sandbox Panel";
@@ -82,6 +84,20 @@ namespace Content.Client.Sandbox
MachineLinkingButton = new Button { Text = Loc.GetString("Link machines"), ToggleMode = true }; MachineLinkingButton = new Button { Text = Loc.GetString("Link machines"), ToggleMode = true };
vBox.AddChild(MachineLinkingButton); vBox.AddChild(MachineLinkingButton);
} }
protected override void EnteredTree()
{
base.EnteredTree();
_gameHud.SandboxButtonDown = true;
}
protected override void ExitedTree()
{
base.ExitedTree();
_gameHud.SandboxButtonDown = false;
}
} }
internal class SandboxManager : SharedSandboxManager, ISandboxManager internal class SandboxManager : SharedSandboxManager, ISandboxManager
@@ -197,7 +213,6 @@ namespace Content.Client.Sandbox
private void WindowOnOnClose() private void WindowOnOnClose()
{ {
_window = null; _window = null;
_gameHud.SandboxButtonDown = false;
_sandboxWindowToggled = false; _sandboxWindowToggled = false;
} }

View File

@@ -27,7 +27,7 @@ namespace Content.Client.State
// OH GOD. // OH GOD.
// Ok actually it's fine. // Ok actually it's fine.
// Instantiated dynamically through the StateManager, Dependencies will be resolved. // Instantiated dynamically through the StateManager, Dependencies will be resolved.
public partial class GameScreenBase : Robust.Client.State.State public partial class GameScreenBase : Robust.Client.State.State, IEntityEventSubscriber
{ {
[Dependency] protected readonly IClientEntityManager EntityManager = default!; [Dependency] protected readonly IClientEntityManager EntityManager = default!;
[Dependency] protected readonly IInputManager InputManager = default!; [Dependency] protected readonly IInputManager InputManager = default!;
@@ -38,17 +38,29 @@ namespace Content.Client.State
[Dependency] protected readonly IMapManager MapManager = default!; [Dependency] protected readonly IMapManager MapManager = default!;
[Dependency] protected readonly IUserInterfaceManager UserInterfaceManager = default!; [Dependency] protected readonly IUserInterfaceManager UserInterfaceManager = default!;
[Dependency] protected readonly IConfigurationManager ConfigurationManager = default!; [Dependency] protected readonly IConfigurationManager ConfigurationManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private IEventBus _eventBus => _entityManager.EventBus;
private IEntity _lastHoveredEntity; private IEntity _lastHoveredEntity;
private bool _outlineEnabled = true;
public override void Startup() public override void Startup()
{ {
InputManager.KeyBindStateChanged += OnKeyBindStateChanged; InputManager.KeyBindStateChanged += OnKeyBindStateChanged;
_eventBus.SubscribeEvent<OutlineToggleMessage>(EventSource.Local, this, HandleOutlineToggle);
} }
public override void Shutdown() public override void Shutdown()
{ {
InputManager.KeyBindStateChanged -= OnKeyBindStateChanged; InputManager.KeyBindStateChanged -= OnKeyBindStateChanged;
_eventBus.UnsubscribeEvent<OutlineToggleMessage>(EventSource.Local, this);
}
private void HandleOutlineToggle(OutlineToggleMessage message)
{
_outlineEnabled = message.Enabled;
} }
public override void FrameUpdate(FrameEventArgs e) public override void FrameUpdate(FrameEventArgs e)
@@ -72,7 +84,7 @@ namespace Content.Client.State
} }
InteractionOutlineComponent outline; InteractionOutlineComponent outline;
if(!ConfigurationManager.GetCVar(CCVars.OutlineEnabled)) if(!_outlineEnabled || !ConfigurationManager.GetCVar(CCVars.OutlineEnabled))
{ {
if(entityToClick != null && entityToClick.TryGetComponent(out outline)) if(entityToClick != null && entityToClick.TryGetComponent(out outline))
{ {

View File

@@ -13,6 +13,7 @@ using Robust.Client.Player;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input.Binding; using Robust.Shared.Input.Binding;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -35,6 +36,7 @@ namespace Content.Client.State
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[ViewVariables] private CharacterSetupGui _characterSetup; [ViewVariables] private CharacterSetupGui _characterSetup;
[ViewVariables] private LobbyGui _lobby; [ViewVariables] private LobbyGui _lobby;
@@ -70,6 +72,12 @@ namespace Content.Client.State
_inputManager.SetInputCommand(ContentKeyFunctions.FocusChat, _inputManager.SetInputCommand(ContentKeyFunctions.FocusChat,
InputCmdHandler.FromDelegate(s => GameScreen.FocusChat(_lobby.Chat))); InputCmdHandler.FromDelegate(s => GameScreen.FocusChat(_lobby.Chat)));
_inputManager.SetInputCommand(ContentKeyFunctions.FocusOOC,
InputCmdHandler.FromDelegate(s => GameScreen.FocusOOC(_lobby.Chat)));
_inputManager.SetInputCommand(ContentKeyFunctions.FocusAdminChat,
InputCmdHandler.FromDelegate(s => GameScreen.FocusAdminChat(_lobby.Chat)));
UpdateLobbyUi(); UpdateLobbyUi();
_lobby.CharacterPreview.CharacterSetupButton.OnPressed += args => _lobby.CharacterPreview.CharacterSetupButton.OnPressed += args =>
@@ -138,10 +146,11 @@ namespace Content.Client.State
} }
else else
{ {
var difference = _clientGameTicker.StartTime - DateTime.UtcNow; var difference = _clientGameTicker.StartTime - _gameTiming.CurTime;
if (difference.Ticks < 0) var seconds = difference.TotalSeconds;
if (seconds < 0)
{ {
if (difference.TotalSeconds < -5) if (seconds < -5)
{ {
text = Loc.GetString("Right Now?"); text = Loc.GetString("Right Now?");
} }
@@ -152,7 +161,7 @@ namespace Content.Client.State
} }
else else
{ {
text = $"{(int) Math.Floor(difference.TotalMinutes)}:{difference.Seconds:D2}"; text = $"{(int) Math.Floor(difference.TotalMinutes / 60)}:{difference.Seconds:D2}";
} }
} }
@@ -172,8 +181,10 @@ namespace Content.Client.State
_clientGameTicker.Status.Remove(p.Key); _clientGameTicker.Status.Remove(p.Key);
} }
} }
UpdatePlayerList(); UpdatePlayerList();
} }
private void LobbyReadyUpdated() => UpdatePlayerList(); private void LobbyReadyUpdated() => UpdatePlayerList();
private void LobbyStatusUpdated() private void LobbyStatusUpdated()
@@ -218,8 +229,6 @@ namespace Content.Client.State
foreach (var session in _playerManager.Sessions.OrderBy(s => s.Name)) foreach (var session in _playerManager.Sessions.OrderBy(s => s.Name))
{ {
var readyState = ""; var readyState = "";
// Don't show ready state if we're ingame // Don't show ready state if we're ingame
if (!_clientGameTicker.IsGameStarted) if (!_clientGameTicker.IsGameStarted)
@@ -238,6 +247,7 @@ namespace Content.Client.State
_ => "", _ => "",
}; };
} }
_lobby.OnlinePlayerList.AddItem(session.Name, readyState); _lobby.OnlinePlayerList.AddItem(session.Name, readyState);
} }
} }

View File

@@ -0,0 +1,14 @@
using Robust.Shared.GameObjects;
namespace Content.Client.State
{
public sealed class OutlineToggleMessage : EntitySystemMessage
{
public bool Enabled { get; }
public OutlineToggleMessage(bool enabled)
{
Enabled = enabled;
}
}
}

View File

@@ -56,6 +56,7 @@ namespace Content.Client.UserInterface
private readonly Button _clearButton; private readonly Button _clearButton;
private readonly GridContainer _resultsGrid; private readonly GridContainer _resultsGrid;
private readonly TextureRect _dragShadow; private readonly TextureRect _dragShadow;
private readonly IGameHud _gameHud;
private readonly DragDropHelper<ActionMenuItem> _dragDropHelper; private readonly DragDropHelper<ActionMenuItem> _dragDropHelper;
@@ -64,6 +65,8 @@ namespace Content.Client.UserInterface
_actionsComponent = actionsComponent; _actionsComponent = actionsComponent;
_actionsUI = actionsUI; _actionsUI = actionsUI;
_actionManager = IoCManager.Resolve<ActionManager>(); _actionManager = IoCManager.Resolve<ActionManager>();
_gameHud = IoCManager.Resolve<IGameHud>();
Title = Loc.GetString("Actions"); Title = Loc.GetString("Actions");
CustomMinimumSize = (300, 300); CustomMinimumSize = (300, 300);
@@ -143,14 +146,13 @@ namespace Content.Client.UserInterface
_dragDropHelper = new DragDropHelper<ActionMenuItem>(OnBeginActionDrag, OnContinueActionDrag, OnEndActionDrag); _dragDropHelper = new DragDropHelper<ActionMenuItem>(OnBeginActionDrag, OnContinueActionDrag, OnEndActionDrag);
} }
protected override void EnteredTree() protected override void EnteredTree()
{ {
base.EnteredTree(); base.EnteredTree();
_clearButton.OnPressed += OnClearButtonPressed; _clearButton.OnPressed += OnClearButtonPressed;
_searchBar.OnTextChanged += OnSearchTextChanged; _searchBar.OnTextChanged += OnSearchTextChanged;
_filterButton.OnItemSelected += OnFilterItemSelected; _filterButton.OnItemSelected += OnFilterItemSelected;
_gameHud.ActionsButtonDown = true;
foreach (var actionMenuControl in _resultsGrid.Children) foreach (var actionMenuControl in _resultsGrid.Children)
{ {
var actionMenuItem = (actionMenuControl as ActionMenuItem); var actionMenuItem = (actionMenuControl as ActionMenuItem);
@@ -167,7 +169,7 @@ namespace Content.Client.UserInterface
_clearButton.OnPressed -= OnClearButtonPressed; _clearButton.OnPressed -= OnClearButtonPressed;
_searchBar.OnTextChanged -= OnSearchTextChanged; _searchBar.OnTextChanged -= OnSearchTextChanged;
_filterButton.OnItemSelected -= OnFilterItemSelected; _filterButton.OnItemSelected -= OnFilterItemSelected;
_gameHud.ActionsButtonDown = false;
foreach (var actionMenuControl in _resultsGrid.Children) foreach (var actionMenuControl in _resultsGrid.Children)
{ {
var actionMenuItem = (actionMenuControl as ActionMenuItem); var actionMenuItem = (actionMenuControl as ActionMenuItem);
@@ -280,6 +282,12 @@ namespace Content.Client.UserInterface
_dragDropHelper.EndDrag(); _dragDropHelper.EndDrag();
} }
private void OnItemFocusExited(ActionMenuItem item)
{
// lost focus, cancel the drag if one is in progress
_dragDropHelper.EndDrag();
}
private void OnItemPressed(BaseButton.ButtonEventArgs args) private void OnItemPressed(BaseButton.ButtonEventArgs args)
{ {
if (args.Button is not ActionMenuItem actionMenuItem) return; if (args.Button is not ActionMenuItem actionMenuItem) return;
@@ -402,8 +410,7 @@ namespace Content.Client.UserInterface
ItemTag => action is ItemActionPrototype, ItemTag => action is ItemActionPrototype,
NotItemTag => action is ActionPrototype, NotItemTag => action is ActionPrototype,
InstantActionTag => action.BehaviorType == BehaviorType.Instant, InstantActionTag => action.BehaviorType == BehaviorType.Instant,
TargetActionTag => action.BehaviorType == BehaviorType.TargetEntity || TargetActionTag => action.IsTargetAction,
action.BehaviorType == BehaviorType.TargetPoint,
ToggleActionTag => action.BehaviorType == BehaviorType.Toggle, ToggleActionTag => action.BehaviorType == BehaviorType.Toggle,
_ => action.Filters.Contains(tag) _ => action.Filters.Contains(tag)
}; };
@@ -462,10 +469,9 @@ namespace Content.Client.UserInterface
_actionList = actions.ToArray(); _actionList = actions.ToArray();
foreach (var action in _actionList.OrderBy(act => act.Name.ToString())) foreach (var action in _actionList.OrderBy(act => act.Name.ToString()))
{ {
var actionItem = new ActionMenuItem(action); var actionItem = new ActionMenuItem(action, OnItemFocusExited);
_resultsGrid.Children.Add(actionItem); _resultsGrid.Children.Add(actionItem);
actionItem.SetActionState(_actionsComponent.IsGranted(action)); actionItem.SetActionState(_actionsComponent.IsGranted(action));
actionItem.OnButtonDown += OnItemButtonDown; actionItem.OnButtonDown += OnItemButtonDown;
actionItem.OnButtonUp += OnItemButtonUp; actionItem.OnButtonUp += OnItemButtonUp;
actionItem.OnPressed += OnItemPressed; actionItem.OnPressed += OnItemPressed;

View File

@@ -1,5 +1,7 @@
#nullable enable #nullable enable
using System;
using Content.Client.GameObjects.Components.Mobs;
using Content.Client.UserInterface.Stylesheets; using Content.Client.UserInterface.Stylesheets;
using Content.Shared.Actions; using Content.Shared.Actions;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
@@ -19,8 +21,11 @@ namespace Content.Client.UserInterface
public BaseActionPrototype Action { get; private set; } public BaseActionPrototype Action { get; private set; }
public ActionMenuItem(BaseActionPrototype action) private Action<ActionMenuItem> _onControlFocusExited;
public ActionMenuItem(BaseActionPrototype action, Action<ActionMenuItem> onControlFocusExited)
{ {
_onControlFocusExited = onControlFocusExited;
Action = action; Action = action;
CustomMinimumSize = (64, 64); CustomMinimumSize = (64, 64);
@@ -38,6 +43,12 @@ namespace Content.Client.UserInterface
TooltipSupplier = SupplyTooltip; TooltipSupplier = SupplyTooltip;
} }
protected override void ControlFocusExited()
{
base.ControlFocusExited();
_onControlFocusExited.Invoke(this);
}
private Control SupplyTooltip(Control? sender) private Control SupplyTooltip(Control? sender)
{ {
return new ActionAlertTooltip(Action.Name, Action.Description, Action.Requires); return new ActionAlertTooltip(Action.Name, Action.Description, Action.Requires);

View File

@@ -32,6 +32,7 @@ namespace Content.Client.UserInterface
private readonly ActionManager _actionManager; private readonly ActionManager _actionManager;
private readonly IEntityManager _entityManager; private readonly IEntityManager _entityManager;
private readonly IGameTiming _gameTiming; private readonly IGameTiming _gameTiming;
private readonly IGameHud _gameHud;
private readonly ActionSlot[] _slots; private readonly ActionSlot[] _slots;
@@ -80,13 +81,15 @@ namespace Content.Client.UserInterface
_actionManager = IoCManager.Resolve<ActionManager>(); _actionManager = IoCManager.Resolve<ActionManager>();
_entityManager = IoCManager.Resolve<IEntityManager>(); _entityManager = IoCManager.Resolve<IEntityManager>();
_gameTiming = IoCManager.Resolve<IGameTiming>(); _gameTiming = IoCManager.Resolve<IGameTiming>();
_gameHud = IoCManager.Resolve<IGameHud>();
_menu = new ActionMenu(_actionsComponent, this); _menu = new ActionMenu(_actionsComponent, this);
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.End); LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.End);
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.End); LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.End);
LayoutContainer.SetAnchorTop(this, 0f); LayoutContainer.SetAnchorTop(this, 0f);
LayoutContainer.SetAnchorBottom(this, 0.8f); LayoutContainer.SetAnchorBottom(this, 0.8f);
LayoutContainer.SetMarginLeft(this, 10); LayoutContainer.SetMarginLeft(this, 13);
LayoutContainer.SetMarginTop(this, 100); LayoutContainer.SetMarginTop(this, 110);
SizeFlagsHorizontal = SizeFlags.None; SizeFlagsHorizontal = SizeFlags.None;
SizeFlagsVertical = SizeFlags.FillExpand; SizeFlagsVertical = SizeFlags.FillExpand;
@@ -208,6 +211,9 @@ namespace Content.Client.UserInterface
_lockButton.OnPressed += OnLockPressed; _lockButton.OnPressed += OnLockPressed;
_settingsButton.OnPressed += OnToggleActionsMenu; _settingsButton.OnPressed += OnToggleActionsMenu;
_loadoutContainer.OnKeyBindDown += OnHotbarPaginate; _loadoutContainer.OnKeyBindDown += OnHotbarPaginate;
_gameHud.ActionsButtonToggled += OnToggleActionsMenuTopButton;
_gameHud.ActionsButtonDown = false;
_gameHud.ActionsButtonVisible = true;
} }
protected override void ExitedTree() protected override void ExitedTree()
@@ -218,6 +224,9 @@ namespace Content.Client.UserInterface
_lockButton.OnPressed -= OnLockPressed; _lockButton.OnPressed -= OnLockPressed;
_settingsButton.OnPressed -= OnToggleActionsMenu; _settingsButton.OnPressed -= OnToggleActionsMenu;
_loadoutContainer.OnKeyBindDown -= OnHotbarPaginate; _loadoutContainer.OnKeyBindDown -= OnHotbarPaginate;
_gameHud.ActionsButtonToggled -= OnToggleActionsMenuTopButton;
_gameHud.ActionsButtonDown = false;
_gameHud.ActionsButtonVisible = false;
} }
protected override Vector2 CalculateMinimumSize() protected override Vector2 CalculateMinimumSize()
@@ -328,9 +337,9 @@ namespace Content.Client.UserInterface
actionSlot.EnableAction(); actionSlot.EnableAction();
actionSlot.Cooldown = actionState.Cooldown; actionSlot.Cooldown = actionState.Cooldown;
// if we are targeting with an action now on cooldown, stop targeting // if we are targeting for this action and it's now on cooldown, stop targeting if we're supposed to
if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action && if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action &&
actionState.IsOnCooldown(_gameTiming)) actionState.IsOnCooldown(_gameTiming) && action.DeselectOnCooldown)
{ {
StopTargeting(); StopTargeting();
} }
@@ -401,10 +410,10 @@ namespace Content.Client.UserInterface
// action is currently granted // action is currently granted
actionSlot.EnableAction(); actionSlot.EnableAction();
// if we are targeting with an action now on cooldown, stop targeting // if we are targeting with an action now on cooldown, stop targeting if we should
if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action && if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action &&
SelectingTargetFor.Item == itemEntity && SelectingTargetFor.Item == itemEntity &&
actionState.IsOnCooldown(_gameTiming)) actionState.IsOnCooldown(_gameTiming) && action.DeselectOnCooldown)
{ {
StopTargeting(); StopTargeting();
} }
@@ -496,6 +505,13 @@ namespace Content.Client.UserInterface
ToggleActionsMenu(); ToggleActionsMenu();
} }
private void OnToggleActionsMenuTopButton(bool open)
{
if (open == _menu.IsOpen) return;
ToggleActionsMenu();
}
public void ToggleActionsMenu() public void ToggleActionsMenu()
{ {
if (_menu.IsOpen) if (_menu.IsOpen)

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Client.Administration;
using Content.Shared.Input; using Content.Shared.Input;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Client.Interfaces.Input; using Robust.Client.Interfaces.Input;
@@ -13,6 +14,8 @@ namespace Content.Client.UserInterface.AdminMenu
{ {
[Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly IClientAdminManager _clientAdminManager = default!;
[Dependency] private readonly IClientConGroupController _clientConGroupController = default!; [Dependency] private readonly IClientConGroupController _clientConGroupController = default!;
private SS14Window _window; private SS14Window _window;
@@ -26,6 +29,30 @@ namespace Content.Client.UserInterface.AdminMenu
_inputManager.SetInputCommand(ContentKeyFunctions.OpenAdminMenu, _inputManager.SetInputCommand(ContentKeyFunctions.OpenAdminMenu,
InputCmdHandler.FromDelegate(session => Toggle())); InputCmdHandler.FromDelegate(session => Toggle()));
_clientAdminManager.AdminStatusUpdated += () =>
{
// when status changes, show the top button if we can open admin menu.
// if we can't or we lost admin status, close it and hide the button.
_gameHud.AdminButtonVisible = CanOpen();
if (!_gameHud.AdminButtonVisible)
{
Close();
}
};
_gameHud.AdminButtonToggled += (open) =>
{
if (open)
{
TryOpen();
}
else
{
Close();
}
};
_gameHud.AdminButtonVisible = CanOpen();
_gameHud.AdminButtonDown = false;
} }
public void ResetWindow() public void ResetWindow()

View File

@@ -1,10 +1,11 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Client.GameObjects.EntitySystems; using Content.Client.GameObjects.EntitySystems;
using Content.Client.StationEvents; using Content.Client.StationEvents;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Roles;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.Placement; using Robust.Client.Interfaces.Placement;
@@ -30,6 +31,7 @@ namespace Content.Client.UserInterface.AdminMenu
public readonly TabContainer MasterTabContainer; public readonly TabContainer MasterTabContainer;
public readonly VBoxContainer PlayerList; public readonly VBoxContainer PlayerList;
public readonly Label PlayerCount; public readonly Label PlayerCount;
private readonly IGameHud _gameHud;
protected override Vector2? CustomSize => (500, 250); protected override Vector2? CustomSize => (500, 250);
@@ -44,7 +46,7 @@ namespace Content.Client.UserInterface.AdminMenu
{ {
new SpawnEntitiesCommandButton(), new SpawnEntitiesCommandButton(),
new SpawnTilesCommandButton(), new SpawnTilesCommandButton(),
new StationEventsCommandButton(), new StationEventsCommandButton()
}; };
private readonly List<CommandButton> _debugButtons = new() private readonly List<CommandButton> _debugButtons = new()
{ {
@@ -206,6 +208,7 @@ namespace Content.Client.UserInterface.AdminMenu
public AdminMenuWindow() //TODO: search for buttons? public AdminMenuWindow() //TODO: search for buttons?
{ {
_gameHud = IoCManager.Resolve<IGameHud>();
Title = Loc.GetString("Admin Menu"); Title = Loc.GetString("Admin Menu");
#region PlayerList #region PlayerList
@@ -376,6 +379,19 @@ namespace Content.Client.UserInterface.AdminMenu
IoCManager.Resolve<IStationEventManager>().RequestEvents(); IoCManager.Resolve<IStationEventManager>().RequestEvents();
} }
protected override void ExitedTree()
{
base.ExitedTree();
_gameHud.AdminButtonDown = false;
}
protected override void EnteredTree()
{
base.EnteredTree();
_gameHud.AdminButtonDown = true;
}
#region CommandButtonBaseClass #region CommandButtonBaseClass
private abstract class CommandButton private abstract class CommandButton
{ {

View File

@@ -0,0 +1,42 @@
using Content.Client.Eui;
using Content.Shared.Eui;
using JetBrains.Annotations;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Content.Shared.Administration;
namespace Content.Client.UserInterface.AdminMenu.SetOutfit
{
[UsedImplicitly]
public sealed class SetOutfitEui : BaseEui
{
private readonly SetOutfitMenu _window;
public SetOutfitEui()
{
_window = new SetOutfitMenu();
}
public override void Opened()
{
_window.OpenCentered();
}
public override void Closed()
{
base.Closed();
_window.Close();
}
public override void HandleState(EuiStateBase state)
{
var outfitState = (SetOutfitEuiState) state;
_window.TargetEntityId = outfitState.TargetEntityId;
}
}
}

View File

@@ -0,0 +1,17 @@
<customControls:SS14Window
xmlns:customControls="clr-namespace:Robust.Client.UserInterface.CustomControls;assembly=Robust.Client"
xmlns:controls="clr-namespace:Robust.Client.UserInterface.Controls;assembly=Robust.Client"
xmlns:userInterface="clr-namespace:Robust.Client.UserInterface;assembly=Robust.Client">
<controls:HBoxContainer SizeFlagsHorizontal="FillExpand">
<controls:VBoxContainer SizeFlagsHorizontal="FillExpand" SizeFlagsStretchRatio="0.45">
<controls:HBoxContainer SizeFlagsHorizontal="FillExpand" SizeFlagsVertical="FillExpand"
SizeFlagsStretchRatio="0.1">
<controls:LineEdit Name="SearchBar" PlaceHolder="Search" SizeFlagsHorizontal="FillExpand"
SizeFlagsStretchRatio="0.6" />
</controls:HBoxContainer>
<controls:ItemList Name="OutfitList" SelectMode="Single" SizeFlagsVertical="FillExpand"
SizeFlagsStretchRatio="0.9" />
<controls:Button Name="ConfirmButton" SizeFlagsHorizontal="FillExpand" />
</controls:VBoxContainer>
</controls:HBoxContainer>
</customControls:SS14Window>

View File

@@ -0,0 +1,105 @@
#nullable enable
using System;
using Content.Shared.Construction;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
namespace Content.Client.UserInterface.AdminMenu.SetOutfit
{
[GenerateTypedNameReferences]
public partial class SetOutfitMenu : SS14Window
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IClientConsole _console = default!;
public EntityUid? TargetEntityId { get; set; }
protected override Vector2? CustomSize => (250, 320);
private StartingGearPrototype? _selectedOutfit;
public SetOutfitMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
Title = Loc.GetString("Set Outfit");
ConfirmButton.Text = Loc.GetString("Confirm");
ConfirmButton.OnPressed += ConfirmButtonOnOnPressed;
SearchBar.OnTextChanged += SearchBarOnOnTextChanged;
OutfitList.OnItemSelected += OutfitListOnOnItemSelected;
OutfitList.OnItemDeselected += OutfitListOnOnItemDeselected;
PopulateList();
}
private void ConfirmButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
if (TargetEntityId == null || _selectedOutfit == null)
return;
var command = $"setoutfit {TargetEntityId} {_selectedOutfit.ID}";
_console.ProcessCommand(command);
Close();
}
private void OutfitListOnOnItemSelected(ItemList.ItemListSelectedEventArgs obj)
{
_selectedOutfit = (StartingGearPrototype) obj.ItemList[obj.ItemIndex].Metadata!;
ConfirmButton.Disabled = false;
}
private void OutfitListOnOnItemDeselected(ItemList.ItemListDeselectedEventArgs obj)
{
_selectedOutfit = null;
ConfirmButton.Disabled = true;
}
private void SearchBarOnOnTextChanged(LineEdit.LineEditEventArgs obj)
{
PopulateByFilter(SearchBar.Text);
}
private void PopulateList()
{
foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
{
OutfitList.Add(GetItem(gear, OutfitList));
}
}
private void PopulateByFilter(string filter)
{
OutfitList.Clear();
foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
{
if (!string.IsNullOrEmpty(filter) &&
gear.ID.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant()))
{
OutfitList.Add(GetItem(gear, OutfitList));
}
}
}
private static ItemList.Item GetItem(StartingGearPrototype gear, ItemList itemList)
{
return new(itemList)
{
Metadata = gear,
Text = gear.ID
};
}
}
}

View File

@@ -53,8 +53,10 @@ namespace Content.Client.UserInterface.Controls
/// <summary> /// <summary>
/// Is there an action in the slot that can currently be used? /// Is there an action in the slot that can currently be used?
/// Target-basedActions on cooldown can still be selected / deselected if they've been configured as such
/// </summary> /// </summary>
public bool CanUseAction => HasAssignment && ActionEnabled && !IsOnCooldown; public bool CanUseAction => Action != null && ActionEnabled &&
(!IsOnCooldown || (Action.IsTargetAction && !Action.DeselectOnCooldown));
/// <summary> /// <summary>
/// Item the action is provided by, only valid if Action is an ItemActionPrototype. May be null /// Item the action is provided by, only valid if Action is an ItemActionPrototype. May be null
@@ -325,6 +327,14 @@ namespace Content.Client.UserInterface.Controls
DrawModeChanged(); DrawModeChanged();
} }
protected override void ControlFocusExited()
{
// lost focus for some reason, cancel the drag if there is one.
base.ControlFocusExited();
_actionsUI.DragDropHelper.EndDrag();
DrawModeChanged();
}
/// <summary> /// <summary>
/// Cancel current press without triggering the action /// Cancel current press without triggering the action
/// </summary> /// </summary>
@@ -340,8 +350,10 @@ namespace Content.Client.UserInterface.Controls
/// </summary> /// </summary>
public void Depress(bool depress) public void Depress(bool depress)
{ {
// action can still be toggled if it's allowed to stay selected
if (!CanUseAction) return; if (!CanUseAction) return;
if (_depressed && !depress) if (_depressed && !depress)
{ {
// fire the action // fire the action

View File

@@ -1,17 +1,25 @@
using System; using System;
using System.Transactions;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility; using Content.Client.Utility;
using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.Input; using Content.Shared.Input;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Drawing;
using Robust.Client.Input;
using Robust.Client.Interfaces.Input; using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding; using Robust.Shared.Input.Binding;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Utility;
using YamlDotNet.Core.Tokens;
using static Robust.Client.Input.Keyboard.Key;
using Control = Robust.Client.UserInterface.Control;
namespace Content.Client.UserInterface namespace Content.Client.UserInterface
{ {
@@ -41,6 +49,16 @@ namespace Content.Client.UserInterface
bool CraftingButtonVisible { get; set; } bool CraftingButtonVisible { get; set; }
Action<bool> CraftingButtonToggled { get; set; } Action<bool> CraftingButtonToggled { get; set; }
// Actions top button.
bool ActionsButtonDown { get; set; }
bool ActionsButtonVisible { get; set; }
Action<bool> ActionsButtonToggled { get; set; }
// Admin top button.
bool AdminButtonDown { get; set; }
bool AdminButtonVisible { get; set; }
Action<bool> AdminButtonToggled { get; set; }
// Sandbox top button. // Sandbox top button.
bool SandboxButtonDown { get; set; } bool SandboxButtonDown { get; set; }
bool SandboxButtonVisible { get; set; } bool SandboxButtonVisible { get; set; }
@@ -48,7 +66,8 @@ namespace Content.Client.UserInterface
Control HandsContainer { get; } Control HandsContainer { get; }
Control SuspicionContainer { get; } Control SuspicionContainer { get; }
Control InventoryQuickButtonContainer { get; } Control RightInventoryQuickButtonContainer { get; }
Control LeftInventoryQuickButtonContainer { get; }
bool CombatPanelVisible { get; set; } bool CombatPanelVisible { get; set; }
bool CombatModeActive { get; set; } bool CombatModeActive { get; set; }
@@ -69,6 +88,8 @@ namespace Content.Client.UserInterface
private TopButton _buttonCharacterMenu; private TopButton _buttonCharacterMenu;
private TopButton _buttonInventoryMenu; private TopButton _buttonInventoryMenu;
private TopButton _buttonCraftingMenu; private TopButton _buttonCraftingMenu;
private TopButton _buttonActionsMenu;
private TopButton _buttonAdminMenu;
private TopButton _buttonSandboxMenu; private TopButton _buttonSandboxMenu;
private TutorialWindow _tutorialWindow; private TutorialWindow _tutorialWindow;
private TargetingDoll _targetingDoll; private TargetingDoll _targetingDoll;
@@ -80,7 +101,8 @@ namespace Content.Client.UserInterface
public Control HandsContainer { get; private set; } public Control HandsContainer { get; private set; }
public Control SuspicionContainer { get; private set; } public Control SuspicionContainer { get; private set; }
public Control InventoryQuickButtonContainer { get; private set; } public Control RightInventoryQuickButtonContainer { get; private set; }
public Control LeftInventoryQuickButtonContainer { get; private set; }
public bool CombatPanelVisible public bool CombatPanelVisible
{ {
@@ -108,16 +130,18 @@ namespace Content.Client.UserInterface
RootControl = new LayoutContainer(); RootControl = new LayoutContainer();
LayoutContainer.SetAnchorPreset(RootControl, LayoutContainer.LayoutPreset.Wide); LayoutContainer.SetAnchorPreset(RootControl, LayoutContainer.LayoutPreset.Wide);
var escapeTexture = _resourceCache.GetTexture("/Textures/Interface/hamburger.svg.96dpi.png"); var escapeTexture = _resourceCache.GetTexture("/Textures/Interface/hamburger.svg.192dpi.png");
var characterTexture = _resourceCache.GetTexture("/Textures/Interface/character.svg.96dpi.png"); var characterTexture = _resourceCache.GetTexture("/Textures/Interface/character.svg.192dpi.png");
var inventoryTexture = _resourceCache.GetTexture("/Textures/Interface/inventory.svg.96dpi.png"); var inventoryTexture = _resourceCache.GetTexture("/Textures/Interface/inventory.svg.192dpi.png");
var craftingTexture = _resourceCache.GetTexture("/Textures/Interface/hammer.svg.96dpi.png"); var craftingTexture = _resourceCache.GetTexture("/Textures/Interface/hammer.svg.192dpi.png");
var tutorialTexture = _resourceCache.GetTexture("/Textures/Interface/students-cap.svg.96dpi.png"); var actionsTexture = _resourceCache.GetTexture("/Textures/Interface/fist.svg.192dpi.png");
var sandboxTexture = _resourceCache.GetTexture("/Textures/Interface/sandbox.svg.96dpi.png"); var adminTexture = _resourceCache.GetTexture("/Textures/Interface/gavel.svg.192dpi.png");
var tutorialTexture = _resourceCache.GetTexture("/Textures/Interface/tutorial.svg.192dpi.png");
var sandboxTexture = _resourceCache.GetTexture("/Textures/Interface/sandbox.svg.192dpi.png");
_topButtonsContainer = new HBoxContainer _topButtonsContainer = new HBoxContainer
{ {
SeparationOverride = 4 SeparationOverride = 8
}; };
RootControl.AddChild(_topButtonsContainer); RootControl.AddChild(_topButtonsContainer);
@@ -125,32 +149,29 @@ namespace Content.Client.UserInterface
LayoutContainer.SetAnchorAndMarginPreset(_topButtonsContainer, LayoutContainer.LayoutPreset.TopLeft, LayoutContainer.SetAnchorAndMarginPreset(_topButtonsContainer, LayoutContainer.LayoutPreset.TopLeft,
margin: 10); margin: 10);
// TODO: Pull key names here from the actual key binding config. // the icon textures here should all have the same image height (32) but different widths, so in order to ensure
// the buttons themselves are consistent widths we set a common custom min size
Vector2 topMinSize = (42, 64);
// Escape // Escape
_buttonEscapeMenu = new TopButton(escapeTexture, "Esc") _buttonEscapeMenu = new TopButton(escapeTexture, EngineKeyFunctions.EscapeMenu, _inputManager)
{ {
ToolTip = Loc.GetString("Open escape menu.") ToolTip = Loc.GetString("Open escape menu."),
CustomMinimumSize = (70, 64),
StyleClasses = {StyleBase.ButtonOpenRight}
}; };
_topButtonsContainer.AddChild(_buttonEscapeMenu); _topButtonsContainer.AddChild(_buttonEscapeMenu);
_buttonEscapeMenu.OnToggled += args => EscapeButtonToggled?.Invoke(args.Pressed); _buttonEscapeMenu.OnToggled += args => EscapeButtonToggled?.Invoke(args.Pressed);
// Tutorial
_buttonTutorial = new TopButton(tutorialTexture, "F1")
{
ToolTip = Loc.GetString("Open tutorial.")
};
_topButtonsContainer.AddChild(_buttonTutorial);
_buttonTutorial.OnToggled += a => ButtonTutorialOnOnToggled();
// Character // Character
_buttonCharacterMenu = new TopButton(characterTexture, "C") _buttonCharacterMenu = new TopButton(characterTexture, ContentKeyFunctions.OpenCharacterMenu, _inputManager)
{ {
ToolTip = Loc.GetString("Open character menu."), ToolTip = Loc.GetString("Open character menu."),
Visible = false CustomMinimumSize = topMinSize,
Visible = false,
StyleClasses = {StyleBase.ButtonSquare}
}; };
_topButtonsContainer.AddChild(_buttonCharacterMenu); _topButtonsContainer.AddChild(_buttonCharacterMenu);
@@ -158,10 +179,12 @@ namespace Content.Client.UserInterface
_buttonCharacterMenu.OnToggled += args => CharacterButtonToggled?.Invoke(args.Pressed); _buttonCharacterMenu.OnToggled += args => CharacterButtonToggled?.Invoke(args.Pressed);
// Inventory // Inventory
_buttonInventoryMenu = new TopButton(inventoryTexture, "I") _buttonInventoryMenu = new TopButton(inventoryTexture, ContentKeyFunctions.OpenInventoryMenu, _inputManager)
{ {
ToolTip = Loc.GetString("Open inventory menu."), ToolTip = Loc.GetString("Open inventory menu."),
Visible = false CustomMinimumSize = topMinSize,
Visible = false,
StyleClasses = {StyleBase.ButtonSquare}
}; };
_topButtonsContainer.AddChild(_buttonInventoryMenu); _topButtonsContainer.AddChild(_buttonInventoryMenu);
@@ -169,27 +192,69 @@ namespace Content.Client.UserInterface
_buttonInventoryMenu.OnToggled += args => InventoryButtonToggled?.Invoke(args.Pressed); _buttonInventoryMenu.OnToggled += args => InventoryButtonToggled?.Invoke(args.Pressed);
// Crafting // Crafting
_buttonCraftingMenu = new TopButton(craftingTexture, "G") _buttonCraftingMenu = new TopButton(craftingTexture, ContentKeyFunctions.OpenCraftingMenu, _inputManager)
{ {
ToolTip = Loc.GetString("Open crafting menu."), ToolTip = Loc.GetString("Open crafting menu."),
Visible = false CustomMinimumSize = topMinSize,
Visible = false,
StyleClasses = {StyleBase.ButtonSquare}
}; };
_topButtonsContainer.AddChild(_buttonCraftingMenu); _topButtonsContainer.AddChild(_buttonCraftingMenu);
_buttonCraftingMenu.OnToggled += args => CraftingButtonToggled?.Invoke(args.Pressed); _buttonCraftingMenu.OnToggled += args => CraftingButtonToggled?.Invoke(args.Pressed);
// Actions
_buttonActionsMenu = new TopButton(actionsTexture, ContentKeyFunctions.OpenActionsMenu, _inputManager)
{
ToolTip = Loc.GetString("Open actions menu."),
CustomMinimumSize = topMinSize,
Visible = false,
StyleClasses = {StyleBase.ButtonSquare}
};
_topButtonsContainer.AddChild(_buttonActionsMenu);
_buttonActionsMenu.OnToggled += args => ActionsButtonToggled?.Invoke(args.Pressed);
// Admin
_buttonAdminMenu = new TopButton(adminTexture, ContentKeyFunctions.OpenAdminMenu, _inputManager)
{
ToolTip = Loc.GetString("Open admin menu."),
CustomMinimumSize = topMinSize,
Visible = false,
StyleClasses = {StyleBase.ButtonSquare}
};
_topButtonsContainer.AddChild(_buttonAdminMenu);
_buttonAdminMenu.OnToggled += args => AdminButtonToggled?.Invoke(args.Pressed);
// Sandbox // Sandbox
_buttonSandboxMenu = new TopButton(sandboxTexture, "B") _buttonSandboxMenu = new TopButton(sandboxTexture, ContentKeyFunctions.OpenSandboxWindow, _inputManager)
{ {
ToolTip = Loc.GetString("Open sandbox menu."), ToolTip = Loc.GetString("Open sandbox menu."),
Visible = false CustomMinimumSize = topMinSize,
Visible = false,
StyleClasses = {StyleBase.ButtonSquare}
}; };
_topButtonsContainer.AddChild(_buttonSandboxMenu); _topButtonsContainer.AddChild(_buttonSandboxMenu);
_buttonSandboxMenu.OnToggled += args => SandboxButtonToggled?.Invoke(args.Pressed); _buttonSandboxMenu.OnToggled += args => SandboxButtonToggled?.Invoke(args.Pressed);
// Tutorial
_buttonTutorial = new TopButton(tutorialTexture, ContentKeyFunctions.OpenTutorial, _inputManager)
{
ToolTip = Loc.GetString("Open tutorial."),
CustomMinimumSize = topMinSize,
StyleClasses = {StyleBase.ButtonOpenLeft, TopButton.StyleClassRedTopButton},
};
_topButtonsContainer.AddChild(_buttonTutorial);
_buttonTutorial.OnToggled += a => ButtonTutorialOnOnToggled();
_tutorialWindow = new TutorialWindow(); _tutorialWindow = new TutorialWindow();
_tutorialWindow.OnClose += () => _buttonTutorial.Pressed = false; _tutorialWindow.OnClose += () => _buttonTutorial.Pressed = false;
@@ -197,21 +262,6 @@ namespace Content.Client.UserInterface
_inputManager.SetInputCommand(ContentKeyFunctions.OpenTutorial, _inputManager.SetInputCommand(ContentKeyFunctions.OpenTutorial,
InputCmdHandler.FromDelegate(s => ButtonTutorialOnOnToggled())); InputCmdHandler.FromDelegate(s => ButtonTutorialOnOnToggled()));
var inventoryContainer = new HBoxContainer
{
SeparationOverride = 10
};
RootControl.AddChild(inventoryContainer);
LayoutContainer.SetGrowHorizontal(inventoryContainer, LayoutContainer.GrowDirection.Begin);
LayoutContainer.SetGrowVertical(inventoryContainer, LayoutContainer.GrowDirection.Begin);
LayoutContainer.SetAnchorAndMarginPreset(inventoryContainer, LayoutContainer.LayoutPreset.BottomRight);
InventoryQuickButtonContainer = new MarginContainer
{
SizeFlagsVertical = Control.SizeFlags.ShrinkEnd
};
_combatPanelContainer = new VBoxContainer _combatPanelContainer = new VBoxContainer
{ {
@@ -226,23 +276,40 @@ namespace Content.Client.UserInterface
} }
}; };
LayoutContainer.SetGrowHorizontal(_combatPanelContainer, LayoutContainer.GrowDirection.Begin);
LayoutContainer.SetGrowVertical(_combatPanelContainer, LayoutContainer.GrowDirection.Begin);
LayoutContainer.SetAnchorAndMarginPreset(_combatPanelContainer, LayoutContainer.LayoutPreset.BottomRight);
LayoutContainer.SetMarginBottom(_combatPanelContainer, -10f);
RootControl.AddChild(_combatPanelContainer);
_combatModeButton.OnToggled += args => OnCombatModeChanged?.Invoke(args.Pressed); _combatModeButton.OnToggled += args => OnCombatModeChanged?.Invoke(args.Pressed);
_targetingDoll.OnZoneChanged += args => OnTargetingZoneChanged?.Invoke(args); _targetingDoll.OnZoneChanged += args => OnTargetingZoneChanged?.Invoke(args);
inventoryContainer.Children.Add(InventoryQuickButtonContainer); var centerBottomContainer = new HBoxContainer
inventoryContainer.Children.Add(_combatPanelContainer); {
SeparationOverride = 5
};
LayoutContainer.SetAnchorAndMarginPreset(centerBottomContainer, LayoutContainer.LayoutPreset.CenterBottom);
LayoutContainer.SetGrowHorizontal(centerBottomContainer, LayoutContainer.GrowDirection.Both);
LayoutContainer.SetGrowVertical(centerBottomContainer, LayoutContainer.GrowDirection.Begin);
LayoutContainer.SetMarginBottom(centerBottomContainer, -10f);
RootControl.AddChild(centerBottomContainer);
HandsContainer = new MarginContainer HandsContainer = new MarginContainer
{ {
SizeFlagsVertical = Control.SizeFlags.ShrinkEnd SizeFlagsVertical = Control.SizeFlags.ShrinkEnd
}; };
RightInventoryQuickButtonContainer = new MarginContainer
RootControl.AddChild(HandsContainer); {
SizeFlagsVertical = Control.SizeFlags.ShrinkEnd
LayoutContainer.SetAnchorAndMarginPreset(HandsContainer, LayoutContainer.LayoutPreset.CenterBottom); };
LayoutContainer.SetGrowHorizontal(HandsContainer, LayoutContainer.GrowDirection.Both); LeftInventoryQuickButtonContainer = new MarginContainer
LayoutContainer.SetGrowVertical(HandsContainer, LayoutContainer.GrowDirection.Begin); {
SizeFlagsVertical = Control.SizeFlags.ShrinkEnd
};
centerBottomContainer.AddChild(LeftInventoryQuickButtonContainer);
centerBottomContainer.AddChild(HandsContainer);
centerBottomContainer.AddChild(RightInventoryQuickButtonContainer);
SuspicionContainer = new MarginContainer SuspicionContainer = new MarginContainer
{ {
@@ -251,13 +318,15 @@ namespace Content.Client.UserInterface
RootControl.AddChild(SuspicionContainer); RootControl.AddChild(SuspicionContainer);
LayoutContainer.SetAnchorAndMarginPreset(SuspicionContainer, LayoutContainer.LayoutPreset.BottomLeft, margin: 10); LayoutContainer.SetAnchorAndMarginPreset(SuspicionContainer, LayoutContainer.LayoutPreset.BottomLeft,
margin: 10);
LayoutContainer.SetGrowHorizontal(SuspicionContainer, LayoutContainer.GrowDirection.End); LayoutContainer.SetGrowHorizontal(SuspicionContainer, LayoutContainer.GrowDirection.End);
LayoutContainer.SetGrowVertical(SuspicionContainer, LayoutContainer.GrowDirection.Begin); LayoutContainer.SetGrowVertical(SuspicionContainer, LayoutContainer.GrowDirection.Begin);
} }
private void ButtonTutorialOnOnToggled() private void ButtonTutorialOnOnToggled()
{ {
_buttonTutorial.StyleClasses.Remove(TopButton.StyleClassRedTopButton);
if (_tutorialWindow.IsOpen) if (_tutorialWindow.IsOpen)
{ {
if (!_tutorialWindow.IsAtFront()) if (!_tutorialWindow.IsAtFront())
@@ -330,6 +399,34 @@ namespace Content.Client.UserInterface
public Action<bool> CraftingButtonToggled { get; set; } public Action<bool> CraftingButtonToggled { get; set; }
public bool ActionsButtonDown
{
get => _buttonActionsMenu.Pressed;
set => _buttonActionsMenu.Pressed = value;
}
public bool ActionsButtonVisible
{
get => _buttonActionsMenu.Visible;
set => _buttonActionsMenu.Visible = value;
}
public Action<bool> ActionsButtonToggled { get; set; }
public bool AdminButtonDown
{
get => _buttonAdminMenu.Pressed;
set => _buttonAdminMenu.Pressed = value;
}
public bool AdminButtonVisible
{
get => _buttonAdminMenu.Visible;
set => _buttonAdminMenu.Visible = value;
}
public Action<bool> AdminButtonToggled { get; set; }
public bool SandboxButtonDown public bool SandboxButtonDown
{ {
get => _buttonSandboxMenu.Pressed; get => _buttonSandboxMenu.Pressed;
@@ -344,94 +441,204 @@ namespace Content.Client.UserInterface
public Action<bool> SandboxButtonToggled { get; set; } public Action<bool> SandboxButtonToggled { get; set; }
public sealed class TopButton : BaseButton public sealed class TopButton : ContainerButton
{ {
public const string StyleClassLabelTopButton = "topButtonLabel"; public const string StyleClassLabelTopButton = "topButtonLabel";
public const string StyleClassRedTopButton = "topButtonLabel";
private const float CustomTooltipDelay = 0.4f;
private static readonly Color ColorNormal = Color.FromHex("#7b7e9e"); private static readonly Color ColorNormal = Color.FromHex("#7b7e9e");
private static readonly Color ColorRedNormal = Color.FromHex("#FEFEFE");
private static readonly Color ColorHovered = Color.FromHex("#9699bb"); private static readonly Color ColorHovered = Color.FromHex("#9699bb");
private static readonly Color ColorRedHovered = Color.FromHex("#FFFFFF");
private static readonly Color ColorPressed = Color.FromHex("#789B8C"); private static readonly Color ColorPressed = Color.FromHex("#789B8C");
private const float VertPad = 8f;
private Color NormalColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedNormal : ColorNormal;
private Color HoveredColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedHovered : ColorHovered;
private readonly TextureRect _textureRect; private readonly TextureRect _textureRect;
private readonly Label _label; private readonly Label _label;
private readonly BoundKeyFunction _function;
private readonly IInputManager _inputManager;
public TopButton(Texture texture, string keyName) public TopButton(Texture texture, BoundKeyFunction function, IInputManager inputManager)
{ {
ToggleMode = true; _function = function;
_inputManager = inputManager;
TooltipDelay = CustomTooltipDelay;
AddChild(new MarginContainer AddChild(
{
MarginTopOverride = 4,
Children =
{
new VBoxContainer new VBoxContainer
{ {
Children = Children =
{ {
new Control {CustomMinimumSize = (0, VertPad)},
(_textureRect = new TextureRect (_textureRect = new TextureRect
{ {
TextureScale = (0.5f, 0.5f),
Texture = texture, Texture = texture,
SizeFlagsHorizontal = SizeFlags.ShrinkCenter, SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
SizeFlagsVertical = SizeFlags.Expand | SizeFlags.ShrinkCenter, SizeFlagsVertical = SizeFlags.Expand | SizeFlags.ShrinkCenter,
ModulateSelfOverride = ColorNormal, ModulateSelfOverride = NormalColor,
CustomMinimumSize = (0, 32),
Stretch = TextureRect.StretchMode.KeepCentered Stretch = TextureRect.StretchMode.KeepCentered
}), }),
new Control {CustomMinimumSize = (0, VertPad)},
(_label = new Label (_label = new Label
{ {
Text = keyName, Text = ShortKeyName(_function),
SizeFlagsHorizontal = SizeFlags.ShrinkCenter, SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
ModulateSelfOverride = ColorNormal, ModulateSelfOverride = NormalColor,
StyleClasses = {StyleClassLabelTopButton} StyleClasses = {StyleClassLabelTopButton}
}) })
} }
} }
} );
});
DrawModeChanged(); ToggleMode = true;
} }
protected override Vector2 CalculateMinimumSize() protected override void EnteredTree()
{ {
var styleSize = ActualStyleBox?.MinimumSize ?? Vector2.Zero; _inputManager.OnKeyBindingAdded += OnKeyBindingChanged;
return (0, 4) + styleSize + base.CalculateMinimumSize(); _inputManager.OnKeyBindingRemoved += OnKeyBindingChanged;
} }
protected override void Draw(DrawingHandleScreen handle) protected override void ExitedTree()
{ {
ActualStyleBox?.Draw(handle, PixelSizeBox); _inputManager.OnKeyBindingAdded -= OnKeyBindingChanged;
_inputManager.OnKeyBindingRemoved -= OnKeyBindingChanged;
} }
private StyleBox ActualStyleBox
private void OnKeyBindingChanged(IKeyBinding obj)
{ {
get _label.Text = ShortKeyName(_function);
{
TryGetStyleProperty(Button.StylePropertyStyleBox, out StyleBox ret);
return ret;
}
} }
protected override void DrawModeChanged() private string ShortKeyName(BoundKeyFunction keyFunction)
{ {
// need to use shortened key names so they fit in the buttons.
return TryGetShortKeyName(keyFunction, out var name) ? Loc.GetString(name) : " ";
}
private bool TryGetShortKeyName(BoundKeyFunction keyFunction, out string name)
{
if (_inputManager.TryGetKeyBinding(keyFunction, out var binding))
{
// can't possibly fit a modifier key in the top button, so omit it
var key = binding.BaseKey;
if (binding.Mod1 != Unknown || binding.Mod2 != Unknown ||
binding.Mod3 != Unknown)
{
name = null;
return false;
}
name = null;
name = key switch
{
Apostrophe => "'",
Comma => ",",
Delete => "Del",
Down => "Dwn",
Escape => "Esc",
Equal => "=",
Home => "Hom",
Insert => "Ins",
Left => "Lft",
Menu => "Men",
Minus => "-",
Num0 => "0",
Num1 => "1",
Num2 => "2",
Num3 => "3",
Num4 => "4",
Num5 => "5",
Num6 => "6",
Num7 => "7",
Num8 => "8",
Num9 => "9",
Pause => "||",
Period => ".",
Return => "Ret",
Right => "Rgt",
Slash => "/",
Space => "Spc",
Tab => "Tab",
Tilde => "~",
BackSlash => "\\",
BackSpace => "Bks",
LBracket => "[",
MouseButton4 => "M4",
MouseButton5 => "M5",
MouseButton6 => "M6",
MouseButton7 => "M7",
MouseButton8 => "M8",
MouseButton9 => "M9",
MouseLeft => "ML",
MouseMiddle => "MM",
MouseRight => "MR",
NumpadDecimal => "N.",
NumpadDivide => "N/",
NumpadEnter => "Ent",
NumpadMultiply => "*",
NumpadNum0 => "0",
NumpadNum1 => "1",
NumpadNum2 => "2",
NumpadNum3 => "3",
NumpadNum4 => "4",
NumpadNum5 => "5",
NumpadNum6 => "6",
NumpadNum7 => "7",
NumpadNum8 => "8",
NumpadNum9 => "9",
NumpadSubtract => "N-",
PageDown => "PgD",
PageUp => "PgU",
RBracket => "]",
SemiColon => ";",
_ => DefaultShortKeyName(keyFunction)
};
return name != null;
}
name = null;
return false;
}
private string DefaultShortKeyName(BoundKeyFunction keyFunction)
{
var name = FormattedMessage.EscapeText(_inputManager.GetKeyFunctionButtonString(keyFunction));
return name.Length > 3 ? null : name;
}
protected override void StylePropertiesChanged()
{
// colors of children depend on style, so ensure we update when style is changed
base.StylePropertiesChanged();
UpdateChildColors();
}
private void UpdateChildColors()
{
if (_label == null || _textureRect == null) return;
switch (DrawMode) switch (DrawMode)
{ {
case DrawModeEnum.Normal: case DrawModeEnum.Normal:
SetOnlyStylePseudoClass(Button.StylePseudoClassNormal); _textureRect.ModulateSelfOverride = NormalColor;
_textureRect.ModulateSelfOverride = ColorNormal; _label.ModulateSelfOverride = NormalColor;
_label.ModulateSelfOverride = ColorNormal;
break; break;
case DrawModeEnum.Pressed: case DrawModeEnum.Pressed:
SetOnlyStylePseudoClass(Button.StylePseudoClassPressed);
_textureRect.ModulateSelfOverride = ColorPressed; _textureRect.ModulateSelfOverride = ColorPressed;
_label.ModulateSelfOverride = ColorPressed; _label.ModulateSelfOverride = ColorPressed;
break; break;
case DrawModeEnum.Hover: case DrawModeEnum.Hover:
SetOnlyStylePseudoClass(Button.StylePseudoClassHover); _textureRect.ModulateSelfOverride = HoveredColor;
_textureRect.ModulateSelfOverride = ColorHovered; _label.ModulateSelfOverride = HoveredColor;
_label.ModulateSelfOverride = ColorHovered;
break; break;
case DrawModeEnum.Disabled: case DrawModeEnum.Disabled:
@@ -439,15 +646,11 @@ namespace Content.Client.UserInterface
} }
} }
protected override void LayoutUpdateOverride()
{
var box = ActualStyleBox ?? new StyleBoxEmpty();
var contentBox = box.GetContentBox(PixelSizeBox);
foreach (var child in Children) protected override void DrawModeChanged()
{ {
FitChildInPixelBox(child, (UIBox2i) contentBox); base.DrawModeChanged();
} UpdateChildColors();
} }
} }
} }

View File

@@ -1,11 +1,15 @@
using Content.Shared.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Items;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
namespace Content.Client.UserInterface namespace Content.Client.UserInterface
{ {
public class HandButton : ItemSlotButton public class HandButton : ItemSlotButton
{ {
private bool _activeHand;
private bool _highlighted;
public HandButton(Texture texture, Texture storageTexture, Texture blockedTexture, HandLocation location) : base(texture, storageTexture) public HandButton(Texture texture, Texture storageTexture, Texture blockedTexture, HandLocation location) : base(texture, storageTexture)
{ {
Location = location; Location = location;
@@ -21,5 +25,23 @@ namespace Content.Client.UserInterface
public HandLocation Location { get; } public HandLocation Location { get; }
public TextureRect Blocked { get; } public TextureRect Blocked { get; }
public void SetActiveHand(bool active)
{
_activeHand = active;
UpdateHighlight();
}
public override void Highlight(bool highlight)
{
_highlighted = highlight;
UpdateHighlight();
}
private void UpdateHighlight()
{
// always stay highlighted if active
base.Highlight(_activeHand || _highlighted);
}
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Content.Client.GameObjects.Components.Items; using Content.Client.GameObjects.Components.Items;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility; using Content.Client.Utility;
using Content.Shared.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Items;
using Content.Shared.Input; using Content.Shared.Input;
@@ -21,8 +22,6 @@ namespace Content.Client.UserInterface
[Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!; [Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
private readonly TextureRect _activeHandRect;
private readonly Texture _leftHandTexture; private readonly Texture _leftHandTexture;
private readonly Texture _middleHandTexture; private readonly Texture _middleHandTexture;
private readonly Texture _rightHandTexture; private readonly Texture _rightHandTexture;
@@ -52,24 +51,14 @@ namespace Content.Client.UserInterface
Children = Children =
{ {
(_topPanel = ItemStatusPanel.FromSide(HandLocation.Middle)), (_topPanel = ItemStatusPanel.FromSide(HandLocation.Middle)),
(_handsContainer = new HBoxContainer {SeparationOverride = 0}) (_handsContainer = new HBoxContainer())
} }
}), }),
(_leftPanel = ItemStatusPanel.FromSide(HandLocation.Left)) (_leftPanel = ItemStatusPanel.FromSide(HandLocation.Left))
} }
}); });
var textureHandActive = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_active.png");
// Active hand
_activeHandRect = new TextureRect
{
Texture = textureHandActive,
TextureScale = (2, 2)
};
_leftHandTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_l.png"); _leftHandTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_l.png");
_middleHandTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_middle.png"); _middleHandTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_l.png");
_rightHandTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_r.png"); _rightHandTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_r.png");
} }
@@ -119,13 +108,6 @@ namespace Content.Client.UserInterface
button.OnStoragePressed += args => _OnStoragePressed(args, slot); button.OnStoragePressed += args => _OnStoragePressed(args, slot);
_handsContainer.AddChild(button); _handsContainer.AddChild(button);
if (_activeHandRect.Parent == null)
{
button.AddChild(_activeHandRect);
_activeHandRect.SetPositionInParent(1);
}
hand.Button = button; hand.Button = button;
} }
@@ -135,11 +117,6 @@ namespace Content.Client.UserInterface
if (button != null) if (button != null)
{ {
if (button.Children.Contains(_activeHandRect))
{
button.RemoveChild(_activeHandRect);
}
_handsContainer.RemoveChild(button); _handsContainer.RemoveChild(button);
} }
} }
@@ -186,14 +163,8 @@ namespace Content.Client.UserInterface
hand.Button!.Button.Texture = HandTexture(hand.Location); hand.Button!.Button.Texture = HandTexture(hand.Location);
hand.Button!.SetPositionInParent(i); hand.Button!.SetPositionInParent(i);
_itemSlotManager.SetItemSlot(hand.Button, hand.Entity); _itemSlotManager.SetItemSlot(hand.Button, hand.Entity);
}
_activeHandRect.Parent?.RemoveChild(_activeHandRect); hand.Button!.SetActiveHand(component.ActiveIndex == hand.Name);
component.GetHand(component.ActiveIndex)?.Button?.AddChild(_activeHandRect);
if (hands.Length > 0)
{
_activeHandRect.SetPositionInParent(1);
} }
_leftPanel.SetPositionFirst(); _leftPanel.SetPositionFirst();

View File

@@ -1,4 +1,4 @@
using Content.Client.GameObjects.Components; using Content.Client.GameObjects.Components;
using Content.Client.GameObjects.Components.Mobs; using Content.Client.GameObjects.Components.Mobs;
using Content.Client.Interfaces; using Content.Client.Interfaces;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
@@ -21,6 +21,7 @@ using Robust.Shared.Localization.Macros;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Client.UserInterface.Stylesheets;
namespace Content.Client.UserInterface namespace Content.Client.UserInterface
{ {
@@ -43,6 +44,7 @@ namespace Content.Client.UserInterface
private readonly Button _sexMaleButton; private readonly Button _sexMaleButton;
private readonly OptionButton _genderButton; private readonly OptionButton _genderButton;
private readonly OptionButton _clothingButton; private readonly OptionButton _clothingButton;
private readonly OptionButton _backpackButton;
private readonly HairStylePicker _hairPicker; private readonly HairStylePicker _hairPicker;
private readonly FacialHairStylePicker _facialHairPicker; private readonly FacialHairStylePicker _facialHairPicker;
@@ -333,6 +335,33 @@ namespace Content.Client.UserInterface
} }
#endregion Clothing #endregion Clothing
#region Backpack
{
var panel = HighlightedContainer();
var hBox = new HBoxContainer();
var backpackLabel = new Label { Text = Loc.GetString("Backpack:") };
_backpackButton = new OptionButton();
_backpackButton.AddItem(Loc.GetString("Backpack"), (int) BackpackPreference.Backpack);
_backpackButton.AddItem(Loc.GetString("Satchel"), (int) BackpackPreference.Satchel);
_backpackButton.AddItem(Loc.GetString("Duffelbag"), (int) BackpackPreference.Duffelbag);
_backpackButton.OnItemSelected += args =>
{
_backpackButton.SelectId(args.Id);
SetBackpack((BackpackPreference) args.Id);
};
hBox.AddChild(backpackLabel);
hBox.AddChild(_backpackButton);
panel.AddChild(hBox);
appearanceVBox.AddChild(panel);
}
#endregion Clothing
} }
#endregion #endregion
@@ -669,6 +698,12 @@ namespace Content.Client.UserInterface
IsDirty = true; IsDirty = true;
} }
private void SetBackpack(BackpackPreference newBackpack)
{
Profile = Profile?.WithBackpackPreference(newBackpack);
IsDirty = true;
}
public void Save() public void Save()
{ {
IsDirty = false; IsDirty = false;
@@ -723,6 +758,11 @@ namespace Content.Client.UserInterface
_clothingButton.SelectId((int) Profile.Clothing); _clothingButton.SelectId((int) Profile.Clothing);
} }
private void UpdateBackpackControls()
{
_backpackButton.SelectId((int) Profile.Backpack);
}
private void UpdateHairPickers() private void UpdateHairPickers()
{ {
_hairPicker.SetData( _hairPicker.SetData(
@@ -754,6 +794,7 @@ namespace Content.Client.UserInterface
UpdateSexControls(); UpdateSexControls();
UpdateGenderControls(); UpdateGenderControls();
UpdateClothingControls(); UpdateClothingControls();
UpdateBackpackControls();
UpdateAgeEdit(); UpdateAgeEdit();
UpdateHairPickers(); UpdateHairPickers();
UpdateSaveButton(); UpdateSaveButton();
@@ -780,12 +821,12 @@ namespace Content.Client.UserInterface
private class JobPrioritySelector : Control private class JobPrioritySelector : Control
{ {
public JobPrototype Job { get; } public JobPrototype Job { get; }
private readonly OptionButton _optionButton; private readonly RadioOptions<int> _optionButton;
public JobPriority Priority public JobPriority Priority
{ {
get => (JobPriority) _optionButton.SelectedId; get => (JobPriority) _optionButton.SelectedValue;
set => _optionButton.SelectId((int) value); set => _optionButton.SelectByValue((int) value);
} }
public event Action<JobPriority> PriorityChanged; public event Action<JobPriority> PriorityChanged;
@@ -793,7 +834,14 @@ namespace Content.Client.UserInterface
public JobPrioritySelector(JobPrototype job) public JobPrioritySelector(JobPrototype job)
{ {
Job = job; Job = job;
_optionButton = new OptionButton(); _optionButton = new RadioOptions<int>(RadioOptionsLayout.Horizontal);
_optionButton.FirstButtonStyle = StyleBase.ButtonOpenRight;
_optionButton.ButtonStyle = StyleBase.ButtonOpenBoth;
_optionButton.LastButtonStyle = StyleBase.ButtonOpenLeft;
// Text, Value
_optionButton.AddItem(Loc.GetString("High"), (int) JobPriority.High); _optionButton.AddItem(Loc.GetString("High"), (int) JobPriority.High);
_optionButton.AddItem(Loc.GetString("Medium"), (int) JobPriority.Medium); _optionButton.AddItem(Loc.GetString("Medium"), (int) JobPriority.Medium);
_optionButton.AddItem(Loc.GetString("Low"), (int) JobPriority.Low); _optionButton.AddItem(Loc.GetString("Low"), (int) JobPriority.Low);
@@ -801,7 +849,7 @@ namespace Content.Client.UserInterface
_optionButton.OnItemSelected += args => _optionButton.OnItemSelected += args =>
{ {
_optionButton.SelectId(args.Id); _optionButton.Select(args.Id);
PriorityChanged?.Invoke(Priority); PriorityChanged?.Invoke(Priority);
}; };

View File

@@ -1,4 +1,5 @@
using System; using System;
using Content.Client.UserInterface.Stylesheets;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Graphics.Shaders; using Robust.Client.Graphics.Shaders;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
@@ -26,11 +27,11 @@ namespace Content.Client.UserInterface
public bool EntityHover => HoverSpriteView.Sprite != null; public bool EntityHover => HoverSpriteView.Sprite != null;
public bool MouseIsHovering = false; public bool MouseIsHovering = false;
private readonly ShaderInstance _highlightShader;
private readonly PanelContainer _highlightRect;
public ItemSlotButton(Texture texture, Texture storageTexture) public ItemSlotButton(Texture texture, Texture storageTexture)
{ {
_highlightShader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>(HighlightShader).Instance();
CustomMinimumSize = (64, 64); CustomMinimumSize = (64, 64);
AddChild(Button = new TextureRect AddChild(Button = new TextureRect
@@ -40,6 +41,13 @@ namespace Content.Client.UserInterface
MouseFilter = MouseFilterMode.Stop MouseFilter = MouseFilterMode.Stop
}); });
AddChild(_highlightRect = new PanelContainer
{
StyleClasses = { StyleNano.StyleClassHandSlotHighlight },
CustomMinimumSize = (32, 32),
Visible = false
});
Button.OnKeyBindDown += OnButtonPressed; Button.OnKeyBindDown += OnButtonPressed;
AddChild(SpriteView = new SpriteView AddChild(SpriteView = new SpriteView
@@ -102,18 +110,16 @@ namespace Content.Client.UserInterface
} }
} }
public void Highlight(bool on) public virtual void Highlight(bool highlight)
{ {
// I make no claim that this actually looks good but it's a start. if (highlight)
if (on)
{ {
Button.ShaderOverride = _highlightShader; _highlightRect.Visible = true;
} }
else else
{ {
Button.ShaderOverride = null; _highlightRect.Visible = false;
} }
} }
private void OnButtonPressed(GUIBoundKeyEventArgs args) private void OnButtonPressed(GUIBoundKeyEventArgs args)

View File

@@ -7,6 +7,7 @@ using Robust.Client.GameObjects.EntitySystems;
using Robust.Client.Interfaces.GameObjects.Components; using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.Input; using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Shared.Input; using Robust.Shared.Input;
@@ -25,6 +26,7 @@ namespace Content.Client.UserInterface
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IUserInterfaceManager _uiMgr = default!;
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
@@ -61,7 +63,7 @@ namespace Content.Client.UserInterface
else if (args.Function == ContentKeyFunctions.OpenContextMenu) else if (args.Function == ContentKeyFunctions.OpenContextMenu)
{ {
_entitySystemManager.GetEntitySystem<VerbSystem>() _entitySystemManager.GetEntitySystem<VerbSystem>()
.OpenContextMenu(item, new ScreenCoordinates(args.PointerLocation.Position)); .OpenContextMenu(item, new ScreenCoordinates(_uiMgr.ScreenToUIPosition(args.PointerLocation)));
} }
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld) else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
{ {

View File

@@ -32,7 +32,7 @@ namespace Content.Client.UserInterface
[ViewVariables] [ViewVariables]
private IEntity? _entity; private IEntity? _entity;
public ItemStatusPanel(Texture texture, StyleBox.Margin margin) public ItemStatusPanel(Texture texture, StyleBox.Margin cutout, StyleBox.Margin flat, Label.AlignMode textAlign)
{ {
var panel = new StyleBoxTexture var panel = new StyleBoxTexture
{ {
@@ -40,7 +40,8 @@ namespace Content.Client.UserInterface
}; };
panel.SetContentMarginOverride(StyleBox.Margin.Vertical, 4); panel.SetContentMarginOverride(StyleBox.Margin.Vertical, 4);
panel.SetContentMarginOverride(StyleBox.Margin.Horizontal, 6); panel.SetContentMarginOverride(StyleBox.Margin.Horizontal, 6);
panel.SetPatchMargin(margin, 13); panel.SetPatchMargin(flat, 2);
panel.SetPatchMargin(cutout, 13);
AddChild(_panel = new PanelContainer AddChild(_panel = new PanelContainer
{ {
@@ -57,7 +58,8 @@ namespace Content.Client.UserInterface
(_itemNameLabel = new Label (_itemNameLabel = new Label
{ {
ClipText = true, ClipText = true,
StyleClasses = {StyleNano.StyleClassItemStatus} StyleClasses = {StyleNano.StyleClassItemStatus},
Align = textAlign
}) })
} }
} }
@@ -78,27 +80,35 @@ namespace Content.Client.UserInterface
public static ItemStatusPanel FromSide(HandLocation location) public static ItemStatusPanel FromSide(HandLocation location)
{ {
string texture; string texture;
StyleBox.Margin margin; StyleBox.Margin cutOut;
StyleBox.Margin flat;
Label.AlignMode textAlign;
switch (location) switch (location)
{ {
case HandLocation.Left: case HandLocation.Left:
texture = "/Textures/Interface/Nano/item_status_right.svg.96dpi.png"; texture = "/Textures/Interface/Nano/item_status_right.svg.96dpi.png";
margin = StyleBox.Margin.Left | StyleBox.Margin.Top; cutOut = StyleBox.Margin.Left | StyleBox.Margin.Top;
flat = StyleBox.Margin.Right | StyleBox.Margin.Bottom;
textAlign = Label.AlignMode.Right;
break; break;
case HandLocation.Middle: case HandLocation.Middle:
texture = "/Textures/Interface/Nano/item_status_left.svg.96dpi.png"; texture = "/Textures/Interface/Nano/item_status_middle.svg.96dpi.png";
margin = StyleBox.Margin.Right | StyleBox.Margin.Top; cutOut = StyleBox.Margin.Right | StyleBox.Margin.Top;
flat = StyleBox.Margin.Left | StyleBox.Margin.Bottom;
textAlign = Label.AlignMode.Left;
break; break;
case HandLocation.Right: case HandLocation.Right:
texture = "/Textures/Interface/Nano/item_status_left.svg.96dpi.png"; texture = "/Textures/Interface/Nano/item_status_left.svg.96dpi.png";
margin = StyleBox.Margin.Right | StyleBox.Margin.Top; cutOut = StyleBox.Margin.Right | StyleBox.Margin.Top;
flat = StyleBox.Margin.Left | StyleBox.Margin.Bottom;
textAlign = Label.AlignMode.Left;
break; break;
default: default:
throw new ArgumentOutOfRangeException(nameof(location), location, null); throw new ArgumentOutOfRangeException(nameof(location), location, null);
} }
return new ItemStatusPanel(ResC.GetTexture(texture), margin); return new ItemStatusPanel(ResC.GetTexture(texture), cutOut, flat, textAlign);
} }
public void Update(IEntity? entity) public void Update(IEntity? entity)

View File

@@ -17,6 +17,7 @@ namespace Content.Client.UserInterface.Stylesheets
public const string ButtonOpenRight = "OpenRight"; public const string ButtonOpenRight = "OpenRight";
public const string ButtonOpenLeft = "OpenLeft"; public const string ButtonOpenLeft = "OpenLeft";
public const string ButtonOpenBoth = "OpenBoth"; public const string ButtonOpenBoth = "OpenBoth";
public const string ButtonSquare = "ButtonSquare";
public const string ButtonCaution = "Caution"; public const string ButtonCaution = "Caution";
@@ -28,6 +29,7 @@ namespace Content.Client.UserInterface.Stylesheets
protected StyleBoxTexture BaseButtonOpenRight { get; } protected StyleBoxTexture BaseButtonOpenRight { get; }
protected StyleBoxTexture BaseButtonOpenLeft { get; } protected StyleBoxTexture BaseButtonOpenLeft { get; }
protected StyleBoxTexture BaseButtonOpenBoth { get; } protected StyleBoxTexture BaseButtonOpenBoth { get; }
protected StyleBoxTexture BaseButtonSquare { get; }
protected StyleBase(IResourceCache resCache) protected StyleBase(IResourceCache resCache)
{ {
@@ -70,6 +72,15 @@ namespace Content.Client.UserInterface.Stylesheets
BaseButtonOpenBoth.SetPadding(StyleBox.Margin.Right, 2); BaseButtonOpenBoth.SetPadding(StyleBox.Margin.Right, 2);
BaseButtonOpenBoth.SetPadding(StyleBox.Margin.Left, 1); BaseButtonOpenBoth.SetPadding(StyleBox.Margin.Left, 1);
BaseButtonSquare = new StyleBoxTexture(BaseButton)
{
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions((10, 0), (3, 24))),
};
BaseButtonSquare.SetPatchMargin(StyleBox.Margin.Horizontal, 0);
BaseButtonSquare.SetContentMarginOverride(StyleBox.Margin.Horizontal, 8);
BaseButtonSquare.SetPadding(StyleBox.Margin.Right, 2);
BaseButtonSquare.SetPadding(StyleBox.Margin.Left, 1);
BaseRules = new[] BaseRules = new[]
{ {
// Default font. // Default font.

View File

@@ -2,6 +2,7 @@
using Content.Client.GameObjects.EntitySystems; using Content.Client.GameObjects.EntitySystems;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Client.Utility; using Content.Client.Utility;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
@@ -15,6 +16,8 @@ namespace Content.Client.UserInterface.Stylesheets
public sealed class StyleNano : StyleBase public sealed class StyleNano : StyleBase
{ {
public const string StyleClassBorderedWindowPanel = "BorderedWindowPanel"; public const string StyleClassBorderedWindowPanel = "BorderedWindowPanel";
public const string StyleClassInventorySlotBackground = "InventorySlotBackground";
public const string StyleClassHandSlotHighlight = "HandSlotHighlight";
public const string StyleClassTransparentBorderedWindowPanel = "TransparentBorderedWindowPanel"; public const string StyleClassTransparentBorderedWindowPanel = "TransparentBorderedWindowPanel";
public const string StyleClassHotbarPanel = "HotbarPanel"; public const string StyleClassHotbarPanel = "HotbarPanel";
public const string StyleClassTooltipPanel = "tooltipBox"; public const string StyleClassTooltipPanel = "tooltipBox";
@@ -44,7 +47,9 @@ namespace Content.Client.UserInterface.Stylesheets
public static readonly Color NanoGold = Color.FromHex("#A88B5E"); public static readonly Color NanoGold = Color.FromHex("#A88B5E");
public static readonly Color ButtonColorDefault = Color.FromHex("#464966"); public static readonly Color ButtonColorDefault = Color.FromHex("#464966");
public static readonly Color ButtonColorDefaultRed = Color.FromHex("#D43B3B");
public static readonly Color ButtonColorHovered = Color.FromHex("#575b7f"); public static readonly Color ButtonColorHovered = Color.FromHex("#575b7f");
public static readonly Color ButtonColorHoveredRed = Color.FromHex("#DF6B6B");
public static readonly Color ButtonColorPressed = Color.FromHex("#3e6c45"); public static readonly Color ButtonColorPressed = Color.FromHex("#3e6c45");
public static readonly Color ButtonColorDisabled = Color.FromHex("#30313c"); public static readonly Color ButtonColorDisabled = Color.FromHex("#30313c");
@@ -100,6 +105,21 @@ namespace Content.Client.UserInterface.Stylesheets
}; };
borderedWindowBackground.SetPatchMargin(StyleBox.Margin.All, 2); borderedWindowBackground.SetPatchMargin(StyleBox.Margin.All, 2);
var invSlotBgTex = resCache.GetTexture("/Textures/Interface/Inventory/inv_slot_background.png");
var invSlotBg = new StyleBoxTexture
{
Texture = invSlotBgTex,
};
invSlotBg.SetPatchMargin(StyleBox.Margin.All, 2);
invSlotBg.SetContentMarginOverride(StyleBox.Margin.All, 0);
var handSlotHighlightTex = resCache.GetTexture("/Textures/Interface/Inventory/hand_slot_highlight.png");
var handSlotHighlight = new StyleBoxTexture
{
Texture = handSlotHighlightTex,
};
handSlotHighlight.SetPatchMargin(StyleBox.Margin.All, 2);
var borderedTransparentWindowBackgroundTex = resCache.GetTexture("/Textures/Interface/Nano/transparent_window_background_bordered.png"); var borderedTransparentWindowBackgroundTex = resCache.GetTexture("/Textures/Interface/Nano/transparent_window_background_bordered.png");
var borderedTransparentWindowBackground = new StyleBoxTexture var borderedTransparentWindowBackground = new StyleBoxTexture
{ {
@@ -162,6 +182,33 @@ namespace Content.Client.UserInterface.Stylesheets
Modulate = ButtonColorPressed Modulate = ButtonColorPressed
}; };
var buttonTex = resCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var topButtonBase = new StyleBoxTexture
{
Texture = buttonTex,
};
topButtonBase.SetPatchMargin(StyleBox.Margin.All, 10);
topButtonBase.SetPadding(StyleBox.Margin.All, 0);
topButtonBase.SetContentMarginOverride(StyleBox.Margin.All, 0);
var topButtonOpenRight = new StyleBoxTexture(topButtonBase)
{
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions((0, 0), (14, 24))),
};
topButtonOpenRight.SetPatchMargin(StyleBox.Margin.Right, 0);
var topButtonOpenLeft = new StyleBoxTexture(topButtonBase)
{
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions((10, 0), (14, 24))),
};
topButtonOpenLeft.SetPatchMargin(StyleBox.Margin.Left, 0);
var topButtonSquare = new StyleBoxTexture(topButtonBase)
{
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions((10, 0), (3, 24))),
};
topButtonSquare.SetPatchMargin(StyleBox.Margin.Horizontal, 0);
var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png"); var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png");
var lineEditTex = resCache.GetTexture("/Textures/Interface/Nano/lineedit.png"); var lineEditTex = resCache.GetTexture("/Textures/Interface/Nano/lineedit.png");
@@ -355,6 +402,20 @@ namespace Content.Client.UserInterface.Stylesheets
{ {
new StyleProperty(PanelContainer.StylePropertyPanel, borderedTransparentWindowBackground), new StyleProperty(PanelContainer.StylePropertyPanel, borderedTransparentWindowBackground),
}), }),
// inventory slot background
new StyleRule(
new SelectorElement(null, new[] {StyleClassInventorySlotBackground}, null, null),
new[]
{
new StyleProperty(PanelContainer.StylePropertyPanel, invSlotBg),
}),
// hand slot highlight
new StyleRule(
new SelectorElement(null, new[] {StyleClassHandSlotHighlight}, null, null),
new[]
{
new StyleProperty(PanelContainer.StylePropertyPanel, handSlotHighlight),
}),
// Hotbar background // Hotbar background
new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {StyleClassHotbarPanel}, null, null), new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {StyleClassHotbarPanel}, null, null),
new[] new[]
@@ -410,6 +471,10 @@ namespace Content.Client.UserInterface.Stylesheets
.Class(ButtonOpenBoth) .Class(ButtonOpenBoth)
.Prop(ContainerButton.StylePropertyStyleBox, BaseButtonOpenBoth), .Prop(ContainerButton.StylePropertyStyleBox, BaseButtonOpenBoth),
Element<ContainerButton>().Class(ContainerButton.StyleClassButton)
.Class(ButtonSquare)
.Prop(ContainerButton.StylePropertyStyleBox, BaseButtonSquare),
new StyleRule(new SelectorElement(typeof(Label), new[] { Button.StyleClassButton }, null, null), new[] new StyleRule(new SelectorElement(typeof(Label), new[] { Button.StyleClassButton }, null, null), new[]
{ {
new StyleProperty(Label.StylePropertyAlignMode, Label.AlignMode.Center), new StyleProperty(Label.StylePropertyAlignMode, Label.AlignMode.Center),
@@ -808,8 +873,43 @@ namespace Content.Client.UserInterface.Stylesheets
}), }),
// Those top menu buttons. // Those top menu buttons.
Element<GameHud.TopButton>() // these use slight variations on the various BaseButton styles so that the content within them appears centered,
.Prop(Button.StylePropertyStyleBox, BaseButton), // which is NOT the case for the default BaseButton styles (OpenLeft/OpenRight adds extra padding on one of the sides
// which makes the TopButton icons appear off-center, which we don't want).
new StyleRule(
new SelectorElement(typeof(GameHud.TopButton), new[] {ButtonSquare}, null, null),
new[]
{
new StyleProperty(Button.StylePropertyStyleBox, topButtonSquare),
}),
new StyleRule(
new SelectorElement(typeof(GameHud.TopButton), new[] {ButtonOpenLeft}, null, null),
new[]
{
new StyleProperty(Button.StylePropertyStyleBox, topButtonOpenLeft),
}),
new StyleRule(
new SelectorElement(typeof(GameHud.TopButton), new[] {ButtonOpenRight}, null, null),
new[]
{
new StyleProperty(Button.StylePropertyStyleBox, topButtonOpenRight),
}),
new StyleRule(
new SelectorElement(typeof(GameHud.TopButton), null, null, new[] {Button.StylePseudoClassNormal}),
new[]
{
new StyleProperty(Button.StylePropertyModulateSelf, ButtonColorDefault),
}),
new StyleRule(
new SelectorElement(typeof(GameHud.TopButton), new[] {GameHud.TopButton.StyleClassRedTopButton}, null, new[] {Button.StylePseudoClassNormal}),
new[]
{
new StyleProperty(Button.StylePropertyModulateSelf, ButtonColorDefaultRed),
}),
new StyleRule( new StyleRule(
new SelectorElement(typeof(GameHud.TopButton), null, null, new[] {Button.StylePseudoClassNormal}), new SelectorElement(typeof(GameHud.TopButton), null, null, new[] {Button.StylePseudoClassNormal}),
@@ -832,6 +932,13 @@ namespace Content.Client.UserInterface.Stylesheets
new StyleProperty(Button.StylePropertyModulateSelf, ButtonColorHovered), new StyleProperty(Button.StylePropertyModulateSelf, ButtonColorHovered),
}), }),
new StyleRule(
new SelectorElement(typeof(GameHud.TopButton), new[] {GameHud.TopButton.StyleClassRedTopButton}, null, new[] {Button.StylePseudoClassHover}),
new[]
{
new StyleProperty(Button.StylePropertyModulateSelf, ButtonColorHoveredRed),
}),
new StyleRule( new StyleRule(
new SelectorElement(typeof(Label), new[] {GameHud.TopButton.StyleClassLabelTopButton}, null, null), new SelectorElement(typeof(Label), new[] {GameHud.TopButton.StyleClassLabelTopButton}, null, null),
new[] new[]

View File

@@ -61,6 +61,10 @@ namespace Content.Client.UserInterface.Stylesheets
.Class(ButtonOpenBoth) .Class(ButtonOpenBoth)
.Prop(ContainerButton.StylePropertyStyleBox, BaseButtonOpenBoth), .Prop(ContainerButton.StylePropertyStyleBox, BaseButtonOpenBoth),
Element<ContainerButton>().Class(ContainerButton.StyleClassButton)
.Class(ButtonSquare)
.Prop(ContainerButton.StylePropertyStyleBox, BaseButtonSquare),
// Colors for the buttons. // Colors for the buttons.
Element<ContainerButton>().Class(ContainerButton.StyleClassButton) Element<ContainerButton>().Class(ContainerButton.StyleClassButton)
.Pseudo(ContainerButton.StylePseudoClassNormal) .Pseudo(ContainerButton.StylePseudoClassNormal)

View File

@@ -18,7 +18,6 @@ namespace Content.Client.UserInterface.Suspicion
public class SuspicionGui : Control public class SuspicionGui : Control
{ {
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly VBoxContainer _container; private readonly VBoxContainer _container;
private readonly Button _roleButton; private readonly Button _roleButton;
@@ -58,14 +57,11 @@ namespace Content.Client.UserInterface.Suspicion
return; return;
} }
var allies = string.Join(", ", var allies = string.Join(", ", role.Allies.Select(tuple => tuple.name));
role.Allies.Select(uid => _entityManager.GetEntity(uid).Name));
var message = role.Allies.Count switch var message = role.Allies.Count switch
{ {
0 => Loc.GetString("You have no allies"), 0 => Loc.GetString("You have no allies"),
1 => Loc.GetString("Your ally is {0}", allies), var n => Loc.GetPluralString("Your ally is {0}", "Your allies are {0}", n, allies),
var n when n > 2 => Loc.GetString("Your allies are {0}", allies),
_ => throw new ArgumentException($"Invalid number of allies: {role.Allies.Count}")
}; };
role.Owner.PopupMessage(message); role.Owner.PopupMessage(message);

View File

@@ -4,6 +4,7 @@ using Content.Client;
using Content.Client.Interfaces.Parallax; using Content.Client.Interfaces.Parallax;
using Content.Server; using Content.Server;
using Content.Server.Interfaces.GameTicking; using Content.Server.Interfaces.GameTicking;
using Content.Shared;
using NUnit.Framework; using NUnit.Framework;
using Robust.Server.Interfaces.Maps; using Robust.Server.Interfaces.Maps;
using Robust.Server.Interfaces.Timing; using Robust.Server.Interfaces.Timing;
@@ -48,7 +49,7 @@ namespace Content.IntegrationTests
// Connecting to Discord is a massive waste of time. // Connecting to Discord is a massive waste of time.
// Basically just makes the CI logs a mess. // Basically just makes the CI logs a mess.
options.CVarOverrides["discord.enabled"] = "true"; options.CVarOverrides["discord.enabled"] = "false";
return base.StartClient(options); return base.StartClient(options);
} }
@@ -76,6 +77,9 @@ namespace Content.IntegrationTests
}); });
}; };
// Avoid funny race conditions with the database.
options.CVarOverrides[CCVars.DatabaseSynchronous.Name] = "true";
return base.StartServer(options); return base.StartServer(options);
} }

View File

@@ -20,9 +20,15 @@ namespace Content.IntegrationTests.Tests.Destructible
[TestOf(typeof(Threshold))] [TestOf(typeof(Threshold))]
public class DestructibleTests : ContentIntegrationTest public class DestructibleTests : ContentIntegrationTest
{ {
private static readonly string SpawnedEntityId = "DestructibleTestsSpawnedEntity";
private static readonly string DestructibleEntityId = "DestructibleTestsDestructibleEntity"; private static readonly string DestructibleEntityId = "DestructibleTestsDestructibleEntity";
private static readonly string DestructibleDestructionEntityId = "DestructibleTestsDestructibleDestructionEntity";
private static readonly string Prototypes = $@" private static readonly string Prototypes = $@"
- type: entity
id: {SpawnedEntityId}
name: {SpawnedEntityId}
- type: entity - type: entity
id: {DestructibleEntityId} id: {DestructibleEntityId}
name: {DestructibleEntityId} name: {DestructibleEntityId}
@@ -35,15 +41,35 @@ namespace Content.IntegrationTests.Tests.Destructible
50: 50:
triggersOnce: false triggersOnce: false
behaviors: behaviors:
- !type:DoActsBehavior
acts: [""Breakage""]
- !type:PlaySoundBehavior - !type:PlaySoundBehavior
sound: /Audio/Effects/woodhit.ogg sound: /Audio/Effects/woodhit.ogg
- !type:SpawnEntitiesBehavior - !type:SpawnEntitiesBehavior
spawn: spawn:
WoodPlank: {SpawnedEntityId}:
min: 1 min: 1
max: 1 max: 1
- !type:DoActsBehavior
acts: [""Breakage""]
- type: TestThresholdListener
- type: entity
id: {DestructibleDestructionEntityId}
name: {DestructibleDestructionEntityId}
components:
- type: Damageable
- type: Destructible
thresholds:
50:
behaviors:
- !type:PlaySoundBehavior
sound: /Audio/Effects/woodhit.ogg
- !type:SpawnEntitiesBehavior
spawn:
{SpawnedEntityId}:
min: 1
max: 1
- !type:DoActsBehavior # This must come last as it destroys the entity.
acts: [""Destruction""]
- type: TestThresholdListener - type: TestThresholdListener
"; ";
@@ -147,15 +173,15 @@ namespace Content.IntegrationTests.Tests.Destructible
// Check that it matches the YAML prototype // Check that it matches the YAML prototype
Assert.That(threshold.Behaviors, Has.Count.EqualTo(3)); Assert.That(threshold.Behaviors, Has.Count.EqualTo(3));
var actsThreshold = (DoActsBehavior) threshold.Behaviors[0]; var soundThreshold = (PlaySoundBehavior) threshold.Behaviors[0];
var soundThreshold = (PlaySoundBehavior) threshold.Behaviors[1]; var spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[1];
var spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[2]; var actsThreshold = (DoActsBehavior) threshold.Behaviors[2];
Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage)); Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage));
Assert.That(soundThreshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg")); Assert.That(soundThreshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg"));
Assert.That(spawnThreshold.Spawn, Is.Not.Null); Assert.That(spawnThreshold.Spawn, Is.Not.Null);
Assert.That(spawnThreshold.Spawn.Count, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Count, Is.EqualTo(1));
Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo("WoodPlank")); Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId));
Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1));
Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1));
Assert.That(threshold.Triggered, Is.True); Assert.That(threshold.Triggered, Is.True);
@@ -202,16 +228,16 @@ namespace Content.IntegrationTests.Tests.Destructible
// Check that it matches the YAML prototype // Check that it matches the YAML prototype
Assert.That(threshold.Behaviors, Has.Count.EqualTo(3)); Assert.That(threshold.Behaviors, Has.Count.EqualTo(3));
actsThreshold = (DoActsBehavior) threshold.Behaviors[0]; soundThreshold = (PlaySoundBehavior) threshold.Behaviors[0];
soundThreshold = (PlaySoundBehavior) threshold.Behaviors[1]; spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[1];
spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[2]; actsThreshold = (DoActsBehavior) threshold.Behaviors[2];
// Check that it matches the YAML prototype // Check that it matches the YAML prototype
Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage)); Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage));
Assert.That(soundThreshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg")); Assert.That(soundThreshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg"));
Assert.That(spawnThreshold.Spawn, Is.Not.Null); Assert.That(spawnThreshold.Spawn, Is.Not.Null);
Assert.That(spawnThreshold.Spawn.Count, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Count, Is.EqualTo(1));
Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo("WoodPlank")); Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId));
Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1));
Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1));
Assert.That(threshold.Triggered, Is.True); Assert.That(threshold.Triggered, Is.True);
@@ -254,16 +280,16 @@ namespace Content.IntegrationTests.Tests.Destructible
Assert.That(threshold.Behaviors, Has.Count.EqualTo(3)); Assert.That(threshold.Behaviors, Has.Count.EqualTo(3));
actsThreshold = (DoActsBehavior) threshold.Behaviors[0]; soundThreshold = (PlaySoundBehavior) threshold.Behaviors[0];
soundThreshold = (PlaySoundBehavior) threshold.Behaviors[1]; spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[1];
spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[2]; actsThreshold = (DoActsBehavior) threshold.Behaviors[2];
// Check that it matches the YAML prototype // Check that it matches the YAML prototype
Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage)); Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage));
Assert.That(soundThreshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg")); Assert.That(soundThreshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg"));
Assert.That(spawnThreshold.Spawn, Is.Not.Null); Assert.That(spawnThreshold.Spawn, Is.Not.Null);
Assert.That(spawnThreshold.Spawn.Count, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Count, Is.EqualTo(1));
Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo("WoodPlank")); Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId));
Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1));
Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1));
Assert.That(threshold.Triggered, Is.True); Assert.That(threshold.Triggered, Is.True);
@@ -305,5 +331,84 @@ namespace Content.IntegrationTests.Tests.Destructible
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty); Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
}); });
} }
[Test]
public async Task DestructibleDestructionTest()
{
var server = StartServerDummyTicker(new ServerContentIntegrationOption
{
ExtraPrototypes = Prototypes,
ContentBeforeIoC = () =>
{
IoCManager.Resolve<IComponentFactory>().Register<TestThresholdListenerComponent>();
}
});
await server.WaitIdleAsync();
var sEntityManager = server.ResolveDependency<IEntityManager>();
var sMapManager = server.ResolveDependency<IMapManager>();
IEntity sDestructibleEntity = null;
IDamageableComponent sDamageableComponent = null;
DestructibleComponent sDestructibleComponent = null;
TestThresholdListenerComponent sThresholdListenerComponent = null;
await server.WaitPost(() =>
{
var mapId = new MapId(1);
var coordinates = new MapCoordinates(0, 0, mapId);
sMapManager.CreateMap(mapId);
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleDestructionEntityId, coordinates);
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
sDestructibleComponent = sDestructibleEntity.GetComponent<DestructibleComponent>();
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
});
await server.WaitAssertion(() =>
{
var coordinates = sDestructibleEntity.Transform.Coordinates;
Assert.DoesNotThrow(() =>
{
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 50, true));
});
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
var threshold = sThresholdListenerComponent.ThresholdsReached[0].Threshold;
Assert.That(threshold.Triggered, Is.True);
Assert.That(threshold.Behaviors.Count, Is.EqualTo(3));
var spawnEntitiesBehavior = (SpawnEntitiesBehavior) threshold.Behaviors.Single(b => b is SpawnEntitiesBehavior);
Assert.That(spawnEntitiesBehavior.Spawn.Count, Is.EqualTo(1));
Assert.That(spawnEntitiesBehavior.Spawn.Keys.Single(), Is.EqualTo(SpawnedEntityId));
Assert.That(spawnEntitiesBehavior.Spawn.Values.Single(), Is.EqualTo(new MinMax {Min = 1, Max = 1}));
var entitiesInRange = sEntityManager.GetEntitiesInRange(coordinates, 2);
var found = false;
foreach (var entity in entitiesInRange)
{
if (entity.Prototype == null)
{
continue;
}
if (entity.Prototype.Name != SpawnedEntityId)
{
continue;
}
found = true;
break;
}
Assert.That(found, Is.True);
});
}
} }
} }

View File

@@ -54,17 +54,21 @@ namespace Content.IntegrationTests.Tests
} }
Assert.Multiple(() => { Assert.Multiple(() => {
Assert.That(one, Is.EqualTo(two)); Assert.That(two, Is.EqualTo(one));
var failed = TestContext.CurrentContext.Result.Assertions.FirstOrDefault(); var failed = TestContext.CurrentContext.Result.Assertions.FirstOrDefault();
if (failed != null) if (failed != null)
{ {
var path1 = Path.Combine(userData.RootDir!,rp1.ToRelativeSystemPath()); var oneTmp = Path.GetTempFileName();
var path2 = Path.Combine(userData.RootDir!,rp2.ToRelativeSystemPath()); var twoTmp = Path.GetTempFileName();
TestContext.AddTestAttachment(path1);
TestContext.AddTestAttachment(path2); File.WriteAllText(oneTmp, one);
File.WriteAllText(twoTmp, two);
TestContext.AddTestAttachment(oneTmp, "First save file");
TestContext.AddTestAttachment(twoTmp, "Second save file");
TestContext.Error.WriteLine("Complete output:"); TestContext.Error.WriteLine("Complete output:");
TestContext.Error.WriteLine(path1); TestContext.Error.WriteLine(oneTmp);
TestContext.Error.WriteLine(path2); TestContext.Error.WriteLine(twoTmp);
} }
}); });
} }

View File

@@ -1,4 +1,4 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.GameObjects.EntitySystems.StationEvents; using Content.Server.GameObjects.EntitySystems.StationEvents;
using NUnit.Framework; using NUnit.Framework;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
@@ -17,23 +17,27 @@ namespace Content.IntegrationTests.Tests.StationEvents
server.Assert(() => server.Assert(() =>
{ {
// Idle each event once // Idle each event
var stationEventsSystem = EntitySystem.Get<StationEventSystem>(); var stationEventsSystem = EntitySystem.Get<StationEventSystem>();
var dummyFrameTime = (float) IoCManager.Resolve<IGameTiming>().TickPeriod.TotalSeconds; var dummyFrameTime = (float) IoCManager.Resolve<IGameTiming>().TickPeriod.TotalSeconds;
foreach (var stationEvent in stationEventsSystem.StationEvents) foreach (var stationEvent in stationEventsSystem.StationEvents)
{ {
stationEvent.Announce();
stationEvent.Update(dummyFrameTime);
stationEvent.Startup(); stationEvent.Startup();
stationEvent.Update(dummyFrameTime); stationEvent.Update(dummyFrameTime);
stationEvent.Running = false;
stationEvent.Shutdown(); stationEvent.Shutdown();
Assert.That(stationEvent.Occurrences == 1); // Due to timings some events might startup twice when in reality they wouldn't.
Assert.That(stationEvent.Occurrences > 0);
} }
stationEventsSystem.Reset(); stationEventsSystem.Reset();
foreach (var stationEvent in stationEventsSystem.StationEvents) foreach (var stationEvent in stationEventsSystem.StationEvents)
{ {
Assert.That(stationEvent.Occurrences == 0); Assert.That(stationEvent.Occurrences, Is.EqualTo(0));
} }
}); });

View File

@@ -0,0 +1,575 @@
// <auto-generated />
using System;
using System.Net;
using Content.Server.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Content.Server.Database.Migrations.Postgres
{
[DbContext(typeof(PostgresServerDbContext))]
[Migration("20210103151756_BackpackPreference")]
partial class BackpackPreference
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseIdentityByDefaultColumns()
.HasAnnotation("Relational:MaxIdentifierLength", 63)
.HasAnnotation("ProductVersion", "5.0.0");
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
b.Property<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("user_id");
b.Property<int?>("AdminRankId")
.HasColumnType("integer")
.HasColumnName("admin_rank_id");
b.Property<string>("Title")
.HasColumnType("text")
.HasColumnName("title");
b.HasKey("UserId");
b.HasIndex("AdminRankId");
b.ToTable("admin");
});
modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("admin_flag_id")
.UseIdentityByDefaultColumn();
b.Property<Guid>("AdminId")
.HasColumnType("uuid")
.HasColumnName("admin_id");
b.Property<string>("Flag")
.IsRequired()
.HasColumnType("text")
.HasColumnName("flag");
b.Property<bool>("Negative")
.HasColumnType("boolean")
.HasColumnName("negative");
b.HasKey("Id");
b.HasIndex("AdminId");
b.HasIndex("Flag", "AdminId")
.IsUnique();
b.ToTable("admin_flag");
});
modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("admin_rank_id")
.UseIdentityByDefaultColumn();
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")
.HasColumnName("name");
b.HasKey("Id");
b.ToTable("admin_rank");
});
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("admin_rank_flag_id")
.UseIdentityByDefaultColumn();
b.Property<int>("AdminRankId")
.HasColumnType("integer")
.HasColumnName("admin_rank_id");
b.Property<string>("Flag")
.IsRequired()
.HasColumnType("text")
.HasColumnName("flag");
b.HasKey("Id");
b.HasIndex("AdminRankId");
b.HasIndex("Flag", "AdminRankId")
.IsUnique();
b.ToTable("admin_rank_flag");
});
modelBuilder.Entity("Content.Server.Database.Antag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("antag_id")
.UseIdentityByDefaultColumn();
b.Property<string>("AntagName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("antag_name");
b.Property<int>("ProfileId")
.HasColumnType("integer")
.HasColumnName("profile_id");
b.HasKey("Id");
b.HasIndex("ProfileId", "AntagName")
.IsUnique();
b.ToTable("antag");
});
modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("assigned_user_id_id")
.UseIdentityByDefaultColumn();
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("user_name");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.HasIndex("UserName")
.IsUnique();
b.ToTable("assigned_user_id");
});
modelBuilder.Entity("Content.Server.Database.Job", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("job_id")
.UseIdentityByDefaultColumn();
b.Property<string>("JobName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("job_name");
b.Property<int>("Priority")
.HasColumnType("integer")
.HasColumnName("priority");
b.Property<int>("ProfileId")
.HasColumnType("integer")
.HasColumnName("profile_id");
b.HasKey("Id");
b.HasIndex("ProfileId");
b.ToTable("job");
});
modelBuilder.Entity("Content.Server.Database.PostgresConnectionLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("connection_log_id")
.UseIdentityByDefaultColumn();
b.Property<IPAddress>("Address")
.IsRequired()
.HasColumnType("inet")
.HasColumnName("address");
b.Property<DateTime>("Time")
.HasColumnType("timestamp with time zone")
.HasColumnName("time");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("user_name");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("connection_log");
b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
});
modelBuilder.Entity("Content.Server.Database.PostgresPlayer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("player_id")
.UseIdentityByDefaultColumn();
b.Property<DateTime>("FirstSeenTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("first_seen_time");
b.Property<IPAddress>("LastSeenAddress")
.IsRequired()
.HasColumnType("inet")
.HasColumnName("last_seen_address");
b.Property<DateTime>("LastSeenTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_seen_time");
b.Property<string>("LastSeenUserName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("last_seen_user_name");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id");
b.HasIndex("LastSeenUserName");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("player");
b.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address");
});
modelBuilder.Entity("Content.Server.Database.PostgresServerBan", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("server_ban_id")
.UseIdentityByDefaultColumn();
b.Property<ValueTuple<IPAddress, int>?>("Address")
.HasColumnType("inet")
.HasColumnName("address");
b.Property<DateTime>("BanTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("ban_time");
b.Property<Guid?>("BanningAdmin")
.HasColumnType("uuid")
.HasColumnName("banning_admin");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("expiration_time");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("text")
.HasColumnName("reason");
b.Property<Guid?>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id");
b.HasIndex("Address");
b.HasIndex("UserId");
b.ToTable("server_ban");
b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
b.HasCheckConstraint("HaveEitherAddressOrUserId", "address IS NOT NULL OR user_id IS NOT NULL");
});
modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("unban_id")
.UseIdentityByDefaultColumn();
b.Property<int>("BanId")
.HasColumnType("integer")
.HasColumnName("ban_id");
b.Property<DateTime>("UnbanTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("unban_time");
b.Property<Guid?>("UnbanningAdmin")
.HasColumnType("uuid")
.HasColumnName("unbanning_admin");
b.HasKey("Id");
b.HasIndex("BanId")
.IsUnique();
b.ToTable("server_unban");
});
modelBuilder.Entity("Content.Server.Database.Preference", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("preference_id")
.UseIdentityByDefaultColumn();
b.Property<int>("SelectedCharacterSlot")
.HasColumnType("integer")
.HasColumnName("selected_character_slot");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("preference");
});
modelBuilder.Entity("Content.Server.Database.Profile", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("profile_id")
.UseIdentityByDefaultColumn();
b.Property<int>("Age")
.HasColumnType("integer")
.HasColumnName("age");
b.Property<string>("Backpack")
.IsRequired()
.HasColumnType("text")
.HasColumnName("backpack");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("char_name");
b.Property<string>("Clothing")
.IsRequired()
.HasColumnType("text")
.HasColumnName("clothing");
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("eye_color");
b.Property<string>("FacialHairColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("facial_hair_color");
b.Property<string>("FacialHairName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("facial_hair_name");
b.Property<string>("Gender")
.IsRequired()
.HasColumnType("text")
.HasColumnName("gender");
b.Property<string>("HairColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("hair_color");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("hair_name");
b.Property<int>("PreferenceId")
.HasColumnType("integer")
.HasColumnName("preference_id");
b.Property<int>("PreferenceUnavailable")
.HasColumnType("integer")
.HasColumnName("pref_unavailable");
b.Property<string>("Sex")
.IsRequired()
.HasColumnType("text")
.HasColumnName("sex");
b.Property<string>("SkinColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("skin_color");
b.Property<int>("Slot")
.HasColumnType("integer")
.HasColumnName("slot");
b.HasKey("Id");
b.HasIndex("PreferenceId");
b.HasIndex("Slot", "PreferenceId")
.IsUnique();
b.ToTable("profile");
});
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
.WithMany("Admins")
.HasForeignKey("AdminRankId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("AdminRank");
});
modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
{
b.HasOne("Content.Server.Database.Admin", "Admin")
.WithMany("Flags")
.HasForeignKey("AdminId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Admin");
});
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
{
b.HasOne("Content.Server.Database.AdminRank", "Rank")
.WithMany("Flags")
.HasForeignKey("AdminRankId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Rank");
});
modelBuilder.Entity("Content.Server.Database.Antag", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Antags")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.Job", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Jobs")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b =>
{
b.HasOne("Content.Server.Database.PostgresServerBan", "Ban")
.WithOne("Unban")
.HasForeignKey("Content.Server.Database.PostgresServerUnban", "BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ban");
});
modelBuilder.Entity("Content.Server.Database.Profile", b =>
{
b.HasOne("Content.Server.Database.Preference", "Preference")
.WithMany("Profiles")
.HasForeignKey("PreferenceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Preference");
});
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
b.Navigation("Flags");
});
modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
{
b.Navigation("Admins");
b.Navigation("Flags");
});
modelBuilder.Entity("Content.Server.Database.PostgresServerBan", b =>
{
b.Navigation("Unban");
});
modelBuilder.Entity("Content.Server.Database.Preference", b =>
{
b.Navigation("Profiles");
});
modelBuilder.Entity("Content.Server.Database.Profile", b =>
{
b.Navigation("Antags");
b.Navigation("Jobs");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Content.Server.Database.Migrations.Postgres
{
public partial class BackpackPreference : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "backpack",
table: "profile",
type: "text",
nullable: false,
defaultValue: "");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "backpack",
table: "profile");
}
}
}

View File

@@ -386,6 +386,11 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("age"); .HasColumnName("age");
b.Property<string>("Backpack")
.IsRequired()
.HasColumnType("text")
.HasColumnName("backpack");
b.Property<string>("CharacterName") b.Property<string>("CharacterName")
.IsRequired() .IsRequired()
.HasColumnType("text") .HasColumnType("text")

View File

@@ -0,0 +1,542 @@
// <auto-generated />
using System;
using Content.Server.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Content.Server.Database.Migrations.Sqlite
{
[DbContext(typeof(SqliteServerDbContext))]
[Migration("20210103151752_BackpackPreference")]
partial class BackpackPreference
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.0");
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
b.Property<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.Property<int?>("AdminRankId")
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_id");
b.Property<string>("Title")
.HasColumnType("TEXT")
.HasColumnName("title");
b.HasKey("UserId");
b.HasIndex("AdminRankId");
b.ToTable("admin");
});
modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_flag_id");
b.Property<Guid>("AdminId")
.HasColumnType("TEXT")
.HasColumnName("admin_id");
b.Property<string>("Flag")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("flag");
b.Property<bool>("Negative")
.HasColumnType("INTEGER")
.HasColumnName("negative");
b.HasKey("Id");
b.HasIndex("AdminId");
b.HasIndex("Flag", "AdminId")
.IsUnique();
b.ToTable("admin_flag");
});
modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_id");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("name");
b.HasKey("Id");
b.ToTable("admin_rank");
});
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_flag_id");
b.Property<int>("AdminRankId")
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_id");
b.Property<string>("Flag")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("flag");
b.HasKey("Id");
b.HasIndex("AdminRankId");
b.HasIndex("Flag", "AdminRankId")
.IsUnique();
b.ToTable("admin_rank_flag");
});
modelBuilder.Entity("Content.Server.Database.Antag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("antag_id");
b.Property<string>("AntagName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("antag_name");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
b.HasKey("Id");
b.HasIndex("ProfileId", "AntagName")
.IsUnique();
b.ToTable("antag");
});
modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("assigned_user_id_id");
b.Property<Guid>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("user_name");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.HasIndex("UserName")
.IsUnique();
b.ToTable("assigned_user_id");
});
modelBuilder.Entity("Content.Server.Database.Job", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("job_id");
b.Property<string>("JobName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("job_name");
b.Property<int>("Priority")
.HasColumnType("INTEGER")
.HasColumnName("priority");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
b.HasKey("Id");
b.HasIndex("ProfileId");
b.ToTable("job");
});
modelBuilder.Entity("Content.Server.Database.Preference", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("preference_id");
b.Property<int>("SelectedCharacterSlot")
.HasColumnType("INTEGER")
.HasColumnName("selected_character_slot");
b.Property<Guid>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("preference");
});
modelBuilder.Entity("Content.Server.Database.Profile", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
b.Property<int>("Age")
.HasColumnType("INTEGER")
.HasColumnName("age");
b.Property<string>("Backpack")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("backpack");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("char_name");
b.Property<string>("Clothing")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("clothing");
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("eye_color");
b.Property<string>("FacialHairColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("facial_hair_color");
b.Property<string>("FacialHairName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("facial_hair_name");
b.Property<string>("Gender")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("gender");
b.Property<string>("HairColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("hair_color");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("hair_name");
b.Property<int>("PreferenceId")
.HasColumnType("INTEGER")
.HasColumnName("preference_id");
b.Property<int>("PreferenceUnavailable")
.HasColumnType("INTEGER")
.HasColumnName("pref_unavailable");
b.Property<string>("Sex")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("sex");
b.Property<string>("SkinColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("skin_color");
b.Property<int>("Slot")
.HasColumnType("INTEGER")
.HasColumnName("slot");
b.HasKey("Id");
b.HasIndex("PreferenceId");
b.HasIndex("Slot", "PreferenceId")
.IsUnique();
b.ToTable("profile");
});
modelBuilder.Entity("Content.Server.Database.SqliteConnectionLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("connection_log_id");
b.Property<string>("Address")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("address");
b.Property<DateTime>("Time")
.HasColumnType("TEXT")
.HasColumnName("time");
b.Property<Guid>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("user_name");
b.HasKey("Id");
b.ToTable("connection_log");
});
modelBuilder.Entity("Content.Server.Database.SqlitePlayer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("player_id");
b.Property<DateTime>("FirstSeenTime")
.HasColumnType("TEXT")
.HasColumnName("first_seen_time");
b.Property<string>("LastSeenAddress")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("last_seen_address");
b.Property<DateTime>("LastSeenTime")
.HasColumnType("TEXT")
.HasColumnName("last_seen_time");
b.Property<string>("LastSeenUserName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("last_seen_user_name");
b.Property<Guid>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("Id");
b.HasIndex("LastSeenUserName");
b.ToTable("player");
});
modelBuilder.Entity("Content.Server.Database.SqliteServerBan", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("ban_id");
b.Property<string>("Address")
.HasColumnType("TEXT")
.HasColumnName("address");
b.Property<DateTime>("BanTime")
.HasColumnType("TEXT")
.HasColumnName("ban_time");
b.Property<Guid?>("BanningAdmin")
.HasColumnType("TEXT")
.HasColumnName("banning_admin");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("TEXT")
.HasColumnName("expiration_time");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("reason");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("Id");
b.ToTable("ban");
});
modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("unban_id");
b.Property<int>("BanId")
.HasColumnType("INTEGER")
.HasColumnName("ban_id");
b.Property<DateTime>("UnbanTime")
.HasColumnType("TEXT")
.HasColumnName("unban_time");
b.Property<Guid?>("UnbanningAdmin")
.HasColumnType("TEXT")
.HasColumnName("unbanning_admin");
b.HasKey("Id");
b.HasIndex("BanId")
.IsUnique();
b.ToTable("unban");
});
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
.WithMany("Admins")
.HasForeignKey("AdminRankId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("AdminRank");
});
modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
{
b.HasOne("Content.Server.Database.Admin", "Admin")
.WithMany("Flags")
.HasForeignKey("AdminId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Admin");
});
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
{
b.HasOne("Content.Server.Database.AdminRank", "Rank")
.WithMany("Flags")
.HasForeignKey("AdminRankId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Rank");
});
modelBuilder.Entity("Content.Server.Database.Antag", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Antags")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.Job", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Jobs")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.Profile", b =>
{
b.HasOne("Content.Server.Database.Preference", "Preference")
.WithMany("Profiles")
.HasForeignKey("PreferenceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Preference");
});
modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b =>
{
b.HasOne("Content.Server.Database.SqliteServerBan", "Ban")
.WithOne("Unban")
.HasForeignKey("Content.Server.Database.SqliteServerUnban", "BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ban");
});
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
b.Navigation("Flags");
});
modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
{
b.Navigation("Admins");
b.Navigation("Flags");
});
modelBuilder.Entity("Content.Server.Database.Preference", b =>
{
b.Navigation("Profiles");
});
modelBuilder.Entity("Content.Server.Database.Profile", b =>
{
b.Navigation("Antags");
b.Navigation("Jobs");
});
modelBuilder.Entity("Content.Server.Database.SqliteServerBan", b =>
{
b.Navigation("Unban");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Content.Server.Database.Migrations.Sqlite
{
public partial class BackpackPreference : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "backpack",
table: "profile",
type: "TEXT",
nullable: false,
defaultValue: "");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "backpack",
table: "profile");
}
}
}

View File

@@ -223,6 +223,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER") .HasColumnType("INTEGER")
.HasColumnName("age"); .HasColumnName("age");
b.Property<string>("Backpack")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("backpack");
b.Property<string>("CharacterName") b.Property<string>("CharacterName")
.IsRequired() .IsRequired()
.HasColumnType("TEXT") .HasColumnType("TEXT")

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@@ -99,6 +99,7 @@ namespace Content.Server.Database
[Column("eye_color")] public string EyeColor { get; set; } = null!; [Column("eye_color")] public string EyeColor { get; set; } = null!;
[Column("skin_color")] public string SkinColor { get; set; } = null!; [Column("skin_color")] public string SkinColor { get; set; } = null!;
[Column("clothing")] public string Clothing { get; set; } = null!; [Column("clothing")] public string Clothing { get; set; } = null!;
[Column("backpack")] public string Backpack { get; set; } = null!;
public List<Job> Jobs { get; } = new(); public List<Job> Jobs { get; } = new();
public List<Antag> Antags { get; } = new(); public List<Antag> Antags { get; } = new();

View File

@@ -5,38 +5,36 @@ namespace Content.Server.AI.Operators
{ {
public abstract class AiOperator public abstract class AiOperator
{ {
public bool HasStartup => _hasStartup; public bool HasStartup { get; private set; }
private bool _hasStartup = false;
private bool _hasShutdown = false; public bool HasShutdown { get; private set; }
/// <summary> /// <summary>
/// Called once when the AiLogicProcessor starts this action /// Called once when the AiLogicProcessor starts this action
/// </summary> /// </summary>
public virtual bool TryStartup() /// <returns>true if it hasn't started up previously</returns>
public virtual bool Startup()
{ {
// If we've already startup then no point continuing // If we've already startup then no point continuing
// This signals to the override that it's already startup // This signals to the override that it's already startup
// Should probably throw but it made some code elsewhere marginally easier // Should probably throw but it made some code elsewhere marginally easier
if (_hasStartup) if (HasStartup)
{
return false; return false;
}
_hasStartup = true; HasStartup = true;
return true; return true;
} }
/// <summary> /// <summary>
/// Called once when the AiLogicProcessor is done with this action if the outcome is successful or fails. /// Called once when the AiLogicProcessor is done with this action if the outcome is successful or fails.
/// </summary> /// </summary>
public virtual void Shutdown(Outcome outcome) public virtual bool Shutdown(Outcome outcome)
{ {
if (_hasShutdown) if (HasShutdown)
{ return false;
throw new InvalidOperationException("AiOperator has already shutdown");
}
_hasShutdown = true; HasShutdown = true;
return true;
} }
/// <summary> /// <summary>

View File

@@ -22,9 +22,9 @@ namespace Content.Server.AI.Operators.Combat.Melee
_burstTime = burstTime; _burstTime = burstTime;
} }
public override bool TryStartup() public override bool Startup()
{ {
if (!base.TryStartup()) if (!base.Startup())
{ {
return true; return true;
} }
@@ -42,13 +42,17 @@ namespace Content.Server.AI.Operators.Combat.Melee
return true; return true;
} }
public override void Shutdown(Outcome outcome) public override bool Shutdown(Outcome outcome)
{ {
base.Shutdown(outcome); if (!base.Shutdown(outcome))
return false;
if (_owner.TryGetComponent(out CombatModeComponent combatModeComponent)) if (_owner.TryGetComponent(out CombatModeComponent combatModeComponent))
{ {
combatModeComponent.IsInCombatMode = false; combatModeComponent.IsInCombatMode = false;
} }
return true;
} }
public override Outcome Execute(float frameTime) public override Outcome Execute(float frameTime)

View File

@@ -22,9 +22,9 @@ namespace Content.Server.AI.Operators.Combat.Melee
_burstTime = burstTime; _burstTime = burstTime;
} }
public override bool TryStartup() public override bool Startup()
{ {
if (!base.TryStartup()) if (!base.Startup())
{ {
return true; return true;
} }
@@ -51,13 +51,17 @@ namespace Content.Server.AI.Operators.Combat.Melee
return true; return true;
} }
public override void Shutdown(Outcome outcome) public override bool Shutdown(Outcome outcome)
{ {
base.Shutdown(outcome); if (!base.Shutdown(outcome))
return false;
if (_owner.TryGetComponent(out CombatModeComponent combatModeComponent)) if (_owner.TryGetComponent(out CombatModeComponent combatModeComponent))
{ {
combatModeComponent.IsInCombatMode = false; combatModeComponent.IsInCombatMode = false;
} }
return true;
} }
public override Outcome Execute(float frameTime) public override Outcome Execute(float frameTime)

View File

@@ -21,9 +21,9 @@ namespace Content.Server.AI.Operators.Inventory
_owner = owner; _owner = owner;
} }
public override bool TryStartup() public override bool Startup()
{ {
if (!base.TryStartup()) if (!base.Startup())
{ {
return true; return true;
} }
@@ -40,12 +40,15 @@ namespace Content.Server.AI.Operators.Inventory
return _target != null; return _target != null;
} }
public override void Shutdown(Outcome outcome) public override bool Shutdown(Outcome outcome)
{ {
base.Shutdown(outcome); if (!base.Shutdown(outcome))
return false;
var blackboard = UtilityAiHelpers.GetBlackboard(_owner); var blackboard = UtilityAiHelpers.GetBlackboard(_owner);
blackboard?.GetState<LastOpenedStorageState>().SetValue(null); blackboard?.GetState<LastOpenedStorageState>().SetValue(null);
return true;
} }
public override Outcome Execute(float frameTime) public override Outcome Execute(float frameTime)

View File

@@ -32,9 +32,9 @@ namespace Content.Server.AI.Operators.Movement
_requiresInRangeUnobstructed = requiresInRangeUnobstructed; _requiresInRangeUnobstructed = requiresInRangeUnobstructed;
} }
public override bool TryStartup() public override bool Startup()
{ {
if (!base.TryStartup()) if (!base.Startup())
{ {
return true; return true;
} }
@@ -45,11 +45,14 @@ namespace Content.Server.AI.Operators.Movement
return true; return true;
} }
public override void Shutdown(Outcome outcome) public override bool Shutdown(Outcome outcome)
{ {
base.Shutdown(outcome); if (!base.Shutdown(outcome))
return false;
var steering = EntitySystem.Get<AiSteeringSystem>(); var steering = EntitySystem.Get<AiSteeringSystem>();
steering.Unregister(_owner); steering.Unregister(_owner);
return true;
} }
public override Outcome Execute(float frameTime) public override Outcome Execute(float frameTime)

View File

@@ -21,9 +21,9 @@ namespace Content.Server.AI.Operators.Movement
DesiredRange = desiredRange; DesiredRange = desiredRange;
} }
public override bool TryStartup() public override bool Startup()
{ {
if (!base.TryStartup()) if (!base.Startup())
{ {
return true; return true;
} }
@@ -34,11 +34,14 @@ namespace Content.Server.AI.Operators.Movement
return true; return true;
} }
public override void Shutdown(Outcome outcome) public override bool Shutdown(Outcome outcome)
{ {
base.Shutdown(outcome); if (!base.Shutdown(outcome))
return false;
var steering = EntitySystem.Get<AiSteeringSystem>(); var steering = EntitySystem.Get<AiSteeringSystem>();
steering.Unregister(_owner); steering.Unregister(_owner);
return true;
} }
public override Outcome Execute(float frameTime) public override Outcome Execute(float frameTime)

View File

@@ -19,7 +19,7 @@ namespace Content.Server.AI.Operators.Sequences
} }
var op = Sequence.Peek(); var op = Sequence.Peek();
op.TryStartup(); op.Startup();
var outcome = op.Execute(frameTime); var outcome = op.Execute(frameTime);
switch (outcome) switch (outcome)

View File

@@ -78,7 +78,7 @@ namespace Content.Server.AI.Utility.Actions
return Outcome.Success; return Outcome.Success;
} }
op.TryStartup(); op.Startup();
var outcome = op.Execute(frameTime); var outcome = op.Execute(frameTime);
switch (outcome) switch (outcome)

View File

@@ -137,6 +137,8 @@ namespace Content.Server.AI.Utility.AiLogic
{ {
var currentOp = CurrentAction?.ActionOperators.Peek(); var currentOp = CurrentAction?.ActionOperators.Peek();
currentOp?.Shutdown(Outcome.Failed); currentOp?.Shutdown(Outcome.Failed);
CurrentAction?.Shutdown();
CurrentAction = null;
} }
public void MobStateChanged(MobStateChangedMessage message) public void MobStateChanged(MobStateChangedMessage message)

View File

@@ -0,0 +1,89 @@
#nullable enable
using System;
using System.Linq;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Pulling;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects;
using Content.Server.Utility;
using Content.Shared.Actions;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Pulling;
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Content.Shared.Interfaces;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
namespace Content.Server.Actions
{
[UsedImplicitly]
public class DisarmAction : ITargetEntityAction
{
private float _failProb;
private float _pushProb;
private float _cooldown;
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _failProb, "failProb", 0.4f);
serializer.DataField(ref _pushProb, "pushProb", 0.4f);
serializer.DataField(ref _cooldown, "cooldown", 1.5f);
}
public void DoTargetEntityAction(TargetEntityActionEventArgs args)
{
var disarmedActs = args.Target.GetAllComponents<IDisarmedAct>().ToArray();
if (disarmedActs.Length == 0 || !args.Performer.InRangeUnobstructed(args.Target)) return;
if (!args.Performer.TryGetComponent<SharedActionsComponent>(out var actions)) return;
if (args.Target == args.Performer || !args.Performer.CanAttack()) return;
var random = IoCManager.Resolve<IRobustRandom>();
var audio = EntitySystem.Get<AudioSystem>();
var system = EntitySystem.Get<MeleeWeaponSystem>();
var angle = new Angle(args.Target.Transform.MapPosition.Position - args.Performer.Transform.MapPosition.Position);
actions.Cooldown(ActionType.Disarm, Cooldowns.SecondsFromNow(_cooldown));
if (random.Prob(_failProb))
{
audio.PlayFromEntity("/Audio/Weapons/punchmiss.ogg", args.Performer,
AudioHelpers.WithVariation(0.025f));
args.Performer.PopupMessageOtherClients(Loc.GetString("{0} fails to disarm {1}!", args.Performer.Name, args.Target.Name));
args.Performer.PopupMessageCursor(Loc.GetString("You fail to disarm {0}!", args.Target.Name));
system.SendLunge(angle, args.Performer);
return;
}
system.SendAnimation("disarm", angle, args.Performer, args.Performer, new []{ args.Target });
var eventArgs = new DisarmedActEventArgs() {Target = args.Target, Source = args.Performer, PushProbability = _pushProb};
// Sort by priority.
Array.Sort(disarmedActs, (a, b) => a.Priority.CompareTo(b.Priority));
foreach (var disarmedAct in disarmedActs)
{
if (disarmedAct.Disarmed(eventArgs))
return;
}
audio.PlayFromEntity("/Audio/Effects/thudswoosh.ogg", args.Performer,
AudioHelpers.WithVariation(0.025f));
}
}
}

View File

@@ -1,4 +1,6 @@
using Content.Server.GameObjects.Components.Observer; using Content.Server.Commands.Observer;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.Interfaces.GameTicking;
using Content.Server.Players; using Content.Server.Players;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Console;
@@ -23,14 +25,15 @@ namespace Content.Server.Administration.Commands
return; return;
} }
var mind = player.ContentData().Mind; var mind = player.ContentData()?.Mind;
if (mind == null) if (mind == null)
{ {
shell.SendText(player, "You can't ghost here!"); shell.SendText(player, "You can't ghost here!");
return; return;
} }
if (mind.VisitingEntity != null && mind.VisitingEntity.Prototype.ID == "AdminObserver") if (mind.VisitingEntity != null && mind.VisitingEntity.Prototype?.ID == "AdminObserver")
{ {
var visiting = mind.VisitingEntity; var visiting = mind.VisitingEntity;
mind.UnVisit(); mind.UnVisit();
@@ -38,13 +41,22 @@ namespace Content.Server.Administration.Commands
} }
else else
{ {
var canReturn = mind.CurrentEntity != null && !mind.CurrentEntity.HasComponent<GhostComponent>(); var canReturn = mind.CurrentEntity != null;
var entityManager = IoCManager.Resolve<IEntityManager>(); var ghost = IoCManager.Resolve<IEntityManager>()
var ghost = entityManager.SpawnEntity("AdminObserver", player.AttachedEntity.Transform.MapPosition); .SpawnEntity("AdminObserver", player.AttachedEntity?.Transform.Coordinates
if(canReturn) ?? IoCManager.Resolve<IGameTicker>().GetObserverSpawnPoint());
if (canReturn)
{
ghost.Name = mind.CharacterName;
mind.Visit(ghost); mind.Visit(ghost);
}
else else
{
ghost.Name = player.Name;
mind.TransferTo(ghost); mind.TransferTo(ghost);
}
ghost.GetComponent<GhostComponent>().CanReturnToBody = canReturn; ghost.GetComponent<GhostComponent>().CanReturnToBody = canReturn;
} }
} }

Some files were not shown because too many files have changed in this diff Show More