ui/PerkIcon.razor

A UI Razor component that renders a perk icon for the game. It computes visual state (rotation, scale, opacity, progress, rarity tint, cooldown, highlights) and handles mouse interactions to show hover state and to allow click/right-click addon/removal perks via PerkClickAdd/PerkClickRemove.

Reflection
@namespace Sandbox
@using Sandbox;
@using Sandbox.UI;
@using System;
@inherits Panel
@attribute [StyleSheet("PerkIcon.razor.scss")]


@{
	var player = Manager.Instance.LocalPlayer;
	var rot = Angle + Perk.IconAngleOffset;
	var scale = Perk.IconScale;
	const float levelUpAnimDuration = 0.5f;
	var levelUpScale = Perk.RealTimeSinceLevelUp < levelUpAnimDuration
		? Utils.Map( Perk.RealTimeSinceLevelUp, 0f, levelUpAnimDuration, 1.75f, 1.0f, EasingType.QuadOut )
		: 1.0f;
}

<root style="transform: rotate(@(rot)deg) scale(@(scale * levelUpScale));">
	@{
		// float opacity = (SS2Game.Current.IsGameOver || SS2Game.Current.SelectedPlayer != null) ? 4f : Utils.Map(Item.TimeSinceLevelUp, 0f, 3f, 4f, 1f);
		float opacity = Utils.Map( Math.Min( Perk.RealTimeSinceLevelUp, Perk.RealTimeSinceLevelDown ), 0f, 3f, 4f, 1f);
		<!-- todo: cache these -->
		var attrib = TypeLibrary.GetType(Perk.GetType()).GetAttribute<PerkAttribute>();
		var rarity = attrib.Rarity;
		var curse = attrib.Curse;
		var highlightColor = PerkManager.GetCardRarityColor(rarity, curse, alpha: 0.7f);
		var rarityBgTint = PerkManager.GetCardRarityColor(rarity, curse);
		var width = Math.Clamp(Perk.Level / (float)Perk.MaxLevel, 0f, 1f) * 100f;
		var maxLevel = PerkManager.GetMaxLevelForRarity(rarity);
	}
	<panel class="rarity" style="opacity: @opacity; background-color: @highlightColor.Rgba; background-image-tint:@rarityBgTint.Rgba;"></panel>

	@if (!curse && maxLevel > 1)
	{
		// @if (Perk.Level > (IsChoice ? 1 : 0))
		// {
			<panel class="progress" style="width:100%; background-color:@(new Color(0f, 0f, 0f, IsChoice ? 0.3f : 0.6f).Rgba); z-index: 1;"></panel>
		// }
	
		<panel class="progress" style="width:@(width)%; background-color:@highlightColor.Rgba; z-index: 2;"></panel>
	}
	
	<panel class="icon" style="background-image: url(@(Perk.GetImagePath( Perk.GetType() ))); background-color:@rarityBgTint.Rgba; opacity: @opacity;" />

	@if (Perk.Level > Perk.MaxLevel)
	{
		<div class="maxed">X</div>
	}

	@if ( !IsChoice && Perk.DisplayCooldown > 0f) // && !Manager.Instance.IsGameOver
	{
		<div class="cooldown" style="width:@(Math.Clamp(Perk.DisplayCooldown, 0f, 1f) * 100f)%; opacity:@(Utils.Map(Parent.Opacity, 0.3f, 1f, 1.3f, 0.75f)); background-color:@(Perk.DisplayCooldownColor.Rgba);"></div>
	}

	@if ( !string.IsNullOrEmpty(Perk.DisplayText)) //&& !Manager.Instance.IsGameOver && SS2Game.Current.SelectedPlayer == null)
	{
		<label class="display_text" style="color:@(Perk.DisplayTextColor.Rgba); opacity:@Perk.DisplayTextOpacity">@Perk.DisplayText</label>
	}

	@if (Banished)
	{
		<div class="banish">X</div>
	}

	@if (Perk.RealTimeSinceHighlight < Perk.HighlightDuration)
	{
		<div class="highlight" style="opacity:@(Utils.Map( Perk.RealTimeSinceHighlight, 0f, Perk.HighlightDuration, Perk.HighlightOpacity, 0f, EasingType.SineOut)); background-color:@(Perk.HighlightColor.Rgba);"></div>
	}

	@if(Perk.RealTimeSinceLevelDown < 3f) 
	{
		<div class="deleveled" style="opacity:@(Utils.Map( Perk.RealTimeSinceLevelDown, 0f, 3f, 4f, 0f, EasingType.QuadOut));"></div>
	}
