mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
merge remote wizden/master
This commit is contained in:
commit
816b7825ba
910 changed files with 20529 additions and 16602 deletions
4
.github/workflows/build-map-renderer.yml
vendored
4
.github/workflows/build-map-renderer.yml
vendored
|
|
@ -8,6 +8,10 @@ on:
|
|||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
branches: [ master, staging, stable ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.actor != 'PJBot' && github.event.pull_request.draft == false
|
||||
|
|
|
|||
4
.github/workflows/build-test-debug.yml
vendored
4
.github/workflows/build-test-debug.yml
vendored
|
|
@ -8,6 +8,10 @@ on:
|
|||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
branches: [ master, staging, stable ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.actor != 'IanComradeBot' && github.event.pull_request.draft == false
|
||||
|
|
|
|||
4
.github/workflows/check-crlf.yml
vendored
4
.github/workflows/check-crlf.yml
vendored
|
|
@ -4,6 +4,10 @@ on:
|
|||
pull_request:
|
||||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true # We only care about latest.
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: CRLF Check
|
||||
|
|
|
|||
27
.github/workflows/no-submodule-update.yml
vendored
27
.github/workflows/no-submodule-update.yml
vendored
|
|
@ -2,13 +2,36 @@ name: No submodule update checker
|
|||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
paths:
|
||||
- 'RobustToolbox'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
this_aint_right:
|
||||
name: Submodule update in pr found
|
||||
if: github.actor != 'PJBot' && github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fail
|
||||
run: exit 1
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check changed files
|
||||
shell: pwsh
|
||||
run: |
|
||||
$changedFiles = @(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}")
|
||||
|
||||
if ($changedFiles.Count -eq 1 -and $changedFiles[0] -eq "RobustToolbox") {
|
||||
Write-Host "Only RobustToolbox was changed; allowing standalone submodule update."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "RobustToolbox was updated alongside other files:"
|
||||
foreach ($file in $changedFiles) {
|
||||
Write-Host " - $file"
|
||||
}
|
||||
exit 1
|
||||
|
|
|
|||
5
.github/workflows/publish.yml
vendored
5
.github/workflows/publish.yml
vendored
|
|
@ -79,6 +79,11 @@ jobs:
|
|||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# DISCORD_WEBHOOK_URL: ${{ secrets.CHANGELOG_DISCORD_WEBHOOK }}
|
||||
- name: Publish changelog (Discord)
|
||||
run: Tools/actions_changelogs_since_last_run.py
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.CHANGELOG_DISCORD_WEBHOOK }}
|
||||
|
||||
# - name: Publish changelog (RSS)
|
||||
# continue-on-error: true
|
||||
|
|
|
|||
5
.github/workflows/rsi-diff.yml
vendored
5
.github/workflows/rsi-diff.yml
vendored
|
|
@ -2,9 +2,14 @@ name: Diff RSIs
|
|||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
paths:
|
||||
- '**.rsi/**.png'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
diff:
|
||||
name: Diff
|
||||
|
|
|
|||
4
.github/workflows/test-packaging.yml
vendored
4
.github/workflows/test-packaging.yml
vendored
|
|
@ -26,6 +26,10 @@ on:
|
|||
- 'RobustToolbox'
|
||||
- 'RobustToolbox/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Test Packaging
|
||||
|
|
|
|||
4
.github/workflows/validate-rgas.yml
vendored
4
.github/workflows/validate-rgas.yml
vendored
|
|
@ -6,6 +6,10 @@ on:
|
|||
pull_request:
|
||||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
jobs:
|
||||
yaml-schema-validation:
|
||||
name: YAML RGA schema validator
|
||||
|
|
|
|||
6
.github/workflows/validate-rsis.yml
vendored
6
.github/workflows/validate-rsis.yml
vendored
|
|
@ -5,12 +5,18 @@ on:
|
|||
branches: [ master, staging, stable ]
|
||||
merge_group:
|
||||
pull_request:
|
||||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
paths:
|
||||
- '**.rsi/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
jobs:
|
||||
validate_rsis:
|
||||
name: Validate RSIs
|
||||
if: github.actor != 'PJBot' && github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
|
|
|||
4
.github/workflows/validate_mapfiles.yml
vendored
4
.github/workflows/validate_mapfiles.yml
vendored
|
|
@ -6,6 +6,10 @@ on:
|
|||
pull_request:
|
||||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
jobs:
|
||||
yaml-schema-validation:
|
||||
name: YAML map schema validator
|
||||
|
|
|
|||
4
.github/workflows/yaml-linter.yml
vendored
4
.github/workflows/yaml-linter.yml
vendored
|
|
@ -7,6 +7,10 @@ on:
|
|||
pull_request:
|
||||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: YAML Linter
|
||||
|
|
|
|||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -303,6 +303,10 @@ ehthumbs.db
|
|||
# Merge driver stuff
|
||||
Content.Tools/test/out.yml
|
||||
|
||||
# Local changelog Discord debug files.
|
||||
Resources/Changelog/Old.yml
|
||||
Resources/Changelog/DiscordDebug.md
|
||||
|
||||
# Windows
|
||||
desktop.ini
|
||||
|
||||
|
|
|
|||
28
.vscode/tasks.json
vendored
28
.vscode/tasks.json
vendored
|
|
@ -13,13 +13,35 @@
|
|||
"/consoleloggerparameters:'ForceNoAlign;NoSummary'" // Do not generate summary otherwise it leads to duplicate errors in Problems panel
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
"kind": "build"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
"problemMatcher": "$msCompile",
|
||||
"runOptions": {
|
||||
"instanceLimit": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "build (C# Dev Kit)",
|
||||
"detail": "needs C# Dev Kit extention. use the (build) task instead if you dont have the extention.",
|
||||
"type": "dotnet",
|
||||
"task": "build",
|
||||
"icon": {
|
||||
"id": "build",
|
||||
"color": "terminal.ansiBlue"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"problemMatcher": "$msCompile",
|
||||
"runOptions": {
|
||||
"instanceLimit": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "build-yaml-linter",
|
||||
|
|
|
|||
18
Content.Client/Access/AgentIdCardSystem.cs
Normal file
18
Content.Client/Access/AgentIdCardSystem.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using Content.Shared.Access.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Access;
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed partial class AgentIdCardSystem : SharedAgentIdCardSystem
|
||||
{
|
||||
[Dependency] private UserInterfaceSystem _ui = default!;
|
||||
|
||||
public override void UpdateUi(EntityUid entity)
|
||||
{
|
||||
if (_ui.TryGetOpenUi(entity, AgentIDCardUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +1,64 @@
|
|||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Access.UI
|
||||
namespace Content.Client.Access.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="AgentIDCardWindow"/> and updates it when new server messages are received.
|
||||
/// </summary>
|
||||
public sealed class AgentIDCardBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="AgentIDCardWindow"/> and updates it when new server messages are received.
|
||||
/// </summary>
|
||||
public sealed class AgentIDCardBoundUserInterface : BoundUserInterface
|
||||
private AgentIDCardWindow? _window;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
private AgentIDCardWindow? _window;
|
||||
base.Open();
|
||||
|
||||
public AgentIDCardBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
if (!EntMan.TryGetComponent(Owner, out AgentIDCardComponent? agent))
|
||||
return;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = this.CreateWindow<AgentIDCardWindow>();
|
||||
|
||||
_window = this.CreateWindow<AgentIDCardWindow>();
|
||||
_window.OnNameChanged += OnNameChanged;
|
||||
_window.OnJobChanged += OnJobChanged;
|
||||
_window.OnJobIconChanged += OnJobIconChanged;
|
||||
|
||||
_window.OnNameChanged += OnNameChanged;
|
||||
_window.OnJobChanged += OnJobChanged;
|
||||
_window.OnJobIconChanged += OnJobIconChanged;
|
||||
}
|
||||
ProtoId<JobIconPrototype> currentIcon = default;
|
||||
if (EntMan.TryGetComponent<IdCardComponent>(Owner, out var card))
|
||||
currentIcon = card.JobIcon;
|
||||
|
||||
private void OnNameChanged(string newName)
|
||||
{
|
||||
SendMessage(new AgentIDCardNameChangedMessage(newName));
|
||||
}
|
||||
_window.SetAllowedIcons(agent.IconGroups, currentIcon);
|
||||
Update();
|
||||
}
|
||||
|
||||
private void OnJobChanged(string newJob)
|
||||
{
|
||||
SendMessage(new AgentIDCardJobChangedMessage(newJob));
|
||||
}
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
public void OnJobIconChanged(ProtoId<JobIconPrototype> newJobIconId)
|
||||
{
|
||||
SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId));
|
||||
}
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
/// <summary>
|
||||
/// Update the UI state based on server-sent info
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (_window == null || state is not AgentIDCardBoundUserInterfaceState cast)
|
||||
return;
|
||||
if (!EntMan.TryGetComponent<IdCardComponent>(Owner, out var card))
|
||||
return;
|
||||
|
||||
_window.SetCurrentName(cast.CurrentName);
|
||||
_window.SetCurrentJob(cast.CurrentJob);
|
||||
_window.SetAllowedIcons(cast.CurrentJobIconId);
|
||||
}
|
||||
_window.Update(card);
|
||||
}
|
||||
|
||||
private void OnNameChanged(string newName)
|
||||
{
|
||||
SendPredictedMessage(new AgentIDCardNameChangedMessage(newName));
|
||||
}
|
||||
|
||||
private void OnJobChanged(string newJob)
|
||||
{
|
||||
SendPredictedMessage(new AgentIDCardJobChangedMessage(newJob));
|
||||
}
|
||||
|
||||
private void OnJobIconChanged(ProtoId<JobIconPrototype> newJobIconId)
|
||||
{
|
||||
SendPredictedMessage(new AgentIDCardJobIconChangedMessage(newJobIconId));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,71 @@
|
|||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="{Loc agent-id-menu-title}">
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="4" MinWidth="150">
|
||||
<Label Name="CurrentName" Text="{Loc 'agent-id-card-current-name'}" />
|
||||
<LineEdit Name="NameLineEdit" />
|
||||
<Label Name="CurrentJob" Text="{Loc 'agent-id-card-current-job'}" />
|
||||
<LineEdit Name="JobLineEdit" />
|
||||
<Label Text="{Loc 'agent-id-card-job-icon-label'}"/>
|
||||
<GridContainer Name="IconGrid" Columns="10">
|
||||
<!-- Job icon buttons are generated in the code -->
|
||||
</GridContainer>
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc agent-id-ui-menu-title}"
|
||||
MinSize="765 398" Resizable="False">
|
||||
<BoxContainer Orientation="Vertical" Margin="12" VerticalExpand="False">
|
||||
|
||||
<!-- Upper area: ID status -->
|
||||
<BoxContainer Margin="0 0 0 8" MinHeight="80">
|
||||
<PanelContainer StyleClasses="PanelInsetDark">
|
||||
<TextureRect Name="CurrentJobIcon" Margin="8 0" Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Center" SetSize="64 64" />
|
||||
</PanelContainer>
|
||||
<BoxContainer Margin="12 0" Orientation="Vertical" Align="Center">
|
||||
<Label Name="CurrentName" StyleClasses="LabelHeadingBigger" SetHeight="32" />
|
||||
<Label Name="CurrentJob" StyleClasses="LabelSubHeading" Margin="16 4 0 0" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Center area: Tabs -->
|
||||
<PanelContainer StyleClasses="PanelInsetDark" MinHeight="230" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<TabContainer Name="AgentTabs" Margin="8 8 8 0" VerticalExpand="True" MinHeight="180">
|
||||
|
||||
<!-- Settings tab -->
|
||||
<ScrollContainer Name="TabSettings">
|
||||
<BoxContainer Margin="8" Orientation="Vertical">
|
||||
<Label Margin="4 2" Text="{Loc 'agent-id-ui-input-name'}" />
|
||||
<LineEdit Name="NameLineEdit" SelectAllOnFocus="True" />
|
||||
<Label Margin="4 2" Text="{Loc 'agent-id-ui-input-job'}" />
|
||||
<LineEdit Name="JobLineEdit" SelectAllOnFocus="True" />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
|
||||
<!-- Job icon tab -->
|
||||
<BoxContainer Name="TabJobIcons" Margin="8">
|
||||
<BoxContainer>
|
||||
<GridContainer Name="JobGroupGrid" Columns="3" />
|
||||
<!-- Group buttons are generated in code -->
|
||||
</BoxContainer>
|
||||
<PanelContainer StyleClasses="LowDivider" VerticalExpand="True" />
|
||||
<PanelContainer StyleClasses="PanelInsetDark" HorizontalExpand="True">
|
||||
<BoxContainer Margin="8">
|
||||
<GridContainer Name="IconGrid" Columns="4" />
|
||||
<!-- Job icon buttons are generated in code -->
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- TODO chameleon clothing tab -->
|
||||
|
||||
</TabContainer>
|
||||
<controls:StripeBack Margin="8 -6 8 4" HasBottomEdge="False"> <!-- The negative margin allows the control to be flush with the TabContainer -->
|
||||
<Label StyleClasses="LabelSubText" Margin="6 2" Text="{Loc 'agent-id-ui-os'}"
|
||||
VerticalAlignment="Center" />
|
||||
<Label StyleClasses="LabelSubText" Margin="6 2" Text="{Loc 'agent-id-ui-os-flavor'}"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||
</controls:StripeBack>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
<!-- Footer -->
|
||||
<BoxContainer Margin="10 2 5 0" VerticalAlignment="Bottom">
|
||||
<Label Text="{Loc 'agent-id-ui-footer-flavor-left'}" StyleClasses="WindowFooterText" />
|
||||
<Label Text="{Loc 'agent-id-ui-footer-flavor-right'}" StyleClasses="WindowFooterText"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
|
||||
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
</controls:FancyWindow>
|
||||
|
|
|
|||
|
|
@ -1,96 +1,183 @@
|
|||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Numerics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Access.UI
|
||||
namespace Content.Client.Access.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AgentIDCardWindow : FancyWindow
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AgentIDCardWindow : DefaultWindow
|
||||
[Dependency] private IConfigurationManager _cfgManager = default!;
|
||||
[Dependency] private IEntitySystemManager _entitySystem = default!;
|
||||
[Dependency] private IPrototypeManager _prototypeManager = default!;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
public event Action<string>? OnNameChanged;
|
||||
public event Action<string>? OnJobChanged;
|
||||
|
||||
public event Action<ProtoId<JobIconPrototype>>? OnJobIconChanged;
|
||||
|
||||
public AgentIDCardWindow()
|
||||
{
|
||||
[Dependency] private IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private IEntitySystemManager _entitySystem = default!;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
|
||||
|
||||
private const int JobIconColumnCount = 10;
|
||||
NameLineEdit.OnTextEntered += e => CommitName(e.Text);
|
||||
NameLineEdit.OnFocusExit += e => CommitName(e.Text);
|
||||
|
||||
public event Action<string>? OnNameChanged;
|
||||
public event Action<string>? OnJobChanged;
|
||||
JobLineEdit.OnTextEntered += e => CommitJob(e.Text);
|
||||
JobLineEdit.OnFocusExit += e => CommitJob(e.Text);
|
||||
|
||||
public event Action<ProtoId<JobIconPrototype>>? OnJobIconChanged;
|
||||
NameLineEdit.IsValid = s => s.Length <= _cfgManager.GetCVar(CCVars.MaxNameLength);
|
||||
JobLineEdit.IsValid = s => s.Length <= _cfgManager.GetCVar(CCVars.MaxIdJobLength);
|
||||
|
||||
public AgentIDCardWindow()
|
||||
AgentTabs.SetTabTitle(0, Loc.GetString("agent-id-ui-tab-settings"));
|
||||
AgentTabs.SetTabTitle(1, Loc.GetString("agent-id-ui-tab-job-icons"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the job icons tab.
|
||||
/// </summary>
|
||||
public void SetAllowedIcons(IReadOnlyList<ProtoId<JobIconGroupPrototype>> jobGroups, ProtoId<JobIconPrototype> currentIcon)
|
||||
{
|
||||
JobGroupGrid.DisposeAllChildren();
|
||||
IconGrid.DisposeAllChildren();
|
||||
|
||||
var jobGroupButtonGroup = new ButtonGroup();
|
||||
ContainerButton? firstGroupButton = null;
|
||||
IReadOnlyList<ProtoId<JobIconPrototype>>? firstGroupIcons = null;
|
||||
ContainerButton? matchingGroupButton = null;
|
||||
IReadOnlyList<ProtoId<JobIconPrototype>>? matchingGroupIcons = null;
|
||||
|
||||
foreach (var group in jobGroups)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
|
||||
if (!_prototypeManager.TryIndex(group, out var groupProto))
|
||||
continue;
|
||||
|
||||
NameLineEdit.OnTextEntered += e => OnNameChanged?.Invoke(e.Text);
|
||||
NameLineEdit.OnFocusExit += e => OnNameChanged?.Invoke(e.Text);
|
||||
var groupButton = CreateGroupButton(groupProto, jobGroupButtonGroup);
|
||||
groupButton.OnPressed += _ => SetJobIcons(groupProto.Icons);
|
||||
|
||||
JobLineEdit.OnTextEntered += e => OnJobChanged?.Invoke(e.Text);
|
||||
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
|
||||
}
|
||||
JobGroupGrid.AddChild(groupButton);
|
||||
|
||||
public void SetAllowedIcons(string currentJobIconId)
|
||||
{
|
||||
IconGrid.RemoveAllChildren();
|
||||
firstGroupButton ??= groupButton;
|
||||
firstGroupIcons ??= groupProto.Icons;
|
||||
|
||||
var jobIconButtonGroup = new ButtonGroup();
|
||||
var i = 0;
|
||||
var icons = _prototypeManager.EnumeratePrototypes<JobIconPrototype>().Where(icon => icon.AllowSelection).ToList();
|
||||
icons.Sort((x, y) => string.Compare(x.LocalizedJobName, y.LocalizedJobName, StringComparison.CurrentCulture));
|
||||
foreach (var jobIcon in icons)
|
||||
// Prefer the group that actually contains the card's current icon so
|
||||
// the opened window shows the relevant set rather than always the first.
|
||||
if (matchingGroupButton == null && groupProto.Icons.Contains(currentIcon))
|
||||
{
|
||||
String styleBase = StyleClass.ButtonOpenBoth;
|
||||
var modulo = i % JobIconColumnCount;
|
||||
if (modulo == 0)
|
||||
styleBase = StyleClass.ButtonOpenRight;
|
||||
else if (modulo == JobIconColumnCount - 1)
|
||||
styleBase = StyleClass.ButtonOpenLeft;
|
||||
|
||||
// Generate buttons
|
||||
var jobIconButton = new Button
|
||||
{
|
||||
Access = AccessLevel.Public,
|
||||
StyleClasses = { styleBase },
|
||||
MaxSize = new Vector2(42, 28),
|
||||
Group = jobIconButtonGroup,
|
||||
Pressed = currentJobIconId == jobIcon.ID,
|
||||
ToolTip = jobIcon.LocalizedJobName
|
||||
};
|
||||
|
||||
// Generate buttons textures
|
||||
var jobIconTexture = new TextureRect
|
||||
{
|
||||
Texture = _spriteSystem.Frame0(jobIcon.Icon),
|
||||
TextureScale = new Vector2(2.5f, 2.5f),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered,
|
||||
};
|
||||
|
||||
jobIconButton.AddChild(jobIconTexture);
|
||||
jobIconButton.OnPressed += _ => OnJobIconChanged?.Invoke(jobIcon.ID);
|
||||
IconGrid.AddChild(jobIconButton);
|
||||
|
||||
i++;
|
||||
matchingGroupButton = groupButton;
|
||||
matchingGroupIcons = groupProto.Icons;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCurrentName(string name)
|
||||
{
|
||||
NameLineEdit.Text = name;
|
||||
}
|
||||
var selectedButton = matchingGroupButton ?? firstGroupButton;
|
||||
var selectedIcons = matchingGroupIcons ?? firstGroupIcons;
|
||||
|
||||
public void SetCurrentJob(string job)
|
||||
if (selectedButton == null || selectedIcons == null)
|
||||
return;
|
||||
|
||||
selectedButton.Pressed = true;
|
||||
SetJobIcons(selectedIcons);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the job icon subgroup within the job icons tab.
|
||||
/// </summary>
|
||||
private void SetJobIcons(IReadOnlyList<ProtoId<JobIconPrototype>> jobIcons)
|
||||
{
|
||||
IconGrid.DisposeAllChildren();
|
||||
|
||||
foreach (var icon in jobIcons)
|
||||
{
|
||||
JobLineEdit.Text = job;
|
||||
if (!_prototypeManager.TryIndex(icon, out var iconProto))
|
||||
continue;
|
||||
|
||||
var texture = _spriteSystem.Frame0(iconProto.Icon);
|
||||
|
||||
// Create new button
|
||||
var jobIconButton = new TextureButton
|
||||
{
|
||||
Access = AccessLevel.Public,
|
||||
ToolTip = Loc.GetString(iconProto.JobName),
|
||||
TextureNormal = texture,
|
||||
SetSize = new Vector2(32, 32),
|
||||
};
|
||||
|
||||
// Finish button and add to UI
|
||||
jobIconButton.OnPressed += _ => OnJobIconChanged?.Invoke(iconProto.ID);
|
||||
|
||||
IconGrid.AddChild(jobIconButton);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(IdCardComponent card)
|
||||
{
|
||||
var name = card.FullName ?? string.Empty;
|
||||
var job = card.LocalizedJobTitle ?? string.Empty;
|
||||
|
||||
NameLineEdit.Text = name;
|
||||
CurrentName.Text = name;
|
||||
JobLineEdit.Text = job;
|
||||
CurrentJob.Text = job;
|
||||
|
||||
var jobIconProto = _prototypeManager.Index(card.JobIcon);
|
||||
CurrentJobIcon.Texture = _spriteSystem.Frame0(jobIconProto.Icon);
|
||||
}
|
||||
|
||||
private ContainerButton CreateGroupButton(JobIconGroupPrototype groupProto, ButtonGroup buttonGroup)
|
||||
{
|
||||
var button = new ContainerButton
|
||||
{
|
||||
Access = AccessLevel.Public,
|
||||
Group = buttonGroup,
|
||||
ToggleMode = true,
|
||||
StyleClasses = { StyleClass.ButtonOpenBoth },
|
||||
Margin = new Thickness(8, 2)
|
||||
};
|
||||
|
||||
button.AddChild(new BoxContainer
|
||||
{
|
||||
SeparationOverride = 8,
|
||||
Children =
|
||||
{
|
||||
new TextureRect
|
||||
{
|
||||
Texture = _spriteSystem.Frame0(groupProto.Sprite),
|
||||
Stretch = TextureRect.StretchMode.KeepAspectCentered,
|
||||
SetSize = new Vector2(32, 32),
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
},
|
||||
new Label
|
||||
{
|
||||
Text = Loc.GetString(groupProto.GroupName),
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private void CommitJob(string job)
|
||||
{
|
||||
OnJobChanged?.Invoke(job);
|
||||
}
|
||||
|
||||
private void CommitName(string name)
|
||||
{
|
||||
OnNameChanged?.Invoke(name);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<Button Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}" StyleClasses="OpenBoth" HorizontalAlignment="Left" />
|
||||
<Control HorizontalExpand="True" />
|
||||
<Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" StyleClasses="OpenRight" />
|
||||
<Button Visible="False" Name="PlayerLogs" Text="{Loc 'admin-player-actions-logs'}" StyleClasses="OpenRight" />
|
||||
<Button Visible="False" Access="Public" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
|
||||
<controls:ConfirmButton Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" StyleClasses="OpenBoth" />
|
||||
|
|
|
|||
|
|
@ -156,11 +156,10 @@ namespace Content.Client.Administration.UI.Bwoink
|
|||
return bch.LastMessage.CompareTo(ach.LastMessage);
|
||||
};
|
||||
|
||||
|
||||
Bans.OnPressed += _ =>
|
||||
PlayerLogs.OnPressed += _ =>
|
||||
{
|
||||
if (_currentPlayer is not null)
|
||||
_console.ExecuteCommand($"banlist \"{_currentPlayer.SessionId}\"");
|
||||
_console.ExecuteCommand($"adminlogs \"{_currentPlayer.SessionId}\"");
|
||||
};
|
||||
|
||||
Notes.OnPressed += _ =>
|
||||
|
|
@ -228,8 +227,8 @@ namespace Content.Client.Administration.UI.Bwoink
|
|||
{
|
||||
var disabled = _currentPlayer == null;
|
||||
|
||||
Bans.Visible = _adminManager.HasFlag(AdminFlags.Ban);
|
||||
Bans.Disabled = !Bans.Visible || disabled;
|
||||
PlayerLogs.Visible = _adminManager.HasFlag(AdminFlags.Logs);
|
||||
PlayerLogs.Disabled = !PlayerLogs.Visible || disabled;
|
||||
|
||||
Notes.Visible = _adminManager.HasFlag(AdminFlags.ViewNotes);
|
||||
Notes.Disabled = !Notes.Visible || disabled;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
using Content.Client.Eui;
|
||||
using Content.Shared.Administration.AdminEventLog;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Administration.UI.EventLog;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed partial class AdminEventLogEui : BaseEui
|
||||
{
|
||||
[Dependency] private IPlayerManager _playerManager = default!;
|
||||
|
||||
private AdminEventLogWindow EventLogWindow { get; }
|
||||
|
||||
public AdminEventLogEui()
|
||||
{
|
||||
EventLogWindow = new AdminEventLogWindow();
|
||||
EventLogWindow.SendEventLog.OnPressed += OnSendEventLogPressed;
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
EventLogWindow.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
|
||||
EventLogWindow.Close();
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
{
|
||||
var s = (AdminEventLogEuiState)state;
|
||||
|
||||
EventLogWindow.SetCurrentRound(s.RoundId);
|
||||
EventLogWindow.SetRoundSpinBox(s.RoundId);
|
||||
}
|
||||
|
||||
private void OnSendEventLogPressed(ButtonEventArgs args)
|
||||
{
|
||||
var eventText = Rope.Collapse(EventLogWindow.EventTextEdit.TextRope).TrimStart();
|
||||
|
||||
if (string.IsNullOrEmpty(eventText))
|
||||
return;
|
||||
|
||||
var message = new AdminEventLogEuiMsg(
|
||||
EventLogWindow.RoundSpinBox.Value,
|
||||
_playerManager.LocalSession!.Name,
|
||||
eventText);
|
||||
|
||||
SendMessage(message);
|
||||
|
||||
// close window to avoid admin accidentally spamming event logs
|
||||
EventLogWindow.Close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<ui:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'admin-event-log-title'}"
|
||||
MinWidth="600"
|
||||
MinHeight="180">
|
||||
<BoxContainer Orientation="Vertical" Margin="10">
|
||||
<BoxContainer Orientation="Horizontal" MinWidth="400">
|
||||
<Label Text="{Loc admin-event-round}"/>
|
||||
<SpinBox Name="RoundSpinBox" Value="0" MinWidth="150" Access="Public"/>
|
||||
<Button Name="ResetRoundButton" Text="{Loc admin-event-round-reset}"
|
||||
StyleClasses="Open"/>
|
||||
</BoxContainer>
|
||||
<TextEdit Name="EventTextEdit" HorizontalExpand="True" VerticalExpand="True" Access="Public"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="SendEventLog" Text="{Loc admin-event-send}" HorizontalAlignment="Left"
|
||||
StyleClasses="Open" Access="Public"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ui:FancyWindow>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Administration.UI.EventLog;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminEventLogWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private ILocalizationManager _localization = default!;
|
||||
|
||||
public AdminEventLogWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
RoundSpinBox.IsValid = i => i > 0 && i <= CurrentRound;
|
||||
RoundSpinBox.ValueChanged += RoundSpinBoxChanged;
|
||||
RoundSpinBox.InitDefaultButtons();
|
||||
|
||||
EventTextEdit.Placeholder = new Rope.Leaf(_localization.GetString("admin-event-description-placeholder"));
|
||||
|
||||
ResetRoundButton.OnPressed += ResetRoundPressed;
|
||||
}
|
||||
|
||||
private int CurrentRound { get; set; }
|
||||
|
||||
public void SetCurrentRound(int round)
|
||||
{
|
||||
CurrentRound = round;
|
||||
ResetRoundButton.Text = Loc.GetString("admin-event-round-reset-with-id", ("id", round));
|
||||
UpdateResetButton();
|
||||
}
|
||||
|
||||
public void SetRoundSpinBox(int round)
|
||||
{
|
||||
RoundSpinBox.Value = round;
|
||||
UpdateResetButton();
|
||||
}
|
||||
|
||||
private void RoundSpinBoxChanged(ValueChangedEventArgs args)
|
||||
{
|
||||
UpdateResetButton();
|
||||
}
|
||||
|
||||
private void ResetRoundPressed(ButtonEventArgs args)
|
||||
{
|
||||
RoundSpinBox.Value = CurrentRound;
|
||||
}
|
||||
|
||||
private void UpdateResetButton()
|
||||
{
|
||||
ResetRoundButton.Disabled = RoundSpinBox.Value == CurrentRound;
|
||||
}
|
||||
}
|
||||
|
|
@ -63,6 +63,8 @@ public sealed partial class AdminLogsControl : Control
|
|||
|
||||
public HashSet<LogImpact> SelectedImpacts { get; } = new();
|
||||
|
||||
private bool firstTimeOpened = true;
|
||||
|
||||
public void SetCurrentRound(int round)
|
||||
{
|
||||
CurrentRound = round;
|
||||
|
|
@ -184,6 +186,22 @@ public sealed partial class AdminLogsControl : Control
|
|||
UpdateLogs();
|
||||
}
|
||||
|
||||
public void SelectPlayers(List<Guid> players)
|
||||
{
|
||||
SelectedPlayers.Clear();
|
||||
SelectedPlayers.UnionWith(players);
|
||||
|
||||
foreach (var control in PlayersContainer.Children)
|
||||
{
|
||||
if (control is not AdminLogPlayerButton player)
|
||||
continue;
|
||||
|
||||
player.Pressed = SelectedPlayers.Contains(player.Id);
|
||||
}
|
||||
|
||||
UpdateLogs();
|
||||
}
|
||||
|
||||
public void SetTypesSelection(HashSet<LogType> selectedTypes, bool invert = false)
|
||||
{
|
||||
SelectedTypes.Clear();
|
||||
|
|
@ -420,16 +438,14 @@ public sealed partial class AdminLogsControl : Control
|
|||
public void SetPlayers(Dictionary<Guid, string> players)
|
||||
{
|
||||
var buttons = new SortedSet<AdminLogPlayerButton>(_adminLogPlayerButtonComparer);
|
||||
var allSelected = true;
|
||||
// we retrieve everything if we open this window for the first time and the selected player list is empty
|
||||
var allSelected = firstTimeOpened && SelectedPlayers.Count == 0;
|
||||
|
||||
foreach (var control in PlayersContainer.Children.ToArray())
|
||||
{
|
||||
if (control is not AdminLogPlayerButton player)
|
||||
continue;
|
||||
|
||||
if (!SelectedPlayers.Contains(player.Id))
|
||||
allSelected = false;
|
||||
|
||||
if (!players.Remove(player.Id))
|
||||
continue;
|
||||
|
||||
|
|
@ -457,6 +473,8 @@ public sealed partial class AdminLogsControl : Control
|
|||
foreach (var player in buttons)
|
||||
{
|
||||
PlayersContainer.AddChild(player);
|
||||
|
||||
player.Pressed = SelectedPlayers.Contains(player.Id);
|
||||
}
|
||||
|
||||
UpdateLogs();
|
||||
|
|
|
|||
|
|
@ -232,6 +232,11 @@ public sealed partial class AdminLogsEui : BaseEui
|
|||
if (setLogFilter.Search != null)
|
||||
LogsControl.LogSearch.SetText(setLogFilter.Search);
|
||||
|
||||
if (setLogFilter.Players != null)
|
||||
{
|
||||
LogsControl.SelectPlayers(setLogFilter.Players);
|
||||
}
|
||||
|
||||
if (setLogFilter.Types != null)
|
||||
LogsControl.SetTypesSelection(setLogFilter.Types, setLogFilter.InvertTypes);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
<cc:CommandButton Command="permissions" Text="{Loc admin-player-actions-window-permissions}" />
|
||||
<cc:CommandButton Command="announceui" Text="{Loc admin-player-actions-window-announce}"/>
|
||||
<cc:UICommandButton Command="callshuttle" Text="{Loc admin-player-actions-window-shuttle}" WindowType="{x:Type at:AdminShuttleWindow}"/>
|
||||
<cc:CommandButton Command="eventlog" Text="{Loc admin-player-actions-window-event-log}"/>
|
||||
<cc:CommandButton Command="adminlogs" Text="{Loc admin-player-actions-window-admin-logs}"/>
|
||||
<cc:CommandButton Command="faxui" Text="{Loc admin-player-actions-window-admin-fax}"/>
|
||||
<cc:CommandButton Command="achatwindow" Text="{Loc admin-player-actions-window-admin-chat}"/>
|
||||
|
|
|
|||
|
|
@ -1,114 +0,0 @@
|
|||
using System.Numerics;
|
||||
using Content.Client.Arcade.UI;
|
||||
using Content.Shared.Arcade;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
|
||||
namespace Content.Client.Arcade
|
||||
{
|
||||
public sealed class SpaceVillainArcadeMenu : DefaultWindow
|
||||
{
|
||||
private readonly Label _enemyNameLabel;
|
||||
private readonly Label _playerInfoLabel;
|
||||
private readonly Label _enemyInfoLabel;
|
||||
private readonly Label _playerActionLabel;
|
||||
private readonly Label _enemyActionLabel;
|
||||
|
||||
private readonly Button[] _gameButtons = new Button[3]; //used to disable/enable all game buttons
|
||||
|
||||
public event Action<SharedSpaceVillainArcadeComponent.PlayerAction>? OnPlayerAction;
|
||||
|
||||
public SpaceVillainArcadeMenu()
|
||||
{
|
||||
MinSize = SetSize = new Vector2(300, 225);
|
||||
Title = Loc.GetString("spacevillain-menu-title");
|
||||
|
||||
var grid = new GridContainer { Columns = 1 };
|
||||
|
||||
var infoGrid = new GridContainer { Columns = 3 };
|
||||
infoGrid.AddChild(new Label { Text = Loc.GetString("spacevillain-menu-label-player"), Align = Label.AlignMode.Center });
|
||||
infoGrid.AddChild(new Label { Text = "|", Align = Label.AlignMode.Center });
|
||||
_enemyNameLabel = new Label { Align = Label.AlignMode.Center };
|
||||
infoGrid.AddChild(_enemyNameLabel);
|
||||
|
||||
_playerInfoLabel = new Label { Align = Label.AlignMode.Center };
|
||||
infoGrid.AddChild(_playerInfoLabel);
|
||||
infoGrid.AddChild(new Label { Text = "|", Align = Label.AlignMode.Center });
|
||||
_enemyInfoLabel = new Label { Align = Label.AlignMode.Center };
|
||||
infoGrid.AddChild(_enemyInfoLabel);
|
||||
var centerContainer = new CenterContainer();
|
||||
centerContainer.AddChild(infoGrid);
|
||||
grid.AddChild(centerContainer);
|
||||
|
||||
_playerActionLabel = new Label { Align = Label.AlignMode.Center };
|
||||
grid.AddChild(_playerActionLabel);
|
||||
|
||||
_enemyActionLabel = new Label { Align = Label.AlignMode.Center };
|
||||
grid.AddChild(_enemyActionLabel);
|
||||
|
||||
var buttonGrid = new GridContainer { Columns = 3 };
|
||||
_gameButtons[0] = new Button()
|
||||
{
|
||||
Text = Loc.GetString("spacevillain-menu-button-attack")
|
||||
};
|
||||
|
||||
_gameButtons[0].OnPressed +=
|
||||
_ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Attack);
|
||||
buttonGrid.AddChild(_gameButtons[0]);
|
||||
|
||||
_gameButtons[1] = new Button()
|
||||
{
|
||||
Text = Loc.GetString("spacevillain-menu-button-heal")
|
||||
};
|
||||
|
||||
_gameButtons[1].OnPressed +=
|
||||
_ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Heal);
|
||||
buttonGrid.AddChild(_gameButtons[1]);
|
||||
|
||||
_gameButtons[2] = new Button()
|
||||
{
|
||||
Text = Loc.GetString("spacevillain-menu-button-recharge")
|
||||
};
|
||||
|
||||
_gameButtons[2].OnPressed +=
|
||||
_ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Recharge);
|
||||
buttonGrid.AddChild(_gameButtons[2]);
|
||||
|
||||
centerContainer = new CenterContainer();
|
||||
centerContainer.AddChild(buttonGrid);
|
||||
grid.AddChild(centerContainer);
|
||||
|
||||
var newGame = new Button()
|
||||
{
|
||||
Text = Loc.GetString("spacevillain-menu-button-new-game")
|
||||
};
|
||||
|
||||
newGame.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.NewGame);
|
||||
grid.AddChild(newGame);
|
||||
|
||||
ContentsContainer.AddChild(grid);
|
||||
}
|
||||
|
||||
private void UpdateMetadata(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage message)
|
||||
{
|
||||
Title = message.GameTitle;
|
||||
_enemyNameLabel.Text = message.EnemyName;
|
||||
|
||||
foreach (var gameButton in _gameButtons)
|
||||
{
|
||||
gameButton.Disabled = message.ButtonsDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateInfo(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeDataUpdateMessage message)
|
||||
{
|
||||
if (message is SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage metaMessage)
|
||||
UpdateMetadata(metaMessage);
|
||||
|
||||
_playerInfoLabel.Text = $"HP: {message.PlayerHP} MP: {message.PlayerMP}";
|
||||
_enemyInfoLabel.Text = $"HP: {message.EnemyHP} MP: {message.EnemyMP}";
|
||||
_playerActionLabel.Text = message.PlayerActionMessage;
|
||||
_enemyActionLabel.Text = message.EnemyActionMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
98
Content.Client/Arcade/SpaceVillainArcadeMenu.xaml
Normal file
98
Content.Client/Arcade/SpaceVillainArcadeMenu.xaml
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'spacevillain-menu-title'}"
|
||||
MinWidth="540"
|
||||
MinHeight="225">
|
||||
<BoxContainer
|
||||
Orientation="Vertical">
|
||||
<!-- Info grid -->
|
||||
<BoxContainer>
|
||||
<!-- Player -->
|
||||
<BoxContainer
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="True">
|
||||
<!-- Name -->
|
||||
<Label
|
||||
Text="{Loc 'spacevillain-menu-label-player'}"
|
||||
Align="Center"
|
||||
HorizontalExpand="True"/>
|
||||
<!-- Stats -->
|
||||
<BoxContainer>
|
||||
<!-- HP -->
|
||||
<Label
|
||||
Name="PlayerHpLabel"
|
||||
Align="Center"
|
||||
HorizontalExpand="True"
|
||||
Modulate="#37e928"/>
|
||||
<!-- MP -->
|
||||
<Label
|
||||
Name="PlayerMpLabel"
|
||||
Align="Center"
|
||||
HorizontalExpand="True"
|
||||
Modulate="#3483ff"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<!-- Divider -->
|
||||
<PanelContainer
|
||||
StyleClasses="LowDivider"/>
|
||||
<!-- Villain -->
|
||||
<BoxContainer
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="True">
|
||||
<!-- Name -->
|
||||
<Label
|
||||
Name="EnemyNameLabel"
|
||||
Text="{Loc 'spacevillain-menu-label-enemy'}"
|
||||
Align="Center"
|
||||
HorizontalExpand="True"/>
|
||||
<!-- Stats -->
|
||||
<BoxContainer>
|
||||
<!-- HP -->
|
||||
<Label
|
||||
Name="EnemyHpLabel"
|
||||
Align="Center"
|
||||
HorizontalExpand="True"
|
||||
Modulate="#e94740"/>
|
||||
<!-- MP -->
|
||||
<Label
|
||||
Name="EnemyMpLabel"
|
||||
Align="Center"
|
||||
HorizontalExpand="True"
|
||||
Modulate="#3483ff"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<!-- Spacer -->
|
||||
<Control VerticalExpand="True"/>
|
||||
<!-- Player and enemy action messages -->
|
||||
<Label Name="PlayerActionLabel"
|
||||
Align="Left"/>
|
||||
<Label Name="EnemyActionLabel"
|
||||
Align="Right"/>
|
||||
<!-- Spacer -->
|
||||
<Control VerticalExpand="True"/>
|
||||
<!-- Action button grid -->
|
||||
<BoxContainer
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Name="AttackButton"
|
||||
Text="{Loc spacevillain-menu-button-attack}"
|
||||
StyleClasses="OpenRight"
|
||||
HorizontalExpand="True"/>
|
||||
<Button
|
||||
Name="HealButton"
|
||||
Text="{Loc spacevillain-menu-button-heal}"
|
||||
StyleClasses="ButtonSquare"
|
||||
HorizontalExpand="True"/>
|
||||
<Button
|
||||
Name="RechargeButton"
|
||||
Text="{Loc spacevillain-menu-button-recharge}"
|
||||
StyleClasses="OpenLeft"
|
||||
HorizontalExpand="True"/>
|
||||
</BoxContainer>
|
||||
<!-- New game button -->
|
||||
<Button
|
||||
Name="NewGameButton"
|
||||
Text="{Loc spacevillain-menu-button-new-game}"/>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
45
Content.Client/Arcade/SpaceVillainArcadeMenu.xaml.cs
Normal file
45
Content.Client/Arcade/SpaceVillainArcadeMenu.xaml.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using Content.Shared.Arcade;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Arcade;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SpaceVillainArcadeMenu : DefaultWindow
|
||||
{
|
||||
public event Action<SharedSpaceVillainArcadeComponent.PlayerAction>? OnPlayerAction;
|
||||
|
||||
public SpaceVillainArcadeMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
AttackButton.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Attack);
|
||||
HealButton.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Heal);
|
||||
RechargeButton.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Recharge);
|
||||
NewGameButton.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.NewGame);
|
||||
}
|
||||
|
||||
private void UpdateMetadata(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage message)
|
||||
{
|
||||
Title = message.GameTitle;
|
||||
EnemyNameLabel.Text = message.EnemyName;
|
||||
|
||||
AttackButton.Disabled = message.ButtonsDisabled;
|
||||
HealButton.Disabled = message.ButtonsDisabled;
|
||||
RechargeButton.Disabled = message.ButtonsDisabled;
|
||||
NewGameButton.Disabled = message.ButtonsDisabled;
|
||||
}
|
||||
|
||||
public void UpdateInfo(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeDataUpdateMessage message)
|
||||
{
|
||||
if (message is SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage metaMessage)
|
||||
UpdateMetadata(metaMessage);
|
||||
|
||||
PlayerHpLabel.Text = Loc.GetString("spacevillain-menu-label-health", ("amount", message.PlayerHP));
|
||||
PlayerMpLabel.Text = Loc.GetString("spacevillain-menu-label-magic", ("amount", message.PlayerMP));
|
||||
EnemyHpLabel.Text = Loc.GetString("spacevillain-menu-label-health", ("amount", message.EnemyHP));
|
||||
EnemyMpLabel.Text = Loc.GetString("spacevillain-menu-label-magic", ("amount", message.EnemyMP));
|
||||
PlayerActionLabel.Text = message.PlayerActionMessage;
|
||||
EnemyActionLabel.Text = message.EnemyActionMessage;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,8 @@ using System.Linq;
|
|||
using Content.Client.Gameplay;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.EntityConditions;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Random.Rules;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Audio;
|
||||
|
|
@ -25,11 +25,11 @@ public sealed partial class ContentAudioSystem
|
|||
[Dependency] private IPlayerManager _player = default!;
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
[Dependency] private IStateManager _state = default!;
|
||||
[Dependency] private RulesSystem _rules = default!;
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private SharedEntityConditionsSystem _conditions = default!;
|
||||
|
||||
private readonly TimeSpan _minAmbienceTime = TimeSpan.FromSeconds(60);
|
||||
private readonly TimeSpan _maxAmbienceTime = TimeSpan.FromSeconds(120);
|
||||
private readonly TimeSpan _minAmbienceTime = TimeSpan.FromSeconds(40);
|
||||
private readonly TimeSpan _maxAmbienceTime = TimeSpan.FromSeconds(100);
|
||||
|
||||
private const float AmbientMusicFadeTime = 10f;
|
||||
private static float _volumeSlider;
|
||||
|
|
@ -88,7 +88,7 @@ public sealed partial class ContentAudioSystem
|
|||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (obj.WasModified<AmbientMusicPrototype>() || obj.WasModified<RulesPrototype>())
|
||||
if (obj.WasModified<AmbientMusicPrototype>())
|
||||
SetupAmbientSounds();
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ public sealed partial class ContentAudioSystem
|
|||
{
|
||||
var player = _player.LocalSession?.AttachedEntity;
|
||||
|
||||
if (player == null || _musicProto == null || !_rules.IsTrue(player.Value, ProtoMan.Index<RulesPrototype>(_musicProto.Rules)))
|
||||
if (player == null || _musicProto == null || !_conditions.TryConditions(player.Value, _musicProto.Conditions))
|
||||
{
|
||||
FadeOut(_ambientMusicStream, duration: AmbientMusicFadeTime);
|
||||
_musicProto = null;
|
||||
|
|
@ -192,9 +192,10 @@ public sealed partial class ContentAudioSystem
|
|||
|
||||
_musicProto = GetAmbience();
|
||||
|
||||
if (_musicProto == null || _musicProto == _lastMusicProto)
|
||||
if (_musicProto == null || _musicProto == _lastMusicProto && !_musicProto.AllowRepeat)
|
||||
{
|
||||
_interruptable = false;
|
||||
_nextAudio = _timing.CurTime + _random.Next(_minAmbienceTime, _maxAmbienceTime);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -228,9 +229,7 @@ public sealed partial class ContentAudioSystem
|
|||
|
||||
private AmbientMusicPrototype? GetAmbience()
|
||||
{
|
||||
var player = _player.LocalEntity;
|
||||
|
||||
if (player == null)
|
||||
if (_player.LocalEntity is not { } player)
|
||||
return null;
|
||||
|
||||
var ev = new PlayAmbientMusicEvent();
|
||||
|
|
@ -244,8 +243,10 @@ public sealed partial class ContentAudioSystem
|
|||
|
||||
foreach (var amb in ambiences)
|
||||
{
|
||||
if (!_rules.IsTrue(player.Value, ProtoMan.Index<RulesPrototype>(amb.Rules)))
|
||||
if (!_conditions.TryConditions(player, amb.Conditions))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return amb;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ public sealed partial class CryostorageEntryControl : BoxContainer
|
|||
Body.Visible = data.ItemSlots.Count != 0 && data.HeldItems.Count != 0;
|
||||
|
||||
ItemsContainer.Children.Clear();
|
||||
foreach (var (name, itemName) in data.ItemSlots)
|
||||
foreach (var (slotId, slotName, itemName) in data.ItemSlots)
|
||||
{
|
||||
var control = new CryostorageSlotControl(name, itemName);
|
||||
control.Button.OnPressed += _ => SlotRemoveButtonPressed?.Invoke(name);
|
||||
var control = new CryostorageSlotControl(slotName, itemName);
|
||||
control.Button.OnPressed += _ => SlotRemoveButtonPressed?.Invoke(slotId);
|
||||
ItemsContainer.AddChild(control);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System.Linq;
|
|||
using Content.Client.DisplacementMap;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.DisplacementMap;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid;
|
||||
using Robust.Client.GameObjects;
|
||||
|
|
@ -239,6 +238,13 @@ public sealed partial class VisualBodySystem : SharedVisualBodySystem
|
|||
);
|
||||
numDisplacements++;
|
||||
}
|
||||
|
||||
if (proto.Shaders is not null &&
|
||||
proto.Shaders.TryGetValue(rsi.RsiState, out var shader))
|
||||
{
|
||||
// TODO: fix this when LayerSetShader is moved out of component
|
||||
target.Comp.LayerSetShader(index + i + 1 + numDisplacements, shader);
|
||||
}
|
||||
}
|
||||
|
||||
applied.Add(marking);
|
||||
|
|
|
|||
|
|
@ -6,23 +6,23 @@
|
|||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="10 5 10 10">
|
||||
<controls:TableContainer Columns="2" HorizontalExpand="True" VerticalExpand="True">
|
||||
<TableContainer Columns="2" HorizontalExpand="True" VerticalExpand="True">
|
||||
<RichTextLabel Name="PrimaryCutLabel" Text="{Loc 'cargo-funding-alloc-console-label-primary-cut'}"/>
|
||||
<SpinBox Name="PrimaryCut"/>
|
||||
<RichTextLabel Name="LockboxCutLabel" Text="{Loc 'cargo-funding-alloc-console-label-lockbox-cut'}"/>
|
||||
<SpinBox Name="LockboxCut"/>
|
||||
</controls:TableContainer>
|
||||
</TableContainer>
|
||||
<Label Name="HelpLabel" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="0 10"/>
|
||||
<PanelContainer VerticalExpand="True" HorizontalExpand="True" VerticalAlignment="Top" MaxHeight="250">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<controls:TableContainer Name="EntriesContainer" Columns="4" HorizontalExpand="True" VerticalExpand="True" Margin="5 0">
|
||||
<TableContainer Name="EntriesContainer" Columns="4" HorizontalExpand="True" VerticalExpand="True" Margin="5 0">
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-account'}" HorizontalAlignment="Center"/>
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-code'}" HorizontalAlignment="Center"/>
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-balance'}" HorizontalAlignment="Center"/>
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-cut'}" HorizontalAlignment="Center"/>
|
||||
</controls:TableContainer>
|
||||
</TableContainer>
|
||||
</PanelContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5 0">
|
||||
<Button Name="SaveButton" Text="{Loc 'cargo-funding-alloc-console-button-save'}" Disabled="True"/>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
<RichTextLabel Name="InitiatorName" VerticalAlignment="Stretch"/>
|
||||
<RichTextLabel Name="WantedReason" VerticalAlignment="Stretch"/>
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
|
||||
<controls:TableContainer Name="HistoryTable" Columns="3" Visible="False" HorizontalAlignment="Stretch" />
|
||||
<TableContainer Name="HistoryTable" Columns="3" Visible="False" HorizontalAlignment="Stretch" />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,15 @@ public sealed class DoAfterOverlay : Overlay
|
|||
private const float StartX = 2;
|
||||
private const float EndX = 22f;
|
||||
|
||||
// Time after which the doafter will lerp to max alpha.
|
||||
private static readonly TimeSpan MaxAlphaTime = TimeSpan.FromSeconds(0.3f);
|
||||
|
||||
// After finishing, how long it takes to fade out to 0 alpha
|
||||
private static readonly TimeSpan FadeoutAlphaTime = TimeSpan.FromSeconds(0.2f);
|
||||
|
||||
// Time after which the doafter will lerp to its final y offset.
|
||||
private static readonly TimeSpan MaxYPosTime = TimeSpan.FromSeconds(0.5f);
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
|
||||
public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager, IGameTiming timing, IPlayerManager player)
|
||||
|
|
@ -110,19 +119,29 @@ public sealed class DoAfterOverlay : Overlay
|
|||
foreach (var doAfter in comp.DoAfters.Values)
|
||||
{
|
||||
// Hide some DoAfters from other players for stealthy actions (ie: thieving gloves)
|
||||
var alpha = 1f;
|
||||
var maxAlpha = 1f;
|
||||
if (doAfter.Args.Hidden || isInContainer)
|
||||
{
|
||||
if (uid != localEnt)
|
||||
continue;
|
||||
|
||||
// Hints to the local player that this do-after is not visible to other players.
|
||||
alpha = 0.5f;
|
||||
maxAlpha = 0.5f;
|
||||
}
|
||||
|
||||
var elapsed = time - doAfter.StartTime;
|
||||
|
||||
var alpha = MathHelper.Lerp(0f, maxAlpha, (float)Math.Clamp(elapsed / MaxAlphaTime, 0.0, 1.0));
|
||||
// fade out if doafter finished
|
||||
if (elapsed >= doAfter.Args.Delay)
|
||||
alpha = MathHelper.Lerp(maxAlpha, 0f, (float)Math.Clamp((elapsed - doAfter.Args.Delay) / FadeoutAlphaTime, 0.0, 1.0));
|
||||
|
||||
// Use the sprite itself if we know its bounds. This means short or tall sprites don't get overlapped
|
||||
// by the bar.
|
||||
var yOffset = _sprite.GetLocalBounds((uid, sprite)).Height / 2f + 0.05f;
|
||||
var spriteBounds = _sprite.GetLocalBounds((uid, sprite));
|
||||
var yFinished = spriteBounds.Height / 2f + 0.05f;
|
||||
var yStart = yFinished / 6f;
|
||||
var yOffset = MathHelper.Lerp(yStart, yFinished, Easings.OutSine((float)Math.Clamp(elapsed / MaxYPosTime, 0.0, 1.0)));
|
||||
|
||||
// Position above the entity (we've already applied the matrix transform to the entity itself)
|
||||
// Offset by the texture size for every do_after we have.
|
||||
|
|
@ -130,7 +149,7 @@ public sealed class DoAfterOverlay : Overlay
|
|||
yOffset / scale + offset / EyeManager.PixelsPerMeter * scale);
|
||||
|
||||
// Draw the underlying bar texture
|
||||
handle.DrawTexture(_barTexture, position);
|
||||
handle.DrawTexture(_barTexture, position, Color.White.WithAlpha(alpha));
|
||||
|
||||
Color color;
|
||||
float elapsedRatio;
|
||||
|
|
@ -138,7 +157,7 @@ public sealed class DoAfterOverlay : Overlay
|
|||
// if we're cancelled then flick red / off.
|
||||
if (doAfter.CancelledTime != null)
|
||||
{
|
||||
var elapsed = doAfter.CancelledTime.Value - doAfter.StartTime;
|
||||
elapsed = doAfter.CancelledTime.Value - doAfter.StartTime;
|
||||
elapsedRatio = (float)Math.Min(1, elapsed.TotalSeconds / doAfter.Args.Delay.TotalSeconds);
|
||||
var cancelElapsed = (time - doAfter.CancelledTime.Value).TotalSeconds;
|
||||
var flash = Math.Floor(cancelElapsed / FlashTime) % 2 == 0;
|
||||
|
|
@ -146,7 +165,6 @@ public sealed class DoAfterOverlay : Overlay
|
|||
}
|
||||
else
|
||||
{
|
||||
var elapsed = time - doAfter.StartTime;
|
||||
elapsedRatio = (float)Math.Min(1, elapsed.TotalSeconds / doAfter.Args.Delay.TotalSeconds);
|
||||
color = GetProgressColor(elapsedRatio, alpha);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Guidebook.Richtext;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,84 +1,91 @@
|
|||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.IconSmoothing
|
||||
namespace Content.Client.IconSmoothing;
|
||||
|
||||
/// <summary>
|
||||
/// Makes sprites of other grid-aligned entities like us connect.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The system is based on Baystation12's smoothwalling, and thus will work with those.
|
||||
/// To use, set <c>base</c> equal to the prefix of the corner states in the sprite base RSI.
|
||||
/// Any objects with the same <c>key</c> will connect.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed partial class IconSmoothComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool Enabled = true;
|
||||
|
||||
public (EntityUid?, Vector2i)? LastPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Objects with this key can smooth with us, and vice versa.
|
||||
/// TODO: YAML HELL, Make smoothing one way.
|
||||
/// </summary>
|
||||
[DataField("key", required: true)]
|
||||
public string SmoothKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// We will attempt to smooth with any objects that contain these keys.
|
||||
/// Only works one way, objects with these keys will not smooth with us
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<string> AdditionalKeys = new();
|
||||
|
||||
/// <summary>
|
||||
/// Index override for our iconsmooth layers. If null, layers will appear on top.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int? Index;
|
||||
|
||||
/// <summary>
|
||||
/// Prepended to the RSI state.
|
||||
/// </summary>
|
||||
[DataField("base")]
|
||||
public string StateBase { get; set; } = string.Empty;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<ShaderPrototype>? Shader;
|
||||
|
||||
/// <summary>
|
||||
/// Mode that controls how the icon should be selected.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public IconSmoothingMode Mode = IconSmoothingMode.Corners;
|
||||
|
||||
/// <summary>
|
||||
/// Used by <see cref="IconSmoothSystem"/> to reduce redundant updates.
|
||||
/// </summary>
|
||||
internal int UpdateGeneration { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls the mode with which icon smoothing is calculated.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public enum IconSmoothingMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes sprites of other grid-aligned entities like us connect.
|
||||
/// Each icon is made up of 4 corners, each of which can get a different state depending on
|
||||
/// adjacent entities clockwise, counter-clockwise and diagonal with the corner.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The system is based on Baystation12's smoothwalling, and thus will work with those.
|
||||
/// To use, set <c>base</c> equal to the prefix of the corner states in the sprite base RSI.
|
||||
/// Any objects with the same <c>key</c> will connect.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed partial class IconSmoothComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("enabled")]
|
||||
public bool Enabled = true;
|
||||
|
||||
public (EntityUid?, Vector2i)? LastPosition;
|
||||
|
||||
/// <summary>
|
||||
/// We will smooth with other objects with the same key.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("key")]
|
||||
public string? SmoothKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional keys to smooth with.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<string> AdditionalKeys = new();
|
||||
|
||||
/// <summary>
|
||||
/// Prepended to the RSI state.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("base")]
|
||||
public string StateBase { get; set; } = string.Empty;
|
||||
|
||||
[DataField("shader", customTypeSerializer:typeof(PrototypeIdSerializer<ShaderPrototype>))]
|
||||
public string? Shader;
|
||||
|
||||
/// <summary>
|
||||
/// Mode that controls how the icon should be selected.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("mode")]
|
||||
public IconSmoothingMode Mode = IconSmoothingMode.Corners;
|
||||
|
||||
/// <summary>
|
||||
/// Used by <see cref="IconSmoothSystem"/> to reduce redundant updates.
|
||||
/// </summary>
|
||||
internal int UpdateGeneration { get; set; }
|
||||
}
|
||||
Corners,
|
||||
|
||||
/// <summary>
|
||||
/// Controls the mode with which icon smoothing is calculated.
|
||||
/// There are 16 icons, only one of which is used at once.
|
||||
/// The icon selected is a bit field made up of the cardinal direction flags that have adjacent entities.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public enum IconSmoothingMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Each icon is made up of 4 corners, each of which can get a different state depending on
|
||||
/// adjacent entities clockwise, counter-clockwise and diagonal with the corner.
|
||||
/// </summary>
|
||||
Corners,
|
||||
CardinalFlags,
|
||||
|
||||
/// <summary>
|
||||
/// There are 16 icons, only one of which is used at once.
|
||||
/// The icon selected is a bit field made up of the cardinal direction flags that have adjacent entities.
|
||||
/// </summary>
|
||||
CardinalFlags,
|
||||
/// <summary>
|
||||
/// The icon represents a triangular sprite with only 2 states, representing South / East being occupied or not.
|
||||
/// </summary>
|
||||
Diagonal,
|
||||
|
||||
/// <summary>
|
||||
/// The icon represents a triangular sprite with only 2 states, representing South / East being occupied or not.
|
||||
/// </summary>
|
||||
Diagonal,
|
||||
|
||||
/// <summary>
|
||||
/// Where this component contributes to our neighbors being calculated but we do not update our own sprite.
|
||||
/// </summary>
|
||||
NoSprite,
|
||||
}
|
||||
/// <summary>
|
||||
/// Where this component contributes to our neighbors being calculated but we do not update our own sprite.
|
||||
/// </summary>
|
||||
NoSprite,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,13 +88,13 @@ namespace Content.Client.IconSmoothing
|
|||
_sprite.LayerMapRemove(sprite, CornerLayers.SW);
|
||||
|
||||
var state0 = $"{component.StateBase}0";
|
||||
_sprite.LayerMapSet(sprite, CornerLayers.SE, _sprite.AddRsiLayer(sprite, state0));
|
||||
_sprite.LayerMapSet(sprite, CornerLayers.SE, _sprite.AddRsiLayer(sprite, state0, index: component.Index));
|
||||
_sprite.LayerSetDirOffset(sprite, CornerLayers.SE, DirectionOffset.None);
|
||||
_sprite.LayerMapSet(sprite, CornerLayers.NE, _sprite.AddRsiLayer(sprite, state0));
|
||||
_sprite.LayerMapSet(sprite, CornerLayers.NE, _sprite.AddRsiLayer(sprite, state0, index: component.Index));
|
||||
_sprite.LayerSetDirOffset(sprite, CornerLayers.NE, DirectionOffset.CounterClockwise);
|
||||
_sprite.LayerMapSet(sprite, CornerLayers.NW, _sprite.AddRsiLayer(sprite, state0));
|
||||
_sprite.LayerMapSet(sprite, CornerLayers.NW, _sprite.AddRsiLayer(sprite, state0, index: component.Index));
|
||||
_sprite.LayerSetDirOffset(sprite, CornerLayers.NW, DirectionOffset.Flip);
|
||||
_sprite.LayerMapSet(sprite, CornerLayers.SW, _sprite.AddRsiLayer(sprite, state0));
|
||||
_sprite.LayerMapSet(sprite, CornerLayers.SW, _sprite.AddRsiLayer(sprite, state0, index: component.Index));
|
||||
_sprite.LayerSetDirOffset(sprite, CornerLayers.SW, DirectionOffset.Clockwise);
|
||||
}
|
||||
|
||||
|
|
@ -369,7 +369,6 @@ namespace Content.Client.IconSmoothing
|
|||
while (candidates.MoveNext(out var entity))
|
||||
{
|
||||
if (_iconSmoothQuery.TryGetComponent(entity, out var other) &&
|
||||
other.SmoothKey != null &&
|
||||
(other.SmoothKey == smooth.SmoothKey || smooth.AdditionalKeys.Contains(other.SmoothKey)) &&
|
||||
other.Enabled)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
<ui:RandomizeLockButton For="Sex" LabelText="{Loc 'humanoid-profile-editor-randomize-sex-button'}"/>
|
||||
<ui:RandomizeLockButton For="Skin" LabelText="{Loc 'humanoid-profile-editor-randomize-skin-button'}"/>
|
||||
<ui:RandomizeLockButton For="Eyes" LabelText="{Loc 'humanoid-profile-editor-randomize-eye-button'}"/>
|
||||
<ui:RandomizeLockButton For="Markings" LabelText="{Loc 'humanoid-profile-editor-randomize-markings-button'}"/>
|
||||
</GridContainer>
|
||||
<Button
|
||||
Name="RandomizeUnlockedButton"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Control xmlns="https://spacestation14.io"
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:Content.Client.Options.UI">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
<CheckBox Name="EventMusicCheckBox" Text="{Loc 'ui-options-event-music'}" />
|
||||
<CheckBox Name="AdminSoundsCheckBox" Text="{Loc 'ui-options-admin-sounds'}" />
|
||||
<CheckBox Name="BwoinkSoundCheckBox" Text="{Loc 'ui-options-bwoink-sound'}" />
|
||||
<CheckBox Name="AudioHrtfCheckBox" Text="{Loc 'ui-options-audio-hrtf'}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<ui:OptionsTabControlRow Name="Control" Access="Public" />
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ public sealed partial class AudioTab : Control
|
|||
Control.AddOptionCheckBox(CCVars.EventMusicEnabled, EventMusicCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminSoundsEnabled, AdminSoundsCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.BwoinkSoundEnabled, BwoinkSoundCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AudioHrtf, AudioHrtfCheckBox);
|
||||
|
||||
Control.Initialize();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,30 +2,63 @@
|
|||
xmlns="https://spacestation14.io"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
HorizontalExpand="True"
|
||||
Orientation="Vertical"
|
||||
>
|
||||
Orientation="Vertical">
|
||||
<customControls:HSeparator></customControls:HSeparator>
|
||||
<BoxContainer>
|
||||
<Label StyleClasses="SiliconLawPositionLabel" Name="PositionText" Margin="5 0 0 0"></Label>
|
||||
<BoxContainer
|
||||
Orientation="Vertical"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 5 0">
|
||||
<Button
|
||||
Name="MoveUp"
|
||||
Text="{Loc silicon-law-ui-minus-one}"
|
||||
StyleClasses="OpenLeft">
|
||||
</Button>
|
||||
<Label
|
||||
StyleClasses="SiliconLawPositionLabel"
|
||||
Name="PositionText"
|
||||
Align="Center">
|
||||
</Label>
|
||||
<Button
|
||||
Name="MoveDown"
|
||||
Text="{Loc silicon-law-ui-plus-one}"
|
||||
StyleClasses="OpenRight">
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
<PanelContainer
|
||||
Margin="20 10 0 0"
|
||||
Margin="0 10 10 0"
|
||||
MinHeight="128"
|
||||
>
|
||||
HorizontalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1B"></graphics:StyleBoxFlat>
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Horizontal" SeparationOverride="5">
|
||||
<TextEdit Name="LawContent" HorizontalExpand="True" Editable="True" MinWidth="500" MinHeight="80"></TextEdit>
|
||||
<BoxContainer
|
||||
Orientation="Horizontal"
|
||||
SeparationOverride="5">
|
||||
<TextEdit
|
||||
Name="LawContent"
|
||||
HorizontalExpand="True"
|
||||
Editable="True">
|
||||
</TextEdit>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 5 0 0" MaxHeight="64" Align="Begin">
|
||||
<Button Name="MoveUp" Text="{Loc silicon-law-ui-minus-one}" StyleClasses="OpenRight"></Button>
|
||||
<Button Name="MoveDown" Text="{Loc silicon-law-ui-plus-one}" StyleClasses="OpenLeft"></Button>
|
||||
<CheckBox Name="Corrupted" Text="{Loc silicon-law-ui-check-corrupted}" ToolTip="{Loc silicon-law-ui-check-corrupted-tooltip}"></CheckBox>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Align="End" Margin="0 10 5 10">
|
||||
<Button Name="Delete" Text="{Loc silicon-law-ui-delete}" ModulateSelfOverride="Red"></Button>
|
||||
<BoxContainer
|
||||
Orientation="Horizontal"
|
||||
Margin="5 10 5 10">
|
||||
<CheckBox
|
||||
Name="Corrupted"
|
||||
Text="{Loc silicon-law-ui-check-corrupted}"
|
||||
ToolTip="{Loc silicon-law-ui-check-corrupted-tooltip}">
|
||||
</CheckBox>
|
||||
<BoxContainer
|
||||
HorizontalExpand="True"> <!--> This is here just to seperate the two buttons on the UI. I hate this. <!-->
|
||||
</BoxContainer>
|
||||
<Button
|
||||
Name="Delete"
|
||||
Text="{Loc silicon-law-ui-delete}"
|
||||
ModulateSelfOverride="Red"
|
||||
Margin="5 0 0 0">
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Silicons.Laws.SiliconLawEditUi;
|
||||
|
|
@ -9,10 +10,9 @@ namespace Content.Client.Silicons.Laws.SiliconLawEditUi;
|
|||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SiliconLawContainer : BoxContainer
|
||||
{
|
||||
public const string StyleClassSiliconLawPositionLabel = "SiliconLawPositionLabel";
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
|
||||
public static readonly string CorruptedString =
|
||||
Loc.GetString("ion-storm-law-scrambled-number", ("length", 5));
|
||||
public const string StyleClassSiliconLawPositionLabel = "SiliconLawPositionLabel";
|
||||
|
||||
private SiliconLaw? _law;
|
||||
|
||||
|
|
@ -24,6 +24,7 @@ public sealed partial class SiliconLawContainer : BoxContainer
|
|||
public SiliconLawContainer()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
MoveUp.OnPressed += _ => MoveLawUp?.Invoke(_law!);
|
||||
MoveDown.OnPressed += _ => MoveLawDown?.Invoke(_law!);
|
||||
|
|
@ -31,11 +32,22 @@ public sealed partial class SiliconLawContainer : BoxContainer
|
|||
{
|
||||
if (Corrupted.Pressed)
|
||||
{
|
||||
_law!.LawIdentifierOverride = CorruptedString;
|
||||
_law!.LawIdentifierOverride = Loc.GetString(
|
||||
"ion-storm-law-scrambled-number",
|
||||
(
|
||||
"length",
|
||||
_random.Next(
|
||||
SharedSiliconLawSystem.IonStormIdentifierMinLength,
|
||||
SharedSiliconLawSystem.IonStormIdentifierMaxLength
|
||||
)
|
||||
)
|
||||
);
|
||||
_law!.Corrupted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_law!.LawIdentifierOverride = null;
|
||||
_law!.Corrupted = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -49,13 +61,6 @@ public sealed partial class SiliconLawContainer : BoxContainer
|
|||
_law = law;
|
||||
LawContent.TextRope = new Rope.Leaf(Loc.GetString(law.LawString));
|
||||
PositionText.Text = law.Order.ToString();
|
||||
if (!string.IsNullOrEmpty(law.LawIdentifierOverride))
|
||||
{
|
||||
Corrupted.Pressed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Corrupted.Pressed = false;
|
||||
}
|
||||
Corrupted.Pressed = law.Corrupted;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,42 @@
|
|||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc silicon-law-ui-title}"
|
||||
MinSize="560 400"
|
||||
>
|
||||
MinSize="300 225"
|
||||
SetSize="450 525">
|
||||
<!-->
|
||||
this shit does not layout properly unless I put the horizontal boxcontainer inside of a vertical one
|
||||
????
|
||||
<!-->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" Align="End">
|
||||
<Button Name="NewLawButton" Text="{Loc silicon-law-ui-new-law}" MaxSize="256 64" StyleClasses="OpenRight"></Button>
|
||||
<Button Name="Save" Text="{Loc silicon-law-ui-save}" MaxSize="256 64" Access="Public" StyleClasses="OpenLeft"></Button>
|
||||
<BoxContainer
|
||||
Orientation="Vertical">
|
||||
<BoxContainer
|
||||
Orientation="Horizontal"
|
||||
Align="Center">
|
||||
<Button
|
||||
Name="NewLawButton"
|
||||
Text="{Loc silicon-law-ui-new-law}"
|
||||
MaxSize="256 64"
|
||||
StyleClasses="OpenRight">
|
||||
</Button>
|
||||
<Button
|
||||
Name="Save"
|
||||
Text="{Loc silicon-law-ui-save}"
|
||||
MaxSize="256 64"
|
||||
Access="Public"
|
||||
StyleClasses="OpenLeft">
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Margin="4 60 0 0">
|
||||
<ScrollContainer VerticalExpand="True" HorizontalExpand="True" HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical" Name="LawContainer" Access="Public" VerticalExpand="True" />
|
||||
<BoxContainer
|
||||
Orientation="Vertical"
|
||||
Margin="4 31 5 5">
|
||||
<ScrollContainer
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer
|
||||
Orientation="Vertical"
|
||||
Name="LawContainer"
|
||||
Access="Public" />
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,27 @@
|
|||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Margin="0 0 0 10">
|
||||
<PanelContainer VerticalExpand="True" StyleClasses="BackgroundDark">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
Margin="5 5 5 5">
|
||||
<RichTextLabel Name="LawNumberLabel" StyleClasses="LabelKeyText"/>
|
||||
<customControls:HSeparator Margin="0 5 0 5"/>
|
||||
<RichTextLabel Name="LawLabel"/>
|
||||
<BoxContainer Name="LawAnnouncementButtons" Orientation="Horizontal" HorizontalExpand="True" Margin="0 5 0 0">
|
||||
<Label Text="{Loc laws-ui-state-law}" StyleClasses="highlight"/>
|
||||
<Control Margin="5 0 0 0" />
|
||||
<!-- Buttons are added via C# code-->
|
||||
<Button
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Margin="0 0 0 5"
|
||||
StyleClasses="ButtonSquare">
|
||||
<PanelContainer
|
||||
VerticalExpand="True"
|
||||
Margin="0 5"
|
||||
StyleClasses="BackgroundDark">
|
||||
<BoxContainer
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
Margin="5 5 5 5">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Name="LawNumberLabel" StyleClasses="LabelKeyText" />
|
||||
<BoxContainer
|
||||
HorizontalExpand="True"> <!--> This is here just to seperate the two elements on the UI. I hate this. <!-->
|
||||
</BoxContainer>
|
||||
<TextureRect Name="LawSelectedIcon" TexturePath="/Textures/Interface/Alerts/Abilities/silenced.png" />
|
||||
</BoxContainer>
|
||||
<customControls:HSeparator Margin="0 5 0 5" />
|
||||
<RichTextLabel Name="LawLabel" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</Control>
|
||||
</Button>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,112 +1,55 @@
|
|||
using Content.Client.Chat.Managers;
|
||||
using Content.Client.RichText;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Silicons.Laws.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LawDisplay : Control
|
||||
public sealed partial class LawDisplay : Button
|
||||
{
|
||||
[Dependency] private IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private IChatManager _chatManager = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private EntityManager _entityManager = default!;
|
||||
private const string LawDisplayUnselectedTexturePath = "/Textures/Interface/Alerts/Abilities/silenced.png";
|
||||
private const string LawDisplaySelectedTexturePath = "/Textures/Interface/Emotes/vocal.png";
|
||||
|
||||
private static readonly TimeSpan PressCooldown = TimeSpan.FromSeconds(3);
|
||||
/// <summary>
|
||||
/// The SiliconLaw tracked by this display.
|
||||
/// </summary>
|
||||
public SiliconLaw Law { get; }
|
||||
|
||||
private readonly Dictionary<Button, TimeSpan> _nextAllowedPress = new();
|
||||
|
||||
public LawDisplay(EntityUid uid, SiliconLaw law, HashSet<ProtoId<RadioChannelPrototype>>? radioChannels)
|
||||
public LawDisplay(SiliconLaw law)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
var identifier = law.LawIdentifierOverride ?? $"{law.Order}";
|
||||
Law = law;
|
||||
|
||||
var identifier = Law.LawIdentifierOverride ?? $"{Law.Order}";
|
||||
var lawIdentifier = Loc.GetString("laws-ui-law-header", ("id", identifier));
|
||||
var lawDescription = Loc.GetString(law.LawString);
|
||||
var lawIdentifierPlaintext = FormattedMessage.RemoveMarkupPermissive(lawIdentifier);
|
||||
var lawDescriptionPlaintext = FormattedMessage.RemoveMarkupPermissive(lawDescription);
|
||||
var lawDescription = Loc.GetString(Law.LawString);
|
||||
|
||||
LawNumberLabel.SetMessage(FormattedMessage.FromMarkupPermissive(lawIdentifier), UserFormattableTags.SiliconAllowedTags);
|
||||
LawLabel.SetMessage(FormattedMessage.FromMarkupPermissive(lawDescription), UserFormattableTags.SiliconAllowedTags);
|
||||
|
||||
// If you can't talk, you can't state your laws...
|
||||
if (!_entityManager.TryGetComponent<SpeechComponent>(uid, out var speech) || speech.SpeechSounds is null)
|
||||
return;
|
||||
|
||||
var localButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("hud-chatbox-select-channel-Local"),
|
||||
Modulate = Color.DarkGray,
|
||||
StyleClasses = { "chatSelectorOptionButton" },
|
||||
MinHeight = 35,
|
||||
MinWidth = 75,
|
||||
};
|
||||
|
||||
_nextAllowedPress[localButton] = TimeSpan.Zero;
|
||||
|
||||
localButton.OnPressed += _ =>
|
||||
{
|
||||
_chatManager.SendMessage($"{lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Local);
|
||||
_nextAllowedPress[localButton] = _timing.CurTime + PressCooldown;
|
||||
};
|
||||
|
||||
LawAnnouncementButtons.AddChild(localButton);
|
||||
|
||||
if (radioChannels == null)
|
||||
return;
|
||||
|
||||
foreach (var radioChannel in radioChannels)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<RadioChannelPrototype>(radioChannel, out var radioChannelProto))
|
||||
continue;
|
||||
|
||||
var radioChannelButton = new Button
|
||||
{
|
||||
Text = Loc.GetString(radioChannelProto.Name),
|
||||
Modulate = radioChannelProto.Color,
|
||||
StyleClasses = { "chatSelectorOptionButton" },
|
||||
MinHeight = 35,
|
||||
MinWidth = 75,
|
||||
};
|
||||
|
||||
_nextAllowedPress[radioChannelButton] = TimeSpan.Zero;
|
||||
|
||||
radioChannelButton.OnPressed += _ =>
|
||||
{
|
||||
if (radioChannel == SharedChatSystem.CommonChannel)
|
||||
{
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioCommonPrefix} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio);
|
||||
}
|
||||
else
|
||||
{
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioChannelPrefix}{radioChannelProto.KeyCode} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio);
|
||||
}
|
||||
_nextAllowedPress[radioChannelButton] = _timing.CurTime + PressCooldown;
|
||||
};
|
||||
|
||||
LawAnnouncementButtons.AddChild(radioChannelButton);
|
||||
}
|
||||
LawNumberLabel.SetMessage(FormattedMessage.FromMarkupPermissive(lawIdentifier),
|
||||
UserFormattableTags.SiliconAllowedTags);
|
||||
LawLabel.SetMessage(FormattedMessage.FromMarkupPermissive(lawDescription),
|
||||
UserFormattableTags.SiliconAllowedTags);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
/// <summary>
|
||||
/// Switches the indicator of this display depending on whether its law has been selected
|
||||
/// </summary>
|
||||
/// <param name="isSelected">Whether or not the law associated with this display is selected</param>
|
||||
public void SetLawSelectedIndicators(bool isSelected)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
foreach (var (button, nextPress) in _nextAllowedPress)
|
||||
if (isSelected)
|
||||
{
|
||||
button.Disabled = curTime < nextPress;
|
||||
AddStyleClass("positive");
|
||||
LawSelectedIcon.TexturePath = LawDisplaySelectedTexturePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveStyleClass("positive");
|
||||
LawSelectedIcon.TexturePath = LawDisplayUnselectedTexturePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,34 @@
|
|||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'laws-ui-menu-title'}"
|
||||
MinSize="200 100"
|
||||
SetSize="450 515">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<PanelContainer VerticalExpand="True" Margin="10 10 10 10">
|
||||
<controls:FancyWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'laws-ui-menu-title'}"
|
||||
MinSize="450 225"
|
||||
SetSize="450 525">
|
||||
<BoxContainer
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer
|
||||
Orientation="Horizontal"
|
||||
Align="Center"
|
||||
Margin="0 10 0 0"
|
||||
HorizontalExpand="True">
|
||||
<Button
|
||||
Name="LawSelectAllButton"
|
||||
Text="{Loc 'laws-ui-select-all'}"
|
||||
ToolTip="{Loc 'laws-ui-select-all-tooltip'}"
|
||||
MaxSize="256 64"
|
||||
StyleClasses="OpenRight" />
|
||||
<Button
|
||||
Name="LawSelectNoneButton"
|
||||
Text="{Loc 'laws-ui-select-none'}"
|
||||
MaxSize="256 64"
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
<PanelContainer VerticalExpand="True" Margin="10 10 10 5">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E"/>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<ScrollContainer
|
||||
HScrollEnabled="False"
|
||||
|
|
@ -19,9 +38,39 @@
|
|||
Name="LawDisplayContainer"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
Margin="10 10 10 0">
|
||||
Margin="10 10 10 5">
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
<BoxContainer
|
||||
Orientation="Horizontal"
|
||||
Align="Center"
|
||||
HorizontalExpand="True">
|
||||
<OptionButton
|
||||
Name="LawChatChannelOption"
|
||||
MaxSize="256 64"
|
||||
StyleClasses="OpenRight" />
|
||||
<Button
|
||||
Name="LawAnnounceButton"
|
||||
Text="{Loc 'laws-ui-state-law'}"
|
||||
MaxSize="256 64"
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical" Margin="0 5 0 0">
|
||||
<PanelContainer />
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<!--Todo: Change Logo based on Laws (Change to Syndicate Logo when Emagged)-->
|
||||
<BoxContainer Name="Footer" Orientation="Horizontal" Margin="10 0 5 0" VerticalAlignment="Bottom">
|
||||
<Label Text="{Loc 'laws-window-footer-left'}" StyleClasses="WindowFooterText" />
|
||||
<Label
|
||||
Name="Version" Text="{Loc 'laws-window-footer-right'}" StyleClasses="WindowFooterText"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
|
||||
<TextureRect
|
||||
StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,195 @@
|
|||
using System.Linq;
|
||||
using Content.Client.Chat.Managers;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Silicons.Laws.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SiliconLawMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private IChatManager _chatManager = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private EntityManager _entityManager = default!;
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
|
||||
private static readonly TimeSpan AnnounceBaseCooldown = TimeSpan.FromSeconds(1);
|
||||
private static readonly float MinChatCooldown = 1;
|
||||
private static readonly float MaxChatCooldown = 2;
|
||||
|
||||
private static readonly List<ChatSelectChannel> SelectableChatChannels =
|
||||
new() { ChatSelectChannel.Local, ChatSelectChannel.Whisper };
|
||||
|
||||
private int _selectedChatChannelIdx;
|
||||
private readonly List<RadioChannelPrototype> _selectableRadioChannels = new();
|
||||
|
||||
private readonly HashSet<SiliconLaw> _selectedLaws = new();
|
||||
private readonly Queue<(SiliconLaw law, TimeSpan announceTime)> _queuedLaws = new();
|
||||
private TimeSpan _nextAllowedAnnouncePress = TimeSpan.Zero;
|
||||
|
||||
private EntityUid _owner;
|
||||
|
||||
public SiliconLawMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
LawChatChannelOption.OnItemSelected += OnLawChatChannelSelected;
|
||||
LawSelectAllButton.OnPressed += _ => OnMassSelectPressed(true);
|
||||
LawSelectNoneButton.OnPressed += _ => OnMassSelectPressed(false);
|
||||
LawAnnounceButton.OnPressed += OnLawAnnounceButtonPressed;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
LawAnnounceButton.Disabled = curTime < _nextAllowedAnnouncePress;
|
||||
// Don't want to change the channel while currently announcing laws
|
||||
LawChatChannelOption.Disabled = curTime < _nextAllowedAnnouncePress;
|
||||
|
||||
// Announce laws
|
||||
// Skip if no queued laws or delay still active
|
||||
if (_queuedLaws.Count < 1 || _queuedLaws.Peek().announceTime > curTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var law = _queuedLaws.Dequeue().law;
|
||||
var identifier = law.LawIdentifierOverride ?? $"{law.Order}";
|
||||
var lawIdentifier = Loc.GetString("laws-ui-law-header", ("id", identifier));
|
||||
var lawDescription = Loc.GetString(law.LawString);
|
||||
var lawIdentifierPlaintext = FormattedMessage.RemoveMarkupPermissive(lawIdentifier);
|
||||
var lawDescriptionPlaintext = FormattedMessage.RemoveMarkupPermissive(lawDescription);
|
||||
|
||||
// Local chat and whisper
|
||||
if (_selectedChatChannelIdx < SelectableChatChannels.Count)
|
||||
{
|
||||
_chatManager.SendMessage($"{lawIdentifierPlaintext}: {lawDescriptionPlaintext}",
|
||||
SelectableChatChannels[_selectedChatChannelIdx]);
|
||||
}
|
||||
// Radio
|
||||
else
|
||||
{
|
||||
var radioChannelProto = _selectableRadioChannels[_selectedChatChannelIdx - SelectableChatChannels.Count];
|
||||
var radioMessage = radioChannelProto.ID == SharedChatSystem.CommonChannel
|
||||
? $"{SharedChatSystem.RadioCommonPrefix} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}"
|
||||
: $"{SharedChatSystem.RadioChannelPrefix}{radioChannelProto.KeyCode} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}";
|
||||
_chatManager.SendMessage(radioMessage, ChatSelectChannel.Radio);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(EntityUid uid, SiliconLawBuiState state)
|
||||
{
|
||||
_owner = uid;
|
||||
|
||||
state.Laws.Sort();
|
||||
LawDisplayContainer.Children.Clear();
|
||||
Version.Text = Loc.GetString("laws-window-footer-right", ("version", state.Version));
|
||||
|
||||
LawChatChannelOption.Clear();
|
||||
foreach (var chatChannel in SelectableChatChannels)
|
||||
{
|
||||
LawChatChannelOption.AddItem(chatChannel.ToString());
|
||||
}
|
||||
|
||||
if (state.RadioChannels is not null)
|
||||
{
|
||||
foreach (var radioChannel in state.RadioChannels)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(radioChannel, out var radioChannelProto))
|
||||
continue;
|
||||
|
||||
_selectableRadioChannels.Add(radioChannelProto);
|
||||
LawChatChannelOption.AddItem(Loc.GetString(radioChannelProto.Name));
|
||||
}
|
||||
}
|
||||
|
||||
LawChatChannelOption.Select(_selectedChatChannelIdx);
|
||||
|
||||
foreach (var law in state.Laws)
|
||||
{
|
||||
var control = new LawDisplay(uid, law, state.RadioChannels);
|
||||
var control = new LawDisplay(law);
|
||||
control.OnPressed += _ =>
|
||||
{
|
||||
if (_selectedLaws.Contains(law))
|
||||
{
|
||||
_selectedLaws.Remove(law);
|
||||
control.SetLawSelectedIndicators(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedLaws.Add(law);
|
||||
control.SetLawSelectedIndicators(true);
|
||||
}
|
||||
};
|
||||
|
||||
LawDisplayContainer.AddChild(control);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMassSelectPressed(bool isPositive)
|
||||
{
|
||||
foreach (var child in LawDisplayContainer.Children)
|
||||
{
|
||||
if (child is not LawDisplay lawDisplay)
|
||||
continue;
|
||||
|
||||
if (isPositive)
|
||||
{
|
||||
_selectedLaws.Add(lawDisplay.Law);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedLaws.Remove(lawDisplay.Law);
|
||||
}
|
||||
|
||||
lawDisplay.SetLawSelectedIndicators(isPositive);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLawChatChannelSelected(OptionButton.ItemSelectedEventArgs obj)
|
||||
{
|
||||
_selectedChatChannelIdx = LawChatChannelOption.GetIdx(obj.Id);
|
||||
LawChatChannelOption.Select(_selectedChatChannelIdx);
|
||||
}
|
||||
|
||||
private void OnLawAnnounceButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
// If you have no laws selected, or you can't talk, you can't state your laws...
|
||||
var cannotSpeak = !_entityManager.TryGetComponent<SpeechComponent>(_owner, out _)
|
||||
|| !_entityManager.System<ActionBlockerSystem>().CanSpeak(_owner);
|
||||
if (_selectedLaws.Count < 1 || cannotSpeak)
|
||||
return;
|
||||
|
||||
var toAnnounce = _selectedLaws.ToList();
|
||||
toAnnounce.Sort();
|
||||
|
||||
// Queue up laws to be announced
|
||||
// The first message will be sent immediately, while future ones have a cooldown
|
||||
var totalChatCooldown = TimeSpan.Zero;
|
||||
foreach (var law in toAnnounce)
|
||||
{
|
||||
var chatCooldown = TimeSpan.FromSeconds(_random.NextFloat(MinChatCooldown, MaxChatCooldown));
|
||||
_queuedLaws.Enqueue((law, _timing.CurTime + totalChatCooldown));
|
||||
totalChatCooldown += chatCooldown;
|
||||
}
|
||||
|
||||
// Disable button until all laws announced, plus an additional cooldown
|
||||
_nextAllowedAnnouncePress = _timing.CurTime + totalChatCooldown + AnnounceBaseCooldown;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Content.Client.Speech.EntitySystems;
|
|||
|
||||
public sealed class SlurredSystem : SharedSlurredSystem
|
||||
{
|
||||
protected override string AccentuateInternal(EntityUid uid, SlurredAccentComponent comp, string message)
|
||||
public override string Accentuate(string message, Entity<SlurredAccentComponent>? ent = null)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Content.Client.Speech.EntitySystems;
|
|||
|
||||
public sealed class StutteringSystem : SharedStutteringSystem
|
||||
{
|
||||
protected override string AccentuateInternal(EntityUid uid, StutteringAccentComponent comp, string message)
|
||||
public override string Accentuate(string message, Entity<StutteringAccentComponent>? ent = null)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public sealed partial class GeneralRecord : Control
|
|||
RecordName.Text = record.Name;
|
||||
Age.Text = Loc.GetString("general-station-record-console-record-age", ("age", record.Age.ToString()));
|
||||
Title.Text = Loc.GetString("general-station-record-console-record-title",
|
||||
("job", Loc.GetString(record.JobTitle)));
|
||||
("job", record.JobTitle));
|
||||
var species = Loc.GetString(prototypeManager.Index<SpeciesPrototype>(record.Species).Name);
|
||||
Species.Text = Loc.GetString("general-station-record-console-record-species", ("species", species));
|
||||
Gender.Text = Loc.GetString("general-station-record-console-record-gender",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
using Content.Shared.Station;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Shared.StationRecords.Components;
|
||||
using Content.Shared.StationRecords.Systems;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.StationRecords;
|
||||
|
||||
public sealed class GeneralStationRecordConsoleBoundUserInterface : BoundUserInterface
|
||||
public sealed partial class GeneralStationRecordConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private GeneralStationRecordConsoleWindow? _window = default!;
|
||||
private GeneralStationRecordConsoleWindow? _window;
|
||||
|
||||
[Dependency] private SharedStationSystem _stationSys = default!;
|
||||
[Dependency] private StationRecordsSystem _recordsSys = default!;
|
||||
|
||||
public GeneralStationRecordConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
|
|
@ -17,20 +23,51 @@ public sealed class GeneralStationRecordConsoleBoundUserInterface : BoundUserInt
|
|||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<GeneralStationRecordConsoleWindow>();
|
||||
_window.OnKeySelected += key =>
|
||||
SendMessage(new SelectStationRecord(key));
|
||||
_window.OnFiltersChanged += (type, filterValue) =>
|
||||
SendMessage(new SetStationRecordFilter(type, filterValue));
|
||||
_window.OnDeleted += id => SendMessage(new DeleteStationRecord(id));
|
||||
_window.OnKeySelected += SelectStationRecord;
|
||||
_window.OnFiltersChanged += SetStationRecordFilter;
|
||||
_window.OnDeleted += id => SendPredictedMessage(new DeleteStationRecord(id));
|
||||
Update();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
public override void Update()
|
||||
{
|
||||
base.UpdateState(state);
|
||||
base.Update();
|
||||
|
||||
if (state is not GeneralStationRecordConsoleState cast)
|
||||
if (!EntMan.TryGetComponent(Owner, out GeneralStationRecordConsoleComponent? comp))
|
||||
return;
|
||||
|
||||
_window?.UpdateState(cast);
|
||||
var owningStation = _stationSys.GetOwningStation(Owner);
|
||||
|
||||
if (!EntMan.TryGetComponent(owningStation, out StationRecordsComponent? stationRecords))
|
||||
return;
|
||||
|
||||
var listing = _recordsSys.BuildListing((owningStation.Value, stationRecords), comp.Filter);
|
||||
|
||||
GeneralStationRecord? record = null;
|
||||
if (comp.ActiveKey != null)
|
||||
{
|
||||
var key = new StationRecordKey(comp.ActiveKey.Value, owningStation.Value);
|
||||
_recordsSys.TryGetRecord(key, out record, stationRecords);
|
||||
}
|
||||
|
||||
_window?.UpdateState(comp.ActiveKey, record, listing, comp.Filter, comp.CanDeleteEntries);
|
||||
}
|
||||
|
||||
private void SelectStationRecord(uint? key)
|
||||
{
|
||||
if (!EntMan.TryGetComponent(Owner, out GeneralStationRecordConsoleComponent? comp))
|
||||
return;
|
||||
|
||||
comp.ActiveKey = key;
|
||||
Update();
|
||||
}
|
||||
|
||||
private void SetStationRecordFilter(StationRecordFilterType type, string value)
|
||||
{
|
||||
if (!EntMan.TryGetComponent(Owner, out GeneralStationRecordConsoleComponent? comp))
|
||||
return;
|
||||
|
||||
comp.Filter = new StationRecordsFilter(type, value);
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
using Content.Shared.StationRecords;
|
||||
using Content.Shared.StationRecords.Components;
|
||||
using Content.Shared.StationRecords.Systems;
|
||||
|
||||
namespace Content.Client.StationRecords;
|
||||
|
||||
public sealed partial class GeneralStationRecordConsoleSystem : SharedGeneralStationRecordConsoleSystem
|
||||
{
|
||||
[Dependency] private SharedUserInterfaceSystem _ui = default!;
|
||||
|
||||
protected override void UpdateUserInterface(Entity<GeneralStationRecordConsoleComponent> ent)
|
||||
{
|
||||
if (_ui.TryGetOpenUi(ent.Owner, GeneralStationRecordConsoleKey.Key, out var bui))
|
||||
bui.Update();
|
||||
}
|
||||
|
||||
// Needed so when a record is created or deleted, it appears in the UI instantly.
|
||||
[SubscribeLocalEvent]
|
||||
private void OnAfterHandleState(Entity<StationRecordsComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
var query = EntityQueryEnumerator<GeneralStationRecordConsoleComponent>();
|
||||
while (query.MoveNext(out var uid, out _))
|
||||
{
|
||||
if (_ui.TryGetOpenUi(uid, GeneralStationRecordConsoleKey.Key, out var bui))
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,24 +74,28 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
|
|||
};
|
||||
}
|
||||
|
||||
public void UpdateState(GeneralStationRecordConsoleState state)
|
||||
public void UpdateState(uint? selectedKey,
|
||||
GeneralStationRecord? record,
|
||||
Dictionary<uint, string>? recordListing,
|
||||
StationRecordsFilter? filter,
|
||||
bool canDeleteEntries)
|
||||
{
|
||||
if (state.Filter != null)
|
||||
if (filter != null)
|
||||
{
|
||||
if (state.Filter.Type != _currentFilterType)
|
||||
if (filter.Type != _currentFilterType)
|
||||
{
|
||||
_currentFilterType = state.Filter.Type;
|
||||
_currentFilterType = filter.Type;
|
||||
}
|
||||
|
||||
if (state.Filter.Value != StationRecordsFiltersValue.Text)
|
||||
if (filter.Value != StationRecordsFiltersValue.Text)
|
||||
{
|
||||
StationRecordsFiltersValue.Text = state.Filter.Value;
|
||||
StationRecordsFiltersValue.Text = filter.Value;
|
||||
}
|
||||
}
|
||||
|
||||
StationRecordsFilterType.SelectId((int)_currentFilterType);
|
||||
|
||||
if (state.RecordListing == null)
|
||||
if (recordListing == null)
|
||||
{
|
||||
RecordListingStatus.Visible = true;
|
||||
RecordListing.Visible = false;
|
||||
|
|
@ -105,17 +109,17 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
|
|||
RecordListing.Visible = true;
|
||||
RecordContainer.Visible = true;
|
||||
|
||||
PopulateRecordListing(state.RecordListing!, state.SelectedKey);
|
||||
PopulateRecordListing(recordListing, selectedKey);
|
||||
|
||||
RecordContainerStatus.Visible = state.Record == null;
|
||||
RecordContainerStatus.Visible = record == null;
|
||||
|
||||
if (state.Record != null)
|
||||
if (record != null)
|
||||
{
|
||||
RecordContainerStatus.Visible = state.SelectedKey == null;
|
||||
RecordContainerStatus.Text = state.SelectedKey == null
|
||||
RecordContainerStatus.Visible = selectedKey == null;
|
||||
RecordContainerStatus.Text = selectedKey == null
|
||||
? Loc.GetString("general-station-record-console-no-record-found")
|
||||
: Loc.GetString("general-station-record-console-select-record-info");
|
||||
PopulateRecordContainer(state.Record, state.CanDeleteEntries, state.SelectedKey);
|
||||
PopulateRecordContainer(record, canDeleteEntries, selectedKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
using Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Client.StationRecords;
|
||||
|
||||
public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
{
|
||||
}
|
||||
|
|
@ -92,6 +92,7 @@ public sealed partial class StealthSystem : SharedStealthSystem
|
|||
|
||||
_shader.SetParameter("reference", reference);
|
||||
_shader.SetParameter("visibility", visibility);
|
||||
_shader.SetParameter("shimmer_frequency", component.ShimmerFrequency);
|
||||
|
||||
visibility = MathF.Max(0, visibility);
|
||||
_sprite.SetColor((uid, args.Sprite), new Color(visibility, visibility, 1, 1));
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ public sealed class LabelSheetlet : Sheetlet<PalettedStylesheet>
|
|||
.Class(StyleClass.LabelHeadingBigger)
|
||||
.Font(sheet.BaseFont.GetFont(20, FontKind.Bold))
|
||||
.FontColor(sheet.HighlightPalette.Text),
|
||||
E<Label>()
|
||||
.Class(StyleClass.LabelSubHeading)
|
||||
.Font(sheet.BaseFont.GetFont(14, FontKind.Italic))
|
||||
.FontColor(sheet.HighlightPalette.TextDark),
|
||||
E<Label>()
|
||||
.Class(StyleClass.LabelSubText)
|
||||
.Font(sheet.BaseFont.GetFont(10))
|
||||
|
|
|
|||
|
|
@ -21,6 +21,13 @@ public sealed class PanelSheetlet<T> : Sheetlet<T> where T : PalettedStylesheet,
|
|||
{
|
||||
BackgroundColor = sheet.SecondaryPalette.BackgroundDark,
|
||||
};
|
||||
var boxInsetDark = new StyleBoxFlat()
|
||||
{
|
||||
BackgroundColor = sheet.SecondaryPalette.BackgroundDark,
|
||||
BorderColor = sheet.PrimaryPalette.Background,
|
||||
BorderThickness = new Thickness(2f),
|
||||
};
|
||||
|
||||
var boxPositive = new StyleBoxFlat { BackgroundColor = sheet.PositivePalette.Background };
|
||||
var boxNegative = new StyleBoxFlat { BackgroundColor = sheet.NegativePalette.Background };
|
||||
var boxHighlight = new StyleBoxFlat { BackgroundColor = sheet.HighlightPalette.Background };
|
||||
|
|
@ -36,6 +43,7 @@ public sealed class PanelSheetlet<T> : Sheetlet<T> where T : PalettedStylesheet,
|
|||
E<PanelContainer>().Class(StyleClass.PanelLight).Panel(boxLight),
|
||||
E<PanelContainer>().Class(StyleClass.PanelDark).Panel(boxDark),
|
||||
E<PanelContainer>().Class(StyleClass.PanelDropTarget).Panel(boxDropTarget),
|
||||
E<PanelContainer>().Class(StyleClass.PanelInsetDark).Panel(boxInsetDark),
|
||||
|
||||
E<PanelContainer>().Class(StyleClass.Positive).Panel(boxPositive),
|
||||
E<PanelContainer>().Class(StyleClass.Negative).Panel(boxNegative),
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public static class StyleClass
|
|||
|
||||
public const string LabelHeading = "LabelHeading";
|
||||
public const string LabelHeadingBigger = "LabelHeadingBigger";
|
||||
public const string LabelSubHeading = "LabelSubHeading";
|
||||
public const string LabelSubText = "LabelSubText";
|
||||
public const string LabelKeyText = "LabelKeyText";
|
||||
public const string LabelWeak = "LabelWeak"; // replaces `LabelSecondaryColor`
|
||||
|
|
@ -52,6 +53,7 @@ public static class StyleClass
|
|||
public const string PanelDark = "PanelDark";
|
||||
public const string PanelLight = "PanelLight";
|
||||
public const string PanelDropTarget = "PanelDropTarget";
|
||||
public const string PanelInsetDark = "PanelInsetDark";
|
||||
|
||||
public const string ButtonOpenRight = "OpenRight";
|
||||
public const string ButtonOpenLeft = "OpenLeft";
|
||||
|
|
|
|||
|
|
@ -1,285 +0,0 @@
|
|||
using System.Numerics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
// This control is not part of engine because I quickly wrote it in 2 hours at 2 AM and don't want to deal with
|
||||
// API stabilization and/or figuring out relation to GridContainer.
|
||||
// Grid layout is a complicated problem and I don't want to commit another half-baked thing into the engine.
|
||||
// It's probably sufficient for its use case (RichTextLabel tables for rules/guidebook).
|
||||
// Despite that, it's still better comment the shit half of you write on a regular basis.
|
||||
//
|
||||
// EMO: thank you PJB i was going to kill myself.
|
||||
|
||||
/// <summary>
|
||||
/// Displays children in a tabular grid. Unlike <see cref="GridContainer"/>,
|
||||
/// properly handles layout constraints so putting word-wrapping <see cref="RichTextLabel"/> in it should work.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All children are automatically laid out in <see cref="Columns"/> columns.
|
||||
/// The first control is in the top left, laid out per row from there.
|
||||
/// </remarks>
|
||||
[Virtual]
|
||||
public class TableContainer : Container
|
||||
{
|
||||
private int _columns = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The absolute minimum width a column can be forced to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If a column *asks* for less width than this (small contents), it can still be smaller.
|
||||
/// But if it asks for more it cannot go below this width.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public float MinForcedColumnWidth { get; set; } = 50;
|
||||
|
||||
// Scratch space used while calculating layout, cached to avoid regular allocations during layout pass.
|
||||
private ColumnData[] _columnDataCache = [];
|
||||
private RowData[] _rowDataCache = [];
|
||||
|
||||
/// <summary>
|
||||
/// How many columns should be displayed.
|
||||
/// </summary>
|
||||
public int Columns
|
||||
{
|
||||
get => _columns;
|
||||
set
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(value, 1, nameof(value));
|
||||
|
||||
_columns = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
ResetCachedArrays();
|
||||
|
||||
// Do a first pass measuring all child controls as if they're given infinite space.
|
||||
// This gives us a maximum width the columns want, which we use to proportion them later.
|
||||
var columnIdx = 0;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
ref var column = ref _columnDataCache[columnIdx];
|
||||
|
||||
child.Measure(new Vector2(float.PositiveInfinity, float.PositiveInfinity));
|
||||
column.MaxWidth = Math.Max(column.MaxWidth, child.DesiredSize.X);
|
||||
|
||||
columnIdx += 1;
|
||||
if (columnIdx == _columns)
|
||||
columnIdx = 0;
|
||||
}
|
||||
|
||||
// Calculate Slack and MinWidth for all columns. Also calculate sums for all columns.
|
||||
var totalMinWidth = 0f;
|
||||
var totalMaxWidth = 0f;
|
||||
var totalSlack = 0f;
|
||||
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
column.MinWidth = Math.Min(column.MaxWidth, MinForcedColumnWidth);
|
||||
column.Slack = column.MaxWidth - column.MinWidth;
|
||||
|
||||
totalMinWidth += column.MinWidth;
|
||||
totalMaxWidth += column.MaxWidth;
|
||||
totalSlack += column.Slack;
|
||||
}
|
||||
|
||||
if (totalMaxWidth <= availableSize.X)
|
||||
{
|
||||
// We want less horizontal space than we're given. Huh, that's convenient.
|
||||
// Just set assigned width to be however much they asked for.
|
||||
// We could probably skip the second measure pass in this scenario,
|
||||
// but that's just an optimization, so I don't care right now.
|
||||
//
|
||||
// There's probably a very clever way to make this behavior work with the else block of logic,
|
||||
// just by fiddling with the math.
|
||||
// I'm dumb, it's 4:30 AM. Yeah, I *started* at 2 AM.
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
|
||||
column.AssignedWidth = column.MaxWidth;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't have enough horizontal space,
|
||||
// at least without causing *some* sort of word wrapping (assuming text contents).
|
||||
//
|
||||
// Assign horizontal space proportional to the wanted maximum size of the columns.
|
||||
var assignableWidth = Math.Max(0, availableSize.X - totalMinWidth);
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
|
||||
var slackRatio = column.Slack / totalSlack;
|
||||
column.AssignedWidth = column.MinWidth + slackRatio * assignableWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Go over controls for a second measuring pass, this time giving them their assigned measure width.
|
||||
// This will give us a height to slot into per-row data.
|
||||
// We still measure assuming infinite vertical space.
|
||||
// This control can't properly handle being constrained on the Y axis.
|
||||
columnIdx = 0;
|
||||
var rowIdx = 0;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
ref var column = ref _columnDataCache[columnIdx];
|
||||
ref var row = ref _rowDataCache[rowIdx];
|
||||
|
||||
child.Measure(new Vector2(column.AssignedWidth, float.PositiveInfinity));
|
||||
row.MeasuredHeight = Math.Max(row.MeasuredHeight, child.DesiredSize.Y);
|
||||
|
||||
columnIdx += 1;
|
||||
if (columnIdx == _columns)
|
||||
{
|
||||
columnIdx = 0;
|
||||
rowIdx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Sum up height of all rows to get final measured table height.
|
||||
var totalHeight = 0f;
|
||||
for (var r = 0; r < _rowDataCache.Length; r++)
|
||||
{
|
||||
ref var row = ref _rowDataCache[r];
|
||||
totalHeight += row.MeasuredHeight;
|
||||
}
|
||||
|
||||
return new Vector2(Math.Min(availableSize.X, totalMaxWidth), totalHeight);
|
||||
}
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
// TODO: Expand to fit given vertical space.
|
||||
|
||||
// Calculate MinWidth and Slack sums again from column data.
|
||||
// We could've cached these from measure but whatever.
|
||||
var totalMinWidth = 0f;
|
||||
var totalSlack = 0f;
|
||||
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
totalMinWidth += column.MinWidth;
|
||||
totalSlack += column.Slack;
|
||||
}
|
||||
|
||||
// Calculate new width based on final given size, also assign horizontal positions of all columns.
|
||||
var assignableWidth = Math.Max(0, finalSize.X - totalMinWidth);
|
||||
var xPos = 0f;
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
|
||||
var slackRatio = column.Slack / totalSlack;
|
||||
column.ArrangedWidth = column.MinWidth + slackRatio * assignableWidth;
|
||||
column.ArrangedX = xPos;
|
||||
|
||||
xPos += column.ArrangedWidth;
|
||||
}
|
||||
|
||||
// Do actual arrangement row-by-row.
|
||||
var arrangeY = 0f;
|
||||
for (var r = 0; r < _rowDataCache.Length; r++)
|
||||
{
|
||||
ref var row = ref _rowDataCache[r];
|
||||
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
var index = c + r * _columns;
|
||||
|
||||
if (index >= ChildCount) // Quit early if we don't actually fill out the row.
|
||||
break;
|
||||
var child = GetChild(c + r * _columns);
|
||||
|
||||
child.Arrange(UIBox2.FromDimensions(column.ArrangedX, arrangeY, column.ArrangedWidth, row.MeasuredHeight));
|
||||
}
|
||||
|
||||
arrangeY += row.MeasuredHeight;
|
||||
}
|
||||
|
||||
return finalSize with { Y = arrangeY };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure cached array space is allocated to correct size and is reset to a clean slate.
|
||||
/// </summary>
|
||||
private void ResetCachedArrays()
|
||||
{
|
||||
// 1-argument Array.Clear() is not currently available in sandbox (added in .NET 6).
|
||||
|
||||
if (_columnDataCache.Length != _columns)
|
||||
_columnDataCache = new ColumnData[_columns];
|
||||
|
||||
Array.Clear(_columnDataCache, 0, _columnDataCache.Length);
|
||||
|
||||
var rowCount = ChildCount / _columns;
|
||||
if (ChildCount % _columns != 0)
|
||||
rowCount += 1;
|
||||
|
||||
if (rowCount != _rowDataCache.Length)
|
||||
_rowDataCache = new RowData[rowCount];
|
||||
|
||||
Array.Clear(_rowDataCache, 0, _rowDataCache.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-column data used during layout.
|
||||
/// </summary>
|
||||
private struct ColumnData
|
||||
{
|
||||
// Measure data.
|
||||
|
||||
/// <summary>
|
||||
/// The maximum width any control in this column wants, if given infinite space.
|
||||
/// Maximum of all controls on the column.
|
||||
/// </summary>
|
||||
public float MaxWidth;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum width this column may be given.
|
||||
/// This is either <see cref="MaxWidth"/> or <see cref="TableContainer.MinForcedColumnWidth"/>.
|
||||
/// </summary>
|
||||
public float MinWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Difference between max and min width; how much this column can expand from its minimum.
|
||||
/// </summary>
|
||||
public float Slack;
|
||||
|
||||
/// <summary>
|
||||
/// How much horizontal space this column was assigned at measure time.
|
||||
/// </summary>
|
||||
public float AssignedWidth;
|
||||
|
||||
// Arrange data.
|
||||
|
||||
/// <summary>
|
||||
/// How much horizontal space this column was assigned at arrange time.
|
||||
/// </summary>
|
||||
public float ArrangedWidth;
|
||||
|
||||
/// <summary>
|
||||
/// The horizontal position this column was assigned at arrange time.
|
||||
/// </summary>
|
||||
public float ArrangedX;
|
||||
}
|
||||
|
||||
private struct RowData
|
||||
{
|
||||
// Measure data.
|
||||
|
||||
/// <summary>
|
||||
/// How much height the tallest control on this row was measured at,
|
||||
/// measuring for infinite vertical space but assigned column width.
|
||||
/// </summary>
|
||||
public float MeasuredHeight;
|
||||
}
|
||||
}
|
||||
11
Content.Client/Wall/Components/WallMountTreeComponent.cs
Normal file
11
Content.Client/Wall/Components/WallMountTreeComponent.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using Content.Shared.Wall;
|
||||
using Robust.Shared.ComponentTrees;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Content.Client.Wall.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class WallMountTreeComponent : Component, IComponentTreeComponent<WallMountComponent>
|
||||
{
|
||||
public DynamicTree<ComponentTreeEntry<WallMountComponent>> Tree { get; set; } = default!;
|
||||
}
|
||||
22
Content.Client/Wall/Systems/WallMountTreeSystem.cs
Normal file
22
Content.Client/Wall/Systems/WallMountTreeSystem.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using System.Numerics;
|
||||
using Content.Client.Wall.Components;
|
||||
using Content.Shared.Wall;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.ComponentTrees;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Content.Client.Wall.Systems;
|
||||
|
||||
public sealed partial class WallMountTreeSystem : ComponentTreeSystem<WallMountTreeComponent, WallMountComponent>
|
||||
{
|
||||
[Dependency] private SpriteSystem _sprite = default!;
|
||||
|
||||
protected override bool DoFrameUpdate => true;
|
||||
protected override bool DoTickUpdate => false;
|
||||
protected override bool Recursive => false;
|
||||
|
||||
protected override Box2 ExtractAabb(in ComponentTreeEntry<WallMountComponent> entry, Vector2 pos, Angle rot)
|
||||
{
|
||||
return _sprite.CalculateBounds((entry.Uid, Comp<SpriteComponent>(entry.Uid)), pos, rot, default).CalcBoundingBox();
|
||||
}
|
||||
}
|
||||
187
Content.Client/Wall/Systems/WallMountVisibilitySystem.cs
Normal file
187
Content.Client/Wall/Systems/WallMountVisibilitySystem.cs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Wall;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Wall.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Manages the directional visibility overlay for wall-mounted entities.
|
||||
/// </summary>
|
||||
public sealed partial class WallMountVisibilitySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private IConfigurationManager _cfg = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private IOverlayManager _overlay = default!;
|
||||
[Dependency] private SharedMapSystem _map = default!;
|
||||
[Dependency] private SpriteSystem _sprite = default!;
|
||||
[Dependency] private TagSystem _tag = default!;
|
||||
[Dependency] private TransformSystem _xform = default!;
|
||||
[Dependency] private WallMountTreeSystem _tree = default!;
|
||||
|
||||
[Dependency] private EntityQuery<MapGridComponent> _gridQuery = default!;
|
||||
[Dependency] private EntityQuery<SpriteComponent> _spriteQuery = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Tags that block visibility when present on the same tile.
|
||||
/// </summary>
|
||||
private static readonly ProtoId<TagPrototype>[] BlockingTags = ["Wall"];
|
||||
|
||||
/// <summary>
|
||||
/// Caches for whether a tile has any blocking entity.
|
||||
/// </summary>
|
||||
private readonly Dictionary<(EntityUid Grid, Vector2i Tile), bool> _tileCache = [];
|
||||
|
||||
private WallMountVisibilityOverlay _overlayInstance = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether directional visibility is currently enabled.
|
||||
/// </summary>
|
||||
internal bool DirectionalVisibilityEnabled = true;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_overlayInstance = new WallMountVisibilityOverlay(_timing, _map, _sprite, _xform, _tree, this, _gridQuery, _spriteQuery);
|
||||
Subs.CVar(_cfg, CCVars.WallMountDirectionalVisibility, OnDirectionalVisibilityChanged, true);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_overlay.RemoveOverlay(_overlayInstance);
|
||||
}
|
||||
|
||||
private void OnDirectionalVisibilityChanged(bool enabled)
|
||||
{
|
||||
DirectionalVisibilityEnabled = enabled;
|
||||
|
||||
if (enabled)
|
||||
_overlay.AddOverlay(_overlayInstance);
|
||||
else
|
||||
{
|
||||
_overlay.RemoveOverlay(_overlayInstance);
|
||||
SetAllVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates tile cache when anchor state changes for a blocking entity.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnTagAnchorChanged(Entity<TagComponent> ent, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (!_tag.HasAnyTag(ent.Comp, BlockingTags))
|
||||
return;
|
||||
|
||||
var xform = args.Transform;
|
||||
if (xform.GridUid is not { } gridUid)
|
||||
return;
|
||||
|
||||
if (!_gridQuery.TryGetComponent(gridUid, out var grid))
|
||||
return;
|
||||
|
||||
var tile = _map.TileIndicesFor(gridUid, grid, xform.Coordinates);
|
||||
_tileCache.Remove((gridUid, tile));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes the entity visible again on component shutdown.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnWallMountShutdown(Entity<WallMountComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (TerminatingOrDeleted(ent))
|
||||
return;
|
||||
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
_sprite.SetVisible((ent, sprite), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes the entity visible again if directional visibility is disabled for this mount.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnWallMountAfterHandleState(Entity<WallMountComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (ent.Comp.DirectionalVisibility)
|
||||
return;
|
||||
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
_sprite.SetVisible((ent, sprite), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all cached entries for a grid that is being removed.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnGridRemoval(GridRemovalEvent ev)
|
||||
{
|
||||
foreach (var key in _tileCache.Keys.Where(k => k.Grid == ev.EntityUid).ToList())
|
||||
{
|
||||
_tileCache.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears tile cache and resets all wall-mount visibility on round restart.
|
||||
/// </summary>
|
||||
[SubscribeNetworkEvent]
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
_tileCache.Clear();
|
||||
SetAllVisible(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces all wall-mount entities to become visible or hidden.
|
||||
/// </summary>
|
||||
internal void SetAllVisible(bool visible)
|
||||
{
|
||||
var query = AllEntityQuery<WallMountComponent, SpriteComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var sprite))
|
||||
{
|
||||
_sprite.SetVisible((uid, sprite), visible);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the tile contains any anchored blocking entity.
|
||||
/// </summary>
|
||||
internal bool IsTileBlocked(EntityUid gridUid, Vector2i tile, EntityUid? ignoreUid = null)
|
||||
{
|
||||
if (!_gridQuery.TryGetComponent(gridUid, out var grid))
|
||||
return false;
|
||||
|
||||
var key = (gridUid, tile);
|
||||
if (_tileCache.TryGetValue(key, out var cached))
|
||||
return cached;
|
||||
|
||||
var enumerator = _map.GetAnchoredEntitiesEnumerator(gridUid, grid, tile);
|
||||
while (enumerator.MoveNext(out var anchored))
|
||||
{
|
||||
if (anchored == ignoreUid)
|
||||
continue;
|
||||
|
||||
if (!_tag.HasAnyTag(anchored.Value, BlockingTags))
|
||||
continue;
|
||||
|
||||
return _tileCache[key] = true;
|
||||
}
|
||||
|
||||
return _tileCache[key] = false;
|
||||
}
|
||||
}
|
||||
88
Content.Client/Wall/WallMountFadeState.cs
Normal file
88
Content.Client/Wall/WallMountFadeState.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Wall;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks fade progress for a wall-mounted entity.
|
||||
/// </summary>
|
||||
internal struct WallMountFadeState
|
||||
{
|
||||
public float OriginalAlpha;
|
||||
public float CurrentAlpha;
|
||||
public float TargetAlpha;
|
||||
|
||||
/// <summary>
|
||||
/// Sprite alpha after applying the fade multiplier.
|
||||
/// </summary>
|
||||
public readonly float EffectiveAlpha => OriginalAlpha * CurrentAlpha;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the entity should be rendered as visible at the current fade level.
|
||||
/// </summary>
|
||||
public readonly bool IsVisible => CurrentAlpha > 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new fade state snapped immediately to the given target alpha.
|
||||
/// </summary>
|
||||
public static WallMountFadeState Snapped(float originalAlpha, float targetAlpha)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
OriginalAlpha = originalAlpha,
|
||||
CurrentAlpha = targetAlpha,
|
||||
TargetAlpha = targetAlpha,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves <see cref="CurrentAlpha"/> one step towards <see cref="TargetAlpha"/>.
|
||||
/// </summary>
|
||||
public void StepTowards(float fadeStep)
|
||||
{
|
||||
if (MathHelper.CloseTo(CurrentAlpha, TargetAlpha))
|
||||
return;
|
||||
|
||||
var delta = TargetAlpha - CurrentAlpha;
|
||||
var step = MathF.Sign(delta) * MathF.Min(MathF.Abs(delta), fadeStep);
|
||||
CurrentAlpha = Math.Clamp(CurrentAlpha + step, 0f, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-viewport fade state for wall-mounted entities.
|
||||
/// </summary>
|
||||
internal sealed class ViewportFadeState(SpriteSystem sprite, EntityQuery<SpriteComponent> spriteQuery) : IDisposable
|
||||
{
|
||||
private readonly SpriteSystem _sprite = sprite;
|
||||
private readonly EntityQuery<SpriteComponent> _spriteQuery = spriteQuery;
|
||||
|
||||
/// <summary>
|
||||
/// Fade states for entities tracked in this viewport.
|
||||
/// </summary>
|
||||
public readonly Dictionary<EntityUid, WallMountFadeState> FadeStates = [];
|
||||
|
||||
/// <summary>
|
||||
/// Entities visible this frame.
|
||||
/// </summary>
|
||||
public readonly HashSet<EntityUid> SeenThisFrame = [];
|
||||
|
||||
/// <summary>
|
||||
/// Whether FOV was enabled during the previous frame.
|
||||
/// </summary>
|
||||
public bool WasFovEnabled = true;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var (uid, state) in FadeStates)
|
||||
{
|
||||
if (!_spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
continue;
|
||||
|
||||
_sprite.SetColor((uid, sprite), sprite.Color.WithAlpha(state.OriginalAlpha));
|
||||
_sprite.SetVisible((uid, sprite), true);
|
||||
}
|
||||
|
||||
FadeStates.Clear();
|
||||
SeenThisFrame.Clear();
|
||||
}
|
||||
}
|
||||
242
Content.Client/Wall/WallMountVisibilityOverlay.cs
Normal file
242
Content.Client/Wall/WallMountVisibilityOverlay.cs
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Graphics;
|
||||
using Content.Client.Wall.Systems;
|
||||
using Content.Shared.Wall;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Wall;
|
||||
|
||||
/// <summary>
|
||||
/// Renders wall-mounted entities conditionally based on their facing arc relative to the viewport's eye.
|
||||
/// </summary>
|
||||
public sealed partial class WallMountVisibilityOverlay(
|
||||
IGameTiming timing,
|
||||
SharedMapSystem map,
|
||||
SpriteSystem sprite,
|
||||
TransformSystem xform,
|
||||
WallMountTreeSystem tree,
|
||||
WallMountVisibilitySystem visibility,
|
||||
EntityQuery<MapGridComponent> gridQuery,
|
||||
EntityQuery<SpriteComponent> spriteQuery) : Overlay
|
||||
{
|
||||
private readonly IGameTiming _timing = timing;
|
||||
private readonly SharedMapSystem _map = map;
|
||||
private readonly SpriteSystem _sprite = sprite;
|
||||
private readonly TransformSystem _xform = xform;
|
||||
private readonly WallMountTreeSystem _tree = tree;
|
||||
private readonly WallMountVisibilitySystem _visibility = visibility;
|
||||
|
||||
private readonly EntityQuery<MapGridComponent> _gridQuery = gridQuery;
|
||||
private readonly EntityQuery<SpriteComponent> _spriteQuery = spriteQuery;
|
||||
|
||||
/// <summary>
|
||||
/// Caches <see cref="ViewportFadeState"/> instances per viewport.
|
||||
/// </summary>
|
||||
private readonly OverlayResourceCache<ViewportFadeState> _fadeCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Original sprite alphas, shared across viewports to avoid capturing values modified by another viewport.
|
||||
/// </summary>
|
||||
private readonly Dictionary<EntityUid, float> _originalAlphas = [];
|
||||
|
||||
/// <summary>
|
||||
/// Alpha change per second during fade.
|
||||
/// </summary>
|
||||
private const float FadeSpeed = 9f;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (!_visibility.DirectionalVisibilityEnabled)
|
||||
return;
|
||||
|
||||
if (args.Viewport.Eye is not { } eye)
|
||||
return;
|
||||
|
||||
var viewportState = _fadeCache.GetForViewport(args.Viewport, _ => new ViewportFadeState(_sprite, _spriteQuery));
|
||||
|
||||
if (!eye.DrawFov)
|
||||
{
|
||||
HandleFovDisabled(args, viewportState);
|
||||
return;
|
||||
}
|
||||
|
||||
viewportState.WasFovEnabled = true;
|
||||
|
||||
var fadeStep = FadeSpeed * (float)_timing.FrameTime.TotalSeconds;
|
||||
var matrix = args.Viewport.GetWorldToLocalMatrix();
|
||||
|
||||
viewportState.SeenThisFrame.Clear();
|
||||
ProcessVisibleEntities(args, eye, matrix, fadeStep, viewportState);
|
||||
|
||||
// Remove entities that left the viewport this frame.
|
||||
foreach (var uid in viewportState.FadeStates.Keys.Except(viewportState.SeenThisFrame).ToList())
|
||||
{
|
||||
RemoveTrackedEntity(uid, viewportState);
|
||||
}
|
||||
|
||||
ApplyFadeToVisibleEntities(viewportState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When FOV gets disabled, clears fade state and restores sprites.
|
||||
/// On subsequent frames, keeps wall-mounts visible and restores alpha modified by other viewports.
|
||||
/// </summary>
|
||||
private void HandleFovDisabled(in OverlayDrawArgs args, ViewportFadeState viewportState)
|
||||
{
|
||||
if (viewportState.WasFovEnabled)
|
||||
{
|
||||
ClearViewportFadeState(viewportState);
|
||||
viewportState.WasFovEnabled = false;
|
||||
}
|
||||
|
||||
// Restore alpha modified by other viewports.
|
||||
foreach (var entity in _tree.QueryAabb(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var uid = entity.Uid;
|
||||
if (!_spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
continue;
|
||||
|
||||
if (_originalAlphas.Remove(uid, out var origAlpha))
|
||||
_sprite.SetColor((uid, sprite), sprite.Color.WithAlpha(origAlpha));
|
||||
|
||||
_sprite.SetVisible((uid, sprite), true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates fade state for all wall-mounted entities in the viewport.
|
||||
/// </summary>
|
||||
private void ProcessVisibleEntities(in OverlayDrawArgs args, IEye eye, Matrix3x2 matrix, float fadeStep, ViewportFadeState viewportState)
|
||||
{
|
||||
foreach (var entity in _tree.QueryAabb(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var (wallmount, xform) = entity;
|
||||
var uid = entity.Uid;
|
||||
|
||||
if (!_spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
continue;
|
||||
|
||||
// Capture original alpha before any viewport modifies it.
|
||||
if (!_originalAlphas.TryGetValue(uid, out var originalAlpha))
|
||||
_originalAlphas[uid] = originalAlpha = sprite.Color.A;
|
||||
|
||||
viewportState.SeenThisFrame.Add(uid);
|
||||
|
||||
var targetAlpha = ComputeTargetAlpha(uid, wallmount, xform, eye, matrix);
|
||||
UpdateFadeState(uid, originalAlpha, targetAlpha, fadeStep, viewportState);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns 1 if the entity is within its facing arc relative to the eye, 0 otherwise.
|
||||
/// </summary>
|
||||
private float ComputeTargetAlpha(EntityUid uid, WallMountComponent wallmount, TransformComponent xform, IEye eye, Matrix3x2 matrix)
|
||||
{
|
||||
if (!wallmount.DirectionalVisibility || wallmount.Arc >= Math.Tau)
|
||||
return 1f;
|
||||
|
||||
if (xform.GridUid is not { } gridUid || !_gridQuery.TryGetComponent(gridUid, out var grid))
|
||||
return 1f;
|
||||
|
||||
var tile = _map.TileIndicesFor(gridUid, grid, xform.Coordinates);
|
||||
if (!_visibility.IsTileBlocked(gridUid, tile, uid))
|
||||
return 1f;
|
||||
|
||||
var (pos, rot) = _xform.GetWorldPositionRotation(xform);
|
||||
var facingAngle = rot + eye.Rotation + wallmount.Direction;
|
||||
|
||||
var entityScreenPos = Vector2.Transform(pos, matrix);
|
||||
var eyeScreenPos = Vector2.Transform(eye.Position.Position, matrix);
|
||||
var toEntity = entityScreenPos - eyeScreenPos;
|
||||
var eyeToEntityAngle = (toEntity with { X = -toEntity.X }).ToWorldAngle();
|
||||
|
||||
var angleDiff = Angle.ShortestDistance(eyeToEntityAngle, facingAngle);
|
||||
return Math.Abs(angleDiff) < wallmount.Arc / 2 ? 1f : 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the current fade state to all entities seen this frame.
|
||||
/// </summary>
|
||||
private void ApplyFadeToVisibleEntities(ViewportFadeState viewportState)
|
||||
{
|
||||
foreach (var uid in viewportState.SeenThisFrame)
|
||||
{
|
||||
if (!viewportState.FadeStates.TryGetValue(uid, out var state))
|
||||
continue;
|
||||
|
||||
if (!_spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
{
|
||||
RemoveTrackedEntity(uid, viewportState);
|
||||
continue;
|
||||
}
|
||||
|
||||
_sprite.SetColor((uid, sprite), sprite.Color.WithAlpha(state.EffectiveAlpha));
|
||||
_sprite.SetVisible((uid, sprite), state.IsVisible);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates fade target for a tracked entity. Newly seen entities snap immediately without fading.
|
||||
/// </summary>
|
||||
private static void UpdateFadeState(EntityUid uid, float originalAlpha, float targetAlpha, float fadeStep, ViewportFadeState viewportState)
|
||||
{
|
||||
if (viewportState.FadeStates.TryGetValue(uid, out var state))
|
||||
{
|
||||
state.TargetAlpha = targetAlpha;
|
||||
state.StepTowards(fadeStep);
|
||||
viewportState.FadeStates[uid] = state;
|
||||
return;
|
||||
}
|
||||
|
||||
viewportState.FadeStates[uid] = WallMountFadeState.Snapped(originalAlpha, targetAlpha);
|
||||
}
|
||||
|
||||
private void RestoreSprite(EntityUid uid, WallMountFadeState state)
|
||||
{
|
||||
if (!_spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
return;
|
||||
|
||||
_sprite.SetColor((uid, sprite), sprite.Color.WithAlpha(state.OriginalAlpha));
|
||||
_sprite.SetVisible((uid, sprite), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an entity from fade tracking and restores its sprite.
|
||||
/// </summary>
|
||||
private void RemoveTrackedEntity(EntityUid uid, ViewportFadeState viewportState)
|
||||
{
|
||||
if (viewportState.FadeStates.Remove(uid, out var state))
|
||||
RestoreSprite(uid, state);
|
||||
|
||||
_originalAlphas.Remove(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all fade tracking for a viewport and restores sprites.
|
||||
/// </summary>
|
||||
private void ClearViewportFadeState(ViewportFadeState viewportState)
|
||||
{
|
||||
foreach (var (uid, state) in viewportState.FadeStates)
|
||||
{
|
||||
RestoreSprite(uid, state);
|
||||
_originalAlphas.Remove(uid);
|
||||
}
|
||||
|
||||
viewportState.FadeStates.Clear();
|
||||
viewportState.SeenThisFrame.Clear();
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
{
|
||||
_fadeCache.Dispose();
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,13 +7,16 @@ namespace Content.Client.Weapons.Melee.UI;
|
|||
/// <summary>
|
||||
/// Initializes a <see cref="MeleeSpeechWindow"/> and updates it when new server messages are received.
|
||||
/// </summary>
|
||||
public sealed class MeleeSpeechBoundUserInterface : BoundUserInterface
|
||||
public sealed partial class MeleeSpeechBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private IEntityManager _entManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private MeleeSpeechWindow? _window;
|
||||
|
||||
public MeleeSpeechBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
|
|
@ -21,7 +24,14 @@ public sealed class MeleeSpeechBoundUserInterface : BoundUserInterface
|
|||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<MeleeSpeechWindow>();
|
||||
_window.OnBattlecryEntered += OnBattlecryChanged;
|
||||
|
||||
if (_entManager.TryGetComponent(Owner, out MeleeSpeechComponent? speech))
|
||||
{
|
||||
_window.SetInitialBattlecry(speech!.Battlecry);
|
||||
_window.SetMaxBattlecryLength(speech!.MaxBattlecryLength);
|
||||
}
|
||||
|
||||
_window.OnBattlecryChanged += OnBattlecryChanged;
|
||||
}
|
||||
|
||||
private void OnBattlecryChanged(string newBattlecry)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="{Loc melee-speech-menu-title}">
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="4" MinWidth="150" >
|
||||
Title="{Loc melee-speech-menu-title}"
|
||||
SetWidth="150"
|
||||
MinWidth="150">
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="4" >
|
||||
<Label Name="CurrentBattlecry" Text="{Loc 'melee-speech-current-battlecry'}" />
|
||||
<LineEdit Name="BattlecryLineEdit" />
|
||||
</BoxContainer>
|
||||
|
|
|
|||
|
|
@ -7,22 +7,37 @@ namespace Content.Client.Weapons.Melee.UI;
|
|||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MeleeSpeechWindow : DefaultWindow
|
||||
{
|
||||
|
||||
public event Action<string>? OnBattlecryEntered;
|
||||
public event Action<string>? OnBattlecryChanged;
|
||||
|
||||
public MeleeSpeechWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
BattlecryLineEdit.OnTextEntered += e => OnBattlecryEntered?.Invoke(e.Text);
|
||||
BattlecryLineEdit.OnTextChanged += e => OnBattlecryChanged?.Invoke(e.Text);
|
||||
}
|
||||
|
||||
|
||||
public void SetCurrentBattlecry(string battlecry)
|
||||
{
|
||||
BattlecryLineEdit.Text = battlecry;
|
||||
if (!BattlecryLineEdit.HasKeyboardFocus())
|
||||
BattlecryLineEdit.Text = battlecry;
|
||||
}
|
||||
|
||||
public void SetInitialBattlecry(string? battlecry)
|
||||
{
|
||||
if (battlecry != null)
|
||||
{
|
||||
BattlecryLineEdit.Text = battlecry;
|
||||
BattlecryLineEdit.CursorPosition = battlecry.Length;
|
||||
BattlecryLineEdit.SelectionStart = 0;
|
||||
}
|
||||
|
||||
BattlecryLineEdit.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
public void SetMaxBattlecryLength(int maxLength)
|
||||
{
|
||||
BattlecryLineEdit.IsValid = s => s.Length <= maxLength;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -521,7 +521,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
|||
|
||||
private bool CheckFixtures(Entity<FixturesComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return false;
|
||||
|
||||
// TODO: Maybe also check that our cursor is intersecting a valid fixture?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
using Content.IntegrationTests.Fixtures.Attributes;
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Server.Stack;
|
||||
using static Content.IntegrationTests.Tests.Stacks.StackTestPrototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Stacks;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(StackSystem))]
|
||||
public sealed class StackInteractionTest : InteractionTest
|
||||
{
|
||||
[SidedDependency(Side.Server)] private readonly StackSystem _sStackSystem = default!;
|
||||
|
||||
[Test]
|
||||
[Description("Test that using a stack on a stack will combine them to the hand.")]
|
||||
public async Task InteractUsingTest()
|
||||
{
|
||||
var held = await Spawn(StackEnt1);
|
||||
await Pickup(held);
|
||||
|
||||
await SpawnTarget(StackEnt1);
|
||||
await Interact();
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
// Assert that the held stack has the full count
|
||||
// And the ground stack was deleted
|
||||
Assert.That(_sStackSystem.GetCount(ToServer(held)), Is.EqualTo(2));
|
||||
Assert.That(SEntMan.EntityExists(ToServer(Target)), Is.False);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Test alt interact with a stack splits it in half.")]
|
||||
public async Task SplitTest()
|
||||
{
|
||||
await SpawnTarget(StackEnt30);
|
||||
await Interact(altInteract: true);
|
||||
|
||||
var targetCount = 0;
|
||||
var heldCount = 0;
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
targetCount = _sStackSystem.GetCount(STarget.Value);
|
||||
|
||||
if (HandSys.GetActiveItem((ToServer(Player), Hands)) is { } held)
|
||||
heldCount = _sStackSystem.GetCount(held);
|
||||
});
|
||||
|
||||
// Check that the count was evenly split
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(targetCount, Is.EqualTo(15));
|
||||
Assert.That(heldCount, Is.EqualTo(15));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO test split verb
|
||||
// I don't know how to navigate the right click menu in integration tests to find verbs
|
||||
|
||||
// TODO a test for eating a stack
|
||||
// Currently the player supplied by InteractionTest doesn't have a body or a stomach to eat with
|
||||
// And BodySystem has no API for cleanly adding the needed parts
|
||||
}
|
||||
68
Content.IntegrationTests/Tests/Stacks/StackTestPrototypes.cs
Normal file
68
Content.IntegrationTests/Tests/Stacks/StackTestPrototypes.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
namespace Content.IntegrationTests.Tests.Stacks;
|
||||
|
||||
public static class StackTestPrototypes
|
||||
{
|
||||
public const string StackPrototype = "TestStack";
|
||||
public const string StackEntEdible = "StackEntEdible";
|
||||
|
||||
public const string StackEnt1 = "StackEnt1";
|
||||
private const string StackCount1 = "1";
|
||||
public const string StackEnt2 = "StackEnt2";
|
||||
private const string StackCount2 = "2";
|
||||
public const string StackEnt30 = "StackEnt30";
|
||||
private const string StackCount30 = "30"; // Also the maximum size of the test stack
|
||||
|
||||
[TestPrototypes]
|
||||
public const string Prototypes =
|
||||
@$"
|
||||
- type: stack
|
||||
id: {StackPrototype}
|
||||
name: stack-steel
|
||||
spawn: {StackEnt1}
|
||||
maxCount: {StackCount30}
|
||||
|
||||
- type: entity
|
||||
id: {StackEnt1}
|
||||
components:
|
||||
- type: Stack
|
||||
stackType: {StackPrototype}
|
||||
count: {StackCount1}
|
||||
- type: Item
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.35
|
||||
layer:
|
||||
- Impassable
|
||||
|
||||
- type: entity
|
||||
parent: {StackEnt1}
|
||||
id: {StackEnt2}
|
||||
components:
|
||||
- type: Stack
|
||||
count: {StackCount2}
|
||||
|
||||
- type: entity
|
||||
parent: {StackEnt1}
|
||||
id: {StackEnt30}
|
||||
components:
|
||||
- type: Stack
|
||||
count: {StackCount30}
|
||||
|
||||
- type: entity
|
||||
parent: {StackEnt1}
|
||||
id: {StackEntEdible}
|
||||
components:
|
||||
- type: Edible
|
||||
- type: Solution
|
||||
id: food
|
||||
solution:
|
||||
reagents:
|
||||
- ReagentId: Nothing
|
||||
Quantity: 5
|
||||
";
|
||||
}
|
||||
153
Content.IntegrationTests/Tests/Stacks/StackTests.cs
Normal file
153
Content.IntegrationTests/Tests/Stacks/StackTests.cs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.IntegrationTests.Fixtures;
|
||||
using Content.IntegrationTests.Fixtures.Attributes;
|
||||
using Content.Server.Stack;
|
||||
using Content.Shared.Stacks;
|
||||
using Robust.Shared.GameObjects;
|
||||
using static Content.IntegrationTests.Tests.Stacks.StackTestPrototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Stacks;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(StackSystem))]
|
||||
public sealed class StackTest : GameTest
|
||||
{
|
||||
[SidedDependency(Side.Server)] private readonly StackSystem _sStackSystem = default!;
|
||||
|
||||
[Test]
|
||||
[Description("Tests for SharedStackSystem.SetCount .")]
|
||||
public async Task SetTest()
|
||||
{
|
||||
var stack = await Spawn(StackEnt1);
|
||||
|
||||
// Raising the count
|
||||
await Server.WaitPost(() => _sStackSystem.SetCount((stack, null), 2));
|
||||
Assert.That(_sStackSystem.GetCount(stack), Is.EqualTo(2));
|
||||
|
||||
// Lowering the count
|
||||
await Server.WaitPost(() =>_sStackSystem.SetCount((stack, null), 1));
|
||||
Assert.That(_sStackSystem.GetCount(stack), Is.EqualTo(1));
|
||||
|
||||
// Setting above the max count clamps to max
|
||||
await Server.WaitPost(() =>_sStackSystem.SetCount((stack, null), 31));
|
||||
Assert.That(_sStackSystem.GetCount(stack), Is.EqualTo(30));
|
||||
|
||||
// Setting to 0 deletes the stack
|
||||
await Server.WaitPost(() =>_sStackSystem.SetCount((stack, null), 0));
|
||||
await Server.WaitRunTicks(1);
|
||||
Assert.That(SEntMan.EntityCount, Is.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Tests that SharedStackSystem.MergeStacks functions as expected with small numbers.")]
|
||||
public async Task MergeTest()
|
||||
{
|
||||
var stacks = new HashSet<EntityUid>();
|
||||
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
stacks =
|
||||
[
|
||||
SSpawn(StackEnt1),
|
||||
SSpawn(StackEnt2),
|
||||
];
|
||||
|
||||
_sStackSystem.MergeStacks(ref stacks);
|
||||
});
|
||||
|
||||
// Wait for the queue deletion of the empty stacks
|
||||
await Server.WaitRunTicks(1);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
// Assert that only one entity was returned
|
||||
// And that it has the correct count
|
||||
Assert.That(stacks, Has.Count.EqualTo(1));
|
||||
Assert.That(_sStackSystem.GetCount(stacks.First()), Is.EqualTo(3));
|
||||
|
||||
// Assert that the other stack was set to zero and deleted
|
||||
Assert.That(SEntMan.EntityCount, Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Tests that SharedStackSystem.MergeStacks functions as expected with large numbers.")]
|
||||
public async Task MergeOverflowTest()
|
||||
{
|
||||
var stacks = new HashSet<EntityUid>();
|
||||
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
stacks =
|
||||
[
|
||||
SSpawn(StackEnt1),
|
||||
SSpawn(StackEnt2),
|
||||
SSpawn(StackEnt30),
|
||||
];
|
||||
|
||||
_sStackSystem.MergeStacks(ref stacks);
|
||||
});
|
||||
|
||||
// Wait for the queue deletion of the empty stacks
|
||||
await Server.WaitRunTicks(1);
|
||||
|
||||
var count = 0;
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
foreach (var stack in stacks)
|
||||
{
|
||||
count += _sStackSystem.GetCount(stack);
|
||||
}
|
||||
});
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
// Assert that both stacks were returned
|
||||
// And that the empty stack was deleted
|
||||
Assert.That(stacks, Has.Count.EqualTo(2));
|
||||
Assert.That(SEntMan.EntityCount, Is.EqualTo(2));
|
||||
|
||||
// Assert we have the same count as what we spawned
|
||||
Assert.That(count, Is.EqualTo(33));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Test for SharedStackSystem.TryMergeToContacts .")]
|
||||
public async Task MergeContactsTest()
|
||||
{
|
||||
var map = await Pair.CreateTestMap();
|
||||
await Server.WaitIdleAsync();
|
||||
|
||||
// Spawn two stacks at the same position so they're contacting
|
||||
var donor = await SpawnAtPosition(StackEnt1, map.GridCoords);
|
||||
var receiver = await SpawnAtPosition(StackEnt1, map.GridCoords);
|
||||
|
||||
_sStackSystem.TryMergeToContacts(donor);
|
||||
|
||||
// Wait for queue deletion
|
||||
await Server.WaitRunTicks(1);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
// Assert that the receiver has the total count
|
||||
// And that the donor was deleted
|
||||
Assert.That(_sStackSystem.GetCount(receiver), Is.EqualTo(2));
|
||||
Assert.That(SEntMan.EntityExists(donor), Is.False);
|
||||
}
|
||||
|
||||
// Now test for when there's more count than the receiver can hold
|
||||
donor = await SpawnAtPosition(StackEnt30, map.GridCoords);
|
||||
|
||||
await Server.WaitPost(() => _sStackSystem.TryMergeToContacts(donor));
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
// Assert that the receiver is at its maximum count
|
||||
// And that the donor has the remainder of the spawned count
|
||||
Assert.That(_sStackSystem.GetCount(receiver), Is.EqualTo(30));
|
||||
Assert.That(_sStackSystem.GetCount(donor), Is.EqualTo(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -169,17 +169,15 @@ public static partial class GameDataScrounger
|
|||
// Take a directory off the stack.
|
||||
var dir = explorationStack.Pop();
|
||||
|
||||
if (ignoreList.Contains(dir))
|
||||
continue; // It's all abstract anyway.
|
||||
var ignoredDir = ignoreList.Contains(Path.GetFullPath(dir));
|
||||
|
||||
explorationStack.AddRange(Directory.EnumerateDirectories(dir));
|
||||
|
||||
foreach (var file in Directory.EnumerateFiles(dir, "*.yml"))
|
||||
{
|
||||
if (ignoreList.Contains(file))
|
||||
continue; // It's all abstract anyway.
|
||||
var ignored = ignoredDir || ignoreList.Contains(Path.GetFullPath(file));
|
||||
|
||||
foreach (var (kind, id) in IndexPrototypesIn(file))
|
||||
foreach (var (kind, id) in IndexPrototypesIn(file, ignored))
|
||||
{
|
||||
// alternate universe where .net has rust's Entry api.
|
||||
if (!_prototypeIndex.TryGetValue(kind, out var list))
|
||||
|
|
@ -204,8 +202,9 @@ public static partial class GameDataScrounger
|
|||
/// yielding all (type, id) pairs.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to index.</param>
|
||||
/// <param name="ignored">Whether or not the file is ignored. This treats the entire file as abstract.</param>
|
||||
/// <returns>An enumerator of all prototypes in the file, regardless of kind.</returns>
|
||||
private static IEnumerable<(string type, string id)> IndexPrototypesIn(string file)
|
||||
private static IEnumerable<(string type, string id)> IndexPrototypesIn(string file, bool ignored = false)
|
||||
{
|
||||
var stream = new YamlStream();
|
||||
|
||||
|
|
@ -223,7 +222,7 @@ public static partial class GameDataScrounger
|
|||
|
||||
var id = entryMapping[IdNode];
|
||||
var type = entryMapping[TypeNode];
|
||||
var @abstract = false;
|
||||
var @abstract = ignored;
|
||||
if (entryMapping.TryGetNode("abstract", out YamlScalarNode? abstractNode))
|
||||
{
|
||||
// TODO: This technically will exclude prototypes that use the abstract field for their own stuff,
|
||||
|
|
@ -359,7 +358,7 @@ public static partial class GameDataScrounger
|
|||
if (entry is not YamlScalarNode { Value: {} value })
|
||||
throw new Exception($"An entry in {path} is not a valid YAML scalar/string literal. Entry: {entry}");
|
||||
|
||||
ignores.Add(value);
|
||||
ignores.Add(Path.GetFullPath($"{resDir}{value}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
namespace Content.Server.Access.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Allows an ID card to copy accesses from other IDs and to change the name, job title and job icon via an interface.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class AgentIDCardComponent : Component { }
|
||||
|
|
@ -1,173 +1,59 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Access.Components;
|
||||
using Content.Server.Clothing.Systems;
|
||||
using Content.Server.Implants;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Lock;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.VoiceMask;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Access.Systems
|
||||
namespace Content.Server.Access.Systems;
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed partial class AgentIdCardSystem : SharedAgentIdCardSystem
|
||||
{
|
||||
public sealed partial class AgentIDCardSystem : SharedAgentIdCardSystem
|
||||
[Dependency] private SharedIdCardSystem _card = default!;
|
||||
[Dependency] private ChameleonClothingSystem _chameleon = default!;
|
||||
[Dependency] private ChameleonControllerSystem _chamController = default!;
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnChameleonControllerOutfitChangedItem(Entity<AgentIDCardComponent> ent, ref InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent> args)
|
||||
{
|
||||
[Dependency] private PopupSystem _popupSystem = default!;
|
||||
[Dependency] private IdCardSystem _cardSystem = default!;
|
||||
[Dependency] private UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private ChameleonClothingSystem _chameleon = default!;
|
||||
[Dependency] private ChameleonControllerSystem _chamController = default!;
|
||||
[Dependency] private LockSystem _lock = default!;
|
||||
[Dependency] private SharedJobStatusSystem _jobStatus = default!;
|
||||
if (!TryComp<IdCardComponent>(ent, out var idCardComp))
|
||||
return;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<AgentIDCardComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
// BUI
|
||||
SubscribeLocalEvent<AgentIDCardComponent, AfterActivatableUIOpenEvent>(AfterUIOpen);
|
||||
SubscribeLocalEvent<AgentIDCardComponent, AgentIDCardNameChangedMessage>(OnNameChanged);
|
||||
SubscribeLocalEvent<AgentIDCardComponent, AgentIDCardJobChangedMessage>(OnJobChanged);
|
||||
SubscribeLocalEvent<AgentIDCardComponent, AgentIDCardJobIconChangedMessage>(OnJobIconChanged);
|
||||
SubscribeLocalEvent<AgentIDCardComponent, InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent>>(OnChameleonControllerOutfitChangedItem);
|
||||
SubscribeLocalEvent<AgentIDCardComponent, InventoryRelayedEvent<VoiceMaskNameUpdatedEvent>>(OnVoiceMaskNameChanged);
|
||||
}
|
||||
ProtoMan.Resolve(args.Args.ChameleonOutfit.Job, out var jobProto);
|
||||
|
||||
private void OnChameleonControllerOutfitChangedItem(Entity<AgentIDCardComponent> ent, ref InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent> args)
|
||||
{
|
||||
if (!TryComp<IdCardComponent>(ent, out var idCardComp))
|
||||
return;
|
||||
var jobIcon = args.Args.ChameleonOutfit.Icon ?? jobProto?.Icon;
|
||||
var jobName = args.Args.ChameleonOutfit.Name ?? jobProto?.Name ?? "";
|
||||
|
||||
ProtoMan.Resolve(args.Args.ChameleonOutfit.Job, out var jobProto);
|
||||
if (jobIcon != null)
|
||||
_card.TryChangeJobIcon(ent, ProtoMan.Index(jobIcon.Value), idCardComp);
|
||||
|
||||
var jobIcon = args.Args.ChameleonOutfit.Icon ?? jobProto?.Icon;
|
||||
var jobName = args.Args.ChameleonOutfit.Name ?? jobProto?.Name ?? "";
|
||||
if (jobName != "")
|
||||
_card.TryChangeJobTitle(ent, Loc.GetString(jobName), idCardComp);
|
||||
|
||||
if (jobIcon != null)
|
||||
_cardSystem.TryChangeJobIcon(ent, ProtoMan.Index(jobIcon.Value), idCardComp);
|
||||
// If you have forced departments use those over the jobs actual departments.
|
||||
if (args.Args.ChameleonOutfit.Departments?.Count > 0)
|
||||
_card.TryChangeJobDepartment(ent, args.Args.ChameleonOutfit.Departments, idCardComp);
|
||||
else if (jobProto != null)
|
||||
_card.TryChangeJobDepartment(ent, jobProto, idCardComp);
|
||||
|
||||
if (jobName != "")
|
||||
_cardSystem.TryChangeJobTitle(ent, Loc.GetString(jobName), idCardComp);
|
||||
// Ensure that you chameleon IDs in PDAs correctly. Yes this is sus...
|
||||
|
||||
// If you have forced departments use those over the jobs actual departments.
|
||||
if (args.Args.ChameleonOutfit?.Departments?.Count > 0)
|
||||
_cardSystem.TryChangeJobDepartment(ent, args.Args.ChameleonOutfit.Departments, idCardComp);
|
||||
else if (jobProto != null)
|
||||
_cardSystem.TryChangeJobDepartment(ent, jobProto, idCardComp);
|
||||
// There is one weird interaction: If the job / icon don't match the PDAs job the chameleon will be updated
|
||||
// to the PDAs IDs sprite but the icon and job title will not match. There isn't a way to get around this
|
||||
// really as there is no tie between job -> pda or pda -> job.
|
||||
|
||||
// Ensure that you chameleon IDs in PDAs correctly. Yes this is sus...
|
||||
var idSlotGear = _chamController.GetGearForSlot(args, "id");
|
||||
if (idSlotGear == null)
|
||||
return;
|
||||
|
||||
// There is one weird interaction: If the job / icon don't match the PDAs job the chameleon will be updated
|
||||
// to the PDAs IDs sprite but the icon and job title will not match. There isn't a way to get around this
|
||||
// really as there is no tie between job -> pda or pda -> job.
|
||||
var proto = ProtoMan.Index(idSlotGear);
|
||||
if (!proto.TryComp<PdaComponent>(out var comp, EntityManager.ComponentFactory))
|
||||
return;
|
||||
|
||||
var idSlotGear = _chamController.GetGearForSlot(args, "id");
|
||||
if (idSlotGear == null)
|
||||
return;
|
||||
|
||||
var proto = ProtoMan.Index(idSlotGear);
|
||||
if (!proto.TryComp<PdaComponent>(out var comp, EntityManager.ComponentFactory))
|
||||
return;
|
||||
|
||||
if (TryComp<ChameleonClothingComponent>(ent, out var chameleonComp) && chameleonComp.CanBeSetByController)
|
||||
_chameleon.SetSelectedPrototype(ent, comp.IdCard, component: chameleonComp);
|
||||
}
|
||||
|
||||
private void OnVoiceMaskNameChanged(Entity<AgentIDCardComponent> ent, ref InventoryRelayedEvent<VoiceMaskNameUpdatedEvent> args)
|
||||
{
|
||||
if (!TryComp<IdCardComponent>(ent, out var idCard))
|
||||
return;
|
||||
|
||||
if (!args.Args.VoiceMask.Comp.ChangeIDName)
|
||||
return;
|
||||
|
||||
_cardSystem.TryChangeFullName(ent, args.Args.NewName, idCard);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, AgentIDCardComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Target == null || !args.CanReach || _lock.IsLocked(uid) ||
|
||||
!TryComp<AccessComponent>(args.Target, out var targetAccess) || !HasComp<IdCardComponent>(args.Target))
|
||||
return;
|
||||
|
||||
if (!TryComp<AccessComponent>(uid, out var access) || !HasComp<IdCardComponent>(uid))
|
||||
return;
|
||||
|
||||
var beforeLength = access.Tags.Count;
|
||||
access.Tags.UnionWith(targetAccess.Tags);
|
||||
var addedLength = access.Tags.Count - beforeLength;
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("agent-id-new", ("number", addedLength), ("card", args.Target)), args.Target.Value, args.User);
|
||||
if (addedLength > 0)
|
||||
Dirty(uid, access);
|
||||
}
|
||||
|
||||
private void AfterUIOpen(EntityUid uid, AgentIDCardComponent component, AfterActivatableUIOpenEvent args)
|
||||
{
|
||||
if (!_uiSystem.HasUi(uid, AgentIDCardUiKey.Key))
|
||||
return;
|
||||
|
||||
if (!TryComp<IdCardComponent>(uid, out var idCard))
|
||||
return;
|
||||
|
||||
var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.LocalizedJobTitle ?? "", idCard.JobIcon);
|
||||
_uiSystem.SetUiState(uid, AgentIDCardUiKey.Key, state);
|
||||
}
|
||||
|
||||
private void OnJobChanged(EntityUid uid, AgentIDCardComponent comp, AgentIDCardJobChangedMessage args)
|
||||
{
|
||||
if (!TryComp<IdCardComponent>(uid, out var idCard))
|
||||
return;
|
||||
|
||||
_cardSystem.TryChangeJobTitle(uid, args.Job, idCard);
|
||||
}
|
||||
|
||||
private void OnNameChanged(EntityUid uid, AgentIDCardComponent comp, AgentIDCardNameChangedMessage args)
|
||||
{
|
||||
if (!TryComp<IdCardComponent>(uid, out var idCard))
|
||||
return;
|
||||
|
||||
_cardSystem.TryChangeFullName(uid, args.Name, idCard);
|
||||
}
|
||||
|
||||
private void OnJobIconChanged(EntityUid uid, AgentIDCardComponent comp, AgentIDCardJobIconChangedMessage args)
|
||||
{
|
||||
if (!TryComp<IdCardComponent>(uid, out var idCard))
|
||||
return;
|
||||
|
||||
if (!ProtoMan.Resolve(args.JobIconId, out var jobIcon))
|
||||
return;
|
||||
|
||||
_cardSystem.TryChangeJobIcon(uid, jobIcon, idCard);
|
||||
|
||||
if (TryFindJobProtoFromIcon(jobIcon, out var job))
|
||||
_cardSystem.TryChangeJobDepartment(uid, job, idCard);
|
||||
|
||||
_jobStatus.UpdateStatus(Transform(uid).ParentUid);
|
||||
}
|
||||
|
||||
private bool TryFindJobProtoFromIcon(JobIconPrototype jobIcon, [NotNullWhen(true)] out JobPrototype? job)
|
||||
{
|
||||
foreach (var jobPrototype in ProtoMan.EnumeratePrototypes<JobPrototype>())
|
||||
{
|
||||
if (jobPrototype.Icon == jobIcon.ID)
|
||||
{
|
||||
job = jobPrototype;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
job = null;
|
||||
return false;
|
||||
}
|
||||
if (TryComp<ChameleonClothingComponent>(ent, out var chameleonComp) && chameleonComp.CanBeSetByController)
|
||||
_chameleon.SetSelectedPrototype(ent, comp.IdCard, component: chameleonComp);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Linq;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Containers;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
|
|
@ -15,6 +14,8 @@ using Content.Shared.Damage.Systems;
|
|||
using Content.Shared.Database;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Shared.StationRecords.Components;
|
||||
using Content.Shared.StationRecords.Systems;
|
||||
using Content.Shared.Throwing;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
|
|
@ -230,7 +231,7 @@ public sealed partial class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
|||
return _accessReader.IsAllowed(id.Value, uid, reader);
|
||||
}
|
||||
|
||||
private void UpdateStationRecord(EntityUid uid, EntityUid targetId, string newFullName, ProtoId<AccessLevelPrototype> newJobTitle, JobPrototype? newJobProto)
|
||||
private void UpdateStationRecord(EntityUid uid, EntityUid targetId, string newFullName, string newJobTitle, JobPrototype? newJobProto)
|
||||
{
|
||||
if (!TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
|
||||
|| keyStorage.Key is not { } key
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Discord.WebhookMessages;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.AdminEventLog;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Server.Administration.AdminEventLog;
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to discord after a admin logs a event
|
||||
/// </summary>
|
||||
public sealed partial class AdminEventLogEui : BaseEui
|
||||
{
|
||||
[Dependency] private IAdminManager _adminManager = default!;
|
||||
[Dependency] private IConfigurationManager _config = default!;
|
||||
[Dependency] private EventWebhook _eventWebhook = default!;
|
||||
[Dependency] private IEntityManager _e = default!;
|
||||
|
||||
private int CurrentRoundId => _e.System<GameTicker>().RoundId;
|
||||
|
||||
public AdminEventLogEui()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public override async void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
_adminManager.OnPermsChanged += OnPermsChanged;
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
|
||||
_adminManager.OnPermsChanged -= OnPermsChanged;
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState()
|
||||
{
|
||||
var state = new AdminEventLogEuiState(CurrentRoundId);
|
||||
return state;
|
||||
}
|
||||
|
||||
public override async void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
var message = (AdminEventLogEuiMsg)msg;
|
||||
|
||||
_eventWebhook.TrySendMessage(
|
||||
message.AdminUser,
|
||||
message.RoundId,
|
||||
message.EventDescription,
|
||||
_config.GetCVar(CCVars.DiscordEventWebhook));
|
||||
}
|
||||
|
||||
private void OnPermsChanged(AdminPermsChangedEventArgs args)
|
||||
{
|
||||
if (args.Player == Player && !_adminManager.HasAdminFlag(Player, AdminFlags.Admin))
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using Content.Server.Administration.AdminEventLog;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed partial class OpenAdminEventLogCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private EuiManager _euiManager = default!;
|
||||
|
||||
public override string Command => "eventlog";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server"));
|
||||
return;
|
||||
}
|
||||
|
||||
var ui = new AdminEventLogEui();
|
||||
_euiManager.OpenEui(ui, player);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.EUI;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
|
@ -8,20 +9,49 @@ namespace Content.Server.Administration.Commands;
|
|||
[AdminCommand(AdminFlags.Logs)]
|
||||
public sealed partial class OpenAdminLogsCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private EuiManager _euiManager = default!;
|
||||
[Dependency] private IAdminLogManager _adminLogManager = default!;
|
||||
[Dependency] private IPlayerLocator _locator = default!;
|
||||
[Dependency] private IPlayerManager _players = default!;
|
||||
|
||||
public override string Command => Cmd;
|
||||
public const string Cmd = "adminlogs";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not { } player)
|
||||
if (shell.Player is not { } admin)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server"));
|
||||
return;
|
||||
}
|
||||
|
||||
var ui = new AdminLogsEui();
|
||||
_euiManager.OpenEui(ui, player);
|
||||
Guid? player = null;
|
||||
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1 when Guid.TryParse(args[0], out var playerGuid):
|
||||
player = playerGuid;
|
||||
break;
|
||||
case 1:
|
||||
var dbGuid = await _locator.LookupIdByNameAsync(args[0]);
|
||||
|
||||
if (dbGuid == null)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-admin-logs-wrong-target", ("user", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
player = dbGuid.UserId;
|
||||
break;
|
||||
}
|
||||
_adminLogManager.OpenEui(admin, targetPlayer: player);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
return CompletionResult.Empty;
|
||||
|
||||
var options = _players.Sessions.Select(c => c.Name).OrderBy(c => c).ToArray();
|
||||
return CompletionResult.FromHintOptions(options, Loc.GetString("cmd-admin-logs-hint"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ public sealed partial class OwoifyCommand : IConsoleCommand
|
|||
|
||||
var owoSys = _entManager.System<OwOAccentSystem>();
|
||||
var metaDataSys = _entManager.System<MetaDataSystem>();
|
||||
|
||||
metaDataSys.SetEntityName(eUid.Value, owoSys.Accentuate(meta.EntityName), meta);
|
||||
metaDataSys.SetEntityDescription(eUid.Value, owoSys.Accentuate(meta.EntityDescription), meta);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ namespace Content.Server.Administration.Commands
|
|||
if (string.IsNullOrEmpty(colorArg))
|
||||
return;
|
||||
|
||||
var color = Color.TryFromHex(colorArg);
|
||||
if (!color.HasValue)
|
||||
if (!Color.TryFromHex(colorArg, out var color))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-invalid-color-hex"));
|
||||
return;
|
||||
|
|
@ -37,10 +36,10 @@ namespace Content.Server.Administration.Commands
|
|||
|
||||
var userId = shell.Player.UserId;
|
||||
// Save the DB
|
||||
_dbManager.SaveAdminOOCColorAsync(userId, color.Value);
|
||||
_dbManager.SaveAdminOOCColorAsync(userId, color);
|
||||
// Update the cached preference
|
||||
var prefs = _preferenceManager.GetPreferences(userId);
|
||||
prefs.AdminOOCColor = color.Value;
|
||||
prefs.AdminOOCColor = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.CCVar;
|
||||
|
|
@ -29,6 +30,7 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
|
|||
{
|
||||
[Dependency] private IConfigurationManager _configuration = default!;
|
||||
[Dependency] private ILogManager _logManager = default!;
|
||||
[Dependency] private EuiManager _euis = default!;
|
||||
[Dependency] private IServerDbManager _db = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private IDynamicTypeFactory _typeFactory = default!;
|
||||
|
|
@ -633,4 +635,16 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
|
|||
{
|
||||
return _db.CountAdminLogs(round);
|
||||
}
|
||||
|
||||
public void OpenEui(ICommonSession admin, string? search = null, Guid? targetPlayer = null)
|
||||
{
|
||||
var ui = new AdminLogsEui();
|
||||
_euis.OpenEui(ui, admin);
|
||||
|
||||
List<Guid>? userList = null;
|
||||
if (targetPlayer is not null)
|
||||
userList = [targetPlayer.Value];
|
||||
|
||||
ui.SetLogFilter(search, userList);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,10 +142,11 @@ public sealed partial class AdminLogsEui : BaseEui
|
|||
}
|
||||
}
|
||||
|
||||
public void SetLogFilter(string? search = null, bool invertTypes = false, HashSet<LogType>? types = null)
|
||||
public void SetLogFilter(string? search = null, List<Guid>? players = null, bool invertTypes = false, HashSet<LogType>? types = null)
|
||||
{
|
||||
var message = new SetLogFilter(
|
||||
search,
|
||||
players,
|
||||
invertTypes,
|
||||
types);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ using System.Threading.Tasks;
|
|||
using Content.Server.Database;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Administration.Logs;
|
||||
|
||||
|
|
@ -24,4 +26,5 @@ public interface IAdminLogManager : ISharedAdminLogManager
|
|||
IAsyncEnumerable<JsonDocument> CurrentRoundJson(LogFilter? filter = null);
|
||||
Task<Round> CurrentRound();
|
||||
Task<int> CountLogs(int round);
|
||||
void OpenEui(ICommonSession admin, string? search = null, Guid? targetPlayer = null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
|
|
@ -17,6 +18,7 @@ namespace Content.Server.Administration.Systems;
|
|||
public sealed partial class AdminQuickInfoSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private IAdminManager _adminManager = null!;
|
||||
[Dependency] private MindSystem _mind = null!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -41,9 +43,9 @@ public sealed partial class AdminQuickInfoSystem : EntitySystem
|
|||
NetUserId? lastPlayer = null;
|
||||
|
||||
// Check the last mind that was attached to the entity and get its userid.
|
||||
if (TryComp<MindContainerComponent>(ent, out var comp) && TryComp<MindComponent>(comp.LastMind, out var mindComp))
|
||||
if (_mind.TryGetLastMind(ent.Value, out var lastMind))
|
||||
{
|
||||
lastPlayer = mindComp.UserId;
|
||||
lastPlayer = lastMind.Value.Comp.UserId;
|
||||
}
|
||||
var metadata = MetaData(ent.Value);
|
||||
return new QuickInfoShared.SingleEntityInfo(e, true, metadata.EntityName, metadata.EntityPrototype?.ID, lastPlayer);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using Content.Server.Hands.Systems;
|
|||
using Content.Server.Mind;
|
||||
using Content.Server.Players.PlayTimeTracking;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Events;
|
||||
using Content.Shared.CCVar;
|
||||
|
|
@ -23,6 +22,8 @@ using Content.Shared.Popups;
|
|||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Shared.StationRecords.Components;
|
||||
using Content.Shared.StationRecords.Systems;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ using Robust.Shared.Toolshed;
|
|||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Mind;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
|
||||
namespace Content.Server.Administration.Systems
|
||||
|
|
@ -47,6 +48,7 @@ namespace Content.Server.Administration.Systems
|
|||
{
|
||||
[Dependency] private IConGroupController _groupController = default!;
|
||||
[Dependency] private IConsoleHost _console = default!;
|
||||
[Dependency] private IAdminLogManager _adminLogs = default!;
|
||||
[Dependency] private IAdminManager _adminManager = default!;
|
||||
[Dependency] private IGameTiming _gameTiming = default!;
|
||||
[Dependency] private SharedMapSystem _map = default!;
|
||||
|
|
@ -243,6 +245,22 @@ namespace Content.Server.Administration.Systems
|
|||
});
|
||||
}
|
||||
|
||||
// Player Admin Logs. this is distinct from entity logs above as it will get the logs of the _player_
|
||||
// that last owned the mind of the entity you are right-clicking on. words.
|
||||
if (_mindSystem.TryGetLastMindOwner(args.Target, out var lastOwner))
|
||||
{
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Priority = -3,
|
||||
Text = Loc.GetString("admin-verbs-admin-logs-player"),
|
||||
Category = VerbCategory.Admin,
|
||||
Act = () =>
|
||||
{
|
||||
_adminLogs.OpenEui(player, targetPlayer: lastOwner);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Freeze
|
||||
var frozen = TryComp<AdminFrozenComponent>(args.Target, out var frozenComp);
|
||||
var frozenAndMuted = frozenComp?.Muted ?? false;
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ namespace Content.Server.Arcade.SpaceVillain;
|
|||
public sealed partial class SpaceVillainArcadeComponent : SharedSpaceVillainArcadeComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Unused flag that can be hacked via wires.
|
||||
/// Name suggests that it was intended to either make the health/mana values underflow while playing the game or turn the arcade machine into an infinite prize fountain.
|
||||
/// Flag that can be hacked via wires.
|
||||
/// If true, makes the game skip checking game end conditions, and sets Uncapped to both fighters when a new game is started.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool OverflowFlag;
|
||||
public bool UncappedFlag;
|
||||
|
||||
/// <summary>
|
||||
/// The current session of the SpaceVillain game for this arcade machine.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,12 @@ public sealed partial class SpaceVillainGame
|
|||
public int Hp
|
||||
{
|
||||
get => _hp;
|
||||
set => _hp = MathHelper.Clamp(value, 0, HpMax);
|
||||
set
|
||||
{
|
||||
_hp = value;
|
||||
if (!Uncapped)
|
||||
_hp = MathHelper.Clamp(_hp, 0, HpMax);
|
||||
}
|
||||
}
|
||||
private int _hp;
|
||||
|
||||
|
|
@ -28,7 +33,7 @@ public sealed partial class SpaceVillainGame
|
|||
set
|
||||
{
|
||||
_hpMax = Math.Max(value, 0);
|
||||
Hp = MathHelper.Clamp(Hp, 0, HpMax);
|
||||
Hp = _hp; // Re-clamp the HP value
|
||||
}
|
||||
}
|
||||
private int _hpMax;
|
||||
|
|
@ -40,7 +45,12 @@ public sealed partial class SpaceVillainGame
|
|||
public int Mp
|
||||
{
|
||||
get => _mp;
|
||||
set => _mp = MathHelper.Clamp(value, 0, MpMax);
|
||||
set
|
||||
{
|
||||
_mp = value;
|
||||
if (!Uncapped)
|
||||
_mp = MathHelper.Clamp(_mp, 0, MpMax);
|
||||
}
|
||||
}
|
||||
private int _mp;
|
||||
|
||||
|
|
@ -54,7 +64,7 @@ public sealed partial class SpaceVillainGame
|
|||
set
|
||||
{
|
||||
_mpMax = Math.Max(value, 0);
|
||||
Mp = MathHelper.Clamp(Mp, 0, MpMax);
|
||||
Mp = _mp; // Re-clamp the MP value
|
||||
}
|
||||
}
|
||||
private int _mpMax;
|
||||
|
|
@ -64,5 +74,11 @@ public sealed partial class SpaceVillainGame
|
|||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Invincible = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given fighter's HP and MP values are capped between 0 and their respective Max values.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Uncapped = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,8 @@ public sealed partial class SpaceVillainGame
|
|||
HpMax = 30,
|
||||
Hp = 30,
|
||||
MpMax = 10,
|
||||
Mp = 10
|
||||
Mp = 10,
|
||||
Uncapped = arcade.UncappedFlag,
|
||||
};
|
||||
|
||||
VillainChar = new()
|
||||
|
|
@ -79,7 +80,8 @@ public sealed partial class SpaceVillainGame
|
|||
HpMax = 45,
|
||||
Hp = 45,
|
||||
MpMax = 20,
|
||||
Mp = 20
|
||||
Mp = 20,
|
||||
Uncapped = arcade.UncappedFlag,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -210,6 +212,9 @@ public sealed partial class SpaceVillainGame
|
|||
/// <returns>A bool indicating if the game should continue.</returns>
|
||||
private bool CheckGameConditions(EntityUid uid, SpaceVillainArcadeComponent arcade)
|
||||
{
|
||||
if (arcade.UncappedFlag)
|
||||
return true;
|
||||
|
||||
switch (
|
||||
PlayerChar.Hp > 0 && PlayerChar.Mp > 0,
|
||||
VillainChar.Hp > 0 && VillainChar.Mp > 0
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Content.Server.Arcade;
|
|||
|
||||
public sealed partial class ArcadeOverflowWireAction : BaseToggleWireAction
|
||||
{
|
||||
public override Color Color { get; set; } = Color.Red;
|
||||
public override Color Color { get; set; } = Color.OrangeRed;
|
||||
public override string Name { get; set; } = "wire-name-arcade-overflow";
|
||||
|
||||
public override object? StatusKey { get; } = SharedSpaceVillainArcadeComponent.Indicators.HealthLimiter;
|
||||
|
|
@ -16,14 +16,19 @@ public sealed partial class ArcadeOverflowWireAction : BaseToggleWireAction
|
|||
{
|
||||
if (EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(owner, out var arcade))
|
||||
{
|
||||
arcade.OverflowFlag = !setting;
|
||||
arcade.UncappedFlag = !setting;
|
||||
if (arcade.Game != null)
|
||||
{
|
||||
arcade.Game.PlayerChar.Uncapped = !setting;
|
||||
arcade.Game.VillainChar.Uncapped = !setting;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool GetValue(EntityUid owner)
|
||||
{
|
||||
return EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(owner, out var arcade)
|
||||
&& !arcade.OverflowFlag;
|
||||
&& !arcade.UncappedFlag;
|
||||
}
|
||||
|
||||
public override StatusLightState? GetLightState(Wire wire)
|
||||
|
|
@ -31,7 +36,7 @@ public sealed partial class ArcadeOverflowWireAction : BaseToggleWireAction
|
|||
if (EntityManager.HasComponent<SpaceVillainArcadeComponent>(wire.Owner))
|
||||
{
|
||||
return !GetValue(wire.Owner)
|
||||
? StatusLightState.BlinkingSlow
|
||||
? StatusLightState.Off
|
||||
: StatusLightState.On;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,8 +91,7 @@ public sealed partial class GasCanisterSystem : SharedGasCanisterSystem
|
|||
{
|
||||
_atmos.React(entity.Comp.Air, entity.Comp);
|
||||
|
||||
if (!TryComp<NodeContainerComponent>(entity, out var nodeContainer)
|
||||
|| !TryComp<AppearanceComponent>(entity, out var appearance))
|
||||
if (!TryComp<NodeContainerComponent>(entity, out var nodeContainer))
|
||||
return;
|
||||
|
||||
if (!_nodeContainer.TryGetNode(nodeContainer, entity.Comp.PortName, out PortablePipeNode? portNode))
|
||||
|
|
@ -125,26 +124,10 @@ public sealed partial class GasCanisterSystem : SharedGasCanisterSystem
|
|||
if (MathHelper.CloseToPercent(entity.Comp.Air.Pressure, entity.Comp.LastPressure))
|
||||
return;
|
||||
|
||||
DirtyUI(entity, entity.Comp, nodeContainer);
|
||||
|
||||
entity.Comp.LastPressure = entity.Comp.Air.Pressure;
|
||||
|
||||
if (entity.Comp.Air.Pressure < 10)
|
||||
{
|
||||
_appearance.SetData(entity, GasCanisterVisuals.PressureState, 0, appearance);
|
||||
}
|
||||
else if (entity.Comp.Air.Pressure < Atmospherics.OneAtmosphere)
|
||||
{
|
||||
_appearance.SetData(entity, GasCanisterVisuals.PressureState, 1, appearance);
|
||||
}
|
||||
else if (entity.Comp.Air.Pressure < (15 * Atmospherics.OneAtmosphere))
|
||||
{
|
||||
_appearance.SetData(entity, GasCanisterVisuals.PressureState, 2, appearance);
|
||||
}
|
||||
else
|
||||
{
|
||||
_appearance.SetData(entity, GasCanisterVisuals.PressureState, 3, appearance);
|
||||
}
|
||||
DirtyUI(entity, entity.Comp, nodeContainer);
|
||||
UpdateAppearance(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Content.Shared.Physics;
|
|||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
|
|
@ -19,7 +18,6 @@ public sealed partial class BeamSystem : SharedBeamSystem
|
|||
[Dependency] private TransformSystem _transform = default!;
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private SharedBroadphaseSystem _broadphase = default!;
|
||||
[Dependency] private SharedPhysicsSystem _physics = default!;
|
||||
|
||||
private static readonly EntProtoId VirtualBeamEntityControllerId = "VirtualBeamEntityController";
|
||||
|
||||
|
|
@ -81,23 +79,22 @@ public sealed partial class BeamSystem : SharedBeamSystem
|
|||
var ent = Spawn(prototype, beamSpawnPos, rotation: userAngle);
|
||||
var shape = new EdgeShape(distanceCorrection, new Vector2(0, 0));
|
||||
|
||||
if (!TryComp<PhysicsComponent>(ent, out var physics) || !TryComp<BeamComponent>(ent, out var beam))
|
||||
if (!TryComp<BeamComponent>(ent, out var beam))
|
||||
return;
|
||||
|
||||
FixturesComponent? manager = null;
|
||||
_fixture.TryCreateFixture(
|
||||
ent,
|
||||
shape,
|
||||
"BeamBody",
|
||||
hard: false,
|
||||
collisionMask: (int)CollisionGroup.ItemMask,
|
||||
collisionLayer: (int)CollisionGroup.MobLayer,
|
||||
manager: manager,
|
||||
body: physics);
|
||||
if (TryComp<PhysicsComponent>(ent, out var physics) && physics.CanCollide)
|
||||
{
|
||||
_fixture.TryCreateFixture(
|
||||
ent,
|
||||
shape,
|
||||
BeamComponent.FixtureID,
|
||||
hard: false,
|
||||
collisionMask: (int)CollisionGroup.ItemMask,
|
||||
collisionLayer: (int)CollisionGroup.MobLayer,
|
||||
body: physics);
|
||||
|
||||
_physics.SetBodyType(ent, BodyType.Dynamic, manager: manager, body: physics);
|
||||
_physics.SetCanCollide(ent, true, manager: manager, body: physics);
|
||||
_broadphase.RegenerateContacts((ent, physics, manager));
|
||||
_broadphase.RegenerateContacts((ent, physics));
|
||||
}
|
||||
|
||||
var distanceLength = distanceCorrection.Length();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Bed.Cryostorage;
|
||||
using Content.Shared.Chat;
|
||||
|
|
@ -18,15 +13,19 @@ using Content.Shared.Hands.Components;
|
|||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Shared.UserInterface;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using System.Globalization;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Station;
|
||||
using Content.Shared.StationRecords.Components;
|
||||
using Content.Shared.StationRecords.Systems;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server.Bed.Cryostorage;
|
||||
|
||||
|
|
@ -35,20 +34,20 @@ public sealed partial class CryostorageSystem : SharedCryostorageSystem
|
|||
{
|
||||
[Dependency] private IChatManager _chatManager = default!;
|
||||
[Dependency] private IPlayerManager _playerManager = default!;
|
||||
[Dependency] private AudioSystem _audio = default!;
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private ChatSystem _chatSystem = default!;
|
||||
[Dependency] private SharedChatSystem _chatSystem = default!;
|
||||
[Dependency] private ClimbSystem _climb = default!;
|
||||
[Dependency] private ContainerSystem _container = default!;
|
||||
[Dependency] private SharedContainerSystem _container = default!;
|
||||
[Dependency] private GhostSystem _ghostSystem = default!;
|
||||
[Dependency] private HandsSystem _hands = default!;
|
||||
[Dependency] private ServerInventorySystem _inventory = default!;
|
||||
[Dependency] private PopupSystem _popup = default!;
|
||||
[Dependency] private StationSystem _station = default!;
|
||||
[Dependency] private SharedHandsSystem _hands = default!;
|
||||
[Dependency] private InventorySystem _inventory = default!;
|
||||
[Dependency] private SharedPopupSystem _popup = default!;
|
||||
[Dependency] private SharedStationSystem _station = default!;
|
||||
[Dependency] private StationJobsSystem _stationJobs = default!;
|
||||
[Dependency] private StationRecordsSystem _stationRecords = default!;
|
||||
[Dependency] private TransformSystem _transform = default!;
|
||||
[Dependency] private UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private SharedTransformSystem _transform = default!;
|
||||
[Dependency] private SharedUserInterfaceSystem _ui = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
|
|
@ -317,7 +316,7 @@ public sealed partial class CryostorageSystem : SharedCryostorageSystem
|
|||
if (HasComp<AttachedClothingComponent>(item))
|
||||
continue;
|
||||
|
||||
data.ItemSlots.Add(slotDef.Name, Name(item));
|
||||
data.ItemSlots.Add((slotDef.Name, slotDef.DisplayName, Name(item)));
|
||||
}
|
||||
|
||||
foreach (var hand in _hands.EnumerateHands(uid))
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ public sealed partial class CloningSystem : SharedCloningSystem
|
|||
|
||||
// If the original does not have the component, then the clone shouldn't have it either.
|
||||
RemComp(clone, componentRegistration.Type);
|
||||
if (EntityManager.TryGetComponent(original, componentRegistration.Type, out var sourceComp)) // Does the original have this component?
|
||||
if (TryComp(original, componentRegistration.Type, out var sourceComp)) // Does the original have this component?
|
||||
{
|
||||
CopyComp(original, clone, sourceComp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Content.Server.Construction
|
|||
if (!ProtoMan.TryIndex(msg.ConstructionId, out ConstructionPrototype? prototype))
|
||||
return;
|
||||
|
||||
if(GetGuide(prototype) is {} guide)
|
||||
if (GetGuide(prototype) is { } guide)
|
||||
RaiseNetworkEvent(new ResponseConstructionGuide(msg.ConstructionId, guide), args.SenderSession.Channel);
|
||||
}
|
||||
|
||||
|
|
@ -46,10 +46,10 @@ namespace Content.Server.Construction
|
|||
if (component.DeconstructionNode == null)
|
||||
return;
|
||||
|
||||
if (GetCurrentNode(uid, component) is not {} currentNode)
|
||||
if (GetCurrentNode(uid, component) is not { } currentNode)
|
||||
return;
|
||||
|
||||
if (graph.Path(currentNode.Name, component.DeconstructionNode) is not {} path || path.Length == 0)
|
||||
if (graph.Path(currentNode.Name, component.DeconstructionNode) is not { } path || path.Length == 0)
|
||||
return;
|
||||
|
||||
Verb verb = new();
|
||||
|
|
@ -57,7 +57,7 @@ namespace Content.Server.Construction
|
|||
//TODO VERBS add more construction verbs? Until then, removing construction category
|
||||
verb.Text = Loc.GetString("deconstructible-verb-begin-deconstruct");
|
||||
verb.Icon = new SpriteSpecifier.Texture(
|
||||
new ("/Textures/Interface/hammer_scaled.svg.192dpi.png"));
|
||||
new("/Textures/Interface/hammer_scaled.svg.192dpi.png"));
|
||||
|
||||
verb.Act = () =>
|
||||
{
|
||||
|
|
@ -80,7 +80,7 @@ namespace Content.Server.Construction
|
|||
{
|
||||
using (args.PushGroup(nameof(ConstructionComponent)))
|
||||
{
|
||||
if (GetTargetNode(uid, component) is {} target)
|
||||
if (GetTargetNode(uid, component) is { } target)
|
||||
{
|
||||
if (target.Name == component.DeconstructionNode)
|
||||
{
|
||||
|
|
@ -88,13 +88,22 @@ namespace Content.Server.Construction
|
|||
}
|
||||
else
|
||||
{
|
||||
args.PushMarkup(Loc.GetString(
|
||||
"construction-component-to-create-header",
|
||||
("targetName", target.Name)) + "\n");
|
||||
// Try to get the name of the prototype on the node, if one exists.
|
||||
var targetProtoId = target.Entity.GetId(uid, args.Examiner, new(EntityManager));
|
||||
if (targetProtoId != null
|
||||
&& ProtoMan.TryIndex(targetProtoId, out var targetPrototype))
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("construction-component-to-create-prototype-header",
|
||||
("targetName", targetPrototype.Name)) + "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("construction-component-to-create-header") + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (component.EdgeIndex == null && GetTargetEdge(uid, component) is {} targetEdge)
|
||||
if (component.EdgeIndex == null && GetTargetEdge(uid, component) is { } targetEdge)
|
||||
{
|
||||
var preventStepExamine = false;
|
||||
|
||||
|
|
@ -108,7 +117,7 @@ namespace Content.Server.Construction
|
|||
return;
|
||||
}
|
||||
|
||||
if (GetCurrentEdge(uid, component) is {} edge)
|
||||
if (GetCurrentEdge(uid, component) is { } edge)
|
||||
{
|
||||
var preventStepExamine = false;
|
||||
|
||||
|
|
@ -148,12 +157,12 @@ namespace Content.Server.Construction
|
|||
return null;
|
||||
|
||||
// If either the start node or the target node are missing, do nothing.
|
||||
if (GetNodeFromGraph(graph, construction.StartNode) is not {} startNode
|
||||
|| GetNodeFromGraph(graph, construction.TargetNode) is not {} targetNode)
|
||||
if (GetNodeFromGraph(graph, construction.StartNode) is not { } startNode
|
||||
|| GetNodeFromGraph(graph, construction.TargetNode) is not { } targetNode)
|
||||
return null;
|
||||
|
||||
// If there's no path from start to target, do nothing.
|
||||
if (graph.Path(construction.StartNode, construction.TargetNode) is not {} path
|
||||
if (graph.Path(construction.StartNode, construction.TargetNode) is not { } path
|
||||
|| path.Length == 0)
|
||||
return null;
|
||||
|
||||
|
|
@ -196,7 +205,7 @@ namespace Content.Server.Construction
|
|||
// Now actually list the construction conditions.
|
||||
foreach (var condition in construction.Conditions)
|
||||
{
|
||||
if (condition.GenerateGuideEntry() is not {} conditionEntry)
|
||||
if (condition.GenerateGuideEntry() is not { } conditionEntry)
|
||||
continue;
|
||||
|
||||
conditionEntry.Padding += 4;
|
||||
|
|
@ -207,7 +216,7 @@ namespace Content.Server.Construction
|
|||
node = path[index++];
|
||||
|
||||
// Add a bit of padding if there will be more steps after this.
|
||||
if(node != targetNode)
|
||||
if (node != targetNode)
|
||||
entries.Add(new ConstructionGuideEntry());
|
||||
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System.Linq;
|
|||
using Content.Server.Administration;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CrewManifest;
|
||||
|
|
@ -10,6 +9,9 @@ using Content.Shared.GameTicking;
|
|||
using Content.Shared.Roles;
|
||||
using Content.Shared.Station.Components;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Shared.StationRecords.Components;
|
||||
using Content.Shared.StationRecords.Events;
|
||||
using Content.Shared.StationRecords.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
|
|
@ -34,7 +36,7 @@ public sealed partial class CrewManifestSystem : EntitySystem
|
|||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<AfterGeneralRecordCreatedEvent>(AfterGeneralRecordCreated);
|
||||
SubscribeLocalEvent<GeneralRecordCreatedEvent>(GeneralRecordCreated);
|
||||
SubscribeLocalEvent<RecordModifiedEvent>(OnRecordModified);
|
||||
SubscribeLocalEvent<RecordRemovedEvent>(OnRecordRemoved);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
|
|
@ -72,19 +74,19 @@ public sealed partial class CrewManifestSystem : EntitySystem
|
|||
// Not a big fan of this one. Rebuilds the crew manifest every time
|
||||
// somebody spawns in, meaning that at round start, it rebuilds the crew manifest
|
||||
// wrt the amount of players readied up.
|
||||
private void AfterGeneralRecordCreated(AfterGeneralRecordCreatedEvent ev)
|
||||
private void GeneralRecordCreated(ref GeneralRecordCreatedEvent ev)
|
||||
{
|
||||
BuildCrewManifest(ev.Key.OriginStation);
|
||||
UpdateEuis(ev.Key.OriginStation);
|
||||
}
|
||||
|
||||
private void OnRecordModified(RecordModifiedEvent ev)
|
||||
private void OnRecordModified(ref RecordModifiedEvent ev)
|
||||
{
|
||||
BuildCrewManifest(ev.Key.OriginStation);
|
||||
UpdateEuis(ev.Key.OriginStation);
|
||||
}
|
||||
|
||||
private void OnRecordRemoved(RecordRemovedEvent ev)
|
||||
private void OnRecordRemoved(ref RecordRemovedEvent ev)
|
||||
{
|
||||
BuildCrewManifest(ev.Key.OriginStation);
|
||||
UpdateEuis(ev.Key.OriginStation);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using Content.Server.Popups;
|
||||
using Content.Server.Radio.EntitySystems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.CriminalRecords;
|
||||
using Content.Shared.CriminalRecords.Components;
|
||||
|
|
@ -12,11 +10,12 @@ using Content.Shared.StationRecords;
|
|||
using Robust.Server.GameObjects;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Security.Components;
|
||||
using System.Linq;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.StationRecords.Components;
|
||||
using Content.Shared.StationRecords.Events;
|
||||
using Content.Shared.StationRecords.Systems;
|
||||
|
||||
namespace Content.Server.CriminalRecords.Systems;
|
||||
|
||||
|
|
@ -38,7 +37,7 @@ public sealed partial class CriminalRecordsConsoleSystem : SharedCriminalRecords
|
|||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<CriminalRecordsConsoleComponent, RecordModifiedEvent>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<CriminalRecordsConsoleComponent, AfterGeneralRecordCreatedEvent>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<CriminalRecordsConsoleComponent, GeneralRecordCreatedEvent>(UpdateUserInterface);
|
||||
|
||||
Subs.BuiEvents<CriminalRecordsConsoleComponent>(CriminalRecordsConsoleKey.Key, subs =>
|
||||
{
|
||||
|
|
@ -64,6 +63,7 @@ public sealed partial class CriminalRecordsConsoleSystem : SharedCriminalRecords
|
|||
ent.Comp.ActiveKey = msg.SelectedKey;
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
|
||||
private void OnStatusFilterPressed(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordSetStatusFilter msg)
|
||||
{
|
||||
ent.Comp.FilterStatus = msg.FilterStatus;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.CriminalRecords;
|
||||
using Content.Shared.CriminalRecords.Components;
|
||||
using Content.Shared.CriminalRecords.Systems;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Security;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Shared.StationRecords.Systems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.CriminalRecords.Systems;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
using System.Linq;
|
||||
using Content.Server.CartridgeLoader;
|
||||
using Content.Server.CartridgeLoader.Cartridges;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.CriminalRecords;
|
||||
using Content.Shared.CriminalRecords.Systems;
|
||||
using Content.Shared.Security;
|
||||
|
|
@ -10,6 +7,8 @@ using Content.Server.GameTicking;
|
|||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.StationRecords.Events;
|
||||
using Content.Shared.StationRecords.Systems;
|
||||
|
||||
namespace Content.Server.CriminalRecords.Systems;
|
||||
|
||||
|
|
@ -32,14 +31,14 @@ public sealed partial class CriminalRecordsSystem : SharedCriminalRecordsSystem
|
|||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AfterGeneralRecordCreatedEvent>(OnGeneralRecordCreated);
|
||||
SubscribeLocalEvent<GeneralRecordCreatedEvent>(OnGeneralRecordCreated);
|
||||
SubscribeLocalEvent<WantedListCartridgeComponent, CriminalRecordChangedEvent>(OnRecordChanged);
|
||||
SubscribeLocalEvent<WantedListCartridgeComponent, CartridgeUiReadyEvent>(OnCartridgeUiReady);
|
||||
SubscribeLocalEvent<WantedListCartridgeComponent, CriminalHistoryAddedEvent>(OnHistoryAdded);
|
||||
SubscribeLocalEvent<WantedListCartridgeComponent, CriminalHistoryRemovedEvent>(OnHistoryRemoved);
|
||||
}
|
||||
|
||||
private void OnGeneralRecordCreated(AfterGeneralRecordCreatedEvent ev)
|
||||
private void OnGeneralRecordCreated(ref GeneralRecordCreatedEvent ev)
|
||||
{
|
||||
_records.AddRecordEntry(ev.Key, new CriminalRecord());
|
||||
_records.Synchronize(ev.Key);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using Content.Shared.Delivery;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.EntityTable;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Shared.StationRecords.Components;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue