-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
<!-- Guidelines: https://docs.spacestation14.io/en/getting-started/pr-guideline --> IT'S A HEALTH BAR HOLY SHID ## About the PR <!-- What did you change? --> ## Why / Balance <!-- Discuss how this would affect game balance or explain why it was changed. Link any relevant discussions or issues. --> ## Technical details <!-- Summary of code changes for easier review. --> ## Media <!-- Attach media if the PR makes ingame changes (clothing, items, features, etc). Small fixes/refactors are exempt. Media may be used in SS14 progress reports with credit. --> ![image](https://github.com/user-attachments/assets/ba664f75-f463-41ad-a5c3-2c28b7193015) ![image](https://github.com/user-attachments/assets/565f838b-9e57-4e4b-8dba-d3da94046cc8) ## Requirements <!-- Confirm the following by placing an X in the brackets [X]: --> - [X] I have read and am following the [Pull Request and Changelog Guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html). - [X] I have added media to this PR or it does not require an ingame showcase. <!-- You should understand that not following the above may get your PR closed at maintainer’s discretion --> ## Breaking changes <!-- List any breaking changes, including namespaces, public class/method/field changes, prototype renames; and provide instructions for fixing them. This will be posted in #codebase-changes. --> **Changelog** <!-- Add a Changelog entry to make players aware of new features or changes that could affect gameplay. Make sure to read the guidelines and take this Changelog template out of the comment block in order for it to show up. Changelog must have a 🆑 symbol, so the bot recognizes the changes and adds them to the game's changelog. --> <!-- 🆑 - add: Added fun! - remove: Removed fun! - tweak: Changed fun! - fix: Fixed fun! --> 🆑 - add: Status UI showing health, role and timer.
- Loading branch information
Showing
19 changed files
with
458 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
253 changes: 253 additions & 0 deletions
253
Content.Client/_SSS/UserInterface/SSSStatusUIController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!; | ||
|
||
[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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
35
Content.Client/_SSS/UserInterface/Widgets/SSSStatusGui.xaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.