Dynamic description (#246)

* Add Dynamic Text

* add UI for entering a dynamic description

* work UI

* work DynamicText

* fixed client

* final bugfix

* mini change

* Okay... Again mini change

* fix request DynamicText

* add limit Dynamic text
This commit is contained in:
Litogin
2025-10-17 18:44:50 +03:00
committed by GitHub
parent 514a11448b
commit 05d6186b8d
19 changed files with 289 additions and 6 deletions

View File

@@ -1,4 +1,4 @@
<Control Name="CFlavorText" xmlns="https://spacestation14.io">
<Control Name="CFlavorText" xmlns="https://spacestation14.io">
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<!-- WL-OOCText-Start -->
<Label Text="{Loc 'flavor-tab-flavor-text'}" Margin="5 5 0 0" />

View File

@@ -1,4 +1,4 @@
using System.Linq;
using Content.Client._WL.DynamicText.UI; // WL-Chages
using Content.Client.CharacterInfo;
using Content.Client.Gameplay;
using Content.Client.Stylesheets;
@@ -19,6 +19,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input.Binding;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Linq;
using static Content.Client.CharacterInfo.CharacterInfoSystem;
using static Robust.Client.UserInterface.Controls.BaseButton;
@@ -31,6 +32,10 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
//WL-Changes-Start
[Dependency] private readonly DynamicTextUIController _dynamicText = default!;
//WL-Changes-end
[UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
[UISystemDependency] private readonly SpriteSystem _sprite = default!;
@@ -54,6 +59,13 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
_window.OnClose += DeactivateButton;
_window.OnOpen += ActivateButton;
//WL-Changes-Start
_window.DynamicTextButton.OnPressed += _ =>
{
_dynamicText.OpenWindow();
};
//WL-Changes-End
CommandBinds.Builder
.Bind(ContentKeyFunctions.OpenCharacterMenu,
InputCmdHandler.FromDelegate(_ => ToggleWindow()))

View File

@@ -13,6 +13,9 @@
<BoxContainer Orientation="Vertical" VerticalAlignment="Top">
<Label Name="NameLabel" Access="Public"/>
<Label Name="SubText" VerticalAlignment="Top" StyleClasses="LabelSubText" Access="Public"/>
<!--WL-Change-Start-->
<Button Access="Public" Name="DynamicTextButton" Text="{Loc 'character-info-dynamic-text-button'}"/>
<!--WL-Change-End-->
</BoxContainer>
</BoxContainer>
<Label Name="ObjectivesLabel" Access="Public" Text="{Loc 'character-info-objectives-label'}" HorizontalAlignment="Center"/>

View File

@@ -32,5 +32,17 @@
<RichTextLabel Name="OocText" HorizontalExpand="True" Margin="8" />
</BoxContainer>
</ScrollContainer>
<ScrollContainer HorizontalExpand="True" HScrollEnabled="False" MinWidth="400">
<BoxContainer Orientation="Vertical">
<Label
Name="DynamicTextLabel"
HorizontalExpand="True"
Text="{Loc 'character-information-ui-dynamic-text'}"
Margin="0 10 0 0"
HorizontalAlignment="Center" />
<RichTextLabel Name="DynamicText" HorizontalExpand="True" Margin="8" />
</BoxContainer>
</ScrollContainer>
</TabContainer>
</controls:FancyWindow>

View File

@@ -23,6 +23,7 @@ public sealed partial class CharacterInformationWindow : FancyWindow
Tabs.SetTabTitle(0, Loc.GetString("character-information-ui-sprite"));
Tabs.SetTabTitle(1, Loc.GetString("character-information-ui-flavor-text"));
Tabs.SetTabTitle(2, Loc.GetString("character-information-ui-ooc-text"));
Tabs.SetTabTitle(3, Loc.GetString("character-information-ui-dynamic-text"));
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -60,6 +61,17 @@ public sealed partial class CharacterInformationWindow : FancyWindow
OocTextLabel.Text = Loc.GetString("character-information-ui-no-ooc-text");
}
if (!string.IsNullOrEmpty(state.DynamicText))
{
DynamicText.SetMessage(state.DynamicText);
DynamicTextLabel.Text = Loc.GetString("character-information-ui-dynamic-text") + ":";
}
else
{
DynamicText.SetMessage("");
DynamicTextLabel.Text = Loc.GetString("character-information-ui-no-dynamic-text");
}
SetWidth = Size.X + 0.1f;
}
}

View File

