using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector; using UnityEngine; using UnityEngine.AI; using VOID.Generic.Objects.Abstract; using VOID.Generic.Objects.Abstract.Events; using VOID.Generic.Objects.Container; using VOID.Generic.Objects.Item; using VOID.Generic.Objects.Item.Events; using VOID.Generic.Objects.Unit; using VOID.Generic.Util; namespace VOID.Generic.DropTable { public class DropTableComponent : MonoBehaviour { // Serialized properties [InfoBox("OnSpawn gives items instantly to target, but works only on Unit and Container\n" + "OnDeath gives or spawns when target dies, works on any object")] [SerializeField, EnumToggleButtons] private DropTableTrigger _trigger; [SerializeField, HideLabel, Title("Drop Table")] private MultiSourceWrapper _dropTable; // Public accessors public DropTableTrigger trigger => _trigger; public DropTable dropTable => _dropTable.value; // Cached object attached to private AbstractObject _obj; /// /// Start listening for attached object events. /// If object already spawned and OnSpawn selected then run immediately /// private void Start() { _obj = GetComponent(); if (_trigger is DropTableTrigger.OnSpawn) { if (_obj.didStart) { OnSpawn(); } else { _obj.basicEvents.onSpawn.AddListener(OnSpawn); } } if (_trigger is DropTableTrigger.OnDeath) { _obj.basicEvents.onDeathAfter.AddListener(OnDeath); } } /// /// Return randomized items from given DropTable /// /// private List GetRandomItemsFromDropTable() { var itemList = new List(); var groups = _dropTable.value.groups.ToList(); var groupCounter = new Dictionary(); for (var i = 0; i < _dropTable.value.count; i++) { var randomGroup = RandomUtil.RandomElementByWeight(groups, group => group.weight); itemList.AddRange(randomGroup.items); if (!randomGroup.isLimited) continue; groupCounter.TryAdd(randomGroup, 0); groupCounter[randomGroup]++; if (groupCounter[randomGroup] < randomGroup.limit) continue; groups.Remove(randomGroup); } return itemList; } /// /// Run when attached object spawn /// private void OnSpawn() { _obj.basicEvents.onSpawn.RemoveListener(OnSpawn); if (_obj is UnitObject unitObject) { GetRandomItemsFromDropTable().ForEach(item => GiveItemToUnit(item, unitObject)); return; } if (_obj is ContainerObject containerObject) { GetRandomItemsFromDropTable().ForEach(item => GiveItemToContainer(item, containerObject)); return; } Debug.LogError($"{_obj.GetType()} can't contain items! Selected trigger won't work!"); } /// /// Run when attached object die /// private void OnDeath(DeathEvent deathEvent) { _obj.basicEvents.onDeathAfter.RemoveListener(OnDeath); if (_obj is UnitObject unitObject) { GetRandomItemsFromDropTable().ForEach(item => GiveItemToUnit(item, unitObject)); return; } GetRandomItemsFromDropTable().ForEach(item => DropItemFromObject(item, _obj)); } /// /// Instantiate given item prefab and put directly to attached unit's backpack /// private void GiveItemToUnit(ItemObject item, UnitObject unit) { var newItemGameObject = Instantiate(item.gameObject); var newItem = newItemGameObject.GetComponent(); var pickEvent = new PickEvent { item = newItem, unit = unit }; newItem.itemEvents.onPickedBefore.Invoke(pickEvent); newItem.itemEvents.onPickedAfter.Invoke(pickEvent); unit.unitBackpack.Take(newItem); } /// /// Instantiate given item prefab and put directly to attached container's content /// private void GiveItemToContainer(ItemObject item, ContainerObject container) { var newItemGameObject = Instantiate(item.gameObject); var newItem = newItemGameObject.GetComponent(); container.containerContent.MoveIn(newItem); } /// /// Instantiate given item prefab and "pop" it randomly near attached object /// private void DropItemFromObject(ItemObject item, AbstractObject obj) { const float dropDistance = 1f; var newItemGameObject = Instantiate(item, obj.transform.position, obj.transform.rotation); var newItem = newItemGameObject.GetComponent(); NavMesh.SamplePosition(obj.transform.position + Random.insideUnitSphere, out var hit, dropDistance, NavAreaManager.Walkable); if (hit.hit) newItem.Move(hit.position); } } }