Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
DEATHB4DEFEAT committed Jun 2, 2024
1 parent bed753a commit b022678
Show file tree
Hide file tree
Showing 12 changed files with 395 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using System.Linq;
using Content.Client.Examine;
using Content.Client.Inventory;
using Content.Shared.Examine.CharacterInformation.Components;
using Content.Client.Examine.CharacterInformation.UI;
using Content.Shared.Access.Components;
using Content.Shared.CCVar;
using Content.Shared.DetailExaminable;
using Content.Shared.IdentityManagement.Components;
using Content.Shared.PDA;
using Content.Shared.Roles;
using Content.Shared.Verbs;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;

namespace Content.Client.Examine.CharacterInformation.Systems;

public sealed class CharacterInformationSystem : EntitySystem
{
[Dependency] private readonly ExamineSystem _examine = default!;
[Dependency] private readonly ClientInventorySystem _inventory = default!;
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IConfigurationManager _config = default!;

private CharacterInformationWindow? _window;


public override void Initialize()
{
base.Initialize();

_window = new CharacterInformationWindow();

SubscribeLocalEvent<CharacterInformationComponent, GetVerbsEvent<ExamineVerb>>(OnGetExamineVerbs);
}


private void OnGetExamineVerbs(EntityUid uid, CharacterInformationComponent component, GetVerbsEvent<ExamineVerb> args)
{
var verb = new ExamineVerb
{
Act = () =>
{
ShowInfoWindow(args.Target);
},
Text = Loc.GetString("character-information-verb-text"),
Message = Loc.GetString("character-information-verb-message"),
Category = VerbCategory.Examine,
Disabled = !_examine.IsInDetailsRange(args.User, uid),
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
ClientExclusive = true,
};

args.Verbs.Add(verb);
}


private void ShowInfoWindow(EntityUid uid)
{
if (_window == null)
return;

string? name = null;
string? job = null;
string? flavorText = null;

// Get ID from inventory, get name and job from ID
var info = GetNameAndJob(uid);

name = info.Item1;
job = info.Item2;

// Fancy job title
if (!string.IsNullOrEmpty(job))
{
var test = job.Replace(" ", "");
// Command will be last in the list
// TODO: Make this not revolve around this fact ^
var departments = _prototype.EnumeratePrototypes<DepartmentPrototype>().OrderBy(d => d.ID).Reverse();
var department = departments.FirstOrDefault(d => d.Roles.Contains(test));

if (department is not null)
{
// Department (ex: Command or Security)
var dept = string.Join(" ", Loc.GetString($"department-{department.ID}").Split(' ').Select(s => s[0].ToString().ToUpper() + s[1..].ToLower()));
// Redo the job title with the department color and department (ex: Captain (Command) or Security Officer (Security))
job = $"[color={department.Color.ToHex()}]{job} ({dept})[/color]";
}
}

// Get and set flavor text
if (_config.GetCVar(CCVars.FlavorText) &&
_entity.TryGetComponent<DetailExaminableComponent>(uid, out var detail))
flavorText = detail.Content;

_window.UpdateUi(uid, name, job, flavorText);
_window.Open();
}

/// <summary>
/// Gets the ID card component from either a PDA or an ID card if the entity has one
/// </summary>
/// <param name="idUid">Entity to check</param>
/// <returns>ID card component if they have one on the entity</returns>
/// <remarks>This function should not exist</remarks> // TODO Remove this function
private IdCardComponent? GetId(EntityUid? idUid)
{
// PDA
if (_entity.TryGetComponent(idUid, out PdaComponent? pda) && pda.ContainedId is not null)
return _entity.GetComponent<IdCardComponent>(pda.ContainedId.Value);
// ID Card
if (_entity.TryGetComponent(idUid, out IdCardComponent? id))
return id;

return null;
}

/// <summary>
/// Gets the name and job title from an ID card component
/// </summary>
/// <param name="uid">The entity to attempt information retrieval from</param>
/// <returns>Name, Job Title</returns>
/// <remarks>This function should not exist</remarks>
private (string, string) GetNameAndJob(EntityUid uid)
{
string? name = null;

if (_entity.TryGetComponent<IdentityComponent>(uid, out var identity) &&
identity.IdentityEntitySlot.ContainedEntity != null)
name = _entity.GetComponent<MetaDataComponent>(identity.IdentityEntitySlot.ContainedEntity.Value).EntityName;

if (_inventory.TryGetSlotEntity(uid, "id", out var idUid))
{
var id = GetId(idUid);
if (id is not null)
{
name ??= id.FullName;
if (string.IsNullOrEmpty(name))
name = "Unknown";

var jobTitle = id.JobTitle;
if (string.IsNullOrEmpty(jobTitle))
jobTitle = "Unknown";
jobTitle = string.Join(" ", jobTitle.Split(' ').Select(s => s[0].ToString().ToUpper() + s[1..].ToLower()));

return (name, jobTitle);
}
}

return (name ?? "Unknown", "Unknown");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:style="clr-namespace:Content.Client.Stylesheets"
Title="{Loc 'character-information-ui-title'}" Name="RootWindow" MinSize="675 384" Resizable="True">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" Margin="8">
<BoxContainer Orientation="Vertical">
<GridContainer Name="SpriteContainer" Columns="2">
<!-- Added by code
<SpriteView Name="SpriteViewS" />
<SpriteView Name="SpriteViewN" />
<SpriteView Name="SpriteViewW" />
<SpriteView Name="SpriteViewE" />
-->
</GridContainer>
<!-- Character name, job title, etc goes here -->
<RichTextLabel Name="Name" HorizontalAlignment="Center" />
<RichTextLabel Name="Job" HorizontalAlignment="Center" VerticalAlignment="Center" />
</BoxContainer>

<PanelContainer Name="Separator" MinSize="2 5" MaxWidth="2" Margin="8 0 8 0">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="{x:Static style:StyleNano.NanoGold}" ContentMarginTopOverride="2" />
</PanelContainer.PanelOverride>
</PanelContainer>

<ScrollContainer Name="FlavorTextScroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<!-- Character description goes here -->
<RichTextLabel Name="FlavorText" HorizontalExpand="True" />
</BoxContainer>
</ScrollContainer>
</BoxContainer>
</controls:FancyWindow>
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using System.Linq;
using System.Numerics;
using Content.Client.Message;
using Content.Client.UserInterface.Controls;
using Content.Shared.Humanoid;
using Content.Shared.Inventory;
using Microsoft.CodeAnalysis;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Containers;
using Robust.Shared.Map;

namespace Content.Client.Examine.CharacterInformation.UI;

[GenerateTypedNameReferences]
public sealed partial class CharacterInformationWindow : FancyWindow
{
private readonly IEntityManager _entity;
private readonly InventorySystem _inventory;

private EntityUid _dummy = EntityUid.Invalid;

// ReSharper disable once InconsistentNaming
private FancyWindow _rootWindow => RootWindow;
// ReSharper disable once InconsistentNaming
private GridContainer _sprites => SpriteContainer;
// ReSharper disable once InconsistentNaming
private RichTextLabel _name => Name;
// ReSharper disable once InconsistentNaming
private RichTextLabel _job => Job;
// ReSharper disable once InconsistentNaming
private PanelContainer _separator => Separator;
// ReSharper disable once InconsistentNaming
private ScrollContainer _flavorTextScroll => FlavorTextScroll;
// ReSharper disable once InconsistentNaming
private RichTextLabel _flavor => FlavorText;

public CharacterInformationWindow()
{
RobustXamlLoader.Load(this);

_entity = IoCManager.Resolve<IEntityManager>();
_inventory = EntitySystem.Get<InventorySystem>();

ResetUi();
}


/// <summary>
/// Placeholder entries
/// </summary>
private void ResetUi()
{
_entity.DeleteEntity(_dummy);
_sprites.RemoveAllChildren();

var unknown = Loc.GetString("generic-unknown");
// Capitalize the first letter of each word (Title Case)
unknown = string.Join(" ", unknown.Split(' ').Select(s => char.ToUpper(s[0]) + s[1..]));

_name.SetMarkup(unknown);
_job.SetMarkup(unknown);

_flavor.SetMarkup("Placeholder flavor text.");
}

/// <summary>
/// Updates the UI to show all relevant information about the entity
/// </summary>
/// <param name="examined">The entity to become informed about</param>
/// <param name="name">The name of the examined entity, taken from their ID</param>
/// <param name="job">The job of the examined entity, taken from their ID</param>
/// <param name="flavorText">The flavor text of the examined entity</param>
public void UpdateUi(EntityUid examined, string? name = null, string? job = null, string? flavorText = null)
{
ResetUi();

// Fill in the omnidirectional sprite views
if (_entity.TryGetComponent<SpriteComponent>(examined, out var sprite))
FillSprites(sprite, examined);

// Fill in the name and job
if (!string.IsNullOrEmpty(name))
_name.SetMarkup(name);
if (!string.IsNullOrEmpty(job))
_job.SetMarkup(job);

// Fill in the flavor text
if (!string.IsNullOrEmpty(flavorText))
{
_flavor.SetMessage(flavorText);
_rootWindow.MinSize = new Vector2(675, 384);
_rootWindow.SetSize = new Vector2(675, 384);
_separator.Visible = true;
_flavorTextScroll.Visible = true;
}
else
{
_rootWindow.MinSize = new Vector2(292, 384);
_rootWindow.SetSize = new Vector2(292, 384);
_separator.Visible = false;
_flavorTextScroll.Visible = false;
}
}


/// <summary>
/// Fills the sprite views with the sprite from the sprite component
/// </summary>
/// <param name="sprite">Sprite component to use</param>
/// <param name="entity">The entity <paramref name="sprite"/> belongs to</param>
private void FillSprites(SpriteComponent sprite, EntityUid entity)
{
// This all should "freeze" the sprite views
// Spawn a dummy entity to get a copy of the sprite component from
_dummy = _entity.SpawnEntity(_entity.GetComponent<MetaDataComponent>(entity).EntityPrototype!.ID, MapCoordinates.Nullspace);

// Ensures the dummy has the same sex as the entity so masks are applied correctly
if (_entity.TryGetComponent(entity, out HumanoidAppearanceComponent? humanoidAppearance))
{
var newHumanoidAppearance = _entity.EnsureComponent<HumanoidAppearanceComponent>(_dummy);
newHumanoidAppearance.Sex = humanoidAppearance.Sex;
}

// Spawn and equip a fake jumpsuit (it won't be shown) so the appearance system doesn't destroy reality when applying masks
var clothing = _entity.SpawnEntity("ClothingUniformJumpsuitColorGrey", MapCoordinates.Nullspace);
_inventory.TryEquip(_dummy, _dummy, clothing, "jumpsuit", true, true);

// Copy the sprite component from the original entity to the dummy
var newSprite = _entity.EnsureComponent<SpriteComponent>(_dummy);
newSprite.CopyFrom(sprite);
newSprite.Scale = Vector2.One;


// Create the SpriteViews
// From lists because redefining everything except direction and margin is annoying and hard to edit in the future
var directions = new List<Direction> {Direction.South, Direction.North, Direction.West, Direction.East};
var margins = new List<Thickness>
{
new(0, 0, 8, 8), // South
new(8, 0, 0, 8), // North
new(0, 8, 8, 0), // West
new(8, 8, 0, 0), // East
};

for (var i = 0; i < directions.Count; i++)
{
var view = new SpriteView
{
Scale = new Vector2(4, 4),
Stretch = SpriteView.StretchMode.None,
MaxSize = new Vector2(128, 128),
OverrideDirection = directions[i],
Margin = margins[i]
};
view.SetEntity(_dummy);

_sprites.AddChild(view);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Content.Server.DeltaV.ParadoxAnomaly.Components;
using Content.Server.DetailExaminable;
using Content.Server.GenericAntag;
using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components;
Expand All @@ -18,6 +17,7 @@
using Robust.Shared.Random;
using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.DetailExaminable;

namespace Content.Server.DeltaV.ParadoxAnomaly.Systems;

Expand Down Expand Up @@ -140,6 +140,7 @@ private bool TrySpawnParadoxAnomaly(string rule, [NotNullWhen(true)] out EntityU
{
var detailCopy = EnsureComp<DetailExaminableComponent>(spawned);
detailCopy.Content = detail.Content;
Dirty(detailCopy);
}

if (job.StartingGear != null && _proto.TryIndex<StartingGearPrototype>(job.StartingGear, out var gear))
Expand Down
9 changes: 0 additions & 9 deletions Content.Server/DetailExaminable/DetailExaminableComponent.cs

This file was deleted.

Loading

0 comments on commit b022678

Please sign in to comment.