@@ -0,0 +1,47 @@
using Content.Client._WL.DynamicText.UI;
using Content.Shared._WL.DynamicText;
using Robust.Client.Player;
using Robust.Client.UserInterface;
namespace Content.Client._WL.DynamicText;
public sealed partial class DynamicTextSystem : EntitySystem
{
[Dependency] private readonly IEntityManager _ent = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<RequestedDynamicTextEvent>(OnDynamicTextReceived);
}
public void SaveDynamicText(string text)
{
if (!_player.LocalEntity.HasValue)
return;
if (!_ent.TryGetNetEntity(_player.LocalEntity.Value, out var netEntity))
return;
if (text is null)
return;
RaiseNetworkEvent(new SetDynamicTextEvent(netEntity.Value, text));
}
public void RequestDynamicText()
{
if (!_player.LocalEntity.HasValue)
return;
if (!_ent.TryGetNetEntity(_player.LocalEntity.Value, out var netEntity))
return;
RaiseNetworkEvent(new RequestDynamicTextEvent(netEntity.Value));
}
public void OnDynamicTextReceived(RequestedDynamicTextEvent ev, EntitySessionEventArgs args)
{
_userInterfaceManager.GetUIController<DynamicTextUIController>().SetDynamicText(ev.DynamicText);
}
}

View File

@@ -0,0 +1,33 @@
using Content.Shared._WL.DynamicText;
using Robust.Client.UserInterface.Controllers;
namespace Content.Client._WL.DynamicText.UI;
public sealed class DynamicTextUIController : UIController
{
[Dependency] private readonly IEntityManager _entManager = default!;
private DynamicTextWindow? _dynamicTextWindow;
public void OpenWindow()
{
if (_dynamicTextWindow == null || _dynamicTextWindow.Disposed)
_dynamicTextWindow = UIManager.CreateWindow<DynamicTextWindow>();
if (_dynamicTextWindow != null)
{
_dynamicTextWindow.OnDynamicTextSaveButtonPressed += OnSave;
}
_entManager.System<DynamicTextSystem>().RequestDynamicText();
_dynamicTextWindow?.OpenCentered();
}
public void SetDynamicText(string text)
{
_dynamicTextWindow?.SetDynamicText(text);
}
private void OnSave(string text)
{
_entManager.System<DynamicTextSystem>().SaveDynamicText(text);
}
}

View File

@@ -0,0 +1,24 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'dynamic-text-title'}"
MinSize="700 350"
SetSize="700 350">
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True"
Margin="5 0 5 0">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
<TextEdit Name="CDynamicTextInput" Access="Public" MinSize="220 100" HorizontalExpand="True" Margin="5 5 0 0" VerticalExpand="True" />
</BoxContainer>
<Control MinHeight="10"/>
<BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
<Label Text="" StyleClasses="WindowFooterText"
HorizontalAlignment="Left" HorizontalExpand="True" Margin="0 0 5 0" />
<Button Access="Public" Name="DynamicTextSaveButton" Text="{Loc 'dynamic-text-save-button'}" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<Button Access="Public" Name="DynamicTextCloseButton" Text="{Loc 'dynamic-text-close-button'}" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,38 @@
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
using Robust.Client.UserInterface.Controls;
namespace Content.Client._WL.DynamicText.UI;
[GenerateTypedNameReferences]
public sealed partial class DynamicTextWindow : FancyWindow
{
public Action<string>? OnDynamicTextSaveButtonPressed;
public DynamicTextWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
var loc = IoCManager.Resolve<ILocalizationManager>();
CDynamicTextInput.Placeholder = new Rope.Leaf(loc.GetString("dynamic-text-placeholder"));
DynamicTextSaveButton.OnPressed += OnDynamicTextSave;
DynamicTextCloseButton.OnPressed += OnClose;
}
public void SetDynamicText(string text)
{
CDynamicTextInput.TextRope = new Rope.Leaf(text);
}
private void OnDynamicTextSave(BaseButton.ButtonEventArgs obj)
{
OnDynamicTextSaveButtonPressed?.Invoke(Rope.Collapse(CDynamicTextInput.TextRope).Trim());
}
private void OnClose(BaseButton.ButtonEventArgs obj)
{
Close();
}
}

View File

@@ -1,4 +1,4 @@
namespace Content.Server._WL.CharacterInformation;
namespace Content.Server._WL.CharacterInformation;
/// <summary>
/// Adds examine details verb and store information that can be accessed without mob actor
@@ -13,4 +13,8 @@ public sealed partial class CharacterInformationComponent : Component
[ViewVariables(VVAccess.ReadWrite)]
[DataField("oocText")]
public string OocText = string.Empty;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("dynamicText")]
public string DynamicText = string.Empty;
}

View File

@@ -55,7 +55,7 @@ public sealed class CharacterInformationSystem : EntitySystem
return;
var charName = Identity.Name(targetUid, EntityManager);
var state = new CharacterInformationBuiState(GetNetEntity(targetUid), charName, charInfo.FlavorText, charInfo.OocText);
var state = new CharacterInformationBuiState(GetNetEntity(targetUid), charName, charInfo.FlavorText, charInfo.OocText, charInfo.DynamicText);
_userInterfaceSystem.SetUiState(uid, CharacterInformationUiKey.Key, state);
}
}

