This commit is contained in:
Oscar
2025-06-10 23:24:50 +03:00
parent de85b85b13
commit 5c9be94aba
24 changed files with 1187 additions and 580 deletions

View File

@@ -9,4 +9,6 @@ public class BaseItemDefinition : GameResource
[ResourceType( "prefab" )] public GameObject Prefab { get; set; }
public Texture ImageTexture { get; set; }
public string ImageUrl { get; set; }
public int MaxCount { get; set; } = 1;
}

View File

@@ -36,34 +36,50 @@ public class Inventar
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 EquipItem( InventoryItem item )
{
if ( item.Definition is not IEquipable equipable ) return;
if ( item.Definition is not IEquipable equipable )
return;
// Если уже экипирован этот же предмет — снять его
if ( EquippedItems.ContainsValue( item ) )
{
UnEquipItem( item );
return;
}
else
// Если на этом слоте уже что-то есть — снять старый предмет
if ( EquippedItems.TryGetValue( equipable.Slot, out var oldItem ) )
{
EquippedItems[equipable.Slot] = item;
OnEquipped?.Invoke( item );
UnEquipItem( oldItem );
// Вернуть снятый предмет обратно в инвентарь, если его там нет
if ( !Items.Contains( oldItem ) )
Items.Add( oldItem );
}
// Экипировать новый предмет
EquippedItems[equipable.Slot] = item;
OnEquipped?.Invoke( item );
}
public void DropItem( InventoryItem item, Vector3 position )
{
var gO = item.Definition.Prefab.Clone( position );
if ( gO.Components.TryGet<InventoryItem>( out var inventoryItem ) )
{
inventoryItem.Count = item.Count;
}
gO.NetworkSpawn( null );
RemoveItem( item );
// Items.Remove( item );
// OnChanged?.Invoke();
}
public void UnEquipItem( InventoryItem item )
{
foreach ( var kvp in EquippedItems.Where( kvp => kvp.Value == item ).ToList() )

View File

@@ -4,7 +4,6 @@ namespace Sasalka;
public class InventoryItem : Component
{
public BaseItemDefinition Definition { get; set; }
public int Count { get; set; } = 1;
public int MaxCount { get; set; } = 1;
[Property] public BaseItemDefinition Definition { get; set; }
[Property] public int Count { get; set; } = 1;
}

View File

@@ -4,9 +4,12 @@
<root class="@( Inventar.IsInventoryOpen ? "" : "hidden" )">
<div class="inventory-panel">
@foreach ( var item in PlayerInventory.Items )
@if ( PlayerInventory.Items.Count > 0 )
{
<Sasalka.Ui.InventoryItem Item="@item" OnItemClick="@( UseItem )"/>
@foreach ( var item in PlayerInventory.Items )
{
<Sasalka.Ui.InventoryItem Item="@item" OnItemClick="@( UseItem )" OnItemRightClick="@( DropItem )"/>
}
}
</div>
</root>
@@ -21,6 +24,11 @@
Player?.Inventory?.EquipItem( item );
}
void DropItem( Sasalka.InventoryItem item )
{
Player?.Inventory?.DropItem( item, Player.CameraPivot.WorldPosition + Player.Camera.LocalRotation.Right * 50 );
}
protected override void OnUpdate()
{
if ( Input.Pressed( "Score" ) )

View File

@@ -3,8 +3,10 @@
@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/>
<root class="inventory-item @( Equipped ? "equipped" : "" )" @onclick="@(() => OnItemClick?.Invoke( Item ))" @onrightclick=@( () => OnItemRightClick?.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"/>
@@ -16,12 +18,17 @@
<div class="inventory-item__name">@Item?.Definition.Name</div>
<div class="inventory-item__count">@Item?.Count / @Item?.MaxCount</div>
@if ( Item?.Definition.MaxCount > 1 )
{
<div class="inventory-item__count">@Item?.Count / @Item?.Definition.MaxCount</div>
}
</root>
@code {
public Sasalka.InventoryItem Item { get; set; }
public Action<Sasalka.InventoryItem> OnItemClick { get; set; }
public Action<Sasalka.InventoryItem> OnItemRightClick { get; set; }
public bool Equipped { get; set; }
protected override int BuildHash()

View File

@@ -6,8 +6,7 @@ public abstract class AmmoUseableBase : UseableBase
private InventoryItem FindAmmoItem()
{
// var ammoDefinition = new InventoryItemDefinition();
//По типу патрон поиск + енум типа патрон
return Dedugan.Local.Inventory.Items.FirstOrDefault( i => i.Definition.Name == "Pistol Ammo" );
}
@@ -28,7 +27,6 @@ public abstract class AmmoUseableBase : UseableBase
if ( ammo != null )
{
ammo.Count--;
Log.Info( $"[AmmoUseableBase] Ammo left: {ammo.Count}" );
if ( ammo.Count <= 0 )
{

View File

@@ -0,0 +1,29 @@
using Sandbox.Gravity;
using Sasalka;
namespace Sandbox.UI;
[Icon( "skip_next" )]
public sealed class PickupItem : InteractionButton
{
[Property] public override string Label { get; set; } = "E";
public override bool Press( IPressable.Event e )
{
base.Press( e );
if ( e.Source.Components.TryGet<Dedugan>( out var dedugan ) )
{
dedugan.Inventory.AddItem( Components.Get<InventoryItem>() );
RpcDestroy();
}
return true;
}
[Rpc.Broadcast]
void RpcDestroy()
{
GameObject.Destroy();
}
}

View File

@@ -2,14 +2,17 @@
public static class UseSystem
{
public static void TryUse( IUseContext context )
public static bool TryUse( IUseContext context )
{
foreach ( var useable in context.GetUsables() )
{
if ( useable.CanUse() )
{
useable.Use();
return true;
}
}
return false;
}
}

View File

@@ -6,11 +6,38 @@ public abstract class UseableBase : Component, IUseable
{
[Property] public float Cooldown { get; set; } = 0.5f;
private TimeSince _timeSinceUsed;
private bool _equipped;
protected override void OnStart()
[Property]
public bool Equipped
{
get => _equipped;
set
{
if ( _equipped != value )
{
_equipped = value;
if ( _equipped )
{
OnEquip?.Invoke();
}
}
}
}
private TimeSince _timeSinceUsed;
public Action OnEquip { get; set; }
protected override void OnAwake()
{
_timeSinceUsed = Cooldown;
OnEquip += OnEquipped;
}
public virtual void OnEquipped()
{
Log.Info( $"OnEquip {this}" );
}
public virtual bool CanUse()

View File

@@ -13,18 +13,9 @@ public sealed partial class Dedugan : Component
[Sync] public bool CameraTraceIsHit { get; set; }
public Vector3 TracedHitNormal { get; set; }
// private TimeSince HoldTime = 0;
// private bool Holding = false;
// private bool HoldingInteractionHappened = false;
void InteractionsUpdate()
{
if ( !EnablePressing )
{
// Holding = false;
// HoldingInteractionHappened = false;
return;
}
if ( !EnablePressing ) return;
if ( Pressed.IsValid() )
{
@@ -66,6 +57,8 @@ public sealed partial class Dedugan : Component
private void UpdateHovered()
{
if ( Pressed.IsValid() ) return;
SwitchHovered( TryGetLookedAt() );
if ( Hovered is IPressable pressable )
@@ -76,15 +69,19 @@ public sealed partial class Dedugan : Component
} );
}
if ( Input.Down( "use" ) )
if ( Input.Pressed( "use" ) )
{
StartPressing( Hovered );
}
}
public void StartPressing( Component obj )
{
if ( Pressed == obj ) return;
StopPressing();
if ( !obj.IsValid() )
{
ISceneEvent<PlayerController.IEvents>.PostToGameObject( GameObject, x => x.FailPressing() );
@@ -162,37 +159,30 @@ public sealed partial class Dedugan : Component
private Component TryGetLookedAt()
{
for ( float num = 0f; num <= 4f; num += 2f )
var from = Scene.Camera.WorldPosition + Scene.Camera.WorldRotation.Forward;
var to = from + Scene.Camera.WorldRotation.Forward * InteractDistance;
var trace = Scene.Trace
.Ray( from, to )
.IgnoreGameObjectHierarchy( GameObject )
.Run();
TracedHitPos = trace.Hit ? trace.HitPosition : trace.EndPosition;
CameraTraceIsHit = trace.Hit;
TracedHitNormal = trace.Normal;
if ( !trace.Hit || !trace.GameObject.IsValid() ) return null;
var go = trace.GameObject;
var pressable = go.Components.Get<IPressable>( FindMode.EnabledInSelfAndDescendants );
if ( pressable != null && pressable.CanPress( new IPressable.Event
{
Ray = new Ray( Camera.WorldPosition, EyeAngles.ToRotation().Forward ), Source = this
} ) )
{
var from = Scene.Camera.WorldPosition + Scene.Camera.WorldRotation.Forward;
var to = from + Scene.Camera.WorldRotation.Forward * (InteractDistance - num);
var trace = Scene.Trace.Ray( from, to ).IgnoreGameObjectHierarchy( GameObject ).Radius( num ).Run();
TracedHitPos = trace.Hit ? trace.HitPosition : trace.EndPosition;
CameraTraceIsHit = trace.Hit;
TracedHitNormal = trace.Normal;
if ( !trace.Hit || !trace.GameObject.IsValid() ) continue;
Component foundComponent = null;
ISceneEvent<PlayerController.IEvents>.PostToGameObject( GameObject, x =>
{
foundComponent = x.GetUsableComponent( trace.GameObject ) ?? foundComponent;
} );
if ( foundComponent.IsValid() ) return foundComponent;
foreach ( var component in trace.GameObject.Components.GetAll<IPressable>() )
{
if ( component.CanPress( new IPressable.Event
{
Ray = new Ray( Camera.WorldPosition, EyeAngles.ToRotation().Forward ), Source = this
} ) )
{
return component as Component;
}
}
return pressable as Component;
}
return null;

View File

@@ -16,29 +16,29 @@ public sealed partial class Dedugan : Component
_resolver = new AttachmentSlotResolver( Renderer.GetAttachmentObject );
Inventory.AddItem( new InventoryItem
{
Definition = ResourceLibrary.Get<ClothingItemDefinition>( "Items/cloth_pijama.clitem" )
} );
// Inventory.AddItem( new InventoryItem
// {
// Definition = ResourceLibrary.Get<BaseItemDefinition>( "Items/shorts_1.inv" )
// Definition = ResourceLibrary.Get<ClothingItemDefinition>( "Items/cloth_pijama.clitem" )
// } );
Inventory.AddItem( new InventoryItem
{
Definition = ResourceLibrary.Get<WeaponItemDefinition>( "Items/pistol_test.weapon" )
} );
var ammo = new InventoryItem
{
Definition = ResourceLibrary.Get<BaseItemDefinition>( "Items/pistol_ammo.inv" )
};
ammo.Count = 30;
ammo.MaxCount = 130;
Inventory.AddItem( ammo );
//
// Inventory.AddItem( new InventoryItem
// {
// Definition = ResourceLibrary.Get<ClothingItemDefinition>( "Items/cloth_pijama_bottom.clitem" )
// } );
//
// Inventory.AddItem( new InventoryItem
// {
// Definition = ResourceLibrary.Get<WeaponItemDefinition>( "Items/pistol_test.weapon" )
// } );
//
// var ammo = new InventoryItem
// {
// Definition = ResourceLibrary.Get<BaseItemDefinition>( "Items/pistol_ammo.inv" )
// };
// ammo.Count = 30;
// ammo.MaxCount = 130;
//
// Inventory.AddItem( ammo );
Inventory.OnEquipped += OnItemEquipped;
Inventory.OnUnEquipped += OnItemUnEquipped;
@@ -70,6 +70,8 @@ public sealed partial class Dedugan : Component
go.LocalPosition = weaponDef.WeaponDefinition.Position;
go.LocalRotation = weaponDef.WeaponDefinition.Rotation;
go.Components.Get<UseableBase>().Equipped = true;
go.NetworkSpawn();
var hand = weaponDef.Slot switch
@@ -145,8 +147,10 @@ public sealed partial class Dedugan : Component
if ( Input.Pressed( "Attack1" ) )
{
UseSystem.TryUse( this );
Attack();
if ( UseSystem.TryUse( this ) )
{
Attack();
}
}
}

View File

@@ -4,7 +4,7 @@ namespace Sandbox.UI;
public abstract class InteractionButton : Component, Component.IPressable
{
[Property] public string Label { get; set; } = "Label";
[Property] public virtual string Label { get; set; } = "Label";
[Property] public bool RequiresHold { get; set; } = false;
private HoverInfoPanel _uiPanel;
private GameObject _interactionPanelPrefab { get; set; }
@@ -12,25 +12,24 @@ public abstract class InteractionButton : Component, Component.IPressable
protected override void OnStart()
{
base.OnStart();
_interactionPanelPrefab = GameObject.GetPrefab("prefabs/InteractionPanel.prefab");
Log.Info(_interactionPanelPrefab.Name);
Assert.True(_interactionPanelPrefab.IsValid(), $"No InteractionPanel prefab found for {GameObject.Name}!");
_interactionPanelPrefab = GameObject.GetPrefab( "prefabs/InteractionPanel.prefab" );
Assert.True( _interactionPanelPrefab.IsValid(), $"No InteractionPanel prefab found for {GameObject.Name}!" );
}
public virtual void Hover(IPressable.Event e)
public virtual void Hover( IPressable.Event e )
{
CreatePanel();
}
public virtual void Blur(IPressable.Event e)
public virtual void Blur( IPressable.Event e )
{
DestroyPanel();
}
public virtual void Look(IPressable.Event e)
public virtual void Look( IPressable.Event e )
{
if (_uiPanel != null)
if ( _uiPanel != null )
{
_uiPanel.ProgressionHold = 0f;
}
@@ -40,34 +39,39 @@ public abstract class InteractionButton : Component, Component.IPressable
{
return true;
}
public virtual bool Press(IPressable.Event e)
public virtual bool Press( IPressable.Event e )
{
if (_uiPanel != null)
// // Проверяем, что событие относится именно к этому объекту
// if ( e.Source.GameObject != this.GameObject )
// return false; // Игнорируем, событие для другого объекта
if ( _uiPanel != null )
{
_uiPanel?.SetPressed(true);
_uiPanel.SetPressed( true );
}
return true;
return false;
}
public virtual void Release(IPressable.Event e)
public virtual void Release( IPressable.Event e )
{
if (_uiPanel != null)
if ( _uiPanel != null )
{
_uiPanel?.SetPressed(false);
_uiPanel?.SetPressed( false );
}
}
protected void CreatePanel()
{
if (_uiPanel != null) return;
if ( _uiPanel != null ) return;
var panelGo = _interactionPanelPrefab.Clone();
panelGo.WorldPosition = WorldPosition;
panelGo.Parent = Scene;
_uiPanel = panelGo.Components.Get<HoverInfoPanel>();
if (_uiPanel != null)
if ( _uiPanel != null )
{
_uiPanel.InteractionString = Label;
_uiPanel.IsHoldInteraction = RequiresHold;
@@ -75,13 +79,13 @@ public abstract class InteractionButton : Component, Component.IPressable
}
}
protected async void DestroyPanel()
protected void DestroyPanel()
{
if (_uiPanel == null) return;
if ( _uiPanel == null ) return;
_uiPanel.GameObject.Destroy();
_uiPanel = null;
await Task.DelaySeconds(0.05f);
// await Task.DelaySeconds(0.05f);
}
}

View File

@@ -13,13 +13,20 @@ public sealed class TeleportMazeButton : InteractionButton
public override bool Press( IPressable.Event e )
{
base.Press( e );
if ( Maze.IsValid() )
{
Maze.RpcRequestMaze();
}
DoTeleport();
return true;
return false;
// if ( Maze.IsValid() )
// {
// Maze.RpcRequestMaze();
// }
// else
// {
// Log.Info( "pressed teleport maze" );
// return false;
// }
//
// DoTeleport();
// return true;
}
private async void DoTeleport()

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using Sandbox.UI;
using Sasalka;
namespace Sandbox.Weapons;
@@ -11,6 +12,7 @@ public sealed class Weapon : AmmoUseableBase
[Property] public GameObject bloodParticle { get; set; }
private SoundPointComponent _sound;
private Rigidbody _rigidbody;
protected override void OnStart()
{
@@ -18,6 +20,13 @@ public sealed class Weapon : AmmoUseableBase
_sound = GameObject.GetComponent<SoundPointComponent>( true );
}
public override void OnEquipped()
{
_rigidbody = GameObject.Components.Get<Rigidbody>();
_rigidbody.Enabled = false;
GameObject.Components.Get<PickupItem>().Enabled = false;
}
public void Attack()
{
AttackEffects();
@@ -87,13 +96,11 @@ public sealed class Weapon : AmmoUseableBase
_sound?.StartSound();
MuzzleLight.Enabled = true;
GunRenderer.Set( "Fire", true );
_ = AttackEffectsAsync();
}
private async Task AttackEffectsAsync()
{
await GameTask.DelaySeconds( 0.05f );
MuzzleLight.Enabled = false;
GameTask.DelaySeconds( 0.05f ).ContinueWith( ( _ ) =>
{
MuzzleLight.Enabled = false;
} );
}
protected override void OnUse()