core/player/PlayerInventory.cs
using Sandbox.UI;

public class PlayerInventory : Component {
	[Property, ReadOnly] public BasePlayer Owner {get; set;}
	public enum Slot {
		Unarmed,
		Primary,
	}
	[Property] public Slot ActiveSlot {get; set;} = Slot.Primary;
	[Property, ReadOnly] public Texture Interface {get; set;} = Texture.CreateRenderTarget().WithUAVBinding().WithSize(384,166).Create();
	public enum WeaponMode {
		Single,
		Reload,
	}
	[Property] public WeaponMode Mode {get; set;} = WeaponMode.Single;
	[Property, ReadOnly] public Dictionary<Slot, string> Equipped {get; set;} = new() {{Slot.Unarmed, "unarmed"}, {Slot.Primary, "1887"}};
	[Property, ReadOnly] public Dictionary<string, int> AmmoLoaded {get; set;} = new() {{"unarmed", 0}, {"1887", 6}};
	[Property, Range(-20,20)] public float Temperature {get; set;} = 20;

	private TimeUntil FreezeDialougeCooldown;
	private TimeUntil NextFreezeDamage;
	public bool NeedToResetFiringMode;
	public PlayerMouse.Mode LastMode;
	protected override void OnFixedUpdate() {
		var a = Owner.IsoEntity.Components.Get<NpcAnimator>();
		var m = Owner.IsoEntity.Components.Get<NpcMovement>();
		if (!a.IsValid() || !m.IsValid())
			return;
		//none of the temeperature or damage logic should be here but FUCK YOU FUCK YOU FUCK YOU FUCK YOU FUCK YOU
		if (!Owner.Isometric)
			Temperature = Math.Max(Temperature.Approach(20, Time.Delta * 5), Temperature);
		else if (HeatSource.IsWarm(Scene, Grid.Snap(m.EntityRoot)))
			Temperature = Temperature.Approach(20, Time.Delta * (Temperature > 10 ? 0.3f : 1));
		else {
			Temperature = Temperature.Approach(-20, Time.Delta * (m.Run && m.Path.Count > 0 ? 1.3f : 0.5f));
		}
		if (FreezeDialougeCooldown && Temperature < -12 && !HeatSource.IsWarm(Scene, Grid.Snap(m.EntityRoot))) {
			FreezeDialougeCooldown = 15f;
			Owner.IsoEntity.Components.GetOrCreate<NpcTalker>().Speak(Random.Shared.FromList(Owner.IsoEntity.TalkerScript.Freezing), 6);
		}
		if (NextFreezeDamage && Temperature < -15) {
			Owner.IsoEntity.Health -= Random.Shared.Int(1,3);
			if (Owner.IsoEntity.Health > 0 && !a.ActivitySet.NonInteruptActivities.Contains(a.Activity))
				a.Activity = "flinch";
			NextFreezeDamage = 3f;
		}
		if (Owner.IsoEntity.Health <= 0) {
			a.Activity = "die";
			Scene.GetAllComponents<PlayerDeath>().FirstOrDefault()?.Dead();
		}
		if (Input.Pressed("attack1") && Owner.Mouse.TargetButton == PlayerMouse.Button.Swap) {
			Sound.Play("sounds/interface/press_button.sound");
			if (ActiveSlot == Slot.Primary)
				ActiveSlot = Slot.Unarmed;
			else if (ActiveSlot == Slot.Unarmed)
				ActiveSlot = Slot.Primary;
			if (Equipped[ActiveSlot] == "1887") {
				m.Path.Clear();
				if (!a.ActivitySet.NonInteruptActivities.Contains(a.Activity))
					a.Activity = "1887_draw";
			}
			Mode = WeaponMode.Single;
		}
		if (Input.Pressed("attack2") && Owner.Mouse.TargetButton == PlayerMouse.Button.Weapon) {
			Sound.Play("sounds/interface/press_button.sound");
			if (Mode == WeaponMode.Reload)
				Mode = WeaponMode.Single;
			else
				Mode = (WeaponMode)((int)Mode + 1);
		}
		if (Input.Pressed("attack1") && Owner.Mouse.TargetButton == PlayerMouse.Button.Weapon) {
			if (Mode == WeaponMode.Reload) {
				if (!Reload(Equipped[ActiveSlot]))
					Sound.Play("sounds/interface/press_button.sound");
				return;
			}
			Sound.Play("sounds/interface/press_button.sound");
			if (ActiveSlot == Slot.Unarmed)
				return;
			if (AmmoLoaded[Equipped[ActiveSlot]] <= 0)
				Mode = WeaponMode.Reload;
			else {
				LastMode = Owner.Mouse.InteractMode;
				Owner.Mouse.InteractMode = PlayerMouse.Mode.Shoot;
				//NeedToResetFiringMode = LastMode != PlayerMouse.Mode.Shoot;
			}
		}
		if (Input.Pressed("attack1") && Owner.Mouse.TargetLayer != PlayerMouse.Layer.Interface && Owner.Mouse.InteractMode == PlayerMouse.Mode.Shoot) {
			if (NeedToResetFiringMode)
				Owner.Mouse.InteractMode = LastMode;
			NeedToResetFiringMode = false;
			if (ActiveSlot == Slot.Unarmed)
				return;
			if (Mode == WeaponMode.Reload) {
				Reload(Equipped[ActiveSlot]);
				return;
			}
			if (Owner.Mouse.HoveredEntity == Owner.IsoEntity)
				return;
			if (m.Path.Count > 0) {
				m.Path.Clear();
				return;
			}
			if (a.ActivitySet.NonInteruptActivities.Contains(a.Activity))
				return;
			if (AmmoLoaded[Equipped[ActiveSlot]] <= 0)
				Shoot(Equipped[ActiveSlot], Owner.Mouse.HoveredEntity);
			else {
				var pos = Owner.IsoCamera.MouseTarget();
				if (Owner.Mouse.HoveredEntity.IsValid()) {
					pos = Owner.Mouse.HoveredEntity.WorldPosition;
					if (Owner.Mouse.HoveredEntity.Components.TryGet<NpcMovement>(out var hm))
						pos = hm.EntityRoot;
				}
				pos = Grid.Snap(pos);
				var best = -1f;
				var angle = Rotation.Identity;
				for (int i = 0; i < 6; i++) {
					var canidate = Grid.Snap(m.EntityRoot + Rotation.FromYaw(i * 60f - 30f).Forward * 30f);
					var dot = Vector3.Direction(m.EntityRoot, canidate).Dot(Vector3.Direction(m.EntityRoot, pos));
					if (dot < best)
						continue;
					angle = Rotation.FromYaw(i * 60f - 30f);
					best = dot;
				}
				Owner.IsoEntity.WorldRotation = angle;
				Owner.IsoEntity.SnapToGrid();
				Owner.IsoEntity.Transform.ClearInterpolation();
				var ent = Owner.Mouse.HoveredEntity;
				a.AttackAction = () => Shoot(Equipped[ActiveSlot], ent);
				if (a.Activity != "die")
					a.Activity = "1887_fire"+Random.Shared.Int(1,3);
			}
		}
		RenderInterface();
		Owner.IsoEntity.Components.Get<NpcAnimator>().Weapon = Equipped[ActiveSlot];
		base.OnFixedUpdate();
	}

