ui/StatsScreen.razor

A Razor UI component (StatsScreen) that builds and renders the player stats panel. It gathers many player stats, formats them into StatDisplayData entries, handles scrolling, mouse wheel, and updates a displayed time when game over.

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

<root class="flat">
	@{
		var player = Manager.Instance.LocalPlayer;
		StatDatasToShow.Clear();

		if(!Networking.IsHost)
			StatDatasToShow.Add(new StatDisplayData("Ping", $"{Connection.Local.Ping}ms", _colorDefault, "ping", _iconColorDefault));

StatDatasToShow.Add(new StatDisplayData("DPS", $"{Player.DpsStat.ToString("0.#")}", _colorDefault, "dps", _iconColorDefault));
		StatDatasToShow.Add(new StatDisplayData("Kills", Player.GetStatStr(PlayerStat.NumEnemiesKilled), _colorDefault, "kill", _iconColorDefault));

		AddBulletDamageStat();
		AddDamageMultStat();
		AddNumProjectilesStat();
		AddAmmoStat();
		AddNumPierceStat();
		AddNumBounceStat();
		AddAttackSpeedStat();
		AddReloadSpeedStat();
		AddMoveSpeedStat();
		AddMaxHpStat();
		AddHpRegenStat();
		AddHealEffectivenessStat();
		AddHpPerKillStat();
		AddDodgeChanceStat();
	}

	<div class="title" onclick="@(e => TitleClicked(e))">
		@* <label style='font-family: Material Icons;'>@("bar_chart")</label> *@
		<InputHint class="ctrl" Button="Tab" AspectRatio=@(1.55f)></InputHint>
	</div>

	<div class="flat panel">
		<div class="flat column list_container">
			@foreach(var statType in player.StatsToDisplay)
			{
				bool isDefaultValue = !player.OriginalStatValues.ContainsKey(statType) || MathF.Abs(player.Stats[statType] - player.OriginalStatValues[statType]) < 0.01f;
				@* isDefaultValue = false; *@ // for debug

				if(!isDefaultValue || player.DefaultValueStatsToDisplay.Contains(statType))
				{
					string title = "";
					string amount = "";
					Color color = new Color(0.9f, 1f, 0.95f, 0.8f);
					string icon = "rule_folder";
					Color iconColor = _iconColorDefault;
					GetLabelForStat(statType, ref title, ref amount, ref color, ref icon, ref iconColor);

					// todo: cache this?
					StatDatasToShow.Add(new StatDisplayData(title, amount, color, icon, iconColor));

					// once a stat has been shown, keep showing even if it returns to default value
					player.DefaultValueStatsToDisplay.Add(statType);
				}
			}

			@for(int i = StartIndex; i < StartIndex + MAX_ROWS; i++) 
			{
				if(i < 0 || i >= StatDatasToShow.Count)
					continue;

				var data = StatDatasToShow[i];
				var nameColor = Color.Lerp(data.iconColor, Color.White, 0.5f);

				@* <div class="flat space-between list" style="background-color: @((i % 2 == 0 ? new Color(0,0,0,0.4f) : new Color(0,0,0,0.7f)).Rgba);"> *@
				<div class="flat space-between list">
					<div>
						@* <label class="icon" style="color:@(data.iconColor.Rgba);">@(data.icon)</label> *@
						<div class="icon_container">
							<label class="icon" style="background-color:@(data.iconColor.Rgba); mask-image:@($"url(/textures/ui/stats/{data.icon}.png)")"></label>
						</div>

						<label class="bold stat_name" style="color:@(nameColor.Rgba); font-size:@(data.title.Length > 15 ? Math.Round(Utils.Map(data.title.Length, 15, 36, 14f, 10f, EasingType.SineIn)) : 14)px;">@(data.title)</label>
					</div>

					<label class="bold stat_value" style="color:@(data.color.Rgba);">@(data.amount)</label>
				</div>
			}
		</div>

		@if(StatDatasToShow.Count > MAX_ROWS)
		{
			var totalRowHeight = StatDatasToShow.Count * 30f;
			@* var visibleRowHeight = MAX_ROWS * 30f; *@
			var heightPercent = (MAX_ROWS / (float)StatDatasToShow.Count);
			var topOffset = StartIndex * 30f * heightPercent;

			//Log.Info($"{MAX_ROWS} / {StatDatasToShow.Count} = {heightPercent}");

			<div class="scrollbar" style="top:@(topOffset)px; height:@(heightPercent * 100f)%;"></div>
		}
		else
		{
			StartIndex = 0;
		}
	</div>
</root>

@code {
	public struct StatDisplayData
	{
		public string title;
		public string amount;
		public Color color;
		public string icon;
		public Color iconColor;

		public StatDisplayData(string _title, string _amount, Color _color, string _icon, Color _iconColor)
		{
			title = _title;
			amount = _amount;
			color = _color;
			icon = _icon;
			iconColor = _iconColor;
		}
	}

	public Player Player { get; set; }
	public string TimeString { get; set; }

	public List<StatDisplayData> StatDatasToShow { get; set; } = new();
	public static int StartIndex { get; set; }
	private const int MAX_ROWS = 25;

	Color _colorDefault;
	Color _colorGood;
	Color _colorBad;
	Color _iconColorDefault;

	public StatsScreen()
	{
		Player = Manager.Instance.LocalPlayer;

		_colorDefault = new Color(0.9f, 1f, 0.95f, 0.8f);
		_colorGood = new Color(0f, 1f, 0f, 0.8f);
		_colorBad = new Color(1f, 0f, 0f, 0.8f);
		_iconColorDefault = new Color(0.91f, 1f, 0.96f, 0.5f);
	}

	public void OnRetry()
	{
		// SS2Game.RestartCmd();
	}

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

		//Log.Info($"StartIndex: {StartIndex}");

		if(Manager.Instance.IsGameOver)
		{
			TimeSpan t = TimeSpan.FromSeconds(Manager.Instance.GameOverTime);
			TimeString = t.TotalSeconds > 3600 ? t.ToString(@"hh\:mm\:ss") : t.ToString(@"mm\:ss");

			if(Input.Pressed("Retry") && Networking.IsHost)
			{
				OnRetry();
			}
		}
	}

	protected override int BuildHash()
	{
		int nthShotHash = Player.Stats[PlayerStat.NthShotNumBullets] > 0f ? ((int)Player.Stats[PlayerStat.TotalShotNum] % (int)Player.Stats[PlayerStat.NthShotReq] == 0 ? 1 : 2) : 0;

		return HashCode.Combine(
			StartIndex,
			Player.ModifierHash,
			HashCode.Combine(Player.GetDamageMultiplierDisplay(), Player.GetAttackSpeedMultiplier(),  Player.GetMoveSpeedMultiplier(), Player.GetReloadSpeedMultiplier(), Player.GetHpRegenAmount(forDisplay: true), Player.GetHealEffectiveness(), Player.GetDodgeChance()),
			Player.GetBulletDamage(isFromClip: true, isLastAmmo: Player.AmmoCount == 0, forDisplay: true),
			HashCode.Combine(Player.Stats[PlayerStat.ShotDamageMult], nthShotHash),
			Player.DpsStat,
			Player.DefaultValueStatsToDisplay.Count,
			Networking.IsHost ? 0 : Connection.Local.Ping
		);
	}

	void GetLabelForStat(PlayerStat statType, ref string title, ref string amount, ref Color color, ref string icon, ref Color iconColor)
	{
		switch(statType)
		{
			// case PlayerStat.MaxHp: title = "MAX HP"; amount = GetStatStr(statType, StatFormat.IntDiff); icon = "favorite"; break;
			// case PlayerStat.MaxHpDisplay: title = "MAX HP"; amount = GetStatStr(statType, StatFormat.IntDiff); icon = "favorite"; break;
			// case PlayerStat.DodgeChance: title = "DODGE CHANCE"; amount = GetStatStr(statType, StatFormat.Percentage); icon = "air"; break;
			case PlayerStat.DodgeExtraAttempts: title = "Extra Dodge Attempts"; amount = Player.GetStatStr(statType, StatFormat.Int); icon = "dodge_extra"; iconColor = new Color(0.5f, 0.5f, 1f); break;
			case PlayerStat.DodgeAoeDamagePercent: title = "Dodge AOE Damage"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "dodge_aoe"; iconColor = new Color(1f, 0.2f, 0f); break;
			case PlayerStat.DodgeHealChance: title = "Dodge Heal Chance"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "dodge_heal"; iconColor = new Color(0.5f, 1f, 0.5f); break;
			case PlayerStat.DodgeReloadChance: title = "Dodge Reload Chance"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "dodge_reload"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.InvulnAfterHurtTimeDisplay: title = "Recovery Invuln Time"; amount = Player.GetStatStr(statType, StatFormat.Seconds1); icon = "invulnerable"; iconColor = new Color(0.9f, 0.9f, 0f); break;
			case PlayerStat.DamageReductionPercent: title = "Damage Taken"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiffInverse); icon = "dmg_reduction"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.BacksideDamageReductionPercent: title = "Backside Damage Taken"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiffInverse); icon = "backstab"; iconColor = new Color(0.7f, 0f, 0f); break;
			// case PlayerStat.ExplosionDamageReductionPercent: title = "Explosion Damage Taken"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiffInverse); icon = "explosion_dmg_reduction"; iconColor = new Color(1f, 0.2f, 0f); break;
			case PlayerStat.FullHpDamageReductionPercent: title = "Full HP Damage Taken"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiffInverse); icon = "full_hp"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.SelfDmgReductionPercent: title = "Self Damage Taken"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiffInverse); icon = "self-dmg"; iconColor = new Color(1f, 0f, 1f); break;
			case PlayerStat.CritChance: title = "Bullet Crit Chance"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "crit"; iconColor = new Color(0.9f, 0.9f, 0.3f); break;
			case PlayerStat.CritMultiplier: title = "Crit Multiplier"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "crit_multiplier"; iconColor = new Color(0.7f, 0.7f, 0.5f); break;
			case PlayerStat.BulletHomingRadiusDisplay: title = "Bullet Homing Range"; amount = Player.GetStatStr(statType, StatFormat.Distance2); icon = "homing"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.BulletSplashChance: title = "Bullet Splash Chance"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "splash_chance"; iconColor = new Color(0.75f, 0.75f, 1f); break;
			case PlayerStat.BulletSplashDamagePercent: title = "Bullet Splash Damage"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "splash"; iconColor = new Color(0.5f, 0.5f, 1f); break;
			case PlayerStat.BulletOverflowPercent: title = "Bullet Overkill Damage"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "overkill"; iconColor = new Color(0.9f, 0.4f, 0.4f); break;
			case PlayerStat.BulletDamageGrow: title = "Bullet Damage Growth"; amount = Player.GetStatStr(statType, StatFormat.PerSecond1); icon = "bullet_grow"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.BulletDistanceDamage: title = "Bullet Distance Damage"; amount = Player.GetStatStr(statType, StatFormat.PerDistance1); icon = "sniper"; iconColor = new Color(0.15f, 0.15f, 0.9f); break;
			case PlayerStat.BulletHealTeammateAmount: title = "Bullet Heal Allies"; amount = Player.GetStatStr(statType, StatFormat.Decimals1Plus); icon = "bullet_heal"; iconColor = new Color(0.2f, 1f, 0.2f); break;
			// case PlayerStat.Friction: title = "FRICTION"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "roller_skating"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.NumDashes: title = "Max Dashes"; amount = Player.GetStatStr(statType, StatFormat.Int); icon = "dash"; iconColor = new Color(0.9f, 1f, 1f); break;
			case PlayerStat.DashCooldown: title = "Dash Cooldown"; amount = Player.GetStatStr(statType, StatFormat.Seconds1); icon = "dash_cooldown"; iconColor = new Color(1f, 0.5f, 0.3f); break;
			case PlayerStat.DashStrength: title = "Dash Strength"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "dash_strength"; iconColor = new Color(0.5f, 0.7f, 1f); break;
			// case PlayerStat.DashInvulnTime: title = "DASH DISTANCE"; amount = Player.GetStatStr(statType, StatFormat.Seconds2); icon = "timer"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.BulletForce: title = "Bullet Knockback"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "force"; iconColor = new Color(0.7f); break;
			case PlayerStat.Kickback: title = "Shot Kickback"; amount = Player.GetStatStr(statType, StatFormat.Int); icon = "recoil"; iconColor = new Color(1f, 1f, 0.9f); break;
			case PlayerStat.BulletSpread: title = "Bullet Spread"; amount = Player.GetStatStr(statType, Player.Stats[PlayerStat.ShootMultipleProjectilesParallel] > 0f ? StatFormat.PercentageDiff : StatFormat.Degrees); icon = "spread"; iconColor = new Color(1f, 0.9f, 0.9f); break;
			case PlayerStat.ShotInaccuracy: title = "Shot Inaccuracy"; amount = Player.GetStatStr(statType, StatFormat.Degrees); icon = "inaccuracy"; iconColor = new Color(0.6f, 0.6f, 0.6f); break;
			case PlayerStat.BulletLifetime: title = "Bullet Lifetime"; amount = Player.GetStatStr(statType, StatFormat.Seconds1); icon = "bullet_lifetime"; iconColor = new Color(0.5f, 0.5f, 1f); break;
			case PlayerStat.BulletSpeed: title = "Bullet Speed"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "bullet_speed"; iconColor = new Color(1f, 0.2f, 0.5f); break;
			case PlayerStat.NumPerkChoices: title = "Perk Choices"; amount = Player.GetStatStr(statType, StatFormat.Int); icon = "perk_choices"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.ExistingPerkChance: title = "Offer Existing Perks"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiff); icon = "perk_lvl"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.LevelUpExistingPerkChance: title = "Auto Choose Existing Perk"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "perk_auto_choose"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.RarityIncreaseCommon: title = "Common Perk Chance"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiff); icon = "perk_new"; iconColor = PerkManager.GetFontRarityColor(Rarity.Common); break;
			case PlayerStat.RarityIncreaseUncommon: title = "Uncommon Perk Chance"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiff); icon = "perk_new"; iconColor = PerkManager.GetFontRarityColor(Rarity.Uncommon); break;
			case PlayerStat.RarityIncreaseRare: title = "Rare Perk Chance"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiff); icon = "perk_new"; iconColor = PerkManager.GetFontRarityColor(Rarity.Rare); break;
			case PlayerStat.RarityIncreaseEpic: title = "Epic Perk Chance"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiff); icon = "perk_new"; iconColor = PerkManager.GetFontRarityColor(Rarity.Epic); break;
			case PlayerStat.RarityIncreaseMythic: title = "Mythic Perk Chance"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiff); icon = "perk_new"; iconColor = PerkManager.GetFontRarityColor(Rarity.Mythic); break;
			case PlayerStat.RarityIncreaseLegendary: title = "Legendary Perk Chance"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiff); icon = "perk_new"; iconColor = PerkManager.GetFontRarityColor(Rarity.Legendary); break;
			case PlayerStat.RarityIncreaseUnique: title = "Unique Perk Chance"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiff); icon = "perk_new"; iconColor = PerkManager.GetFontRarityColor(Rarity.Unique); break;
			// case PlayerStat.PerkChanceCommon: title = "COMMON PERK CHANCE"; amount = GetStatStr(statType, StatFormat.PercentageDecimals1); icon = "square"; iconColor = PerkManager.GetFontRarityColor(Rarity.Common, alpha: 1f); break;
			// case PlayerStat.PerkChanceUncommon: title = "UNCOMMON PERK CHANCE"; amount = GetStatStr(statType, StatFormat.PercentageDecimals1); icon = "looks_6"; iconColor = PerkManager.GetFontRarityColor(Rarity.Uncommon, alpha: 1f); break;
			// case PlayerStat.PerkChanceRare: title = "RARE PERK CHANCE"; amount = GetStatStr(statType, StatFormat.PercentageDecimals1); icon = "looks_5"; iconColor = PerkManager.GetFontRarityColor(Rarity.Rare, alpha: 1f); break;
			// case PlayerStat.PerkChanceEpic: title = "EPIC PERK CHANCE"; amount = GetStatStr(statType, StatFormat.PercentageDecimals1); icon = "looks_4"; iconColor = PerkManager.GetFontRarityColor(Rarity.Epic, alpha: 1f); break;
			// case PlayerStat.PerkChanceMythic: title = "MYTHIC PERK CHANCE"; amount = GetStatStr(statType, StatFormat.PercentageDecimals1); icon = "looks_3"; iconColor = PerkManager.GetFontRarityColor(Rarity.Mythic, alpha: 1f); break;
			// case PlayerStat.PerkChanceLegendary: title = "LEGENDARY PERK CHANCE"; amount = GetStatStr(statType, StatFormat.PercentageDecimals1); icon = "looks_two"; iconColor = PerkManager.GetFontRarityColor(Rarity.Legendary, alpha: 1f); break;
			// case PlayerStat.PerkChanceUnique: title = "UNIQUE PERK CHANCE"; amount = GetStatStr(statType, StatFormat.PercentageDecimals2); icon = "looks_one"; iconColor = PerkManager.GetFontRarityColor(Rarity.Unique, alpha: 1f); break;
			case PlayerStat.NumRerollsPerLevel: title = "Rerolls Per Level"; amount = Player.GetStatStr(statType, StatFormat.Int); icon = "reroll"; iconColor = new Color(1f); break;
			case PlayerStat.ArmorRerollCost: title = "Reroll Armor Cost"; amount = Player.GetStatStr(statType, StatFormat.Int); icon = "reroll_armor_cost"; iconColor = new Color(0.8f, 0.8f, 1f); break;
			case PlayerStat.RerollHealDisplay: title = "Reroll HP Gain"; amount = Player.GetStatStr(statType, StatFormat.Decimals1Plus); icon = "reroll_hp"; iconColor = new Color(0.8f, 1f, 0.8f); break;
			case PlayerStat.HealthPackExtraHp: title = "Healthpack Bonus HP"; amount = Player.GetStatStr(statType, StatFormat.IntDiff); icon = "healthpack_hp"; iconColor = new Color(0.4f, 1f, 1f); break;
			case PlayerStat.CoinAttractRange: title = "Item Attract Range"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "coin_attract"; iconColor = new Color(0.6f, 0.6f, 1f); break;
			// case PlayerStat.CoinAttractStrength: title = "ITEM ATTRACT STRENGTH"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "blank"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.XpGainMultiplier: title = "XP Gain"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "xp_gain"; iconColor = new Color(0.5f, 0.3f, 1f); break;
			case PlayerStat.XpDamageDisplay: title = "Coin AOE Damage"; amount = Player.GetStatStr(statType, StatFormat.Decimals1); icon = "xp_dmg"; iconColor = new Color(0.4f, 0.4f, 1f); break;
			case PlayerStat.CoinHealAmount: title = "Coin HP Gain"; amount = Player.GetStatStr(statType, StatFormat.Decimals1); icon = "xp_hp"; iconColor = new Color(0f, 1f, 0.5f); break;
			case PlayerStat.PushStrength: title = "Push Strength"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "push_strength"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.TurnSpeed: title = "Turn Speed"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "turn_speed"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			// case PlayerStat.DetectionRangeModifier: title = "ENEMY DETECTION RANGE"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "visibility"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.ShootFireIgniteChance: title = "Fire Bullet Chance"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "fire_bullet"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.FireDamage: title = "Fire Damage"; amount = Player.GetStatStr(Player.Stats[statType], StatFormat.Decimals1); icon = "fire"; iconColor = new Color(1f, 0.25f, 0f); break;
			case PlayerStat.FireLifetime: title = "Fire Time"; amount = Player.GetStatStr(statType, StatFormat.Seconds1); icon = "fire_lifetime"; iconColor = new Color(1f, 0.5f, 0.25f); break;
			case PlayerStat.FireSpreadChance: title = "Fire Spread Chance"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "fire_spread"; iconColor = new Color(1f, 0.2f, 0.6f); break;
			case PlayerStat.ShootFreezeChance: title = "Freeze Bullet Chance"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "freeze_bullet"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.FreezeLifetime: title = "Freeze Time"; amount = Player.GetStatStr(statType, StatFormat.Seconds1); icon = "freeze_lifetime"; iconColor = new Color(0.8f, 0.8f, 1f); break;
			case PlayerStat.FreezeStrengthDisplay: title = "Freeze Strength"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiff); icon = "freeze"; iconColor = new Color(0.7f, 0.7f, 1f); break;
			case PlayerStat.FreezeOnMeleeCooldownDisplay: title = "Touch Freeze Cooldown"; amount = Player.GetStatStr(statType, StatFormat.Seconds1); icon = "touch_freeze"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.FreezeFireDamageMultiplier: title = "Fire Damage To Frozen"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "fire_freeze"; iconColor = new Color(0.8f, 0.5f, 1f); break;
			case PlayerStat.ShootPoisonChance: title = "Poison Bullet Chance"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "poison_bullet"; iconColor = new Color(0.3f,1f, 0.3f); break;
			case PlayerStat.PoisonDamage: title = "Poison Damage"; amount = Player.GetStatStr(Player.Stats[statType], StatFormat.Decimals1); icon = "droplet"; iconColor = new Color(0f, 1f, 0f); break;
			case PlayerStat.PoisonFinishDamagePercent: title = "Remove Poison Damage"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "poison_lose"; iconColor = new Color(0f, 0.7f, 0f); break;
			case PlayerStat.PoisonDieSpreadChance: title = "Poison Spread Chance"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "poison_spread"; iconColor = new Color(0.4f, 1f, 0.4f); break;
			case PlayerStat.FearLifetime: title = "Fear Time"; amount = Player.GetStatStr(statType, StatFormat.Seconds1); icon = "fear_lifetime"; iconColor = new Color(0.6f, 0.3f, 0.9f); break;
			case PlayerStat.FearDamageMultiplier: title = "Fear Damage"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "fear_dmg"; iconColor = new Color(0.7f, 0.4f, 1f); break;
			case PlayerStat.FearOnMeleeCooldownDisplay: title = "Melee Fear Cooldown"; amount = Player.GetStatStr(statType, StatFormat.Seconds1); icon = "fear_melee"; iconColor = new Color(0.8f, 0.3f, 0.7f); break;
			case PlayerStat.FearDamageReductionPercent: title = "Fear Damage Taken"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiffInverse); icon = "fear_dmg_reduction"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			// case PlayerStat.DelayedRecoveryPercentDisplay: title = "DELAYED RECOVERY HP"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "blank"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			// case PlayerStat.DelayedRecoveryDelayDisplay: title = "DELAYED RECOVERY TIME"; amount = Player.GetStatStr(statType, StatFormat.Seconds1); icon = "blank"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.DashSlashBulletDamagePercent: title = "Dash Slash Damage"; amount = Player.GetStatStr(statType, StatFormat.Percentage); icon = "dash_slash"; iconColor = new Color(0.75f, 0.75f, 1f); break;
			case PlayerStat.ExplosionDamageMultiplier: title = "Explosion Damage"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "explosion"; iconColor = new Color(0.75f, 0f, 0f); break;
			case PlayerStat.ExplosionSizeMultiplier: title = "Explosion Size"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "explosion_size"; iconColor = new Color(0.75f, 0.3f, 0f); break;
			case PlayerStat.RadiusMultiplier: title = "Area Radius"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "area_radius"; iconColor = new Color(0.7f, 0.7f, 1f); break;
			case PlayerStat.CameraDistance: title = "Camera Distance"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "camera"; iconColor = new Color(0.9f, 0.9f, 0.9f); break;
			case PlayerStat.Scale: title = "Body Size"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "body"; iconColor = new Color(0.9f, 0.9f, 0.5f); break;
			case PlayerStat.HealthyUnitDamagePercent: title = "Damage To Full HP Enemy"; amount = Player.GetStatStr(statType, StatFormat.PercentageDiff); icon = "dmg_to_full_hp"; iconColor = new Color(0.7f, 0.9f, 0.8f); break;
			case PlayerStat.BackstabBonusDamagePercent: title = "Backstab Damage"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiff); icon = "backstab"; iconColor = new Color(1f, 0.9f, 0.6f); break;
			case PlayerStat.AlternatePlayerDamagePercent: title = "Tag Team Damage"; amount = Player.GetStatStr(statType, StatFormat.PercentageAddDiff); icon = "tag_team"; iconColor = new Color(0.9f, 1f, 0.9f); break;

				// published_with_changes
				// local_police
		}

		if(statType == PlayerStat.FireDamage || statType == PlayerStat.PoisonDamage)
		{
			var current = Player.Stats[statType];// * Player.GetDamageMultiplierDisplay();
			var original = Player.GetOriginalStatValue(statType);

			if(current != original)
				color = (current < original) ? _colorBad : _colorGood;
		}


		// else if( statType == PlayerStat.PerkChanceCommon ) { color = Player.Stats[PlayerStat.RarityIncreaseCommon] < Player.OriginalStatValues[PlayerStat.RarityIncreaseCommon] ? _colorGood : Player.Stats[PlayerStat.RarityIncreaseCommon] > Player.OriginalStatValues[PlayerStat.RarityIncreaseCommon] ? _colorBad : _colorDefault; }
		// else if( statType == PlayerStat.PerkChanceUncommon ) { color = Player.Stats[PlayerStat.RarityIncreaseUncommon] > Player.OriginalStatValues[PlayerStat.RarityIncreaseUncommon] ? _colorGood : Player.Stats[PlayerStat.RarityIncreaseUncommon] < Player.OriginalStatValues[PlayerStat.RarityIncreaseUncommon] ? _colorBad : _colorDefault; }
		// else if( statType == PlayerStat.PerkChanceRare ) { color = Player.Stats[PlayerStat.RarityIncreaseRare] > Player.OriginalStatValues[PlayerStat.RarityIncreaseRare] ? _colorGood : Player.Stats[PlayerStat.RarityIncreaseRare] < Player.OriginalStatValues[PlayerStat.RarityIncreaseRare] ? _colorBad : _colorDefault; }
		// else if( statType == PlayerStat.PerkChanceEpic ) { color = Player.Stats[PlayerStat.RarityIncreaseEpic] > Player.OriginalStatValues[PlayerStat.RarityIncreaseEpic] ? _colorGood : Player.Stats[PlayerStat.RarityIncreaseEpic] < Player.OriginalStatValues[PlayerStat.RarityIncreaseEpic] ? _colorBad : _colorDefault; }
		// else if( statType == PlayerStat.PerkChanceMythic ) { color = Player.Stats[PlayerStat.RarityIncreaseMythic] > Player.OriginalStatValues[PlayerStat.RarityIncreaseMythic] ? _colorGood : Player.Stats[PlayerStat.RarityIncreaseMythic] < Player.OriginalStatValues[PlayerStat.RarityIncreaseMythic] ? _colorBad : _colorDefault; }
		// else if( statType == PlayerStat.PerkChanceLegendary ) { color = Player.Stats[PlayerStat.RarityIncreaseLegendary] > Player.OriginalStatValues[PlayerStat.RarityIncreaseLegendary] ? _colorGood : Player.Stats[PlayerStat.RarityIncreaseLegendary] < Player.OriginalStatValues[PlayerStat.RarityIncreaseLegendary] ? _colorBad : _colorDefault; }
		// else if( statType == PlayerStat.PerkChanceUnique ) { color = Player.Stats[PlayerStat.RarityIncreaseUnique] > Player.OriginalStatValues[PlayerStat.RarityIncreaseUnique] ? _colorGood : Player.Stats[PlayerStat.RarityIncreaseUnique] < Player.OriginalStatValues[PlayerStat.RarityIncreaseUnique] ? _colorBad : _colorDefault; }
		else if(Player.OriginalStatValues.ContainsKey(statType))
		{
			var current = Player.Stats[statType];
			var original = Player.OriginalStatValues[statType];
			if (current < original)
				color = IsHigherGood(statType) ? _colorBad : _colorGood;
			else if (current > original)
				color = IsHigherGood(statType) ? _colorGood : _colorBad;
		}
	}

	bool IsHigherGood(PlayerStat statType)
	{
		if (statType == PlayerStat.DashCooldown ||
			statType == PlayerStat.FreezeTimeScale ||
			statType == PlayerStat.Friction ||
			statType == PlayerStat.ShotInaccuracy ||
			// statType == PlayerStat.DetectionRangeModifier ||
			statType == PlayerStat.RarityIncreaseCommon ||
			statType == PlayerStat.BulletSpread
		)
			return false;

		return true;
	}

	public override void OnMouseWheel([Description("The scroll wheel delta. Positive values are scrolling down, negative - up.")] Vector2 value)
	{
		base.OnMouseWheel(value.y);

		//Log.Info($"OnMouseWheel {value} -> {StartIndex} .... {StatDatasToShow.Count} .... {StatDatasToShow.Count - MAX_ROWS}");

		if(StatDatasToShow.Count > MAX_ROWS)
			StartIndex = Math.Clamp(StartIndex + (int)value.y, 0, StatDatasToShow.Count - MAX_ROWS);
	}

	public void AddCombinedStat(string name, float current, float original, string icon, StatFormat displayType, Color iconColor)
	{
		string str = Player.GetStatStr(current, displayType, original);
		Color color;
		if(current > original)
			color = _colorGood;
		else if(current < original)
			color = _colorBad;
		else
			color = _colorDefault;

		StatDatasToShow.Add(new StatDisplayData(name, str, color, icon, iconColor));
	}

	public void AddBulletDamageStat()
	{
		float bulletDamage = Player.GetBulletDamage(isFromClip: true, isLastAmmo: Player.AmmoCount == 0, forDisplay: true);

		if(Player.Stats[PlayerStat.NextBulletDamageMult] > 0f)
			bulletDamage *= Player.Stats[PlayerStat.NextBulletDamageMult];

		bulletDamage *= Player.Stats[PlayerStat.ShotDamageMult];

		if(Player.Stats[PlayerStat.BulletRandomDamagePercentMin] > 0f)
			StatDatasToShow.Add(new StatDisplayData("Bullet Damage", $"{(bulletDamage * Player.Stats[PlayerStat.BulletRandomDamagePercentMin]).ToString("0.#")}-{(bulletDamage * Player.Stats[PlayerStat.BulletRandomDamagePercentMax]).ToString("0.#")}", _colorGood, "circle", _iconColorDefault));
		else
			AddCombinedStat("Bullet Damage", bulletDamage, 5f, "bullet", StatFormat.Decimals1, new Color(1f));
	}

	void AddDamageMultStat()
	{
		var mult = Player.GetDamageMultiplierDisplay();
		if (mult == 1f && !Player.DefaultValueStatsToDisplay.Contains(PlayerStat.OverallDamageMultiplier))
			return;

		AddCombinedStat("Damage Multiplier", mult, 1f, "dmg_multiplier", StatFormat.PercentageDiff, new Color(1f, 0.6f, 0.6f));
		Player.DefaultValueStatsToDisplay.Add(PlayerStat.OverallDamageMultiplier);
	}

	public void AddNumProjectilesStat()
	{
		var num = Player.Stats[PlayerStat.NumProjectiles];

		if (Player.Stats[PlayerStat.NthShotNumBullets] > 0f && (int)Player.Stats[PlayerStat.TotalShotNum] % (int)Player.Stats[PlayerStat.NthShotReq] == 0)
			num += Player.Stats[PlayerStat.NthShotNumBullets];

		var numExtra = Player.Stats[PlayerStat.ExtraProjectileNum];
		Color color = (num > 1 || numExtra > 0) ? _colorGood : (num < 1 ? _colorBad : _colorDefault);

		if (numExtra > 0f)
			StatDatasToShow.Add(new StatDisplayData("Bullets Per Shot", $"{num}-{num + numExtra}", color, "num_projectiles", _iconColorDefault));
		else
			StatDatasToShow.Add(new StatDisplayData("Bullets Per Shot", $"{num}", color, "num_projectiles", _iconColorDefault));
	}

	public void AddAmmoStat()
	{
		var num = Player.Stats[PlayerStat.MaxAmmoCount];
		Color color = num > 5 ? _colorGood : (num < 5 ? _colorBad : _colorDefault);

		StatDatasToShow.Add(new StatDisplayData("Ammo", $"{num}", color, "ammo", new Color(1f)));
	}

	public void AddNumPierceStat()
	{
		var num = Player.Stats[PlayerStat.BulletNumPiercing];
		var numExtra = Player.Stats[PlayerStat.BulletExtraPierceChance] > 0f ? 1 : 0;
		if(num + numExtra == 0 && !Player.DefaultValueStatsToDisplay.Contains(PlayerStat.BulletNumPiercing))
			return;

		Color color = (num > 0 || numExtra > 0) ? _colorGood : (num < 0 ? _colorBad : _colorDefault);

		if(numExtra > 0f)
			StatDatasToShow.Add(new StatDisplayData("Pierces", $"{num}-{num + numExtra}", color, "pierce", new Color(1f, 1f, 0.8f)));
		else
			StatDatasToShow.Add(new StatDisplayData("Pierces", $"{num}", color, "pierce", new Color(1f, 1f, 0.8f)));

		Player.DefaultValueStatsToDisplay.Add(PlayerStat.BulletNumPiercing);
	}

	public void AddNumBounceStat()
	{
		var num = Player.Stats[PlayerStat.BulletNumBouncing];
		var numExtra = Player.Stats[PlayerStat.BulletExtraBounceChance] > 0f ? (int)Player.Stats[PlayerStat.BulletExtraBounceAmount] : 0;
		if(num + numExtra == 0 && !Player.DefaultValueStatsToDisplay.Contains(PlayerStat.BulletNumBouncing))
			return;

		Color color = (num > 0 || numExtra > 0) ? _colorGood : (num < 0 ? _colorBad : _colorDefault);

		if(numExtra > 0f)
			StatDatasToShow.Add(new StatDisplayData("Bounces", $"{num}-{num + numExtra}", color, "bounce", new Color(0.92f, 0.8f, 1f)));
		else
			StatDatasToShow.Add(new StatDisplayData("Bounces", $"{num}", color, "bounce", new Color(0.92f, 0.8f, 1f)));

		Player.DefaultValueStatsToDisplay.Add(PlayerStat.BulletNumBouncing);
	}

	void AddAttackSpeedStat()
	{
		var mult = Player.GetAttackSpeedMultiplier();
		if(mult == 1f && !Player.DefaultValueStatsToDisplay.Contains(PlayerStat.AttackSpeed))
			return;

		AddCombinedStat("Attack Speed", mult, 1f, "attack_speed", StatFormat.PercentageDiff, new Color(1f, 0.6f, 0.6f));
		Player.DefaultValueStatsToDisplay.Add(PlayerStat.AttackSpeed);
	}

	void AddReloadSpeedStat()
	{
		var mult = Player.GetReloadSpeedMultiplier();
		if(mult == 1f && !Player.DefaultValueStatsToDisplay.Contains(PlayerStat.ReloadSpeed))
			return;

		AddCombinedStat("Reload Speed", mult, 1f, "reload", StatFormat.PercentageDiff, new Color(1f, 0.5f, 0.5f));
		Player.DefaultValueStatsToDisplay.Add(PlayerStat.ReloadSpeed);
	}

	void AddMoveSpeedStat()
	{
		var mult = Player.GetMoveSpeedMultiplier();
		if(mult == 1f && !Player.DefaultValueStatsToDisplay.Contains(PlayerStat.MoveSpeedMultiplier))
			return;

		AddCombinedStat("Move Speed", mult, 1f, "move", StatFormat.PercentageDiff, new Color(0.5f, 0.5f, 0.7f));
		Player.DefaultValueStatsToDisplay.Add(PlayerStat.MoveSpeedMultiplier);
	}

	void AddMaxHpStat()
	{
		var amount = Player.Stats[PlayerStat.MaxHp];
		if(amount == 100f && !Player.DefaultValueStatsToDisplay.Contains(PlayerStat.MaxHp))
			return;

		AddCombinedStat("Max Health", amount, 100f, "max_hp", StatFormat.IntDiff, new Color(1f, 0f, 0f));
		Player.DefaultValueStatsToDisplay.Add(PlayerStat.MaxHp);
	}

	void AddHpRegenStat()
	{
		var amount = Player.GetHpRegenAmount(forDisplay: true);
		if(amount == 0f && !Player.DefaultValueStatsToDisplay.Contains(PlayerStat.HpRegenDisplay))
			return;

		AddCombinedStat("Health Regen", amount, 0f, "hp_regen", StatFormat.PerSecond2, new Color(0f, 1f, 0f));
		Player.DefaultValueStatsToDisplay.Add(PlayerStat.HpRegenDisplay);
	}

	void AddHealEffectivenessStat()
	{
		var amount = Player.GetHealEffectiveness();
		if(amount == 1f && !Player.DefaultValueStatsToDisplay.Contains(PlayerStat.HealEffectiveness))
			return;

		AddCombinedStat("Healing Received", amount, 1f, "heal_effectiveness", StatFormat.PercentageAddDiff, new Color(0.7f, 1f, 0.7f));
		Player.DefaultValueStatsToDisplay.Add(PlayerStat.HealEffectiveness);
	}

	void AddHpPerKillStat()
	{
		var amount = Player.Stats[PlayerStat.HpRegenPerKillDisplay];
		if(amount == 0f && !Player.DefaultValueStatsToDisplay.Contains(PlayerStat.HpRegenPerKillDisplay))
			return;

		AddCombinedStat("Health Per Kill", amount, 0f, "vampire", StatFormat.Decimals1Plus, new Color(1f, 0.2f, 0.3f));
		Player.DefaultValueStatsToDisplay.Add(PlayerStat.HpRegenPerKillDisplay);
	}

	void AddDodgeChanceStat()
	{
		var chance = Player.GetDodgeChance();
		if (chance == 0f && !Player.DefaultValueStatsToDisplay.Contains(PlayerStat.DodgeChance))
			return;

		AddCombinedStat("Dodge Chance", chance, 0f, "dodge", StatFormat.Percentage, new Color(1f));
		Player.DefaultValueStatsToDisplay.Add(PlayerStat.DodgeChance);
	}

	void TitleClicked(PanelEvent e)
	{
		e.StopPropagation();

		Manager.Instance.LocalPlayer.ShouldShowStats = false;
	}
}