using System; using System.Collections.Generic; using GameFramework.DataTable; using GeometryTD.DataTable; using GeometryTD.Definition; using GeometryTD.UI; using UnityEngine; using UnityGameFramework.Runtime; namespace GeometryTD.CustomComponent { public class PlayerInventoryComponent : GameFrameworkComponent { private const int TowerLevelCount = 5; private const int MaxParticipantTowerCount = 4; private BackpackInventoryData _inventory = new BackpackInventoryData(); private long _nextInstanceId = 1; private bool _initialized; private IDataTable _drMuzzleComp; private IDataTable _drBearingComp; private IDataTable _drBaseComp; public int Gold { get { EnsureInitialized(); return _inventory.Gold; } } public void OnInit() { _inventory = CloneInventory(RepoFormUseCase.SampleInventory()); NormalizeParticipantState(); RebuildNextInstanceId(); _initialized = true; Log.Info( "PlayerInventory initialized. Gold={0}, Tower={1}, Muzzle={2}, Bearing={3}, Base={4}.", _inventory.Gold, _inventory.Towers.Count, _inventory.MuzzleComponents.Count, _inventory.BearingComponents.Count, _inventory.BaseComponents.Count); } public BackpackInventoryData GetInventorySnapshot() { EnsureInitialized(); return CloneInventory(_inventory); } public void MergeInventory(BackpackInventoryData gainedInventory) { EnsureInitialized(); if (gainedInventory == null) { return; } int gainedGold = Mathf.Max(0, gainedInventory.Gold); int gainedMuzzleCount = 0; int gainedBearingCount = 0; int gainedBaseCount = 0; int gainedTowerCount = 0; if (gainedGold > 0) { _inventory.Gold += gainedGold; } if (gainedInventory.MuzzleComponents != null) { for (int i = 0; i < gainedInventory.MuzzleComponents.Count; i++) { MuzzleCompItemData source = gainedInventory.MuzzleComponents[i]; if (source == null) { continue; } MuzzleCompItemData cloned = CloneMuzzleComp(source); cloned.InstanceId = AllocateInstanceId(); _inventory.MuzzleComponents.Add(cloned); gainedMuzzleCount++; } } if (gainedInventory.BearingComponents != null) { for (int i = 0; i < gainedInventory.BearingComponents.Count; i++) { BearingCompItemData source = gainedInventory.BearingComponents[i]; if (source == null) { continue; } BearingCompItemData cloned = CloneBearingComp(source); cloned.InstanceId = AllocateInstanceId(); _inventory.BearingComponents.Add(cloned); gainedBearingCount++; } } if (gainedInventory.BaseComponents != null) { for (int i = 0; i < gainedInventory.BaseComponents.Count; i++) { BaseCompItemData source = gainedInventory.BaseComponents[i]; if (source == null) { continue; } BaseCompItemData cloned = CloneBaseComp(source); cloned.InstanceId = AllocateInstanceId(); _inventory.BaseComponents.Add(cloned); gainedBaseCount++; } } if (gainedInventory.Towers != null) { for (int i = 0; i < gainedInventory.Towers.Count; i++) { TowerItemData source = gainedInventory.Towers[i]; if (source == null) { continue; } TowerItemData cloned = CloneTower(source); cloned.InstanceId = AllocateInstanceId(); _inventory.Towers.Add(cloned); gainedTowerCount++; } } if (gainedGold > 0 || gainedMuzzleCount > 0 || gainedBearingCount > 0 || gainedBaseCount > 0 || gainedTowerCount > 0) { Log.Info( "PlayerInventory merged reward. Gold+{0}, Tower+{1}, Muzzle+{2}, Bearing+{3}, Base+{4}.", gainedGold, gainedTowerCount, gainedMuzzleCount, gainedBearingCount, gainedBaseCount); } } public bool TryConsumeGold(int costGold) { EnsureInitialized(); int resolvedCost = Mathf.Max(0, costGold); if (resolvedCost <= 0) { return true; } if (_inventory.Gold < resolvedCost) { return false; } _inventory.Gold -= resolvedCost; return true; } public void AddGold(int gainGold) { EnsureInitialized(); int resolvedGain = Mathf.Max(0, gainGold); if (resolvedGain <= 0) { return; } _inventory.Gold += resolvedGain; } public bool TryAddParticipantTower(long towerInstanceId, int maxCount = 4) { EnsureInitialized(); int resolvedMaxCount = Mathf.Max(1, maxCount); resolvedMaxCount = Mathf.Min(resolvedMaxCount, MaxParticipantTowerCount); if (!TryGetTowerById(towerInstanceId, out TowerItemData tower)) { return false; } _inventory.ParticipantTowerInstanceIds ??= new List(); if (_inventory.ParticipantTowerInstanceIds.Contains(towerInstanceId)) { tower.IsParticipatingInCombat = true; return false; } if (_inventory.ParticipantTowerInstanceIds.Count >= resolvedMaxCount) { return false; } _inventory.ParticipantTowerInstanceIds.Add(towerInstanceId); tower.IsParticipatingInCombat = true; return true; } public bool TryRemoveParticipantTower(long towerInstanceId) { EnsureInitialized(); if (towerInstanceId <= 0 || _inventory.ParticipantTowerInstanceIds == null) { return false; } bool removed = _inventory.ParticipantTowerInstanceIds.Remove(towerInstanceId); if (!removed) { return false; } if (TryGetTowerById(towerInstanceId, out TowerItemData tower)) { tower.IsParticipatingInCombat = false; } return true; } public bool TryAssembleTower( long muzzleInstanceId, long bearingInstanceId, long baseInstanceId, out TowerItemData assembledTower) { EnsureInitialized(); assembledTower = null; if (muzzleInstanceId <= 0 || bearingInstanceId <= 0 || baseInstanceId <= 0) { return false; } if (!TryGetComponentById(_inventory.MuzzleComponents, muzzleInstanceId, out MuzzleCompItemData muzzleComp) || !TryGetComponentById(_inventory.BearingComponents, bearingInstanceId, out BearingCompItemData bearingComp) || !TryGetComponentById(_inventory.BaseComponents, baseInstanceId, out BaseCompItemData baseComp)) { return false; } if (muzzleComp.IsAssembledIntoTower || bearingComp.IsAssembledIntoTower || baseComp.IsAssembledIntoTower) { return false; } if (!TryBuildTowerStats(muzzleComp, bearingComp, baseComp, out TowerStatsData stats)) { return false; } long towerInstanceId = AllocateInstanceId(); TowerItemData tower = new TowerItemData { InstanceId = towerInstanceId, Name = $"组装防御塔-{towerInstanceId}", Rarity = ResolveAverageRarity(muzzleComp.Rarity, bearingComp.Rarity, baseComp.Rarity), MuzzleComponentInstanceId = muzzleComp.InstanceId, BearingComponentInstanceId = bearingComp.InstanceId, BaseComponentInstanceId = baseComp.InstanceId, Stats = stats }; muzzleComp.IsAssembledIntoTower = true; bearingComp.IsAssembledIntoTower = true; baseComp.IsAssembledIntoTower = true; _inventory.Towers.Add(tower); assembledTower = CloneTower(tower); return true; } public int ReduceAllTowerEndurance(float enduranceLoss) { EnsureInitialized(); float resolvedLoss = Mathf.Max(0f, enduranceLoss); if (resolvedLoss <= 0f || _inventory.Towers == null || _inventory.Towers.Count <= 0) { return 0; } Dictionary muzzleMap = BuildComponentMap(_inventory.MuzzleComponents); Dictionary bearingMap = BuildComponentMap(_inventory.BearingComponents); Dictionary baseMap = BuildComponentMap(_inventory.BaseComponents); int affectedCount = 0; foreach (var tower in _inventory.Towers) { if (tower == null) { continue; } bool towerAffected = false; if (muzzleMap.TryGetValue(tower.MuzzleComponentInstanceId, out MuzzleCompItemData muzzleComp)) { towerAffected |= TryReduceComponentEndurance(muzzleComp, resolvedLoss); } if (bearingMap.TryGetValue(tower.BearingComponentInstanceId, out BearingCompItemData bearingComp)) { towerAffected |= TryReduceComponentEndurance(bearingComp, resolvedLoss); } if (baseMap.TryGetValue(tower.BaseComponentInstanceId, out BaseCompItemData baseComp)) { towerAffected |= TryReduceComponentEndurance(baseComp, resolvedLoss); } if (towerAffected) { affectedCount++; } } return affectedCount; } private static bool TryReduceComponentEndurance(TowerCompItemData component, float enduranceLoss) { if (component == null) { return false; } float originalEndurance = component.Endurance; float nextEndurance = Mathf.Clamp(originalEndurance - Mathf.Max(0f, enduranceLoss), 0f, 100f); if (nextEndurance >= originalEndurance) { return false; } component.Endurance = nextEndurance; return true; } private static Dictionary BuildComponentMap(List components) where TComp : TowerCompItemData { Dictionary map = new Dictionary(); if (components == null || components.Count <= 0) { return map; } foreach (var component in components) { if (component == null || component.InstanceId <= 0) { continue; } map[component.InstanceId] = component; } return map; } private static bool TryGetComponentById(List components, long instanceId, out TComp result) where TComp : TowerCompItemData { result = null; if (components == null || instanceId <= 0) { return false; } foreach (TComp component in components) { if (component != null && component.InstanceId == instanceId) { result = component; return true; } } return false; } private bool TryGetTowerById(long towerInstanceId, out TowerItemData tower) { tower = null; if (towerInstanceId <= 0 || _inventory.Towers == null) { return false; } for (int i = 0; i < _inventory.Towers.Count; i++) { TowerItemData candidate = _inventory.Towers[i]; if (candidate != null && candidate.InstanceId == towerInstanceId) { tower = candidate; return true; } } return false; } private bool TryBuildTowerStats( MuzzleCompItemData muzzleComp, BearingCompItemData bearingComp, BaseCompItemData baseComp, out TowerStatsData stats) { stats = null; if (muzzleComp == null || bearingComp == null || baseComp == null) { return false; } DRMuzzleComp muzzleConfig = EnsureMuzzleTable()?.GetDataRow(muzzleComp.ConfigId); DRBearingComp bearingConfig = EnsureBearingTable()?.GetDataRow(bearingComp.ConfigId); DRBaseComp baseConfig = EnsureBaseTable()?.GetDataRow(baseComp.ConfigId); if (muzzleConfig == null || bearingConfig == null || baseConfig == null) { return false; } stats = new TowerStatsData { AttackDamage = BuildLevelIntArray(muzzleComp.AttackDamage, muzzleComp.Rarity, muzzleConfig.AttackDamagePerLevel), DamageRandomRate = Mathf.Max(0f, muzzleComp.DamageRandomRate), RotateSpeed = BuildLevelFloatArray(bearingComp.RotateSpeed, bearingComp.Rarity, bearingConfig.RotateSpeedPerLevel), AttackRange = BuildLevelFloatArray(bearingComp.AttackRange, bearingComp.Rarity, bearingConfig.AttackRangePerLevel), AttackSpeed = BuildLevelFloatArray(baseComp.AttackSpeed, baseComp.Rarity, baseConfig.AttackSpeedPerLevel), AttackMethodType = muzzleComp.AttackMethodType, AttackPropertyType = baseComp.AttackPropertyType, Tags = MergeTags(muzzleComp.Tags, bearingComp.Tags, baseComp.Tags) }; return true; } private static int[] BuildLevelIntArray(int[] rarityBaseArray, RarityType rarity, int perLevel) { int baseValue = ResolveRarityBaseValue(rarityBaseArray, rarity); int[] values = new int[TowerLevelCount]; for (int i = 0; i < values.Length; i++) { values[i] = baseValue + perLevel * i; } return values; } private static float[] BuildLevelFloatArray(float[] rarityBaseArray, RarityType rarity, float perLevel) { float baseValue = ResolveRarityBaseValue(rarityBaseArray, rarity); float[] values = new float[TowerLevelCount]; for (int i = 0; i < values.Length; i++) { values[i] = baseValue + perLevel * i; } return values; } private static float[] BuildConstantLevelFloatArray(float value) { float[] values = new float[TowerLevelCount]; for (int i = 0; i < values.Length; i++) { values[i] = value; } return values; } private static int ResolveRarityBaseValue(int[] rarityBaseArray, RarityType rarity) { if (rarityBaseArray == null || rarityBaseArray.Length <= 0) { return 0; } int rarityIndex = Mathf.Clamp((int)rarity - 1, 0, rarityBaseArray.Length - 1); return rarityBaseArray[rarityIndex]; } private static float ResolveRarityBaseValue(float[] rarityBaseArray, RarityType rarity) { if (rarityBaseArray == null || rarityBaseArray.Length <= 0) { return 0f; } int rarityIndex = Mathf.Clamp((int)rarity - 1, 0, rarityBaseArray.Length - 1); return rarityBaseArray[rarityIndex]; } private static TagType[] MergeTags(params TagType[][] sources) { HashSet uniqueTags = new HashSet(); if (sources != null) { for (int i = 0; i < sources.Length; i++) { TagType[] tags = sources[i]; if (tags == null) { continue; } for (int j = 0; j < tags.Length; j++) { uniqueTags.Add(tags[j]); } } } TagType[] mergedTags = new TagType[uniqueTags.Count]; uniqueTags.CopyTo(mergedTags); return mergedTags; } private static RarityType ResolveAverageRarity(RarityType muzzleRarity, RarityType bearingRarity, RarityType baseRarity) { float avg = ((int)muzzleRarity + (int)bearingRarity + (int)baseRarity) / 3f; int rounded = Mathf.RoundToInt(avg); int clamped = Mathf.Clamp(rounded, (int)RarityType.White, (int)RarityType.Red); return (RarityType)clamped; } private IDataTable EnsureMuzzleTable() { _drMuzzleComp ??= GameEntry.DataTable.GetDataTable(); return _drMuzzleComp; } private IDataTable EnsureBearingTable() { _drBearingComp ??= GameEntry.DataTable.GetDataTable(); return _drBearingComp; } private IDataTable EnsureBaseTable() { _drBaseComp ??= GameEntry.DataTable.GetDataTable(); return _drBaseComp; } private void EnsureInitialized() { if (_initialized) { return; } OnInit(); } private long AllocateInstanceId() { if (_nextInstanceId < 1) { _nextInstanceId = 1; } return _nextInstanceId++; } private void RebuildNextInstanceId() { long maxInstanceId = 0; if (_inventory.Towers != null) { foreach (var item in _inventory.Towers) { if (item != null) { maxInstanceId = Math.Max(maxInstanceId, item.InstanceId); } } } if (_inventory.MuzzleComponents != null) { foreach (var item in _inventory.MuzzleComponents) { if (item != null) { maxInstanceId = Math.Max(maxInstanceId, item.InstanceId); } } } if (_inventory.BearingComponents != null) { foreach (var item in _inventory.BearingComponents) { if (item != null) { maxInstanceId = Math.Max(maxInstanceId, item.InstanceId); } } } if (_inventory.BaseComponents != null) { foreach (var item in _inventory.BaseComponents) { if (item != null) { maxInstanceId = Math.Max(maxInstanceId, item.InstanceId); } } } _nextInstanceId = Math.Max(1, maxInstanceId + 1); } private void NormalizeParticipantState() { if (_inventory == null) { return; } _inventory.ParticipantTowerInstanceIds ??= new List(); Dictionary towerMap = new Dictionary(); if (_inventory.Towers != null) { for (int i = 0; i < _inventory.Towers.Count; i++) { TowerItemData tower = _inventory.Towers[i]; if (tower == null || tower.InstanceId <= 0) { continue; } tower.IsParticipatingInCombat = false; towerMap[tower.InstanceId] = tower; } } List normalizedIds = new List(_inventory.ParticipantTowerInstanceIds.Count); HashSet uniqueIds = new HashSet(); for (int i = 0; i < _inventory.ParticipantTowerInstanceIds.Count; i++) { if (normalizedIds.Count >= MaxParticipantTowerCount) { break; } long id = _inventory.ParticipantTowerInstanceIds[i]; if (id <= 0 || !uniqueIds.Add(id)) { continue; } if (!towerMap.TryGetValue(id, out TowerItemData tower)) { continue; } tower.IsParticipatingInCombat = true; normalizedIds.Add(id); } _inventory.ParticipantTowerInstanceIds = normalizedIds; } private static BackpackInventoryData CloneInventory(BackpackInventoryData source) { BackpackInventoryData cloned = new BackpackInventoryData(); if (source == null) { return cloned; } cloned.Gold = Mathf.Max(0, source.Gold); if (source.MuzzleComponents != null) { for (int i = 0; i < source.MuzzleComponents.Count; i++) { MuzzleCompItemData item = source.MuzzleComponents[i]; if (item != null) { cloned.MuzzleComponents.Add(CloneMuzzleComp(item)); } } } if (source.BearingComponents != null) { for (int i = 0; i < source.BearingComponents.Count; i++) { BearingCompItemData item = source.BearingComponents[i]; if (item != null) { cloned.BearingComponents.Add(CloneBearingComp(item)); } } } if (source.BaseComponents != null) { for (int i = 0; i < source.BaseComponents.Count; i++) { BaseCompItemData item = source.BaseComponents[i]; if (item != null) { cloned.BaseComponents.Add(CloneBaseComp(item)); } } } if (source.Towers != null) { for (int i = 0; i < source.Towers.Count; i++) { TowerItemData item = source.Towers[i]; if (item != null) { cloned.Towers.Add(CloneTower(item)); } } } if (source.ParticipantTowerInstanceIds != null) { for (int i = 0; i < source.ParticipantTowerInstanceIds.Count; i++) { long id = source.ParticipantTowerInstanceIds[i]; if (id > 0) { cloned.ParticipantTowerInstanceIds.Add(id); } } } return cloned; } private static MuzzleCompItemData CloneMuzzleComp(MuzzleCompItemData source) { return new MuzzleCompItemData { InstanceId = source.InstanceId, ConfigId = source.ConfigId, Name = source.Name, Rarity = source.Rarity, Endurance = source.Endurance, IsAssembledIntoTower = source.IsAssembledIntoTower, Constraint = source.Constraint, Tags = CloneTags(source.Tags), AttackDamage = CloneIntArray(source.AttackDamage), DamageRandomRate = source.DamageRandomRate, AttackMethodType = source.AttackMethodType }; } private static BearingCompItemData CloneBearingComp(BearingCompItemData source) { return new BearingCompItemData { InstanceId = source.InstanceId, ConfigId = source.ConfigId, Name = source.Name, Rarity = source.Rarity, Endurance = source.Endurance, IsAssembledIntoTower = source.IsAssembledIntoTower, Constraint = source.Constraint, Tags = CloneTags(source.Tags), RotateSpeed = CloneFloatArray(source.RotateSpeed), AttackRange = CloneFloatArray(source.AttackRange) }; } private static BaseCompItemData CloneBaseComp(BaseCompItemData source) { return new BaseCompItemData { InstanceId = source.InstanceId, ConfigId = source.ConfigId, Name = source.Name, Rarity = source.Rarity, Endurance = source.Endurance, IsAssembledIntoTower = source.IsAssembledIntoTower, Constraint = source.Constraint, Tags = CloneTags(source.Tags), AttackSpeed = CloneFloatArray(source.AttackSpeed), AttackPropertyType = source.AttackPropertyType }; } private static TowerItemData CloneTower(TowerItemData source) { return new TowerItemData { InstanceId = source.InstanceId, Name = source.Name, Rarity = source.Rarity, IsParticipatingInCombat = source.IsParticipatingInCombat, MuzzleComponentInstanceId = source.MuzzleComponentInstanceId, BearingComponentInstanceId = source.BearingComponentInstanceId, BaseComponentInstanceId = source.BaseComponentInstanceId, Stats = CloneTowerStats(source.Stats) }; } private static TowerStatsData CloneTowerStats(TowerStatsData source) { if (source == null) { return new TowerStatsData(); } return new TowerStatsData { AttackDamage = CloneIntArray(source.AttackDamage), DamageRandomRate = source.DamageRandomRate, RotateSpeed = CloneFloatArray(source.RotateSpeed), AttackRange = CloneFloatArray(source.AttackRange), AttackSpeed = CloneFloatArray(source.AttackSpeed), AttackMethodType = source.AttackMethodType, AttackPropertyType = source.AttackPropertyType, Tags = CloneTags(source.Tags) }; } private static int[] CloneIntArray(int[] source) { return source != null ? (int[])source.Clone() : Array.Empty(); } private static float[] CloneFloatArray(float[] source) { return source != null ? (float[])source.Clone() : Array.Empty(); } private static TagType[] CloneTags(TagType[] source) { return source != null ? (TagType[])source.Clone() : Array.Empty(); } } }