tst
This commit is contained in:
parent
495c672818
commit
235d5ad90a
@ -83,6 +83,7 @@ public class Inventar : Component
|
|||||||
_itemCache[item.Definition] = item;
|
_itemCache[item.Definition] = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_cacheDirty = false;
|
_cacheDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +117,10 @@ public class Inventar : Component
|
|||||||
while ( toAdd > 0 && (UnlimitedSlots || Items.Count < MaxInventorySlots) )
|
while ( toAdd > 0 && (UnlimitedSlots || Items.Count < MaxInventorySlots) )
|
||||||
{
|
{
|
||||||
int stackCount = Math.Min( toAdd, item.Definition.MaxCount );
|
int stackCount = Math.Min( toAdd, item.Definition.MaxCount );
|
||||||
var newStack = new InventoryItem { Definition = item.Definition, Count = stackCount, MagazineAmmo = item.MagazineAmmo };
|
var newStack = new InventoryItem
|
||||||
|
{
|
||||||
|
Definition = item.Definition, Count = stackCount, MagazineAmmo = item.MagazineAmmo
|
||||||
|
};
|
||||||
Items.Add( newStack );
|
Items.Add( newStack );
|
||||||
toAdd -= stackCount;
|
toAdd -= stackCount;
|
||||||
_cacheDirty = true; // Помечаем кэш как устаревший
|
_cacheDirty = true; // Помечаем кэш как устаревший
|
||||||
@ -193,22 +197,89 @@ public class Inventar : Component
|
|||||||
if ( item == null || !Items.Contains( item ) )
|
if ( item == null || !Items.Contains( item ) )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
GameObject gO = item.Definition.Prefab.Clone( position );
|
// Проверяем, является ли предмет одеждой
|
||||||
|
if ( item.Definition is ClothingItemDefinition clothingDef )
|
||||||
if ( gO.Components.TryGet<InventoryItem>( out var inventoryItem ) )
|
|
||||||
{
|
{
|
||||||
inventoryItem.Count = item.Count;
|
// Для одежды создаем специальный физический объект
|
||||||
inventoryItem.Definition = item.Definition;
|
DropClothingItem( item, position, clothingDef );
|
||||||
// Копируем патроны из оригинального предмета
|
|
||||||
inventoryItem.MagazineAmmo = item.MagazineAmmo;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Для остальных предметов используем стандартный префаб
|
||||||
|
if ( item.Definition.Prefab != null )
|
||||||
|
{
|
||||||
|
GameObject gO = item.Definition.Prefab.Clone( position );
|
||||||
|
|
||||||
gO.NetworkSpawn();
|
if ( gO.Components.TryGet<InventoryItem>( out var inventoryItem ) )
|
||||||
|
{
|
||||||
|
inventoryItem.Count = item.Count;
|
||||||
|
inventoryItem.Definition = item.Definition;
|
||||||
|
// Копируем патроны из оригинального предмета
|
||||||
|
inventoryItem.MagazineAmmo = item.MagazineAmmo;
|
||||||
|
}
|
||||||
|
|
||||||
|
gO.NetworkSpawn();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Warning( $"Префаб не найден для предмета: {item.Definition.Name}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Удаляем весь предмет из инвентаря
|
// Удаляем весь предмет из инвентаря
|
||||||
RemoveItem( item, item.Count );
|
RemoveItem( item, item.Count );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выбрасывает предмет одежды как физический объект
|
||||||
|
/// </summary>
|
||||||
|
private void DropClothingItem( InventoryItem item, Vector3 position, ClothingItemDefinition clothingDef )
|
||||||
|
{
|
||||||
|
// Пытаемся найти подходящий префаб для одежды
|
||||||
|
GameObject clothingPrefab = GameObject.GetPrefab( "prefabs/item_parcel.prefab" );
|
||||||
|
GameObject clothingObject = null;
|
||||||
|
|
||||||
|
clothingObject = clothingPrefab.Clone( position );
|
||||||
|
|
||||||
|
// Добавляем компонент InventoryItem
|
||||||
|
if ( clothingObject.Components.TryGet<InventoryItem>( out var inventoryItem ) )
|
||||||
|
{
|
||||||
|
inventoryItem.Count = item.Count;
|
||||||
|
inventoryItem.Definition = item.Definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем компонент PickupItem для подбора
|
||||||
|
if ( clothingObject.Components.TryGet<PickupItem>( out var pickupItem ) )
|
||||||
|
{
|
||||||
|
// Устанавливаем правильную метку для одежды
|
||||||
|
var slotName = GetSlotDisplayName( clothingDef.Slot );
|
||||||
|
pickupItem.Label = $"{clothingDef.Name} ({slotName})";
|
||||||
|
}
|
||||||
|
|
||||||
|
clothingObject.NetworkSpawn();
|
||||||
|
|
||||||
|
Log.Info( $"Выброшена одежда: {clothingDef.Name} ({clothingDef.Slot})" );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает отображаемое название слота
|
||||||
|
/// </summary>
|
||||||
|
private string GetSlotDisplayName( InventorySlot slot )
|
||||||
|
{
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
InventorySlot.LeftHand => "Л.Рука",
|
||||||
|
InventorySlot.RightHand => "П.Рука",
|
||||||
|
InventorySlot.Head => "Голова",
|
||||||
|
InventorySlot.Body => "Тело",
|
||||||
|
InventorySlot.Hands => "Руки",
|
||||||
|
InventorySlot.Bottom => "Ноги",
|
||||||
|
InventorySlot.Feet => "Обувь",
|
||||||
|
_ => "Неизвестно"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public void UnEquipItem( InventoryItem item )
|
public void UnEquipItem( InventoryItem item )
|
||||||
{
|
{
|
||||||
if ( item == null )
|
if ( item == null )
|
||||||
@ -334,5 +405,55 @@ public class Inventar : Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выбрасывает все предметы из инвентаря
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dropPosition">Базовая позиция для выбрасывания</param>
|
||||||
|
/// <param name="scatterRadius">Радиус разброса предметов</param>
|
||||||
|
/// <returns>Количество выброшенных предметов</returns>
|
||||||
|
public int DropAllItems( Vector3 dropPosition, float scatterRadius = 50f )
|
||||||
|
{
|
||||||
|
if ( !Network.IsOwner ) return 0;
|
||||||
|
|
||||||
|
Log.Info( "Выбрасываем все предметы из инвентаря..." );
|
||||||
|
|
||||||
|
// Создаем копию списка предметов для безопасной итерации
|
||||||
|
var itemsToDrop = Items.ToList();
|
||||||
|
var equippedItemsToDrop = EquippedItems.Values.ToList();
|
||||||
|
|
||||||
|
int droppedCount = 0;
|
||||||
|
|
||||||
|
// Сначала снимаем все экипированные предметы и добавляем их в список для выбрасывания
|
||||||
|
foreach ( var equippedItem in equippedItemsToDrop )
|
||||||
|
{
|
||||||
|
if ( equippedItem != null )
|
||||||
|
{
|
||||||
|
// Снимаем предмет
|
||||||
|
UnEquipItem( equippedItem );
|
||||||
|
|
||||||
|
// Добавляем в список для выбрасывания, если его там еще нет
|
||||||
|
if ( !itemsToDrop.Contains( equippedItem ) )
|
||||||
|
{
|
||||||
|
itemsToDrop.Add( equippedItem );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Затем выбрасываем все предметы
|
||||||
|
foreach ( var item in itemsToDrop )
|
||||||
|
{
|
||||||
|
if ( item != null && item.Count > 0 )
|
||||||
|
{
|
||||||
|
// Выбираем случайную позицию в радиусе разброса
|
||||||
|
var scatteredPosition = dropPosition + Vector3.Random * scatterRadius + Vector3.Up * 10f;
|
||||||
|
DropItem( item, scatteredPosition );
|
||||||
|
droppedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info( $"Выброшено {droppedCount} предметов из инвентаря" );
|
||||||
|
return droppedCount;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,9 +20,6 @@ public sealed partial class Dedugan
|
|||||||
// Плавная интерполяция позиции пивота для устранения колебаний
|
// Плавная интерполяция позиции пивота для устранения колебаний
|
||||||
CameraPivot.WorldPosition =
|
CameraPivot.WorldPosition =
|
||||||
Vector3.Lerp( CameraPivot.WorldPosition, Eyes.WorldPosition, Time.Delta * 25f );
|
Vector3.Lerp( CameraPivot.WorldPosition, Eyes.WorldPosition, Time.Delta * 25f );
|
||||||
|
|
||||||
// Компенсируем поворот тела, чтобы камера вращалась правильно
|
|
||||||
// Используем только yaw от EyeAngles, но pitch оставляем как есть
|
|
||||||
var bodyYaw = Renderer.LocalRotation.Yaw();
|
var bodyYaw = Renderer.LocalRotation.Yaw();
|
||||||
var cameraYaw = EyeAngles.yaw;
|
var cameraYaw = EyeAngles.yaw;
|
||||||
var compensatedYaw = cameraYaw - bodyYaw;
|
var compensatedYaw = cameraYaw - bodyYaw;
|
||||||
|
|||||||
@ -22,5 +22,42 @@ public sealed partial class Dedugan
|
|||||||
{
|
{
|
||||||
await GameTask.DelaySeconds( 0.1f );
|
await GameTask.DelaySeconds( 0.1f );
|
||||||
RagdollController.Enabled = true;
|
RagdollController.Enabled = true;
|
||||||
|
|
||||||
|
// Выбрасываем все предметы при смерти
|
||||||
|
DropAllItemsOnDeath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выбрасывает все предметы из инвентаря при смерти игрока
|
||||||
|
/// </summary>
|
||||||
|
private void DropAllItemsOnDeath()
|
||||||
|
{
|
||||||
|
if ( Inventory == null || !Network.IsOwner ) return;
|
||||||
|
|
||||||
|
Log.Info( $"Игрок {Name} умер. Выбрасываем все предметы..." );
|
||||||
|
|
||||||
|
// Используем метод из инвентаря для выбрасывания всех предметов
|
||||||
|
int droppedCount = Inventory.DropAllItems( WorldPosition, 50f );
|
||||||
|
|
||||||
|
Log.Info( $"Выброшено {droppedCount} предметов при смерти игрока {Name}" );
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Публичный метод для принудительного выбрасывания всех предметов
|
||||||
|
/// Можно вызывать из других мест (например, из команд или UI)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dropPosition">Позиция для выбрасывания предметов (если null, используется позиция игрока)</param>
|
||||||
|
/// <param name="scatterRadius">Радиус разброса предметов</param>
|
||||||
|
public void ForceDropAllItems( Vector3? dropPosition = null, float scatterRadius = 50f )
|
||||||
|
{
|
||||||
|
if ( Inventory == null || !Network.IsOwner ) return;
|
||||||
|
|
||||||
|
Log.Info( $"Принудительно выбрасываем все предметы игрока {Name}..." );
|
||||||
|
|
||||||
|
// Используем метод из инвентаря для выбрасывания всех предметов
|
||||||
|
var finalDropPosition = dropPosition ?? WorldPosition;
|
||||||
|
int droppedCount = Inventory.DropAllItems( finalDropPosition, scatterRadius );
|
||||||
|
|
||||||
|
Log.Info( $"Принудительно выброшено {droppedCount} предметов игрока {Name}" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,15 +94,19 @@ public sealed partial class Dedugan : Component, IUseContext, Component.INetwork
|
|||||||
|
|
||||||
UpdateBodyRotation();
|
UpdateBodyRotation();
|
||||||
|
|
||||||
Camera.LocalRotation = EyeAngles.ToRotation();
|
CameraPivot.WorldPosition =
|
||||||
|
Vector3.Lerp( CameraPivot.WorldPosition, Eyes.WorldPosition, Time.Delta * 25f );
|
||||||
|
var bodyYaw = Renderer.LocalRotation.Yaw();
|
||||||
|
var cameraYaw = EyeAngles.yaw;
|
||||||
|
var compensatedYaw = cameraYaw - bodyYaw;
|
||||||
|
CameraPivot.LocalRotation = Rotation.FromYaw( compensatedYaw ) * Rotation.FromPitch( EyeAngles.pitch );
|
||||||
|
|
||||||
var pivotOffset = CameraPivot.LocalRotation.Right * CamOffset.y +
|
// Camera.LocalRotation = EyeAngles.ToRotation();
|
||||||
CameraPivot.LocalRotation.Forward * CamOffset.x +
|
// var pivotOffset = CameraPivot.LocalRotation.Right * CamOffset.y +
|
||||||
CameraPivot.LocalRotation.Up * CamOffset.z;
|
// CameraPivot.LocalRotation.Forward * CamOffset.x +
|
||||||
|
// CameraPivot.LocalRotation.Up * CamOffset.z;
|
||||||
var rotatedOffset = pivotOffset * EyeAngles.ToRotation();
|
// var rotatedOffset = pivotOffset * EyeAngles.ToRotation();
|
||||||
|
// Camera.WorldPosition = CameraPivot.WorldPosition + rotatedOffset;
|
||||||
Camera.WorldPosition = CameraPivot.WorldPosition + rotatedOffset;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,29 +25,32 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@* <!-- Панель управления сохранением инвентаря --> *@
|
@* <div class="save-panel"> *@
|
||||||
@* <div class="save-status"> *@
|
@* <div class="save-title">Сохранение инвентаря</div> *@
|
||||||
@* <div class="save-indicator @( HasSaveFile ? "has-save" : "no-save" )"> *@
|
@* <div class="save-buttons"> *@
|
||||||
@* <div class="save-text">@( HasSaveFile ? "Сохранение есть" : "Нет сохранения" )</div> *@
|
@* <button class="save-button" @onclick="SaveInventory"> *@
|
||||||
|
@* 💾 Сохранить *@
|
||||||
|
@* </button> *@
|
||||||
|
@* <button class="save-button" @onclick="LoadInventory"> *@
|
||||||
|
@* 📂 Загрузить *@
|
||||||
|
@* </button> *@
|
||||||
|
@* <button class="save-button danger" @onclick="ClearSave"> *@
|
||||||
|
@* 🗑️ Очистить *@
|
||||||
|
@* </button> *@
|
||||||
|
@* <button class="save-button danger" @onclick="DropAllItems"> *@
|
||||||
|
@* 📦 Выбросить все *@
|
||||||
|
@* </button> *@
|
||||||
|
@* </div> *@
|
||||||
|
@* <div class="save-status"> *@
|
||||||
|
@* @if ( HasSaveFile ) *@
|
||||||
|
@* { *@
|
||||||
|
@* <span class="save-status-ok">✓ Файл сохранения найден</span> *@
|
||||||
|
@* } *@
|
||||||
|
@* else *@
|
||||||
|
@* { *@
|
||||||
|
@* <span class="save-status-none">✗ Файл сохранения не найден</span> *@
|
||||||
|
@* } *@
|
||||||
@* </div> *@
|
@* </div> *@
|
||||||
@* *@
|
|
||||||
@* @if ( HasSaveFile ) *@
|
|
||||||
@* { *@
|
|
||||||
@* <button class="load-button" onmousedown="LoadInventory"> *@
|
|
||||||
@* <div class="button-text">Загрузить</div> *@
|
|
||||||
@* </button> *@
|
|
||||||
@* } *@
|
|
||||||
@* *@
|
|
||||||
@* <button class="save-button" onmousedown="SaveInventory"> *@
|
|
||||||
@* <div class="button-text">Сохранить</div> *@
|
|
||||||
@* </button> *@
|
|
||||||
@* *@
|
|
||||||
@* @if ( HasSaveFile ) *@
|
|
||||||
@* { *@
|
|
||||||
@* <button class="clear-button" onmousedown="ClearSave"> *@
|
|
||||||
@* <div class="button-text">Очистить</div> *@
|
|
||||||
@* </button> *@
|
|
||||||
@* } *@
|
|
||||||
@* </div> *@
|
@* </div> *@
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
@ -125,7 +128,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-status {
|
.save-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
@ -137,79 +140,61 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
|
|
||||||
.save-indicator {
|
.save-title {
|
||||||
display: flex;
|
font-size: 18px;
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
&.has-save {
|
.save-buttons {
|
||||||
background: rgba(76, 175, 80, 0.2);
|
display: flex;
|
||||||
color: #4CAF50;
|
justify-content: space-between;
|
||||||
border: 1px solid #4CAF50;
|
gap: 8px;
|
||||||
}
|
|
||||||
|
|
||||||
&.no-save {
|
button {
|
||||||
background: rgba(244, 67, 54, 0.2);
|
padding: 8px 12px;
|
||||||
color: #F44336;
|
border-radius: 4px;
|
||||||
border: 1px solid #F44336;
|
border: none;
|
||||||
}
|
cursor: pointer;
|
||||||
|
|
||||||
.save-text {
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.save-button {
|
||||||
|
background: #2196F3;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #1976D2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
background: #F44336;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #D32F2F;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
.save-status {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.2s ease;
|
color: white;
|
||||||
|
|
||||||
&:hover {
|
&.save-status-ok {
|
||||||
transform: translateY(-1px);
|
color: #4CAF50;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.save-button {
|
&.save-status-none {
|
||||||
background: #2196F3;
|
color: #F44336;
|
||||||
color: white;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #1976D2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.load-button {
|
|
||||||
background: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #388E3C;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.clear-button {
|
|
||||||
background: #F44336;
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #D32F2F;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-text {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,6 +269,14 @@
|
|||||||
CheckSaveFile();
|
CheckSaveFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DropAllItems()
|
||||||
|
{
|
||||||
|
if ( Dedugan.Local != null )
|
||||||
|
{
|
||||||
|
Dedugan.Local.ForceDropAllItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override int BuildHash()
|
protected override int BuildHash()
|
||||||
{
|
{
|
||||||
if ( Dedugan.Local == null || !HasWeapon )
|
if ( Dedugan.Local == null || !HasWeapon )
|
||||||
|
|||||||
65
Code/UI/GUI.razor.scss
Normal file
65
Code/UI/GUI.razor.scss
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
.save-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
min-width: 200px;
|
||||||
|
|
||||||
|
.save-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: #2196F3;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #1976D2;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
background: #F44336;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #D32F2F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-status {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.save-status-ok {
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-status-none {
|
||||||
|
color: #F44336;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
README.md
95
README.md
@ -76,3 +76,98 @@
|
|||||||
- Кэширование проверок владельца
|
- Кэширование проверок владельца
|
||||||
- Уменьшение частоты обновлений
|
- Уменьшение частоты обновлений
|
||||||
- Эффективная синхронизация состояний
|
- Эффективная синхронизация состояний
|
||||||
|
|
||||||
|
## Система выбрасывания предметов при смерти
|
||||||
|
|
||||||
|
### Автоматическое выбрасывание при смерти
|
||||||
|
|
||||||
|
При смерти игрока (когда здоровье достигает 0) автоматически выбрасываются все предметы из инвентаря:
|
||||||
|
|
||||||
|
- **Все экипированные предметы** автоматически снимаются
|
||||||
|
- **Все предметы в инвентаре** выбрасываются на землю рядом с игроком
|
||||||
|
- **Предметы разбрасываются** в радиусе 50 единиц от позиции игрока
|
||||||
|
- **Логирование** процесса выбрасывания в консоль
|
||||||
|
|
||||||
|
### Специальная обработка одежды
|
||||||
|
|
||||||
|
Одежда обрабатывается особым образом при выбрасывании:
|
||||||
|
|
||||||
|
- **Создание физического объекта** - для одежды создается специальный физический объект
|
||||||
|
- **Fallback система** - если префаб не найден, создается простой объект с коллайдером
|
||||||
|
- **Правильная метка** - отображается название одежды и слот (например, "Куртка (Тело)")
|
||||||
|
- **Поддержка подбора** - выброшенная одежда может быть подобрана обратно
|
||||||
|
|
||||||
|
### Методы для выбрасывания предметов
|
||||||
|
|
||||||
|
#### `DropAllItemsOnDeath()` (приватный)
|
||||||
|
Автоматически вызывается при смерти игрока:
|
||||||
|
```csharp
|
||||||
|
private void DropAllItemsOnDeath()
|
||||||
|
{
|
||||||
|
if ( Inventory == null || !Network.IsOwner ) return;
|
||||||
|
|
||||||
|
int droppedCount = Inventory.DropAllItems( WorldPosition, 50f );
|
||||||
|
Log.Info( $"Выброшено {droppedCount} предметов при смерти игрока {Name}" );
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `ForceDropAllItems()` (публичный)
|
||||||
|
Можно вызывать принудительно из других мест:
|
||||||
|
```csharp
|
||||||
|
public void ForceDropAllItems( Vector3? dropPosition = null, float scatterRadius = 50f )
|
||||||
|
{
|
||||||
|
if ( Inventory == null || !Network.IsOwner ) return;
|
||||||
|
|
||||||
|
var finalDropPosition = dropPosition ?? WorldPosition;
|
||||||
|
int droppedCount = Inventory.DropAllItems( finalDropPosition, scatterRadius );
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `Inventory.DropAllItems()` (публичный)
|
||||||
|
Метод в классе инвентаря для массового выбрасывания:
|
||||||
|
```csharp
|
||||||
|
public int DropAllItems( Vector3 dropPosition, float scatterRadius = 50f )
|
||||||
|
{
|
||||||
|
// Снимает все экипированные предметы
|
||||||
|
// Выбрасывает все предметы в указанной позиции с разбросом
|
||||||
|
// Возвращает количество выброшенных предметов
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `DropClothingItem()` (приватный)
|
||||||
|
Специальный метод для выбрасывания одежды:
|
||||||
|
```csharp
|
||||||
|
private void DropClothingItem( InventoryItem item, Vector3 position, ClothingItemDefinition clothingDef )
|
||||||
|
{
|
||||||
|
// Создает физический объект для одежды
|
||||||
|
// Добавляет правильную метку и компоненты
|
||||||
|
// Поддерживает fallback если префаб не найден
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI для тестирования
|
||||||
|
|
||||||
|
В интерфейсе добавлена кнопка "📦 Выбросить все" для тестирования системы выбрасывания предметов.
|
||||||
|
|
||||||
|
### Особенности реализации
|
||||||
|
|
||||||
|
1. **Безопасная итерация** - создаются копии списков предметов для избежания ошибок при изменении коллекций
|
||||||
|
2. **Сетевая синхронизация** - выбрасывание происходит только на владельце объекта
|
||||||
|
3. **Снятие экипировки** - все экипированные предметы сначала снимаются, затем выбрасываются
|
||||||
|
4. **Случайный разброс** - предметы разбрасываются случайным образом в указанном радиусе
|
||||||
|
5. **Специальная обработка одежды** - одежда создает физические объекты с правильными метками
|
||||||
|
6. **Fallback система** - если префаб не найден, создается простой объект с базовыми компонентами
|
||||||
|
7. **Логирование** - все действия логируются для отладки
|
||||||
|
|
||||||
|
### Использование
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Принудительно выбросить все предметы игрока
|
||||||
|
Dedugan.Local.ForceDropAllItems();
|
||||||
|
|
||||||
|
// Выбросить предметы в определенной позиции
|
||||||
|
Dedugan.Local.ForceDropAllItems( new Vector3(100, 0, 100), 30f );
|
||||||
|
|
||||||
|
// Выбросить все предметы из инвентаря напрямую
|
||||||
|
player.Inventory.DropAllItems( player.WorldPosition, 25f );
|
||||||
|
```
|
||||||
Loading…
x
Reference in New Issue
Block a user