save
This commit is contained in:
parent
eaeb0c45ac
commit
db31f362d1
@ -125,6 +125,7 @@ public class Inventar : Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Возвращаем остаток, если не всё удалось добавить
|
// 3. Возвращаем остаток, если не всё удалось добавить
|
||||||
|
AutoSave(); // Автоматическое сохранение
|
||||||
return toAdd;
|
return toAdd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +142,7 @@ public class Inventar : Component
|
|||||||
_cacheDirty = true; // Помечаем кэш как устаревший
|
_cacheDirty = true; // Помечаем кэш как устаревший
|
||||||
OnChanged?.Invoke();
|
OnChanged?.Invoke();
|
||||||
OnItemRemoved?.Invoke( item );
|
OnItemRemoved?.Invoke( item );
|
||||||
|
AutoSave(); // Автоматическое сохранение
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -149,6 +151,7 @@ public class Inventar : Component
|
|||||||
item.Count -= count;
|
item.Count -= count;
|
||||||
_cacheDirty = true; // Помечаем кэш как устаревший
|
_cacheDirty = true; // Помечаем кэш как устаревший
|
||||||
OnChanged?.Invoke();
|
OnChanged?.Invoke();
|
||||||
|
AutoSave(); // Автоматическое сохранение
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,6 +184,7 @@ public class Inventar : Component
|
|||||||
item.OnEquipped();
|
item.OnEquipped();
|
||||||
OnEquipped?.Invoke( item );
|
OnEquipped?.Invoke( item );
|
||||||
OnChanged?.Invoke();
|
OnChanged?.Invoke();
|
||||||
|
AutoSave(); // Автоматическое сохранение
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +223,7 @@ public class Inventar : Component
|
|||||||
item.OnUnEquipped();
|
item.OnUnEquipped();
|
||||||
OnUnEquipped?.Invoke( item );
|
OnUnEquipped?.Invoke( item );
|
||||||
OnChanged?.Invoke();
|
OnChanged?.Invoke();
|
||||||
|
AutoSave(); // Автоматическое сохранение
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,4 +290,49 @@ public class Inventar : Component
|
|||||||
|
|
||||||
return (float)Items.Count / MaxInventorySlots * 100f;
|
return (float)Items.Count / MaxInventorySlots * 100f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Сохранение и загрузка
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Автоматически сохраняет инвентарь при изменениях
|
||||||
|
/// </summary>
|
||||||
|
public void AutoSave()
|
||||||
|
{
|
||||||
|
if ( Network.IsOwner )
|
||||||
|
{
|
||||||
|
InventorySaveSystem.SaveInventory( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Загружает инвентарь из файла сохранения
|
||||||
|
/// </summary>
|
||||||
|
public void LoadFromSave()
|
||||||
|
{
|
||||||
|
if ( Network.IsOwner )
|
||||||
|
{
|
||||||
|
InventorySaveSystem.LoadInventory( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверяет, существует ли файл сохранения
|
||||||
|
/// </summary>
|
||||||
|
public bool HasSaveFile()
|
||||||
|
{
|
||||||
|
return InventorySaveSystem.HasSaveFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удаляет файл сохранения
|
||||||
|
/// </summary>
|
||||||
|
public void DeleteSaveFile()
|
||||||
|
{
|
||||||
|
if ( Network.IsOwner )
|
||||||
|
{
|
||||||
|
InventorySaveSystem.DeleteSaveFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
186
Code/Inventory/InventorySaveSystem.cs
Normal file
186
Code/Inventory/InventorySaveSystem.cs
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
// Система сохранения инвентаря для s&box
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Sandbox;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Sasalka;
|
||||||
|
|
||||||
|
public class InventorySaveData
|
||||||
|
{
|
||||||
|
public List<SavedInventoryItem> Items { get; set; } = new();
|
||||||
|
public Dictionary<string, string> EquippedItems { get; set; } = new(); // slot -> itemId
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SavedInventoryItem
|
||||||
|
{
|
||||||
|
public string ItemDefinitionPath { get; set; } = "";
|
||||||
|
public int Count { get; set; } = 1;
|
||||||
|
public int MagazineAmmo { get; set; } = 0;
|
||||||
|
public string Id { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InventorySaveSystem
|
||||||
|
{
|
||||||
|
private const string SAVE_FILE_NAME = "inventory_save.json";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сохраняет инвентарь игрока в файл
|
||||||
|
/// </summary>
|
||||||
|
public static void SaveInventory( Inventar inventory )
|
||||||
|
{
|
||||||
|
if ( inventory == null ) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var saveData = new InventorySaveData();
|
||||||
|
|
||||||
|
// Сохраняем предметы в инвентаре
|
||||||
|
foreach ( var item in inventory.Items )
|
||||||
|
{
|
||||||
|
if ( item.Definition == null ) continue;
|
||||||
|
|
||||||
|
var savedItem = new SavedInventoryItem
|
||||||
|
{
|
||||||
|
ItemDefinitionPath = item.Definition.ResourcePath,
|
||||||
|
Count = item.Count,
|
||||||
|
MagazineAmmo = item.MagazineAmmo,
|
||||||
|
Id = Guid.NewGuid().ToString() // Генерируем уникальный ID для предмета
|
||||||
|
};
|
||||||
|
|
||||||
|
saveData.Items.Add( savedItem );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем экипированные предметы
|
||||||
|
foreach ( var kvp in inventory.EquippedItems )
|
||||||
|
{
|
||||||
|
var slot = kvp.Key;
|
||||||
|
var item = kvp.Value;
|
||||||
|
|
||||||
|
// Находим соответствующий сохраненный предмет
|
||||||
|
var savedItem = saveData.Items.FirstOrDefault( x =>
|
||||||
|
x.ItemDefinitionPath == item.Definition?.ResourcePath &&
|
||||||
|
x.Count == item.Count &&
|
||||||
|
x.MagazineAmmo == item.MagazineAmmo );
|
||||||
|
|
||||||
|
if ( savedItem != null )
|
||||||
|
{
|
||||||
|
saveData.EquippedItems[slot.ToString()] = savedItem.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сериализуем и сохраняем в файл
|
||||||
|
var json = JsonSerializer.Serialize( saveData, new JsonSerializerOptions { WriteIndented = true } );
|
||||||
|
FileSystem.Data.WriteAllText( SAVE_FILE_NAME, json );
|
||||||
|
|
||||||
|
Log.Info( "Инвентарь успешно сохранен" );
|
||||||
|
}
|
||||||
|
catch ( Exception ex )
|
||||||
|
{
|
||||||
|
Log.Error( $"Ошибка при сохранении инвентаря: {ex.Message}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Загружает инвентарь игрока из файла
|
||||||
|
/// </summary>
|
||||||
|
public static void LoadInventory( Inventar inventory )
|
||||||
|
{
|
||||||
|
if ( inventory == null ) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if ( !FileSystem.Data.FileExists( SAVE_FILE_NAME ) )
|
||||||
|
{
|
||||||
|
Log.Info( "Файл сохранения инвентаря не найден" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = FileSystem.Data.ReadAllText( SAVE_FILE_NAME );
|
||||||
|
var saveData = JsonSerializer.Deserialize<InventorySaveData>( json );
|
||||||
|
|
||||||
|
if ( saveData == null )
|
||||||
|
{
|
||||||
|
Log.Error( "Не удалось десериализовать данные сохранения" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Очищаем текущий инвентарь
|
||||||
|
inventory.ClearInventory();
|
||||||
|
|
||||||
|
// Словарь для связи ID предметов с объектами InventoryItem
|
||||||
|
var itemIdMap = new Dictionary<string, InventoryItem>();
|
||||||
|
|
||||||
|
// Загружаем предметы в инвентарь
|
||||||
|
foreach ( var savedItem in saveData.Items )
|
||||||
|
{
|
||||||
|
var itemDefinition = ResourceLibrary.Get<BaseItemDefinition>( savedItem.ItemDefinitionPath );
|
||||||
|
if ( itemDefinition == null )
|
||||||
|
{
|
||||||
|
Log.Warning( $"Не удалось загрузить определение предмета: {savedItem.ItemDefinitionPath}" );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inventoryItem = new InventoryItem
|
||||||
|
{
|
||||||
|
Definition = itemDefinition,
|
||||||
|
Count = savedItem.Count,
|
||||||
|
MagazineAmmo = savedItem.MagazineAmmo
|
||||||
|
};
|
||||||
|
|
||||||
|
inventory.Items.Add( inventoryItem );
|
||||||
|
itemIdMap[savedItem.Id] = inventoryItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Экипируем предметы
|
||||||
|
foreach ( var kvp in saveData.EquippedItems )
|
||||||
|
{
|
||||||
|
var slotName = kvp.Key;
|
||||||
|
var itemId = kvp.Value;
|
||||||
|
|
||||||
|
if ( Enum.TryParse<Inventar.InventorySlot>( slotName, out var slot ) &&
|
||||||
|
itemIdMap.TryGetValue( itemId, out var item ) )
|
||||||
|
{
|
||||||
|
inventory.EquipItem( item );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уведомляем об изменениях
|
||||||
|
inventory.NotifyChanged();
|
||||||
|
|
||||||
|
Log.Info( "Инвентарь успешно загружен" );
|
||||||
|
}
|
||||||
|
catch ( Exception ex )
|
||||||
|
{
|
||||||
|
Log.Error( $"Ошибка при загрузке инвентаря: {ex.Message}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверяет, существует ли файл сохранения
|
||||||
|
/// </summary>
|
||||||
|
public static bool HasSaveFile()
|
||||||
|
{
|
||||||
|
return FileSystem.Data.FileExists( SAVE_FILE_NAME );
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удаляет файл сохранения
|
||||||
|
/// </summary>
|
||||||
|
public static void DeleteSaveFile()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if ( FileSystem.Data.FileExists( SAVE_FILE_NAME ) )
|
||||||
|
{
|
||||||
|
FileSystem.Data.DeleteFile( SAVE_FILE_NAME );
|
||||||
|
Log.Info( "Файл сохранения инвентаря удален" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch ( Exception ex )
|
||||||
|
{
|
||||||
|
Log.Error( $"Ошибка при удалении файла сохранения: {ex.Message}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
118
Code/Inventory/README_SaveSystem.md
Normal file
118
Code/Inventory/README_SaveSystem.md
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# Система сохранения инвентаря
|
||||||
|
|
||||||
|
## Описание
|
||||||
|
|
||||||
|
Система автоматического сохранения и загрузки инвентаря игрока, включая экипированные предметы (одежда, оружие).
|
||||||
|
|
||||||
|
## Функциональность
|
||||||
|
|
||||||
|
### Автоматическое сохранение
|
||||||
|
- Инвентарь автоматически сохраняется при любых изменениях:
|
||||||
|
- Добавление предметов
|
||||||
|
- Удаление предметов
|
||||||
|
- Экипировка предметов
|
||||||
|
- Снятие предметов
|
||||||
|
|
||||||
|
### Сохранение при выходе из игры
|
||||||
|
- **При отключении от сервера** - инвентарь сохраняется автоматически
|
||||||
|
- **При закрытии игры** - инвентарь сохраняется при уничтожении объекта игрока
|
||||||
|
- **При разрыве соединения** - дополнительное сохранение для защиты от потери данных
|
||||||
|
|
||||||
|
### Периодическое сохранение
|
||||||
|
- **Каждые 60 секунд** - автоматическое сохранение для защиты от сбоев
|
||||||
|
- Защищает от потери прогресса при неожиданном закрытии игры
|
||||||
|
|
||||||
|
### Автоматическая загрузка
|
||||||
|
- При входе в игру система автоматически проверяет наличие сохранения
|
||||||
|
- Если сохранение найдено, инвентарь загружается автоматически
|
||||||
|
- Если сохранения нет, начинается с пустым инвентарем
|
||||||
|
|
||||||
|
### Сохраняемые данные
|
||||||
|
- Все предметы в инвентаре (название, количество, патроны в магазине)
|
||||||
|
- Экипированные предметы (одежда, оружие)
|
||||||
|
- Слоты экипировки
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### В игре
|
||||||
|
1. **UI панель**: В правом верхнем углу экрана отображается панель управления сохранением
|
||||||
|
- Зеленая индикация = есть сохранение
|
||||||
|
- Красная индикация = нет сохранения
|
||||||
|
- Кнопки: Сохранить, Загрузить, Очистить
|
||||||
|
|
||||||
|
### Автоматическое сохранение
|
||||||
|
- **При изменениях** - сохранение происходит автоматически
|
||||||
|
- **При выходе** - сохранение при отключении/закрытии игры
|
||||||
|
- **Периодически** - каждые 60 секунд для защиты от сбоев
|
||||||
|
|
||||||
|
## Технические детали
|
||||||
|
|
||||||
|
### Файл сохранения
|
||||||
|
- Расположение: `data/inventory_save.json`
|
||||||
|
- Формат: JSON
|
||||||
|
- Структура:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Items": [
|
||||||
|
{
|
||||||
|
"ItemDefinitionPath": "path/to/item.resource",
|
||||||
|
"Count": 1,
|
||||||
|
"MagazineAmmo": 0,
|
||||||
|
"Id": "unique-id"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"EquippedItems": {
|
||||||
|
"RightHand": "item-id",
|
||||||
|
"Body": "item-id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Классы системы
|
||||||
|
- `InventorySaveSystem` - статический класс для работы с сохранением
|
||||||
|
- `InventorySaveData` - модель данных для сохранения
|
||||||
|
- `SavedInventoryItem` - модель сохраненного предмета
|
||||||
|
|
||||||
|
### Интеграция
|
||||||
|
- `Inventar` - добавлены методы `AutoSave()`, `LoadFromSave()`, `HasSaveFile()`, `DeleteSaveFile()`
|
||||||
|
- `Dedugan.Inventory.cs` - автоматическая загрузка при старте и периодическое сохранение
|
||||||
|
- `Dedugan.cs` - сохранение при выходе из игры (`OnDestroy`, `SaveInventoryOnExit()`)
|
||||||
|
- `NetworkManager.cs` - сохранение при отключении игрока (`OnDisconnected`, `OnBecomeInactive`)
|
||||||
|
- `GUI.razor` - интегрированная панель управления сохранением
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
- Сохранение происходит только на клиенте (Network.IsOwner)
|
||||||
|
- Проверка валидности данных при загрузке
|
||||||
|
- Обработка ошибок с логированием
|
||||||
|
- Fallback на пустой инвентарь при ошибках
|
||||||
|
- **Множественные точки сохранения** для максимальной защиты данных
|
||||||
|
|
||||||
|
## Совместимость
|
||||||
|
|
||||||
|
- Работает со всеми типами предметов (оружие, одежда, патроны и т.д.)
|
||||||
|
- Поддерживает все слоты экипировки
|
||||||
|
- Сохраняет патроны в магазине оружия
|
||||||
|
- Совместима с существующей системой инвентаря
|
||||||
|
|
||||||
|
## Устранение неполадок
|
||||||
|
|
||||||
|
### Проблема: Инвентарь не загружается
|
||||||
|
1. Проверьте консоль на ошибки
|
||||||
|
2. Убедитесь, что файл `data/inventory_save.json` существует
|
||||||
|
3. Проверьте права доступа к папке data
|
||||||
|
|
||||||
|
### Проблема: Предметы не сохраняются
|
||||||
|
1. Убедитесь, что вы владелец игрока (Network.IsOwner)
|
||||||
|
2. Проверьте, что предметы имеют валидные определения
|
||||||
|
3. Проверьте консоль на ошибки сериализации
|
||||||
|
|
||||||
|
### Проблема: UI не отображается
|
||||||
|
1. Убедитесь, что панель сохранения интегрирована в GUI.razor
|
||||||
|
2. Проверьте, что стили добавлены в GUI.razor
|
||||||
|
3. Перезапустите игру
|
||||||
|
|
||||||
|
### Проблема: Сохранение не работает при выходе
|
||||||
|
1. Проверьте, что метод `SaveInventoryOnExit()` вызывается
|
||||||
|
2. Убедитесь, что NetworkManager правильно обрабатывает отключения
|
||||||
|
3. Проверьте логи на наличие ошибок сохранения
|
||||||
@ -73,6 +73,13 @@ public sealed class NetworkManager : Component, Component.INetworkListener
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnDisconnected( Connection channel )
|
public void OnDisconnected( Connection channel )
|
||||||
{
|
{
|
||||||
|
// Сохраняем инвентарь игрока перед отключением
|
||||||
|
var player = Dedugan.GetByID( channel.Id );
|
||||||
|
if ( player != null )
|
||||||
|
{
|
||||||
|
player.SaveInventoryOnExit();
|
||||||
|
}
|
||||||
|
|
||||||
Dedugan.InternalPlayers.Remove( Dedugan.GetByID( channel.Id ) );
|
Dedugan.InternalPlayers.Remove( Dedugan.GetByID( channel.Id ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +88,12 @@ public sealed class NetworkManager : Component, Component.INetworkListener
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnBecomeInactive( Connection channel )
|
public void OnBecomeInactive( Connection channel )
|
||||||
{
|
{
|
||||||
// Optional: Handle any cleanup before player becomes fully inactive
|
// Сохраняем инвентарь игрока при разрыве соединения
|
||||||
|
var player = Dedugan.GetByID( channel.Id );
|
||||||
|
if ( player != null )
|
||||||
|
{
|
||||||
|
player.SaveInventoryOnExit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -14,6 +14,10 @@ public sealed partial class Dedugan : Component
|
|||||||
private bool _isShooting = false;
|
private bool _isShooting = false;
|
||||||
private TimeSince _lastShootTime = 0f;
|
private TimeSince _lastShootTime = 0f;
|
||||||
|
|
||||||
|
// Периодическое сохранение
|
||||||
|
private TimeSince _lastAutoSave = 0f;
|
||||||
|
private const float AUTO_SAVE_INTERVAL = 60f; // Сохраняем каждые 60 секунд
|
||||||
|
|
||||||
void InventoryStart()
|
void InventoryStart()
|
||||||
{
|
{
|
||||||
if ( !Network.IsOwner ) return;
|
if ( !Network.IsOwner ) return;
|
||||||
@ -26,6 +30,25 @@ public sealed partial class Dedugan : Component
|
|||||||
Inventory.OnUnEquipped += OnItemUnEquipped;
|
Inventory.OnUnEquipped += OnItemUnEquipped;
|
||||||
Inventory.OnItemAdded += OnItemAdded;
|
Inventory.OnItemAdded += OnItemAdded;
|
||||||
Inventory.OnItemRemoved += OnItemRemoved;
|
Inventory.OnItemRemoved += OnItemRemoved;
|
||||||
|
|
||||||
|
// Загружаем инвентарь из сохранения при старте
|
||||||
|
LoadInventoryFromSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Загружает инвентарь из сохранения
|
||||||
|
/// </summary>
|
||||||
|
private void LoadInventoryFromSave()
|
||||||
|
{
|
||||||
|
if ( Inventory.HasSaveFile() )
|
||||||
|
{
|
||||||
|
Log.Info( "Загружаем инвентарь из сохранения..." );
|
||||||
|
Inventory.LoadFromSave();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Info( "Файл сохранения инвентаря не найден, начинаем с пустым инвентарем" );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnItemEquipped( InventoryItem item )
|
private void OnItemEquipped( InventoryItem item )
|
||||||
@ -159,6 +182,17 @@ public sealed partial class Dedugan : Component
|
|||||||
{
|
{
|
||||||
TryReloadWeapon();
|
TryReloadWeapon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Периодическое автоматическое сохранение
|
||||||
|
if ( _lastAutoSave > AUTO_SAVE_INTERVAL )
|
||||||
|
{
|
||||||
|
if ( Inventory != null )
|
||||||
|
{
|
||||||
|
Log.Info( "Периодическое автоматическое сохранение инвентаря..." );
|
||||||
|
Sasalka.InventorySaveSystem.SaveInventory( Inventory );
|
||||||
|
}
|
||||||
|
_lastAutoSave = 0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -116,6 +116,18 @@ public sealed partial class Dedugan : Component, IUseContext, Component.INetwork
|
|||||||
DrawDebugGizmos();
|
DrawDebugGizmos();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сохраняет инвентарь при выходе из игры
|
||||||
|
/// </summary>
|
||||||
|
public void SaveInventoryOnExit()
|
||||||
|
{
|
||||||
|
if ( Network.IsOwner && Inventory != null )
|
||||||
|
{
|
||||||
|
Log.Info( "Сохраняем инвентарь при выходе из игры..." );
|
||||||
|
Sasalka.InventorySaveSystem.SaveInventory( Inventory );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Реализация интерфейса IUseContext
|
/// Реализация интерфейса IUseContext
|
||||||
/// Возвращает список используемых предметов
|
/// Возвращает список используемых предметов
|
||||||
@ -189,4 +201,10 @@ public sealed partial class Dedugan : Component, IUseContext, Component.INetwork
|
|||||||
_bodyRotationDirty = true; // Помечаем, что тело повернулось
|
_bodyRotationDirty = true; // Помечаем, что тело повернулось
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDestroy()
|
||||||
|
{
|
||||||
|
// Сохраняем инвентарь при уничтожении объекта (закрытие игры)
|
||||||
|
SaveInventoryOnExit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
@using Sandbox
|
@using Sandbox
|
||||||
@using Sandbox.UI
|
@using Sandbox.UI
|
||||||
|
@using Sasalka
|
||||||
@inherits PanelComponent
|
@inherits PanelComponent
|
||||||
@namespace Sandbox
|
@namespace Sandbox
|
||||||
|
|
||||||
@ -23,6 +24,31 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Панель управления сохранением инвентаря -->
|
||||||
|
<div class="save-status">
|
||||||
|
<div class="save-indicator @( HasSaveFile ? "has-save" : "no-save" )">
|
||||||
|
<div class="save-text">@( HasSaveFile ? "Сохранение есть" : "Нет сохранения" )</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>
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -98,6 +124,95 @@
|
|||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.save-status {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 200px;
|
||||||
|
|
||||||
|
.save-indicator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&.has-save {
|
||||||
|
background: rgba(76, 175, 80, 0.2);
|
||||||
|
color: #4CAF50;
|
||||||
|
border: 1px solid #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.no-save {
|
||||||
|
background: rgba(244, 67, 54, 0.2);
|
||||||
|
color: #F44336;
|
||||||
|
border: 1px solid #F44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.save-button {
|
||||||
|
background: #2196F3;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@ -108,6 +223,10 @@
|
|||||||
private int TotalInInventory { get; set; } = 0;
|
private int TotalInInventory { get; set; } = 0;
|
||||||
private bool HasWeapon { get; set; } = false;
|
private bool HasWeapon { get; set; } = false;
|
||||||
|
|
||||||
|
// Переменные для системы сохранения
|
||||||
|
private bool HasSaveFile { get; set; } = false;
|
||||||
|
private TimeSince _lastCheck = 0f;
|
||||||
|
|
||||||
protected override void OnUpdate()
|
protected override void OnUpdate()
|
||||||
{
|
{
|
||||||
if ( Dedugan.Local == null ) return;
|
if ( Dedugan.Local == null ) return;
|
||||||
@ -122,6 +241,47 @@
|
|||||||
|
|
||||||
// Проверяем, есть ли оружие в руках (если MaxAmmo > 0, значит есть оружие)
|
// Проверяем, есть ли оружие в руках (если MaxAmmo > 0, значит есть оружие)
|
||||||
HasWeapon = MaxAmmo > 0;
|
HasWeapon = MaxAmmo > 0;
|
||||||
|
|
||||||
|
// Проверяем наличие файла сохранения каждые 2 секунды
|
||||||
|
if ( _lastCheck > 2f )
|
||||||
|
{
|
||||||
|
CheckSaveFile();
|
||||||
|
_lastCheck = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnStart()
|
||||||
|
{
|
||||||
|
CheckSaveFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckSaveFile()
|
||||||
|
{
|
||||||
|
HasSaveFile = Sasalka.InventorySaveSystem.HasSaveFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveInventory()
|
||||||
|
{
|
||||||
|
if ( Dedugan.Local?.Inventory != null )
|
||||||
|
{
|
||||||
|
Sasalka.InventorySaveSystem.SaveInventory( Dedugan.Local.Inventory );
|
||||||
|
CheckSaveFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadInventory()
|
||||||
|
{
|
||||||
|
if ( Dedugan.Local?.Inventory != null )
|
||||||
|
{
|
||||||
|
Sasalka.InventorySaveSystem.LoadInventory( Dedugan.Local.Inventory );
|
||||||
|
CheckSaveFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearSave()
|
||||||
|
{
|
||||||
|
Sasalka.InventorySaveSystem.DeleteSaveFile();
|
||||||
|
CheckSaveFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int BuildHash()
|
protected override int BuildHash()
|
||||||
@ -136,8 +296,8 @@
|
|||||||
hash.Add( IsReloading );
|
hash.Add( IsReloading );
|
||||||
hash.Add( ReloadProgress );
|
hash.Add( ReloadProgress );
|
||||||
hash.Add( HasWeapon );
|
hash.Add( HasWeapon );
|
||||||
|
hash.Add( HasSaveFile );
|
||||||
|
|
||||||
return hash.ToHashCode();
|
return hash.ToHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user