引入属性、伤害管线
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
public static class DamageTypeExtensions
|
||||
{
|
||||
public static StatType ToScalingStat(this DamageType damageType)
|
||||
{
|
||||
switch (damageType)
|
||||
{
|
||||
case DamageType.Melee:
|
||||
return StatType.MeleeDamage;
|
||||
case DamageType.Ranged:
|
||||
return StatType.RangedDamage;
|
||||
case DamageType.Element:
|
||||
return StatType.ElementalDamage;
|
||||
default:
|
||||
return StatType.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e6128fa910e4a778c187123490b4119
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 属性查询接口。
|
||||
/// 供战斗碰撞(ImpactData)与伤害管线解耦读取属性值。
|
||||
/// </summary>
|
||||
public interface IAttributeProvider
|
||||
{
|
||||
float GetValue(StatType statType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b483f12479014631bb5914285260aa31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,37 +1,45 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public struct ImpactData
|
||||
{
|
||||
public ImpactData(CampType camp /*, int health*/, int attackBase, StatProperty attackStat = null,
|
||||
StatProperty defenseStat = null, StatProperty dodgeStat = null)
|
||||
{
|
||||
Camp = camp;
|
||||
//Health = health;
|
||||
AttackBase = attackBase;
|
||||
AttackStat = attackStat;
|
||||
DefenseStat = defenseStat;
|
||||
DodgeStat = dodgeStat;
|
||||
}
|
||||
|
||||
public CampType Camp { get; }
|
||||
|
||||
public int AttackBase { get; }
|
||||
|
||||
public StatProperty AttackStat { get; }
|
||||
public DamageType DamageType { get; }
|
||||
|
||||
public StatProperty DefenseStat { get; }
|
||||
public float ScalingRatio { get; }
|
||||
|
||||
public StatProperty DodgeStat { get; }
|
||||
public float BaseCritChance { get; }
|
||||
|
||||
public float CritMultiplier { get; }
|
||||
|
||||
public IAttributeProvider AttributeProvider { get; }
|
||||
|
||||
public ImpactData(
|
||||
CampType camp,
|
||||
int attackBase,
|
||||
IAttributeProvider attributeProvider = null,
|
||||
DamageType damageType = DamageType.None,
|
||||
float scalingRatio = 1.0f,
|
||||
float baseCritChance = 0f,
|
||||
float critMultiplier = 0f)
|
||||
{
|
||||
Camp = camp;
|
||||
AttackBase = attackBase;
|
||||
AttributeProvider = attributeProvider;
|
||||
DamageType = damageType;
|
||||
ScalingRatio = scalingRatio;
|
||||
BaseCritChance = baseCritChance;
|
||||
CritMultiplier = critMultiplier;
|
||||
}
|
||||
|
||||
public ImpactData(CampType camp, int attackBase)
|
||||
: this(camp, attackBase, null, DamageType.None, 1.0f, 0f, 0f)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,24 +4,24 @@ namespace SepCore.Definition
|
||||
{
|
||||
public class PropItem
|
||||
{
|
||||
private readonly List<modifier> _modifiers = new();
|
||||
private readonly List<StatModifierConfig> _modifiers = new();
|
||||
public string Title { get; private set; }
|
||||
public string IconAssetName { get; private set; }
|
||||
public Rarity Rarity { get; private set; }
|
||||
public IReadOnlyList<modifier> Modifiers => _modifiers;
|
||||
public IReadOnlyList<StatModifierConfig> Modifiers => _modifiers;
|
||||
|
||||
public PropItem(PropConfig prop)
|
||||
{
|
||||
if (prop.Modifiers != null)
|
||||
if (prop.StatModifierConfigs != null)
|
||||
{
|
||||
_modifiers.AddRange(prop.Modifiers);
|
||||
_modifiers.AddRange(prop.StatModifierConfigs);
|
||||
}
|
||||
Title = prop.Name;
|
||||
Rarity = prop.Rarity;
|
||||
IconAssetName = prop.Sprite;
|
||||
}
|
||||
|
||||
public PropItem(IEnumerable<modifier> modifiers)
|
||||
public PropItem(IEnumerable<StatModifierConfig> modifiers)
|
||||
{
|
||||
if (modifiers != null)
|
||||
{
|
||||
@@ -29,7 +29,7 @@ namespace SepCore.Definition
|
||||
}
|
||||
}
|
||||
|
||||
public PropItem(IEnumerable<modifier> modifiers, Rarity rarity, string title, string iconAssetName)
|
||||
public PropItem(IEnumerable<StatModifierConfig> modifiers, Rarity rarity, string title, string iconAssetName)
|
||||
{
|
||||
if (modifiers != null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
public sealed partial class StatConfig
|
||||
{
|
||||
public StatConfig()
|
||||
{
|
||||
}
|
||||
|
||||
public StatConfig(
|
||||
StatType statType,
|
||||
string name = "",
|
||||
float defaultBaseValue = 0f,
|
||||
bool hasMin = false,
|
||||
float minValue = 0f,
|
||||
bool hasMax = false,
|
||||
float maxValue = 0f,
|
||||
bool isPercentDisplay = false)
|
||||
{
|
||||
StatType = statType;
|
||||
Name = name;
|
||||
DefaultBaseValue = defaultBaseValue;
|
||||
HasMin = hasMin;
|
||||
MinValue = minValue;
|
||||
HasMax = hasMax;
|
||||
MaxValue = maxValue;
|
||||
IsPercentDisplay = isPercentDisplay;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b910e53a2454b5f903517865c36ad18
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 属性修饰器。
|
||||
/// 包含修饰数值、修饰类型(固定值/百分比加算/独立乘区)以及来源对象(道具/武器/羁绊/特质/Buff)。
|
||||
/// </summary>
|
||||
public sealed class StatModifier
|
||||
{
|
||||
public float Value { get; }
|
||||
public ModifierType Type { get; }
|
||||
public object Source { get; }
|
||||
|
||||
public StatModifier(float value, ModifierType type, object source = null)
|
||||
{
|
||||
Value = value;
|
||||
Type = type;
|
||||
Source = source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 Luban 导出的 modifier 数据结构转换。
|
||||
/// </summary>
|
||||
/// <param name="mod">Luban modifier 数据实例</param>
|
||||
/// <param name="source">来源对象引用</param>
|
||||
public static StatModifier FromLuban(StatModifierConfig mod, object source = null)
|
||||
{
|
||||
if (mod == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mod));
|
||||
}
|
||||
|
||||
ModifierType type = mod.ModifierType;
|
||||
return new StatModifier(mod.Value, type, source);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[StatModifier Type={Type}, Value={Value}, Source={Source}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 887b4b22fce86cb43b95cfdf3c6b967c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
public sealed partial class StatModifierConfig
|
||||
{
|
||||
public StatModifierConfig()
|
||||
{
|
||||
}
|
||||
|
||||
public StatModifierConfig(StatType statType, float value, ModifierType modifierType)
|
||||
{
|
||||
StatType = statType;
|
||||
Value = value;
|
||||
ModifierType = modifierType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
public class StatProperty
|
||||
{
|
||||
public float Value;
|
||||
public float Percent;
|
||||
|
||||
public StatProperty()
|
||||
{
|
||||
Value = 0f;
|
||||
Percent = 1f;
|
||||
}
|
||||
|
||||
public StatProperty(float value, float percent)
|
||||
{
|
||||
Value = value;
|
||||
Percent = percent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18e7bc9587114ae4a91c70f829543de8
|
||||
timeCreated: 1770353735
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
public sealed partial class modifier
|
||||
{
|
||||
public StatType StatType => Key;
|
||||
|
||||
/// <summary>
|
||||
/// 实际参与数值计算的浮点数值。
|
||||
/// 1. 百分比加成(IsPercent == true):表内千分比(如 200 代表 20%),转换为 0.2f。
|
||||
/// 2. 非百分比加成(IsPercent == false):移动速度原为 float 放大了 1000 倍(如 800 代表 0.8f),除以 1000f;其余(如生命+53、攻击+10)直接使用原值。
|
||||
/// </summary>
|
||||
public float RealValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsPercent)
|
||||
{
|
||||
return Value / 1000f;
|
||||
}
|
||||
|
||||
if (Key == StatType.MovementSpeed)
|
||||
{
|
||||
return Value / 1000f;
|
||||
}
|
||||
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
public modifier()
|
||||
{
|
||||
}
|
||||
|
||||
public modifier(StatType key, int value, bool isPercent)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
IsPercent = isPercent;
|
||||
}
|
||||
|
||||
public modifier(StatType key, float value, bool isPercent)
|
||||
{
|
||||
Key = key;
|
||||
Value = (isPercent || key == StatType.MovementSpeed)
|
||||
? Mathf.RoundToInt(value * 1000f)
|
||||
: Mathf.RoundToInt(value);
|
||||
IsPercent = isPercent;
|
||||
}
|
||||
|
||||
public string ToDisplayString()
|
||||
{
|
||||
if (IsPercent)
|
||||
{
|
||||
float percent = Value / 10f;
|
||||
return $"{Key} +{percent:0.##}%";
|
||||
}
|
||||
|
||||
if (Key == StatType.MovementSpeed)
|
||||
{
|
||||
float speed = Value / 1000f;
|
||||
return $"{Key} +{speed:0.##}";
|
||||
}
|
||||
|
||||
return $"{Key} +{Value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,8 @@ namespace SepCore.Entity
|
||||
|
||||
public int Attack => Config.Attack;
|
||||
|
||||
public DamageType DamageType => Config.DamageType;
|
||||
|
||||
public string Title => Config.WeaponType.ToString();
|
||||
|
||||
public string Sprite => Config.Sprite;
|
||||
@@ -38,7 +40,7 @@ namespace SepCore.Entity
|
||||
|
||||
public IReadOnlyDictionary<ParamType, int> Parameters => Config.Parameters;
|
||||
|
||||
public IReadOnlyList<modifier> Modifiers => Config.Modifiers;
|
||||
public IReadOnlyList<StatModifierConfig> Modifiers => Config.StatModifierConfigs;
|
||||
|
||||
public int GetParam(ParamType paramType)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 伤害类型
|
||||
/// </summary>
|
||||
[System.Flags]
|
||||
public enum DamageType : int
|
||||
{
|
||||
/// <summary>
|
||||
/// 无
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// 近战伤害
|
||||
/// </summary>
|
||||
Melee = 1,
|
||||
/// <summary>
|
||||
/// 远程伤害
|
||||
/// </summary>
|
||||
Ranged = 2,
|
||||
/// <summary>
|
||||
/// 元素伤害
|
||||
/// </summary>
|
||||
Element = 4,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 178341689d25049458e8460d398db2dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -33,6 +33,7 @@ public sealed partial class GlobalConfig : Luban.BeanBase
|
||||
EnemySpawnDistanceMin = _buf.ReadInt();
|
||||
EnemySpawnDistanceMax = _buf.ReadInt();
|
||||
WeaponRotateSpeed = _buf.ReadInt();
|
||||
WeaponBaseCrit = _buf.ReadFloat();
|
||||
PlayerGroupName = _buf.ReadString();
|
||||
EnemyGroupName = _buf.ReadString();
|
||||
ProjectileGroupName = _buf.ReadString();
|
||||
@@ -48,6 +49,7 @@ public sealed partial class GlobalConfig : Luban.BeanBase
|
||||
ProjectileEntity_Ref = null;
|
||||
DamageTextYOffset = _buf.ReadInt();
|
||||
EntityAsyncTimeout = _buf.ReadInt();
|
||||
CritMultiplier = _buf.ReadFloat();
|
||||
EnemyBodyRadius = _buf.ReadInt();
|
||||
EnemySeparationIterations = _buf.ReadInt();
|
||||
}
|
||||
@@ -123,6 +125,10 @@ public sealed partial class GlobalConfig : Luban.BeanBase
|
||||
/// </summary>
|
||||
public readonly int WeaponRotateSpeed;
|
||||
/// <summary>
|
||||
/// 武器基本暴击率
|
||||
/// </summary>
|
||||
public readonly float WeaponBaseCrit;
|
||||
/// <summary>
|
||||
/// 玩家对象组名
|
||||
/// </summary>
|
||||
public readonly string PlayerGroupName;
|
||||
@@ -180,6 +186,10 @@ public sealed partial class GlobalConfig : Luban.BeanBase
|
||||
/// </summary>
|
||||
public readonly int EntityAsyncTimeout;
|
||||
/// <summary>
|
||||
/// 暴击伤害倍率
|
||||
/// </summary>
|
||||
public readonly float CritMultiplier;
|
||||
/// <summary>
|
||||
/// 敌人碰撞半径
|
||||
/// </summary>
|
||||
public readonly int EnemyBodyRadius;
|
||||
@@ -218,6 +228,7 @@ public sealed partial class GlobalConfig : Luban.BeanBase
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ProjectileEntity_Ref = tables.TbEntityConfig.GetOrDefault(ProjectileEntity);
|
||||
@@ -225,6 +236,7 @@ public sealed partial class GlobalConfig : Luban.BeanBase
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
@@ -246,6 +258,7 @@ public sealed partial class GlobalConfig : Luban.BeanBase
|
||||
+ "enemySpawnDistanceMin:" + EnemySpawnDistanceMin + ","
|
||||
+ "enemySpawnDistanceMax:" + EnemySpawnDistanceMax + ","
|
||||
+ "weaponRotateSpeed:" + WeaponRotateSpeed + ","
|
||||
+ "weaponBaseCrit:" + WeaponBaseCrit + ","
|
||||
+ "playerGroupName:" + PlayerGroupName + ","
|
||||
+ "enemyGroupName:" + EnemyGroupName + ","
|
||||
+ "projectileGroupName:" + ProjectileGroupName + ","
|
||||
@@ -260,6 +273,7 @@ public sealed partial class GlobalConfig : Luban.BeanBase
|
||||
+ "projectileEntity:" + ProjectileEntity + ","
|
||||
+ "damageTextYOffset:" + DamageTextYOffset + ","
|
||||
+ "entityAsyncTimeout:" + EntityAsyncTimeout + ","
|
||||
+ "critMultiplier:" + CritMultiplier + ","
|
||||
+ "enemyBodyRadius:" + EnemyBodyRadius + ","
|
||||
+ "enemySeparationIterations:" + EnemySeparationIterations + ","
|
||||
+ "}";
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed partial class LevelConfig : Luban.BeanBase
|
||||
public LevelConfig(ByteBuf _buf)
|
||||
{
|
||||
Id = _buf.ReadInt();
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);EnemySpawnConfigs = new System.Collections.Generic.List<enemySpawnConfig>(n0);for(var i0 = 0 ; i0 < n0 ; i0++) { enemySpawnConfig _e0; _e0 = enemySpawnConfig.DeserializeenemySpawnConfig(_buf); EnemySpawnConfigs.Add(_e0);}}
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);EnemySpawnConfigs = new System.Collections.Generic.List<EnemySpawnConfig>(n0);for(var i0 = 0 ; i0 < n0 ; i0++) { EnemySpawnConfig _e0; _e0 = EnemySpawnConfig.DeserializeEnemySpawnConfig(_buf); EnemySpawnConfigs.Add(_e0);}}
|
||||
Duration = _buf.ReadInt();
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed partial class LevelConfig : Luban.BeanBase
|
||||
/// 关卡id
|
||||
/// </summary>
|
||||
public readonly int Id;
|
||||
public readonly System.Collections.Generic.List<enemySpawnConfig> EnemySpawnConfigs;
|
||||
public readonly System.Collections.Generic.List<EnemySpawnConfig> EnemySpawnConfigs;
|
||||
/// <summary>
|
||||
/// 关卡时间
|
||||
/// </summary>
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed partial class LevelUpRewardConfig : Luban.BeanBase
|
||||
Id = _buf.ReadInt();
|
||||
Title = _buf.ReadString();
|
||||
Rarity = (Rarity)_buf.ReadInt();
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);Modifiers = new System.Collections.Generic.List<modifier>(n0);for(var i0 = 0 ; i0 < n0 ; i0++) { modifier _e0; _e0 = modifier.Deserializemodifier(_buf); Modifiers.Add(_e0);}}
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);StatModifierConfigs = new System.Collections.Generic.List<StatModifierConfig>(n0);for(var i0 = 0 ; i0 < n0 ; i0++) { StatModifierConfig _e0; _e0 = StatModifierConfig.DeserializeStatModifierConfig(_buf); StatModifierConfigs.Add(_e0);}}
|
||||
Sprite = _buf.ReadString();
|
||||
Sprite_Ref = null;
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public sealed partial class LevelUpRewardConfig : Luban.BeanBase
|
||||
/// 道具品质
|
||||
/// </summary>
|
||||
public readonly Rarity Rarity;
|
||||
public readonly System.Collections.Generic.List<modifier> Modifiers;
|
||||
public readonly System.Collections.Generic.List<StatModifierConfig> StatModifierConfigs;
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
@@ -56,7 +56,7 @@ public sealed partial class LevelUpRewardConfig : Luban.BeanBase
|
||||
|
||||
|
||||
|
||||
foreach (var _e in Modifiers) { _e?.ResolveRef(tables); }
|
||||
foreach (var _e in StatModifierConfigs) { _e?.ResolveRef(tables); }
|
||||
Sprite_Ref = tables.TbSpriteConfig.GetOrDefault(Sprite);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public sealed partial class LevelUpRewardConfig : Luban.BeanBase
|
||||
+ "id:" + Id + ","
|
||||
+ "title:" + Title + ","
|
||||
+ "rarity:" + Rarity + ","
|
||||
+ "modifiers:" + Luban.StringUtil.CollectionToString(Modifiers) + ","
|
||||
+ "statModifierConfigs:" + Luban.StringUtil.CollectionToString(StatModifierConfigs) + ","
|
||||
+ "sprite:" + Sprite + ","
|
||||
+ "}";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 修饰器类型
|
||||
/// </summary>
|
||||
public enum ModifierType : int
|
||||
{
|
||||
/// <summary>
|
||||
/// 无
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// 固定值
|
||||
/// </summary>
|
||||
Flat = 100,
|
||||
/// <summary>
|
||||
/// 百分比累加
|
||||
/// </summary>
|
||||
PercentAdd = 200,
|
||||
/// <summary>
|
||||
/// 最终百分比独立乘区
|
||||
/// </summary>
|
||||
PercentMult = 300,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a95015722b22de41a817f1d17e7d560
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -20,7 +20,7 @@ public sealed partial class PropConfig : Luban.BeanBase
|
||||
Name = _buf.ReadString();
|
||||
Rarity = (Rarity)_buf.ReadInt();
|
||||
Price = _buf.ReadInt();
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);Modifiers = new System.Collections.Generic.List<modifier>(n0);for(var i0 = 0 ; i0 < n0 ; i0++) { modifier _e0; _e0 = modifier.Deserializemodifier(_buf); Modifiers.Add(_e0);}}
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);StatModifierConfigs = new System.Collections.Generic.List<StatModifierConfig>(n0);for(var i0 = 0 ; i0 < n0 ; i0++) { StatModifierConfig _e0; _e0 = StatModifierConfig.DeserializeStatModifierConfig(_buf); StatModifierConfigs.Add(_e0);}}
|
||||
Sprite = _buf.ReadString();
|
||||
Sprite_Ref = null;
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public sealed partial class PropConfig : Luban.BeanBase
|
||||
/// 道具价格
|
||||
/// </summary>
|
||||
public readonly int Price;
|
||||
public readonly System.Collections.Generic.List<modifier> Modifiers;
|
||||
public readonly System.Collections.Generic.List<StatModifierConfig> StatModifierConfigs;
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
@@ -62,7 +62,7 @@ public sealed partial class PropConfig : Luban.BeanBase
|
||||
|
||||
|
||||
|
||||
foreach (var _e in Modifiers) { _e?.ResolveRef(tables); }
|
||||
foreach (var _e in StatModifierConfigs) { _e?.ResolveRef(tables); }
|
||||
Sprite_Ref = tables.TbSpriteConfig.GetOrDefault(Sprite);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public sealed partial class PropConfig : Luban.BeanBase
|
||||
+ "name:" + Name + ","
|
||||
+ "rarity:" + Rarity + ","
|
||||
+ "price:" + Price + ","
|
||||
+ "modifiers:" + Luban.StringUtil.CollectionToString(Modifiers) + ","
|
||||
+ "statModifierConfigs:" + Luban.StringUtil.CollectionToString(StatModifierConfigs) + ","
|
||||
+ "sprite:" + Sprite + ","
|
||||
+ "}";
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public sealed partial class RoleConfig : Luban.BeanBase
|
||||
MaxHealth = _buf.ReadInt();
|
||||
Speed = _buf.ReadFloat();
|
||||
Coin = _buf.ReadInt();
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);Modifiers = new System.Collections.Generic.List<modifier>(n0);for(var i0 = 0 ; i0 < n0 ; i0++) { modifier _e0; _e0 = modifier.Deserializemodifier(_buf); Modifiers.Add(_e0);}}
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);StatModifierConfigs = new System.Collections.Generic.List<StatModifierConfig>(n0);for(var i0 = 0 ; i0 < n0 ; i0++) { StatModifierConfig _e0; _e0 = StatModifierConfig.DeserializeStatModifierConfig(_buf); StatModifierConfigs.Add(_e0);}}
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);InitialItems = new System.Collections.Generic.List<int>(n0);for(var i0 = 0 ; i0 < n0 ; i0++) { int _e0; _e0 = _buf.ReadInt(); InitialItems.Add(_e0);}}
|
||||
Prefab = _buf.ReadString();
|
||||
Prefab_Ref = null;
|
||||
@@ -38,9 +38,9 @@ public sealed partial class RoleConfig : Luban.BeanBase
|
||||
public readonly float Speed;
|
||||
public readonly int Coin;
|
||||
/// <summary>
|
||||
/// key
|
||||
/// statType
|
||||
/// </summary>
|
||||
public readonly System.Collections.Generic.List<modifier> Modifiers;
|
||||
public readonly System.Collections.Generic.List<StatModifierConfig> StatModifierConfigs;
|
||||
public readonly System.Collections.Generic.List<int> InitialItems;
|
||||
public readonly string Prefab;
|
||||
public EntityConfig Prefab_Ref;
|
||||
@@ -56,7 +56,7 @@ public sealed partial class RoleConfig : Luban.BeanBase
|
||||
|
||||
|
||||
|
||||
foreach (var _e in Modifiers) { _e?.ResolveRef(tables); }
|
||||
foreach (var _e in StatModifierConfigs) { _e?.ResolveRef(tables); }
|
||||
|
||||
Prefab_Ref = tables.TbEntityConfig.GetOrDefault(Prefab);
|
||||
Sprite_Ref = tables.TbSpriteConfig.GetOrDefault(Sprite);
|
||||
@@ -69,7 +69,7 @@ public sealed partial class RoleConfig : Luban.BeanBase
|
||||
+ "maxHealth:" + MaxHealth + ","
|
||||
+ "speed:" + Speed + ","
|
||||
+ "coin:" + Coin + ","
|
||||
+ "modifiers:" + Luban.StringUtil.CollectionToString(Modifiers) + ","
|
||||
+ "statModifierConfigs:" + Luban.StringUtil.CollectionToString(StatModifierConfigs) + ","
|
||||
+ "initialItems:" + Luban.StringUtil.CollectionToString(InitialItems) + ","
|
||||
+ "prefab:" + Prefab + ","
|
||||
+ "sprite:" + Sprite + ","
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Luban;
|
||||
|
||||
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
public sealed partial class StatConfig : Luban.BeanBase
|
||||
{
|
||||
public StatConfig(ByteBuf _buf)
|
||||
{
|
||||
StatType = (StatType)_buf.ReadInt();
|
||||
Name = _buf.ReadString();
|
||||
DefaultBaseValue = _buf.ReadFloat();
|
||||
HasMin = _buf.ReadBool();
|
||||
MinValue = _buf.ReadFloat();
|
||||
HasMax = _buf.ReadBool();
|
||||
MaxValue = _buf.ReadFloat();
|
||||
IsPercentDisplay = _buf.ReadBool();
|
||||
}
|
||||
|
||||
public static StatConfig DeserializeStatConfig(ByteBuf _buf)
|
||||
{
|
||||
return new StatConfig(_buf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 属性类型
|
||||
/// </summary>
|
||||
public readonly StatType StatType;
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
/// <summary>
|
||||
/// 默认值
|
||||
/// </summary>
|
||||
public readonly float DefaultBaseValue;
|
||||
/// <summary>
|
||||
/// 是否有最小值
|
||||
/// </summary>
|
||||
public readonly bool HasMin;
|
||||
/// <summary>
|
||||
/// 最小值
|
||||
/// </summary>
|
||||
public readonly float MinValue;
|
||||
/// <summary>
|
||||
/// 是否有最大值
|
||||
/// </summary>
|
||||
public readonly bool HasMax;
|
||||
/// <summary>
|
||||
/// 最大值
|
||||
/// </summary>
|
||||
public readonly float MaxValue;
|
||||
/// <summary>
|
||||
/// UI 文本是否百分比
|
||||
/// </summary>
|
||||
public readonly bool IsPercentDisplay;
|
||||
|
||||
public const int __ID__ = 807092950;
|
||||
public override int GetTypeId() => __ID__;
|
||||
|
||||
public void ResolveRef(Tables tables)
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{ "
|
||||
+ "statType:" + StatType + ","
|
||||
+ "name:" + Name + ","
|
||||
+ "defaultBaseValue:" + DefaultBaseValue + ","
|
||||
+ "hasMin:" + HasMin + ","
|
||||
+ "minValue:" + MinValue + ","
|
||||
+ "hasMax:" + HasMax + ","
|
||||
+ "maxValue:" + MaxValue + ","
|
||||
+ "isPercentDisplay:" + IsPercentDisplay + ","
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2dfe101008e1ded4ea35681a5a656d2f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+14
-14
@@ -15,34 +15,34 @@ namespace SepCore.Definition
|
||||
/// <summary>
|
||||
/// 修饰器
|
||||
/// </summary>
|
||||
public sealed partial class modifier : Luban.BeanBase
|
||||
public sealed partial class StatModifierConfig : Luban.BeanBase
|
||||
{
|
||||
public modifier(ByteBuf _buf)
|
||||
public StatModifierConfig(ByteBuf _buf)
|
||||
{
|
||||
Key = (StatType)_buf.ReadInt();
|
||||
Value = _buf.ReadInt();
|
||||
IsPercent = _buf.ReadBool();
|
||||
StatType = (StatType)_buf.ReadInt();
|
||||
Value = _buf.ReadFloat();
|
||||
ModifierType = (ModifierType)_buf.ReadInt();
|
||||
}
|
||||
|
||||
public static modifier Deserializemodifier(ByteBuf _buf)
|
||||
public static StatModifierConfig DeserializeStatModifierConfig(ByteBuf _buf)
|
||||
{
|
||||
return new modifier(_buf);
|
||||
return new StatModifierConfig(_buf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 属性类型
|
||||
/// </summary>
|
||||
public readonly StatType Key;
|
||||
public readonly StatType StatType;
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public readonly int Value;
|
||||
public readonly float Value;
|
||||
/// <summary>
|
||||
/// 是否为百分比
|
||||
/// 修饰器类型
|
||||
/// </summary>
|
||||
public readonly bool IsPercent;
|
||||
public readonly ModifierType ModifierType;
|
||||
|
||||
public const int __ID__ = -615513385;
|
||||
public const int __ID__ = 235118733;
|
||||
public override int GetTypeId() => __ID__;
|
||||
|
||||
public void ResolveRef(Tables tables)
|
||||
@@ -55,9 +55,9 @@ public sealed partial class modifier : Luban.BeanBase
|
||||
public override string ToString()
|
||||
{
|
||||
return "{ "
|
||||
+ "key:" + Key + ","
|
||||
+ "statType:" + StatType + ","
|
||||
+ "value:" + Value + ","
|
||||
+ "isPercent:" + IsPercent + ","
|
||||
+ "modifierType:" + ModifierType + ","
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -23,39 +23,67 @@ namespace SepCore.Definition
|
||||
/// <summary>
|
||||
/// 最大生命
|
||||
/// </summary>
|
||||
MaxHealth = 1,
|
||||
MaxHp = 1,
|
||||
/// <summary>
|
||||
/// 移动速度
|
||||
/// 生命恢复
|
||||
/// </summary>
|
||||
MovementSpeed = 2,
|
||||
HpRegen = 2,
|
||||
/// <summary>
|
||||
/// 攻击力
|
||||
/// 生命窃取
|
||||
/// </summary>
|
||||
Attack = 3,
|
||||
LifeSteal = 3,
|
||||
/// <summary>
|
||||
/// 防御力
|
||||
/// 伤害
|
||||
/// </summary>
|
||||
Defense = 4,
|
||||
DamagePercent = 4,
|
||||
/// <summary>
|
||||
/// 近战伤害
|
||||
/// </summary>
|
||||
MeleeDamage = 5,
|
||||
/// <summary>
|
||||
/// 远程伤害
|
||||
/// </summary>
|
||||
RangedDamage = 6,
|
||||
/// <summary>
|
||||
/// 元素伤害
|
||||
/// </summary>
|
||||
ElementalDamage = 7,
|
||||
/// <summary>
|
||||
/// 攻击速度
|
||||
/// </summary>
|
||||
AttackSpeed = 5,
|
||||
AttackSpeed = 8,
|
||||
/// <summary>
|
||||
/// 暴击率
|
||||
/// </summary>
|
||||
Critical = 6,
|
||||
CritChance = 9,
|
||||
/// <summary>
|
||||
/// 暴击伤害
|
||||
/// 工程学
|
||||
/// </summary>
|
||||
CriticalDamage = 7,
|
||||
Engineering = 10,
|
||||
/// <summary>
|
||||
/// 攻击范围
|
||||
/// </summary>
|
||||
Range = 11,
|
||||
/// <summary>
|
||||
/// 护甲
|
||||
/// </summary>
|
||||
Armor = 12,
|
||||
/// <summary>
|
||||
/// 闪避率
|
||||
/// </summary>
|
||||
Dodge = 8,
|
||||
Dodge = 13,
|
||||
/// <summary>
|
||||
/// 自动吸收范围
|
||||
/// 移动速度
|
||||
/// </summary>
|
||||
AbsorbRange = 9,
|
||||
MoveSpeed = 14,
|
||||
/// <summary>
|
||||
/// 收获
|
||||
/// </summary>
|
||||
Harvesting = 15,
|
||||
/// <summary>
|
||||
/// 幸运
|
||||
/// </summary>
|
||||
Luck = 16,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@ public partial class Tables
|
||||
/// 界面配置
|
||||
/// </summary>
|
||||
public TbWeaponConfig TbWeaponConfig {get; }
|
||||
/// <summary>
|
||||
/// 角色属性配置
|
||||
/// </summary>
|
||||
public TbStatConfig TbStatConfig {get; }
|
||||
|
||||
public Tables(System.Func<string, ByteBuf> loader)
|
||||
{
|
||||
@@ -82,6 +86,7 @@ public partial class Tables
|
||||
TbUIFormConfig = new TbUIFormConfig(loader("tbuiformconfig"));
|
||||
TbUISoundConfig = new TbUISoundConfig(loader("tbuisoundconfig"));
|
||||
TbWeaponConfig = new TbWeaponConfig(loader("tbweaponconfig"));
|
||||
TbStatConfig = new TbStatConfig(loader("tbstatconfig"));
|
||||
ResolveRef();
|
||||
}
|
||||
|
||||
@@ -105,6 +110,7 @@ public partial class Tables
|
||||
TbUIFormConfig.ResolveRef(this);
|
||||
TbUISoundConfig.ResolveRef(this);
|
||||
TbWeaponConfig.ResolveRef(this);
|
||||
TbStatConfig.ResolveRef(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,10 @@ public partial class TbGlobalConfig
|
||||
/// </summary>
|
||||
public int WeaponRotateSpeed => _data.WeaponRotateSpeed;
|
||||
/// <summary>
|
||||
/// 武器基本暴击率
|
||||
/// </summary>
|
||||
public float WeaponBaseCrit => _data.WeaponBaseCrit;
|
||||
/// <summary>
|
||||
/// 玩家对象组名
|
||||
/// </summary>
|
||||
public string PlayerGroupName => _data.PlayerGroupName;
|
||||
@@ -151,6 +155,10 @@ public partial class TbGlobalConfig
|
||||
/// </summary>
|
||||
public int EntityAsyncTimeout => _data.EntityAsyncTimeout;
|
||||
/// <summary>
|
||||
/// 暴击伤害倍率
|
||||
/// </summary>
|
||||
public float CritMultiplier => _data.CritMultiplier;
|
||||
/// <summary>
|
||||
/// 敌人碰撞半径
|
||||
/// </summary>
|
||||
public int EnemyBodyRadius => _data.EnemyBodyRadius;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Luban;
|
||||
|
||||
|
||||
namespace SepCore.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色属性配置
|
||||
/// </summary>
|
||||
public partial class TbStatConfig
|
||||
{
|
||||
private readonly System.Collections.Generic.Dictionary<StatType, StatConfig> _dataMap;
|
||||
private readonly System.Collections.Generic.List<StatConfig> _dataList;
|
||||
|
||||
public TbStatConfig(ByteBuf _buf)
|
||||
{
|
||||
_dataMap = new System.Collections.Generic.Dictionary<StatType, StatConfig>();
|
||||
_dataList = new System.Collections.Generic.List<StatConfig>();
|
||||
|
||||
for(int n = _buf.ReadSize() ; n > 0 ; --n)
|
||||
{
|
||||
StatConfig _v;
|
||||
_v = StatConfig.DeserializeStatConfig(_buf);
|
||||
_dataList.Add(_v);
|
||||
_dataMap.Add(_v.StatType, _v);
|
||||
}
|
||||
}
|
||||
|
||||
public System.Collections.Generic.Dictionary<StatType, StatConfig> DataMap => _dataMap;
|
||||
public System.Collections.Generic.List<StatConfig> DataList => _dataList;
|
||||
|
||||
public StatConfig GetOrDefault(StatType key) => _dataMap.TryGetValue(key, out var v) ? v : null;
|
||||
public StatConfig Get(StatType key) => _dataMap[key];
|
||||
public StatConfig this[StatType key] => _dataMap[key];
|
||||
|
||||
public void ResolveRef(Tables tables)
|
||||
{
|
||||
foreach(var _v in _dataList)
|
||||
{
|
||||
_v.ResolveRef(tables);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54c58e47ce828d246ae57a00a9704563
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -22,10 +22,11 @@ public sealed partial class WeaponConfig : Luban.BeanBase
|
||||
Price = _buf.ReadInt();
|
||||
Attack = _buf.ReadInt();
|
||||
Cooldown = _buf.ReadInt();
|
||||
DamageType = (DamageType)_buf.ReadInt();
|
||||
AttackRange = _buf.ReadInt();
|
||||
AttackSoundId = _buf.ReadInt();
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);Parameters = new System.Collections.Generic.Dictionary<ParamType, int>(n0 * 3 / 2);for(var i0 = 0 ; i0 < n0 ; i0++) { ParamType _k0; _k0 = (ParamType)_buf.ReadInt(); int _v0; _v0 = _buf.ReadInt(); Parameters.Add(_k0, _v0);}}
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);Modifiers = new System.Collections.Generic.List<modifier>(n0);for(var i0 = 0 ; i0 < n0 ; i0++) { modifier _e0; _e0 = modifier.Deserializemodifier(_buf); Modifiers.Add(_e0);}}
|
||||
{int n0 = System.Math.Min(_buf.ReadSize(), _buf.Size);StatModifierConfigs = new System.Collections.Generic.List<StatModifierConfig>(n0);for(var i0 = 0 ; i0 < n0 ; i0++) { StatModifierConfig _e0; _e0 = StatModifierConfig.DeserializeStatModifierConfig(_buf); StatModifierConfigs.Add(_e0);}}
|
||||
Prefab = _buf.ReadString();
|
||||
Prefab_Ref = null;
|
||||
Sprite = _buf.ReadString();
|
||||
@@ -62,6 +63,10 @@ public sealed partial class WeaponConfig : Luban.BeanBase
|
||||
/// </summary>
|
||||
public readonly int Cooldown;
|
||||
/// <summary>
|
||||
/// 伤害类型
|
||||
/// </summary>
|
||||
public readonly DamageType DamageType;
|
||||
/// <summary>
|
||||
/// 攻击范围
|
||||
/// </summary>
|
||||
public readonly int AttackRange;
|
||||
@@ -73,7 +78,7 @@ public sealed partial class WeaponConfig : Luban.BeanBase
|
||||
/// 参数类型
|
||||
/// </summary>
|
||||
public readonly System.Collections.Generic.Dictionary<ParamType, int> Parameters;
|
||||
public readonly System.Collections.Generic.List<modifier> Modifiers;
|
||||
public readonly System.Collections.Generic.List<StatModifierConfig> StatModifierConfigs;
|
||||
/// <summary>
|
||||
/// 预制体
|
||||
/// </summary>
|
||||
@@ -99,7 +104,8 @@ public sealed partial class WeaponConfig : Luban.BeanBase
|
||||
|
||||
|
||||
|
||||
foreach (var _e in Modifiers) { _e?.ResolveRef(tables); }
|
||||
|
||||
foreach (var _e in StatModifierConfigs) { _e?.ResolveRef(tables); }
|
||||
Prefab_Ref = tables.TbEntityConfig.GetOrDefault(Prefab);
|
||||
Sprite_Ref = tables.TbSpriteConfig.GetOrDefault(Sprite);
|
||||
}
|
||||
@@ -113,10 +119,11 @@ public sealed partial class WeaponConfig : Luban.BeanBase
|
||||
+ "price:" + Price + ","
|
||||
+ "attack:" + Attack + ","
|
||||
+ "cooldown:" + Cooldown + ","
|
||||
+ "damageType:" + DamageType + ","
|
||||
+ "attackRange:" + AttackRange + ","
|
||||
+ "attackSoundId:" + AttackSoundId + ","
|
||||
+ "parameters:" + Luban.StringUtil.CollectionToString(Parameters) + ","
|
||||
+ "modifiers:" + Luban.StringUtil.CollectionToString(Modifiers) + ","
|
||||
+ "statModifierConfigs:" + Luban.StringUtil.CollectionToString(StatModifierConfigs) + ","
|
||||
+ "prefab:" + Prefab + ","
|
||||
+ "sprite:" + Sprite + ","
|
||||
+ "}";
|
||||
|
||||
@@ -15,18 +15,18 @@ namespace SepCore.Definition
|
||||
/// <summary>
|
||||
/// 出怪配置
|
||||
/// </summary>
|
||||
public sealed partial class enemySpawnConfig : Luban.BeanBase
|
||||
public sealed partial class EnemySpawnConfig : Luban.BeanBase
|
||||
{
|
||||
public enemySpawnConfig(ByteBuf _buf)
|
||||
public EnemySpawnConfig(ByteBuf _buf)
|
||||
{
|
||||
EnemyType = (EnemyType)_buf.ReadInt();
|
||||
EnemyCount = _buf.ReadInt();
|
||||
Interval = _buf.ReadInt();
|
||||
}
|
||||
|
||||
public static enemySpawnConfig DeserializeenemySpawnConfig(ByteBuf _buf)
|
||||
public static EnemySpawnConfig DeserializeEnemySpawnConfig(ByteBuf _buf)
|
||||
{
|
||||
return new enemySpawnConfig(_buf);
|
||||
return new EnemySpawnConfig(_buf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -42,7 +42,7 @@ public sealed partial class enemySpawnConfig : Luban.BeanBase
|
||||
/// </summary>
|
||||
public readonly int Interval;
|
||||
|
||||
public const int __ID__ = -1182208523;
|
||||
public const int __ID__ = -2024984043;
|
||||
public override int GetTypeId() => __ID__;
|
||||
|
||||
public void ResolveRef(Tables tables)
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace SepCore.Editor
|
||||
EditorGUILayout.LabelField("Title", selectedProp.Name);
|
||||
EditorGUILayout.LabelField("Rarity", selectedProp.Rarity.ToString());
|
||||
EditorGUILayout.LabelField("Description");
|
||||
EditorGUILayout.HelpBox(ItemDescUtility.CreatePropDescription(selectedProp.Modifiers), MessageType.None);
|
||||
EditorGUILayout.HelpBox(ItemDescUtility.CreatePropDescription(selectedProp.StatModifierConfigs), MessageType.None);
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace SepCore.UI
|
||||
Title = reward.Title,
|
||||
Icon = null,
|
||||
Rarity = reward.Rarity,
|
||||
Description = ItemDescUtility.CreatePropDescription(reward.Modifiers),
|
||||
Description = ItemDescUtility.CreatePropDescription(reward.StatModifierConfigs),
|
||||
IconAssetName = reward.Sprite
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace SepCore.UI
|
||||
{
|
||||
var prop = (PropConfig)rawData.DataRow;
|
||||
Name = prop.Name;
|
||||
Description = ItemDescUtility.CreatePropDescription(prop.Modifiers);
|
||||
Description = ItemDescUtility.CreatePropDescription(prop.StatModifierConfigs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,46 +20,52 @@ namespace SepCore.CustomUtility
|
||||
|
||||
private static readonly string[] _statTypeNames =
|
||||
{
|
||||
"无效",
|
||||
"最大生命",
|
||||
"移动速度",
|
||||
"伤害",
|
||||
"防御",
|
||||
"冷却",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"闪避",
|
||||
"金币/经验吸收范围"
|
||||
"无效", // None = 0
|
||||
"最大生命", // MaxHp = 1
|
||||
"生命恢复", // HpRegen = 2
|
||||
"生命窃取", // LifeSteal = 3
|
||||
"伤害加成", // DamagePercent = 4
|
||||
"近战伤害", // MeleeDamage = 5
|
||||
"远程伤害", // RangedDamage = 6
|
||||
"元素伤害", // ElementalDamage = 7
|
||||
"攻击速度", // AttackSpeed = 8
|
||||
"暴击率", // CritChance = 9
|
||||
"工程学", // Engineering = 10
|
||||
"攻击范围", // Range = 11
|
||||
"护甲", // Armor = 12
|
||||
"闪避率", // Dodge = 13
|
||||
"移动速度", // MoveSpeed = 14
|
||||
"收获", // Harvesting = 15
|
||||
"幸运" // Luck = 16
|
||||
};
|
||||
|
||||
public static string Describe(modifier modifier)
|
||||
public static string Describe(StatModifierConfig statModifierConfig)
|
||||
{
|
||||
if (modifier == null)
|
||||
if (statModifierConfig == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string name = _statTypeNames[(int)modifier.Key];
|
||||
string colorTag = modifier.Value > 0 ? "green" : "red";
|
||||
string suffix = modifier.IsPercent ? "%" : string.Empty;
|
||||
float displayValue;
|
||||
if (modifier.IsPercent)
|
||||
int index = (int)statModifierConfig.StatType;
|
||||
string name = (index >= 0 && index < _statTypeNames.Length) ? _statTypeNames[index] : statModifierConfig.StatType.ToString();
|
||||
string colorTag = statModifierConfig.Value >= 0 ? "green" : "red";
|
||||
string sign = statModifierConfig.Value >= 0 ? "+" : "";
|
||||
|
||||
if (statModifierConfig.ModifierType == ModifierType.PercentAdd)
|
||||
{
|
||||
displayValue = modifier.Value / 10f;
|
||||
}
|
||||
else if (modifier.Key == StatType.MovementSpeed)
|
||||
{
|
||||
displayValue = modifier.Value / 1000f;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayValue = modifier.Value;
|
||||
float displayPercent = statModifierConfig.Value * 100f;
|
||||
return $"{name}: <color={colorTag}>{sign}{displayPercent:0.##}%</color>";
|
||||
}
|
||||
|
||||
return $"{name}: <color={colorTag}>+{displayValue:0.##}{suffix}</color>";
|
||||
if (statModifierConfig.ModifierType == ModifierType.PercentMult)
|
||||
{
|
||||
return $"{name}: <color={colorTag}>x{statModifierConfig.Value:0.##}</color>";
|
||||
}
|
||||
|
||||
return $"{name}: <color={colorTag}>{sign}{statModifierConfig.Value:0.##}</color>";
|
||||
}
|
||||
|
||||
public static string CreatePropDescription(IEnumerable<modifier> modifiers)
|
||||
public static string CreatePropDescription(IEnumerable<StatModifierConfig> modifiers)
|
||||
{
|
||||
if (modifiers == null)
|
||||
{
|
||||
@@ -67,7 +73,7 @@ namespace SepCore.CustomUtility
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (modifier modifier in modifiers)
|
||||
foreach (StatModifierConfig modifier in modifiers)
|
||||
{
|
||||
sb.Append(Describe(modifier));
|
||||
sb.Append('\n');
|
||||
@@ -84,7 +90,7 @@ namespace SepCore.CustomUtility
|
||||
}
|
||||
|
||||
return CreateWeaponDescription(weaponConfig.Attack, weaponConfig.Cooldown / 1000f,
|
||||
weaponConfig.AttackRange / 1000f, weaponConfig.Modifiers, weaponConfig.Parameters);
|
||||
weaponConfig.AttackRange / 1000f, weaponConfig.StatModifierConfigs, weaponConfig.Parameters);
|
||||
}
|
||||
|
||||
public static string CreateWeaponDescription(WeaponBase weapon)
|
||||
@@ -119,7 +125,7 @@ namespace SepCore.CustomUtility
|
||||
}
|
||||
|
||||
private static string CreateWeaponDescription(int attack, float cooldown, float attackRange,
|
||||
IEnumerable<modifier> modifiers, IReadOnlyDictionary<ParamType, int> parameters)
|
||||
IEnumerable<StatModifierConfig> modifiers, IReadOnlyDictionary<ParamType, int> parameters)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine($"攻击: {attack}");
|
||||
|
||||
@@ -214,7 +214,7 @@ namespace SepCore.DebugPanel
|
||||
if (selectedProp == null) return;
|
||||
|
||||
GUILayout.Label($"Selected: {selectedProp.Name} ({selectedProp.Rarity})");
|
||||
GUILayout.Label(ItemDescUtility.CreatePropDescription(selectedProp.Modifiers), GUILayout.Height(40f));
|
||||
GUILayout.Label(ItemDescUtility.CreatePropDescription(selectedProp.StatModifierConfigs), GUILayout.Height(40f));
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("Count", GUILayout.Width(78f));
|
||||
@@ -255,16 +255,16 @@ namespace SepCore.DebugPanel
|
||||
|
||||
if (_showCollisionStats)
|
||||
{
|
||||
SimulationWorld simulationWorld = GameEntry.SimulationWorld;
|
||||
SimulationStats simulationStats = GameEntry.SimulationWorld.Stats;
|
||||
GUILayout.Space(4f);
|
||||
GUILayout.Label(
|
||||
$"Collision Queries: total {simulationWorld.LastCollisionQueryCount} (Projectile {simulationWorld.LastProjectileCollisionQueryCount} / Area {simulationWorld.LastAreaCollisionQueryCount})");
|
||||
$"Collision Queries: total {simulationStats.LastCollisionQueryCount} (Projectile {simulationStats.LastProjectileCollisionQueryCount} / Area {simulationStats.LastAreaCollisionQueryCount})");
|
||||
GUILayout.Label(
|
||||
$"Collision Candidates: total {simulationWorld.LastCollisionCandidateCount} (Projectile {simulationWorld.LastProjectileCollisionCandidateCount} / Area {simulationWorld.LastAreaCollisionCandidateCount})");
|
||||
$"Collision Candidates: total {simulationStats.LastCollisionCandidateCount} (Projectile {simulationStats.LastProjectileCollisionCandidateCount} / Area {simulationStats.LastAreaCollisionCandidateCount})");
|
||||
GUILayout.Label(
|
||||
$"Area Resolve: hits {simulationWorld.LastResolvedAreaHitCount}");
|
||||
$"Area Resolve: hits {simulationStats.LastResolvedAreaHitCount}");
|
||||
GUILayout.Label(
|
||||
$"Broad Phase: cell {simulationWorld.LastCollisionCellSize:F2}, hasEnemyTargets {(simulationWorld.LastCollisionHasEnemyTargets ? "Yes" : "No")}");
|
||||
$"Broad Phase: cell {simulationStats.LastCollisionCellSize:F2}, hasEnemyTargets {(simulationStats.LastCollisionHasEnemyTargets ? "Yes" : "No")}");
|
||||
}
|
||||
|
||||
if (_showSpawnControls)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace SepCore.Procedure
|
||||
_enemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
Vector3 playerPosition = Player.CachedTransform.position;
|
||||
GameEntry.SimulationWorld.Tick(new SimulationTickContext(elapseSeconds, realElapseSeconds, playerPosition));
|
||||
GameEntry.SimulationWorld.Tick(new SimulationTickContext(elapseSeconds, playerPosition));
|
||||
|
||||
int timeLeft = Mathf.Max(0, (int)GameEntry.Timer.GetRemainingTime(_levelTimerHandle));
|
||||
GameEntry.Event.Fire(this, LevelProcessEventArgs.Create(timeLeft));
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Simulation.EditModeTests")]
|
||||
[assembly: InternalsVisibleTo("Simulation.PlayModeTests")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d68a67948722984392ea42a1cec3594
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -24,7 +24,7 @@ public partial class GameEntry
|
||||
|
||||
public static EnemyManagerComponent EnemyManager { get; private set; }
|
||||
|
||||
public static SimulationWorld SimulationWorld { get; private set; }
|
||||
public static ISimulationWorld SimulationWorld { get; private set; }
|
||||
|
||||
public static UIRouterComponent UIRouter { get; private set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 715771c24b633ee49b200700baec66de
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 多维属性修饰器管线(Attribute Pipeline)。
|
||||
/// 管理角色所有属性维度,提供三段修饰求值、脏标记缓存与按 Source 批量移除机制。
|
||||
/// 纯 C# 类,无 MonoBehaviour 依赖。
|
||||
/// </summary>
|
||||
public sealed class AttributePipeline : IAttributeProvider
|
||||
{
|
||||
private readonly Dictionary<StatType, CharacterStat> _stats = new();
|
||||
|
||||
/// <summary>
|
||||
/// 当某个属性的值发生变动(添加/移除修饰器、修改基础值)时触发。
|
||||
/// 参数:(StatType 属性类型, float 新的当前值)。
|
||||
/// </summary>
|
||||
public event Action<StatType, float> OnStatChanged;
|
||||
|
||||
public AttributePipeline()
|
||||
{
|
||||
// 预初始化已定义的所有 StatType 枚举
|
||||
Array values = Enum.GetValues(typeof(StatType));
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
StatType statType = (StatType)values.GetValue(i);
|
||||
_stats[statType] = new CharacterStat(statType, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定类型的属性容器。
|
||||
/// </summary>
|
||||
public CharacterStat Get(StatType statType)
|
||||
{
|
||||
if (!_stats.TryGetValue(statType, out CharacterStat stat))
|
||||
{
|
||||
stat = new CharacterStat(statType, 0f);
|
||||
_stats.Add(statType, stat);
|
||||
}
|
||||
|
||||
return stat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定属性的当前最终计算值(带脏标记缓存与边界钳制)。
|
||||
/// </summary>
|
||||
public float GetValue(StatType statType)
|
||||
{
|
||||
return Get(statType).Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定属性的基础值。
|
||||
/// </summary>
|
||||
public void SetBaseValue(StatType statType, float baseValue)
|
||||
{
|
||||
CharacterStat stat = Get(statType);
|
||||
float oldValue = stat.Value;
|
||||
stat.BaseValue = baseValue;
|
||||
float newValue = stat.Value;
|
||||
|
||||
if (Math.Abs(oldValue - newValue) > 1e-6f)
|
||||
{
|
||||
OnStatChanged?.Invoke(statType, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加属性修饰器。
|
||||
/// </summary>
|
||||
public void AddModifier(StatType statType, StatModifier modifier)
|
||||
{
|
||||
if (modifier == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modifier));
|
||||
}
|
||||
|
||||
CharacterStat stat = Get(statType);
|
||||
stat.AddModifier(modifier);
|
||||
OnStatChanged?.Invoke(statType, stat.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除属性修饰器。
|
||||
/// </summary>
|
||||
public bool RemoveModifier(StatType statType, StatModifier modifier)
|
||||
{
|
||||
if (modifier == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CharacterStat stat = Get(statType);
|
||||
bool removed = stat.RemoveModifier(modifier);
|
||||
if (removed)
|
||||
{
|
||||
OnStatChanged?.Invoke(statType, stat.Value);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 Luban 导出的 modifier(自动转换为 StatModifier)。
|
||||
/// </summary>
|
||||
public void AddModifier(StatModifierConfig lubanMod, object source = null)
|
||||
{
|
||||
if (lubanMod == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(lubanMod));
|
||||
}
|
||||
|
||||
StatModifier statMod = StatModifier.FromLuban(lubanMod, source);
|
||||
AddModifier(lubanMod.StatType, statMod);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除由 Luban 导出的 modifier 所生成的对应修饰器。
|
||||
/// </summary>
|
||||
public bool RemoveModifier(StatModifierConfig lubanMod, object source = null)
|
||||
{
|
||||
if (lubanMod == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CharacterStat stat = Get(lubanMod.StatType);
|
||||
float targetValue = lubanMod.Value;
|
||||
ModifierType targetType = lubanMod.ModifierType;
|
||||
|
||||
IReadOnlyList<StatModifier> modifiers = stat.Modifiers;
|
||||
for (int i = modifiers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
StatModifier m = modifiers[i];
|
||||
if (m.Type == targetType &&
|
||||
Math.Abs(m.Value - targetValue) < 1e-6f &&
|
||||
(source == null || ReferenceEquals(m.Source, source) || Equals(m.Source, source)))
|
||||
{
|
||||
return RemoveModifier(lubanMod.StatType, m);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一键移除所有属性中指定来源(道具、武器、羁绊、Buff 等)的所有修饰器。
|
||||
/// </summary>
|
||||
/// <param name="source">来源对象引用</param>
|
||||
/// <returns>移除的修饰器总数</returns>
|
||||
public int RemoveAllModifiersFromSource(object source)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int totalRemoved = 0;
|
||||
foreach (KeyValuePair<StatType, CharacterStat> pair in _stats)
|
||||
{
|
||||
int removed = pair.Value.RemoveAllModifiersFromSource(source);
|
||||
if (removed > 0)
|
||||
{
|
||||
totalRemoved += removed;
|
||||
OnStatChanged?.Invoke(pair.Key, pair.Value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return totalRemoved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有属性的修饰器并标记为脏。
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (KeyValuePair<StatType, CharacterStat> pair in _stats)
|
||||
{
|
||||
pair.Value.ClearModifiers();
|
||||
OnStatChanged?.Invoke(pair.Key, pair.Value.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 312b0c5905f501449b3dc700f4ac0e5b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个属性实例容器。
|
||||
/// 维护基础值、修饰器列表,通过脏标记(Dirty Flag)缓存计算结果,并基于 StatConfig 进行边界钳制。
|
||||
/// </summary>
|
||||
public sealed class CharacterStat
|
||||
{
|
||||
private readonly List<StatModifier> _modifiers = new();
|
||||
private float _baseValue;
|
||||
private bool _isDirty = true;
|
||||
private float _cachedValue;
|
||||
private StatConfig _config;
|
||||
|
||||
public StatType Type { get; }
|
||||
|
||||
public StatConfig Config
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_config == null && GameEntry.Luban != null && GameEntry.Luban.IsReady)
|
||||
{
|
||||
_config = GameEntry.Luban.Get<StatConfig>((int)Type);
|
||||
}
|
||||
|
||||
return _config;
|
||||
}
|
||||
set
|
||||
{
|
||||
_config = value;
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public float BaseValue
|
||||
{
|
||||
get => _baseValue;
|
||||
set
|
||||
{
|
||||
if (Math.Abs(_baseValue - value) > 1e-6f)
|
||||
{
|
||||
_baseValue = value;
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isDirty)
|
||||
{
|
||||
_cachedValue = CalculateFinalValue();
|
||||
_isDirty = false;
|
||||
}
|
||||
|
||||
return _cachedValue;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<StatModifier> Modifiers => _modifiers;
|
||||
|
||||
public CharacterStat(StatType type, float baseValue = 0f, StatConfig config = null)
|
||||
{
|
||||
Type = type;
|
||||
_baseValue = baseValue;
|
||||
_config = config;
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
public void AddModifier(StatModifier modifier)
|
||||
{
|
||||
if (modifier == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modifier));
|
||||
}
|
||||
|
||||
_modifiers.Add(modifier);
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
public bool RemoveModifier(StatModifier modifier)
|
||||
{
|
||||
if (modifier == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_modifiers.Remove(modifier))
|
||||
{
|
||||
_isDirty = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int RemoveAllModifiersFromSource(object source)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int removed = _modifiers.RemoveAll(m => ReferenceEquals(m.Source, source) || Equals(m.Source, source));
|
||||
if (removed > 0)
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void ClearModifiers()
|
||||
{
|
||||
if (_modifiers.Count > 0)
|
||||
{
|
||||
_modifiers.Clear();
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void MarkDirty()
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
private float CalculateFinalValue()
|
||||
{
|
||||
float sumFlat = 0f;
|
||||
float sumPercentAdd = 0f;
|
||||
float prodPercentMult = 1f;
|
||||
|
||||
for (int i = 0; i < _modifiers.Count; i++)
|
||||
{
|
||||
StatModifier mod = _modifiers[i];
|
||||
switch (mod.Type)
|
||||
{
|
||||
case ModifierType.Flat:
|
||||
sumFlat += mod.Value;
|
||||
break;
|
||||
case ModifierType.PercentAdd:
|
||||
sumPercentAdd += mod.Value;
|
||||
break;
|
||||
case ModifierType.PercentMult:
|
||||
prodPercentMult *= mod.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float finalValue = ((_baseValue + sumFlat) * (1f + sumPercentAdd)) * prodPercentMult;
|
||||
return ClampStat(finalValue);
|
||||
}
|
||||
|
||||
private float ClampStat(float value)
|
||||
{
|
||||
StatConfig config = Config;
|
||||
if (config != null)
|
||||
{
|
||||
if (config.HasMin && value < config.MinValue)
|
||||
{
|
||||
value = config.MinValue;
|
||||
}
|
||||
|
||||
if (config.HasMax && value > config.MaxValue)
|
||||
{
|
||||
value = config.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67040be8b42ae744a81f961aa4ec5333
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 伤害结算输入上下文。
|
||||
/// 封装攻击方与受击方的属性管线、武器基础伤害、加成属性、暴击等参数。
|
||||
/// </summary>
|
||||
public struct DamageContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 基础伤害(武器攻击力或敌人基础伤害)。
|
||||
/// </summary>
|
||||
public int BaseDamage;
|
||||
|
||||
/// <summary>
|
||||
/// 伤害加成属性(近战伤害/远程伤害/元素伤害/None)。
|
||||
/// </summary>
|
||||
public StatType DamageScalingStat;
|
||||
|
||||
/// <summary>
|
||||
/// 伤害加成系数,默认 1.0f。
|
||||
/// </summary>
|
||||
public float ScalingRatio;
|
||||
|
||||
/// <summary>
|
||||
/// 基础暴击率(例如来自武器或全局配置)。
|
||||
/// </summary>
|
||||
public float BaseCritChance;
|
||||
|
||||
/// <summary>
|
||||
/// 暴击伤害倍率(例如 1.5f 或 2.0f)。
|
||||
/// </summary>
|
||||
public float CritMultiplier;
|
||||
|
||||
/// <summary>
|
||||
/// 攻击方属性修饰管线(可为 null,如普通怪物未挂载管线)。
|
||||
/// </summary>
|
||||
public IAttributeProvider AttackerPipeline;
|
||||
|
||||
/// <summary>
|
||||
/// 防御方属性修饰管线(可为 null,如普通怪物未挂载管线)。
|
||||
/// </summary>
|
||||
public IAttributeProvider DefenderPipeline;
|
||||
|
||||
/// <summary>
|
||||
/// 防御方备用护甲(当 DefenderPipeline 为 null 时生效)。
|
||||
/// </summary>
|
||||
public float FallbackDefenderArmor;
|
||||
|
||||
/// <summary>
|
||||
/// 防御方备用闪避率(当 DefenderPipeline 为 null 时生效)。
|
||||
/// </summary>
|
||||
public float FallbackDefenderDodge;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8192a0b127344919ba1812836248cc01
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,134 @@
|
||||
using SepCore.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 伤害结算管线(纯 C# 静态计算器)。
|
||||
/// 实现武器类型加成、百分比放大、闪避判定、暴击判定、双曲护甲折减、保底伤害与生命偷取判定。
|
||||
/// </summary>
|
||||
public static class DamagePipeline
|
||||
{
|
||||
public const float ArmorDivisor = 15f; // Brotato 双曲护甲基数
|
||||
public const float DefaultCritMultiplier = 1.5f;
|
||||
public const float DefaultBaseCrit = 0.05f;
|
||||
|
||||
/// <summary>
|
||||
/// 计算最终伤害结果。
|
||||
/// </summary>
|
||||
public static DamageResult Calculate(in DamageContext context)
|
||||
{
|
||||
// 1. 闪避判定 (Defender Dodge)
|
||||
float dodgeChance = context.DefenderPipeline != null
|
||||
? context.DefenderPipeline.GetValue(StatType.Dodge)
|
||||
: context.FallbackDefenderDodge;
|
||||
|
||||
float maxDodge = 0.8f;
|
||||
if (GameEntry.Luban != null && GameEntry.Luban.IsReady)
|
||||
{
|
||||
maxDodge = GameEntry.Luban.Global.MaxDodgeRate / 1000f;
|
||||
}
|
||||
|
||||
dodgeChance = Mathf.Clamp(dodgeChance, 0f, maxDodge);
|
||||
if (dodgeChance > 0f && Random.value < dodgeChance)
|
||||
{
|
||||
return new DamageResult
|
||||
{
|
||||
Damage = 0,
|
||||
IsDodge = true,
|
||||
IsCrit = false,
|
||||
IsLifeSteal = false,
|
||||
LifeStealAmount = 0
|
||||
};
|
||||
}
|
||||
|
||||
// 2. 武器类型伤害加成 (Melee / Ranged / Elemental)
|
||||
float rawDamage = context.BaseDamage;
|
||||
if (context.AttackerPipeline != null && context.DamageScalingStat != StatType.None)
|
||||
{
|
||||
float scalingBonus = context.AttackerPipeline.GetValue(context.DamageScalingStat);
|
||||
float ratio = context.ScalingRatio > 0f ? context.ScalingRatio : 1f;
|
||||
rawDamage += scalingBonus * ratio;
|
||||
}
|
||||
rawDamage = Mathf.Max(rawDamage, 1f);
|
||||
|
||||
// 3. 全局百分比增伤放大 (DamagePercent)
|
||||
float damagePercent = context.AttackerPipeline != null
|
||||
? context.AttackerPipeline.GetValue(StatType.DamagePercent)
|
||||
: 0f;
|
||||
float amplifiedDamage = rawDamage * (1f + damagePercent);
|
||||
amplifiedDamage = Mathf.Max(amplifiedDamage, 1f);
|
||||
|
||||
// 4. 暴击判定 (CritChance & CritMultiplier)
|
||||
float attackerCrit = context.AttackerPipeline != null
|
||||
? context.AttackerPipeline.GetValue(StatType.CritChance)
|
||||
: 0f;
|
||||
|
||||
float baseCrit = context.BaseCritChance;
|
||||
if (baseCrit <= 0f && GameEntry.Luban != null && GameEntry.Luban.IsReady)
|
||||
{
|
||||
baseCrit = GameEntry.Luban.Global.WeaponBaseCrit;
|
||||
}
|
||||
|
||||
float totalCritChance = Mathf.Clamp(baseCrit + attackerCrit, 0f, 0.9f);
|
||||
bool isCrit = totalCritChance > 0f && Random.value < totalCritChance;
|
||||
|
||||
float critMultiplier = context.CritMultiplier;
|
||||
if (critMultiplier <= 0f)
|
||||
{
|
||||
critMultiplier = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.CritMultiplier
|
||||
: DefaultCritMultiplier;
|
||||
}
|
||||
|
||||
float damageAfterCrit = isCrit ? (amplifiedDamage * critMultiplier) : amplifiedDamage;
|
||||
|
||||
// 5. Brotato 标准双曲护甲折减 (Armor Mitigation)
|
||||
float armor = context.DefenderPipeline != null
|
||||
? context.DefenderPipeline.GetValue(StatType.Armor)
|
||||
: context.FallbackDefenderArmor;
|
||||
|
||||
float mitigation;
|
||||
if (armor >= 0f)
|
||||
{
|
||||
mitigation = 1f / (1f + armor / ArmorDivisor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 负护甲易伤放大: 2 - 1/(1 - armor/15)
|
||||
mitigation = 2f - (1f / (1f - armor / ArmorDivisor));
|
||||
}
|
||||
|
||||
float finalDamageFloat = damageAfterCrit * mitigation;
|
||||
int minDamage = 1;
|
||||
if (GameEntry.Luban != null && GameEntry.Luban.IsReady)
|
||||
{
|
||||
minDamage = GameEntry.Luban.Global.MinDamage;
|
||||
}
|
||||
|
||||
int finalDamage = Mathf.Max(Mathf.CeilToInt(finalDamageFloat), minDamage);
|
||||
|
||||
// 6. 生命偷取判定 (LifeSteal)
|
||||
bool isLifeSteal = false;
|
||||
int lifeStealAmount = 0;
|
||||
if (finalDamage > 0 && context.AttackerPipeline != null)
|
||||
{
|
||||
float lifeStealRate = context.AttackerPipeline.GetValue(StatType.LifeSteal);
|
||||
if (lifeStealRate > 0f && Random.value < lifeStealRate)
|
||||
{
|
||||
isLifeSteal = true;
|
||||
lifeStealAmount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return new DamageResult
|
||||
{
|
||||
Damage = finalDamage,
|
||||
IsDodge = false,
|
||||
IsCrit = isCrit,
|
||||
IsLifeSteal = isLifeSteal,
|
||||
LifeStealAmount = lifeStealAmount
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a394c2d349564121dc3834058460ee23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 伤害计算输出结果。
|
||||
/// </summary>
|
||||
public struct DamageResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 最终扣除的生命值(若闪避则为 0)。
|
||||
/// </summary>
|
||||
public int Damage;
|
||||
|
||||
/// <summary>
|
||||
/// 是否闪避。
|
||||
/// </summary>
|
||||
public bool IsDodge;
|
||||
|
||||
/// <summary>
|
||||
/// 是否暴击。
|
||||
/// </summary>
|
||||
public bool IsCrit;
|
||||
|
||||
/// <summary>
|
||||
/// 是否触发生命偷取。
|
||||
/// </summary>
|
||||
public bool IsLifeSteal;
|
||||
|
||||
/// <summary>
|
||||
/// 偷取回复的生命值(通常为 1)。
|
||||
/// </summary>
|
||||
public int LifeStealAmount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9283b1c238454920cb2923947359dd12
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using SepCore.Definition;
|
||||
using SepCore.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -7,85 +5,32 @@ namespace SepCore.Components
|
||||
{
|
||||
public class AbsorbComponent : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private int _maxDetectCount = 64;
|
||||
|
||||
private Player _player;
|
||||
private Transform _target;
|
||||
private StatComponent _statComponent;
|
||||
private Collider[] _buffer;
|
||||
|
||||
private StatProperty _absorbRangeStat;
|
||||
private Action<modifier, bool> _absorbRangeStatCallback;
|
||||
|
||||
private float AbsorbRangeBase => GameEntry.Luban.Global.DefaultAbsorbRange / 1000f;
|
||||
private float AbsorbSpeed => GameEntry.Luban.Global.AbsorbSpeed / 1000f;
|
||||
private float CollectDistance => GameEntry.Luban.Global.CollectDistance / 1000f;
|
||||
|
||||
private float AbsorbRange => Mathf.Max(0f, (AbsorbRangeBase + _absorbRangeStat.Value) * _absorbRangeStat.Percent);
|
||||
private float AbsorbRange => AbsorbRangeBase;
|
||||
|
||||
public void OnInit(Player player, StatComponent statComponent)
|
||||
{
|
||||
_player = player;
|
||||
_target = player.CachedTransform;
|
||||
_statComponent = statComponent;
|
||||
_buffer = new Collider[Mathf.Max(1, _maxDetectCount)];
|
||||
|
||||
_absorbRangeStat = _statComponent.GetStat(StatType.AbsorbRange);
|
||||
_absorbRangeStatCallback = (modifier, isApply) =>
|
||||
_statComponent.UpdateStat(_absorbRangeStat, modifier, isApply);
|
||||
_statComponent.Subscribe(StatType.AbsorbRange, _absorbRangeStatCallback);
|
||||
}
|
||||
|
||||
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
if (!_player.Available) return;
|
||||
|
||||
int hitCount = Physics.OverlapSphereNonAlloc(
|
||||
_target.position,
|
||||
AbsorbRange,
|
||||
_buffer,
|
||||
Constant.Layer.DropLayerMask
|
||||
);
|
||||
|
||||
for (int i = 0; i < hitCount; i++)
|
||||
{
|
||||
Collider hit = _buffer[i];
|
||||
if (hit == null) continue;
|
||||
|
||||
CoinEntity coin = hit.GetComponent<CoinEntity>();
|
||||
if (coin != null && coin.Available)
|
||||
{
|
||||
AbsorbDrop(coin.CachedTransform, elapseSeconds);
|
||||
coin.TryCollect(_player, CollectDistance);
|
||||
continue;
|
||||
}
|
||||
|
||||
ExpEntity exp = hit.GetComponent<ExpEntity>();
|
||||
if (exp != null && exp.Available)
|
||||
{
|
||||
AbsorbDrop(exp.CachedTransform, elapseSeconds);
|
||||
exp.TryCollect(_player, CollectDistance);
|
||||
}
|
||||
}
|
||||
GameEntry.SimulationWorld.SyncPlayerAbsorbSettings(AbsorbRange, AbsorbSpeed, CollectDistance);
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
if (_statComponent != null)
|
||||
{
|
||||
_statComponent.Unsubscribe(StatType.AbsorbRange, _absorbRangeStatCallback);
|
||||
_absorbRangeStatCallback = null;
|
||||
}
|
||||
|
||||
_statComponent = null;
|
||||
_target = null;
|
||||
_player = null;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
private void AbsorbDrop(Transform drop, float deltaTime)
|
||||
{
|
||||
drop.position = Vector3.MoveTowards(drop.position, _target.position, AbsorbSpeed * deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,23 +13,17 @@ namespace SepCore.Components
|
||||
|
||||
private StatComponent _statComponent;
|
||||
|
||||
private StatProperty _maxHealthStat;
|
||||
private Action<modifier, bool> _maxHealthStatCallback;
|
||||
public StatProperty MaxHealthStat => _maxHealthStat;
|
||||
|
||||
private StatProperty _defenseStat;
|
||||
private Action<modifier, bool> _defenseStatCallback;
|
||||
public StatProperty DefenseStat => _defenseStat;
|
||||
|
||||
private StatProperty _dodgeStat;
|
||||
private Action<modifier, bool> _dodgeStatCallback;
|
||||
public StatProperty DodgeStat => _dodgeStat;
|
||||
private Action<StatType, float> _onStatChangedAction;
|
||||
private float _lastLifeStealTime = -1f;
|
||||
public const float LifeStealIcd = 0.1f;
|
||||
|
||||
private bool _isPlayer => _statComponent != null;
|
||||
|
||||
public float HealthRatio => MaxHealth > 0 ? (float)CurrentHealth / MaxHealth : 0f;
|
||||
|
||||
public int MaxHealth => Mathf.CeilToInt((_maxHealthBase + _maxHealthStat.Value) * _maxHealthStat.Percent);
|
||||
public int MaxHealth => _statComponent != null
|
||||
? Mathf.CeilToInt(_statComponent.GetStatValue(StatType.MaxHp))
|
||||
: _maxHealthBase;
|
||||
|
||||
public int CurrentHealth
|
||||
{
|
||||
@@ -46,82 +40,115 @@ namespace SepCore.Components
|
||||
_statComponent = statComponent;
|
||||
if (_statComponent != null)
|
||||
{
|
||||
_maxHealthStat = _statComponent.GetStat(StatType.MaxHealth);
|
||||
_maxHealthStatCallback = (modifier, isApply) =>
|
||||
{
|
||||
_statComponent.UpdateStat(_maxHealthStat, modifier, isApply);
|
||||
GameEntry.Event.Fire(this, PlayerHealthChangeEventArgs.Create(0, CurrentHealth, MaxHealth));
|
||||
};
|
||||
_statComponent.Subscribe(StatType.MaxHealth, _maxHealthStatCallback);
|
||||
|
||||
_defenseStat = _statComponent.GetStat(StatType.Defense);
|
||||
_defenseStatCallback = (modifier, isApply) =>
|
||||
_statComponent.UpdateStat(_defenseStat, modifier, isApply);
|
||||
_statComponent.Subscribe(StatType.Defense, _defenseStatCallback);
|
||||
|
||||
_dodgeStat = _statComponent.GetStat(StatType.Dodge);
|
||||
_dodgeStatCallback = (modifier, isApply) =>
|
||||
_statComponent.UpdateStat(_dodgeStat, modifier, isApply);
|
||||
_statComponent.Subscribe(StatType.Dodge, _dodgeStatCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
_maxHealthStat = new StatProperty();
|
||||
_defenseStat = new StatProperty();
|
||||
_dodgeStat = new StatProperty();
|
||||
_statComponent.Pipeline.SetBaseValue(StatType.MaxHp, maxHealth);
|
||||
_onStatChangedAction = OnStatChanged;
|
||||
_statComponent.Pipeline.OnStatChanged += _onStatChangedAction;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
if (_statComponent != null)
|
||||
if (_statComponent != null && _onStatChangedAction != null)
|
||||
{
|
||||
_statComponent.Unsubscribe(StatType.MaxHealth, _maxHealthStatCallback);
|
||||
_maxHealthStatCallback = null;
|
||||
|
||||
_statComponent.Unsubscribe(StatType.Defense, _defenseStatCallback);
|
||||
_defenseStatCallback = null;
|
||||
|
||||
_statComponent.Unsubscribe(StatType.Dodge, _dodgeStatCallback);
|
||||
_dodgeStatCallback = null;
|
||||
_statComponent.Pipeline.OnStatChanged -= _onStatChangedAction;
|
||||
_onStatChangedAction = null;
|
||||
}
|
||||
|
||||
_statComponent = null;
|
||||
_lastLifeStealTime = -1f;
|
||||
}
|
||||
|
||||
private void OnStatChanged(StatType statType, float newValue)
|
||||
{
|
||||
if (statType == StatType.MaxHp)
|
||||
{
|
||||
if (_currentHealth > MaxHealth)
|
||||
{
|
||||
_currentHealth = MaxHealth;
|
||||
}
|
||||
|
||||
if (GameEntry.Event != null)
|
||||
{
|
||||
GameEntry.Event.Fire(this, PlayerHealthChangeEventArgs.Create(0, CurrentHealth, MaxHealth));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public void TakeDamage(int damage)
|
||||
public void TakeDamage(int damage, bool isCrit = false, bool isDodge = false, Vector3? hitPosition = null)
|
||||
{
|
||||
if (_isDead) return;
|
||||
|
||||
float maxDodge = GameEntry.Luban.Global.MaxDodgeRate / 1000f;
|
||||
if (UnityEngine.Random.value < Mathf.Clamp(_dodgeStat.Value, 0f, maxDodge))
|
||||
if (isDodge)
|
||||
{
|
||||
damage = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
int defenseValue = (int)_defenseStat.Value;
|
||||
float defensePercent = _defenseStat.Percent;
|
||||
damage -= defenseValue;
|
||||
damage = Mathf.CeilToInt(damage / defensePercent);
|
||||
int minDamage = GameEntry.Luban.Global.MinDamage;
|
||||
if (damage < minDamage) damage = minDamage;
|
||||
float yOffset = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.DamageTextYOffset / 1000f
|
||||
: 1f;
|
||||
Vector3 pos = hitPosition ?? (transform.position + Vector3.up * yOffset);
|
||||
if (GameEntry.DamageText != null)
|
||||
{
|
||||
GameEntry.DamageText.ShowDamage(pos, 0, false, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_currentHealth -= damage;
|
||||
CauseDamage(damage);
|
||||
if (_isPlayer)
|
||||
if (damage <= 0) return;
|
||||
|
||||
_currentHealth = Mathf.Max(0, _currentHealth - damage);
|
||||
CauseDamage(damage, isCrit, hitPosition);
|
||||
|
||||
if (_isPlayer && GameEntry.Event != null)
|
||||
{
|
||||
GameEntry.Event.Fire(this, PlayerHealthChangeEventArgs.Create(damage, CurrentHealth, MaxHealth));
|
||||
}
|
||||
}
|
||||
|
||||
private void CauseDamage(int damage)
|
||||
public void TakeDamage(int damage)
|
||||
{
|
||||
TakeDamage(damage, false, false, null);
|
||||
}
|
||||
|
||||
public void Heal(int healAmount)
|
||||
{
|
||||
if (_isDead || healAmount <= 0) return;
|
||||
|
||||
int oldHealth = _currentHealth;
|
||||
_currentHealth = Mathf.Min(_currentHealth + healAmount, MaxHealth);
|
||||
int healed = _currentHealth - oldHealth;
|
||||
|
||||
if (healed > 0 && _isPlayer && GameEntry.Event != null)
|
||||
{
|
||||
GameEntry.Event.Fire(this, PlayerHealthChangeEventArgs.Create(-healed, CurrentHealth, MaxHealth));
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryTriggerLifeSteal(int healAmount, float? customTime = null)
|
||||
{
|
||||
if (_isDead || healAmount <= 0) return false;
|
||||
float currentTime = customTime ?? Time.time;
|
||||
if (_lastLifeStealTime >= 0f && currentTime - _lastLifeStealTime < LifeStealIcd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_lastLifeStealTime = currentTime;
|
||||
Heal(healAmount);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CauseDamage(int damage, bool isCrit = false, Vector3? hitPosition = null)
|
||||
{
|
||||
if (damage <= 0) return;
|
||||
float yOffset = GameEntry.Luban.Global.DamageTextYOffset / 1000f;
|
||||
GameEntry.DamageText.ShowDamage(transform.position + Vector3.up * yOffset, damage);
|
||||
float yOffset = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.DamageTextYOffset / 1000f
|
||||
: 1f;
|
||||
Vector3 pos = hitPosition ?? (transform.position + Vector3.up * yOffset);
|
||||
if (GameEntry.DamageText != null)
|
||||
{
|
||||
GameEntry.DamageText.ShowDamage(pos, damage, isCrit, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace SepCore.Components
|
||||
[SerializeField] private float _enemyBodyRadius;
|
||||
[SerializeField] private int _separationIterations;
|
||||
|
||||
public float Speed => (_speedBase + _movementStat.Value) * _movementStat.Percent;
|
||||
public float Speed => _statComponent != null ? _statComponent.GetStatValue(StatType.MoveSpeed) : _speedBase;
|
||||
public bool AvoidEnemyOverlap => _avoidEnemyOverlap;
|
||||
public float EnemyBodyRadius => _enemyBodyRadius;
|
||||
public int SeparationIterations => _separationIterations;
|
||||
@@ -28,9 +28,7 @@ namespace SepCore.Components
|
||||
private StatComponent _statComponent;
|
||||
private Player _player;
|
||||
private EnemyBase _enemy;
|
||||
|
||||
private StatProperty _movementStat;
|
||||
private Action<modifier, bool> _movementStatCallback;
|
||||
private Action<StatType, float> _onStatChangedAction;
|
||||
|
||||
public void OnInit(float speed, Transform target, StatComponent statComponent = null,
|
||||
bool avoidEnemyOverlap = false, float? enemyBodyRadius = null, int? separationIterations = null)
|
||||
@@ -39,8 +37,15 @@ namespace SepCore.Components
|
||||
_cachedTransform = target;
|
||||
_direction = Vector3.forward;
|
||||
_avoidEnemyOverlap = avoidEnemyOverlap;
|
||||
_enemyBodyRadius = Mathf.Max(0.01f, enemyBodyRadius ?? (GameEntry.Luban.Global.EnemyBodyRadius / 1000f));
|
||||
_separationIterations = Mathf.Max(1, separationIterations ?? GameEntry.Luban.Global.EnemySeparationIterations);
|
||||
float defaultRadius = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.EnemyBodyRadius / 1000f
|
||||
: 0.5f;
|
||||
int defaultIterations = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.EnemySeparationIterations
|
||||
: 4;
|
||||
|
||||
_enemyBodyRadius = Mathf.Max(0.01f, enemyBodyRadius ?? defaultRadius);
|
||||
_separationIterations = Mathf.Max(1, separationIterations ?? defaultIterations);
|
||||
|
||||
TryGetComponent(out _player);
|
||||
TryGetComponent(out _enemy);
|
||||
@@ -48,17 +53,9 @@ namespace SepCore.Components
|
||||
_statComponent = statComponent;
|
||||
if (_statComponent != null)
|
||||
{
|
||||
_movementStat = _statComponent.GetStat(StatType.MovementSpeed);
|
||||
_movementStatCallback = (modifier, isApply) =>
|
||||
{
|
||||
_statComponent.UpdateStat(_movementStat, modifier, isApply);
|
||||
SyncToSimulationWorld();
|
||||
};
|
||||
_statComponent.Subscribe(StatType.MovementSpeed, _movementStatCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
_movementStat = new StatProperty();
|
||||
_statComponent.Pipeline.SetBaseValue(StatType.MoveSpeed, speed);
|
||||
_onStatChangedAction = OnStatChanged;
|
||||
_statComponent.Pipeline.OnStatChanged += _onStatChangedAction;
|
||||
}
|
||||
|
||||
SyncToSimulationWorld();
|
||||
@@ -73,25 +70,32 @@ namespace SepCore.Components
|
||||
{
|
||||
Transform transformToUnregister = _cachedTransform;
|
||||
|
||||
float defaultRadius = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.EnemyBodyRadius / 1000f
|
||||
: 0.5f;
|
||||
int defaultIterations = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.EnemySeparationIterations
|
||||
: 4;
|
||||
|
||||
_speedBase = 0;
|
||||
_cachedTransform = null;
|
||||
_direction = Vector3.zero;
|
||||
_isMoving = false;
|
||||
_avoidEnemyOverlap = false;
|
||||
_enemyBodyRadius = GameEntry.Luban.Global.EnemyBodyRadius / 1000f;
|
||||
_separationIterations = GameEntry.Luban.Global.EnemySeparationIterations;
|
||||
_enemyBodyRadius = defaultRadius;
|
||||
_separationIterations = defaultIterations;
|
||||
_player = null;
|
||||
_enemy = null;
|
||||
|
||||
if (_statComponent != null)
|
||||
if (_statComponent != null && _onStatChangedAction != null)
|
||||
{
|
||||
_statComponent.Unsubscribe(StatType.MovementSpeed, _movementStatCallback);
|
||||
_movementStatCallback = null;
|
||||
_statComponent.Pipeline.OnStatChanged -= _onStatChangedAction;
|
||||
_onStatChangedAction = null;
|
||||
}
|
||||
|
||||
_statComponent = null;
|
||||
|
||||
if (transformToUnregister != null)
|
||||
if (transformToUnregister != null && GameEntry.SimulationWorld != null)
|
||||
{
|
||||
GameEntry.SimulationWorld.UnregisterPlayerMovement(transformToUnregister);
|
||||
}
|
||||
@@ -113,7 +117,7 @@ namespace SepCore.Components
|
||||
{
|
||||
using (CustomProfilerMarker.Movement_Update.Auto())
|
||||
{
|
||||
if (_cachedTransform == null)
|
||||
if (_cachedTransform == null || GameEntry.SimulationWorld == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -138,5 +142,13 @@ namespace SepCore.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStatChanged(StatType statType, float newValue)
|
||||
{
|
||||
if (statType == StatType.MoveSpeed)
|
||||
{
|
||||
SyncToSimulationWorld();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Battle;
|
||||
using SepCore.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -7,8 +8,12 @@ namespace SepCore.Components
|
||||
{
|
||||
public class StatComponent : MonoBehaviour
|
||||
{
|
||||
private readonly Dictionary<StatType, List<modifier>> _statModifiers = new();
|
||||
private readonly Dictionary<StatType, Action<modifier, bool>> _statModifierCallbacks = new();
|
||||
private readonly AttributePipeline _pipeline = new();
|
||||
|
||||
/// <summary>
|
||||
/// 纯逻辑属性修饰器管线。
|
||||
/// </summary>
|
||||
public AttributePipeline Pipeline => _pipeline;
|
||||
|
||||
public void OnInit()
|
||||
{
|
||||
@@ -16,82 +21,42 @@ namespace SepCore.Components
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
_statModifiers.Clear();
|
||||
_statModifierCallbacks.Clear();
|
||||
_pipeline.Clear();
|
||||
}
|
||||
|
||||
public void AddModifier(modifier modifier)
|
||||
public void AddModifier(StatType statType, StatModifier modifier)
|
||||
{
|
||||
if (!_statModifiers.TryGetValue(modifier.StatType, out var modifiers))
|
||||
{
|
||||
modifiers = new List<modifier>();
|
||||
_statModifiers.Add(modifier.StatType, modifiers);
|
||||
}
|
||||
modifiers.Add(modifier);
|
||||
|
||||
if (_statModifierCallbacks.TryGetValue(modifier.StatType, out var callbacks))
|
||||
callbacks?.Invoke(modifier, true);
|
||||
_pipeline.AddModifier(statType, modifier);
|
||||
}
|
||||
|
||||
public void RemoveModifier(modifier modifier)
|
||||
public bool RemoveModifier(StatType statType, StatModifier modifier)
|
||||
{
|
||||
if (_statModifiers.TryGetValue(modifier.StatType, out var modifiers))
|
||||
{
|
||||
modifiers.Remove(modifier);
|
||||
}
|
||||
|
||||
if (_statModifierCallbacks.TryGetValue(modifier.StatType, out var callbacks))
|
||||
callbacks?.Invoke(modifier, false);
|
||||
return _pipeline.RemoveModifier(statType, modifier);
|
||||
}
|
||||
|
||||
public void Subscribe(StatType statType, Action<modifier, bool> callback)
|
||||
public void AddModifier(StatModifierConfig statModifierConfig, object source = null)
|
||||
{
|
||||
if (!_statModifierCallbacks.TryAdd(statType, callback))
|
||||
{
|
||||
_statModifierCallbacks[statType] += callback;
|
||||
}
|
||||
_pipeline.AddModifier(statModifierConfig, source);
|
||||
}
|
||||
|
||||
public void Unsubscribe(StatType statType, Action<modifier, bool> callback)
|
||||
public void RemoveModifier(StatModifierConfig statModifierConfig, object source = null)
|
||||
{
|
||||
if (!_statModifierCallbacks.TryGetValue(statType, out var callbacks))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
callbacks -= callback;
|
||||
if (callbacks == null)
|
||||
{
|
||||
_statModifierCallbacks.Remove(statType);
|
||||
}
|
||||
_pipeline.RemoveModifier(statModifierConfig, source);
|
||||
}
|
||||
|
||||
public StatProperty GetStat(StatType statType)
|
||||
public int RemoveAllModifiersFromSource(object source)
|
||||
{
|
||||
var result = new StatProperty();
|
||||
|
||||
if (!_statModifiers.TryGetValue(statType, out var modifiers)) return result;
|
||||
|
||||
foreach (var modifier in modifiers)
|
||||
{
|
||||
if (modifier.IsPercent) result.Percent += modifier.RealValue;
|
||||
else result.Value += modifier.RealValue;
|
||||
}
|
||||
|
||||
return result;
|
||||
return _pipeline.RemoveAllModifiersFromSource(source);
|
||||
}
|
||||
|
||||
public void UpdateStat(StatProperty stat, modifier modifier, bool isApply)
|
||||
public float GetStatValue(StatType statType)
|
||||
{
|
||||
float coef = isApply ? 1f : -1f;
|
||||
if (modifier.IsPercent)
|
||||
{
|
||||
stat.Percent += modifier.RealValue * coef;
|
||||
}
|
||||
else
|
||||
{
|
||||
stat.Value += modifier.RealValue * coef;
|
||||
}
|
||||
return _pipeline.GetValue(statType);
|
||||
}
|
||||
|
||||
public CharacterStat GetCharacterStat(StatType statType)
|
||||
{
|
||||
return _pipeline.Get(statType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ namespace SepCore.DamageText
|
||||
_instancePoolCapacity);
|
||||
}
|
||||
|
||||
public void ShowDamage(Vector3 worldPosition, int damage)
|
||||
public void ShowDamage(Vector3 worldPosition, int damage, bool isCrit = false, bool isDodge = false)
|
||||
{
|
||||
if (damage <= 0) return;
|
||||
if (damage <= 0 && !isDodge) return;
|
||||
|
||||
DamageTextItem item = CreateDamageTextItem();
|
||||
item.Show(worldPosition, damage, _canvas, OnItemComplete);
|
||||
item.Show(worldPosition, damage, isCrit, isDodge, _canvas, OnItemComplete);
|
||||
_activeDamageTextItems.Add(item);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace SepCore.DamageText
|
||||
private const float FadeDelay = 0.2f;
|
||||
private const float FadeDuration = 0.35f;
|
||||
private static readonly Color NormalColor = new Color(1f, 0.32f, 0.23f, 1f);
|
||||
private static readonly Color CritColor = new Color(1f, 0.85f, 0.15f, 1f);
|
||||
private static readonly Color DodgeColor = new Color(0.75f, 0.75f, 0.75f, 1f);
|
||||
|
||||
[SerializeField] private RectTransform _cachedTransform = null;
|
||||
|
||||
@@ -22,7 +24,7 @@ namespace SepCore.DamageText
|
||||
private Sequence _sequence;
|
||||
private Action<DamageTextItem> _onComplete;
|
||||
|
||||
public void Show(Vector3 worldPosition, int damage, Canvas canvas, Action<DamageTextItem> onComplete)
|
||||
public void Show(Vector3 worldPosition, int damage, bool isCrit, bool isDodge, Canvas canvas, Action<DamageTextItem> onComplete)
|
||||
{
|
||||
_onComplete = onComplete;
|
||||
gameObject.SetActive(true);
|
||||
@@ -30,8 +32,27 @@ namespace SepCore.DamageText
|
||||
|
||||
_canvasGroup.alpha = 1f;
|
||||
_cachedTransform.localScale = Vector3.one;
|
||||
_text.color = NormalColor;
|
||||
_text.text = damage.ToString();
|
||||
|
||||
Vector3 punchScale;
|
||||
if (isDodge)
|
||||
{
|
||||
_text.color = DodgeColor;
|
||||
_text.text = "MISS";
|
||||
punchScale = new Vector3(0.1f, 0.1f, 0f);
|
||||
}
|
||||
else if (isCrit)
|
||||
{
|
||||
_text.color = CritColor;
|
||||
_text.text = damage.ToString() + "!";
|
||||
_cachedTransform.localScale = new Vector3(1.3f, 1.3f, 1f);
|
||||
punchScale = new Vector3(0.35f, 0.35f, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_text.color = NormalColor;
|
||||
_text.text = damage.ToString();
|
||||
punchScale = new Vector3(0.15f, 0.15f, 0f);
|
||||
}
|
||||
|
||||
Vector3 screenPos = GameEntry.Scene.MainCamera.WorldToScreenPoint(worldPosition);
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle((RectTransform)canvas.transform, screenPos,
|
||||
@@ -42,11 +63,16 @@ namespace SepCore.DamageText
|
||||
_sequence = DOTween.Sequence();
|
||||
_sequence.Append(_cachedTransform.DOAnchorPosY(localPos.y + RiseDistance, RiseDuration)
|
||||
.SetEase(Ease.OutQuad));
|
||||
_sequence.Join(_cachedTransform.DOPunchScale(new Vector3(0.15f, 0.15f, 0f), 0.18f, 6, 0.75f));
|
||||
_sequence.Join(_cachedTransform.DOPunchScale(punchScale, 0.18f, 6, 0.75f));
|
||||
_sequence.Join(_canvasGroup.DOFade(0f, FadeDuration).SetDelay(FadeDelay));
|
||||
_sequence.OnComplete(() => _onComplete(this));
|
||||
}
|
||||
|
||||
public void Show(Vector3 worldPosition, int damage, Canvas canvas, Action<DamageTextItem> onComplete)
|
||||
{
|
||||
Show(worldPosition, damage, false, false, canvas, onComplete);
|
||||
}
|
||||
|
||||
public void ResetItem()
|
||||
{
|
||||
KillSequence();
|
||||
|
||||
@@ -35,7 +35,8 @@ namespace SepCore.CustomComponent
|
||||
"tbspriteconfig",
|
||||
"tbuiformconfig",
|
||||
"tbuisoundconfig",
|
||||
"tbweaponconfig"
|
||||
"tbweaponconfig",
|
||||
"tbstatconfig"
|
||||
};
|
||||
|
||||
private sealed class TableAccessor
|
||||
@@ -133,6 +134,11 @@ namespace SepCore.CustomComponent
|
||||
Accessor(tables => id => tables.TbWeaponConfig.GetOrDefault((WeaponType)id),
|
||||
tables => tables.TbWeaponConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(StatConfig),
|
||||
Accessor(tables => id => tables.TbStatConfig.GetOrDefault((StatType)id),
|
||||
tables => tables.TbStatConfig.DataList)
|
||||
},
|
||||
};
|
||||
|
||||
private static TableAccessor Accessor<TValue>(Func<Tables, Func<int, object>> rowGetterFactory,
|
||||
|
||||
+15
-8
@@ -1,3 +1,4 @@
|
||||
using SepCore.Battle;
|
||||
using SepCore.Components;
|
||||
using SepCore.Debugger;
|
||||
using SepCore.CustomUtility;
|
||||
@@ -143,16 +144,22 @@ namespace SepCore.Simulation
|
||||
return false;
|
||||
}
|
||||
|
||||
damage = AIUtility.CalcDamageHP(sourceImpact.AttackBase, sourceImpact.AttackStat,
|
||||
targetImpact.DefenseStat,
|
||||
targetImpact.DodgeStat);
|
||||
shouldDispatchPresentation = true;
|
||||
if (damage <= 0)
|
||||
DamageContext context = new DamageContext
|
||||
{
|
||||
return true;
|
||||
}
|
||||
BaseDamage = sourceImpact.AttackBase,
|
||||
DamageScalingStat = sourceImpact.DamageType.ToScalingStat(),
|
||||
ScalingRatio = sourceImpact.ScalingRatio,
|
||||
BaseCritChance = sourceImpact.BaseCritChance,
|
||||
CritMultiplier = sourceImpact.CritMultiplier,
|
||||
AttackerPipeline = sourceImpact.AttributeProvider,
|
||||
DefenderPipeline = targetImpact.AttributeProvider
|
||||
};
|
||||
|
||||
target.ApplyDamage(attacker ?? sourceEntity ?? ownerEntity, damage);
|
||||
DamageResult result = DamagePipeline.Calculate(in context);
|
||||
damage = result.Damage;
|
||||
shouldDispatchPresentation = true;
|
||||
|
||||
target.ApplyDamage(attacker ?? sourceEntity ?? ownerEntity, in result, hitPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -18,6 +18,7 @@ namespace SepCore.Simulation
|
||||
[SerializeField]
|
||||
private bool _projectileHitMarkerEnabled = true;
|
||||
|
||||
#pragma warning disable CS0414
|
||||
[Tooltip("命中标记尺寸。")][SerializeField] private float _projectileHitMarkerSize = 0.2f;
|
||||
|
||||
[Tooltip("命中标记在目标上的高度偏移。")]
|
||||
@@ -27,6 +28,7 @@ namespace SepCore.Simulation
|
||||
[Tooltip("命中标记持续时间。")]
|
||||
[SerializeField]
|
||||
private float _projectileHitMarkerDuration = 0.15f;
|
||||
#pragma warning restore CS0414
|
||||
|
||||
[Tooltip("命中标记颜色。")][SerializeField] private Color _projectileHitMarkerColor = new(1f, 0f, 0f, 0.95f);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using SepCore.Battle;
|
||||
using SepCore.CustomUtility;
|
||||
using SepCore.Definition;
|
||||
using UnityEngine;
|
||||
@@ -145,9 +146,19 @@ namespace SepCore.Entity
|
||||
return;
|
||||
}
|
||||
|
||||
int damage = AIUtility.CalcDamageHP(AttackDamage, null, targetImpactData.DefenseStat,
|
||||
targetImpactData.DodgeStat);
|
||||
_targetableTarget.ApplyDamage(this, damage);
|
||||
DamageContext context = new DamageContext
|
||||
{
|
||||
BaseDamage = AttackDamage,
|
||||
DamageScalingStat = StatType.None,
|
||||
ScalingRatio = 1.0f,
|
||||
BaseCritChance = 0f,
|
||||
CritMultiplier = 0f,
|
||||
AttackerPipeline = selfImpactData.AttributeProvider,
|
||||
DefenderPipeline = targetImpactData.AttributeProvider
|
||||
};
|
||||
|
||||
DamageResult result = DamagePipeline.Calculate(in context);
|
||||
_targetableTarget.ApplyDamage(this, in result, _targetableTarget.CachedTransform.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace SepCore.Entity
|
||||
AddWeaponAsync(EntityDataFactory.Create(GameEntry.Entity.NextId(), WeaponType.Knife, Id, _playerData.Camp)).Forget();
|
||||
|
||||
// StatComponent
|
||||
foreach (var modifier in role.Modifiers)
|
||||
foreach (var modifier in role.StatModifierConfigs)
|
||||
{
|
||||
_statComponent.AddModifier(modifier);
|
||||
}
|
||||
@@ -189,7 +189,7 @@ namespace SepCore.Entity
|
||||
|
||||
public override ImpactData GetImpactData()
|
||||
{
|
||||
return new ImpactData(_playerData.Camp, 0, null, _healthComponent.DefenseStat, _healthComponent.DodgeStat);
|
||||
return new ImpactData(_playerData.Camp, 0, _statComponent.Pipeline);
|
||||
}
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using SepCore.Components;
|
||||
using SepCore.Battle;
|
||||
using SepCore.Components;
|
||||
using SepCore.Definition;
|
||||
using SepCore.CustomUtility;
|
||||
using UnityEngine;
|
||||
@@ -18,22 +19,54 @@ namespace SepCore.Entity
|
||||
|
||||
public abstract ImpactData GetImpactData();
|
||||
|
||||
public void ApplyDamage(EntityBase attacker, int damageHP)
|
||||
public void ApplyDamage(EntityBase attacker, in DamageResult damageResult, Vector3? hitPosition = null)
|
||||
{
|
||||
if (damageResult.IsDodge)
|
||||
{
|
||||
_healthComponent.TakeDamage(0, false, true, hitPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
float fromHPRatio = _healthComponent.HealthRatio;
|
||||
_healthComponent.TakeDamage(damageHP);
|
||||
_healthComponent.TakeDamage(damageResult.Damage, damageResult.IsCrit, false, hitPosition);
|
||||
float toHPRatio = _healthComponent.HealthRatio;
|
||||
if (fromHPRatio > toHPRatio)
|
||||
{
|
||||
GameEntry.HPBar.ShowHPBar(this, fromHPRatio, toHPRatio);
|
||||
}
|
||||
|
||||
if (damageResult.IsLifeSteal && attacker != null)
|
||||
{
|
||||
HealthComponent attackerHealth = attacker.GetComponent<HealthComponent>();
|
||||
if (attackerHealth == null && attacker is WeaponBase weapon)
|
||||
{
|
||||
attackerHealth = weapon.GetComponentInParent<HealthComponent>();
|
||||
}
|
||||
|
||||
if (attackerHealth != null)
|
||||
{
|
||||
attackerHealth.TryTriggerLifeSteal(damageResult.LifeStealAmount);
|
||||
}
|
||||
}
|
||||
|
||||
if (_healthComponent.CurrentHealth <= 0)
|
||||
{
|
||||
OnDead(attacker);
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyDamage(EntityBase attacker, int damageHP)
|
||||
{
|
||||
ApplyDamage(attacker, new DamageResult
|
||||
{
|
||||
Damage = damageHP,
|
||||
IsDodge = false,
|
||||
IsCrit = false,
|
||||
IsLifeSteal = false,
|
||||
LifeStealAmount = 0
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
+1
-7
@@ -43,13 +43,7 @@ namespace SepCore.Entity
|
||||
return false;
|
||||
}
|
||||
|
||||
var simulationWorld = GameEntry.SimulationWorld;
|
||||
if (simulationWorld == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!simulationWorld.TryGetNearestEnemyEntityId(weapon.CachedTransform.position, maxSqrRange,
|
||||
if (!GameEntry.SimulationWorld.TryGetNearestEnemyEntityId(weapon.CachedTransform.position, maxSqrRange,
|
||||
out int entityId))
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -27,16 +27,33 @@ namespace SepCore.Entity
|
||||
|
||||
protected EntityBase _target;
|
||||
|
||||
private float _sqrRange;
|
||||
|
||||
public EntityBase Target => _target;
|
||||
|
||||
public float SqrRange => _sqrRange;
|
||||
public float CurrentCooldown
|
||||
{
|
||||
get
|
||||
{
|
||||
if (WeaponData == null) return 0f;
|
||||
float attackSpeed = _ownerAttributeProvider?.GetValue(StatType.AttackSpeed) ?? 0f;
|
||||
float speedFactor = Mathf.Max(0.2f, 1f + attackSpeed);
|
||||
return Mathf.Max(0.02f, WeaponData.Cooldown / speedFactor);
|
||||
}
|
||||
}
|
||||
|
||||
protected StatProperty AttackStat { get; private set; } = new();
|
||||
public float CurrentAttackRange
|
||||
{
|
||||
get
|
||||
{
|
||||
if (WeaponData == null) return 0f;
|
||||
float rangeBonus = _ownerAttributeProvider?.GetValue(StatType.Range) ?? 0f;
|
||||
float effectiveBonus = rangeBonus >= 50f ? rangeBonus / 1000f : rangeBonus;
|
||||
return Mathf.Max(0.5f, WeaponData.AttackRange + effectiveBonus);
|
||||
}
|
||||
}
|
||||
|
||||
private StatComponent _attackStatComponent;
|
||||
private System.Action<modifier, bool> _attackStatCallback;
|
||||
public float SqrRange => CurrentAttackRange * CurrentAttackRange;
|
||||
|
||||
private IAttributeProvider _ownerAttributeProvider;
|
||||
|
||||
private static float _rotateSpeed => GameEntry.Luban.Global.WeaponRotateSpeed;
|
||||
|
||||
@@ -63,8 +80,7 @@ namespace SepCore.Entity
|
||||
return;
|
||||
}
|
||||
|
||||
StartAttackCooldown(WeaponData.Cooldown);
|
||||
_sqrRange = WeaponData.AttackRange * WeaponData.AttackRange;
|
||||
StartAttackCooldown(CurrentCooldown);
|
||||
_cachedRotation = CachedTransform.rotation;
|
||||
|
||||
if (WeaponData.OwnerCamp == CampType.Player)
|
||||
@@ -136,14 +152,33 @@ namespace SepCore.Entity
|
||||
|
||||
public virtual ImpactData GetImpactData()
|
||||
{
|
||||
return new ImpactData(WeaponData.OwnerCamp, WeaponData.Attack, AttackStat);
|
||||
float baseCrit = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.WeaponBaseCrit
|
||||
: 0.05f;
|
||||
float critMult = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.CritMultiplier
|
||||
: 1.5f;
|
||||
|
||||
return new ImpactData(
|
||||
WeaponData.OwnerCamp,
|
||||
WeaponData.Attack,
|
||||
_ownerAttributeProvider,
|
||||
WeaponData.DamageType,
|
||||
1.0f,
|
||||
baseCrit,
|
||||
critMult);
|
||||
}
|
||||
|
||||
public abstract void Attack();
|
||||
|
||||
public void ResetAttackCooldown()
|
||||
{
|
||||
ResetAttackCooldown(CurrentCooldown);
|
||||
}
|
||||
|
||||
public virtual void Check()
|
||||
{
|
||||
_target = SelectTarget(_sqrRange);
|
||||
_target = SelectTarget(SqrRange);
|
||||
}
|
||||
|
||||
public virtual void RotateToTarget(float elapseSeconds)
|
||||
@@ -272,30 +307,18 @@ namespace SepCore.Entity
|
||||
protected void BindAttackStatFromOwner(EntityLogic parentEntity)
|
||||
{
|
||||
ReleaseAttackStatSubscription();
|
||||
AttackStat = new StatProperty();
|
||||
|
||||
if (parentEntity is not Player player) return;
|
||||
|
||||
var statComponent = player.GetComponent<StatComponent>();
|
||||
if (statComponent == null) return;
|
||||
|
||||
AttackStat = statComponent.GetStat(StatType.Attack);
|
||||
_attackStatComponent = statComponent;
|
||||
_attackStatCallback = (modifier, isApply) =>
|
||||
statComponent.UpdateStat(AttackStat, modifier, isApply);
|
||||
statComponent.Subscribe(StatType.Attack, _attackStatCallback);
|
||||
_ownerAttributeProvider = statComponent.Pipeline;
|
||||
}
|
||||
|
||||
protected void ReleaseAttackStatSubscription()
|
||||
{
|
||||
if (_attackStatComponent != null && _attackStatCallback != null)
|
||||
{
|
||||
_attackStatComponent.Unsubscribe(StatType.Attack, _attackStatCallback);
|
||||
}
|
||||
|
||||
_attackStatComponent = null;
|
||||
_attackStatCallback = null;
|
||||
AttackStat = new StatProperty();
|
||||
_ownerAttributeProvider = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace SepCore.Entity
|
||||
|
||||
Vector3 fireOrigin = CachedTransform.TransformPoint(_fireOriginOffset);
|
||||
Vector3 directionToTarget = targetPosition - fireOrigin;
|
||||
float maxDistance = WeaponData.AttackRange;
|
||||
float maxDistance = CurrentAttackRange;
|
||||
if (directionToTarget.sqrMagnitude > Mathf.Epsilon &&
|
||||
Physics.Raycast(fireOrigin, directionToTarget.normalized, out RaycastHit hit, maxDistance, _hitMask,
|
||||
QueryTriggerInteraction.Collide))
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ namespace SepCore.Entity
|
||||
|
||||
public override void OnEnter()
|
||||
{
|
||||
_weapon.ResetAttackCooldown(_weapon.WeaponData.Cooldown);
|
||||
_weapon.ResetAttackCooldown(_weapon.CurrentCooldown);
|
||||
_weapon.Attack();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace SepCore.Entity
|
||||
_attackCenter = _target != null ? _target.CachedTransform.position : CachedTransform.position;
|
||||
CachedTransform.SetParent(null);
|
||||
|
||||
Vector3 targetPos = CachedTransform.position + CachedTransform.forward * WeaponData.AttackRange;
|
||||
Vector3 targetPos = CachedTransform.position + CachedTransform.forward * CurrentAttackRange;
|
||||
_attackSequence = DOTween.Sequence();
|
||||
_attackSequence.Append(CachedTransform.DOMove(targetPos, _attackDuration).SetEase(Ease.OutQuad));
|
||||
_attackSequence.AppendCallback(() =>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ namespace SepCore.Entity
|
||||
|
||||
public override void OnEnter()
|
||||
{
|
||||
_weapon.ResetAttackCooldown(_weapon.WeaponData.Cooldown);
|
||||
_weapon.ResetAttackCooldown(_weapon.CurrentCooldown);
|
||||
_weapon.Attack();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ namespace SepCore.Entity
|
||||
|
||||
public override void OnEnter()
|
||||
{
|
||||
_weapon.ResetAttackCooldown(_weapon.WeaponData.Cooldown);
|
||||
_weapon.ResetAttackCooldown(_weapon.CurrentCooldown);
|
||||
_weapon.Attack();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ namespace SepCore.Entity
|
||||
|
||||
public override void OnEnter()
|
||||
{
|
||||
_weapon.ResetAttackCooldown(_weapon.WeaponData.Cooldown);
|
||||
_weapon.ResetAttackCooldown(_weapon.CurrentCooldown);
|
||||
_weapon.Attack();
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace SepCore.Entity
|
||||
CachedTransform.DORotateQuaternion(endRotation, _attackDuration).SetEase(Ease.OutSine));
|
||||
_attackSequence.InsertCallback(_attackDuration * 0.5f, () =>
|
||||
{
|
||||
_attackEffect.Play(this, _attackCenter, _target, WeaponData.AttackRange);
|
||||
_attackEffect.Play(this, _attackCenter, _target, CurrentAttackRange);
|
||||
ApplySectorDamage();
|
||||
});
|
||||
_attackSequence.Append(CachedTransform.DORotateQuaternion(_cachedAttackRotation, _returnDuration)
|
||||
@@ -71,7 +71,7 @@ namespace SepCore.Entity
|
||||
|
||||
private void ApplySectorDamage()
|
||||
{
|
||||
float attackRadius = WeaponData.AttackRange;
|
||||
float attackRadius = CurrentAttackRange;
|
||||
if (attackRadius <= 0f) return;
|
||||
|
||||
Vector3 forward = CachedTransform.forward;
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ namespace SepCore.Entity
|
||||
|
||||
public override void OnEnter()
|
||||
{
|
||||
_weapon.ResetAttackCooldown(_weapon.WeaponData.Cooldown);
|
||||
_weapon.ResetAttackCooldown(_weapon.CurrentCooldown);
|
||||
_weapon.Attack();
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace SepCore.UI
|
||||
|
||||
private void ApplyReward(LevelUpRewardConfig reward)
|
||||
{
|
||||
_player.AddProp(new PropItem(reward.Modifiers, reward.Rarity, reward.Title, reward.Sprite));
|
||||
_player.AddProp(new PropItem(reward.StatModifierConfigs, reward.Rarity, reward.Title, reward.Sprite));
|
||||
}
|
||||
|
||||
private void BuildRewardRarityPools()
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace SepCore.UI
|
||||
selectedRoleData = new ShowAreaRawData
|
||||
{
|
||||
Name = selectedRole.Name,
|
||||
InitialProperties = selectedRole.Modifiers
|
||||
InitialProperties = selectedRole.StatModifierConfigs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ namespace SepCore.UI
|
||||
public class ShowAreaRawData
|
||||
{
|
||||
public string Name;
|
||||
public List<modifier> InitialProperties;
|
||||
public List<StatModifierConfig> InitialProperties;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using SepCore.Battle;
|
||||
using SepCore.Definition;
|
||||
using SepCore.Entity;
|
||||
using UnityEngine;
|
||||
@@ -209,10 +210,19 @@ namespace SepCore.CustomUtility
|
||||
return;
|
||||
}
|
||||
|
||||
int entityDamageHP = CalcDamageHP(projectileImpactData.AttackBase, projectileImpactData.AttackStat,
|
||||
entityImpactData.DefenseStat, entityImpactData.DodgeStat);
|
||||
DamageContext context = new DamageContext
|
||||
{
|
||||
BaseDamage = projectileImpactData.AttackBase,
|
||||
DamageScalingStat = projectileImpactData.DamageType.ToScalingStat(),
|
||||
ScalingRatio = projectileImpactData.ScalingRatio,
|
||||
BaseCritChance = projectileImpactData.BaseCritChance,
|
||||
CritMultiplier = projectileImpactData.CritMultiplier,
|
||||
AttackerPipeline = projectileImpactData.AttributeProvider,
|
||||
DefenderPipeline = entityImpactData.AttributeProvider
|
||||
};
|
||||
|
||||
entity.ApplyDamage(enemyProjectile, entityDamageHP);
|
||||
DamageResult result = DamagePipeline.Calculate(in context);
|
||||
entity.ApplyDamage(enemyProjectile, in result, enemyProjectile.CachedTransform.position);
|
||||
enemyProjectile.Expire();
|
||||
return;
|
||||
}
|
||||
@@ -228,43 +238,23 @@ namespace SepCore.CustomUtility
|
||||
return;
|
||||
}
|
||||
|
||||
int entityDamageHP = CalcDamageHP(weaponImpactData.AttackBase, weaponImpactData.AttackStat,
|
||||
entityImpactData.DefenseStat, entityImpactData.DodgeStat);
|
||||
DamageContext context = new DamageContext
|
||||
{
|
||||
BaseDamage = weaponImpactData.AttackBase,
|
||||
DamageScalingStat = weaponImpactData.DamageType.ToScalingStat(),
|
||||
ScalingRatio = weaponImpactData.ScalingRatio,
|
||||
BaseCritChance = weaponImpactData.BaseCritChance,
|
||||
CritMultiplier = weaponImpactData.CritMultiplier,
|
||||
AttackerPipeline = weaponImpactData.AttributeProvider,
|
||||
DefenderPipeline = entityImpactData.AttributeProvider
|
||||
};
|
||||
|
||||
entity.ApplyDamage(weapon, entityDamageHP);
|
||||
DamageResult result = DamagePipeline.Calculate(in context);
|
||||
entity.ApplyDamage(weapon, in result, entity.CachedTransform.position);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static int CalcDamageHP(int attack, StatProperty attackStat, StatProperty defenseStat,
|
||||
StatProperty dodgeStat)
|
||||
{
|
||||
// 1. 处理闪避(从全局表读取闪避率上限)
|
||||
float maxDodge = GameEntry.Luban.Global.MaxDodgeRate / 1000f;
|
||||
if (dodgeStat != null)
|
||||
{
|
||||
if (Random.value < Mathf.Clamp(dodgeStat.Value, 0f, maxDodge)) return 0;
|
||||
}
|
||||
|
||||
// 2. 处理攻击加成 最终伤害 = (基础伤害 + 伤害提升固定值) * 伤害提升率
|
||||
float damage = attack;
|
||||
if (attackStat != null)
|
||||
{
|
||||
damage = (attack + attackStat.Value) * attackStat.Percent;
|
||||
}
|
||||
|
||||
// 3. 处理防御加成 最终伤害 = (基础伤害 - 防御提升固定值) / 防御提升率
|
||||
if (defenseStat != null)
|
||||
{
|
||||
damage = (damage - defenseStat.Value) / defenseStat.Percent;
|
||||
}
|
||||
|
||||
// 4. 将伤害限制为保底伤害,避免堆防御导致无法造成伤害
|
||||
int minDamage = GameEntry.Luban.Global.MinDamage;
|
||||
if (damage < minDamage) return minDamage;
|
||||
return Mathf.CeilToInt(damage);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private struct CampPair
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user