UI Razor component for the perk choice panel. Renders available perk choices, reroll/banish/skip/random buttons, timers and labels, and handles input callbacks to modify the player's perk selection state.
@namespace Sandbox
@using Sandbox;
@using Sandbox.UI;
@using System;
@inherits Panel
@attribute [StyleSheet("PerkChoicePanel.razor.scss")]
@{
var player = GetViewedPlayer();
var isReadOnly = IsReadOnly( player );
var showViewingLabel = isReadOnly && !Manager.Instance.IsSpectator;
if ( !player.IsValid() || (!player.IsChoosingLevelUpReward && !showViewingLabel) )
{
Manager.Instance.IsHoveringPerkChoicePanel = false;
return;
}
}
@{
var showBanish = player.HasGottenBanish;
var showSkip = GetStat( player, PlayerStat.SkipPerkNumRerolls ) > 0f && !player.IsBeingShownCurseChoices;
var showRandom = GetStat( player, PlayerStat.CanChooseRandomPerk ) > 0f;
int numChoices = player.GetDisplayedPerkChoiceCount();
}
<root style="bottom: @(Manager.Instance.ShowBossHealthbar ? 50 : 10)px;">
@if ( showViewingLabel )
{
<div class="viewing_label">
<label>VIEWING CHOICES OF</label>
<div class="viewing_avatar" style="background-image: url( avatar:@player.Network.Owner.SteamId );"></div>
</div>
}
@if ( player.IsChoosingLevelUpReward )
{
@if ( player.NumPerkPointsAvailable > (player.IsBeingShownCurseChoices ? 0 : 1) )
{
@{
var left = 33 + (showBanish ? -8 : 0) + (showSkip ? -6 : 0) + (showRandom ? -5 : 0);
}
<div class="num_left" style="left:@(left)%;">
@{ var extraPerks = player.NumPerkPointsAvailable - 1; }
<label style="color: #ffffffbb; padding-right: 2px;">@($"{extraPerks}")</label>
<label style="color: #ffffff88; padding-left: 2px;">@($"MORE {(extraPerks > 1 ? "PERKS" : "PERK")}")</label>
</div>
}
<div class="itemselection @(numChoices > 17 ? "scrollable" : "")" style="gap: @(numChoices == 5 ? 6 : 12)px;">
@{
bool onlyShowIcons = numChoices > 5;
}
@for ( int i = 0; i < player.GetDisplayedPerkChoiceCount(); i++ )
{
var perkType = player.GetDisplayedPerkChoice( i );
var isUnknown = player.GetDisplayedPerkChoiceUnknown( i );
var index = i;
<PerkChoice ViewedPlayer=@player PerkType=@perkType IsChoice=@true OnlyShowIcon=@onlyShowIcons Slot=@i IsUnknown=@isUnknown onclick="@(e => PerkChosen( e, perkType, index ))" />
}
@if ( GetStat( player, PlayerStat.ChooseTimeLimit ) > 0f )
{
<div class="timelimitbar">
@{
var timeLimitProgress = Utils.Map( player.RealTimeSinceOfferedChoices, 0f, GetStat( player, PlayerStat.ChooseTimeLimit ), 0f, 1f );
}
<div style="width:@((1f - timeLimitProgress) * 100f)%; background-color:@(Color.Lerp(new Color(1f, 0.8f, 0.8f), new Color(1f, 0f, 0f), timeLimitProgress).Rgba);"></div>
</div>
}
</div>
<div class="lower_row">
<div class="button_outer">
@{
bool canUseArmor = GetStat( player, PlayerStat.ArmorRerollCost ) > 0f && player.Armor >= (int)GetStat( player, PlayerStat.ArmorRerollCost );
bool isFree = GetStat( player, PlayerStat.FreeRerollTime ) > 0f && player.RealTimeSinceLvlUp < GetStat( player, PlayerStat.FreeRerollTime );
bool canReroll = player.NumRerollAvailable > 0 || isFree || canUseArmor;
var autoRerollTimerProgress = player.GetDisplayedAutoRerollTimerProgress();
}
<panel class="button_container @((canReroll && !isReadOnly) ? "" : "disabled")" style="background-image: url(@("/textures/ui/panel/reroll_panel.png"));" onclick="@(e => RerollClicked( e ))">
@{
string amountText = isFree ? "∞" : (canUseArmor ? $"{(int)GetStat(player, PlayerStat.ArmorRerollCost)}⛊" : $"{player.NumRerollAvailable}");
int amountFontSize = canUseArmor ? 16 : 20;
}
<InputHint class="ctrl" Button="R" style="opacity: @((canReroll && !isReadOnly) ? 1f : 0.08f);"></InputHint>
<label class="button" style="color:@((canReroll && !isReadOnly) ? "#E8FFF4CC" : "#ffffff99");"></label>
<label class="amount" style="color:@((canReroll && !isReadOnly) ? "#88B19E" : "#ffffff99"); font-size:@($"{amountFontSize}px");">@amountText</label>
@if ( isFree )
{
@{
var freeRerollProgress = Utils.Map( player.RealTimeSinceLvlUp, 0f, GetStat( player, PlayerStat.FreeRerollTime ), 0f, 1f );
}
<div class="free_reroll_bar">
<div style="width:@((1f - freeRerollProgress) * 100f)%;"></div>
</div>
}
@if ( autoRerollTimerProgress > 0f && canReroll )
{
<div class="autoreroll_bar">
<div style="width:@((1f - autoRerollTimerProgress) * 100f)%;"></div>
</div>
}
</panel>
</div>
@if ( showBanish )
{
<div class="button_outer">
<panel class="button_container @((player.NumBanishAvailable > 0 && !isReadOnly) ? "" : "disabled")" style="background-image: url(@("/textures/ui/panel/banish_panel.png"));" onclick="@(e => BanishClicked( e ))">
<InputHint class="ctrl" Button="banish" style="opacity: @((player.NumBanishAvailable > 0 && !isReadOnly) ? 1f : 0.08f);"></InputHint>
<label class="button" style="color:@((player.NumBanishAvailable > 0 && !isReadOnly) ? "#E8FFF4CC" : "#ffffff99");"></label>
<label class="amount" style="color:@((player.NumBanishAvailable > 0 && !isReadOnly) ? "#88B19E" : "#ffffff99");">@player.NumBanishAvailable</label>
</panel>
</div>
}
@if ( showSkip )
{
<div class="button_outer">
<panel class="button_container @(isReadOnly ? "disabled" : "")" style="background-image: url(@("/textures/ui/panel/skip_panel.png"));" onclick="@(e => SkipClicked( e ))">
<label class="button" style="color:@(isReadOnly ? "#ffffff99" : "#E8FFF4CC"); width: 90px;"></label>
<label class="amount" style="font-size: 16px; color:@(!isReadOnly && (int)GetStat(player, PlayerStat.SkipPerkNumRerolls) > 0 ? "#88B19E" : "#ffffff55");">@($"+{(int)GetStat(player, PlayerStat.SkipPerkNumRerolls)}")</label>
</panel>
</div>
}
@if ( showRandom )
{
<div class="button_outer">
<panel class="button_container @(isReadOnly ? "disabled" : "")" style="background-image: url(@("/textures/ui/panel/random_panel.png"));" onclick="@(e => RandomClicked( e ))">
<label class="button" style="color:@(isReadOnly ? "#ffffff99" : "#E8FFF4CC"); width: 120px;"></label>
</panel>
</div>
}
</div>
}
</root>
@code {
private Player GetViewedPlayer()
{
var manager = Manager.Instance;
var player = manager.SelectedPlayer.IsValid()
? manager.SelectedPlayer
: manager.LocalPlayer;
if ( player.IsValid() && player.IsDead )
return null;
return player;
}
private bool IsReadOnly( Player player )
{
return player.IsValid() && player != Manager.Instance.LocalPlayer;
}
private Player GetInteractivePlayer()
{
var player = GetViewedPlayer();
return IsReadOnly( player ) ? null : player;
}
private float GetStat( Player player, PlayerStat stat )
{
return player.IsValid() ? player.GetUiStat( stat ) : 0f;
}
protected override void OnMouseOver( MousePanelEvent e )
{
Manager.Instance.IsHoveringPerkChoicePanel = true;
}
protected override void OnMouseOut( MousePanelEvent e )
{
Manager.Instance.IsHoveringPerkChoicePanel = false;
}
protected void PerkChosen( PanelEvent e, TypeDescription type, int index )
{
e.StopPropagation();
if ( Manager.Instance.IsGameOver || Manager.Instance.IsPaused )
return;
var player = GetInteractivePlayer();
if ( !player.IsValid() )
return;
if ( Manager.Instance.HoveredPerkType != null && Manager.Instance.IsHoveredPerkAChoice )
{
Manager.Instance.HoveredPerkType = null;
Manager.Instance.HoveredPerkViewedPlayer = null;
Manager.Instance.HoveredPerkChoiceSlot = -1;
}
if ( !player.CanInteractWithChoices )
return;
if ( player.IsBanishMode )
{
player.CanInteractWithChoices = false;
player.BanishPerkUIChoice( type );
Manager.Instance.PlaySfxUI( "banish_perk", pitch: Utils.Map( player.NumBanishAvailable, 0, 5, 1f, 1.3f, EasingType.QuadIn ), volume: 0.95f );
}
else
{
player.CanInteractWithChoices = false;
player.AddPerkUIChoice( type );
Manager.Instance.PlaySfxUI( "click", pitch: Utils.Map( player.NumPerkPointsAvailable, 0, 10, 1f, 0.8f, EasingType.QuadIn ), volume: 0.75f );
}
}
protected void RerollClicked( PanelEvent e )
{
e.StopPropagation();
var player = GetInteractivePlayer();
if ( !player.IsValid() )
return;
player.UseReroll();
}
protected void BanishClicked( PanelEvent e )
{
e.StopPropagation();
var player = GetInteractivePlayer();
if ( !player.IsValid() )
return;
player.ToggleBanish();
}
protected void SkipClicked( PanelEvent e )
{
e.StopPropagation();
var player = GetInteractivePlayer();
if ( !player.IsValid() )
return;
player.RefreshAfterChoosingPerk();
var perkType = TypeLibrary.GetType( typeof( PerkSkipChoices ) );
if ( player.HasPerk( perkType ) )
{
var skipPerk = player.GetPerk( perkType ) as PerkSkipChoices;
if ( skipPerk != null )
skipPerk.OnSkip();
}
}
protected void RandomClicked( PanelEvent e )
{
e.StopPropagation();
var player = GetInteractivePlayer();
if ( !player.IsValid() )
return;
var perkType = TypeLibrary.GetType( typeof( PerkRandomChoice ) );
if ( player.HasPerk( perkType ) )
{
var randomPerk = player.GetPerk( perkType ) as PerkRandomChoice;
if ( randomPerk != null )
randomPerk.OnRandom();
}
}
protected override int BuildHash()
{
var player = GetViewedPlayer();
if ( player is null || !player.IsValid() )
return 0;
bool canUseArmor = GetStat( player, PlayerStat.ArmorRerollCost ) > 0f && player.Armor >= (int)GetStat( player, PlayerStat.ArmorRerollCost );
bool isFree = GetStat( player, PlayerStat.FreeRerollTime ) > 0f && player.RealTimeSinceLvlUp < GetStat( player, PlayerStat.FreeRerollTime );
var timeLimitHash = GetStat( player, PlayerStat.ChooseTimeLimit ) > 0f ? player.RealTimeSinceOfferedChoices.Relative : 0f;
var freeRerollHash = isFree ? player.RealTimeSinceLvlUp.Relative : 0f;
var autoRerollTimerHash = player.GetDisplayedAutoRerollTimerProgress();
var lockBanishHash = HashCode.Combine(
player.NumBanishAvailable,
player.HasGottenBanish
);
int buttonsHash = HashCode.Combine(
player.NumRerollAvailable,
canUseArmor,
player.Armor,
isFree,
(int)GetStat( player, PlayerStat.SkipPerkNumRerolls ),
(int)GetStat( player, PlayerStat.CanChooseRandomPerk )
);
var panelStateHash = HashCode.Combine(
player.IsChoosingLevelUpReward,
player.NumPerkPointsAvailable,
player.GetDisplayedPerkChoiceCount(),
player.PerkChoiceHash,
player.IsBeingShownCurseChoices,
IsReadOnly( player )
);
return HashCode.Combine(
timeLimitHash,
freeRerollHash,
lockBanishHash,
buttonsHash,
panelStateHash,
HashCode.Combine(
autoRerollTimerHash,
Manager.Instance.ShowBossHealthbar,
Input.UsingController
)
);
}
}