架构大更

This commit is contained in:
SoulliesOfficial
2026-03-20 11:56:50 -04:00
parent e60ef64d01
commit d09b58fd80
3663 changed files with 15232012 additions and 105579 deletions

View File

@@ -1,134 +1,177 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEditor;
using UnityEngine;
using Continentis.MainGame.Card;
using Continentis.MainGame.Character;
using SLSFramework.UModAssistance;
namespace Continentis.MainGame.Equipment
{
[CustomEditor(typeof(EquipmentData))]
public class EquipmentDataEditor : DataEditor
{
// Fundamental
private SerializedProperty _haveCustomClassProp;
private SerializedProperty _modNameProp;
private SerializedProperty _classNameProp;
private SerializedProperty _displayNameProp;
private SerializedProperty _tagsProp;
private SerializedProperty _equipmentRarityProp;
private SerializedProperty _equipmentIconProp;
private SerializedProperty _equipmentDescriptionProp;
// Attributes
private SerializedProperty _coreNumericChangeProp;
private SerializedProperty _corePercentageChangeOfAccumulationProp;
private SerializedProperty _corePercentageChangeOfMultiplicationProp;
private SerializedProperty _generalNumericChangeProp;
private SerializedProperty _generalPercentageChangeOfAccumulationProp;
private SerializedProperty _generalPercentageChangeOfMultiplicationProp;
// References
private SerializedProperty _prefabRefsProp;
private SerializedProperty _derivativeCardDataRefsProp;
private SerializedProperty _derivativeCharacterDataRefsProp;
private SerializedProperty _belongingCardDataRefsProp;
// =========================================================================
// EquipmentDataEditor
//
// 注意:此类已不再使用 [CustomEditor] 注解。
// Odin Inspector 会自动接管 EquipmentDataScriptableObject的 Inspector 渲染,
// 无需任何自定义 Editor 类。本文件现主要用于承载 EquipmentData 的编辑器扩展分部类。
// =========================================================================
internal static class EquipmentDataEditorPlaceholder
{
// 保留此类以维持文件存在意义,可在此添加未来的编辑器工具方法。
}
protected override void OnEnable()
// =========================================================================
// partial class EquipmentData — 编辑器专属扩展
//
// 包含:
// 1. GetEquipmentLogicDropdownItems() — 装备逻辑类下拉列表(静态)
// 2. OnEquipmentLogicClassSelected() — 选中逻辑类后自动填充字段的回调
// 3. GetAvailable*() — References 列表的资产名称选择器
// 4. 内部共享辅助方法AssetDatabase 查询)
// =========================================================================
public partial class EquipmentData
{
// ── 1. 装备逻辑类选择器 ────────────────────────────────────────────────
// 替代旧 EquipmentDataEditor 中的 DrawSearchableTypeSelector()
// 配合 EquipmentData.cs 中 _classSelector 字段上的
// [ValueDropdown("@EquipmentData.GetEquipmentLogicDropdownItems()")]
/// <summary>
/// 为 [ValueDropdown] 提供所有 EquipmentBase 子类的层级下拉项。
/// 路径格式为 "ModName/Category/ClassName",与旧 TypeSelectorWindow 分组逻辑一致。
/// 此方法为 static故在 [ValueDropdown] 中使用 @ 表达式语法引用。
/// </summary>
public static IEnumerable<ValueDropdownItem<string>> GetEquipmentLogicDropdownItems()
{
base.OnEnable();
// --- 在OnEnable中找到所有属性 ---
_haveCustomClassProp = serializedObject.FindProperty("haveCustomClass");
_modNameProp = serializedObject.FindProperty("modName");
_classNameProp = serializedObject.FindProperty("className");
_displayNameProp = serializedObject.FindProperty("displayName");
_tagsProp = serializedObject.FindProperty("tags");
_equipmentRarityProp = serializedObject.FindProperty("equipmentRarity");
_equipmentIconProp = serializedObject.FindProperty("equipmentIcon");
_equipmentDescriptionProp = serializedObject.FindProperty("equipmentDescription");
const string namespacePrefix = "Continentis.Mods";
const string namespaceToRemove = "Equipments";
_coreNumericChangeProp = serializedObject.FindProperty("coreNumericChange");
_corePercentageChangeOfAccumulationProp = serializedObject.FindProperty("corePercentageChangeOfAccumulation");
_corePercentageChangeOfMultiplicationProp = serializedObject.FindProperty("corePercentageChangeOfMultiplication");
_generalNumericChangeProp = serializedObject.FindProperty("generalNumericChange");
_generalPercentageChangeOfAccumulationProp = serializedObject.FindProperty("generalPercentageChangeOfAccumulation");
_generalPercentageChangeOfMultiplicationProp = serializedObject.FindProperty("generalPercentageChangeOfMultiplication");
IEnumerable<Type> types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a =>
{
try { return a.GetTypes(); }
catch { return Type.EmptyTypes; }
})
.Where(t => typeof(EquipmentBase).IsAssignableFrom(t)
&& !t.IsAbstract
&& !t.IsInterface
&& t != typeof(EquipmentBase));
_prefabRefsProp = serializedObject.FindProperty("prefabRefs");
_derivativeCardDataRefsProp = serializedObject.FindProperty("derivativeCardDataRefs");
_derivativeCharacterDataRefsProp = serializedObject.FindProperty("derivativeCharacterDataRefs");
_belongingCardDataRefsProp = serializedObject.FindProperty("belongingCardDataRefs");
foreach (Type type in types.OrderBy(t => t.FullName))
{
string path;
if (type.Namespace != null && type.Namespace.StartsWith(namespacePrefix))
{
List<string> segments = type.Namespace
.Substring(namespacePrefix.Length)
.Split('.')
.Where(s => !string.IsNullOrEmpty(s) && s != namespaceToRemove)
.ToList();
path = segments.Count > 0
? string.Join("/", segments) + "/" + type.Name
: type.Name;
}
else
{
path = "Uncategorized/" + type.Name;
}
// value 存储类型的完整名称FullName便于在 OnValueChanged 中精确反查
yield return new ValueDropdownItem<string>(path, type.FullName ?? type.Name);
}
}
public override void OnInspectorGUI()
/// <summary>
/// 当 classFullName 通过下拉菜单改变后,自动填充 className / modName / displayName。
/// 被 EquipmentData.cs 中 classFullName 字段上的 [OnValueChanged("OnEquipmentLogicClassSelected")] 触发。
/// </summary>
private void OnEquipmentLogicClassSelected()
{
serializedObject.Update();
if (string.IsNullOrEmpty(classFullName)) return;
// --- 按照你在EquipmentData.cs中声明的顺序手动绘制所有字段 ---
EditorGUILayout.LabelField("Fundamental", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_haveCustomClassProp);
// 根据 FullName 反查对应 Type
Type selectedType = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a =>
{
try { return a.GetTypes(); }
catch { return Type.EmptyTypes; }
})
.FirstOrDefault(t =>
(t.FullName == classFullName || t.Name == classFullName)
&& typeof(EquipmentBase).IsAssignableFrom(t)
&& !t.IsAbstract);
// --- 核心逻辑:根据 haveCustomClass 的值来决定显示/隐藏 ---
if (_haveCustomClassProp.boolValue)
if (selectedType == null) return;
const string prefix = "Continentis.Mods";
string ns = selectedType.Namespace ?? string.Empty;
if (ns.StartsWith(prefix))
{
// 如果勾选则显示class选择器 (假设基类为EquipmentBase, 命名空间为.Equipments)
DrawSearchableTypeSelector(
_classNameProp,
"Equipment Class",
typeof(EquipmentBase),
(outType) =>
{
string className = outType.Name;
string modName = outType.Namespace!.Replace("Continentis.Mods.", "").Split('.')[0];
string displayName = "Card_" + modName + "_" + className + "_DisplayName";
// ns = "Continentis.Mods.Basic.Equipments.Sword"
// → afterPrefix = ".Basic.Equipments.Sword" (if prefix is without dot)
// 统一处理点号
string afterPrefix = ns.Substring(prefix.Length).TrimStart('.');
string[] parts = afterPrefix.Split('.');
string resolvedMod = parts.Length > 0 ? parts[0] : string.Empty;
_classNameProp.stringValue = className;
_modNameProp.stringValue = modName;
_displayNameProp.stringValue = displayName;
},
"Continentis.Mods",
"Equipments");
// 移除 "Equipments" 层级并计算 className(短路径模式)
string equipmentsSegment = "Equipments";
string resolvedCategory = ns.Contains(equipmentsSegment)
? ns.Split(new[] { equipmentsSegment }, StringSplitOptions.None).Last().TrimStart('.')
: string.Empty;
modName = resolvedMod;
className = string.IsNullOrEmpty(resolvedCategory)
? selectedType.Name
: resolvedCategory + "." + selectedType.Name;
EditorGUI.BeginDisabledGroup(true);
displayName = $"Equipment_{resolvedMod}_{selectedType.Name}_DisplayName";
}
EditorGUILayout.PropertyField(_modNameProp);
EditorGUILayout.PropertyField(_classNameProp);
EditorGUILayout.PropertyField(_displayNameProp);
if (_haveCustomClassProp.boolValue)
else
{
EditorGUI.EndDisabledGroup();
modName = string.Empty;
className = selectedType.Name;
displayName = $"Equipment_{selectedType.Name}_DisplayName";
}
EditorGUILayout.PropertyField(_tagsProp, true);
EditorGUILayout.PropertyField(_equipmentRarityProp);
EditorGUILayout.PropertyField(_equipmentIconProp);
EditorGUILayout.PropertyField(_equipmentDescriptionProp);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Attributes", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_coreNumericChangeProp, true);
EditorGUILayout.PropertyField(_corePercentageChangeOfAccumulationProp, true);
EditorGUILayout.PropertyField(_corePercentageChangeOfMultiplicationProp, true);
EditorGUILayout.PropertyField(_generalNumericChangeProp, true);
EditorGUILayout.PropertyField(_generalPercentageChangeOfAccumulationProp, true);
EditorGUILayout.PropertyField(_generalPercentageChangeOfMultiplicationProp, true);
EditorGUILayout.Space();
EditorGUILayout.LabelField("References", EditorStyles.boldLabel);
DrawCharacterListGUI<GameObject>(_prefabRefsProp);
DrawCharacterListGUI<CardData>(_derivativeCardDataRefsProp);
DrawCharacterListGUI<CharacterData>(_derivativeCharacterDataRefsProp);
DrawCharacterListGUI<CardData>(_belongingCardDataRefsProp);
EditorUtility.SetDirty(this);
// 处理所有可能发生的ObjectPicker事件
HandleObjectPicker();
serializedObject.ApplyModifiedProperties();
Debug.Log($"[EquipmentData] 已自动填充 → modName: {modName}, className: {className}, " +
$"displayName: {displayName}, classFullName: {classFullName}");
}
}
// ── 2. References 列表的资产名称选择器 ──────────────────────────────────
// 替代旧 DrawCharacterListGUI<T>(prop)
private IEnumerable<ValueDropdownItem<string>> GetAvailablePrefabs()
=> GetAssetNameDropdown("t:Prefab");
private IEnumerable<ValueDropdownItem<string>> GetAvailableCardData()
=> GetAssetNameDropdown("t:CardData");
private IEnumerable<ValueDropdownItem<string>> GetAvailableCharacterData()
=> GetAssetNameDropdown("t:CharacterData");
// ── 内部共享辅助方法 ─────────────────────────────────────────────────
/// <summary>
/// 通过 AssetDatabase 搜索特定类型/标签的资产,将文件名(无扩展名)作为下拉项返回。
/// 用于 References 列表的字符串引用选择。
/// </summary>
private static IEnumerable<ValueDropdownItem<string>> GetAssetNameDropdown(string searchFilter)
{
string[] guids = AssetDatabase.FindAssets(searchFilter);
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
string name = Path.GetFileNameWithoutExtension(path);
yield return new ValueDropdownItem<string>(name, name);
}
}
}
}
#endif

