diff --git a/.gitignore b/.gitignore index 7dcfacd..981c180 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,4 @@ AGENTS.md /[Aa]ssets/RawResources /[Aa]ssets/RawResources.meta /docs/TODO.md +/.dotnet diff --git a/Assets/GameMain/Scripts/Components/DefenseTowerController.cs b/Assets/GameMain/Scripts/Components/TowerController.cs similarity index 98% rename from Assets/GameMain/Scripts/Components/DefenseTowerController.cs rename to Assets/GameMain/Scripts/Components/TowerController.cs index 7bcc268..1addfde 100644 --- a/Assets/GameMain/Scripts/Components/DefenseTowerController.cs +++ b/Assets/GameMain/Scripts/Components/TowerController.cs @@ -5,7 +5,7 @@ using UnityEngine; namespace Components { [DisallowMultipleComponent] - public class DefenseTowerController : MonoBehaviour + public class TowerController : MonoBehaviour { private const string AttackRangeIndicatorObjectName = "AttackRangeIndicator"; private const int MinTowerLevel = 0; @@ -35,12 +35,12 @@ namespace Components ResolveComponents(); } - public void OnInit(DefenseTowerStatsData stats) + public void OnInit(TowerStatsData stats) { OnInit(stats, MinTowerLevel); } - public void OnInit(DefenseTowerStatsData stats, int towerLevel) + public void OnInit(TowerStatsData stats, int towerLevel) { ResolveComponents(); if (stats == null) diff --git a/Assets/GameMain/Scripts/Components/DefenseTowerController.cs.meta b/Assets/GameMain/Scripts/Components/TowerController.cs.meta similarity index 100% rename from Assets/GameMain/Scripts/Components/DefenseTowerController.cs.meta rename to Assets/GameMain/Scripts/Components/TowerController.cs.meta diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatResourceManager.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatResourceManager.cs index fc42020..b77e002 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatResourceManager.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatResourceManager.cs @@ -54,7 +54,7 @@ namespace GeometryTD.CustomComponent MuzzleComponents = new List(_rewardInventory.MuzzleComponents), BearingComponents = new List(_rewardInventory.BearingComponents), BaseComponents = new List(_rewardInventory.BaseComponents), - Towers = new List(_rewardInventory.Towers) + Towers = new List(_rewardInventory.Towers) }; } diff --git a/Assets/GameMain/Scripts/CustomComponent/PlayerInventoryComponent.cs b/Assets/GameMain/Scripts/CustomComponent/PlayerInventoryComponent.cs index ad0dea9..d24b58e 100644 --- a/Assets/GameMain/Scripts/CustomComponent/PlayerInventoryComponent.cs +++ b/Assets/GameMain/Scripts/CustomComponent/PlayerInventoryComponent.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using GameFramework.DataTable; +using GeometryTD.DataTable; using GeometryTD.Definition; using GeometryTD.UI; using UnityEngine; @@ -8,9 +11,13 @@ namespace GeometryTD.CustomComponent { public class PlayerInventoryComponent : GameFrameworkComponent { + private const int TowerLevelCount = 5; private BackpackInventoryData _inventory = new BackpackInventoryData(); private long _nextInstanceId = 1; private bool _initialized; + private IDataTable _drMuzzleComp; + private IDataTable _drBearingComp; + private IDataTable _drBaseComp; public int Gold { @@ -115,13 +122,13 @@ namespace GeometryTD.CustomComponent { for (int i = 0; i < gainedInventory.Towers.Count; i++) { - DefenseTowerItemData source = gainedInventory.Towers[i]; + TowerItemData source = gainedInventory.Towers[i]; if (source == null) { continue; } - DefenseTowerItemData cloned = CloneTower(source); + TowerItemData cloned = CloneTower(source); cloned.InstanceId = AllocateInstanceId(); _inventory.Towers.Add(cloned); gainedTowerCount++; @@ -171,6 +178,58 @@ namespace GeometryTD.CustomComponent _inventory.Gold += resolvedGain; } + 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(); @@ -180,27 +239,250 @@ namespace GeometryTD.CustomComponent return 0; } + Dictionary muzzleMap = BuildComponentMap(_inventory.MuzzleComponents); + Dictionary bearingMap = BuildComponentMap(_inventory.BearingComponents); + Dictionary baseMap = BuildComponentMap(_inventory.BaseComponents); + int affectedCount = 0; - for (int i = 0; i < _inventory.Towers.Count; i++) + foreach (var tower in _inventory.Towers) { - DefenseTowerItemData tower = _inventory.Towers[i]; if (tower == null) { continue; } - float originalEndurance = tower.Endurance; - float nextEndurance = Mathf.Clamp(originalEndurance - resolvedLoss, 0f, 100f); - if (nextEndurance >= originalEndurance) + 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; } - tower.Endurance = nextEndurance; - affectedCount++; + map[component.InstanceId] = component; } - return affectedCount; + 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 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() @@ -228,9 +510,8 @@ namespace GeometryTD.CustomComponent long maxInstanceId = 0; if (_inventory.Towers != null) { - for (int i = 0; i < _inventory.Towers.Count; i++) + foreach (var item in _inventory.Towers) { - DefenseTowerItemData item = _inventory.Towers[i]; if (item != null) { maxInstanceId = Math.Max(maxInstanceId, item.InstanceId); @@ -240,9 +521,8 @@ namespace GeometryTD.CustomComponent if (_inventory.MuzzleComponents != null) { - for (int i = 0; i < _inventory.MuzzleComponents.Count; i++) + foreach (var item in _inventory.MuzzleComponents) { - MuzzleCompItemData item = _inventory.MuzzleComponents[i]; if (item != null) { maxInstanceId = Math.Max(maxInstanceId, item.InstanceId); @@ -252,9 +532,8 @@ namespace GeometryTD.CustomComponent if (_inventory.BearingComponents != null) { - for (int i = 0; i < _inventory.BearingComponents.Count; i++) + foreach (var item in _inventory.BearingComponents) { - BearingCompItemData item = _inventory.BearingComponents[i]; if (item != null) { maxInstanceId = Math.Max(maxInstanceId, item.InstanceId); @@ -264,9 +543,8 @@ namespace GeometryTD.CustomComponent if (_inventory.BaseComponents != null) { - for (int i = 0; i < _inventory.BaseComponents.Count; i++) + foreach (var item in _inventory.BaseComponents) { - BaseCompItemData item = _inventory.BaseComponents[i]; if (item != null) { maxInstanceId = Math.Max(maxInstanceId, item.InstanceId); @@ -327,7 +605,7 @@ namespace GeometryTD.CustomComponent { for (int i = 0; i < source.Towers.Count; i++) { - DefenseTowerItemData item = source.Towers[i]; + TowerItemData item = source.Towers[i]; if (item != null) { cloned.Towers.Add(CloneTower(item)); @@ -347,6 +625,7 @@ namespace GeometryTD.CustomComponent Name = source.Name, Rarity = source.Rarity, Endurance = source.Endurance, + IsAssembledIntoTower = source.IsAssembledIntoTower, Constraint = source.Constraint, Tags = CloneTags(source.Tags), AttackDamage = CloneIntArray(source.AttackDamage), @@ -364,6 +643,7 @@ namespace GeometryTD.CustomComponent Name = source.Name, Rarity = source.Rarity, Endurance = source.Endurance, + IsAssembledIntoTower = source.IsAssembledIntoTower, Constraint = source.Constraint, Tags = CloneTags(source.Tags), RotateSpeed = CloneFloatArray(source.RotateSpeed), @@ -380,6 +660,7 @@ namespace GeometryTD.CustomComponent Name = source.Name, Rarity = source.Rarity, Endurance = source.Endurance, + IsAssembledIntoTower = source.IsAssembledIntoTower, Constraint = source.Constraint, Tags = CloneTags(source.Tags), AttackSpeed = CloneFloatArray(source.AttackSpeed), @@ -387,14 +668,13 @@ namespace GeometryTD.CustomComponent }; } - private static DefenseTowerItemData CloneTower(DefenseTowerItemData source) + private static TowerItemData CloneTower(TowerItemData source) { - return new DefenseTowerItemData + return new TowerItemData { InstanceId = source.InstanceId, Name = source.Name, Rarity = source.Rarity, - Endurance = source.Endurance, MuzzleComponentInstanceId = source.MuzzleComponentInstanceId, BearingComponentInstanceId = source.BearingComponentInstanceId, BaseComponentInstanceId = source.BaseComponentInstanceId, @@ -402,20 +682,20 @@ namespace GeometryTD.CustomComponent }; } - private static DefenseTowerStatsData CloneTowerStats(DefenseTowerStatsData source) + private static TowerStatsData CloneTowerStats(TowerStatsData source) { if (source == null) { - return new DefenseTowerStatsData(); + return new TowerStatsData(); } - return new DefenseTowerStatsData + return new TowerStatsData { - AttackDamage = source.AttackDamage, + AttackDamage = CloneIntArray(source.AttackDamage), DamageRandomRate = source.DamageRandomRate, - RotateSpeed = source.RotateSpeed, - AttackRange = source.AttackRange, - AttackSpeed = source.AttackSpeed, + RotateSpeed = CloneFloatArray(source.RotateSpeed), + AttackRange = CloneFloatArray(source.AttackRange), + AttackSpeed = CloneFloatArray(source.AttackSpeed), AttackMethodType = source.AttackMethodType, AttackPropertyType = source.AttackPropertyType, Tags = CloneTags(source.Tags) @@ -437,4 +717,4 @@ namespace GeometryTD.CustomComponent return source != null ? (TagType[])source.Clone() : Array.Empty(); } } -} +} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Definition/DataStruct/BackpackInventoryData.cs b/Assets/GameMain/Scripts/Definition/DataStruct/BackpackInventoryData.cs index dfdc0b9..6453bd4 100644 --- a/Assets/GameMain/Scripts/Definition/DataStruct/BackpackInventoryData.cs +++ b/Assets/GameMain/Scripts/Definition/DataStruct/BackpackInventoryData.cs @@ -32,6 +32,6 @@ namespace GeometryTD.Definition /// /// 背包中的防御塔实例。 /// - public List Towers { get; set; } = new List(); + public List Towers { get; set; } = new List(); } } diff --git a/Assets/GameMain/Scripts/Definition/DataStruct/ImpactData.cs b/Assets/GameMain/Scripts/Definition/DataStruct/ImpactData.cs index 313cdd1..1db0f0b 100644 --- a/Assets/GameMain/Scripts/Definition/DataStruct/ImpactData.cs +++ b/Assets/GameMain/Scripts/Definition/DataStruct/ImpactData.cs @@ -5,49 +5,20 @@ namespace GeometryTD.Definition [StructLayout(LayoutKind.Auto)] public struct ImpactData { - private readonly CampType m_Camp; - private readonly int m_HP; - private readonly int m_Attack; - private readonly int m_Defense; - public ImpactData(CampType camp, int hp, int attack, int defense) { - m_Camp = camp; - m_HP = hp; - m_Attack = attack; - m_Defense = defense; + Camp = camp; + HP = hp; + Attack = attack; + Defense = defense; } - public CampType Camp - { - get - { - return m_Camp; - } - } + public CampType Camp { get; } - public int HP - { - get - { - return m_HP; - } - } + public int HP { get; } - public int Attack - { - get - { - return m_Attack; - } - } + public int Attack { get; } - public int Defense - { - get - { - return m_Defense; - } - } + public int Defense { get; } } -} +} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Definition/DataStruct/TowerCompItemData.cs b/Assets/GameMain/Scripts/Definition/DataStruct/TowerCompItemData.cs index e9bea28..e024274 100644 --- a/Assets/GameMain/Scripts/Definition/DataStruct/TowerCompItemData.cs +++ b/Assets/GameMain/Scripts/Definition/DataStruct/TowerCompItemData.cs @@ -37,6 +37,7 @@ namespace GeometryTD.Definition /// 组件当前耐久(0~100)。 /// public float Endurance { get; set; } = 100f; + public bool IsAssembledIntoTower { get; set; } /// /// 组件约束(先沿用 DataTable 原定义)。 @@ -94,4 +95,4 @@ namespace GeometryTD.Definition SlotType = TowerCompSlotType.Accessory; } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Definition/DataStruct/DefenseTowerItemData.cs b/Assets/GameMain/Scripts/Definition/DataStruct/TowerItemData.cs similarity index 82% rename from Assets/GameMain/Scripts/Definition/DataStruct/DefenseTowerItemData.cs rename to Assets/GameMain/Scripts/Definition/DataStruct/TowerItemData.cs index de9140f..3554649 100644 --- a/Assets/GameMain/Scripts/Definition/DataStruct/DefenseTowerItemData.cs +++ b/Assets/GameMain/Scripts/Definition/DataStruct/TowerItemData.cs @@ -7,10 +7,10 @@ namespace GeometryTD.Definition /// 注意:这里是塔实例的独立值,不通过组件引用实时计算。 /// [Serializable] - public sealed class DefenseTowerStatsData + public sealed class TowerStatsData { public int[] AttackDamage { get; set; } - public float[] DamageRandomRate { get; set; } + public float DamageRandomRate { get; set; } public float[] RotateSpeed { get; set; } public float[] AttackRange { get; set; } public float[] AttackSpeed { get; set; } @@ -23,7 +23,7 @@ namespace GeometryTD.Definition /// 背包内防御塔实例数据。 /// [Serializable] - public sealed class DefenseTowerItemData + public sealed class TowerItemData { /// /// 防御塔实例唯一 Id。 @@ -39,12 +39,7 @@ namespace GeometryTD.Definition /// 防御塔品质。 /// public RarityType Rarity { get; set; } - - /// - /// 当前耐久(0~100)。 - /// - public float Endurance { get; set; } = 100f; - + /// /// 构成该防御塔的枪口组件实例 Id。 /// @@ -63,6 +58,6 @@ namespace GeometryTD.Definition /// /// 防御塔独立属性,不依赖组件对象引用。 /// - public DefenseTowerStatsData Stats { get; set; } = new DefenseTowerStatsData(); + public TowerStatsData Stats { get; set; } = new TowerStatsData(); } } diff --git a/Assets/GameMain/Scripts/Definition/DataStruct/DefenseTowerItemData.cs.meta b/Assets/GameMain/Scripts/Definition/DataStruct/TowerItemData.cs.meta similarity index 100% rename from Assets/GameMain/Scripts/Definition/DataStruct/DefenseTowerItemData.cs.meta rename to Assets/GameMain/Scripts/Definition/DataStruct/TowerItemData.cs.meta diff --git a/Assets/GameMain/Scripts/Entity/EntityData/DefenseTowerData.cs b/Assets/GameMain/Scripts/Entity/EntityData/TowerData.cs similarity index 54% rename from Assets/GameMain/Scripts/Entity/EntityData/DefenseTowerData.cs rename to Assets/GameMain/Scripts/Entity/EntityData/TowerData.cs index 00ac405..d69252a 100644 --- a/Assets/GameMain/Scripts/Entity/EntityData/DefenseTowerData.cs +++ b/Assets/GameMain/Scripts/Entity/EntityData/TowerData.cs @@ -5,25 +5,25 @@ using UnityEngine; namespace GeometryTD.Entity.EntityData { [Serializable] - public class DefenseTowerData : EntityDataBase + public class TowerData : EntityDataBase { - [SerializeField] private DefenseTowerStatsData _stats = new DefenseTowerStatsData(); + [SerializeField] private TowerStatsData _stats = new TowerStatsData(); [SerializeField] private int _towerLevel = 0; - public DefenseTowerData(int entityId, int typeId, Vector3 position, Quaternion rotation, - DefenseTowerStatsData stats, int towerLevel = 0) + public TowerData(int entityId, int typeId, Vector3 position, Quaternion rotation, + TowerStatsData stats, int towerLevel = 0) : base(entityId, typeId) { Position = position; Rotation = rotation; - _stats = stats ?? new DefenseTowerStatsData(); + _stats = stats ?? new TowerStatsData(); _towerLevel = Mathf.Max(0, towerLevel); } - public DefenseTowerStatsData Stats + public TowerStatsData Stats { get => _stats; - set => _stats = value ?? new DefenseTowerStatsData(); + set => _stats = value ?? new TowerStatsData(); } public int TowerLevel diff --git a/Assets/GameMain/Scripts/Entity/EntityData/DefenseTowerData.cs.meta b/Assets/GameMain/Scripts/Entity/EntityData/TowerData.cs.meta similarity index 100% rename from Assets/GameMain/Scripts/Entity/EntityData/DefenseTowerData.cs.meta rename to Assets/GameMain/Scripts/Entity/EntityData/TowerData.cs.meta diff --git a/Assets/GameMain/Scripts/Entity/EntityExtension.cs b/Assets/GameMain/Scripts/Entity/EntityExtension.cs index ad0c1ba..b03515c 100644 --- a/Assets/GameMain/Scripts/Entity/EntityExtension.cs +++ b/Assets/GameMain/Scripts/Entity/EntityExtension.cs @@ -46,9 +46,9 @@ namespace GeometryTD entityComponent.ShowEntity(typeof(EnemyEntity), "Enemy", Constant.AssetPriority.EnemyAsset, data); } - public static void ShowDefenseTower(this EntityComponent entityComponent, DefenseTowerData data) + public static void ShowDefenseTower(this EntityComponent entityComponent, TowerData data) { - entityComponent.ShowEntity(typeof(DefenseTowerEntity), "Tower", Constant.AssetPriority.EnemyAsset, data); + entityComponent.ShowEntity(typeof(TowerEntity), "Tower", Constant.AssetPriority.EnemyAsset, data); } public static void ShowBullet(this EntityComponent entityComponent, BulletData data) diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/TowerEntity.cs similarity index 84% rename from Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs rename to Assets/GameMain/Scripts/Entity/EntityLogic/TowerEntity.cs index 2856455..3cfe681 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/TowerEntity.cs @@ -5,16 +5,16 @@ using UnityGameFramework.Runtime; namespace GeometryTD.Entity { - public class DefenseTowerEntity : EntityBase + public class TowerEntity : EntityBase { - private DefenseTowerController _towerController; + private TowerController _towerController; public void SetAttackRangeVisible(bool visible) { _towerController?.SetAttackRangeVisible(visible); } - public bool TryApplyStats(DefenseTowerStatsData stats, int towerLevel) + public bool TryApplyStats(TowerStatsData stats, int towerLevel) { if (_towerController == null || stats == null) { @@ -29,7 +29,7 @@ namespace GeometryTD.Entity { base.OnInit(userData); - _towerController = GetComponent(); + _towerController = GetComponent(); if (_towerController == null) { Log.Error("DefenseTowerController is missing on tower entity '{0}'.", name); @@ -45,7 +45,7 @@ namespace GeometryTD.Entity return; } - if (userData is not DefenseTowerData towerData) + if (userData is not TowerData towerData) { Log.Warning("DefenseTowerData is invalid for tower entity '{0}'.", Id); _towerController.OnReset(); diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs.meta b/Assets/GameMain/Scripts/Entity/EntityLogic/TowerEntity.cs.meta similarity index 100% rename from Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs.meta rename to Assets/GameMain/Scripts/Entity/EntityLogic/TowerEntity.cs.meta diff --git a/Assets/GameMain/Scripts/Event/RepoForm/RepoCombineRequestedEventArgs.cs b/Assets/GameMain/Scripts/Event/RepoForm/RepoCombineRequestedEventArgs.cs new file mode 100644 index 0000000..2fef81e --- /dev/null +++ b/Assets/GameMain/Scripts/Event/RepoForm/RepoCombineRequestedEventArgs.cs @@ -0,0 +1,32 @@ +using GameFramework; +using GameFramework.Event; + +namespace GeometryTD.CustomEvent +{ + public sealed class RepoCombineRequestedEventArgs : GameEventArgs + { + public static int EventId => typeof(RepoCombineRequestedEventArgs).GetHashCode(); + + public override int Id => EventId; + + public long MuzzleItemId { get; private set; } + public long BearingItemId { get; private set; } + public long BaseItemId { get; private set; } + + public static RepoCombineRequestedEventArgs Create(long muzzleItemId, long bearingItemId, long baseItemId) + { + RepoCombineRequestedEventArgs args = ReferencePool.Acquire(); + args.MuzzleItemId = muzzleItemId; + args.BearingItemId = bearingItemId; + args.BaseItemId = baseItemId; + return args; + } + + public override void Clear() + { + MuzzleItemId = 0; + BearingItemId = 0; + BaseItemId = 0; + } + } +} diff --git a/Assets/GameMain/Scripts/Event/RepoForm/RepoCombineRequestedEventArgs.cs.meta b/Assets/GameMain/Scripts/Event/RepoForm/RepoCombineRequestedEventArgs.cs.meta new file mode 100644 index 0000000..c10305e --- /dev/null +++ b/Assets/GameMain/Scripts/Event/RepoForm/RepoCombineRequestedEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93ff2618b5b54f6f9058ec17b9bf3329 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs b/Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs index 7e7e493..2c83bc2 100644 --- a/Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs +++ b/Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs @@ -16,7 +16,7 @@ namespace GeometryTD.Map private readonly Dictionary _towerEntityIdByFoundationCell = new(); private readonly Dictionary _foundationCellByTowerEntityId = new(); - private readonly Dictionary _towerStatsByEntityId = new(); + private readonly Dictionary _towerStatsByEntityId = new(); private readonly Dictionary _towerLevelByEntityId = new(); private readonly List _towerEntityIdBuffer = new(); @@ -30,8 +30,8 @@ namespace GeometryTD.Map return false; } - DefenseTowerStatsData towerStats = - _towerStatsByEntityId.TryGetValue(towerEntityId, out DefenseTowerStatsData cachedStats) + TowerStatsData towerStats = + _towerStatsByEntityId.TryGetValue(towerEntityId, out TowerStatsData cachedStats) ? cachedStats : null; int currentLevel = GetTowerLevel(towerEntityId); @@ -61,7 +61,7 @@ namespace GeometryTD.Map return false; } - DefenseTowerStatsData towerStats = BuildTowerStats(buildIndex); + TowerStatsData towerStats = BuildTowerStats(buildIndex); if (!TryShowTowerEntity(foundationCell, towerStats, towerTypeId, tilemap, out int newTowerEntityId)) { addCoin?.Invoke(buildCost); @@ -87,8 +87,8 @@ namespace GeometryTD.Map return false; } - DefenseTowerStatsData towerStats = - _towerStatsByEntityId.TryGetValue(towerEntityId, out DefenseTowerStatsData cachedStats) + TowerStatsData towerStats = + _towerStatsByEntityId.TryGetValue(towerEntityId, out TowerStatsData cachedStats) ? CloneTowerStats(cachedStats) : BuildTowerStats(0); int currentTowerLevel = GetTowerLevel(towerEntityId); @@ -177,7 +177,7 @@ namespace GeometryTD.Map return Mathf.Max(0, buildTowerCosts[buildIndex]); } - private static bool TryShowTowerEntity(Vector3Int foundationCell, DefenseTowerStatsData towerStats, + private static bool TryShowTowerEntity(Vector3Int foundationCell, TowerStatsData towerStats, int towerTypeId, Tilemap tilemap, out int towerEntityId) { @@ -191,7 +191,7 @@ namespace GeometryTD.Map int typeId = towerTypeId > 0 ? towerTypeId : DefaultTowerTypeId; Vector3 towerPosition = tilemap != null ? tilemap.GetCellCenterWorld(foundationCell) : foundationCell; towerPosition.z = 0f; - var towerData = new DefenseTowerData(entityId, typeId, towerPosition, Quaternion.identity, towerStats, + var towerData = new TowerData(entityId, typeId, towerPosition, Quaternion.identity, towerStats, MinTowerLevel); GameEntry.Entity.ShowDefenseTower(towerData); @@ -213,15 +213,15 @@ namespace GeometryTD.Map } } - private static DefenseTowerStatsData BuildTowerStats(int buildIndex) + private static TowerStatsData BuildTowerStats(int buildIndex) { switch (buildIndex) { case 0: - return new DefenseTowerStatsData + return new TowerStatsData { AttackDamage = new[] { 200, 220, 240, 260, 300 }, - DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f }, + DamageRandomRate = 0f, RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f }, AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f }, AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f }, @@ -230,10 +230,10 @@ namespace GeometryTD.Map Tags = Array.Empty() }; case 1: - return new DefenseTowerStatsData + return new TowerStatsData { AttackDamage = new[] { 200, 220, 240, 260, 300 }, - DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f }, + DamageRandomRate = 0f, RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f }, AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f }, AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f }, @@ -242,10 +242,10 @@ namespace GeometryTD.Map Tags = Array.Empty() }; case 2: - return new DefenseTowerStatsData + return new TowerStatsData { AttackDamage = new[] { 200, 220, 240, 260, 300 }, - DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f }, + DamageRandomRate = 0f, RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f }, AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f }, AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f }, @@ -254,10 +254,10 @@ namespace GeometryTD.Map Tags = Array.Empty() }; case 3: - return new DefenseTowerStatsData + return new TowerStatsData { AttackDamage = new[] { 200, 220, 240, 260, 300 }, - DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f }, + DamageRandomRate = 0f, RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f }, AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f }, AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f }, @@ -266,10 +266,10 @@ namespace GeometryTD.Map Tags = Array.Empty() }; default: - return new DefenseTowerStatsData + return new TowerStatsData { AttackDamage = new[] { 200, 220, 240, 260, 300 }, - DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f }, + DamageRandomRate = 0f, RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f }, AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f }, AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f }, @@ -280,7 +280,7 @@ namespace GeometryTD.Map } } - private static DefenseTowerStatsData CloneTowerStats(DefenseTowerStatsData source) + private static TowerStatsData CloneTowerStats(TowerStatsData source) { if (source == null) { @@ -288,12 +288,10 @@ namespace GeometryTD.Map } TagType[] copiedTags = source.Tags != null ? (TagType[])source.Tags.Clone() : Array.Empty(); - return new DefenseTowerStatsData + return new TowerStatsData { AttackDamage = source.AttackDamage != null ? (int[])source.AttackDamage.Clone() : Array.Empty(), - DamageRandomRate = source.DamageRandomRate != null - ? (float[])source.DamageRandomRate.Clone() - : Array.Empty(), + DamageRandomRate = source.DamageRandomRate, RotateSpeed = source.RotateSpeed != null ? (float[])source.RotateSpeed.Clone() : Array.Empty(), AttackRange = source.AttackRange != null ? (float[])source.AttackRange.Clone() : Array.Empty(), AttackSpeed = source.AttackSpeed != null ? (float[])source.AttackSpeed.Clone() : Array.Empty(), @@ -313,11 +311,10 @@ namespace GeometryTD.Map return Mathf.Clamp(towerLevel, MinTowerLevel, MaxTowerLevel); } - private static int ResolveMaxTowerLevel(DefenseTowerStatsData stats) + private static int ResolveMaxTowerLevel(TowerStatsData stats) { int maxCount = Mathf.Max( GetLength(stats?.AttackDamage), - GetLength(stats?.DamageRandomRate), GetLength(stats?.RotateSpeed), GetLength(stats?.AttackRange), GetLength(stats?.AttackSpeed)); @@ -329,14 +326,14 @@ namespace GeometryTD.Map return Mathf.Clamp(maxCount - 1, MinTowerLevel, MaxTowerLevel); } - private static bool TryApplyTowerStats(int towerEntityId, DefenseTowerStatsData towerStats, int towerLevel) + private static bool TryApplyTowerStats(int towerEntityId, TowerStatsData towerStats, int towerLevel) { if (towerEntityId == 0 || towerStats == null || GameEntry.Entity == null) { return false; } - if (GameEntry.Entity.GetGameEntity(towerEntityId) is not DefenseTowerEntity towerEntity) + if (GameEntry.Entity.GetGameEntity(towerEntityId) is not TowerEntity towerEntity) { return false; } @@ -349,4 +346,4 @@ namespace GeometryTD.Map return values != null ? values.Length : 0; } } -} +} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Scene/Map/TowerSelectionPresenter.cs b/Assets/GameMain/Scripts/Scene/Map/TowerSelectionPresenter.cs index 4e402be..89a60a2 100644 --- a/Assets/GameMain/Scripts/Scene/Map/TowerSelectionPresenter.cs +++ b/Assets/GameMain/Scripts/Scene/Map/TowerSelectionPresenter.cs @@ -120,7 +120,7 @@ namespace GeometryTD.Map } EntityBase gameEntity = GameEntry.Entity.GetGameEntity(towerEntityId); - if (gameEntity is not DefenseTowerEntity towerEntity) + if (gameEntity is not TowerEntity towerEntity) { return false; } diff --git a/Assets/GameMain/Scripts/UI/Combat/Controller/CombatFinishFormController.cs b/Assets/GameMain/Scripts/UI/Combat/Controller/CombatFinishFormController.cs index 5c67cdd..e6cf017 100644 --- a/Assets/GameMain/Scripts/UI/Combat/Controller/CombatFinishFormController.cs +++ b/Assets/GameMain/Scripts/UI/Combat/Controller/CombatFinishFormController.cs @@ -17,6 +17,7 @@ namespace GeometryTD.UI { public string Title; public string TypeText; + public string Description; public TagType[] Tags; } @@ -128,7 +129,11 @@ namespace GeometryTD.UI IconAreaContext = BuildIconAreaContext(tower) }); - AddItemDescSeed(tower.InstanceId, tower.Name, "Tower", + AddItemDescSeed( + tower.InstanceId, + tower.Name, + "Tower", + ItemDescUtility.BuildTowerDesc(tower.Stats) ?? string.Empty, tower.Stats != null ? tower.Stats.Tags : null); } } @@ -151,7 +156,12 @@ namespace GeometryTD.UI IconAreaContext = BuildIconAreaContext(item) }); - AddItemDescSeed(item.InstanceId, item.Name, BuildComponentTypeText(item.SlotType), item.Tags); + AddItemDescSeed( + item.InstanceId, + item.Name, + BuildComponentTypeText(item.SlotType), + ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty, + item.Tags); } } @@ -172,7 +182,12 @@ namespace GeometryTD.UI IconAreaContext = BuildIconAreaContext(item) }); - AddItemDescSeed(item.InstanceId, item.Name, BuildComponentTypeText(item.SlotType), item.Tags); + AddItemDescSeed( + item.InstanceId, + item.Name, + BuildComponentTypeText(item.SlotType), + ItemDescUtility.BuildBearingDesc(item) ?? string.Empty, + item.Tags); } } @@ -194,14 +209,19 @@ namespace GeometryTD.UI IconAreaContext = BuildIconAreaContext(item) }); - AddItemDescSeed(item.InstanceId, item.Name, BuildComponentTypeText(item.SlotType), item.Tags); + AddItemDescSeed( + item.InstanceId, + item.Name, + BuildComponentTypeText(item.SlotType), + ItemDescUtility.BuildBaseDesc(item) ?? string.Empty, + item.Tags); } } return itemContexts.ToArray(); } - private void AddItemDescSeed(long itemId, string title, string typeText, TagType[] tags) + private void AddItemDescSeed(long itemId, string title, string typeText, string description, TagType[] tags) { if (itemId <= 0) { @@ -212,6 +232,7 @@ namespace GeometryTD.UI { Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title, TypeText = typeText ?? string.Empty, + Description = description ?? string.Empty, Tags = CloneTags(tags) }; } @@ -233,7 +254,7 @@ namespace GeometryTD.UI }; } - private static IconAreaContext BuildIconAreaContext(DefenseTowerItemData tower) + private static IconAreaContext BuildIconAreaContext(TowerItemData tower) { if (tower == null) { @@ -308,7 +329,7 @@ namespace GeometryTD.UI { Title = seed.Title, TypeText = seed.TypeText, - Description = string.Empty, + Description = seed.Description ?? string.Empty, Price = 0, TargetPos = args.TargetPos, Tags = CloneTags(seed.Tags) diff --git a/Assets/GameMain/Scripts/UI/Game/Controller/RepoFormController.cs b/Assets/GameMain/Scripts/UI/Game/Controller/RepoFormController.cs index a13c0a4..6b0ac33 100644 --- a/Assets/GameMain/Scripts/UI/Game/Controller/RepoFormController.cs +++ b/Assets/GameMain/Scripts/UI/Game/Controller/RepoFormController.cs @@ -19,6 +19,7 @@ namespace GeometryTD.UI { public string Title; public string TypeText; + public string Description; public TagType[] Tags; } @@ -34,6 +35,7 @@ namespace GeometryTD.UI GameEntry.Event.Subscribe(RepoItemDetailRequestedEventArgs.EventId, OnRepoItemDetailRequested); GameEntry.Event.Subscribe(RepoItemDragEndedEventArgs.EventId, OnRepoItemDragEnded); GameEntry.Event.Subscribe(CombineSlotClickedEventArgs.EventId, OnCombineSlotClicked); + GameEntry.Event.Subscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested); GameEntry.Event.Subscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn); } @@ -42,6 +44,7 @@ namespace GeometryTD.UI GameEntry.Event.Unsubscribe(RepoItemDetailRequestedEventArgs.EventId, OnRepoItemDetailRequested); GameEntry.Event.Unsubscribe(RepoItemDragEndedEventArgs.EventId, OnRepoItemDragEnded); GameEntry.Event.Unsubscribe(CombineSlotClickedEventArgs.EventId, OnCombineSlotClicked); + GameEntry.Event.Unsubscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested); GameEntry.Event.Unsubscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn); } @@ -103,7 +106,7 @@ namespace GeometryTD.UI if (rawData.Inventory.Towers != null) { - foreach (DefenseTowerItemData tower in rawData.Inventory.Towers) + foreach (TowerItemData tower in rawData.Inventory.Towers) { if (tower == null) { @@ -117,7 +120,11 @@ namespace GeometryTD.UI ComponentSlotType = TowerCompSlotType.None, IconAreaContext = BuildIconAreaContext(tower) }); - AddItemDescSeed(tower.InstanceId, tower.Name, "Tower", + AddItemDescSeed( + tower.InstanceId, + tower.Name, + "Tower", + ItemDescUtility.BuildTowerDesc(tower.Stats) ?? string.Empty, tower.Stats != null ? tower.Stats.Tags : null); } } @@ -126,7 +133,7 @@ namespace GeometryTD.UI { foreach (MuzzleCompItemData item in rawData.Inventory.MuzzleComponents) { - if (item == null) + if (item == null || item.IsAssembledIntoTower) { continue; } @@ -138,7 +145,12 @@ namespace GeometryTD.UI ComponentSlotType = TowerCompSlotType.Muzzle, IconAreaContext = BuildIconAreaContext(item) }); - AddItemDescSeed(item.InstanceId, item.Name, BuildComponentTypeText(item.SlotType), item.Tags); + AddItemDescSeed( + item.InstanceId, + item.Name, + BuildComponentTypeText(item.SlotType), + ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty, + item.Tags); } } @@ -146,7 +158,7 @@ namespace GeometryTD.UI { foreach (BearingCompItemData item in rawData.Inventory.BearingComponents) { - if (item == null) + if (item == null || item.IsAssembledIntoTower) { continue; } @@ -158,7 +170,12 @@ namespace GeometryTD.UI ComponentSlotType = TowerCompSlotType.Bearing, IconAreaContext = BuildIconAreaContext(item) }); - AddItemDescSeed(item.InstanceId, item.Name, BuildComponentTypeText(item.SlotType), item.Tags); + AddItemDescSeed( + item.InstanceId, + item.Name, + BuildComponentTypeText(item.SlotType), + ItemDescUtility.BuildBearingDesc(item) ?? string.Empty, + item.Tags); } } @@ -166,7 +183,7 @@ namespace GeometryTD.UI { foreach (BaseCompItemData item in rawData.Inventory.BaseComponents) { - if (item == null) + if (item == null || item.IsAssembledIntoTower) { continue; } @@ -178,7 +195,12 @@ namespace GeometryTD.UI ComponentSlotType = TowerCompSlotType.Base, IconAreaContext = BuildIconAreaContext(item) }); - AddItemDescSeed(item.InstanceId, item.Name, BuildComponentTypeText(item.SlotType), item.Tags); + AddItemDescSeed( + item.InstanceId, + item.Name, + BuildComponentTypeText(item.SlotType), + ItemDescUtility.BuildBaseDesc(item) ?? string.Empty, + item.Tags); } } @@ -203,7 +225,7 @@ namespace GeometryTD.UI _itemContextMap[itemContext.InstanceId] = itemContext; } - private void AddItemDescSeed(long itemId, string title, string typeText, TagType[] tags) + private void AddItemDescSeed(long itemId, string title, string typeText, string description, TagType[] tags) { if (itemId <= 0) { @@ -214,6 +236,7 @@ namespace GeometryTD.UI { Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title, TypeText = typeText ?? string.Empty, + Description = description ?? string.Empty, Tags = CloneTags(tags) }; } @@ -235,7 +258,7 @@ namespace GeometryTD.UI }; } - private static IconAreaContext BuildIconAreaContext(DefenseTowerItemData tower) + private static IconAreaContext BuildIconAreaContext(TowerItemData tower) { if (tower == null) { @@ -302,7 +325,7 @@ namespace GeometryTD.UI { Title = seed.Title, TypeText = seed.TypeText, - Description = string.Empty, + Description = seed.Description ?? string.Empty, Price = 0, TargetPos = args.TargetPos, Tags = CloneTags(seed.Tags) @@ -367,6 +390,36 @@ namespace GeometryTD.UI this.CloseUI(); } + private void OnRepoCombineRequested(object sender, GameEventArgs e) + { + if (!IsEventFromCurrentForm(sender)) + { + return; + } + + if (!(e is RepoCombineRequestedEventArgs args)) + { + return; + } + + if (_useCase == null || Form == null) + { + return; + } + + if (!_useCase.TryAssembleTower(args.MuzzleItemId, args.BearingItemId, args.BaseItemId)) + { + return; + } + + RepoFormRawData latestRawData = _useCase.CreateInitialModel(); + RepoFormContext latestContext = BuildContext(latestRawData); + if (latestContext != null) + { + Form.RefreshUI(latestContext); + } + } + private bool IsEventFromCurrentForm(object sender) { if (Form == null) diff --git a/Assets/GameMain/Scripts/UI/Game/UseCase/RepoFormUseCase.cs b/Assets/GameMain/Scripts/UI/Game/UseCase/RepoFormUseCase.cs index 7d1de35..da62683 100644 --- a/Assets/GameMain/Scripts/UI/Game/UseCase/RepoFormUseCase.cs +++ b/Assets/GameMain/Scripts/UI/Game/UseCase/RepoFormUseCase.cs @@ -15,6 +15,20 @@ namespace GeometryTD.UI }; } + public bool TryAssembleTower(long muzzleItemId, long bearingItemId, long baseItemId) + { + if (GameEntry.PlayerInventory == null) + { + return false; + } + + return GameEntry.PlayerInventory.TryAssembleTower( + muzzleItemId, + bearingItemId, + baseItemId, + out _); + } + public static BackpackInventoryData SampleInventory() { BackpackInventoryData inventory = new BackpackInventoryData @@ -29,6 +43,7 @@ namespace GeometryTD.UI Name = "元素枪口", Rarity = RarityType.Green, Endurance = 90f, + IsAssembledIntoTower = true, AttackDamage = new[] { 20, 30, 40, 50, 80 }, DamageRandomRate = 0.05f, AttackMethodType = AttackMethodType.NormalBullet, @@ -36,6 +51,34 @@ namespace GeometryTD.UI Tags = new[] { TagType.Fire } }; inventory.MuzzleComponents.Add(muzzle); + inventory.MuzzleComponents.Add(new MuzzleCompItemData + { + InstanceId = 10002, + ConfigId = 2, + Name = "控制枪口", + Rarity = RarityType.Blue, + Endurance = 100f, + IsAssembledIntoTower = false, + AttackDamage = new[] { 30, 50, 70, 90, 100 }, + DamageRandomRate = 0.01f, + AttackMethodType = AttackMethodType.NormalBullet, + Constraint = string.Empty, + Tags = new[] { TagType.Ice, TagType.FreezeMask } + }); + inventory.MuzzleComponents.Add(new MuzzleCompItemData + { + InstanceId = 10003, + ConfigId = 3, + Name = "穿透枪口", + Rarity = RarityType.Purple, + Endurance = 97f, + IsAssembledIntoTower = false, + AttackDamage = new[] { 50, 55, 60, 80, 90 }, + DamageRandomRate = 0.02f, + AttackMethodType = AttackMethodType.NormalBullet, + Constraint = string.Empty, + Tags = new[] { TagType.Pierce, TagType.Crit } + }); BearingCompItemData bearing = new BearingCompItemData { @@ -44,12 +87,39 @@ namespace GeometryTD.UI Name = "元素轴承", Rarity = RarityType.Green, Endurance = 95f, + IsAssembledIntoTower = true, RotateSpeed = new[] { 10f, 12f, 13f, 14f, 15f }, AttackRange = new[] { 2f, 2f, 2f, 2f, 2f }, Constraint = string.Empty, Tags = new[] { TagType.Fire } }; inventory.BearingComponents.Add(bearing); + inventory.BearingComponents.Add(new BearingCompItemData + { + InstanceId = 20002, + ConfigId = 2, + Name = "控制轴承", + Rarity = RarityType.Blue, + Endurance = 100f, + IsAssembledIntoTower = false, + RotateSpeed = new[] { 20f, 25f, 30f, 32f, 35f }, + AttackRange = new[] { 6f, 6.5f, 7f, 8f, 8f }, + Constraint = string.Empty, + Tags = new[] { TagType.Ice, TagType.Shatter } + }); + inventory.BearingComponents.Add(new BearingCompItemData + { + InstanceId = 20003, + ConfigId = 3, + Name = "穿透轴承", + Rarity = RarityType.Purple, + Endurance = 96f, + IsAssembledIntoTower = false, + RotateSpeed = new[] { 60f, 70f, 80f, 90f, 100f }, + AttackRange = new[] { 4f, 4.5f, 5f, 5.5f, 6f }, + Constraint = string.Empty, + Tags = new[] { TagType.Pierce, TagType.Overpenetrate } + }); BaseCompItemData baseComp = new BaseCompItemData { @@ -58,26 +128,52 @@ namespace GeometryTD.UI Name = "元素底座", Rarity = RarityType.Green, Endurance = 88f, + IsAssembledIntoTower = true, AttackSpeed = new[] { 2f, 1.5f, 1f, 0.8f, 0.7f }, AttackPropertyType = AttackPropertyType.Fire, Constraint = string.Empty, Tags = new[] { TagType.Fire } }; inventory.BaseComponents.Add(baseComp); + inventory.BaseComponents.Add(new BaseCompItemData + { + InstanceId = 30002, + ConfigId = 2, + Name = "控制底座", + Rarity = RarityType.Blue, + Endurance = 100f, + IsAssembledIntoTower = false, + AttackSpeed = new[] { 4f, 4.2f, 4.4f, 4.6f, 4.8f }, + AttackPropertyType = AttackPropertyType.Ice, + Constraint = string.Empty, + Tags = new[] { TagType.Ice, TagType.AbsoluteZero } + }); + inventory.BaseComponents.Add(new BaseCompItemData + { + InstanceId = 30003, + ConfigId = 3, + Name = "穿透底座", + Rarity = RarityType.Purple, + Endurance = 95f, + IsAssembledIntoTower = false, + AttackSpeed = new[] { 1f, 1f, 1f, 1f, 1f }, + AttackPropertyType = AttackPropertyType.Physics, + Constraint = string.Empty, + Tags = new[] { TagType.Pierce, TagType.Execution } + }); - DefenseTowerItemData tower = new DefenseTowerItemData + TowerItemData tower = new TowerItemData { InstanceId = 90001, Name = "测试防御塔-A", Rarity = RarityType.Green, - Endurance = 92f, MuzzleComponentInstanceId = muzzle.InstanceId, BearingComponentInstanceId = bearing.InstanceId, BaseComponentInstanceId = baseComp.InstanceId, - Stats = new DefenseTowerStatsData + Stats = new TowerStatsData { AttackDamage = new[] { 200, 220, 240, 260, 300 }, - DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f }, + DamageRandomRate = 0f, RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f }, AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f }, AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f }, @@ -88,19 +184,18 @@ namespace GeometryTD.UI }; inventory.Towers.Add(tower); - inventory.Towers.Add(new DefenseTowerItemData + inventory.Towers.Add(new TowerItemData { InstanceId = 90002, Name = "测试防御塔-B", Rarity = RarityType.Blue, - Endurance = 100f, MuzzleComponentInstanceId = 0, BearingComponentInstanceId = 0, BaseComponentInstanceId = 0, - Stats = new DefenseTowerStatsData + Stats = new TowerStatsData { AttackDamage = new[] { 200, 220, 240, 260, 300 }, - DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f }, + DamageRandomRate = 0.1f, RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f }, AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f }, AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f }, diff --git a/Assets/GameMain/Scripts/UI/Game/View/CombineArea.cs b/Assets/GameMain/Scripts/UI/Game/View/CombineArea.cs index a11e30a..2e99f28 100644 --- a/Assets/GameMain/Scripts/UI/Game/View/CombineArea.cs +++ b/Assets/GameMain/Scripts/UI/Game/View/CombineArea.cs @@ -1,4 +1,5 @@ using GeometryTD.Definition; +using GeometryTD.CustomEvent; using UnityEngine; using UnityEngine.EventSystems; @@ -42,7 +43,14 @@ namespace GeometryTD.UI public void OnCombineClick() { - // TODO: Combine logic. + if (!TryGetBoundItemId(TowerCompSlotType.Muzzle, out long muzzleItemId) || + !TryGetBoundItemId(TowerCompSlotType.Bearing, out long bearingItemId) || + !TryGetBoundItemId(TowerCompSlotType.Base, out long baseItemId)) + { + return; + } + + GameEntry.Event.Fire(this, RepoCombineRequestedEventArgs.Create(muzzleItemId, bearingItemId, baseItemId)); } public bool TryAssignItem(RepoItemContext itemContext) @@ -127,5 +135,18 @@ namespace GeometryTD.UI return null; } + + private bool TryGetBoundItemId(TowerCompSlotType slotType, out long boundItemId) + { + boundItemId = 0; + CombineSlotItem slot = FindSlot(slotType); + if (slot == null || !slot.HasItem || slot.BoundItemId <= 0) + { + return false; + } + + boundItemId = slot.BoundItemId; + return true; + } } } diff --git a/Assets/GameMain/Scripts/UI/General/IconColorGenerator.cs b/Assets/GameMain/Scripts/UI/General/IconColorGenerator.cs index 1e9b909..9d7c8f2 100644 --- a/Assets/GameMain/Scripts/UI/General/IconColorGenerator.cs +++ b/Assets/GameMain/Scripts/UI/General/IconColorGenerator.cs @@ -61,17 +61,17 @@ namespace GeometryTD.CustomUtility item.SlotType); } - public static Color GenerateForTower(DefenseTowerItemData tower) + public static Color GenerateForTower(TowerItemData tower) { if (tower?.Stats == null) { return Color.white; } - DefenseTowerStatsData stats = tower.Stats; + TowerStatsData stats = tower.Stats; return GenerateColor( ResolveArrayValue(stats.AttackDamage), - ResolveArrayValue(stats.DamageRandomRate), + stats.DamageRandomRate, ResolveArrayValue(stats.RotateSpeed), ResolveArrayValue(stats.AttackRange), ResolveArrayValue(stats.AttackSpeed), @@ -127,14 +127,16 @@ namespace GeometryTD.CustomUtility return; } - if (!s_HasMuzzleRange && TryResolveMuzzleRange(out StatRange attackDamageRange, out StatRange damageRandomRateRange)) + if (!s_HasMuzzleRange && + TryResolveMuzzleRange(out StatRange attackDamageRange, out StatRange damageRandomRateRange)) { _attackDamageRange = attackDamageRange; _damageRandomRateRange = damageRandomRateRange; s_HasMuzzleRange = true; } - if (!s_HasBearingRange && TryResolveBearingRange(out StatRange rotateSpeedRange, out StatRange attackRangeRange)) + if (!s_HasBearingRange && + TryResolveBearingRange(out StatRange rotateSpeedRange, out StatRange attackRangeRange)) { _rotateSpeedRange = rotateSpeedRange; _attackRangeRange = attackRangeRange; @@ -181,7 +183,8 @@ namespace GeometryTD.CustomUtility } IncludeValues(row.AttackDamage, ref minAttackDamage, ref maxAttackDamage, ref hasAttackDamage); - IncludeValue(Mathf.Max(0f, row.DamageRandomRate), ref minDamageRandomRate, ref maxDamageRandomRate, ref hasDamageRandomRate); + IncludeValue(Mathf.Max(0f, row.DamageRandomRate), ref minDamageRandomRate, ref maxDamageRandomRate, + ref hasDamageRandomRate); } if (!hasAttackDamage || !hasDamageRandomRate) @@ -437,4 +440,4 @@ namespace GeometryTD.CustomUtility public float Max { get; } } } -} +} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs b/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs new file mode 100644 index 0000000..63c6e56 --- /dev/null +++ b/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs @@ -0,0 +1,142 @@ +using System; +using System.Text; +using GeometryTD.Definition; + +namespace GeometryTD.CustomUtility +{ + public static class ItemDescUtility + { + public static string BuildTowerDesc(TowerStatsData towerData) + { + StringBuilder sb = new StringBuilder(); + + // MuzzleComp + sb.Append("攻击伤害:"); + for (int i = 0; i < towerData.AttackDamage.Length; i++) + { + if (i != 0) sb.Append("|"); + sb.Append(towerData.AttackDamage[i]); + } + sb.Append('\n'); + + sb.Append($"伤害浮动:{towerData.DamageRandomRate:P0}\n"); + + sb.Append($"攻击方式:{ConvertAttackMethod(towerData.AttackMethodType)}\n"); + + // BearingComp + sb.Append("旋转速度:"); + for (int i = 0; i < towerData.RotateSpeed.Length; i++) + { + if (i != 0) sb.Append("|"); + sb.Append(towerData.RotateSpeed[i]); + } + sb.Append('\n'); + + sb.Append("攻击范围:"); + for (int i = 0; i < towerData.AttackRange.Length; i++) + { + if (i != 0) sb.Append("|"); + sb.Append(towerData.AttackRange[i]); + } + sb.Append('\n'); + + // BaseComp + sb.Append("攻击速度:"); + for (int i = 0; i < towerData.AttackSpeed.Length; i++) + { + if (i != 0) sb.Append("|"); + sb.Append(towerData.AttackSpeed[i]); + } + sb.Append('\n'); + + sb.Append($"伤害属性:{ConvertAttackProperty(towerData.AttackPropertyType)}\n"); + + return sb.ToString(); + } + + public static string BuildMuzzleDesc(MuzzleCompItemData muzzleData) + { + StringBuilder sb = new StringBuilder(); + + sb.Append("攻击伤害:"); + for (int i = 0; i < muzzleData.AttackDamage.Length; i++) + { + if (i != 0) sb.Append("|"); + sb.Append(muzzleData.AttackDamage[i]); + } + sb.Append('\n'); + + sb.Append($"伤害浮动:{muzzleData.DamageRandomRate:P0}\n"); + + sb.Append($"攻击方式:{ConvertAttackMethod(muzzleData.AttackMethodType)}\n"); + + return sb.ToString(); + } + + public static string BuildBearingDesc(BearingCompItemData bearingData) + { + StringBuilder sb = new StringBuilder(); + + sb.Append("旋转速度:"); + for (int i = 0; i < bearingData.RotateSpeed.Length; i++) + { + if (i != 0) sb.Append("|"); + sb.Append(bearingData.RotateSpeed[i]); + } + sb.Append('\n'); + + sb.Append("攻击范围:"); + for (int i = 0; i < bearingData.AttackRange.Length; i++) + { + if (i != 0) sb.Append("|"); + sb.Append(bearingData.AttackRange[i]); + } + sb.Append('\n'); + + return sb.ToString(); + } + + public static string BuildBaseDesc(BaseCompItemData baseData) + { + StringBuilder sb = new StringBuilder(); + + sb.Append("攻击速度:"); + for (int i = 0; i < baseData.AttackSpeed.Length; i++) + { + if (i != 0) sb.Append("|"); + sb.Append(baseData.AttackSpeed[i]); + } + sb.Append('\n'); + + sb.Append($"伤害属性:{ConvertAttackProperty(baseData.AttackPropertyType)}\n"); + + return sb.ToString(); + } + + public static string ConvertAttackMethod(AttackMethodType type) + { + return type switch + { + AttackMethodType.None => "无效", + AttackMethodType.NormalBullet => "发射子弹", + AttackMethodType.Range => "范围攻击", + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) + }; + } + + public static string ConvertAttackProperty(AttackPropertyType type) + { + return type switch + { + AttackPropertyType.None => "无效", + AttackPropertyType.Physics => "物理", //yellow + AttackPropertyType.Fire => "火", //red + AttackPropertyType.Water => "水", //cyan + AttackPropertyType.Earth => "自然", //lime + AttackPropertyType.Poison => "毒", //green + AttackPropertyType.Ice => "冰", //darkblue + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) + }; + } + } +} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs.meta b/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs.meta new file mode 100644 index 0000000..9ba6eb7 --- /dev/null +++ b/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 855b508f90404cf38b99179d596426df +timeCreated: 1772614668 \ No newline at end of file diff --git a/Assets/GameMain/UI/UIForms/ItemDescForm.prefab b/Assets/GameMain/UI/UIForms/ItemDescForm.prefab index e39a5e1..87aa4a8 100644 --- a/Assets/GameMain/UI/UIForms/ItemDescForm.prefab +++ b/Assets/GameMain/UI/UIForms/ItemDescForm.prefab @@ -65,6 +65,9 @@ MonoBehaviour: _screenEdgePadding: 0 _sideGap: 16 _anchorItemWidth: 0 + _blankAreaGraphic: {fileID: 0} + _disableBlankAreaRaycast: 1 + _closeOnPointerDownOutsideContent: 1 --- !u!1 &518771420464217804 GameObject: m_ObjectHideFlags: 0 @@ -595,7 +598,7 @@ MonoBehaviour: m_textAlignment: 65535 m_characterSpacing: 0 m_wordSpacing: 0 - m_lineSpacing: 0 + m_lineSpacing: 15 m_lineSpacingMax: 0 m_paragraphSpacing: 0 m_charWidthMaxAdj: 0 diff --git a/Assets/GameMain/UI/UIForms/RepoForm.prefab b/Assets/GameMain/UI/UIForms/RepoForm.prefab index ce6367b..1a61efc 100644 --- a/Assets/GameMain/UI/UIForms/RepoForm.prefab +++ b/Assets/GameMain/UI/UIForms/RepoForm.prefab @@ -49,10 +49,10 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: f3b1aede0e52479995cf71ba52a69d63, type: 3} m_Name: m_EditorClassIdentifier: - m_Content: {fileID: 1805918912036091081} - m_ItemTemplate: {fileID: 8394974685918372820, guid: 2ead8e403e355dd4b9296a9d0a871a83, + _content: {fileID: 1805918912036091081} + _itemTemplate: {fileID: 8394974685918372820, guid: 2ead8e403e355dd4b9296a9d0a871a83, type: 3} - m_InstancePoolCapacity: 64 + _instancePoolCapacity: 64 --- !u!1 &2380285751925872027 GameObject: m_ObjectHideFlags: 0 @@ -1736,6 +1736,41 @@ PrefabInstance: propertyPath: m_hasFontAssetChanged value: 0 objectReference: {fileID: 0} + - target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_Mode + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target + value: + objectReference: {fileID: 207314004719697847} + - target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_Target + value: + objectReference: {fileID: 2687805356437946940} + - target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: _onHover.m_PersistentCalls.m_Calls.Array.data[0].m_Target + value: + objectReference: {fileID: 207314004719697847} + - target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_MethodName + value: OnCombineClick + objectReference: {fileID: 0} + - target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_TargetAssemblyTypeName + value: GeometryTD.UI.CombineArea, Assembly-CSharp + objectReference: {fileID: 0} + - target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc, type: 3} propertyPath: m_Pivot.x diff --git a/docs/TODO.md b/docs/TODO.md index 362345d..dea54c5 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -20,8 +20,8 @@ | [ ] | P0-05 | 实现 10 节点地图生成(最后节点固定 Boss) | `Assets/GameMain/Scripts/Scene/` | 每局都生成 10 节点且第 10 节点为 Boss | | [x] | P0-06 | 实现节点选择与跳转流程(战斗/事件/商店) | `Assets/GameMain/Scripts/Procedure/` | 节点完成后可返回地图并进入下个节点 | | [x] | P0-07 | 战斗节点基础玩法:放置塔、出怪、基地扣血、胜负判定 | `Assets/GameMain/Scripts/Entity/`
`Assets/GameMain/Scripts/Scene/` | 可完整打一场并得到胜利/失败结果 | -| [ ] | P0-08 | 胜利波次与结算规则(100/80/50/<50) | `Assets/GameMain/Scripts/Procedure/` | 结算奖励与惩罚严格匹配设计文档 | -| [ ] | P0-09 | 敌人掉落与关卡奖励(组件/配件/金币) | `Assets/GameMain/Scripts/Entity/` | 战斗结束能发放掉落并写入库存 | +| [x] | P0-08 | 胜利波次与结算规则(100/80/50/<50) | `Assets/GameMain/Scripts/Procedure/` | 结算奖励与惩罚严格匹配设计文档 | +| [x] | P0-09 | 敌人掉落与关卡奖励(组件/配件/金币) | `Assets/GameMain/Scripts/Entity/` | 战斗结束能发放掉落并写入库存 | | [ ] | P0-10 | 节点后组装:枪口/轴承/底座三组件约束 | `Assets/GameMain/Scripts/Entity/`
`Assets/GameMain/Scripts/UI/Templates/GameScene/` | 未满足三组件时禁止出战 | | [ ] | P0-11 | 品质/槽位/Tag 计算落地(白绿蓝紫红) | `Assets/GameMain/Scripts/Definition/`
`Assets/GameMain/Scripts/Entity/` | 计算结果可复现并与规则一致 | | [ ] | P0-12 | 组件/配件耐久生效与 0 耐久销毁 | `Assets/GameMain/Scripts/Entity/` | 耐久影响属性,归零后物品移除 |