This commit is contained in:
SoulliesOfficial
2025-10-23 00:49:44 -04:00
parent 9b1b5ca93f
commit 61a397dd4c
9846 changed files with 2618439 additions and 793547 deletions

View File

@@ -1,14 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Continentis.MainGame.Character;
using Continentis.MainGame.Commands;
using NUnit.Framework;
using SLSFramework.General;
using UnityEngine;
namespace Continentis.MainGame.Card
{
#region Fundamental
public partial class CardLogicBase
{
public bool HasTag(string tag)
{
return functionalTags.Contains(tag) || elementalTags.Contains(tag);
return tags.Contains(tag);
}
public List<string> GetElementTags(List<string> tags = null)
{
tags ??= this.tags;
return tags.Filtered((tag) => MainGameManager.Instance.elementTags.Contains(tag));
}
public bool HasKeyword(string keyword)
@@ -16,7 +28,94 @@ namespace Continentis.MainGame.Card
return contentSubmodule.keywords.Contains(keyword);
}
}
#endregion
#region Command
public partial class CardLogicBase
{
/// <summary>
/// 创建一个命令组,组内命令按顺序执行
/// </summary>
/// <param name="commands">命令模板</param>
protected CommandGroup SingleCommandGroup(params CommandBase[] commands)
{
return SingleCommandGroup(ExecutionMode.Parallel, commands);
}
/// <summary>
/// 创建一个命令组,组内命令按指定顺序执行
/// </summary>
/// <param name="executionMode">执行模式,顺序或并行</param>
/// <param name="commands">命令模板</param>
protected virtual CommandGroup SingleCommandGroup(
ExecutionMode executionMode = ExecutionMode.Parallel, params CommandBase[] commands)
{
CommandGroup singleGroup = new CommandGroup(executionMode);
foreach (CommandBase template in commands)
{
singleGroup.AddCommand(template.Clone());
}
return singleGroup;
}
/// <summary>
/// 对目标列表中的每个目标依次执行一组有参函数命令每组命令的参数为targetList中的个体主体Group顺序执行单体Group并行执行。
/// </summary>
/// <param name="targetList">目标列表</param>
/// <param name="singleCommands">单体命令模板</param>
protected CommandGroup TargetListCommandGroup(List<CharacterBase> targetList,
params CommandBase[] singleCommands)
{
return TargetListCommandGroup(targetList, ExecutionMode.Sequential, ExecutionMode.Parallel, singleCommands);
}
/// <summary>
/// 对目标列表中的每个目标依次执行一组有参函数命令每组命令的参数为targetList中的个体。
/// </summary>
/// <param name="targetList">目标列表</param>
/// <param name="mainExecutionMode">主体Group各个目标的执行顺序</param>
/// <param name="singleExecutionMode">单体Group一个目标中指令的执行顺序</param>
/// <param name="singleCommands">单体命令模板</param>
protected virtual CommandGroup TargetListCommandGroup(
List<CharacterBase> targetList, ExecutionMode mainExecutionMode = ExecutionMode.Sequential,
ExecutionMode singleExecutionMode = ExecutionMode.Parallel,
params CommandBase[] singleCommands)
{
CommandGroup mainGroup = new CommandGroup(mainExecutionMode);
foreach (CharacterBase target in targetList)
{
CommandGroup singleGroup = new CommandGroup(singleExecutionMode);
foreach (CommandBase template in singleCommands)
{
CommandBase clone = template.Clone();
if (clone is CommandGroup group)
{
foreach (CommandBase cmd in group.commands)
{
cmd.selfContext.context["Target"] = target;
}
}
clone.selfContext.context["Target"] = target;
singleGroup.AddCommand(clone);
}
mainGroup.AddCommand(singleGroup);
}
return mainGroup;
}
}
#endregion
#region Attack
public partial class CardLogicBase
{
/// <summary>
@@ -24,25 +123,60 @@ namespace Continentis.MainGame.Card
/// </summary>
/// <param name="target">目标</param>
/// <param name="elementalTags">元素标签若为null则使用卡牌的元素标签</param>
protected virtual int GetFinalDamage(CharacterBase target, List<string> elementalTags = null)
public virtual int GetFinalDamage(CharacterBase target, List<string> elementalTags = null)
{
elementalTags ??= this.elementalTags;
float elementalAmplifier = 1;
foreach (var element in elementalTags) //计算元素伤害加成
return GetFinalDamage(target, elementalTags, out _, out _, out _, out _);
}
protected virtual int GetFinalDamage(CharacterBase target, List<string> elementalTags,
out float baseDamageAfterOffset, out float elementalMultiplier, out float magicMultiplier, out float finalMultiplier)
{
elementalTags ??= GetElementTags();
//----计算基础伤害增量----
int physicsOffset = 0;
if (HasTag("Physics") || HasKeyword("Slash") || HasKeyword("Prick") || HasKeyword("Strike"))
{
elementalAmplifier *= user.attributeSubmodule.GetCurrentGeneralAttribute(element + "DamageDealtMultiplier", 1);
elementalAmplifier *= target.attributeSubmodule.GetCurrentGeneralAttribute(element + "DamageGainMultiplier", 1);
physicsOffset = user.GetAttribute("PhysicsDamageDealtOffset"); //物理伤害基础增量
}
int magicOffset = 0;
if (HasTag("Magic") || HasKeyword("Arcane") || HasKeyword("Sorcery"))
{
magicOffset = user.GetAttribute("MagicDamageDealtOffset"); //魔法伤害基础增量
}
//----计算伤害因数----
float finalDamage =
attributeSubmodule.GetRoundCurrentAttribute("Damage") * elementalAmplifier *
attributeSubmodule.GetCurrentAttribute("FinalDamageDealtMultiplier", 1) *
target.attributeSubmodule.GetCurrentGeneralAttribute("FinalDamageGainMultiplier", 1);
//计算元素伤害加成注意“物理Physics”也是一种元素因此下方没有“通用物理伤害加成”的计算
elementalMultiplier = 1;
foreach (string element in elementalTags)
{
elementalMultiplier *= user.GetRawAttribute(element + "DamageDealtMultiplier", 1) *
target.GetRawAttribute(element + "DamageGainMultiplier", 1);
}
//计算通用的魔法伤害加成
magicMultiplier = 1;
if (HasTag("Magic") || HasKeyword("Arcane") || HasKeyword("Sorcery"))
{
magicMultiplier = user.GetRawAttribute("MagicDamageDealtMultiplier", 1) *
target.GetRawAttribute("MagicDamageGainMultiplier", 1);
}
//计算最终伤害加成
finalMultiplier = user.GetRawAttribute("FinalDamageDealtMultiplier", 1) *
target.GetRawAttribute("FinalDamageGainMultiplier", 1);
//----计算最终伤害----
baseDamageAfterOffset = attributeSubmodule.GetCurrentAttribute("Damage") + physicsOffset + magicOffset;
float finalDamage = baseDamageAfterOffset * elementalMultiplier * magicMultiplier * finalMultiplier;
return Mathf.RoundToInt(finalDamage);
}
}
#endregion
#region Attributes
public partial class CardLogicBase
@@ -54,7 +188,7 @@ namespace Continentis.MainGame.Card
/// <param name="additive">是否为叠加true为叠加false为覆盖true时originalValue为外部传入值</param>
/// <param name="originalValue">原始伤害值仅在additive为true时有效否则此值被覆盖为BaseAttribute</param>
/// <param name="baseOffset">伤害增量</param>
public void SetVariableAttribute(string attributeName, bool additive, int originalValue, int baseOffset)
public void SetVariableAttribute(string attributeName, int baseOffset, bool additive = false, int originalValue = 0)
{
string baseName = "Base" + attributeName;
string baseOffsetName = baseName + "Offset";
@@ -66,12 +200,25 @@ namespace Continentis.MainGame.Card
ModifyAttribute(attributeName, baseOffset);
}
/// <summary>
/// 检查卡牌是否具有某属性
/// </summary>
public bool HasAttribute(string attributeName)
{
return attributeSubmodule.attributeGroup.current.ContainsKey(attributeName);
}
/// <summary>
/// 获取卡牌的属性值
/// </summary>
public int GetAttribute(string attributeName)
public int GetAttribute(string attributeName, int defaultValue = 0)
{
return attributeSubmodule.GetRoundCurrentAttribute(attributeName);
return attributeSubmodule.GetRoundCurrentAttribute(attributeName, defaultValue);
}
public float GetRawAttribute(string attributeName, float defaultValue = 0)
{
return attributeSubmodule.GetCurrentAttribute(attributeName, defaultValue);
}
/// <summary>
@@ -82,6 +229,14 @@ namespace Continentis.MainGame.Card
attributeSubmodule.attributeGroup.current[attributeName] = value;
}
/// <summary>
/// 设置卡牌的属性值
/// </summary>
public void SetAttribute(string attributeName, float value)
{
attributeSubmodule.attributeGroup.current[attributeName] = value;
}
/// <summary>
/// 修改卡牌的属性值
/// </summary>
@@ -91,43 +246,11 @@ namespace Continentis.MainGame.Card
}
}
#endregion
#region CombatResoures
public partial class CardLogicBase
{
/// <summary>
/// 检查是否有足够的体力
/// </summary>
protected virtual bool CheckEnoughStamina()
{
int staminaCost = attributeSubmodule.GetRoundCurrentAttribute("StaminaCost");
return user.attributeSubmodule.generalAttributeGroup.current["Stamina"] >= staminaCost;
}
/// <summary>
/// 消耗体力
/// </summary>
protected virtual void ConsumeStamina()
{
int staminaCost = attributeSubmodule.GetRoundCurrentAttribute("StaminaCost");
user.attributeSubmodule.generalAttributeGroup.current["Stamina"] -= staminaCost;
}
/// <summary>
/// 检查是否有足够的魔法
/// </summary>
protected virtual bool CheckEnoughMana()
{
int manaCost = attributeSubmodule.GetRoundCurrentAttribute("ManaCost");
return user.attributeSubmodule.generalAttributeGroup.current["Mana"] >= manaCost;
}
/// <summary>
/// 消耗魔法
/// </summary>
protected virtual void ConsumeMana()
{
int manaCost = attributeSubmodule.GetRoundCurrentAttribute("ManaCost");
user.attributeSubmodule.generalAttributeGroup.current["Mana"] -= manaCost;
}
}
#endregion
}

View File