	public bool Reload(string weapon) {
		var max = 0;
		if (weapon == "1887")
			max = 6;
		//if (AmmoLoaded[weapon] == 0)
		//	max = Math.Max(max - 1, 0);
		if (AmmoLoaded[weapon] >= max)
			return false;
		AmmoLoaded[weapon] = max;
		Sound.Play("sounds/weapons/1887_reload.sound");
		Mode = WeaponMode.Single;
		return true;
	}

	private TimeUntil NextFire;
	private TimeUntil WarnAmmo;
	public void Shoot(string weapon, IsoEntity target) {
		if (!NextFire)
			return;
		NextFire = 0.5f;
		if (AmmoLoaded[weapon] <= 0) {
			WarnAmmo = 1f;
			Sound.Play("sounds/weapons/dry_fire.sound");
			return;
		}
		AmmoLoaded[weapon]--;
		Sound.Play("sounds/weapons/1887_fire.sound");
		if (target.IsValid()) {
			if (target.WorldPosition.Distance(Owner.IsoEntity.WorldPosition) > 600)
				return;
			if (target.WorldPosition.Distance(Owner.IsoEntity.WorldPosition) > 200)
				target.Health -= Random.Shared.Int(20,30);
			else
				target.Health -= Random.Shared.Int(30,60);
			if (target.Components.TryGet<NpcAnimator>(out var a)) {
				if (target.Health > 0)
					a.Activity = "flinch";
				else
					a.Activity = "die";
			}
			if (target.Components.TryGet<NpcWolfBrain>(out var br)) {
				br.ExitCombat = 5f;
			}
		}
	}