</root>

@code
{
	public Perk Perk { get; set; }
	public bool NoTips { get; set; } = false;
	public bool Banished;

	public float Angle { get; set; }
	public float Scale { get; set; }

	public bool IsChoice { get; set; }

	protected override void OnMouseOver(MousePanelEvent e)
	{
		if (e.Target != this || NoTips || Perk == null)
			return;

		Manager.Instance.HoveredPerkType = TypeLibrary.GetType( Perk.GetType() );
		Manager.Instance.HoveredPerkPanel = this;
		Manager.Instance.HoveredPerkLevel = Perk.Level;
		Manager.Instance.IsHoveredPerkBanished = Banished;
		Manager.Instance.IsHoveredPerkAChoice = IsChoice;
		Manager.Instance.IsHoveredPerkHidden = false;

		Manager.Instance.HoveredPlayer = null;
		Manager.Instance.HoveredPlayerIcon = null;
	}

	protected override void OnMouseOut(MousePanelEvent e)
	{
		if ( Manager.Instance.HoveredPerkPanel != this ) return;
		Manager.Instance.HoveredPerkType = null;
		Manager.Instance.HoveredPerkPanel = null;
		Manager.Instance.IsHoveredPerkBanished = false;
	}

	protected override void OnAfterTreeRender ( bool firstTime )
	{
		base.OnAfterTreeRender( firstTime );

		if ( !IsVisible && Manager.Instance.HoveredPerkPanel == this )
		{
			Manager.Instance.HoveredPerkType = null;
			Manager.Instance.HoveredPerkPanel = null;
			Manager.Instance.IsHoveredPerkBanished = false;
		}
	}

	protected override int BuildHash()
	{
		var fadeHash = Math.Min(Perk.RealTimeSinceLevelUp, Perk.RealTimeSinceLevelDown) < 3f ? RealTime.Now : 0f;
		var highlightHash = Perk.RealTimeSinceHighlight.Relative < Perk.HighlightDuration ? Perk.RealTimeSinceHighlight.Relative : 0f;
		var cooldownHash = Perk.DisplayCooldown > 0f ? HashCode.Combine(Perk.DisplayCooldown, Parent.Opacity, Perk.DisplayCooldownColor) : 0f;
		var textHash = !string.IsNullOrEmpty(Perk.DisplayText) ? HashCode.Combine(Perk.DisplayText, Perk.DisplayTextColor, Perk.DisplayTextOpacity) : 0f;
		var transformHash = HashCode.Combine(Angle, Perk.IconAngleOffset, Perk.IconScale);
		return HashCode.Combine(
			Perk.Level, 
			fadeHash, 
			textHash,
			Manager.Instance.IsGameOver, 
			cooldownHash, 
			highlightHash,
			transformHash
		);
	}

	protected override void OnClick(MousePanelEvent e)
	{
		// if non-local player is selected, do nothing
		if (Manager.Instance.SelectedPlayer.IsValid())
			return;

		var player = Manager.Instance.LocalPlayer;
		if ( !player.IsValid() )
			return;

		if( player.Stats[PlayerStat.ClickAddPerk] > 0f )
		{
			var perkType = TypeLibrary.GetType( typeof( PerkClickAdd ) );
			if ( player.HasPerk( perkType ) )
			{
				var perk = player.GetPerk( perkType ) as PerkClickAdd;
				if ( perk != null ) 
				{
					perk.Activate(TypeLibrary.GetType(Perk.GetType()));

					Manager.Instance.HoveredPerkType = null;
					Manager.Instance.IsHoveredPerkBanished = false;

					e.StopPropagation();
				}
			}
		}
	}

	protected override void OnRightClick(MousePanelEvent e)
	{
		// if non-local player is selected, do nothing
		if (Manager.Instance.SelectedPlayer.IsValid())
			return;

		var player = Manager.Instance.LocalPlayer;
		if ( !player.IsValid() )
			return;

		if( player.Stats[PlayerStat.ClickRemovePerk] > 0f )
		{
			var perkType = TypeLibrary.GetType( typeof( PerkClickRemove ) );
			if ( player.HasPerk( perkType ) )
			{
				var perk = player.GetPerk( perkType ) as PerkClickRemove;
				if ( perk != null ) 
				{
					perk.Activate(TypeLibrary.GetType(Perk.GetType()));

					Manager.Instance.HoveredPerkType = null;
					Manager.Instance.IsHoveredPerkBanished = false;

					e.StopPropagation();
				}
			}
		}

		// player.LevelDownPerk(TypeLibrary.GetType(Perk.GetType()));
	}
}