Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Health bar #13

Merged
merged 7 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Content.Client/UserInterface/Screens/DefaultGameScreen.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
xmlns:hotbar="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:inventory="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Widgets"
xmlns:sss="clr-namespace:Content.Client._SSS.UserInterface.Widgets"
Name="DefaultHud"
VerticalExpand="False"
VerticalAlignment="Bottom"
Expand All @@ -30,4 +31,5 @@
<hotbar:HotbarGui Name="Hotbar" Access="Protected" />
<chat:ResizableChatBox Name="Chat" Access="Protected" />
<alerts:AlertsUI Name="Alerts" Access="Protected" />
<sss:SSSStatusGui Name="SSSStatus" Access="Protected" />
</screens:DefaultGameScreen>
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public DefaultGameScreen()
SetAnchorAndMarginPreset(Chat, LayoutPreset.TopRight, margin: 10);
SetAnchorAndMarginPreset(Alerts, LayoutPreset.TopRight, margin: 10);

SetAnchorAndMarginPreset(SSSStatus, LayoutPreset.BottomRight, margin: 10);

Chat.OnResized += ChatOnResized;
Chat.OnChatResizeFinish += ChatOnResizeFinish;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:inventory="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Widgets"
xmlns:sss="clr-namespace:Content.Client._SSS.UserInterface.Widgets"
Name="SeparatedChatHud"
VerticalExpand="False"
VerticalAlignment="Bottom"
Expand All @@ -25,6 +26,7 @@
<BoxContainer Name="VoteMenu" Access="Public" Orientation="Vertical"/>
</BoxContainer>
<alerts:AlertsUI Name="Alerts" Access="Protected" />
<sss:SSSStatusGui Name="SSSStatus" Access="Protected" />
</LayoutContainer>
<PanelContainer HorizontalExpand="True" MinWidth="300">
<PanelContainer.PanelOverride>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public SeparatedChatGameScreen()
SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5);
SetAnchorAndMarginPreset(Alerts, LayoutPreset.CenterRight, margin: 10);

SetAnchorAndMarginPreset(SSSStatus, LayoutPreset.BottomRight, margin: 10);

ScreenContainer.OnSplitResizeFinished += () =>
OnChatResized?.Invoke(new Vector2(ScreenContainer.SplitFraction, 0));

Expand Down
253 changes: 253 additions & 0 deletions Content.Client/_SSS/UserInterface/SSSStatusUIController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
using Content.Client._SSS.UserInterface.Widgets;
using Content.Client.Gameplay;
using Content.Client.GameTicking.Managers;
using Content.Shared._SSS.SuspicionGameRule;
using Content.Shared._SSS.SuspicionGameRule.Components;
using Content.Shared.Damage;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Timing;

namespace Content.Client._SSS.UserInterface;

public sealed class SSSStatusUIController : UIController, IOnSystemChanged<SSSStatusUISystem>, IOnStateChanged<GameplayState>
{
private ISawmill _log = default!;
Callmore marked this conversation as resolved.
Show resolved Hide resolved

[Dependency] private readonly IPlayerManager _playerManager = default!;

[UISystemDependency] private readonly MobThresholdSystem? _mobThreshold = default!;
[UISystemDependency] private readonly ClientGameTicker? _clientGameTicker = default!;

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

_log = Logger.GetSawmill("StatusUI");

SubscribeNetworkEvent<SuspicionRuleTimerUpdate>(UpdateTimerEnd);
SubscribeNetworkEvent<SuspicionRulePreroundStarted>(PreroundStarted);
SubscribeNetworkEvent<SuspicionRuleUpdateRole>(UpdateRoleDisplay);
SubscribeNetworkEvent<SuspicionRulePlayerSpawn>(UpdatePlayerSpawn);
}

private TimeSpan _lastEndTime;
private TimeSpan _lastDrawnSeconds;

private (string, Color)? _queuedRole = null;
private (string, float, float)? _queuedHealth = null;