View File

@@ -0,0 +1,52 @@
using Content.Server._WL.CharacterInformation;
using Content.Shared._WL.CCVars;
using Content.Shared._WL.DynamicText;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server._WL.DynamicText;
public sealed class DynamicTextSystem : EntitySystem
{
[Dependency] private readonly IEntityManager _ent = default!;
[Dependency] private readonly IConfigurationManager _cfm = default!;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<SetDynamicTextEvent>(SetDynamicText);
SubscribeNetworkEvent<RequestDynamicTextEvent>(RequestDynamicText);
}
private void SetDynamicText(SetDynamicTextEvent ev, EntitySessionEventArgs args)
{
if (!_ent.TryGetEntity(ev.Entity, out var ent))
return;
if (!TryComp<CharacterInformationComponent>(ent, out var comp))
return;
if (args.SenderSession.AttachedEntity != ent)
return;
var maxDynamicTextLength = _cfm.GetCVar(WLCVars.MaxDynamicTextLength);
comp.DynamicText = ev.DynamicText.Length > maxDynamicTextLength ? FormattedMessage.RemoveMarkupOrThrow(ev.DynamicText)[..maxDynamicTextLength] : FormattedMessage.RemoveMarkupOrThrow(ev.DynamicText);
}
private void RequestDynamicText(RequestDynamicTextEvent ev, EntitySessionEventArgs args)
{
if (!_ent.TryGetEntity(ev.Entity, out var ent))
return;
if (args.SenderSession.AttachedEntity != ent)
return;
if (!TryComp<CharacterInformationComponent>(ent, out var comp))
return;
RaiseNetworkEvent(new RequestedDynamicTextEvent(comp.DynamicText ?? string.Empty), Filter.SinglePlayer(args.SenderSession));
}
}

View File

@@ -138,4 +138,11 @@ public sealed class WLCVars
/// </summary>
public static readonly CVarDef<int> VoteShuttleTimer =
CVarDef.Create("vote.evacuation_shuttle_vote_time", 40, CVar.SERVERONLY);
/*
* Ic
*/
public static readonly CVarDef<int> MaxDynamicTextLength =
CVarDef.Create("ic.dynamic_text_length", 1024, CVar.SERVER | CVar.REPLICATED);
}

View File

@@ -1,4 +1,4 @@
using Robust.Shared.Serialization;
using Robust.Shared.Serialization;
namespace Content.Shared._WL.CharacterInformation;
@@ -15,16 +15,19 @@ public sealed class CharacterInformationBuiState : BoundUserInterfaceState
public string CharacterName;
public string FlavorText;
public string? OocText;
public string? DynamicText;
public CharacterInformationBuiState(
NetEntity uid,
string characterName,
string flavorText,
string? oocText)
string? oocText,
string? dynamicText)
{
Uid = uid;
CharacterName = characterName;
FlavorText = flavorText;
OocText = oocText;
DynamicText = dynamicText;
}
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.Serialization;
namespace Content.Shared._WL.DynamicText;
[Serializable, NetSerializable]
public sealed class RequestDynamicTextEvent(NetEntity entity) : EntityEventArgs
{
public NetEntity Entity { get; } = entity;
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.Serialization;
namespace Content.Shared._WL.DynamicText;
[Serializable, NetSerializable]
public sealed class RequestedDynamicTextEvent(string dynamicText) : EntityEventArgs
{
public string DynamicText { get; } = dynamicText;
}

View File

@@ -0,0 +1,10 @@
using Robust.Shared.Serialization;
namespace Content.Shared._WL.DynamicText;
[Serializable, NetSerializable]
public sealed class SetDynamicTextEvent(NetEntity netEntity, string dynamicText) : EntityEventArgs
{
public NetEntity Entity { get; } = netEntity;
public string DynamicText { get; } = dynamicText;
}

View File

@@ -1,6 +1,10 @@
character-information-ui-title = Информация о персонаже
character-information-ui-flavor-text = Описание персонажа
character-information-ui-ooc-text = OOC заметки
character-information-ui-dynamic-text = Динамические заметки
character-information-ui-sprite = Спрайт
character-information-ui-no-flavor-text = Описания персонажа нет
character-information-ui-no-ooc-text = ООС заметок нет
character-information-ui-no-dynamic-text = Динамических заметок нет
character-info-dynamic-text-button = Динамическое описание

View File

@@ -0,0 +1,4 @@
dynamic-text-title = Динамическое описание
dynamic-text-save-button = Сохранить
dynamic-text-close-button = Закрыть
dynamic-text-placeholder = Динамическое описание