This commit is contained in:
Oscar
2025-06-10 02:09:21 +03:00
parent 38754d2b59
commit 6e784491fd
34 changed files with 1429 additions and 574 deletions

View File

@@ -0,0 +1,22 @@
namespace Sasalka;
public class AttachmentSlotResolver
{
private readonly Func<string, GameObject> _attachmentGetter;
public AttachmentSlotResolver( Func<string, GameObject> attachmentGetter )
{
_attachmentGetter = attachmentGetter;
}
public GameObject GetSlotObject( Inventar.InventorySlot slot )
{
return slot switch
{
Inventar.InventorySlot.LeftHand => _attachmentGetter.Invoke( "hold_L" ),
Inventar.InventorySlot.RightHand => _attachmentGetter.Invoke( "hold_R" ),
Inventar.InventorySlot.Body => _attachmentGetter.Invoke( "forward_reference_modelspace" ),
_ => _attachmentGetter.Invoke( "forward_reference_modelspace" )
};
}
}

View File

@@ -0,0 +1,69 @@
namespace Sasalka;
public class Inventar
{
[Flags]
public enum InventorySlot
{
None = 0,
LeftHand = 1 << 0, // 1
RightHand = 1 << 1, // 2
Head = 1 << 2, // 4
Body = 1 << 3, // 8
Hands = 1 << 4, // 16
Bottom = 1 << 5, // 32
Feet = 1 << 6 // 64
}
public List<InventoryItem> Items { get; private set; } = new();
public static bool IsInventoryOpen = false;
// public Dictionary<InventorySlot, EquippedItem> EquippedItems { get; private set; } = new();
public Dictionary<InventorySlot, InventoryItem> EquippedItems { get; private set; } = new();
public event Action OnChanged;
public event Action<InventoryItem> OnEquipped;
public event Action<InventoryItem> OnUnEquipped;
// public class EquippedItem
// {
// public InventoryItem Item { get; set; }
// public GameObject SpawnedObject { get; set; }
// }
public void AddItem( InventoryItem item )
{
Items.Add( item );
OnChanged?.Invoke();
}
public void RemoveItem( InventoryItem item )
{
UnEquipItem( item );
Items.Remove( item );
OnChanged?.Invoke();
}
public void EquipItem( InventoryItem item )
{
if ( EquippedItems.ContainsValue( item ) )
{
UnEquipItem( item );
}
else
{
EquippedItems.Add( item.Definition.Slot, item );
OnEquipped?.Invoke( item );
}
}
public void UnEquipItem( InventoryItem item )
{
foreach ( var kvp in EquippedItems.Where( kvp => kvp.Value == item ).ToList() )
{
EquippedItems.Remove( kvp.Key );
}
OnUnEquipped?.Invoke( item );
}
}

View File

@@ -0,0 +1,15 @@
using Sandbox;
namespace Sasalka;
public class InventoryItem : Component
{
public InventoryItemDefinition Definition { get; set; }
public int Count { get; set; } = 1;
public int MaxCount { get; set; } = 1;
// public GameObject SpawnedObject { get; set; }
}
// public int Count { get; set; } = 1;
// public int MaxCount { get; set; } = 1;

View File

@@ -0,0 +1,9 @@
using Sandbox;
namespace Sasalka;
public class InventoryItemCountable : InventoryItem
{
// public int Count { get; set; } = 1;
// public int MaxCount { get; set; } = 1;
}

View File

@@ -0,0 +1,30 @@
using Sandbox.Citizen;
namespace Sasalka;
[GameResource( "Inventory Item Definition", "inv", "", Category = "Sasalka", Icon = "inventory_2" )]
public class InventoryItemDefinition : GameResource
{
public Inventar.InventorySlot Slot { get; set; }
public CitizenAnimationHelper.HoldTypes HoldType { get; set; } = CitizenAnimationHelper.HoldTypes.None;
public Texture ImageTexture { get; set; }
public string ImageUrl { get; set; }
public string ClothUrl { get; set; }
public string Name { get; set; }
public string Description { get; set; }
[ResourceType( "prefab" )] public GameObject Prefab { get; set; }
[InlineEditor, Space] public WeaponDefinition WeaponDefinition { get; set; }
}
public struct WeaponDefinition
{
// public CitizenAnimationHelper.Hand Hand { get; set; }
public Vector3 Position { get; set; }
public Rotation Rotation { get; set; }
}

View File

@@ -0,0 +1,43 @@
@using Sasalka
@inherits PanelComponent
@namespace Sasalka.Ui
<root class="@( Inventar.IsInventoryOpen ? "" : "hidden" )">
<div class="inventory-panel">
@foreach ( var item in PlayerInventory.Items )
{
<Sasalka.Ui.InventoryItem Item="@item" OnItemClick="@( UseItem )"/>
}
</div>
</root>
@code {
Dedugan Player => Dedugan.Local;
Inventar PlayerInventory => Player?.Inventory;
void UseItem( Sasalka.InventoryItem item )
{
Player?.Inventory?.EquipItem( item );
}
protected override void OnUpdate()
{
if ( Input.Pressed( "Score" ) )
{
Inventar.IsInventoryOpen = !Inventar.IsInventoryOpen;
}
}
protected override int BuildHash()
{
if ( !Inventar.IsInventoryOpen || PlayerInventory == null )
return -1;
var hash = new HashCode();
hash.Add( Inventar.IsInventoryOpen );
return hash.ToHashCode();
}
}