public override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);

// I LOVE THIS HACK I LOVE THIS HACK I LOVE THIS HACK I LOVE THIS HACK
SetRoleFromQueued();
SetHealthBarFromQueued();

// TODO: limit this to only update when the timer is not the same
if (_clientGameTicker is null)
return;

if (_lastEndTime == TimeSpan.MinValue)
return;

var drawTime = _lastEndTime.Subtract(_clientGameTicker.RoundDuration());
if ((int)drawTime.TotalSeconds != (int)_lastDrawnSeconds.TotalSeconds)
{
UpdateTimer(drawTime);
_lastDrawnSeconds = drawTime;
}
}

private SSSStatusGui? StatusUI => UIManager.GetActiveUIWidgetOrNull<SSSStatusGui>();

public void UpdateHealth(Entity<DamageableComponent> ent)
{

if (!EntityManager.TryGetComponent<MobStateComponent>(ent, out var mobState))
return;

if (mobState.CurrentState == MobState.Dead)
{
SetHealthBarUI(Loc.GetString("suspicion-status-ui-health-dead"), 0, 100);
return;
}

if (!EntityManager.TryGetComponent<MobThresholdsComponent>(ent, out var mobThresholds))
return;

if (_mobThreshold is null)
return;

var maxHp = _mobThreshold.GetThresholdForState(ent, MobState.Critical, mobThresholds);
var hp = Math.Max(Math.Ceiling((maxHp - ent.Comp.TotalDamage).Double()), 0);

SetHealthBar((float)hp, maxHp.Float());
}

private void SetHealthBarUI(string text, float value, float maxValue)
{
var ui = StatusUI;

if (ui == null)
{
_queuedHealth = (text, value, maxValue);
return;
}

ui.SetHealthBar(text, value, maxValue);
}

private bool SetHealthBarFromQueued()
{
if (_queuedHealth is null)
return false;

var ui = StatusUI;
if (ui is null)
return false;

var (text, value, maxValue) = _queuedHealth.Value;
ui.SetHealthBar(text, value, maxValue);
_queuedHealth = null;

return true;
}

private void SetHealthBar(float hp, float maxHp)
{
SetHealthBarUI($"\u2665 {hp}", hp, maxHp);
}

private void UpdateTimer(TimeSpan ts)
{
var ui = StatusUI;

if (ui == null)
return;

if (ts < TimeSpan.Zero)
{
ts = TimeSpan.Zero;
}

// nice job c#, TimeSpan.ToString doesn't support having no leading zeros
ui.TimerText.Text = $"{ts.Minutes}:{ts.Seconds:00}";
}

public void UpdateTimerEnd(SuspicionRuleTimerUpdate ev, EntitySessionEventArgs args)
{
_lastEndTime = ev.EndTime;
}

public void PreroundStarted(SuspicionRulePreroundStarted ev, EntitySessionEventArgs args)
{
_lastEndTime = ev.PreroundEndTime;
SetRoleToPreround();
}

private void SetRoleUI(string role, Color color)
{
var ui = StatusUI;

if (ui is null)
{
_queuedRole = (role, color);
return;
}

ui.SetRole(role, color);
}

private bool SetRoleFromQueued()
{
if (_queuedRole is null)
return false;

var ui = StatusUI;
if (ui is null)
return false;

var (role, color) = _queuedRole.Value;
ui.SetRole(role, color);
_queuedRole = null;

return true;
}

private void SetRoleToPreround()
{
SetRoleUI(Loc.GetString("suspicion-status-ui-role-preround"), Color.Gray);
}

private void SetRoleToObserbing()
{
SetRoleUI(Loc.GetString("suspicion-status-ui-role-obserbing"), Color.Gray);
}

