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;
}
protected override void FocusExited()
protected override void KeyboardFocusExited()
{
if (!IsOpen) return;
if(_gameOver) return;

View File

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

View File

@@ -1,11 +1,13 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Content.Client.Administration;
using Content.Client.GameObjects.Components.Observer;
using Content.Client.Interfaces.Chat;
using Content.Shared.Administration;
using Content.Shared.Chat;
using Robust.Client.Console;
using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
@@ -25,6 +27,8 @@ namespace Content.Client.Chat
{
internal sealed class ChatManager : IChatManager, IPostInjectInit
{
[Dependency] private IPlayerManager _playerManager = default!;
private struct SpeechBubbleData
{
public string Message;
@@ -61,13 +65,14 @@ namespace Content.Client.Chat
private const char MeAlias = '@';
private const char AdminChatAlias = ']';
private readonly List<StoredChatMessage> filteredHistory = new();
private readonly List<StoredChatMessage> _filteredHistory = new();
// Filter Button States
private bool _allState;
private bool _localState;
private bool _oocState;
private bool _adminState;
private bool _deadState;
// Flag Enums for holding filtered channels
private ChatChannel _filteredChannels;
@@ -98,8 +103,8 @@ namespace Content.Client.Chat
public void Initialize()
{
_netManager.RegisterNetMessage<MsgChatMessage>(MsgChatMessage.NAME, _onChatMessage);
_netManager.RegisterNetMessage<ChatMaxMsgLengthMessage>(ChatMaxMsgLengthMessage.NAME, _onMaxLengthReceived);
_netManager.RegisterNetMessage<MsgChatMessage>(MsgChatMessage.NAME, OnChatMessage);
_netManager.RegisterNetMessage<ChatMaxMsgLengthMessage>(ChatMaxMsgLengthMessage.NAME, OnMaxLengthReceived);
_speechBubbleRoot = new LayoutContainer();
LayoutContainer.SetAnchorPreset(_speechBubbleRoot, LayoutContainer.LayoutPreset.Wide);
@@ -152,24 +157,25 @@ namespace Content.Client.Chat
{
if (_currentChatBox != null)
{
_currentChatBox.TextSubmitted -= _onChatBoxTextSubmitted;
_currentChatBox.FilterToggled -= _onFilterButtonToggled;
_currentChatBox.TextSubmitted -= OnChatBoxTextSubmitted;
_currentChatBox.FilterToggled -= OnFilterButtonToggled;
}
_currentChatBox = chatBox;
if (_currentChatBox != null)
{
_currentChatBox.TextSubmitted += _onChatBoxTextSubmitted;
_currentChatBox.FilterToggled += _onFilterButtonToggled;
_currentChatBox.TextSubmitted += OnChatBoxTextSubmitted;
_currentChatBox.FilterToggled += OnFilterButtonToggled;
_currentChatBox.AllButton.Pressed = !_allState;
_currentChatBox.LocalButton.Pressed = !_localState;
_currentChatBox.OOCButton.Pressed = !_oocState;
_currentChatBox.AdminButton.Pressed = !_adminState;
_currentChatBox.DeadButton.Pressed = !_deadState;
AdminStatusUpdated();
}
RepopulateChat(filteredHistory);
RepopulateChat(_filteredHistory);
}
public void RemoveSpeechBubble(EntityUid entityUid, SpeechBubble bubble)
@@ -224,7 +230,7 @@ namespace Content.Client.Chat
_currentChatBox?.AddLine(messageText, message.Channel, color);
}
private void _onChatBoxTextSubmitted(ChatBox chatBox, string text)
private void OnChatBoxTextSubmitted(ChatBox chatBox, string text)
{
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)
{
@@ -336,6 +342,13 @@ namespace Content.Client.Chat
_filteredChannels &= ~ChatChannel.AdminChat;
break;
}
case "Dead":
_deadState = !_deadState;
if (_deadState)
_filteredChannels |= ChatChannel.Dead;
else
_filteredChannels &= ~ChatChannel.Dead;
break;
case "ALL":
chatBox.LocalButton.Pressed ^= true;
@@ -346,7 +359,7 @@ namespace Content.Client.Chat
break;
}
RepopulateChat(filteredHistory);
RepopulateChat(_filteredHistory);
}
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
var storedMessage = new StoredChatMessage(msg);
filteredHistory.Add(storedMessage);
_filteredHistory.Add(storedMessage);
WriteChatMessage(storedMessage);
// Local messages that have an entity attached get a speech bubble.
@@ -378,7 +391,13 @@ namespace Content.Client.Chat
switch (msg.Channel)
{
case ChatChannel.Local:
AddSpeechBubble(msg, SpeechBubble.SpeechType.Say);
break;
case ChatChannel.Dead:
if (!_playerManager.LocalPlayer?.ControlledEntity?.HasComponent<GhostComponent>() ?? true)
break;
AddSpeechBubble(msg, SpeechBubble.SpeechType.Say);
break;
@@ -388,7 +407,7 @@ namespace Content.Client.Chat
}
}
private void _onMaxLengthReceived(ChatMaxMsgLengthMessage msg)
private void OnMaxLengthReceived(ChatMaxMsgLengthMessage msg)
{
_maxMessageLength = msg.MaxMessageLength;
}
@@ -522,6 +541,18 @@ namespace Content.Client.Chat
if (_currentChatBox != null)
{
_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
using System.Collections.Generic;
using Content.Client.GameObjects.Components.Construction;
using Content.Client.GameObjects.EntitySystems;
using Content.Shared.Construction;
using Robust.Client.Graphics;
using Robust.Client.Placement;
using Robust.Client.Utility;
using Robust.Shared.Interfaces.GameObjects;
@@ -49,7 +51,15 @@ namespace Content.Client.Construction
{
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("barSign");
prototypes.RegisterIgnore("objective");
prototypes.RegisterIgnore("dataset");
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();
_menu = new BlockGameMenu(this);
_menu.OnClose += () => SendMessage(new BlockGameMessages.BlockGameUserUnregisterMessage());
_menu.OnClose += Close;
_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 JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -10,6 +10,7 @@ using Robust.Shared.GameObjects.Components.Renderable;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
@@ -18,14 +19,18 @@ namespace Content.Client.GameObjects.Components.Atmos
[UsedImplicitly]
public class PipeVisualizer : AppearanceVisualizer
{
private string _rsiString;
private RSI _pipeRSI;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
var rsiString = node.GetNode("pipeRSI").ToString();
var rsiPath = SharedSpriteComponent.TextureRoot / rsiString;
var serializer = YamlObjectSerializer.NewReader(node);
serializer.DataField(ref _rsiString, "rsiString", "Constructible/Atmos/pipe.rsi");
var rsiPath = SharedSpriteComponent.TextureRoot / _rsiString;
try
{
var resourceCache = IoCManager.Resolve<IResourceCache>();
@@ -62,7 +67,6 @@ namespace Content.Client.GameObjects.Components.Atmos
{
var stateId = "pipe";
stateId += pipeVisualState.PipeDirection.PipeDirectionToPipeShape().ToString();
stateId += (int) pipeVisualState.ConduitLayer;
return stateId;
}

View File

@@ -1,9 +1,9 @@
using Content.Shared.GameObjects.Components.Atmos;
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.Utility;
using Robust.Shared.Serialization;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Atmos
@@ -16,7 +16,9 @@ namespace Content.Client.GameObjects.Components.Atmos
public override void LoadData(YamlMappingNode 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)

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
using Content.Client.GameObjects.Components.Disposal;
using Content.Client.GameObjects.Components.MedicalScanner;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
@@ -11,16 +9,14 @@ namespace Content.Client.GameObjects.Components.Body
[ComponentReference(typeof(IBody))]
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
{
[RegisterComponent]
[ComponentReference(typeof(SharedBuckleComponent))]
public class BuckleComponent : SharedBuckleComponent
{
private bool _buckled;

View File

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

View File

@@ -1,4 +1,4 @@
using Content.Shared.Chemistry;
#nullable enable
using Content.Shared.GameObjects.Components.Chemistry;
using Robust.Shared.GameObjects;
@@ -8,22 +8,6 @@ namespace Content.Client.GameObjects.Components.Chemistry
[ComponentReference(typeof(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)
{
if (!layer.Visible) continue;
if (layer.Texture != null)
{
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.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Disposal
@@ -7,5 +8,9 @@ namespace Content.Client.GameObjects.Components.Disposal
[ComponentReference(typeof(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.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Disposal
@@ -7,5 +8,9 @@ namespace Content.Client.GameObjects.Components.Disposal
[ComponentReference(typeof(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 Content.Client.GameObjects.Components.Clothing;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.Preferences.Appearance;
using Robust.Client.GameObjects;
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);
}
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)
{
base.HandleComponentState(curState, nextState);
@@ -117,6 +158,11 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
_slots.Remove(slot);
}
}
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? mod))
{
mod.RefreshMovementSpeedModifiers();
}
}
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 _hudButtonBack;
private ItemSlotButton _hudButtonId;
private Control _quickButtonsContainer;
private Control _rightQuickButtonsContainer;
private Control _leftQuickButtonsContainer;
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 _hudButtonId, Slots.IDCARD, "id");
_quickButtonsContainer = new HBoxContainer
_leftQuickButtonsContainer = new HBoxContainer
{
Children =
{
_hudButtonId,
_hudButtonBelt,
_hudButtonBack,
_hudButtonBelt,
},
SeparationOverride = 5
};
_rightQuickButtonsContainer = new HBoxContainer
{
Children =
{
_hudButtonPocket1,
_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();
GameHud.InventoryQuickButtonContainer.AddChild(_quickButtonsContainer);
GameHud.RightInventoryQuickButtonContainer.AddChild(_rightQuickButtonsContainer);
GameHud.LeftInventoryQuickButtonContainer.AddChild(_leftQuickButtonsContainer);
// Update all the buttons to make sure they check out.
@@ -183,7 +195,8 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
{
base.PlayerDetached();
GameHud.InventoryQuickButtonContainer.RemoveChild(_quickButtonsContainer);
GameHud.RightInventoryQuickButtonContainer.RemoveChild(_rightQuickButtonsContainer);
GameHud.LeftInventoryQuickButtonContainer.RemoveChild(_leftQuickButtonsContainer);
foreach (var (slot, list) in _inventoryButtons)
{
@@ -197,7 +210,7 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
private class HumanInventoryWindow : SS14Window
{
private const int ButtonSize = 64;
private const int ButtonSeparation = 2;
private const int ButtonSeparation = 4;
private const int RightSeparation = 2;
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.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.MedicalScanner
@@ -7,5 +8,9 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
[ComponentReference(typeof(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)
{
case Off: return "scanner_off";
case Open: return "scanner_open";
case Red: return "scanner_red";
case Death: return "scanner_death";
case Green: return "scanner_green";
case Yellow: return "scanner_yellow";
case Off: return "closed";
case Open: return "open";
case Red: return "closed";
case Death: return "closed";
case Green: return "occupied";
case Yellow: return "closed";
default:
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus");
}
@@ -39,12 +39,12 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
{
switch (status)
{
case Off: return "scanner_terminal_off";
case Open: return "scanner_terminal_blue";
case Red: return "scanner_terminal_red";
case Death: return "scanner_terminal_dead";
case Green: return "scanner_terminal_green";
case Yellow: return "scanner_terminal_blue";
case Off: return "off_unlit";
case Open: return "idle_unlit";
case Red: return "red_unlit";
case Death: return "red_unlit";
case Green: return "idle_unlit";
case Yellow: return "maint_unlit";
default:
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus");
}

View File

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

View File

@@ -184,8 +184,10 @@ namespace Content.Client.GameObjects.Components.Mobs
// only do something for actual target-based actions
if (_ui?.SelectingTargetFor?.Action == null ||
(_ui.SelectingTargetFor.Action.BehaviorType != BehaviorType.TargetEntity &&
_ui.SelectingTargetFor.Action.BehaviorType != BehaviorType.TargetPoint)) return false;
(!_ui.SelectingTargetFor.Action.IsTargetAction)) return false;
// do nothing if we know it's on cooldown
if (_ui.SelectingTargetFor.IsOnCooldown) return false;
var attempt = _ui.SelectingTargetFor.ActionAttempt();
if (attempt == null)
@@ -217,6 +219,13 @@ namespace Content.Client.GameObjects.Components.Mobs
}
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:
_ui.StopTargeting();
return false;

View File

@@ -12,12 +12,6 @@ namespace Content.Client.GameObjects.Components.Mobs
[ComponentReference(typeof(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)
{
base.HandleComponentState(curState, nextState);

View File

@@ -1,5 +1,8 @@
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.Components.Movement
{
@@ -7,6 +10,22 @@ namespace Content.Client.GameObjects.Components.Movement
[ComponentReference(typeof(IClimbable))]
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
{
[RegisterComponent]
[ComponentReference(typeof(SharedClimbingComponent))]
public class ClimbingComponent : SharedClimbingComponent
{
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
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
namespace Content.Client.GameObjects.Components
namespace Content.Client.GameObjects.Components.Observer
{
public sealed class AcceptCloningWindow : SS14Window
{
public readonly Button DenyButton;
public readonly Button ConfirmButton;
public readonly Button AcceptButton;
public AcceptCloningWindow()
{
@@ -23,18 +24,25 @@ namespace Content.Client.GameObjects.Components
{
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
{
Align = BoxContainer.AlignMode.Center,
Children =
{
(ConfirmButton = new Button
(AcceptButton = new Button
{
Text = Loc.GetString("Yes"),
}),
(new Control()
{
CustomMinimumSize = (20, 0)
}),
(DenyButton = new Button
{
Text = Loc.GetString("No"),

View File

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

View File

@@ -1,6 +1,7 @@
#nullable enable
using Content.Shared.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Client.GameObjects.Components
{
@@ -9,6 +10,8 @@ namespace Content.Client.GameObjects.Components
public class PlaceableSurfaceComponent : SharedPlaceableSurfaceComponent
{
private bool _isPlaceable;
private bool _placeCentered;
private Vector2 _positionOffset;
public override bool IsPlaceable
{
@@ -22,7 +25,36 @@ namespace Content.Client.GameObjects.Components
_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;
_placeCentered = state.PlaceCentered;
_positionOffset = state.PositionOffset;
}
}
}

View File

@@ -1,5 +1,6 @@
#nullable enable
using Content.Shared.GameObjects.Components.Strap;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Strap
@@ -8,5 +9,9 @@ namespace Content.Client.GameObjects.Components.Strap
[ComponentReference(typeof(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.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Suspicion
{
@@ -28,6 +25,7 @@ namespace Content.Client.GameObjects.Components.Suspicion
private SuspicionGui? _gui;
private string? _role;
private bool? _antagonist;
private bool _overlayActive;
public string? Role
{
@@ -67,37 +65,8 @@ namespace Content.Client.GameObjects.Components.Suspicion
}
}
public HashSet<EntityUid> 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);
}
[ViewVariables]
public List<(string name, EntityUid uid)> Allies { get; } = new();
private void AddTraitorOverlay()
{
@@ -106,12 +75,18 @@ namespace Content.Client.GameObjects.Components.Suspicion
return;
}
var overlay = new TraitorOverlay(Owner, Owner.EntityManager, _resourceCache, _eyeManager);
_overlayActive = true;
var overlay = new TraitorOverlay(Owner.EntityManager, _resourceCache, _eyeManager);
_overlayManager.AddOverlay(overlay);
}
private void RemoveTraitorOverlay()
{
if (!_overlayActive)
{
return;
}
_overlayManager.RemoveOverlay(nameof(TraitorOverlay));
}
@@ -126,6 +101,8 @@ namespace Content.Client.GameObjects.Components.Suspicion
Role = state.Role;
Antagonist = state.Antagonist;
Allies.Clear();
Allies.AddRange(state.Allies);
}
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()
{
base.OnRemove();

View File

@@ -1,14 +1,14 @@
using System.Collections.Generic;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Graphics.Overlays;
using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
@@ -18,37 +18,25 @@ namespace Content.Client.GameObjects.Components.Suspicion
{
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
private readonly IPlayerManager _playerManager;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
private readonly Font _font;
private readonly IEntity _user;
private readonly HashSet<EntityUid> _allies = new();
private readonly string _traitorText = Loc.GetString("Traitor");
public TraitorOverlay(
IEntity user,
IEntityManager entityManager,
IResourceCache resourceCache,
IEyeManager eyeManager)
: base(nameof(TraitorOverlay))
{
_playerManager = IoCManager.Resolve<IPlayerManager>();
_entityManager = entityManager;
_eyeManager = eyeManager;
_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)
@@ -65,23 +53,29 @@ namespace Content.Client.GameObjects.Components.Suspicion
{
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
if (!_entityManager.TryGetEntity(uid, out var ally))
{
return;
continue;
}
if (!ally.TryGetComponent(out IPhysicsComponent physics))
{
return;
continue;
}
if (!ExamineSystemShared.InRangeUnOccluded(_user.Transform.MapPosition, ally.Transform.MapPosition, 15,
entity => entity == _user || entity == ally))
if (!ExamineSystemShared.InRangeUnOccluded(ent.Transform.MapPosition, ally.Transform.MapPosition, 15,
entity => entity == ent || entity == ally))
{
return;
continue;
}
// 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)
return;
if (_eyeManager.CurrentMap != AttachedEntity.Transform.MapID)
{
Visible = false;
return;
}
else
{
Visible = true;
}
// Set position ready for 2nd+ frames.
var screenCoordinates = _eyeManager.CoordinatesToScreen(AttachedEntity.Transform.Coordinates);
_playerPosition = new ScreenCoordinates(screenCoordinates.X / UIScale, screenCoordinates.Y / UIScale);

View File

@@ -1,24 +1,32 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Client.State;
using Content.Client.Utility;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.EntitySystemMessages;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.GameObjects.EntitySystems;
using Robust.Client.Graphics.Shaders;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.State;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using DrawDepth = Content.Shared.GameObjects.DrawDepth;
namespace Content.Client.GameObjects.EntitySystems
{
@@ -29,7 +37,9 @@ namespace Content.Client.GameObjects.EntitySystems
public class DragDropSystem : EntitySystem
{
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
// how often to recheck possible targets (prevents calling expensive
@@ -45,9 +55,11 @@ namespace Content.Client.GameObjects.EntitySystems
private const string ShaderDropTargetOutOfRange = "SelectionOutline";
// entity performing the drag action
private IEntity _dragger;
private IEntity? _dragger;
private readonly List<IDraggable> _draggables = new();
private IEntity _dragShadow;
private IEntity? _dragShadow;
// time since mouse down over the dragged entity
private float _mouseDownTime;
// 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
private bool _isReplaying;
private DragDropHelper<IEntity> _dragDropHelper;
private DragDropHelper<IEntity> _dragDropHelper = default!;
private ShaderInstance _dropTargetInRangeShader;
private ShaderInstance _dropTargetOutOfRangeShader;
private SharedInteractionSystem _interactionSystem;
private InputSystem _inputSystem;
private ShaderInstance? _dropTargetInRangeShader;
private ShaderInstance? _dropTargetOutOfRangeShader;
private SharedInteractionSystem _interactionSystem = default!;
private InputSystem _inputSystem = default!;
private readonly List<SpriteComponent> _highlightedSprites = new();
private readonly List<ISpriteComponent> _highlightedSprites = new();
public override void Initialize()
{
@@ -112,7 +124,7 @@ namespace Content.Client.GameObjects.EntitySystems
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
// the mouse, canceling the drag, but just being cautious)
_dragDropHelper.EndDrag();
@@ -130,7 +142,7 @@ namespace Content.Client.GameObjects.EntitySystems
var canDrag = false;
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))
{
_draggables.Add(draggable);
@@ -156,7 +168,6 @@ namespace Content.Client.GameObjects.EntitySystems
return false;
}
private bool OnBeginDrag()
{
if (_dragDropHelper.Dragged == null || _dragDropHelper.Dragged.Deleted)
@@ -183,6 +194,7 @@ namespace Content.Client.GameObjects.EntitySystems
}
HighlightTargets();
EntityManager.EventBus.RaiseEvent(EventSource.Local, new OutlineToggleMessage(false));
// drag initiated
return true;
@@ -209,6 +221,9 @@ namespace Content.Client.GameObjects.EntitySystems
var mousePos = _eyeManager.ScreenToMap(_dragDropHelper.MouseScreenPosition);
// 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?
if (_dragShadow == null)
return false;
_dragShadow.Transform.WorldPosition = mousePos.Position;
_targetRecheckTime += frameTime;
@@ -229,6 +244,7 @@ namespace Content.Client.GameObjects.EntitySystems
EntityManager.DeleteEntity(_dragShadow);
}
EntityManager.EventBus.RaiseEvent(EventSource.Local, new OutlineToggleMessage(true));
_dragShadow = null;
_draggables.Clear();
_dragger = null;
@@ -238,7 +254,7 @@ namespace Content.Client.GameObjects.EntitySystems
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
// replaying the original cmd
@@ -251,40 +267,68 @@ namespace Content.Client.GameObjects.EntitySystems
var adjustedInputMsg = new FullInputCmdMessage(args.OriginalMessage.Tick, args.OriginalMessage.SubTick,
replayMsg.InputFunctionId, replayMsg.State, replayMsg.Coordinates, replayMsg.ScreenCoordinates, replayMsg.Uid);
_inputSystem.HandleInputCommand(savedValue.Session, EngineKeyFunctions.Use,
adjustedInputMsg, true);
if (savedValue.Session != null)
{
_inputSystem.HandleInputCommand(savedValue.Session, EngineKeyFunctions.Use, adjustedInputMsg, true);
}
_isReplaying = false;
}
_dragDropHelper.EndDrag();
return false;
}
// 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
if (_dragger == null)
{
_dragDropHelper.EndDrag();
return false;
}
// 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
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.
// We don't use args.EntityUid here because drag interactions generally should
// work even if there's something "on top" of the drop target
if (!_interactionSystem.InRangeUnobstructed(_dragger,
args.Coordinates, ignoreInsideBlocker: true))
args.Coordinates, range, ignoreInsideBlocker: true))
{
_dragDropHelper.EndDrag();
return false;
}
var entities = GameScreenBase.GetEntitiesUnderPosition(_stateManager, args.Coordinates);
var outOfRange = false;
foreach (var entity in entities)
{
// check if it's able to be dropped on by current 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)
{
if (!draggable.CanDrop(dropArgs))
{
continue;
}
if (!draggable.CanDrop(dropArgs)) continue;
// tell the server about the drop attempt
RaiseNetworkEvent(new DragDropMessage(args.Coordinates, _dragDropHelper.Dragged.Uid,
@@ -292,11 +336,22 @@ namespace Content.Client.GameObjects.EntitySystems
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();
return true;
}
}
if (outOfRange)
{
_playerManager.LocalPlayer?.ControlledEntity?.PopupMessage(Loc.GetString("You can't reach there!"));
}
_dragDropHelper.EndDrag();
return false;
}
@@ -304,7 +359,9 @@ namespace Content.Client.GameObjects.EntitySystems
private void HighlightTargets()
{
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 " +
"dragging anything or dragged entity / shadow was deleted.");
@@ -319,28 +376,42 @@ namespace Content.Client.GameObjects.EntitySystems
// find possible targets on screen even if not reachable
// TODO: Duplicated in SpriteSystem
var pvsBounds = _eyeManager.GetWorldViewport().Enlarged(5);
var pvsEntities = EntityManager.GetEntitiesIntersecting(_eyeManager.CurrentMap, pvsBounds, true);
var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition).Position;
var bounds = new Box2(mousePos - 1.5f, mousePos + 1.5f);
var pvsEntities = EntityManager.GetEntitiesIntersecting(_eyeManager.CurrentMap, bounds, true);
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
if (inRangeSprite.Visible == false) continue;
var valid = (bool?) null;
// check if it's able to be dropped on by current dragged entity
var dropArgs = new DragDropEventArgs(_dragger, pvsEntity.Transform.Coordinates, _dragDropHelper.Dragged, pvsEntity);
foreach (var comp in pvsEntity.GetAllComponents<IDragDropOn>())
{
// can't highlight if there's no sprite or it's not visible
if (inRangeSprite.Visible == false) continue;
valid = comp.CanDragDropOn(dropArgs);
// check if it's able to be dropped on by current dragged entity
var canDropArgs = new CanDropEventArgs(_dragger, _dragDropHelper.Dragged, pvsEntity);
var anyValidDraggable = _draggables.Any(draggable => draggable.CanDrop(canDropArgs));
if (anyValidDraggable)
{
// highlight depending on whether its in or out of range
var inRange = _interactionSystem.InRangeUnobstructed(_dragger, pvsEntity);
inRangeSprite.PostShader = inRange ? _dropTargetInRangeShader : _dropTargetOutOfRangeShader;
inRangeSprite.RenderOrder = EntityManager.CurrentTick.Value;
_highlightedSprites.Add(inRangeSprite);
}
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
inRangeSprite.PostShader = valid.Value ? _dropTargetInRangeShader : _dropTargetOutOfRangeShader;
inRangeSprite.RenderOrder = EntityManager.CurrentTick.Value;
_highlightedSprites.Add(inRangeSprite);
}
}
@@ -351,6 +422,7 @@ namespace Content.Client.GameObjects.EntitySystems
highlightedSprite.PostShader = null;
highlightedSprite.RenderOrder = 0;
}
_highlightedSprites.Clear();
}

View File

@@ -28,6 +28,7 @@ namespace Content.Client.GameObjects.EntitySystems
public override void Initialize()
{
SubscribeNetworkEvent<PlayMeleeWeaponAnimationMessage>(PlayWeaponArc);
SubscribeNetworkEvent<PlayLungeAnimationMessage>(PlayLunge);
}
public override void FrameUpdate(float frameTime)
@@ -50,39 +51,42 @@ namespace Content.Client.GameObjects.EntitySystems
var attacker = EntityManager.GetEntity(msg.Attacker);
var lunge = attacker.EnsureComponent<MeleeLungeComponent>();
lunge.SetData(msg.Angle);
var entity = EntityManager.SpawnEntity(weaponArc.Prototype, attacker.Transform.Coordinates);
entity.Transform.LocalRotation = msg.Angle;
var weaponArcAnimation = entity.GetComponent<MeleeWeaponArcAnimationComponent>();
weaponArcAnimation.SetData(weaponArc, msg.Angle, attacker, msg.ArcFollowAttacker);
// Due to ISpriteComponent limitations, weapons that don't use an RSI won't have this effect.
if (EntityManager.TryGetEntity(msg.Source, out var source) && msg.TextureEffect && source.TryGetComponent(out ISpriteComponent sourceSprite)
&& sourceSprite.BaseRSI?.Path != null)
if (!attacker.Deleted)
{
var sys = Get<EffectSystem>();
var curTime = _gameTiming.CurTime;
var effect = new EffectSystemMessage
var lunge = attacker.EnsureComponent<MeleeLungeComponent>();
lunge.SetData(msg.Angle);
var entity = EntityManager.SpawnEntity(weaponArc.Prototype, attacker.Transform.Coordinates);
entity.Transform.LocalRotation = msg.Angle;
var weaponArcAnimation = entity.GetComponent<MeleeWeaponArcAnimationComponent>();
weaponArcAnimation.SetData(weaponArc, msg.Angle, attacker, msg.ArcFollowAttacker);
// Due to ISpriteComponent limitations, weapons that don't use an RSI won't have this effect.
if (EntityManager.TryGetEntity(msg.Source, out var source) && msg.TextureEffect && source.TryGetComponent(out ISpriteComponent sourceSprite)
&& sourceSprite.BaseRSI?.Path != null)
{
EffectSprite = sourceSprite.BaseRSI.Path.ToString(),
RsiState = sourceSprite.LayerGetState(0).Name,
Coordinates = attacker.Transform.Coordinates,
Color = Vector4.Multiply(new Vector4(255, 255, 255, 125), 1.0f),
ColorDelta = Vector4.Multiply(new Vector4(0, 0, 0, -10), 1.0f),
Velocity = msg.Angle.ToVec(),
Acceleration = msg.Angle.ToVec() * 5f,
Born = curTime,
DeathTime = curTime.Add(TimeSpan.FromMilliseconds(300f)),
};
sys.CreateEffect(effect);
var sys = Get<EffectSystem>();
var curTime = _gameTiming.CurTime;
var effect = new EffectSystemMessage
{
EffectSprite = sourceSprite.BaseRSI.Path.ToString(),
RsiState = sourceSprite.LayerGetState(0).Name,
Coordinates = attacker.Transform.Coordinates,
Color = Vector4.Multiply(new Vector4(255, 255, 255, 125), 1.0f),
ColorDelta = Vector4.Multiply(new Vector4(0, 0, 0, -10), 1.0f),
Velocity = msg.Angle.ToVec(),
Acceleration = msg.Angle.ToVec() * 5f,
Born = curTime,
DeathTime = curTime.Add(TimeSpan.FromMilliseconds(300f)),
};
sys.CreateEffect(effect);
}
}
foreach (var uid in msg.Hits)
{
if (!EntityManager.TryGetEntity(uid, out var hitEntity))
if (!EntityManager.TryGetEntity(uid, out var hitEntity) || hitEntity.Deleted)
{
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.State;
using Content.Client.UserInterface;
using Content.Shared;
using Content.Shared.GameTicking;
using Content.Shared.Network.NetMessages;
using Robust.Client.Interfaces.Graphics;
@@ -11,6 +10,7 @@ using Robust.Client.Interfaces.State;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -28,7 +28,7 @@ namespace Content.Client.GameTicking
[ViewVariables] public bool IsGameStarted { get; private set; }
[ViewVariables] public bool DisallowedLateJoin { 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 Dictionary<NetUserId, PlayerStatus> Status { get; private set; }
[ViewVariables] public IReadOnlyList<string> JobsAvailable => _jobsAvailable;

View File

@@ -169,6 +169,9 @@ namespace Content.Client
"Flammable",
"CreamPie",
"CreamPied",
"Smoking",
"Matchstick",
"Matchbox",
"BlockGameArcade",
"KitchenSpike",
"Butcherable",
@@ -228,7 +231,15 @@ namespace Content.Client
"BiologicalSurgeryData",
"CargoTelepad",
"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 RemoveSpeechBubble(EntityUid entityUid, SpeechBubble bubble);
void ToggleDeadChatButtonVisibility(bool visibility);
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using static Content.Shared.GameTicking.SharedGameTicker;
namespace Content.Client.Interfaces
@@ -11,7 +12,7 @@ namespace Content.Client.Interfaces
string ServerInfoBlob { get; }
bool AreWeReady { get; }
bool DisallowedLateJoin { get; }
DateTime StartTime { get; }
TimeSpan StartTime { get; }
bool Paused { get; }
Dictionary<NetUserId, PlayerStatus> Status { 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 ShowBbButton; //Shows bounding boxes
public readonly Button MachineLinkingButton; // Enables/disables machine linking mode.
private readonly IGameHud _gameHud;
public SandboxWindow()
{
Resizable = false;
_gameHud = IoCManager.Resolve<IGameHud>();
Title = "Sandbox Panel";
@@ -82,6 +84,20 @@ namespace Content.Client.Sandbox
MachineLinkingButton = new Button { Text = Loc.GetString("Link machines"), ToggleMode = true };
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
@@ -197,7 +213,6 @@ namespace Content.Client.Sandbox
private void WindowOnOnClose()
{
_window = null;
_gameHud.SandboxButtonDown = false;
_sandboxWindowToggled = false;
}

View File

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

View File

@@ -13,6 +13,7 @@ using Robust.Client.Player;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input.Binding;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
@@ -35,6 +36,7 @@ namespace Content.Client.State
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[ViewVariables] private CharacterSetupGui _characterSetup;
[ViewVariables] private LobbyGui _lobby;
@@ -70,6 +72,12 @@ namespace Content.Client.State
_inputManager.SetInputCommand(ContentKeyFunctions.FocusChat,
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();
_lobby.CharacterPreview.CharacterSetupButton.OnPressed += args =>
@@ -138,10 +146,11 @@ namespace Content.Client.State
}
else
{
var difference = _clientGameTicker.StartTime - DateTime.UtcNow;
if (difference.Ticks < 0)
var difference = _clientGameTicker.StartTime - _gameTiming.CurTime;
var seconds = difference.TotalSeconds;
if (seconds < 0)
{
if (difference.TotalSeconds < -5)
if (seconds < -5)
{
text = Loc.GetString("Right Now?");
}
@@ -152,7 +161,7 @@ namespace Content.Client.State
}
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);
}
}
UpdatePlayerList();
}
private void LobbyReadyUpdated() => UpdatePlayerList();
private void LobbyStatusUpdated()
@@ -218,8 +229,6 @@ namespace Content.Client.State
foreach (var session in _playerManager.Sessions.OrderBy(s => s.Name))
{
var readyState = "";
// Don't show ready state if we're ingame
if (!_clientGameTicker.IsGameStarted)
@@ -238,6 +247,7 @@ namespace Content.Client.State
_ => "",
};
}
_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 GridContainer _resultsGrid;
private readonly TextureRect _dragShadow;
private readonly IGameHud _gameHud;
private readonly DragDropHelper<ActionMenuItem> _dragDropHelper;
@@ -64,6 +65,8 @@ namespace Content.Client.UserInterface
_actionsComponent = actionsComponent;
_actionsUI = actionsUI;
_actionManager = IoCManager.Resolve<ActionManager>();
_gameHud = IoCManager.Resolve<IGameHud>();
Title = Loc.GetString("Actions");
CustomMinimumSize = (300, 300);
@@ -143,14 +146,13 @@ namespace Content.Client.UserInterface
_dragDropHelper = new DragDropHelper<ActionMenuItem>(OnBeginActionDrag, OnContinueActionDrag, OnEndActionDrag);
}
protected override void EnteredTree()
{
base.EnteredTree();
_clearButton.OnPressed += OnClearButtonPressed;
_searchBar.OnTextChanged += OnSearchTextChanged;
_filterButton.OnItemSelected += OnFilterItemSelected;
_gameHud.ActionsButtonDown = true;
foreach (var actionMenuControl in _resultsGrid.Children)
{
var actionMenuItem = (actionMenuControl as ActionMenuItem);
@@ -167,7 +169,7 @@ namespace Content.Client.UserInterface
_clearButton.OnPressed -= OnClearButtonPressed;
_searchBar.OnTextChanged -= OnSearchTextChanged;
_filterButton.OnItemSelected -= OnFilterItemSelected;
_gameHud.ActionsButtonDown = false;
foreach (var actionMenuControl in _resultsGrid.Children)
{
var actionMenuItem = (actionMenuControl as ActionMenuItem);
@@ -280,6 +282,12 @@ namespace Content.Client.UserInterface
_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)
{
if (args.Button is not ActionMenuItem actionMenuItem) return;
@@ -402,8 +410,7 @@ namespace Content.Client.UserInterface
ItemTag => action is ItemActionPrototype,
NotItemTag => action is ActionPrototype,
InstantActionTag => action.BehaviorType == BehaviorType.Instant,
TargetActionTag => action.BehaviorType == BehaviorType.TargetEntity ||
action.BehaviorType == BehaviorType.TargetPoint,
TargetActionTag => action.IsTargetAction,
ToggleActionTag => action.BehaviorType == BehaviorType.Toggle,
_ => action.Filters.Contains(tag)
};
@@ -462,10 +469,9 @@ namespace Content.Client.UserInterface
_actionList = actions.ToArray();
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);
actionItem.SetActionState(_actionsComponent.IsGranted(action));
actionItem.OnButtonDown += OnItemButtonDown;
actionItem.OnButtonUp += OnItemButtonUp;
actionItem.OnPressed += OnItemPressed;

View File

@@ -1,5 +1,7 @@
#nullable enable
using System;
using Content.Client.GameObjects.Components.Mobs;
using Content.Client.UserInterface.Stylesheets;
using Content.Shared.Actions;
using Robust.Client.UserInterface;
@@ -19,8 +21,11 @@ namespace Content.Client.UserInterface
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;
CustomMinimumSize = (64, 64);
@@ -38,6 +43,12 @@ namespace Content.Client.UserInterface
TooltipSupplier = SupplyTooltip;
}
protected override void ControlFocusExited()
{
base.ControlFocusExited();
_onControlFocusExited.Invoke(this);
}
private Control SupplyTooltip(Control? sender)
{
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 IEntityManager _entityManager;
private readonly IGameTiming _gameTiming;
private readonly IGameHud _gameHud;
private readonly ActionSlot[] _slots;
@@ -80,13 +81,15 @@ namespace Content.Client.UserInterface
_actionManager = IoCManager.Resolve<ActionManager>();
_entityManager = IoCManager.Resolve<IEntityManager>();
_gameTiming = IoCManager.Resolve<IGameTiming>();
_gameHud = IoCManager.Resolve<IGameHud>();
_menu = new ActionMenu(_actionsComponent, this);
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.End);
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.End);
LayoutContainer.SetAnchorTop(this, 0f);
LayoutContainer.SetAnchorBottom(this, 0.8f);
LayoutContainer.SetMarginLeft(this, 10);
LayoutContainer.SetMarginTop(this, 100);
LayoutContainer.SetMarginLeft(this, 13);
LayoutContainer.SetMarginTop(this, 110);
SizeFlagsHorizontal = SizeFlags.None;
SizeFlagsVertical = SizeFlags.FillExpand;
@@ -208,6 +211,9 @@ namespace Content.Client.UserInterface
_lockButton.OnPressed += OnLockPressed;
_settingsButton.OnPressed += OnToggleActionsMenu;
_loadoutContainer.OnKeyBindDown += OnHotbarPaginate;
_gameHud.ActionsButtonToggled += OnToggleActionsMenuTopButton;
_gameHud.ActionsButtonDown = false;
_gameHud.ActionsButtonVisible = true;
}
protected override void ExitedTree()
@@ -218,6 +224,9 @@ namespace Content.Client.UserInterface
_lockButton.OnPressed -= OnLockPressed;
_settingsButton.OnPressed -= OnToggleActionsMenu;
_loadoutContainer.OnKeyBindDown -= OnHotbarPaginate;
_gameHud.ActionsButtonToggled -= OnToggleActionsMenuTopButton;
_gameHud.ActionsButtonDown = false;
_gameHud.ActionsButtonVisible = false;
}
protected override Vector2 CalculateMinimumSize()
@@ -328,9 +337,9 @@ namespace Content.Client.UserInterface
actionSlot.EnableAction();
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 &&
actionState.IsOnCooldown(_gameTiming))
actionState.IsOnCooldown(_gameTiming) && action.DeselectOnCooldown)
{
StopTargeting();
}
@@ -401,10 +410,10 @@ namespace Content.Client.UserInterface
// action is currently granted
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 &&
SelectingTargetFor.Item == itemEntity &&
actionState.IsOnCooldown(_gameTiming))
actionState.IsOnCooldown(_gameTiming) && action.DeselectOnCooldown)
{
StopTargeting();
}
@@ -496,6 +505,13 @@ namespace Content.Client.UserInterface
ToggleActionsMenu();
}
private void OnToggleActionsMenuTopButton(bool open)
{
if (open == _menu.IsOpen) return;
ToggleActionsMenu();
}
public void ToggleActionsMenu()
{
if (_menu.IsOpen)

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Content.Client.Administration;
using Content.Shared.Input;
using Robust.Client.Console;
using Robust.Client.Interfaces.Input;
@@ -13,6 +14,8 @@ namespace Content.Client.UserInterface.AdminMenu
{
[Dependency] private readonly INetManager _netManager = 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!;
private SS14Window _window;
@@ -26,6 +29,30 @@ namespace Content.Client.UserInterface.AdminMenu
_inputManager.SetInputCommand(ContentKeyFunctions.OpenAdminMenu,
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()

View File

@@ -1,10 +1,11 @@
#nullable enable
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.GameObjects.EntitySystems;
using Content.Client.StationEvents;
using Content.Shared.Atmos;
using Content.Shared.Roles;
using Robust.Client.Console;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.Placement;
@@ -30,6 +31,7 @@ namespace Content.Client.UserInterface.AdminMenu
public readonly TabContainer MasterTabContainer;
public readonly VBoxContainer PlayerList;
public readonly Label PlayerCount;
private readonly IGameHud _gameHud;
protected override Vector2? CustomSize => (500, 250);
@@ -44,7 +46,7 @@ namespace Content.Client.UserInterface.AdminMenu
{
new SpawnEntitiesCommandButton(),
new SpawnTilesCommandButton(),
new StationEventsCommandButton(),
new StationEventsCommandButton()
};
private readonly List<CommandButton> _debugButtons = new()
{
@@ -206,6 +208,7 @@ namespace Content.Client.UserInterface.AdminMenu
public AdminMenuWindow() //TODO: search for buttons?
{
_gameHud = IoCManager.Resolve<IGameHud>();
Title = Loc.GetString("Admin Menu");
#region PlayerList
@@ -376,6 +379,19 @@ namespace Content.Client.UserInterface.AdminMenu
IoCManager.Resolve<IStationEventManager>().RequestEvents();
}
protected override void ExitedTree()
{
base.ExitedTree();
_gameHud.AdminButtonDown = false;
}
protected override void EnteredTree()
{
base.EnteredTree();
_gameHud.AdminButtonDown = true;
}
#region CommandButtonBaseClass
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>
/// 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>
public bool CanUseAction => HasAssignment && ActionEnabled && !IsOnCooldown;
public bool CanUseAction => Action != null && ActionEnabled &&
(!IsOnCooldown || (Action.IsTargetAction && !Action.DeselectOnCooldown));
/// <summary>
/// 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();
}
protected override void ControlFocusExited()
{
// lost focus for some reason, cancel the drag if there is one.
base.ControlFocusExited();
_actionsUI.DragDropHelper.EndDrag();
DrawModeChanged();
}
/// <summary>
/// Cancel current press without triggering the action
/// </summary>
@@ -340,8 +350,10 @@ namespace Content.Client.UserInterface.Controls
/// </summary>
public void Depress(bool depress)
{
// action can still be toggled if it's allowed to stay selected
if (!CanUseAction) return;
if (_depressed && !depress)
{
// fire the action

View File

@@ -1,17 +1,25 @@
using System;
using System.Transactions;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.Input;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Input;
using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
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
{
@@ -41,6 +49,16 @@ namespace Content.Client.UserInterface
bool CraftingButtonVisible { 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.
bool SandboxButtonDown { get; set; }
bool SandboxButtonVisible { get; set; }
@@ -48,7 +66,8 @@ namespace Content.Client.UserInterface
Control HandsContainer { get; }
Control SuspicionContainer { get; }
Control InventoryQuickButtonContainer { get; }
Control RightInventoryQuickButtonContainer { get; }
Control LeftInventoryQuickButtonContainer { get; }
bool CombatPanelVisible { get; set; }
bool CombatModeActive { get; set; }
@@ -69,6 +88,8 @@ namespace Content.Client.UserInterface
private TopButton _buttonCharacterMenu;
private TopButton _buttonInventoryMenu;
private TopButton _buttonCraftingMenu;
private TopButton _buttonActionsMenu;
private TopButton _buttonAdminMenu;
private TopButton _buttonSandboxMenu;
private TutorialWindow _tutorialWindow;
private TargetingDoll _targetingDoll;
@@ -80,7 +101,8 @@ namespace Content.Client.UserInterface
public Control HandsContainer { 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
{
@@ -108,16 +130,18 @@ namespace Content.Client.UserInterface
RootControl = new LayoutContainer();
LayoutContainer.SetAnchorPreset(RootControl, LayoutContainer.LayoutPreset.Wide);
var escapeTexture = _resourceCache.GetTexture("/Textures/Interface/hamburger.svg.96dpi.png");
var characterTexture = _resourceCache.GetTexture("/Textures/Interface/character.svg.96dpi.png");
var inventoryTexture = _resourceCache.GetTexture("/Textures/Interface/inventory.svg.96dpi.png");
var craftingTexture = _resourceCache.GetTexture("/Textures/Interface/hammer.svg.96dpi.png");
var tutorialTexture = _resourceCache.GetTexture("/Textures/Interface/students-cap.svg.96dpi.png");
var sandboxTexture = _resourceCache.GetTexture("/Textures/Interface/sandbox.svg.96dpi.png");
var escapeTexture = _resourceCache.GetTexture("/Textures/Interface/hamburger.svg.192dpi.png");
var characterTexture = _resourceCache.GetTexture("/Textures/Interface/character.svg.192dpi.png");
var inventoryTexture = _resourceCache.GetTexture("/Textures/Interface/inventory.svg.192dpi.png");
var craftingTexture = _resourceCache.GetTexture("/Textures/Interface/hammer.svg.192dpi.png");
var actionsTexture = _resourceCache.GetTexture("/Textures/Interface/fist.svg.192dpi.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
{
SeparationOverride = 4
SeparationOverride = 8
};
RootControl.AddChild(_topButtonsContainer);
@@ -125,32 +149,29 @@ namespace Content.Client.UserInterface
LayoutContainer.SetAnchorAndMarginPreset(_topButtonsContainer, LayoutContainer.LayoutPreset.TopLeft,
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
_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);
_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
_buttonCharacterMenu = new TopButton(characterTexture, "C")
_buttonCharacterMenu = new TopButton(characterTexture, ContentKeyFunctions.OpenCharacterMenu, _inputManager)
{
ToolTip = Loc.GetString("Open character menu."),
Visible = false
CustomMinimumSize = topMinSize,
Visible = false,
StyleClasses = {StyleBase.ButtonSquare}
};
_topButtonsContainer.AddChild(_buttonCharacterMenu);
@@ -158,10 +179,12 @@ namespace Content.Client.UserInterface
_buttonCharacterMenu.OnToggled += args => CharacterButtonToggled?.Invoke(args.Pressed);
// Inventory
_buttonInventoryMenu = new TopButton(inventoryTexture, "I")
_buttonInventoryMenu = new TopButton(inventoryTexture, ContentKeyFunctions.OpenInventoryMenu, _inputManager)
{
ToolTip = Loc.GetString("Open inventory menu."),
Visible = false
CustomMinimumSize = topMinSize,
Visible = false,
StyleClasses = {StyleBase.ButtonSquare}
};
_topButtonsContainer.AddChild(_buttonInventoryMenu);
@@ -169,27 +192,69 @@ namespace Content.Client.UserInterface
_buttonInventoryMenu.OnToggled += args => InventoryButtonToggled?.Invoke(args.Pressed);
// Crafting
_buttonCraftingMenu = new TopButton(craftingTexture, "G")
_buttonCraftingMenu = new TopButton(craftingTexture, ContentKeyFunctions.OpenCraftingMenu, _inputManager)
{
ToolTip = Loc.GetString("Open crafting menu."),
Visible = false
CustomMinimumSize = topMinSize,
Visible = false,
StyleClasses = {StyleBase.ButtonSquare}
};
_topButtonsContainer.AddChild(_buttonCraftingMenu);
_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
_buttonSandboxMenu = new TopButton(sandboxTexture, "B")
_buttonSandboxMenu = new TopButton(sandboxTexture, ContentKeyFunctions.OpenSandboxWindow, _inputManager)
{
ToolTip = Loc.GetString("Open sandbox menu."),
Visible = false
CustomMinimumSize = topMinSize,
Visible = false,
StyleClasses = {StyleBase.ButtonSquare}
};
_topButtonsContainer.AddChild(_buttonSandboxMenu);
_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.OnClose += () => _buttonTutorial.Pressed = false;
@@ -197,21 +262,6 @@ namespace Content.Client.UserInterface
_inputManager.SetInputCommand(ContentKeyFunctions.OpenTutorial,
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
{
@@ -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);
_targetingDoll.OnZoneChanged += args => OnTargetingZoneChanged?.Invoke(args);
inventoryContainer.Children.Add(InventoryQuickButtonContainer);
inventoryContainer.Children.Add(_combatPanelContainer);
var centerBottomContainer = new HBoxContainer
{
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
{
SizeFlagsVertical = Control.SizeFlags.ShrinkEnd
};
RootControl.AddChild(HandsContainer);
LayoutContainer.SetAnchorAndMarginPreset(HandsContainer, LayoutContainer.LayoutPreset.CenterBottom);
LayoutContainer.SetGrowHorizontal(HandsContainer, LayoutContainer.GrowDirection.Both);
LayoutContainer.SetGrowVertical(HandsContainer, LayoutContainer.GrowDirection.Begin);
RightInventoryQuickButtonContainer = new MarginContainer
{
SizeFlagsVertical = Control.SizeFlags.ShrinkEnd
};
LeftInventoryQuickButtonContainer = new MarginContainer
{
SizeFlagsVertical = Control.SizeFlags.ShrinkEnd
};
centerBottomContainer.AddChild(LeftInventoryQuickButtonContainer);
centerBottomContainer.AddChild(HandsContainer);
centerBottomContainer.AddChild(RightInventoryQuickButtonContainer);
SuspicionContainer = new MarginContainer
{
@@ -251,13 +318,15 @@ namespace Content.Client.UserInterface
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.SetGrowVertical(SuspicionContainer, LayoutContainer.GrowDirection.Begin);
}
private void ButtonTutorialOnOnToggled()
{
_buttonTutorial.StyleClasses.Remove(TopButton.StyleClassRedTopButton);
if (_tutorialWindow.IsOpen)
{
if (!_tutorialWindow.IsAtFront())
@@ -330,6 +399,34 @@ namespace Content.Client.UserInterface
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
{
get => _buttonSandboxMenu.Pressed;
@@ -344,94 +441,204 @@ namespace Content.Client.UserInterface
public Action<bool> SandboxButtonToggled { get; set; }
public sealed class TopButton : BaseButton
public sealed class TopButton : ContainerButton
{
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 ColorRedNormal = Color.FromHex("#FEFEFE");
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 const float VertPad = 8f;
private Color NormalColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedNormal : ColorNormal;
private Color HoveredColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedHovered : ColorHovered;
private readonly TextureRect _textureRect;
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
{
MarginTopOverride = 4,
Children =
AddChild(
new VBoxContainer
{
new VBoxContainer
Children =
{
Children =
new Control {CustomMinimumSize = (0, VertPad)},
(_textureRect = new TextureRect
{
(_textureRect = new TextureRect
{
Texture = texture,
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
SizeFlagsVertical = SizeFlags.Expand | SizeFlags.ShrinkCenter,
ModulateSelfOverride = ColorNormal,
CustomMinimumSize = (0, 32),
Stretch = TextureRect.StretchMode.KeepCentered
}),
(_label = new Label
{
Text = keyName,
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
ModulateSelfOverride = ColorNormal,
StyleClasses = {StyleClassLabelTopButton}
})
}
TextureScale = (0.5f, 0.5f),
Texture = texture,
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
SizeFlagsVertical = SizeFlags.Expand | SizeFlags.ShrinkCenter,
ModulateSelfOverride = NormalColor,
Stretch = TextureRect.StretchMode.KeepCentered
}),
new Control {CustomMinimumSize = (0, VertPad)},
(_label = new Label
{
Text = ShortKeyName(_function),
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
ModulateSelfOverride = NormalColor,
StyleClasses = {StyleClassLabelTopButton}
})
}
}
});
);
DrawModeChanged();
ToggleMode = true;
}
protected override Vector2 CalculateMinimumSize()
protected override void EnteredTree()
{
var styleSize = ActualStyleBox?.MinimumSize ?? Vector2.Zero;
return (0, 4) + styleSize + base.CalculateMinimumSize();
_inputManager.OnKeyBindingAdded += OnKeyBindingChanged;
_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);
}
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))
{
TryGetStyleProperty(Button.StylePropertyStyleBox, out StyleBox ret);
return ret;
// 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;
}
protected override void DrawModeChanged()
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)
{
case DrawModeEnum.Normal:
SetOnlyStylePseudoClass(Button.StylePseudoClassNormal);
_textureRect.ModulateSelfOverride = ColorNormal;
_label.ModulateSelfOverride = ColorNormal;
_textureRect.ModulateSelfOverride = NormalColor;
_label.ModulateSelfOverride = NormalColor;
break;
case DrawModeEnum.Pressed:
SetOnlyStylePseudoClass(Button.StylePseudoClassPressed);
_textureRect.ModulateSelfOverride = ColorPressed;
_label.ModulateSelfOverride = ColorPressed;
break;
case DrawModeEnum.Hover:
SetOnlyStylePseudoClass(Button.StylePseudoClassHover);
_textureRect.ModulateSelfOverride = ColorHovered;
_label.ModulateSelfOverride = ColorHovered;
_textureRect.ModulateSelfOverride = HoveredColor;
_label.ModulateSelfOverride = HoveredColor;
break;
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)
{
FitChildInPixelBox(child, (UIBox2i) contentBox);
}
protected override void DrawModeChanged()
{
base.DrawModeChanged();
UpdateChildColors();
}
}
}

View File

@@ -1,11 +1,15 @@
using Content.Shared.GameObjects.Components.Items;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
namespace Content.Client.UserInterface
{
public class HandButton : ItemSlotButton
{
private bool _activeHand;
private bool _highlighted;
public HandButton(Texture texture, Texture storageTexture, Texture blockedTexture, HandLocation location) : base(texture, storageTexture)
{
Location = location;
@@ -21,5 +25,23 @@ namespace Content.Client.UserInterface
public HandLocation Location { 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.Linq;
using Content.Client.GameObjects.Components.Items;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.GameObjects.Components.Items;
using Content.Shared.Input;
@@ -21,8 +22,6 @@ namespace Content.Client.UserInterface
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
private readonly TextureRect _activeHandRect;
private readonly Texture _leftHandTexture;
private readonly Texture _middleHandTexture;
private readonly Texture _rightHandTexture;
@@ -52,24 +51,14 @@ namespace Content.Client.UserInterface
Children =
{
(_topPanel = ItemStatusPanel.FromSide(HandLocation.Middle)),
(_handsContainer = new HBoxContainer {SeparationOverride = 0})
(_handsContainer = new HBoxContainer())
}
}),
(_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");
_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");
}
@@ -119,13 +108,6 @@ namespace Content.Client.UserInterface
button.OnStoragePressed += args => _OnStoragePressed(args, slot);
_handsContainer.AddChild(button);
if (_activeHandRect.Parent == null)
{
button.AddChild(_activeHandRect);
_activeHandRect.SetPositionInParent(1);
}
hand.Button = button;
}
@@ -135,11 +117,6 @@ namespace Content.Client.UserInterface
if (button != null)
{
if (button.Children.Contains(_activeHandRect))
{
button.RemoveChild(_activeHandRect);
}
_handsContainer.RemoveChild(button);
}
}
@@ -186,14 +163,8 @@ namespace Content.Client.UserInterface
hand.Button!.Button.Texture = HandTexture(hand.Location);
hand.Button!.SetPositionInParent(i);
_itemSlotManager.SetItemSlot(hand.Button, hand.Entity);
}
_activeHandRect.Parent?.RemoveChild(_activeHandRect);
component.GetHand(component.ActiveIndex)?.Button?.AddChild(_activeHandRect);
if (hands.Length > 0)
{
_activeHandRect.SetPositionInParent(1);
hand.Button!.SetActiveHand(component.ActiveIndex == hand.Name);
}
_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.Interfaces;
using Content.Shared.GameTicking;
@@ -21,6 +21,7 @@ using Robust.Shared.Localization.Macros;
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.UserInterface.Stylesheets;
namespace Content.Client.UserInterface
{
@@ -43,6 +44,7 @@ namespace Content.Client.UserInterface
private readonly Button _sexMaleButton;
private readonly OptionButton _genderButton;
private readonly OptionButton _clothingButton;
private readonly OptionButton _backpackButton;
private readonly HairStylePicker _hairPicker;
private readonly FacialHairStylePicker _facialHairPicker;
@@ -333,6 +335,33 @@ namespace Content.Client.UserInterface
}
#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
@@ -669,6 +698,12 @@ namespace Content.Client.UserInterface
IsDirty = true;
}
private void SetBackpack(BackpackPreference newBackpack)
{
Profile = Profile?.WithBackpackPreference(newBackpack);
IsDirty = true;
}
public void Save()
{
IsDirty = false;
@@ -723,6 +758,11 @@ namespace Content.Client.UserInterface
_clothingButton.SelectId((int) Profile.Clothing);
}
private void UpdateBackpackControls()
{
_backpackButton.SelectId((int) Profile.Backpack);
}
private void UpdateHairPickers()
{
_hairPicker.SetData(
@@ -754,6 +794,7 @@ namespace Content.Client.UserInterface
UpdateSexControls();
UpdateGenderControls();
UpdateClothingControls();
UpdateBackpackControls();
UpdateAgeEdit();
UpdateHairPickers();
UpdateSaveButton();
@@ -780,12 +821,12 @@ namespace Content.Client.UserInterface
private class JobPrioritySelector : Control
{
public JobPrototype Job { get; }
private readonly OptionButton _optionButton;
private readonly RadioOptions<int> _optionButton;
public JobPriority Priority
{
get => (JobPriority) _optionButton.SelectedId;
set => _optionButton.SelectId((int) value);
get => (JobPriority) _optionButton.SelectedValue;
set => _optionButton.SelectByValue((int) value);
}
public event Action<JobPriority> PriorityChanged;
@@ -793,7 +834,14 @@ namespace Content.Client.UserInterface
public JobPrioritySelector(JobPrototype 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("Medium"), (int) JobPriority.Medium);
_optionButton.AddItem(Loc.GetString("Low"), (int) JobPriority.Low);
@@ -801,7 +849,7 @@ namespace Content.Client.UserInterface
_optionButton.OnItemSelected += args =>
{
_optionButton.SelectId(args.Id);
_optionButton.Select(args.Id);
PriorityChanged?.Invoke(Priority);
};

View File

@@ -1,4 +1,5 @@
using System;
using Content.Client.UserInterface.Stylesheets;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Shaders;
using Robust.Client.UserInterface;
@@ -26,11 +27,11 @@ namespace Content.Client.UserInterface
public bool EntityHover => HoverSpriteView.Sprite != null;
public bool MouseIsHovering = false;
private readonly ShaderInstance _highlightShader;
private readonly PanelContainer _highlightRect;
public ItemSlotButton(Texture texture, Texture storageTexture)
{
_highlightShader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>(HighlightShader).Instance();
CustomMinimumSize = (64, 64);
AddChild(Button = new TextureRect
@@ -40,6 +41,13 @@ namespace Content.Client.UserInterface
MouseFilter = MouseFilterMode.Stop
});
AddChild(_highlightRect = new PanelContainer
{
StyleClasses = { StyleNano.StyleClassHandSlotHighlight },
CustomMinimumSize = (32, 32),
Visible = false
});
Button.OnKeyBindDown += OnButtonPressed;
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 (on)
if (highlight)
{
Button.ShaderOverride = _highlightShader;
_highlightRect.Visible = true;
}
else
{
Button.ShaderOverride = null;
_highlightRect.Visible = false;
}
}
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.Graphics.ClientEye;
using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Shared.Input;
@@ -25,6 +26,7 @@ namespace Content.Client.UserInterface
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IUserInterfaceManager _uiMgr = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -61,7 +63,7 @@ namespace Content.Client.UserInterface
else if (args.Function == ContentKeyFunctions.OpenContextMenu)
{
_entitySystemManager.GetEntitySystem<VerbSystem>()
.OpenContextMenu(item, new ScreenCoordinates(args.PointerLocation.Position));
.OpenContextMenu(item, new ScreenCoordinates(_uiMgr.ScreenToUIPosition(args.PointerLocation)));
}
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
{

View File

@@ -32,7 +32,7 @@ namespace Content.Client.UserInterface
[ViewVariables]
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
{
@@ -40,7 +40,8 @@ namespace Content.Client.UserInterface
};
panel.SetContentMarginOverride(StyleBox.Margin.Vertical, 4);
panel.SetContentMarginOverride(StyleBox.Margin.Horizontal, 6);
panel.SetPatchMargin(margin, 13);
panel.SetPatchMargin(flat, 2);
panel.SetPatchMargin(cutout, 13);
AddChild(_panel = new PanelContainer
{
@@ -57,7 +58,8 @@ namespace Content.Client.UserInterface
(_itemNameLabel = new Label
{
ClipText = true,
StyleClasses = {StyleNano.StyleClassItemStatus}
StyleClasses = {StyleNano.StyleClassItemStatus},
Align = textAlign
})
}
}
@@ -78,27 +80,35 @@ namespace Content.Client.UserInterface
public static ItemStatusPanel FromSide(HandLocation location)
{
string texture;
StyleBox.Margin margin;
StyleBox.Margin cutOut;
StyleBox.Margin flat;
Label.AlignMode textAlign;
switch (location)
{
case HandLocation.Left:
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;
case HandLocation.Middle:
texture = "/Textures/Interface/Nano/item_status_left.svg.96dpi.png";
margin = StyleBox.Margin.Right | StyleBox.Margin.Top;
texture = "/Textures/Interface/Nano/item_status_middle.svg.96dpi.png";
cutOut = StyleBox.Margin.Right | StyleBox.Margin.Top;
flat = StyleBox.Margin.Left | StyleBox.Margin.Bottom;
textAlign = Label.AlignMode.Left;
break;
case HandLocation.Right:
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;
default:
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)

View File

@@ -17,6 +17,7 @@ namespace Content.Client.UserInterface.Stylesheets
public const string ButtonOpenRight = "OpenRight";
public const string ButtonOpenLeft = "OpenLeft";
public const string ButtonOpenBoth = "OpenBoth";
public const string ButtonSquare = "ButtonSquare";
public const string ButtonCaution = "Caution";
@@ -28,6 +29,7 @@ namespace Content.Client.UserInterface.Stylesheets
protected StyleBoxTexture BaseButtonOpenRight { get; }
protected StyleBoxTexture BaseButtonOpenLeft { get; }
protected StyleBoxTexture BaseButtonOpenBoth { get; }
protected StyleBoxTexture BaseButtonSquare { get; }
protected StyleBase(IResourceCache resCache)
{
@@ -70,6 +72,15 @@ namespace Content.Client.UserInterface.Stylesheets
BaseButtonOpenBoth.SetPadding(StyleBox.Margin.Right, 2);
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[]
{
// Default font.

View File

@@ -2,6 +2,7 @@
using Content.Client.GameObjects.EntitySystems;
using Content.Client.UserInterface.Controls;
using Content.Client.Utility;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface;
@@ -15,6 +16,8 @@ namespace Content.Client.UserInterface.Stylesheets
public sealed class StyleNano : StyleBase
{
public const string StyleClassBorderedWindowPanel = "BorderedWindowPanel";
public const string StyleClassInventorySlotBackground = "InventorySlotBackground";
public const string StyleClassHandSlotHighlight = "HandSlotHighlight";
public const string StyleClassTransparentBorderedWindowPanel = "TransparentBorderedWindowPanel";
public const string StyleClassHotbarPanel = "HotbarPanel";
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 ButtonColorDefault = Color.FromHex("#464966");
public static readonly Color ButtonColorDefaultRed = Color.FromHex("#D43B3B");
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 ButtonColorDisabled = Color.FromHex("#30313c");
@@ -100,6 +105,21 @@ namespace Content.Client.UserInterface.Stylesheets
};
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 borderedTransparentWindowBackground = new StyleBoxTexture
{
@@ -162,6 +182,33 @@ namespace Content.Client.UserInterface.Stylesheets
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 lineEditTex = resCache.GetTexture("/Textures/Interface/Nano/lineedit.png");
@@ -355,6 +402,20 @@ namespace Content.Client.UserInterface.Stylesheets
{
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
new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {StyleClassHotbarPanel}, null, null),
new[]
@@ -410,6 +471,10 @@ namespace Content.Client.UserInterface.Stylesheets
.Class(ButtonOpenBoth)
.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 StyleProperty(Label.StylePropertyAlignMode, Label.AlignMode.Center),
@@ -808,8 +873,43 @@ namespace Content.Client.UserInterface.Stylesheets
}),
// Those top menu buttons.
Element<GameHud.TopButton>()
.Prop(Button.StylePropertyStyleBox, BaseButton),
// these use slight variations on the various BaseButton styles so that the content within them appears centered,
// 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 SelectorElement(typeof(GameHud.TopButton), null, null, new[] {Button.StylePseudoClassNormal}),
@@ -832,6 +932,13 @@ namespace Content.Client.UserInterface.Stylesheets
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 SelectorElement(typeof(Label), new[] {GameHud.TopButton.StyleClassLabelTopButton}, null, null),
new[]

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ using Content.Client;
using Content.Client.Interfaces.Parallax;
using Content.Server;
using Content.Server.Interfaces.GameTicking;
using Content.Shared;
using NUnit.Framework;
using Robust.Server.Interfaces.Maps;
using Robust.Server.Interfaces.Timing;
@@ -48,7 +49,7 @@ namespace Content.IntegrationTests
// Connecting to Discord is a massive waste of time.
// Basically just makes the CI logs a mess.
options.CVarOverrides["discord.enabled"] = "true";
options.CVarOverrides["discord.enabled"] = "false";
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);
}

View File

@@ -20,9 +20,15 @@ namespace Content.IntegrationTests.Tests.Destructible
[TestOf(typeof(Threshold))]
public class DestructibleTests : ContentIntegrationTest
{
private static readonly string SpawnedEntityId = "DestructibleTestsSpawnedEntity";
private static readonly string DestructibleEntityId = "DestructibleTestsDestructibleEntity";
private static readonly string DestructibleDestructionEntityId = "DestructibleTestsDestructibleDestructionEntity";
private static readonly string Prototypes = $@"
- type: entity
id: {SpawnedEntityId}
name: {SpawnedEntityId}
- type: entity
id: {DestructibleEntityId}
name: {DestructibleEntityId}
@@ -35,15 +41,35 @@ namespace Content.IntegrationTests.Tests.Destructible
50:
triggersOnce: false
behaviors:
- !type:DoActsBehavior
acts: [""Breakage""]
- !type:PlaySoundBehavior
sound: /Audio/Effects/woodhit.ogg
- !type:SpawnEntitiesBehavior
spawn:
WoodPlank:
{SpawnedEntityId}:
min: 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
";
@@ -147,15 +173,15 @@ namespace Content.IntegrationTests.Tests.Destructible
// Check that it matches the YAML prototype
Assert.That(threshold.Behaviors, Has.Count.EqualTo(3));
var actsThreshold = (DoActsBehavior) threshold.Behaviors[0];
var soundThreshold = (PlaySoundBehavior) threshold.Behaviors[1];
var spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[2];
var soundThreshold = (PlaySoundBehavior) threshold.Behaviors[0];
var spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[1];
var actsThreshold = (DoActsBehavior) threshold.Behaviors[2];
Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage));
Assert.That(soundThreshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg"));
Assert.That(spawnThreshold.Spawn, Is.Not.Null);
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.Max, Is.EqualTo(1));
Assert.That(threshold.Triggered, Is.True);
@@ -202,16 +228,16 @@ namespace Content.IntegrationTests.Tests.Destructible
// Check that it matches the YAML prototype
Assert.That(threshold.Behaviors, Has.Count.EqualTo(3));
actsThreshold = (DoActsBehavior) threshold.Behaviors[0];
soundThreshold = (PlaySoundBehavior) threshold.Behaviors[1];
spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[2];
soundThreshold = (PlaySoundBehavior) threshold.Behaviors[0];
spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[1];
actsThreshold = (DoActsBehavior) threshold.Behaviors[2];
// Check that it matches the YAML prototype
Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage));
Assert.That(soundThreshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg"));
Assert.That(spawnThreshold.Spawn, Is.Not.Null);
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.Max, Is.EqualTo(1));
Assert.That(threshold.Triggered, Is.True);
@@ -254,16 +280,16 @@ namespace Content.IntegrationTests.Tests.Destructible
Assert.That(threshold.Behaviors, Has.Count.EqualTo(3));
actsThreshold = (DoActsBehavior) threshold.Behaviors[0];
soundThreshold = (PlaySoundBehavior) threshold.Behaviors[1];
spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[2];
soundThreshold = (PlaySoundBehavior) threshold.Behaviors[0];
spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[1];
actsThreshold = (DoActsBehavior) threshold.Behaviors[2];
// Check that it matches the YAML prototype
Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage));
Assert.That(soundThreshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg"));
Assert.That(spawnThreshold.Spawn, Is.Not.Null);
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.Max, Is.EqualTo(1));
Assert.That(threshold.Triggered, Is.True);
@@ -305,5 +331,84 @@ namespace Content.IntegrationTests.Tests.Destructible
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.That(one, Is.EqualTo(two));
Assert.That(two, Is.EqualTo(one));
var failed = TestContext.CurrentContext.Result.Assertions.FirstOrDefault();
if (failed != null)
{
var path1 = Path.Combine(userData.RootDir!,rp1.ToRelativeSystemPath());
var path2 = Path.Combine(userData.RootDir!,rp2.ToRelativeSystemPath());
TestContext.AddTestAttachment(path1);
TestContext.AddTestAttachment(path2);
var oneTmp = Path.GetTempFileName();
var twoTmp = Path.GetTempFileName();
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(path1);
TestContext.Error.WriteLine(path2);
TestContext.Error.WriteLine(oneTmp);
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 NUnit.Framework;
using Robust.Shared.GameObjects.Systems;
@@ -17,23 +17,27 @@ namespace Content.IntegrationTests.Tests.StationEvents
server.Assert(() =>
{
// Idle each event once
// Idle each event
var stationEventsSystem = EntitySystem.Get<StationEventSystem>();
var dummyFrameTime = (float) IoCManager.Resolve<IGameTiming>().TickPeriod.TotalSeconds;
foreach (var stationEvent in stationEventsSystem.StationEvents)
{
stationEvent.Announce();
stationEvent.Update(dummyFrameTime);
stationEvent.Startup();
stationEvent.Update(dummyFrameTime);
stationEvent.Running = false;
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();
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")
.HasColumnName("age");
b.Property<string>("Backpack")
.IsRequired()
.HasColumnType("text")
.HasColumnName("backpack");
b.Property<string>("CharacterName")
.IsRequired()
.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")
.HasColumnName("age");
b.Property<string>("Backpack")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("backpack");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("TEXT")

View File

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

View File

@@ -5,38 +5,36 @@ namespace Content.Server.AI.Operators
{
public abstract class AiOperator
{
public bool HasStartup => _hasStartup;
private bool _hasStartup = false;
private bool _hasShutdown = false;
public bool HasStartup { get; private set; }
public bool HasShutdown { get; private set; }
/// <summary>
/// Called once when the AiLogicProcessor starts this action
/// </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
// This signals to the override that it's already startup
// Should probably throw but it made some code elsewhere marginally easier
if (_hasStartup)
{
if (HasStartup)
return false;
}
_hasStartup = true;
HasStartup = true;
return true;
}
/// <summary>
/// Called once when the AiLogicProcessor is done with this action if the outcome is successful or fails.
/// </summary>
public virtual void Shutdown(Outcome outcome)
public virtual bool Shutdown(Outcome outcome)
{
if (_hasShutdown)
{
throw new InvalidOperationException("AiOperator has already shutdown");
}
if (HasShutdown)
return false;
_hasShutdown = true;
HasShutdown = true;
return true;
}
/// <summary>
@@ -53,4 +51,4 @@ namespace Content.Server.AI.Operators
Continuing,
Failed,
}
}
}

View File

@@ -22,9 +22,9 @@ namespace Content.Server.AI.Operators.Combat.Melee
_burstTime = burstTime;
}
public override bool TryStartup()
public override bool Startup()
{
if (!base.TryStartup())
if (!base.Startup())
{
return true;
}
@@ -42,13 +42,17 @@ namespace Content.Server.AI.Operators.Combat.Melee
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))
{
combatModeComponent.IsInCombatMode = false;
}
return true;
}
public override Outcome Execute(float frameTime)

View File

@@ -22,9 +22,9 @@ namespace Content.Server.AI.Operators.Combat.Melee
_burstTime = burstTime;
}
public override bool TryStartup()
public override bool Startup()
{
if (!base.TryStartup())
if (!base.Startup())
{
return true;
}
@@ -51,13 +51,17 @@ namespace Content.Server.AI.Operators.Combat.Melee
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))
{
combatModeComponent.IsInCombatMode = false;
}
return true;
}
public override Outcome Execute(float frameTime)

View File

@@ -21,9 +21,9 @@ namespace Content.Server.AI.Operators.Inventory
_owner = owner;
}
public override bool TryStartup()
public override bool Startup()
{
if (!base.TryStartup())
if (!base.Startup())
{
return true;
}
@@ -40,12 +40,15 @@ namespace Content.Server.AI.Operators.Inventory
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);
blackboard?.GetState<LastOpenedStorageState>().SetValue(null);
return true;
}
public override Outcome Execute(float frameTime)

View File

@@ -32,9 +32,9 @@ namespace Content.Server.AI.Operators.Movement
_requiresInRangeUnobstructed = requiresInRangeUnobstructed;
}
public override bool TryStartup()
public override bool Startup()
{
if (!base.TryStartup())
if (!base.Startup())
{
return true;
}
@@ -45,11 +45,14 @@ namespace Content.Server.AI.Operators.Movement
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>();
steering.Unregister(_owner);
return true;
}
public override Outcome Execute(float frameTime)

View File

@@ -21,9 +21,9 @@ namespace Content.Server.AI.Operators.Movement
DesiredRange = desiredRange;
}
public override bool TryStartup()
public override bool Startup()
{
if (!base.TryStartup())
if (!base.Startup())
{
return true;
}
@@ -34,11 +34,14 @@ namespace Content.Server.AI.Operators.Movement
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>();
steering.Unregister(_owner);
return true;
}
public override Outcome Execute(float frameTime)

View File

@@ -17,9 +17,9 @@ namespace Content.Server.AI.Operators.Sequences
{
return Outcome.Success;
}
var op = Sequence.Peek();
op.TryStartup();
op.Startup();
var outcome = op.Execute(frameTime);
switch (outcome)
@@ -35,10 +35,10 @@ namespace Content.Server.AI.Operators.Sequences
op.Shutdown(outcome);
Sequence.Clear();
return Outcome.Failed;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
}

View File

@@ -62,7 +62,7 @@ namespace Content.Server.AI.Utility.Actions
{
Owner = owner;
}
public virtual void Shutdown() {}
/// <summary>
@@ -78,7 +78,7 @@ namespace Content.Server.AI.Utility.Actions
return Outcome.Success;
}
op.TryStartup();
op.Startup();
var outcome = op.Execute(frameTime);
switch (outcome)
@@ -116,7 +116,7 @@ namespace Content.Server.AI.Utility.Actions
// Overall structure is based on Building a better centaur
// Ideally we should early-out each action as cheaply as possible if it's not valid, thus
// the finalScore can only go down over time.
var finalScore = 1.0f;
var minThreshold = min / Bonus;
context.GetState<ConsiderationState>().SetValue(considerations.Count);

View File

@@ -137,6 +137,8 @@ namespace Content.Server.AI.Utility.AiLogic
{
var currentOp = CurrentAction?.ActionOperators.Peek();
currentOp?.Shutdown(Outcome.Failed);
CurrentAction?.Shutdown();
CurrentAction = null;
}
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.Shared.Administration;
using Robust.Server.Interfaces.Console;
@@ -23,14 +25,15 @@ namespace Content.Server.Administration.Commands
return;
}
var mind = player.ContentData().Mind;
var mind = player.ContentData()?.Mind;
if (mind == null)
{
shell.SendText(player, "You can't ghost here!");
return;
}
if (mind.VisitingEntity != null && mind.VisitingEntity.Prototype.ID == "AdminObserver")
if (mind.VisitingEntity != null && mind.VisitingEntity.Prototype?.ID == "AdminObserver")
{
var visiting = mind.VisitingEntity;
mind.UnVisit();
@@ -38,13 +41,22 @@ namespace Content.Server.Administration.Commands
}
else
{
var canReturn = mind.CurrentEntity != null && !mind.CurrentEntity.HasComponent<GhostComponent>();
var entityManager = IoCManager.Resolve<IEntityManager>();
var ghost = entityManager.SpawnEntity("AdminObserver", player.AttachedEntity.Transform.MapPosition);
if(canReturn)
var canReturn = mind.CurrentEntity != null;
var ghost = IoCManager.Resolve<IEntityManager>()
.SpawnEntity("AdminObserver", player.AttachedEntity?.Transform.Coordinates
?? IoCManager.Resolve<IGameTicker>().GetObserverSpawnPoint());
if (canReturn)
{
ghost.Name = mind.CharacterName;
mind.Visit(ghost);
}
else
{
ghost.Name = player.Name;
mind.TransferTo(ghost);
}
ghost.GetComponent<GhostComponent>().CanReturnToBody = canReturn;
}
}

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