using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Frozen; using System.Collections.Generic; using System.Linq; using BITKit; using BITKit.Entities; using Cysharp.Threading.Tasks; using DrawXXL; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Net.BITKit.Quadtree; using Net.Project.B.Item; using Net.Project.B.WorldNode; using Project.B.Item; using Project.B.Map; using Unity.Mathematics; using UnityEngine; using UnityEngine.Pool; using Object = UnityEngine.Object; using Random = UnityEngine.Random; namespace Net.Project.B.Loot { public class UnityLootService : ILootService, IDisposable { private readonly ILogger _logger; private readonly IManagedItemService _managedItemService; private readonly IEntitiesService _entitiesService; private readonly IAsyncTicker _asyncTicker; private readonly IReadOnlyDictionary _scriptableLoots; private readonly IReadOnlyDictionary _scriptableItems; private readonly IGameMapService _gameMapService; private Quadtree _quadtree = new(default, new float2(2048, 2048)); private readonly ConcurrentDictionary _lootNodes = new(); private readonly ConcurrentQueue _removeQueue = new(); private Quadtree _spawnTree = new Quadtree(default, new float2(2048, 2048)); public UnityLootService(IEntitiesService entitiesService, IManagedItemService managedItemService, IAsyncTicker asyncTicker, IGameMapService gameMapService, ILogger logger) { _entitiesService = entitiesService; _managedItemService = managedItemService; _asyncTicker = asyncTicker; _gameMapService = gameMapService; _logger = logger; _entitiesService.OnAdd += OnAdd; _scriptableLoots = _entitiesService.QueryComponents() .ToArray().ToFrozenDictionary(x => x.Name, x => x); _scriptableItems = _entitiesService.QueryComponents() .ToArray().ToFrozenDictionary(x => x.Id, x => x); _asyncTicker.OnTickAsync += OnTickAsync; _gameMapService.OnMapChange += OnMapChange; } private UniTask OnMapChange(string arg) { _quadtree = new Quadtree(default, new float2(2048, 2048)); _spawnTree = new Quadtree(default, new float2(2048, 2048)); return UniTask.CompletedTask; } private UniTask OnTickAsync(float arg) { var cameraPosition = Camera.main.transform.position; var query =_quadtree.Query(new float2(cameraPosition.x, cameraPosition.z), 16); foreach (var id in query) { Spawn(id); _removeQueue.Enqueue(id); } while (_removeQueue.TryDequeue(out var id)) { _quadtree.Remove(id); } return UniTask.CompletedTask; } private void OnAdd(IEntity obj) { if (obj.ServiceProvider.GetService() is not { } lootNode) return; var transform = obj.ServiceProvider.GetRequiredService(); var pos = transform.position; var matrix = Matrix4x4.TRS(transform.position, transform.rotation, transform.localScale); _lootNodes.TryAdd(obj.Id, new(matrix, lootNode)); _quadtree.Insert(obj.Id, new float2(pos.x, pos.z)); } public void Dispose() { _gameMapService.OnMapChanging -= OnMapChange; _entitiesService.OnAdd -= OnAdd; _asyncTicker.OnTickAsync -= OnTickAsync; } public UniTask> GetLoot(IWorldLootType type) { throw new NotImplementedException(); } private void Spawn(int lootId) { var lootList = ListPool.Get(); try { if (_lootNodes.TryRemove(lootId, out var v) is false) return; var matrix = v.matrix4X4; var lootNode = v.lootNode; float3 position = matrix.GetPosition(); if (_scriptableLoots.TryGetValue(lootNode.LootName, out var scriptableLoot) is false) return; var sumWeight = scriptableLoot.ItemWeights.Values.Sum(); sumWeight = math.clamp(sumWeight, 100, sumWeight); var spawned = _spawnTree.Query(position.xz, 8); var spawnedThisFrame = 0; var max = 3; if (spawned.Length > max) { return; } var count = Random.Range(0, max); for (var i = 0; i < count && spawnedThisFrame++ < max; i++) { var id = scriptableLoot.ItemWeights.ElementAt(Random.Range(0, scriptableLoot.ItemWeights.Count)) .Key; var weight = scriptableLoot.ItemWeights[id]; if (Random.Range(0, sumWeight) <= weight) continue; var runtimeItem = _scriptableItems[id].CreateRuntimeItem(); _managedItemService.AddOrUpdateItem(runtimeItem); lootList.Add(runtimeItem); } if (_entitiesService.Entities.TryGetValue(lootId, out var lootEntity) is false) return; lootEntity.ServiceProvider .QueryComponents(out IRuntimeItemContainer container); lootEntity.ServiceProvider .QueryComponents(out Transform transform); if (lootList.Count > 0) { if (container is not null) { foreach (var runtimeItem in lootList) { container.Add(runtimeItem); _spawnTree.Insert(runtimeItem.Id,position.xz); } } else if (transform && transform.TryGetComponent(out var collider)) { foreach (var runtimeItem in lootList) { var random = new Unity.Mathematics.Random(); random.InitState((uint)Random.Range(1, uint.MaxValue)); var randomPoint = random.NextFloat3(collider.bounds.min, collider.bounds.max); var randomRotation = random.NextQuaternionRotation(); _managedItemService.InstanceItem(randomPoint, randomRotation, runtimeItem); _spawnTree.Insert(runtimeItem.Id,randomPoint.xz); } } else { foreach (var runtimeItem in lootList) { _managedItemService.InstanceItem(matrix.GetPosition(), matrix.rotation, runtimeItem); _spawnTree.Insert(runtimeItem.Id,position.xz); } } } } finally { ListPool.Release(lootList); } } } }