public void UpdateRoleDisplay(SuspicionRuleUpdateRole ev, EntitySessionEventArgs args)
{
var roleName = Loc.GetString(ev.NewRole switch
{
SuspicionRole.Traitor => "roles-antag-suspicion-traitor-name",
SuspicionRole.Detective => "roles-antag-suspicion-detective-name",
SuspicionRole.Innocent => "roles-antag-suspicion-innocent-name",
_ => "roles-antag-suspicion-unknown",
});
SetRoleUI(roleName, Color.FromName(ev.NewRole.GetRoleColor()));
}

public void UpdatePlayerSpawn(SuspicionRulePlayerSpawn ev, EntitySessionEventArgs args)
{
if (ev.GameState == SuspicionGameState.Preparing)
{
SetRoleToPreround();

if (EntityManager.TryGetComponent<DamageableComponent>(_playerManager.LocalEntity!.Value, out var damagable))
UpdateHealth((_playerManager.LocalEntity!.Value, damagable));
}
else
{
SetRoleToObserbing();
SetHealthBar(0, 100);
}

_lastEndTime = ev.EndTime;
}

public void OnSystemLoaded(SSSStatusUISystem system)
{
system.OnPlayerDamageChanged += UpdateHealth;
}

public void OnSystemUnloaded(SSSStatusUISystem system)
{
system.OnPlayerDamageChanged -= UpdateHealth;
}

public void OnStateEntered(GameplayState state)
{
if (EntityManager.TryGetComponent<DamageableComponent>(_playerManager.LocalEntity!.Value, out var damagable))
UpdateHealth((_playerManager.LocalEntity!.Value, damagable));
else
SetHealthBar(0, 100);

UpdateTimer(TimeSpan.Zero);

SetRoleUI("-", Color.Black);
}

public void OnStateExited(GameplayState state)
{
SetHealthBarUI("-", 0, 100);

UpdateTimer(TimeSpan.Zero);

SetRoleUI("-", Color.Black);
}
}
28 changes: 28 additions & 0 deletions Content.Client/_SSS/UserInterface/SSSStatusUISystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Content.Shared.Damage;
using Robust.Shared.Player;

namespace Content.Client._SSS.UserInterface;

public sealed partial class SSSStatusUISystem : EntitySystem
{
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;

public event Action<Entity<DamageableComponent>>? OnPlayerDamageChanged;

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

SubscribeLocalEvent<DamageableComponent, DamageChangedEvent>(OnDamageChanged);
}

private void OnDamageChanged(Entity<DamageableComponent> ent, ref DamageChangedEvent args)
{
var (uid, damagable) = ent;

if (uid == _playerManager.LocalEntity)
{
OnPlayerDamageChanged?.Invoke(ent);
}
}
}
35 changes: 35 additions & 0 deletions Content.Client/_SSS/UserInterface/Widgets/SSSStatusGui.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<widgets:SSSStatusGui
xmlns="https://spacestation14.io"
xmlns:widgets="clr-namespace:Content.Client._SSS.UserInterface.Widgets"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
>

<Control>

<PanelContainer StyleClasses="AngleRect" />
<BoxContainer Orientation="Vertical" Margin="8">
<BoxContainer Orientation="Horizontal">
<Control>
<PanelContainer Name="RoleBG" Access="Public">
</PanelContainer>
<Label Name="RoleText" Access="Public" Text="Traitor?" Margin="4"/>
</Control>
<Label Name="TimerText" Access="Public" Text="5:00" Align="Right" HorizontalExpand="True" />
</BoxContainer>
<Control MinSize="0 4" />
<Control MinSize="200 20">
<ProgressBar Name="HealthBar" Access="Public" Value="75" MaxValue="100">
<ProgressBar.ForegroundStyleBoxOverride>
<graphics:StyleBoxFlat BackgroundColor="#DE3A3A" />
</ProgressBar.ForegroundStyleBoxOverride>
</ProgressBar>
<Label Name="HealthNumber" Access="Public" Text="100" Align="Right" />
</Control>
</BoxContainer>

</Control>

</widgets:SSSStatusGui>
Loading
Loading