@@ -65,7 +65,7 @@ namespace Continentis.MainGame.Card
{
protected bool FindExistingBuff<T>(out T existingBuff) where T : CardBuffBase
{
return base.FindExistingBuff(out existingBuff, attachedCard.combatBuffSubmodule.buffList);
return base.FindExistingSameBuff(out existingBuff, attachedCard.combatBuffSubmodule.buffList);
}
public override void Apply(CardLogicBase attachedCard, CharacterBase sourceCharacter = null)

View File

@@ -1,20 +1,18 @@
using System.Collections.Generic;
using Sirenix.OdinInspector;
using SLSFramework.General;
using UnityEngine;
namespace Continentis.MainGame.Card
{
[CreateAssetMenu(menuName = "Continentis/MainGame/Card/AttributesDefaultCollection", fileName = "CardAttributesDefaultCollection")]
public class CardAttributesDefaultCollection : SerializedScriptableObject
public class CardAttributesDefaultCollection : ScriptableObject
{
[Title("Attributes")]
[Searchable]
[DictionaryDrawerSettings(KeyColumnWidth = 400)]
public Dictionary<string, float> originalAttributes;
[Header("Attributes")]
public SerializableDictionary<string, float> variableAttributes;
public SerializableDictionary<string, float> originalAttributes;
[Searchable]
[DictionaryDrawerSettings(KeyColumnWidth = 400)]
[Tooltip("初始化时赋予给CurrentAttributes的属性第一栏是属性名第二栏是初始化时使用对应名称的CurrentAttributes的数据或一个常数留空则默认为0")]
public Dictionary<string, string> endowingCurrentAttributes;
public SerializableDictionary<string, string> endowingCurrentAttributes;
}
}

View File

@@ -3,25 +3,18 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Continentis.MainGame.Character;
using Sirenix.OdinInspector;
using SoulliesFramework.General;
using UnityEditor;
using SLSFramework.General;
using UnityEngine;
using NaughtyAttributes;
using SLSFramework.UModAssistance;
using UnityEngine.Serialization;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Continentis.MainGame.Card
{
public enum CardRarity
{
None = 0,
Common = 10,
Uncommon = 20,
Rare = 30,
Epic = 40,
Legendary = 50,
Divine = 60
}
public enum CardType
{
Attack = 0,
@@ -33,45 +26,64 @@ namespace Continentis.MainGame.Card
}
[CreateAssetMenu(menuName = "Continentis/MainGame/Card/CardData", fileName = "CardData")]
public partial class CardData : SerializedScriptableObject
public partial class CardData : ScriptableObject
{
[Title("Fundamental")]
[ValueDropdown("GetLogicTypes", IsUniqueList = true)]
[OnValueChanged("SetCardIdentifier")]
public Type cardClass;
public string cardName;
public string cardIdentifier;
public CardRarity cardRarity;
[FormerlySerializedAs("cardLogicClassName")] [Header("Fundamental")]
public string classFullName;
public string displayName;
public Rarity cardRarity;
public CardType cardType;
public List<string> tags;
public Sprite cardSprite;
[TextArea(1, 10)]
public string functionText;
public string cardDescription;
[FormerlySerializedAs("tags")] public List<string> functionalTags;
public List<string> elementalTags;
[Header("Intention")]
public float baseWeight = 1f;
[Searchable]
[Title("References")]
public Dictionary<string, GameObject> prefabs = new Dictionary<string, GameObject>();
[Searchable]
public List<CardData> derivativeCardDataList = new List<CardData>();
[Title("Attributes")]
[Searchable]
[Header("Attributes")]
[Tooltip("可变属性这个属性会自动设置BaseAttr进入Original设置AttrBaseAttrOffset=0以及DisplayAttr进入Current")]
public Dictionary<string, float> variableAttributes = new Dictionary<string, float>();
[Searchable]
[Tooltip("基础属性,不会改变,通常情况下不会直接使用")]
public Dictionary<string, float> originalAttributes = new Dictionary<string, float>();
public SerializableDictionary<string, float> variableAttributes = new SerializableDictionary<string, float>();
[Searchable]
[Tooltip("初始化时赋予给CurrentAttributes的属性第一栏是属性名第二栏是初始化时使用对应名称的OriginalAttributes的留空则默认为0如果是float数字则直接使用该数字")]
public Dictionary<string, string> endowingCurrentAttributes = new Dictionary<string, string>();
[Tooltip("基础属性,不会改变,通常情况下不会直接使用")]
public SerializableDictionary<string, float> originalAttributes = new SerializableDictionary<string, float>();
[FormerlySerializedAs("endowingCurrentAttributes")] [Tooltip("初始化时赋予给CurrentAttributes的属性第一栏是属性名第二栏是初始化时使用对应名称的OriginalAttributes的留空则默认为0如果是float数字则直接使用该数字")]
public SerializableDictionary<string, string> runtimeCurrentAttributes = new SerializableDictionary<string, string>();
[Title("Upgrade")]
[InlineButton("CreateUpgradeNode", "Create")]
[Header("Upgrade")]
public CardUpgradeNode upgradeNode;
[Header("References")]
public List<string> prefabRefs = new List<string>();
public List<string> derivativeCardDataRefs = new List<string>();
public List<string> derivativeCharacterDataRefs = new List<string>();
}
public partial class CardData
{
public string ModName => classFullName.Split('_').First();
public string ClassName => classFullName.Split('_').Last();
public bool HasTag(string tag)
{
return tags.Contains(tag);
}
public bool HasKeyword(string keyword)
{
return functionText.Contains($"$Keyword(\"{keyword}\")");
}
}
public partial class CardData
{
public static CardData Get(string dataID)
{
return ModManager.GetData<CardData>(dataID);
}
}
public partial class CardData
@@ -82,41 +94,69 @@ namespace Continentis.MainGame.Card
/// <param name="owner">卡牌持有者</param>
/// <param name="pileName">初始卡堆名称,默认为"Draw"</param>
/// <param name="index">插入位置默认为0</param>
public CardInstance GenerateCardInstance(ICardOwner owner, string pileName = "Draw", int index = 0)
public CardInstance GenerateCardInstance(ICardOwner owner, string pileName = "Draw", int index = -1)
{
CardInstance cardInstance = new CardInstance(GenerateCardLogic(), owner, pileName, index);
cardInstance.cardLogic.InitialRefresh();
cardInstance.cardLogic.Initialize();
return cardInstance;
}
/// <summary>
/// 生成卡牌逻辑实例
/// </summary>
public CardLogicBase GenerateCardLogic()
{
if (Activator.CreateInstance(cardClass) is CardLogicBase cardLogic)
Type cardLogicType = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.FirstOrDefault(t => typeof(CardLogicBase).IsAssignableFrom(t) && t.Name == this.classFullName);//TODO: 后续优化为共用字典
if(cardLogicType == null)
{
Debug.LogError($"Card class '{classFullName}' not found in assemblies.");
return null;
}
if (Activator.CreateInstance(cardLogicType) is CardLogicBase cardLogic)
{
cardLogic.cardData = this;
cardLogic.Setup();
return cardLogic;
}
Debug.LogError($"Card class '{cardClass}' not found or could not be instantiated.");
Debug.LogError($"Card class '{classFullName}' not found or could not be instantiated.");
return null;
}
/// <summary>
/// 通过名称获取衍生卡牌数据
/// 通过索引获取衍生卡牌数据
/// </summary>
public CardData GetDerivativeCardData(string cardName)
public CardData GetDerivativeCardData(int index)
{
return derivativeCardDataList.FirstOrDefault(card => card.cardName == cardName);
return ModManager.GetData<CardData>(derivativeCardDataRefs[index]);
}
/// <summary>
/// 通过索引获取衍生角色数据
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public CharacterData GetDerivativeCharacterData(int index)
{
return ModManager.GetData<CharacterData>(derivativeCharacterDataRefs[index]);
}
}
#if UNITY_EDITOR
public partial class CardData
{
{/*
private void SetCardIdentifier()
{
cardIdentifier = cardClass.Name;
int underscoreIndex = cardClass.Name.IndexOf('_');
string cardNamePart = cardClass.Name.Substring(underscoreIndex + 1);
string result = Regex.Replace(cardNamePart, "([a-z])([A-Z])", "$1 $2");
cardName = result;
}
private void CreateUpgradeNode()
@@ -155,7 +195,7 @@ namespace Continentis.MainGame.Card
yield return new ValueDropdownItem<Type>(path, type);
}
}
[FoldoutGroup("Functions")]
[Button("从所有的DefaultCollection中粘贴默认属性")]
public void PasteDefaultAttributes()
@@ -179,10 +219,10 @@ namespace Continentis.MainGame.Card
Type assetType = collection.GetType();
string assetNamespace = assetType.Namespace;
// 检查目录是否有效,是否在"Assets/CoreMods/"下,并且是否以"/Characters/DefaultCollections"结尾
// 检查目录是否有效,是否在"Assets/Mods/"下,并且是否以"/Characters/DefaultCollections"结尾
if (!string.IsNullOrEmpty(directory) &&
assetNamespace == typeof(CardAttributesDefaultCollection).Namespace &&
directory.StartsWith("Assets/CoreMods/") &&
directory.StartsWith("Assets/Mods/") &&
directory.EndsWith("/Cards/DefaultCollections"))
{
// 3. 如果路径和命名空间都符合要求,则将该资产添加到目标列表中
@@ -191,45 +231,23 @@ namespace Continentis.MainGame.Card
Debug.Log($"Loaded DefaultStringList from: {path}");
}
}
Dictionary<string, float> variableAttributes = new Dictionary<string, float>();
Dictionary<string, float> originalAttributes = new Dictionary<string, float>();
Dictionary<string, string> endowingCurrentAttributes = new Dictionary<string, string>();
foreach (CardAttributesDefaultCollection collection in targetCollections)
{
collection.variableAttributes.PasteDictionary(variableAttributes);
collection.originalAttributes.PasteDictionary(originalAttributes);
collection.endowingCurrentAttributes.PasteDictionary(endowingCurrentAttributes);
}
variableAttributes.PasteDictionary(this.variableAttributes);
originalAttributes.PasteDictionary(this.originalAttributes);
endowingCurrentAttributes.PasteDictionary(this.endowingCurrentAttributes);
Debug.Log($"Pasted default attributes to file {this.name}");
}
[FoldoutGroup("Functions")]
[Button("自动设置需要加权(名称中带有Base)的属性")]
private void AutoSetUpWeightedAttributes()
{
List<string> baseAttributeNames = originalAttributes
.Where(attr => attr.Key.Contains("Base"))
.Select(attr => attr.Key).ToList();
List<string> attributeNames = baseAttributeNames
.Select(attr => attr.Replace("Base", "")).ToList();
List<string> baseOffsetAttributeName = baseAttributeNames
.Select(attr => attr + "Offset").ToList();
List<string> displayAttributeNames = baseAttributeNames
.Select(attr => attr.Replace("Base", "Display")).ToList();
for (int index = 0; index < baseAttributeNames.Count; index++)
{
endowingCurrentAttributes.TryAdd(attributeNames[index], baseAttributeNames[index]);
endowingCurrentAttributes.TryAdd(baseOffsetAttributeName[index], "0");
endowingCurrentAttributes.TryAdd(displayAttributeNames[index], baseAttributeNames[index]);
}
}
}*/
}
#endif
}

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using SoulliesFramework.General;
using NaughtyAttributes;
using SLSFramework.General;
using UnityEngine;
namespace Continentis.MainGame.Card
{
[Serializable]
public class CardUpgradeNode
{
[ReadOnly]
@@ -76,7 +78,7 @@ namespace Continentis.MainGame.Card
}
else
{
Debug.LogError($"[CardUpgradeNode] Attempted to get upgrade attributes for a non-terminal node card {sourceCard.cardName}.");
Debug.LogError($"[CardUpgradeNode] Attempted to get upgrade attributes for a non-terminal node card {sourceCard.classFullName}.");
}
return upgradeAttributes;

View File

@@ -2,36 +2,64 @@ using System.Collections.Generic;
using Continentis.MainGame.Character;
using Continentis.MainGame.UI;
using Lean.Pool;
using Sirenix.OdinInspector;
using UnityEngine;
namespace Continentis.MainGame.Card
{
public partial class CardInstance
{
[Title("References")]
[Header("References")]
public DeckSubmodule deck;
public string currentPileName;
//public string currentPileName;
public ICardOwner owner;
public CombatTeam team;
public CharacterBase user;
public CombatTeam team;
public CardLogicBase cardLogic;
public CardLocation cardLocation;
public HandCardView handCardView;
public IntentionCardView intentionCardView;
public CardInstance (CardLogicBase cardLogic, ICardOwner owner, string initialPileName, int index)
public CardInstance(CardLogicBase cardLogic, ICardOwner owner, string initialPileName, int index = -1)
{
cardLogic.cardInstance = this;
this.cardLogic = cardLogic;
this.owner = owner;
this.team = owner as CombatTeam;
this.user = owner as CharacterBase;
if (this.owner is CombatTeam team)
{
this.team = team;
}
else if (this.owner is CharacterBase character)
{
this.team = character.team;
}
this.deck = owner.deckSubmodule;
this.currentPileName = initialPileName;
this.deck.Pile(currentPileName).Insert(index, this);
this.cardLocation = new CardLocation(initialPileName, index);
if (index < 0)
{
this.deck.Pile(cardLocation.pileName).Add(this);
}
else
{
this.deck.Pile(cardLocation.pileName).Insert(index, this);
}
}
public HandCardView GenerateHandCardView(string pileName, int index = -1)
{
PileBase pile = CombatUIManager.Instance.combatMainPage.Pile(pileName);
return GenerateHandCardView(pile, index);
}
/// <summary>
/// 生成手牌
/// </summary>
/// <param name="pile">手牌生成位置</param>
/// <param name="index">插入位置,-1为末尾</param>
/// <returns></returns>
public HandCardView GenerateHandCardView(PileBase pile, int index = -1)
{
GameObject handCardObjectPrefab = MainGameManager.Instance.basePrefabs.handCardObject;
@@ -90,4 +118,16 @@ namespace Continentis.MainGame.Card
}
}
}
public class CardLocation
{
public string pileName;
public int index;
public CardLocation(string pileName, int index)
{
this.pileName = pileName;
this.index = index;
}
}
}

View File

@@ -2,99 +2,145 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Continentis.MainGame.Character;
using Continentis.MainGame.UI;
using Lean.Pool;
using Sirenix.OdinInspector;
using Unity.VisualScripting;
using SLSFramework.General;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Continentis.MainGame.Card
{
public abstract partial class CardLogicBase
{
[Title("Reference")]
public CardData cardData;
[Header("Reference")] public CardData cardData;
public CardInstance cardInstance;
public ICardOwner owner => cardInstance.owner;
public CombatTeam team => cardInstance.team;
public CharacterBase user => cardInstance.user;
public CombatTeam team => cardInstance.team;
public HandCardView handCardView => cardInstance.handCardView;
[Title("Card Base Info")]
public IntentionCardView intentionCardView => cardInstance.intentionCardView;
[Header("Card Base Info")]
public Guid cardID;
public List<string> functionalTags;
public List<string> elementalTags;
public List<string> tags;
public int upgradeLevel;
[Title("Submodules")]
[ShowInInspector]
[Header("Submodules")]
public AttributeSubmodule attributeSubmodule { get; private set; }
[ShowInInspector]
public WeightSubmodule weightSubmodule { get; private set; }
[ShowInInspector]
public CombatBuffSubmodule combatBuffSubmodule { get; private set; }
[ShowInInspector]
public EventSubmodule eventSubmodule { get; private set; }
[ShowInInspector]
public EventSubmodule eventSubmodule { get; private set; }
public ContentSubmodule contentSubmodule { get; private set; }
[ShowInInspector]
public PlaySubmodule playSubmodule { get; private set; }
public HashSet<CardLogicComponentBase> logicComponents { get; private set; }
public void Setup()
{
this.cardID = Guid.NewGuid();
this.functionalTags = new List<string>(cardData.functionalTags);
this.elementalTags = new List<string>(cardData.elementalTags);
attributeSubmodule = new AttributeSubmodule(this);
weightSubmodule = new WeightSubmodule(this);
eventSubmodule = new EventSubmodule(this);
combatBuffSubmodule = new CombatBuffSubmodule(this);
contentSubmodule = new ContentSubmodule(this);
playSubmodule = new PlaySubmodule(this);
this.tags = new List<string>(cardData.tags);
this.attributeSubmodule = new AttributeSubmodule(this);
this.weightSubmodule = new WeightSubmodule(this);
this.eventSubmodule = new EventSubmodule(this);
this.combatBuffSubmodule = new CombatBuffSubmodule(this);
this.contentSubmodule = new ContentSubmodule(this);
this.playSubmodule = new PlaySubmodule(this);
this.logicComponents = new HashSet<CardLogicComponentBase>();
SetUpLogicComponents();
}
public virtual void InitialRefresh()
protected virtual void SetUpLogicComponents()
{
}
public virtual void Initialize()
{
RefreshCardAttributes();
CardDescriptionInterpreter.InterpretDescription(this);
CardTextInterpreter.InterpretText(this);
if (HasKeyword("Instant")) //如果是“瞬发”牌,添加抽牌后立刻打出的事件
{
eventSubmodule.onDraw.InsertByPriority("Instant", new EventUnit(() =>
{
DetectTargetsValidity(out List<CharacterBase> valid, out _, out _);
Play(SetRandomTargets(valid), user);
}, 99));
}
}
public T AddLogicComponent<T>() where T : CardLogicComponentBase, new()
{
if (logicComponents.Any(component => component is T))
{
Debug.LogWarning($"Card {cardData.classFullName} already has component of type {typeof(T)}, cannot add duplicate.");
return null;
}
else
{
T component = new T();
component.Initialize(this);
logicComponents.Add(component);
return component;
}
}
public T LogicComponent<T>() where T : CardLogicComponentBase
{
return logicComponents.OfType<T>().FirstOrDefault();
}
public void UpgradeCard()
{
if (team == null)
if (owner is not CombatTeam)
{
KeyValuePair<string, List<CardInstance>> currentPile = cardInstance.deck.GetCardLocation(cardInstance, out int index);
if (!cardData.upgradeNode.isTerminalNode)
{
cardInstance.DestroyHandCardView();
CardData newData = cardData.upgradeNode.upgradeCards[0]; //后续可改为选择升级方向
CardLogicBase newLogic = newData.GenerateCardLogic();
cardInstance.cardLogic = newLogic;
newLogic.cardInstance = cardInstance;
cardInstance.cardLogic.InitialRefresh();
if(user is PlayerHero)
cardInstance.GenerateHandCardView(CombatUIManager.Instance.deckPage.Pile(currentPile.Key), index);
cardInstance.cardLogic.Initialize();
if (user is PlayerHero)
cardInstance.GenerateHandCardView(CombatUIManager.Instance.combatMainPage.Pile(currentPile.Key), index);
}
}
else
{
}
}
}
public partial class CardLogicBase
/// <summary>
/// 卡牌逻辑组件基类接口
/// 注意,所有的子接口需要实现的函数:
/// ComponentTargetingEffect此牌瞄准目标时调用
/// ComponentUntargetingEffect此牌取消瞄准目标时调用
/// </summary>
public abstract class CardLogicComponentBase
{
protected CardLogicBase card;
protected CharacterBase user => card.user;
protected CombatTeam team => card.team;
public virtual void Initialize(CardLogicBase card)
{
this.card = card;
card.eventSubmodule.onTargeting += TargetingEffect;
card.eventSubmodule.onUntargeting += UntargetingEffect;
}
protected virtual void TargetingEffect(CharacterBase target)
{
}
protected virtual void UntargetingEffect()
{
}
}
}

View File

@@ -2,32 +2,32 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Continentis.MainGame.Character;
using Continentis.MainGame.Combat;
using Continentis.MainGame.Commands;
using DamageNumbersPro;
using SoulliesFramework.General;
using UnityEngine;
using SLSFramework.General;
using SLSFramework.UModAssistance;
using UniRx;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Continentis.MainGame.Card
{
public partial class CardLogicBase
{
public void Targeting(CharacterBase target)
{
TargetingEffect(target);
}
public void Untargeting()
{
UntargetingEffect();
}
protected virtual void TargetingEffect(CharacterBase target)
/// <summary>
/// 选中目标时触发的效果效果在所有逻辑组件的Targeting之前执行在SetUp函数生成EventSubmodule的时候
/// 如果必须需要在逻辑组件之后执行请重写Initialize函数。
/// </summary>
public virtual void TargetingEffect(CharacterBase target)
{
}
protected virtual void UntargetingEffect()
/// <summary>
/// 取消选中目标时触发的效果效果在所有逻辑组件的Untargeting之前执行在SetUp函数生成EventSubmodule的时候
/// 如果必须需要在逻辑组件之后执行请重写Initialize函数。
/// </summary>
public virtual void UntargetingEffect()
{
}
@@ -35,14 +35,23 @@ namespace Continentis.MainGame.Card
public partial class CardLogicBase
{
/// <summary>
/// 刷新卡牌属性
/// </summary>
public void RefreshCardAttributes()
{
if(user == null) return;
attributeSubmodule.RefreshAllAttributes();
if(handCardView == null || !handCardView.isSelecting) Untargeting();
if ((handCardView == null && intentionCardView == null) || !handCardView.isSelecting)
{
eventSubmodule.onUntargeting();
}
}
/// <summary>
/// 根据卡牌内容应用属性变化
/// </summary>
public virtual void ApplyAttributeChangesByCard()
{
@@ -51,28 +60,27 @@ namespace Continentis.MainGame.Card
public partial class CardLogicBase
{
public virtual void SetTargets(out List<CharacterBase> valid,
out List<CharacterBase> conditionNotMet, out List<CharacterBase> invalid)
public virtual void DetectTargetsValidity(out List<CharacterBase> valid, out List<CharacterBase> notMet, out List<CharacterBase> invalid)
{
List<CharacterBase> characters = CombatMainManager.Instance.characters;
List<CharacterBase> characters = CombatMainManager.Instance.characterController.characters;
invalid = new List<CharacterBase>();
conditionNotMet = new List<CharacterBase>();
notMet = new List<CharacterBase>();
valid = new List<CharacterBase>();
int targetCount = attributeSubmodule.targetCount;
if (targetCount <= -2)
{
throw new Exception("Target count is invalid.");
return;
}
if (targetCount == 0 || cardData.functionalTags.Contains("TargetSelf")) // 卡牌目标为自身
if (targetCount == 0 || cardData.tags.Contains("TargetSelf")) // 卡牌目标为自身
{
valid.Add(user);
invalid.AddRange(characters);
invalid.Remove(user);
}
else if (cardData.functionalTags.Contains("TargetAllies")) // 卡牌目标为友方单位
else if (cardData.tags.Contains("TargetAllies")) // 卡牌目标为友方单位
{
if(user.fraction is Fraction.Ally or Fraction.Player)
{
@@ -103,7 +111,7 @@ namespace Continentis.MainGame.Card
}
}
}
else if (cardData.functionalTags.Contains("TargetEnemies")) // 卡牌目标为敌人
else if (cardData.tags.Contains("TargetEnemies")) // 卡牌目标为敌人
{
if (user.fraction is Fraction.Ally or Fraction.Player)
{
@@ -142,44 +150,58 @@ namespace Continentis.MainGame.Card
.Where(target => !target.statusSubmodule.HasStatus(StatusType.Taunt))
.ToList();
conditionNotMet.AddRange(protectedTargets);
notMet.AddRange(protectedTargets);
valid.RemoveAll(target => !target.statusSubmodule.HasStatus(StatusType.Taunt));
}
}
}
else if(cardData.functionalTags.Contains("TargetAll")) // 卡牌目标为全体
else if(cardData.tags.Contains("TargetAll")) // 卡牌目标为全体
{
valid.AddRange(characters);
}
else
{
throw new Exception("No valid target found, please check the card data.");
Debug.Log("No valid target found");
}
}
public virtual List<CharacterBase> SetRandomTargets(List<CharacterBase> valid)
{
List<CharacterBase> targets = new List<CharacterBase>();
int maximumTargets = attributeSubmodule.targetCount;
if (maximumTargets == -1 || maximumTargets >= valid.Count)
{
targets.AddRange(valid);
}
else
{
while (targets.Count < maximumTargets && valid.Count > 0)
{
CharacterBase target = valid[Random.Range(0, valid.Count)];
valid.Remove(target);
targets.Add(target);
}
}
return targets;
}
public virtual bool CheckBeforePlay()
{
Vector3 userPosition = user.characterView.transform.position;
if (!CheckEnoughStamina())
if (!user.CheckEnoughStamina(GetAttribute("StaminaCost")))
{
MainGameManager.Instance.basePrefabs.GenerateInfoText("Not Enough Stamina", userPosition);
MainGameManager.Instance.basePrefabs.GenerateInfoText("Not Enough Stamina", user.characterView);
return false;
}
if (!CheckEnoughMana())
if (!user.CheckEnoughMana(GetAttribute("ManaCost")))
{
MainGameManager.Instance.basePrefabs.GenerateInfoText("Not Enough Mana", userPosition);
MainGameManager.Instance.basePrefabs.GenerateInfoText("Not Enough Mana", user.characterView);
return false;
}
return true;
}
/*public void Play(List<CharacterBase> targetList, CharacterBase user = null, bool willCheckBeforePlay = true)
{
CommandQueueManager.Instance.AddCommand(_Play(targetList, user, willCheckBeforePlay));
}*/
/// <summary>
/// 打出卡牌
@@ -199,19 +221,45 @@ namespace Continentis.MainGame.Card
handCardView.isDuringPlaying = true;
}
cardInstance.user = user ?? CombatMainManager.Instance.currentCharacter;
if (!willCheckBeforePlay || CheckBeforePlay())
{
eventSubmodule.onBeforePlay.Invoke(targetList);
CommandQueueManager.Instance.AddCommand(PlayEffect(targetList));
eventSubmodule.onAfterPlay.Invoke(targetList);
AfterPlayEffect(targetList);
combatBuffSubmodule.buffList.For(buff => buff.usageSubmodule?.UpdateModule());
if (cardInstance.user is PlayerHero)
CombatUIManager.Instance.deckPage.combatResourcesDisplayer.UpdateIcons();
cardInstance.user.ModifyStamina(-GetAttribute("StaminaCost"));
cardInstance.user.ModifyMana(-GetAttribute("ManaCost"));
if (cardInstance.user is PlayerHero)
{
CombatUIManager.Instance.combatMainPage.combatResourcesDisplayer.UpdateIcons();
}
Debug.Log($"Starting to play card: {contentSubmodule.cardName}");
CommandQueueManager.Instance.AddCommand(new Cmd_Function(() =>
{
playSubmodule.isDuringPlayEffect = true;
eventSubmodule.onBeforePlay.Invoke(targetList);
cardInstance.user.eventSubmodule?.onBeforePlayCard.Invoke(cardInstance, targetList);
cardInstance.user.combatBuffSubmodule.buffList.For(buff =>
{
buff.eventSubmodule?.onBeforePlayCard.Invoke(cardInstance, targetList);
});
}));
CommandQueueManager.Instance.AddCommand(PlayEffect(targetList));
CommandQueueManager.Instance.AddCommand(new Cmd_Function(() =>
{
eventSubmodule.onAfterPlay.Invoke(targetList);
combatBuffSubmodule.buffList.For(buff => buff.usageSubmodule?.UpdateModule());
cardInstance.user.eventSubmodule.onAfterPlayCard.Invoke(cardInstance, targetList);
cardInstance.user.combatBuffSubmodule.buffList.For(buff =>
{
buff.eventSubmodule?.onAfterPlayCard.Invoke(cardInstance, targetList);
});
AfterPlayEffect(targetList);
playSubmodule.isDuringPlayEffect = false;
}));
return true;
}
else
@@ -227,9 +275,6 @@ namespace Continentis.MainGame.Card
protected virtual CommandBase PlayEffect(List<CharacterBase> targetList)
{
ConsumeStamina();
ConsumeMana();
return null;
}
@@ -237,14 +282,14 @@ namespace Continentis.MainGame.Card
{
if (user is PlayerHero playerHero)
{
if (HasTag("Exhaust"))
if (HasKeyword("Exhaust"))
{
playerHero.deckSubmodule.ExhaustCard(cardInstance);
}
else
{
playerHero.deckSubmodule.DiscardCard(cardInstance);
CommandQueueManager.Instance.AddCommand(new Cmd_Function(0, () =>
CommandQueueManager.Instance.AddCommand(new Cmd_Function(() =>
{
if (handCardView != null)
{
@@ -255,7 +300,7 @@ namespace Continentis.MainGame.Card
}
else if (user is CombatNPC npc)
{
if (HasTag("Exhaust"))
if (HasKeyword("Exhaust"))
{
npc.deckSubmodule.ExhaustCard(cardInstance);
}
@@ -265,34 +310,6 @@ namespace Continentis.MainGame.Card
public partial class CardLogicBase
{
/// <summary>
/// 添加格挡(格挡每回合结束后会清空)
/// </summary>
protected void AddBlock(int block, CharacterBase target = null)
{
target ??= user;
target.attributeSubmodule.generalAttributeGroup.current["Block"] += block;
target.characterView.hudContainer.UpdateAllHUD();
}
/// <summary>
/// 添加闪避(闪避在回合结束后或被击中后清空)
/// </summary>
protected void AddDodge(int dodge, CharacterBase target = null)
{
target ??= user;
target.attributeSubmodule.generalAttributeGroup.current["Dodge"] += dodge;
target.characterView.hudContainer.UpdateAllHUD();
}
/// <summary>
/// 添加护盾(护盾不会自动清空)
/// </summary>
protected void AddShield(int shield, CharacterBase target = null)
{
target ??= user;
target.attributeSubmodule.generalAttributeGroup.current["Shield"] += shield;
target.characterView.hudContainer.UpdateAllHUD();
}
}
}

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using SoulliesFramework.General;
using SLSFramework.General;
using Unity.VisualScripting;
using UnityEngine;
@@ -18,9 +18,9 @@ namespace Continentis.MainGame.Card
private void Initialize(CardData cardData)
{
Dictionary<string, float> originalAttributes = new Dictionary<string, float>(cardData.originalAttributes);
Dictionary<string, string> endowingAttributes = new Dictionary<string, string>(cardData.endowingCurrentAttributes);
Dictionary<string, string> endowingAttributes = new Dictionary<string, string>(cardData.runtimeCurrentAttributes);
if (cardData.upgradeNode.isInfiniteUpgrade)
if (cardData.upgradeNode is { isInfiniteUpgrade: true })
{
foreach (KeyValuePair<string, float> upgrade in cardData.upgradeNode.GetUpgradeAttributes(owner.upgradeLevel))
{

View File

@@ -1,249 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using DynamicExpresso;
using UnityEngine;
namespace Continentis.MainGame.Card
{
public static partial class CardDescriptionInterpreter
{
public static Interpreter DescriptionInterpreter;
static CardDescriptionInterpreter()
{
DescriptionInterpreter = new Interpreter();
DescriptionInterpreter.SetFunction("GetDynamicValue", new Func<float, float, bool, string>(GetDynamicValue));
DescriptionInterpreter.SetFunction("GetDynamicValue", new Func<float, string>(GetDynamicValue));
DescriptionInterpreter.SetFunction("AttributeCheck", new Func<string, float, float, string>(AttributeCheck));
foreach (KeyValuePair<string,string> keyword in CombatMainManager.Instance.keywordCollection.keywords)
{
DescriptionInterpreter.SetVariable(keyword.Key, keyword.Key);
}
foreach (KeyValuePair<string, InterpretedKeyword> keyword in CombatMainManager.Instance.keywordCollection.interpretedKeywords)
{
DescriptionInterpreter.SetVariable(keyword.Key, keyword.Value.name);
}
}
public static string InterpretDescription(CardLogicBase card)
{
card.contentSubmodule.keywords.Clear();
foreach (KeyValuePair<string, float> attribute in card.attributeSubmodule.attributeGroup.current)
{
DescriptionInterpreter.SetVariable(attribute.Key, attribute.Value);
}
DescriptionInterpreter.SetFunction("RemoveWhenSelecting", new Func<string, string>((toRemove) => RemoveWhenSelecting(card, toRemove)));
string result = ParseKeywords(card.cardData.cardDescription, card.contentSubmodule.keywords);
card.contentSubmodule.cardDescription = result;
Debug.Log($"Interpreted Description: {result}");
return result;
}
}
public partial class CardDescriptionInterpreter
{
public static List<string> GetKeywords(string template)
{
List<string> keywords = new List<string>();
DescriptionInterpreter.UnsetExpression("Keyword");
DescriptionInterpreter.SetFunction("Keyword", new Action<string>((keywordName) =>
{
if (!string.IsNullOrEmpty(keywordName) && !keywords.Contains(keywordName))
{
keywords.Add(keywordName);
}
}));
try
{
//提取所有$Keyword(...)的关键词
//其它的表达式直接跳过
while (template.Contains("$"))
{
int startIndex = template.LastIndexOf('$');
int endIndex = FindMatchingClosingParenthesis(template, startIndex);
if (endIndex == -1)
{
Debug.LogError($"解析错误: 在 '{template}' 中找不到与 '{template.Substring(startIndex)}' 匹配的闭合括号。");
break; // 中断以防止死循环
}
string expressionToEvaluate = template.Substring(startIndex, endIndex - startIndex + 1);
string cleanExpression = expressionToEvaluate.Substring(1);
// 仅处理 Keyword 函数,跳过其他表达式
if (cleanExpression.StartsWith("Keyword"))
{
DescriptionInterpreter.Eval(cleanExpression);
}
// 移除已处理的表达式,继续查找下一个
template = template.Substring(0, startIndex) + template.Substring(endIndex + 1);
}
}
catch (Exception ex)
{
throw new Exception($"解析模板时发生严重错误: {ex.Message}\nStackTrace: {ex.StackTrace}");
}
return keywords;
}
public static string ParseKeywords(string template, List<string> keywords)
{
DescriptionInterpreter.UnsetExpression("Keyword");
DescriptionInterpreter.SetFunction("Keyword", new Func<string, string>((keywordName) =>
{
if (!string.IsNullOrEmpty(keywordName) && !keywords.Contains(keywordName))
{
keywords.Add(keywordName);
}
return Keyword(keywordName);
}));
try
{
while (template.Contains("$"))
{
int startIndex = template.LastIndexOf('$');
int endIndex = FindMatchingClosingParenthesis(template, startIndex);
if (endIndex == -1)
{
Debug.LogError($"解析错误: 在 '{template}' 中找不到与 '{template.Substring(startIndex)}' 匹配的闭合括号。");
return template; // 中断以防止死循环
}
string expressionToEvaluate = template.Substring(startIndex, endIndex - startIndex + 1);
string cleanExpression = expressionToEvaluate.Substring(1);
object result = DescriptionInterpreter.Eval(cleanExpression);
string resultAsLiteral = result.ToString();
template = template.Substring(0, startIndex) + resultAsLiteral + template.Substring(endIndex + 1);
}
}
catch (Exception ex)
{
throw new Exception($"解析模板时发生严重错误: {ex.Message}\nStackTrace: {ex.StackTrace}");
}
return template;
}
private static int FindMatchingClosingParenthesis(string text, int startIndex)
{
int openParenIndex = text.IndexOf('(', startIndex);
if (openParenIndex == -1) return -1;
int parenthesisCounter = 1;
bool isInString = false;
for (int i = openParenIndex + 1; i < text.Length; i++)
{
char c = text[i];
char prevC = i > 0 ? text[i - 1] : '\0';
// 检查是否进入或退出字符串(忽略转义的引号 \"
if (c == '"' && prevC != '\\')
{
isInString = !isInString;
}
// 如果在字符串中,则跳过对括号的计数
if (isInString)
{
continue;
}
if (c == '(')
{
parenthesisCounter++;
}
else if (c == ')')
{
parenthesisCounter--;
}
if (parenthesisCounter == 0)
{
return i;
}
}
return -1; // 没有找到匹配的闭合括号
}
}
public partial class CardDescriptionInterpreter
{
private static string GetDynamicValue(float current, float baseValue, bool normalMode)
{
string color;
if (current > baseValue)
color = normalMode ? "green" : "red";
else if (current < baseValue)
color = normalMode ? "red" : "green";
else
color = "white";
//Debug.Log($"GetDynamicValue: current={current}, baseValue={baseValue}, normalMode={normalMode}, color={color}");
return $"<color={color}>{current}</color>";
}
private static string GetDynamicValue(float current)
{
return current.ToString(CultureInfo.CurrentCulture);
}
private static string Keyword(string keyword)
{
string color = "orange";
return $"<color={color}>{keyword}</color>";
}
private static string RemoveWhenSelecting(CardLogicBase card, string toRemove)
{
return card.handCardView != null && card.handCardView.isSelecting ? "" : toRemove;
}
private static string AttributeCheck(string attributeName, float requirement, float probability)
{
if (probability < 0)
{
return "{" + attributeName + " check: " + requirement + "}";
}
int percent = Mathf.RoundToInt(probability * 100);
string result = percent + "%";
if (percent == 0)
{
result = "<color=red>" + result + "</color>";
}
else if (percent > 0 && percent < 100)
{
result = "<color=yellow>" + result + "</color>";
}
else if (percent == 100)
{
result = "<color=green>" + result + "</color>";
}
return result;
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: cbc566d961d78f142a5699dc40f43462

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using SoulliesFramework.General;
using SLSFramework.General;
using UnityEngine;
namespace Continentis.MainGame.Card
@@ -38,7 +38,7 @@ namespace Continentis.MainGame.Card
public void RoundStart()
{
buffList.For(buff => buff.combatRoundTimeSubmodule?.UpdateModule());
buffList.For(buff => buff.combatRoundTimeSubmodule?.Update());
buffList.For(buff => buff.OnRoundStart());
}
@@ -49,7 +49,7 @@ namespace Continentis.MainGame.Card
public void ActionStart()
{
buffList.For(buff => buff.combatActionTimeSubmodule?.UpdateModule());
buffList.For(buff => buff.combatActionTimeSubmodule?.Update());
buffList.For(buff => buff.OnActionStart());
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using SLSFramework.General;
using UnityEngine;
namespace Continentis.MainGame.Card
@@ -6,30 +7,27 @@ namespace Continentis.MainGame.Card
public class ContentSubmodule : SubmoduleBase<CardLogicBase>
{
public List<string> keywords;
public List<string> hintKeywords;
public string cardName;
public Sprite cardSprite;
public CardRarity cardRarity;
public Rarity cardRarity;
public CardType cardType;
public string cardDescription;
public string originalFunctionText;
public string interpretedFunctionText;
public ContentSubmodule(CardLogicBase card) : base(card)
{
keywords = new List<string>();
cardName = card.cardData.cardName;
hintKeywords = new List<string>();
cardName = card.cardData.displayName.Localize();
cardSprite = card.cardData.cardSprite;
cardDescription = card.cardData.cardDescription;
originalFunctionText = card.cardData.functionText.Localize();
cardRarity = card.cardData.cardRarity;
cardType = card.cardData.cardType;
//CardDescriptionInterpreter.InterpretDescription(card);
//keywords = CardDescriptionInterpreter.GetKeywords(card.cardData.cardDescription);
//Debug.Log($"Extracted Keywords: {string.Join(", ", keywords)}");
}
public string GetFinalDescription()
{
//finalDescription = CardDescriptionInterpreter.InterpretDescription(card);
return cardDescription;
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using Continentis.MainGame.Character;
using SoftCircuits.Collections;
using SLSFramework.General;
using UnityEngine;
using UnityEngine.Events;
@@ -8,6 +9,9 @@ namespace Continentis.MainGame.Card
{
public partial class EventSubmodule : SubmoduleBase<CardLogicBase>
{
public UnityAction<CharacterBase> onTargeting; //选中目标时
public UnityAction onUntargeting; //取消选中目标时
public OrderedDictionary<string, EventUnit> onCombatStart; //战斗开始时
public OrderedDictionary<string, EventUnit> onCombatEnd; //战斗结束时
@@ -25,6 +29,9 @@ namespace Continentis.MainGame.Card
public EventSubmodule(CardLogicBase card) : base(card)
{
onTargeting += card.TargetingEffect;
onUntargeting = card.UntargetingEffect;
onCombatStart = new OrderedDictionary<string, EventUnit>();
onCombatEnd = new OrderedDictionary<string, EventUnit>();
@@ -44,9 +51,6 @@ namespace Continentis.MainGame.Card
public partial class EventSubmodule
{
protected void SetUpDefaultEvents()
{
}
}
}

View File

@@ -4,9 +4,11 @@ namespace Continentis.MainGame.Card
{
public partial class PlaySubmodule : SubmoduleBase<CardLogicBase>
{
public bool isDuringPlayEffect;
public PlaySubmodule(CardLogicBase card) : base(card)
{
isDuringPlayEffect = false;
}
}

View File

@@ -1,10 +1,9 @@
using System;
using Sirenix.OdinInspector;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace Continentis.MainGame.Card
@@ -21,16 +20,27 @@ namespace Continentis.MainGame.Card
public TMP_Text staminaCostText;
public TMP_Text manaCostText;
public TMP_Text cardTypeText;
public Image cardBackground;
public Image cardImage;
public Image cardRarityMark;
public CardViewKeywordList keywordList;
public Image normalShadow;
public Image hintShadow;
public Image selectShadow;
public string collectionName;
}
public partial class CardViewBase
{
public bool isOccupied;
public bool isHovering;
public bool isSelecting;
public bool isDuringPlaying;
private void Update()
@@ -46,7 +56,11 @@ namespace Continentis.MainGame.Card
if(isDuringPlaying) return;
isHovering = true;
keywordList.Enable(cardInstance.cardLogic.contentSubmodule.keywords);
List<string> allKeywords = new List<string>();
allKeywords.AddRange(cardLogic.contentSubmodule.keywords);
allKeywords.AddRange(cardLogic.contentSubmodule.hintKeywords);
keywordList.Enable(allKeywords);
}
public virtual void OnPointerExit(PointerEventData eventData)
@@ -65,37 +79,41 @@ namespace Continentis.MainGame.Card
cardInstance = card;
}
isOccupied = false;
isHovering = false;
isSelecting = false;
isDuringPlaying = false;
cardNameText.text = cardLogic.contentSubmodule.cardName;
cardDescriptionText.text = cardLogic.contentSubmodule.cardDescription;
cardDescriptionText.text = cardLogic.contentSubmodule.interpretedFunctionText;
cardImage.sprite = cardLogic.contentSubmodule.cardSprite;
if (cardLogic.contentSubmodule.cardRarity != CardRarity.None)
if (cardLogic.contentSubmodule.cardRarity != Rarity.None)
{
cardRarityMark.color = cardLogic.contentSubmodule.cardRarity switch
{
CardRarity.Common => Color.white,
CardRarity.Uncommon => new Color(0.2f, 0.7f, 0.2f), // 绿色
CardRarity.Rare => new Color(0f, 0.5f, 1f), // 蓝色
CardRarity.Epic => new Color(0.6f, 0f, 0.8f), // 紫色
CardRarity.Legendary => new Color(1f, 0.5f, 0f), // 橙色
CardRarity.Divine => new Color(1f, 0.84f, 0f), // 金色
_ => Color.grey
};
cardRarityMark.color = MainGameManager.Instance.basePrefabs.GetRarityColor(cardLogic.contentSubmodule.cardRarity);
}
else
{
cardRarityMark.gameObject.SetActive(false);
}
if (string.IsNullOrEmpty(collectionName)) collectionName = "Basic";
CardViewCollection collection = MainGameManager.Instance.basePrefabs.cardViewCollections[collectionName];
List<string> elementTags = cardLogic.GetElementTags();
string firstElementTag = elementTags.Count > 0 ? elementTags[0] : "Default";
if (!collection.cardViews.ContainsKey(firstElementTag)) firstElementTag = "Default";
cardBackground.sprite = collection.cardViews[firstElementTag].background;
cardTypeText.text = cardLogic.contentSubmodule.cardType.ToString();
staminaCostText.rectTransform.parent.gameObject.SetActive(true);
staminaCostText.text = cardLogic.attributeSubmodule.GetRoundCurrentAttribute("StaminaCost").ToString();
int manaCost = cardLogic.attributeSubmodule.GetRoundCurrentAttribute("ManaCost");
manaCostText.rectTransform.parent.gameObject.SetActive(manaCost > 0);
manaCostText.text = manaCost.ToString();
if (cardLogic.HasTag("Unplayable")) // 如果卡牌不能被打出,则隐藏费用文本
if (cardLogic.HasKeyword("Unplayable")) // 如果卡牌不能被打出,则隐藏费用文本
{
staminaCostText.rectTransform.parent.gameObject.SetActive(false);
manaCostText.rectTransform.parent.gameObject.SetActive(false);

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using SLSFramework.General;
using UnityEngine;
namespace Continentis.MainGame.Card
{
[CreateAssetMenu(fileName = "CardViewCollection", menuName = "Continentis/MainGame/Card/CardViewCollection")]
public class CardViewCollection : ScriptableObject
{
[KeyWidth(0.25f)]
public SerializableDictionary<string, CardViewParts> cardViews;
}
[Serializable]
public class CardViewParts
{
public Sprite background;
[KeyWidth(0.25f)]
public SerializableDictionary<string, GameObject> customParts;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d9af8ea1600887d498ff2b813a9aaef8

View File

@@ -0,0 +1,65 @@
using DG.Tweening;
using UnityEngine;
namespace Continentis.MainGame.Card
{
public abstract partial class CardViewBase
{
Tweener hintShadowTweener;
Tweener selectShadowTweener;
public void ClearShadows()
{
hintShadowTweener?.Kill(true);
selectShadowTweener?.Kill(true);
hintShadow.gameObject.SetActive(false);
selectShadow.gameObject.SetActive(false);
}
public void TryDisableAllShadows(bool immediately = false)
{
DisableHintShadow(immediately);
DisableSelectShadow(immediately);
}
public void EnableHintShadow(Color shadowColor)
{
hintShadow.gameObject.SetActive(true);
hintShadow.color = Color.clear;
hintShadowTweener = hintShadow.DOColor(shadowColor, 0.2f).Play();
}
public void DisableHintShadow(bool immediately = false)
{
if (immediately)
{
hintShadowTweener?.Kill(true);
hintShadow.gameObject.SetActive(false);
}
else
{
hintShadowTweener = hintShadow.DOColor(Color.clear, 0.2f).OnComplete(() => { hintShadow.gameObject.SetActive(false); }).Play();
}
}
public void EnableSelectShadow()
{
selectShadow.gameObject.SetActive(true);
selectShadow.color = Color.clear;
selectShadowTweener = selectShadow.DOColor(new Color(0.33f, 0.66f, 1f), 0.2f).Play();
}
public void DisableSelectShadow(bool immediately = false)
{
if (immediately)
{
selectShadowTweener?.Kill(true);
selectShadow.gameObject.SetActive(false);
}
else
{
selectShadowTweener = selectShadow.DOColor(Color.clear, 0.2f).OnComplete(() => { selectShadow.gameObject.SetActive(false); }).Play();
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5352b6ab2496aa34e82a2182052d556c

View File

@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using Continentis.MainGame.Combat;
using Continentis.MainGame.UI;
using DG.Tweening;
using Lean.Pool;
using SoulliesFramework.General;
using SLSFramework.General;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
@@ -27,15 +28,19 @@ namespace Continentis.MainGame.Card
disableTweener = canvasGroup.DOFade(0f, 0.2f).SetEase(Ease.OutQuad).SetAutoKill(false);
}
public void SetUp(List<string> keywords)
public void SetUp(List<string> keys)
{
keywordsContainer.DespawnAllChildren();
foreach (string keyword in keywords)
foreach (string key in keys)
{
string desc = CombatMainManager.Instance.keywordCollection.GetKeywordDescription(keyword);
InformationBox keywordBox = LeanPool.Spawn(MainGameManager.Instance.basePrefabs.informationBox, keywordsContainer).GetComponent<InformationBox>();
keywordBox.Initialize(keyword, desc);
if (MainGameManager.Instance.keywordData.TryGetLocalizedKeyword(key, out string locName, out string locDesc))
{
InformationBox keywordBox = LeanPool.Spawn(MainGameManager.Instance.basePrefabs.informationBox, keywordsContainer)
.GetComponent<InformationBox>();
keywordBox.Initialize(locName, locDesc);
}
}
}

View File

@@ -1,7 +1,8 @@
using System.Collections.Generic;
using Continentis.MainGame.Character;
using Continentis.MainGame.Combat;
using Continentis.MainGame.UI;
using SoulliesFramework.General;
using SLSFramework.General;
using UnityEngine;
using UnityEngine.EventSystems;
@@ -9,6 +10,8 @@ namespace Continentis.MainGame.Card
{
public partial class HandCardView : IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerClickHandler
{
public CombatCharacterViewBase currentTargetingCharacterView;
public List<CharacterBase> validTargets = new List<CharacterBase>();
public List<CharacterBase> conditionNotMetTargets = new List<CharacterBase>();
public List<CharacterBase> invalidTargets = new List<CharacterBase>();
@@ -37,17 +40,30 @@ namespace Continentis.MainGame.Card
public void OnPointerClick(PointerEventData eventData)
{
if (CombatUIManager.Instance.deckPage.handCardSelector.gameObject.activeInHierarchy)
if (CombatUIManager.Instance.combatMainPage.handCardSelector.gameObject.activeInHierarchy)
{
if(isOccupied) return;
if (CombatUIManager.Instance.deckPage.handCardSelector.selectedCards.ContainsKey(cardInstance))
if (CombatUIManager.Instance.combatMainPage.handCardSelector.selectedCards.Contains(cardInstance))
{
CombatUIManager.Instance.deckPage.handCardSelector.Deselect(this);
CombatUIManager.Instance.combatMainPage.handCardSelector.Deselect(this);
}
else
{
CombatUIManager.Instance.deckPage.handCardSelector.Select(this);
CombatUIManager.Instance.combatMainPage.handCardSelector.Select(this);
}
}
else if (CombatUIManager.Instance.combatMainPage.customCardSelector.gameObject.activeInHierarchy)
{
if(isOccupied) return;
if (CombatUIManager.Instance.combatMainPage.customCardSelector.selectedCards.Contains(cardInstance))
{
CombatUIManager.Instance.combatMainPage.customCardSelector.Deselect(this);
}
else
{
CombatUIManager.Instance.combatMainPage.customCardSelector.Select(this);
}
}
}
@@ -58,7 +74,7 @@ namespace Continentis.MainGame.Card
CombatUIManager.Instance.selectingCardView = this;
cardInstance.user = CombatMainManager.Instance.currentCharacter;
cardLogic.SetTargets(out validTargets, out conditionNotMetTargets, out invalidTargets);
cardLogic.DetectTargetsValidity(out validTargets, out conditionNotMetTargets, out invalidTargets);
if (cardLogic.attributeSubmodule.targetCount == 1)
{
@@ -81,19 +97,26 @@ namespace Continentis.MainGame.Card
Camera uiCamera = CombatUIManager.Instance.uiCamera;
Camera worldCamera = CombatUIManager.Instance.combatCamera;
if (CombatUIManager.Instance.hoveringCharacterView != null)
// 先检查悬停的视图是否和当前记录的目标视图发生了变化
if (CombatUIManager.Instance.hoveringCharacterView != currentTargetingCharacterView)
{
cardLogic.Targeting(CombatUIManager.Instance.hoveringCharacterView.character);
Debug.Log("Hovering over " + CombatUIManager.Instance.hoveringCharacterView.character);
}
else
{
cardLogic.Untargeting();
// 如果悬停视图不是空的,说明鼠标移动到了一个新的目标上
if (CombatUIManager.Instance.hoveringCharacterView != null)
{
cardLogic.eventSubmodule.onTargeting(CombatUIManager.Instance.hoveringCharacterView.character);
currentTargetingCharacterView = CombatUIManager.Instance.hoveringCharacterView;
}
else // 悬停视图是空的,说明鼠标离开了之前的目标
{
cardLogic.eventSubmodule.onUntargeting();
currentTargetingCharacterView = null;
}
// 因为目标发生了变化(无论是选中了新的还是取消了),所以统一在这里更新描述
CardTextInterpreter.InterpretText(cardLogic);
cardDescriptionText.text = cardLogic.contentSubmodule.interpretedFunctionText;
}
CardDescriptionInterpreter.InterpretDescription(cardLogic);
cardDescriptionText.text = cardLogic.contentSubmodule.cardDescription;
Vector3 startPosition = cardTransform.position + new Vector3(0, cardTransform.rect.height * cardTransform.lossyScale.y / 2, 0);
Vector3 endPosition = SpaceConverter.ScreenPointToUIPoint(arrowCanvasRect, eventData.position, uiCamera);
PointerArrow mainPointerArrow = CombatUIManager.Instance.arrowsPage.mainPointerArrow;
@@ -203,7 +226,7 @@ namespace Continentis.MainGame.Card
CharacterBase hoveringCharacter = hoveringCharacterView != null ? hoveringCharacterView.character : null;
Camera uiCamera = CombatUIManager.Instance.uiCamera;
CombatUIManager.Instance.arrowsPage.ClearPointerArrows();
if (isSelecting)
{
isSelecting = false;
@@ -211,16 +234,21 @@ namespace Continentis.MainGame.Card
CombatUIManager.Instance.selectingCardView = null;
canvas.overrideSorting = false;
canvas.sortingOrder = 0;
cardLogic.Untargeting();
CardDescriptionInterpreter.InterpretDescription(cardLogic);
cardDescriptionText.text = cardLogic.contentSubmodule.cardDescription;
cardLogic.eventSubmodule.onUntargeting();
CardTextInterpreter.InterpretText(cardLogic);
cardDescriptionText.text = cardLogic.contentSubmodule.interpretedFunctionText;
}
else
{
return;
}
if (cardLogic.HasKeyword("Unplayable")) // 如果有“不能打出”关键词,直接返回
{
return;
}
if (!cardLogic.cardData.functionalTags.Contains("TargetSelf"))
if (!cardLogic.HasTag("TargetSelf"))
{
if (!validTargets.Contains(hoveringCharacter))
{
@@ -236,15 +264,15 @@ namespace Continentis.MainGame.Card
{
if (!cardLogic.Play(new List<CharacterBase>() { CombatUIManager.Instance.hoveringCharacterView.character }))
{
cardLogic.Untargeting();
CardDescriptionInterpreter.InterpretDescription(cardLogic);
cardDescriptionText.text = cardLogic.contentSubmodule.cardDescription;
cardLogic.eventSubmodule.onUntargeting();
CardTextInterpreter.InterpretText(cardLogic);
cardDescriptionText.text = cardLogic.contentSubmodule.interpretedFunctionText;
}
}
}
else
{
RectTransform dropZone = CombatUIManager.Instance.deckPage.dropZone;
RectTransform dropZone = CombatUIManager.Instance.combatMainPage.dropZone;
bool isInDropZone = RectTransformUtility.RectangleContainsScreenPoint(dropZone, eventData.position, uiCamera);
List<CharacterBase> targetList = new List<CharacterBase>();

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1d9724259ccea534b91e4aff8161b6a1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,74 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using SLSFramework.UModAssistance;
using Continentis.MainGame.Character;
namespace Continentis.MainGame.Card
{
[CustomEditor(typeof(CardData))]
public class CardDataEditor : DataEditor
{
// 存储我们需要自定义绘制的属性的引用
private SerializedProperty classFullNameProp;
private SerializedProperty prefabsProp;
private SerializedProperty derivativeCardsProp;
private SerializedProperty derivativeCharactersProp;
protected override void OnEnable()
{
base.OnEnable();
// 在启用时根据我们修改后的字段名找到对应的SerializedProperty
classFullNameProp = serializedObject.FindProperty("classFullName");
prefabsProp = serializedObject.FindProperty("prefabRefs");
derivativeCardsProp = serializedObject.FindProperty("derivativeCardDataRefs");
derivativeCharactersProp = serializedObject.FindProperty("derivativeCharacterDataRefs");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// --- 绘制自定义的Type选择器 ---
// 我们把它从所有自动绘制的属性中分离出来,放在最前面或最后面,让布局更清晰
EditorGUILayout.Space(); // 增加一点间距
EditorGUILayout.LabelField("Logic", EditorStyles.boldLabel);
if (DrawTypeSelectorGUI(classFullNameProp, "Card Logic Class", typeof(CardLogicBase), "Continentis.Mods", "Cards"))
{
string classFullName = classFullNameProp.stringValue;
string displayName = "Card_" + classFullName + "_DisplayName";
SerializedProperty displayNameProp = serializedObject.FindProperty("displayName");
displayNameProp.stringValue = displayName;
string functionTextName = "Card_" + classFullName + "_FunctionText";
SerializedProperty functionTextProp = serializedObject.FindProperty("functionText");
functionTextProp.stringValue = functionTextName;
}
// --- 核心修复 2将 _cardLogicClassNameProp 也加入排除列表 ---
// 因为这也是我们手动绘制的
DrawPropertiesExcluding(serializedObject, new string[]
{
"m_Script",
classFullNameProp.name, // <-- 新增
prefabsProp.name,
derivativeCardsProp.name,
derivativeCharactersProp.name
});
// --- 绘制自定义的引用列表 ---
EditorGUILayout.Space();
EditorGUILayout.LabelField("References", EditorStyles.boldLabel);
DrawCharacterListGUI<GameObject>(prefabsProp);
DrawCharacterListGUI<CardData>(derivativeCardsProp);
DrawCharacterListGUI<CharacterData>(derivativeCharactersProp);
HandleObjectPicker();
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8385aa085e0e6154da49b54bdc5d26e7

View File

@@ -0,0 +1,76 @@
using Continentis.MainGame.Character;
using Continentis.Mods.Basic.Cards;
using UnityEngine;
namespace Continentis.MainGame.Card
{
public class CardLogicComponent_Attack : CardLogicComponentBase
{
protected override void TargetingEffect(CharacterBase target)
{
card.SetAttribute("DisplayDamage", card.GetFinalDamage(target));
}
protected override void UntargetingEffect()
{
card.SetAttribute("DisplayDamage", card.GetAttribute("Damage"));
}
/// <summary>
/// 设置伤害值
/// </summary>
/// <param name="additive">是否为叠加true为叠加false为覆盖</param>
/// <param name="originalDamage">原始伤害值仅在additive为true时有效否则被覆盖为BaseDamage</param>
/// <param name="damageOffset">伤害增量</param>
public void SetDamage(int damageOffset, bool additive = false, int originalDamage = 0)
{
card.SetVariableAttribute("Damage", damageOffset, additive, originalDamage);
}
/// <summary>
/// 斩击伤害计算,伤害=基础伤害+(力量加成+敏捷加成)/2
/// </summary>
public void SetDamage_Slash(bool additive = false, int originalDamage = 0)
{
float rawDamageOffsetFromStrength = user.GetRawAttribute("OffsetFromStrength");
float rawDamageOffsetFromAgility = user.GetRawAttribute("OffsetFromAgility");
SetDamage(Mathf.RoundToInt((rawDamageOffsetFromStrength + rawDamageOffsetFromAgility) / 2f), additive, originalDamage);
}
/// <summary>
/// 打击伤害计算,伤害=基础伤害+力量加成
/// </summary>
public void SetDamage_Strike(bool additive = false, int originalDamage = 0)
{
int damageOffset = user.GetAttribute("OffsetFromStrength");
SetDamage(damageOffset, additive, originalDamage);
}
/// <summary>
/// 突刺伤害计算,伤害=基础伤害+敏捷加成
/// </summary>
public void SetDamage_Prick(bool additive = false, int originalDamage = 0)
{
int damageOffset = user.GetAttribute("OffsetFromAgility");
SetDamage(damageOffset, additive, originalDamage);
}
/// <summary>
/// 奥术伤害计算,伤害=基础伤害+智力加成
/// </summary>
public void SetDamage_Arcane(bool additive = false, int originalDamage = 0)
{
int damageOffset = user.GetAttribute("OffsetFromIntelligence");
SetDamage(damageOffset, additive, originalDamage);
}
/// <summary>
/// 契术伤害计算,伤害=基础伤害+魅力加成
/// </summary>
public void SetDamage_Sorcery(bool additive = false, int originalDamage = 0)
{
int damageOffset = user.GetAttribute("OffsetFromCharisma");
SetDamage(damageOffset, additive, originalDamage);
}
}
}

View File

@@ -0,0 +1,112 @@
using Continentis.MainGame.Card;
using Continentis.MainGame.Character;
using UnityEngine;
namespace Continentis.MainGame.Card
{
public class CardLogicComponent_Defense : CardLogicComponentBase
{
protected override void TargetingEffect(CharacterBase target)
{
if (card.HasAttribute("Block"))
{
card.SetAttribute("DisplayBlock", card.GetAttribute("Block"));
}
else if(card.HasAttribute("Dodge"))
{
card.SetAttribute("DisplayDodge", card.GetAttribute("Dodge"));
}
else if(card.HasAttribute("Shield"))
{
card.SetAttribute("DisplayShield", card.GetAttribute("Shield"));
}
}
protected override void UntargetingEffect()
{
if (card.HasAttribute("Block"))
{
card.SetAttribute("DisplayBlock", card.GetAttribute("Block"));
}
else if(card.HasAttribute("Dodge"))
{
card.SetAttribute("DisplayDodge", card.GetAttribute("Dodge"));
}
else if(card.HasAttribute("Shield"))
{
card.SetAttribute("DisplayShield", card.GetAttribute("Shield"));
}
}
/// <summary>
/// 设置格挡值,默认由体质加成
/// </summary>
public void SetBlock_Fortitude(bool additive = false, int originalBlock = 0)
{
int blockOffsetFromPhysique = user.GetAttribute("OffsetFromPhysique");
card.SetVariableAttribute("Block", blockOffsetFromPhysique, additive, originalBlock);
}
/// <summary>
/// 设置格挡值,由智力加成
/// </summary>
public void SetBlock_Arcane(bool additive = false, int originalBlock = 0)
{
int blockOffsetFromIntelligence = user.GetAttribute("OffsetFromIntelligence");
card.SetVariableAttribute("Block", blockOffsetFromIntelligence, additive, originalBlock);
}
/// <summary>
/// 设置格挡值,由魅力加成
/// </summary>
public void SetBlock_Sorcery(bool additive = false, int originalBlock = 0)
{
int blockOffsetFromCharisma = user.GetAttribute("OffsetFromCharisma");
card.SetVariableAttribute("Block", blockOffsetFromCharisma, additive, originalBlock);
}
/// <summary>
/// 设置闪避值,由敏捷加成
/// </summary>
public void SetDodge_Swiftness(bool additive = false, int originalDodge = 0)
{
int dodgeOffsetFromAgility = user.GetAttribute("OffsetFromAgility");
card.SetVariableAttribute("Dodge", dodgeOffsetFromAgility, additive, originalDodge);
}
/// <summary>
/// 设置闪避值,由智力加成
/// </summary>
public void SetDodge_Arcane(bool additive = false, int originalDodge = 0)
{
int dodgeOffsetFromIntelligence = user.GetAttribute("OffsetFromIntelligence");
card.SetVariableAttribute("Dodge", dodgeOffsetFromIntelligence, additive, originalDodge);
}
/// <summary>
/// 设置闪避值,由魅力加成
/// </summary>
public void SetDodge_Sorcery(bool additive = false, int originalDodge = 0)
{
int dodgeOffsetFromCharisma = user.GetAttribute("OffsetFromCharisma");
card.SetVariableAttribute("Dodge", dodgeOffsetFromCharisma, additive, originalDodge);
}
/// <summary>
/// 设置闪避值,由感知加成
/// </summary>
public void SetDodge_Prediction(bool additive = false, int originalDodge = 0)
{
int dodgeOffsetFromPrediction = user.GetAttribute("OffsetFromPerception");
card.SetVariableAttribute("Dodge", dodgeOffsetFromPrediction, additive, originalDodge);
}
/// <summary>
/// 设置护盾值,默认无加成
/// </summary>
public void SetShield(bool additive = false, int originalShield = 0)
{
card.SetVariableAttribute("Shield", 0, additive, originalShield);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0e81c09b6cb1ff44091ccc365049bbb0

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Continentis.MainGame;
using Continentis.MainGame.Card;
using SLSFramework.UModAssistance;
using UnityEngine;
namespace Continentis.Mods.Basic.Cards
{
public class CardLogicComponent_GenerateCards : CardLogicComponentBase
{
private Func<CardData, bool> cardFilter;
public override void Initialize(CardLogicBase card)
{
base.Initialize(card);
cardFilter = cardData => !cardData.HasTag("Unobtainable");
}
/// <summary>
/// 设置卡牌过滤器
/// </summary>
public void SetFilter(Func<CardData, bool> filter)
{
List<Func<CardData, bool>> originalFilters = new List<Func<CardData, bool>>();
originalFilters.Add(cardData => !cardData.HasTag("Unobtainable"));
if (filter != null) originalFilters.Add(filter);
this.cardFilter = cardData => originalFilters.All(f => f(cardData));
}
/// <summary>
/// 获取衍生卡牌数据
/// </summary>
public CardData GetDerivativeCardData(int index)
{
return card.cardData.GetDerivativeCardData(index);
}
/// <summary>
/// 从指定的cardDataID中根据needFilter决定是否进行过滤获取符合条件的卡牌数据列表
/// </summary>
/// <param name="needFilter">是否需要过滤</param>
/// <param name="cardDataIDs">指定的卡牌数据ID列表</param>
/// <returns>符合条件的卡牌数据列表</returns>
public List<CardData> GetDesignatedGlobalCardData(bool needFilter, params string[] cardDataIDs)
{
List<CardData> result = new List<CardData>();
foreach (string dataID in cardDataIDs)
{
if (ModManager.TryGetData(dataID, out CardData cardData))
{
if (needFilter && cardFilter(cardData))
{
result.Add(cardData);
}
else if (!needFilter)
{
result.Add(cardData);
}
}
}
return result;
}
/// <summary>
/// 在游戏全部的卡牌中,获取经过全局过滤器过滤后的所有卡牌数据列表
/// </summary>
public List<CardData> GetFilteredGlobalCardData()
{
return ModManager.Database[typeof(CardData)].Values.Cast<CardData>().Where(cardFilter).ToList();
}
/// <summary>
/// 在游戏全部的卡牌中,获取经过指定过滤器过滤后的所有卡牌数据列表
/// </summary>
public List<CardData> GetFilteredGlobalCardData(Func<CardData, bool> filter)
{
return ModManager.Database[typeof(CardData)].Values.Cast<CardData>().Where(filter).ToList();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c0c9acec889d2da44a18ad1d15a372ae

View File

@@ -0,0 +1,29 @@
using Continentis.MainGame.Character;
using UnityEngine;
namespace Continentis.MainGame.Card
{
public class CardLogicComponent_LifeSteal : CardLogicComponentBase
{
protected override void TargetingEffect(CharacterBase target)
{
card.SetAttribute("DisplayLifeStealPercent", GetLifeStealPercent());
}
protected override void UntargetingEffect()
{
card.SetAttribute("DisplayLifeStealPercent", card.GetRawAttribute("LifeStealPercent"));
}
public float GetLifeStealPercent()
{
return card.GetRawAttribute("LifeStealPercent") * (user.GetRawAttribute("LifeStealMultiplier", 1));
}
public void LifeSteal(float damageDealtOnHealth)
{
int heal = Mathf.RoundToInt(damageDealtOnHealth * GetLifeStealPercent());
user.Heal(heal);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 69c578f9c9d557749b4f9001da42b816

View File

@@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.Linq;
using Continentis.MainGame.Commands;
using Continentis.MainGame.UI;
using SLSFramework.General;
using UnityEngine;
namespace Continentis.MainGame.Card
{
public class CardLogicComponent_SelectCustomCards : CardLogicComponentBase
{
public List<CardInstance> selectedCards;
/// <summary>
/// 添加选择手牌的指令
/// </summary>
/// <param name="commandGroup">目标指令组</param>
/// <param name="cardsToSelect">可供选择的卡牌列表</param>
/// <param name="title">选择卡牌的描述性标题</param>
/// <param name="maxSelection">最大选择数量</param>
/// <param name="forceMax">是否强制选择最大数量</param>
public void AddSelectionCommands(ref CommandGroup commandGroup, List<CardInstance> cardsToSelect, string title, int maxSelection, bool forceMax = false)
{
selectedCards = new List<CardInstance>();
CustomCardSelectionInterface customCardSelector = CombatUIManager.Instance.combatMainPage.customCardSelector;
commandGroup.AddCommand(new Cmd_Function(() =>
{
customCardSelector.Setup(title, card.cardInstance, cardsToSelect, maxSelection, forceMax);
}));
commandGroup.AddCommand(new Cmd_WaitForUI(customCardSelector));
commandGroup.AddCommand(new Cmd_Function(() =>
{
selectedCards = customCardSelector.selectedCards.ToList();
selectedCards.ForEach(SelectEffect);
}));
}
/// <summary>
/// 卡牌被选择后的效果
/// </summary>
public void SelectEffect(CardInstance card)
{
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 454f2361104caef46a6aa0ebbc74a7dd

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Continentis.MainGame.Commands;
using Continentis.MainGame.UI;
using SLSFramework.General;
namespace Continentis.MainGame.Card
{
public class CardLogicComponent_SelectHandCards : CardLogicComponentBase
{
private Func<CardInstance, bool> selectCondition;
private Action<CardInstance> selectEffect;
public List<CardInstance> selectedCards;
public CardLogicComponent_SelectHandCards SetCondition(Func<CardInstance, bool> condition)
{
selectCondition = condition;
return this;
}
public CardLogicComponent_SelectHandCards SetEffect(Action<CardInstance> effect)
{
selectEffect = effect;
return this;
}
/// <summary>
/// 添加选择手牌的指令
/// </summary>
/// <param name="commandGroup">目标指令组</param>
/// <param name="title">选择卡牌的描述性标题</param>
/// <param name="maxSelection">最大选择数量</param>
/// <param name="forceMax">是否强制选择最大数量</param>
public void AddSelectionCommands(ref CommandGroup commandGroup, string title, int maxSelection, bool forceMax = false)
{
selectedCards = new List<CardInstance>();
HandCardSelectionInterface handCardSelector = CombatUIManager.Instance.combatMainPage.handCardSelector;
commandGroup.AddCommand(new Cmd_Function(() =>
{
handCardSelector.Setup(title, card.cardInstance, maxSelection, selectCondition, forceMax);
}));
commandGroup.AddCommand(new Cmd_WaitForUI(handCardSelector));
commandGroup.AddCommand(new Cmd_Function(() =>
{
selectedCards = handCardSelector.selectedCards.ToList();
selectedCards.ForEach(selectEffect);
}));
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 20613f1c30ff5434c968adba59d99bfc

View File

@@ -1,78 +0,0 @@
using Continentis.MainGame.Character;
using UnityEngine;
namespace Continentis.MainGame.Card
{
public abstract partial class CardLogicBase_Attack : CardLogicBase
{
protected override void TargetingEffect(CharacterBase target)
{
SetAttribute("DisplayDamage", GetFinalDamage(target));
}
protected override void UntargetingEffect()
{
SetAttribute("DisplayDamage", GetAttribute("Damage"));
}
}
public abstract partial class CardLogicBase_Attack
{
/// <summary>
/// 设置伤害值
/// </summary>
/// <param name="additive">是否为叠加true为叠加false为覆盖</param>
/// <param name="originalDamage">原始伤害值仅在additive为true时有效否则被覆盖为BaseDamage</param>
/// <param name="damageOffset">伤害增量</param>
protected void SetDamage(bool additive, int originalDamage, int damageOffset)
{
SetVariableAttribute("Damage", additive, originalDamage, damageOffset);
}
/// <summary>
/// 斩击伤害计算,伤害=基础伤害+(力量加成+敏捷加成)/2
/// </summary>
protected void SetDamage_Slash(bool additive = false, int originalDamage = 0)
{
float rawDamageOffsetFromStrength = user.GetRawAttribute("PhysicsDamageDealtOffsetFromStrength");
float rawDamageOffsetFromAgility = user.GetRawAttribute("PhysicsDamageDealtOffsetFromAgility");
SetDamage(additive, originalDamage, Mathf.RoundToInt((rawDamageOffsetFromStrength + rawDamageOffsetFromAgility) / 2f));
}
/// <summary>
/// 打击伤害计算,伤害=基础伤害+力量加成
/// </summary>
protected void SetDamage_Strike(bool additive = false, int originalDamage = 0)
{
int damageOffset = user.GetAttribute("PhysicsDamageDealtOffsetFromStrength");
SetDamage(additive, originalDamage, damageOffset);
}
/// <summary>
/// 突刺伤害计算,伤害=基础伤害+敏捷加成
/// </summary>
protected void SetDamage_Prick(bool additive = false, int originalDamage = 0)
{
int damageOffset = user.GetAttribute("PhysicsDamageDealtOffsetFromAgility");
SetDamage(additive, originalDamage, damageOffset);
}
/// <summary>
/// 奥术伤害计算,伤害=基础伤害+智力加成
/// </summary>
protected void SetDamage_Arcane(bool additive = false, int originalDamage = 0)
{
int damageOffset = user.GetAttribute("MagicDamageDealtOffsetFromIntelligence");
SetDamage(additive, originalDamage, damageOffset);
}
/// <summary>
/// 契术伤害计算,伤害=基础伤害+魅力加成
/// </summary>
protected void SetDamage_Sorcery(bool additive = false, int originalDamage = 0)
{
int damageOffset = user.GetAttribute("MagicDamageDealtOffsetFromCharisma");
SetDamage(additive, originalDamage, damageOffset);
}
}
}

View File

@@ -1,30 +0,0 @@
using System.Collections.Generic;
using Continentis.MainGame.Character;
using UnityEngine;
namespace Continentis.MainGame.Card
{
public abstract class CardLogicBase_MagicalAttack : CardLogicBase_Attack
{
protected override int GetFinalDamage(CharacterBase target, List<string> elementalTags = null)
{
elementalTags ??= this.elementalTags;
float elementalAmplifier = 1;
foreach (var element in elementalTags) //计算元素伤害加成
{
elementalAmplifier *= user.attributeSubmodule.GetCurrentGeneralAttribute(element + "DamageDealtMultiplier", 1);
elementalAmplifier *= target.attributeSubmodule.GetCurrentGeneralAttribute(element + "DamageGainMultiplier", 1);
}
float finalDamage =
attributeSubmodule.GetRoundCurrentAttribute("Damage") * elementalAmplifier *
user.attributeSubmodule.GetCurrentGeneralAttribute("MagicDamageDealtMultiplier", 1) *
target.attributeSubmodule.GetCurrentGeneralAttribute("MagicDamageGainMultiplier", 1) *
attributeSubmodule.GetCurrentAttribute("FinalDamageDealtMultiplier", 1) *
target.attributeSubmodule.GetCurrentGeneralAttribute("FinalDamageGainMultiplier", 1);
return Mathf.RoundToInt(finalDamage);
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 23176f7c6d22f4d448e10c22d0242ca3

View File

@@ -1,10 +0,0 @@
using Continentis.MainGame.Character;
using UnityEngine;
namespace Continentis.MainGame.Card
{
public abstract partial class CardLogicBase_PhysicalAttack : CardLogicBase_Attack
{
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: a27ca03d2c3e84f408af33246a9d6c85