View File

@@ -8,18 +8,14 @@ namespace Continentis.MainGame.Equipment
{
public partial class EquipmentBase
{
[Header("Equipment Data")]
public EquipmentData equipmentData;
[Header("References")]
public CharacterBase character;
[Header("Base Info")]
public Guid equipmentID;
[Header("Equipment Data")] public EquipmentData equipmentData;
[Header("References")] public CharacterBase character;
[Header("Base Info")] public Guid equipmentID;
public List<string> tags;
[Header("Submodules")]
public AttributeSubmodule coreAttributeSubmodule { get; private set; }
[Header("Submodules")] public AttributeSubmodule coreAttributeSubmodule { get; private set; }
public AttributeSubmodule generalAttributeSubmodule { get; private set; }
public EventSubmodule eventSubmodule { get; private set; }
public ContentSubmodule contentSubmodule { get; private set; }
@@ -31,18 +27,19 @@ namespace Continentis.MainGame.Equipment
this.coreAttributeSubmodule = new AttributeSubmodule(this, equipmentData.coreNumericChange,
equipmentData.corePercentageChangeOfAccumulation, equipmentData.corePercentageChangeOfMultiplication);
this.generalAttributeSubmodule = new AttributeSubmodule(this, equipmentData.generalNumericChange,
equipmentData.generalPercentageChangeOfAccumulation, equipmentData.generalPercentageChangeOfMultiplication);
equipmentData.generalPercentageChangeOfAccumulation,
equipmentData.generalPercentageChangeOfMultiplication);
this.eventSubmodule = new EventSubmodule(this);
this.contentSubmodule = new ContentSubmodule(this);
}
public virtual void Initialize(CharacterBase character)
{
if (character != null)
{
this.character = character;
this.character.equipmentSubmodule.currentEquipments.Add(this); //TODO: 后续换成装备函数
List<string> coreNames = coreAttributeSubmodule.GetModifiedAttributeNames();
if (coreNames.Count > 0)
@@ -51,7 +48,7 @@ namespace Continentis.MainGame.Equipment
{
this.character.attributeSubmodule.RefreshCoreAttribute(attributeName);
});
this.character.attributeSubmodule.RefreshAllGeneralAttributes();
}
else
@@ -64,28 +61,28 @@ namespace Continentis.MainGame.Equipment
}
}
}
public partial class EquipmentBase
{
public static EquipmentBase GenerateEquipment(EquipmentData data, CharacterBase character = null)
{
string typeID = "NoFunctionEquipment";
Type logicType = typeof(EquipmentBase);
if (data.haveCustomClass)
{
typeID = ModManager.GetTypeID(data.modName, "Equipments", "", data.className);
logicType = ModManager.GetType(typeID);
}
if (Activator.CreateInstance(logicType) is EquipmentBase equipment)
{
equipment.equipmentData = data;
equipment.SetUp();
if(character != null) equipment.Initialize(character);
if (character != null) equipment.Initialize(character);
return equipment;
}
Debug.LogError($"Failed to create equipment of type {typeID}");
return null;
}

View File

@@ -1,97 +1,159 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Continentis.MainGame.Card;
using Continentis.MainGame.Character;
using SLSFramework.General;
using Sirenix.OdinInspector;
using SLSFramework.UModAssistance;
using UnityEngine;
using UnityEngine.Serialization;
namespace Continentis.MainGame.Equipment
{
// ─────────────────────────────────────────────────────────────────────────
// EquipmentData — ScriptableObject 数据定义
// ─────────────────────────────────────────────────────────────────────────
[CreateAssetMenu(menuName = "Continentis/MainGame/Equipment/EquipmentData", fileName = "EquipmentData")]
public partial class EquipmentData : ScriptableObject
{
[Header("Fundamental")]
// ── Fundamental ───────────────────────────────────────────────────────
[BoxGroup("Fundamental"), PropertyOrder(0)]
[LabelText("使用自定义逻辑类")]
[Tooltip("勾选后可通过下拉菜单选择 EquipmentBase 子类modName / className / displayName 将自动填充;" +
"取消勾选后三个字段变为可手动编辑。")]
public bool haveCustomClass;
public string modName;
// 当 haveCustomClass = true 时,下拉菜单选择逻辑类;
// OnValueChanged 回调会将结果同步写入 className / modName / displayName。
// 当 haveCustomClass = false 时,此选择器隐藏,三个字段直接手动编辑。
[BoxGroup("Fundamental"), PropertyOrder(1)]
[ShowIf("haveCustomClass")]
[ValueDropdown("@EquipmentData.GetEquipmentLogicDropdownItems()", AppendNextDrawer = true, DisableGUIInAppendedDrawer = true)]
[OnValueChanged("OnEquipmentLogicClassSelected")]
[LabelText("逻辑类全名")]
public string classFullName;
[BoxGroup("Fundamental"), PropertyOrder(2)]
[DisableIf("haveCustomClass")]
[LabelText("逻辑类名")]
public string className;
[BoxGroup("Fundamental"), PropertyOrder(3)]
[DisableIf("haveCustomClass")]
[LabelText("Mod 名称")]
public string modName;
[BoxGroup("Fundamental"), PropertyOrder(4)]
[DisableIf("haveCustomClass")]
[LabelText("显示名称 Key")]
public string displayName;
[BoxGroup("Fundamental"), PropertyOrder(5)]
[ListDrawerSettings(ShowIndexLabels = false, DraggableItems = true)]
[LabelText("标签")]
public List<string> tags;
[BoxGroup("Fundamental"), PropertyOrder(6)]
[LabelText("稀有度")]
public Rarity equipmentRarity;
// ── 图标与描述 ────────────────────────────────────────────────────────
[BoxGroup("Display"), PropertyOrder(7)]
[PreviewField(80, ObjectFieldAlignment.Left), LabelText("装备图标")]
public Sprite equipmentIcon;
[TextArea(1, 10)]
[BoxGroup("Display"), PropertyOrder(8)]
[LabelText("装备描述 Key")]
public string equipmentDescription;
[Header("Attributes")]
// ── 核心属性变化 ──────────────────────────────────────────────────────
[TabGroup("Data", "核心属性"), PropertyOrder(20)]
[DictionaryDrawerSettings(KeyLabel = "属性名", ValueLabel = "数值变化")]
[Tooltip("对角色 CoreAttributes 施加固定数值加成")]
[LabelText("数值变化 (Numeric)")]
public SerializableDictionary<string, float> coreNumericChange = new SerializableDictionary<string, float>();
[TabGroup("Data", "核心属性"), PropertyOrder(21)]
[DictionaryDrawerSettings(KeyLabel = "属性名", ValueLabel = "百分比(累加)")]
[Tooltip("对角色 CoreAttributes 施加百分比累加加成(如 +10% 填写 0.1")]
[LabelText("百分比变化—累加 (Accum%)")]
public SerializableDictionary<string, float> corePercentageChangeOfAccumulation = new SerializableDictionary<string, float>();
[TabGroup("Data", "核心属性"), PropertyOrder(22)]
[DictionaryDrawerSettings(KeyLabel = "属性名", ValueLabel = "百分比(乘法)")]
[Tooltip("对角色 CoreAttributes 施加百分比乘法加成")]
[LabelText("百分比变化—乘法 (Multi%)")]
public SerializableDictionary<string, float> corePercentageChangeOfMultiplication = new SerializableDictionary<string, float>();
// ── 通用属性变化 ──────────────────────────────────────────────────────
[TabGroup("Data", "通用属性"), PropertyOrder(30)]
[DictionaryDrawerSettings(KeyLabel = "属性名", ValueLabel = "数值变化")]
[Tooltip("对角色 GeneralAttributes 施加固定数值加成")]
[LabelText("数值变化 (Numeric)")]
public SerializableDictionary<string, float> generalNumericChange = new SerializableDictionary<string, float>();
[TabGroup("Data", "通用属性"), PropertyOrder(31)]
[DictionaryDrawerSettings(KeyLabel = "属性名", ValueLabel = "百分比(累加)")]
[Tooltip("对角色 GeneralAttributes 施加百分比累加加成(如 +10% 填写 0.1")]
[LabelText("百分比变化—累加 (Accum%)")]
public SerializableDictionary<string, float> generalPercentageChangeOfAccumulation = new SerializableDictionary<string, float>();
[TabGroup("Data", "通用属性"), PropertyOrder(32)]
[DictionaryDrawerSettings(KeyLabel = "属性名", ValueLabel = "百分比(乘法)")]
[Tooltip("对角色 GeneralAttributes 施加百分比乘法加成")]
[LabelText("百分比变化—乘法 (Multi%)")]
public SerializableDictionary<string, float> generalPercentageChangeOfMultiplication = new SerializableDictionary<string, float>();
[Header("References")]
// ── References ────────────────────────────────────────────────────────
[TabGroup("Data", "引用"), PropertyOrder(40)]
[ListDrawerSettings(ShowIndexLabels = false, DraggableItems = false)]
[ValueDropdown("GetAvailablePrefabs", AppendNextDrawer = true)]
[LabelText("Prefab 引用")]
public List<string> prefabRefs = new List<string>();
[TabGroup("Data", "引用"), PropertyOrder(41)]
[ListDrawerSettings(ShowIndexLabels = false, DraggableItems = false)]
[ValueDropdown("GetAvailableCardData", AppendNextDrawer = true)]
[LabelText("衍生卡牌数据引用")]
public List<string> derivativeCardDataRefs = new List<string>();
[TabGroup("Data", "引用"), PropertyOrder(42)]
[ListDrawerSettings(ShowIndexLabels = false, DraggableItems = false)]
[ValueDropdown("GetAvailableCharacterData", AppendNextDrawer = true)]
[LabelText("衍生角色数据引用")]
public List<string> derivativeCharacterDataRefs = new List<string>();
[TabGroup("Data", "引用"), PropertyOrder(43)]
[ListDrawerSettings(ShowIndexLabels = false, DraggableItems = false)]
[ValueDropdown("GetAvailableCardData", AppendNextDrawer = true)]
[Tooltip("装备自带的卡牌的引用列表")]
[LabelText("附带卡牌引用")]
public List<string> belongingCardDataRefs = new List<string>();
//[Header("Upgrades")]
//public List<EquipmentData> upgrades;
// 已预留:升级系统(待设计确定后扩展)
// [TabGroup("Data", "升级")]
// public List<EquipmentData> upgrades;
}
// ─────────────────────────────────────────────────────────────────────────
// Runtime: 数据库查询
// ─────────────────────────────────────────────────────────────────────────
public partial class EquipmentData
{
/// <summary>
/// 通过 DataID 从 ModManager 数据库查找 EquipmentData。
/// </summary>
public static EquipmentData Get(string dataID)
{
return ModManager.GetData<EquipmentData>(dataID);
}
}
// ─────────────────────────────────────────────────────────────────────────
// 扩展留白(运行时业务方法在此添加)
// ─────────────────────────────────────────────────────────────────────────
public partial class EquipmentData
{
}
#if UNITY_EDITOR
public partial class EquipmentData
{
/*
private IEnumerable<ValueDropdownItem<Type>> GetLogicTypes()
{
IEnumerable<Type> types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(t => typeof(EquipmentBase).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface);
// 我们将从命名空间中移除这个公共前缀,让路径更简洁
string commonNamespacePrefix = "Continentis.Mods";
foreach (var type in types)
{
string path = "Uncategorized/" + type.Name; // 默认路径
if (type.Namespace != null && type.Namespace.StartsWith(commonNamespacePrefix))
{
// 1. 移除公共前缀
string formattedNamespace = type.Namespace.Substring(commonNamespacePrefix.Length);
// 2. 移除特定的子命名空间部分(例如 "Cards"
formattedNamespace = formattedNamespace.Replace(".Equipments", "");
// 3. 将点 '.' 替换为斜杠 '/' 来创建分组
formattedNamespace = formattedNamespace.Replace('.', '/');
// 4. 组合成最终的路径
path = formattedNamespace + "/" + type.Name;
}
yield return new ValueDropdownItem<Type>(path, type);
}
}
*/
}
#endif
}