View File

@@ -0,0 +1,34 @@
Inventory {
background: linear-gradient(135deg, #0a1a2b 0%, #08111f 100%);
border: 3px solid #2a3d54;
border-radius: 14px;
font-family: 'Orbitron', 'Poppins', sans-serif;
position: absolute;
width: 30%;
height: 96vh;
right: 20px;
top: 20px;
padding: 24px;
display: flex;
flex-direction: column;
gap: 20px;
transition: all 0.2s ease;
z-index: 100;
overflow: hidden;
pointer-events: all;
&.hidden {
opacity: 0;
}
}
.inventory-panel {
display: flex;
flex-direction: column;
gap: 12px;
}
.hidden {
opacity: 0;
}

View File

@@ -0,0 +1,38 @@
@using Sandbox.UI
@using Sasalka
@inherits Sandbox.UI.Panel
@namespace Sasalka.Ui
<root class="inventory-item @( Equipped ? "equipped" : "" )" @onclick="@(() => OnItemClick?.Invoke( Item ))">
<input type="checkbox" class="equipped-checkbox" checked="@Equipped" disabled/>
@if ( Item.Definition.ImageTexture.IsValid() )
{
<img src="@Item.Definition.ImageTexture.ResourcePath" alt="@Item.Definition.Name"/>
}
else if ( Item.Definition.ImageUrl.Length > 0 )
{
<img src="@Item.Definition.ImageUrl" alt="@Item.Definition.Name">
}
<div class="inventory-item__name">@Item?.Definition.Name</div>
<div class="inventory-item__count">@Item?.Count / @Item?.MaxCount</div>
</root>
@code {
public Sasalka.InventoryItem Item { get; set; }
public Action<Sasalka.InventoryItem> OnItemClick { get; set; }
public bool Equipped { get; set; }
protected override int BuildHash()
{
base.BuildHash();
var hash = new HashCode();
hash.Add( Item.Count );
return hash.ToHashCode();
}
}

View File

@@ -0,0 +1,41 @@
InventoryItem {
width: 100%;
//height: 64px;
background: #2a3d53;
display: flex;
gap: 24px;
align-items: center;
justify-content: flex-start;
border: 1px solid #666;
cursor: pointer;
transition: background 0.2s;
border-radius: 12px;
padding: 12px 24px;
position: relative;
&:hover {
background: #444;
}
img {
width: 32px;
height: 32px;
}
&.name {
font-size: 20px;
}
&.equipped {
border: 2px solid #4caf50;
background: #2e3e2e;
}
.equipped-checkbox {
position: absolute;
top: 6px;
left: 6px;
pointer-events: none;
accent-color: #4caf50;
}
}

View File

@@ -0,0 +1,39 @@
namespace Sasalka;
public abstract class AmmoUseableBase : UseableBase
{
protected InventoryItem AmmoItem => FindAmmoItem();
private InventoryItem FindAmmoItem()
{
// var ammoDefinition = new InventoryItemDefinition();
return Dedugan.Local.Inventory.Items.FirstOrDefault( i => i.Definition.Name == "Pistol Ammo" );
}
public override bool CanUse()
{
var ammo = AmmoItem;
return base.CanUse() && ammo != null && ammo.Count > 0;
}
public override void Use()
{
if ( !CanUse() )
return;
OnUse();
var ammo = AmmoItem;
if ( ammo != null )
{
ammo.Count--;
Log.Info( $"[AmmoUseableBase] Ammo left: {ammo.Count}" );
if ( ammo.Count <= 0 )
{
Dedugan.Local.Inventory.RemoveItem( ammo );
}
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Sasalka;
public interface IUseContext
{
public IEnumerable<IUseable> GetUsables();
}

View File

@@ -0,0 +1,8 @@
namespace Sasalka;
public interface IUseable
{
public void Use();
bool CanUse();
float Cooldown { get; }
}

View File

@@ -0,0 +1,15 @@
namespace Sasalka;
public static class UseSystem
{
public static void TryUse( IUseContext context )
{
foreach ( var useable in context.GetUsables() )
{
if ( useable.CanUse() )
{
useable.Use();
}
}
}
}

View File

@@ -0,0 +1,31 @@
using Sandbox;
namespace Sasalka;
public abstract class UseableBase : Component, IUseable
{
[Property] public float Cooldown { get; set; } = 0.5f;
private TimeSince _timeSinceUsed;
protected override void OnStart()
{
_timeSinceUsed = Cooldown;
}
public virtual bool CanUse()
{
return _timeSinceUsed >= Cooldown && !Inventar.IsInventoryOpen;
}
public virtual void Use()
{
if ( !CanUse() )
return;
OnUse();
_timeSinceUsed = 0;
}
protected abstract void OnUse();
}