	public void RenderInterface() {
		var shader = new ComputeShader("shaders/cs_interface_draw.shader");
		shader.Attributes.Set("Base", Texture.Load("materials/interface/interface_base.vtex"));
		if (!WarnAmmo && AmmoLoaded[Equipped[ActiveSlot]] <= 0 && MathF.Sin(WarnAmmo.Relative * 15) > 0f)
			shader.Attributes.Set("Ammo", Texture.Load("materials/interface/interface_ammo_error.vtex"));
		else
			shader.Attributes.Set("Ammo", Texture.Load("materials/interface/interface_ammo_"+AmmoLoaded[Equipped[ActiveSlot]]+".vtex"));
		shader.Attributes.Set("Swap", Texture.Load("materials/interface/interface_swap_base.vtex"));
		if (Temperature < -12 && MathF.Sin(Time.Now * 15) > 0f)
			shader.Attributes.Set("Temp", Texture.Load("materials/interface/interface_temp_on.vtex"));
		else
			shader.Attributes.Set("Temp", Texture.Load("materials/interface/interface_temp_off.vtex"));
		shader.Attributes.Set("Thermo0", Texture.Load("materials/interface/interface_thermometer_empty.vtex"));
		shader.Attributes.Set("Thermo1", Texture.Load("materials/interface/interface_thermometer_full.vtex"));
		shader.Attributes.Set("ThermoFill", Temperature.Remap(-20f, 20f, 0.53f, 0.94f));
		shader.Attributes.Set("Unarmed", Equipped[ActiveSlot] == "unarmed");
		if (Mode == WeaponMode.Single)
			shader.Attributes.Set("WeapAp", Texture.Load("materials/interface/interface_weapon_ap_3.vtex"));
		else if (Mode == WeaponMode.Reload)
			shader.Attributes.Set("WeapAp", Texture.Load("materials/interface/interface_weapon_ap_2.vtex"));
		shader.Attributes.Set("WeapBase", Texture.Load("materials/interface/interface_weapon_base.vtex"));
		shader.Attributes.Set("WeapIcon", Texture.Load("materials/interface/interface_weapon_icon_1887.vtex"));
		shader.Attributes.Set("WeapMode", Texture.Load("materials/interface/interface_weapon_mode_"+Mode.ToString().ToLower()+".vtex"));
		shader.Attributes.Set("WeapSlot", Texture.Load("materials/interface/interface_weapon_slot_primary.vtex"));
		var text = new Bitmap(384,166);
		text.DrawText(new() {
			Text = Owner.IsoEntity.Health.Clamp(0,100).ToString("000"),
			TextColor = Owner.IsoEntity.Health > 10 ? Color.Green.Darken(0.3f) : Color.Red,
			FontSize = 12,
		}, new(new(34f,47), 80), TextFlag.LeftTop);
		text.DrawText(new() {
			Text = "ERR",
			TextColor = Color.Red.Darken(0.5f),
			FontSize = 12,
		}, new(new(34f,71), 80), TextFlag.LeftTop);
		shader.Attributes.Set("Text", text.ToTexture());
		shader.Attributes.Set("RenderSize", Interface.Size);
		shader.Attributes.Set("Result", Interface);
		shader.Dispatch(Interface.Width, Interface.Height, 1);
	}
}