Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/Loot/UnityLootService.cs

226 lines
7.6 KiB
C#

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<UnityLootService> _logger;
private readonly IManagedItemService _managedItemService;
private readonly IEntitiesService _entitiesService;
private readonly IAsyncTicker _asyncTicker;
private readonly IReadOnlyDictionary<string, IScriptableLoot> _scriptableLoots;
private readonly IReadOnlyDictionary<int, ScriptableItem> _scriptableItems;
private readonly IGameMapService _gameMapService;
private Quadtree _quadtree = new(default, new float2(2048, 2048));
private readonly ConcurrentDictionary<int, (Matrix4x4 matrix4X4, UnityLootNode lootNode)> _lootNodes = new();
private readonly ConcurrentQueue<int> _removeQueue = new();
private Quadtree _spawnTree = new Quadtree(default, new float2(2048, 2048));
public UnityLootService(IEntitiesService entitiesService, IManagedItemService managedItemService,
IAsyncTicker asyncTicker, IGameMapService gameMapService, ILogger<UnityLootService> logger)
{
_entitiesService = entitiesService;
_managedItemService = managedItemService;
_asyncTicker = asyncTicker;
_gameMapService = gameMapService;
_logger = logger;
_entitiesService.OnAdd += OnAdd;
_scriptableLoots = _entitiesService.QueryComponents<IScriptableLoot>()
.ToArray().ToFrozenDictionary(x => x.Name, x => x);
_scriptableItems = _entitiesService.QueryComponents<ScriptableItem>()
.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<UnityLootNode>() is not { } lootNode) return;
var transform = obj.ServiceProvider.GetRequiredService<Transform>();
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<IReadOnlyDictionary<int, int>> GetLoot(IWorldLootType type)
{
throw new NotImplementedException();
}
private void Spawn(int lootId)
{
var lootList = ListPool<IRuntimeItem>.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<Collider>(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<IRuntimeItem>.Release(lootList);
}
}
}
}