buff系统

2020/03/09 Mmo-Game

战斗系统重构后-buff系统学习记录

目录

我们项目技能系统的一些概念

一.普攻:
    1.可以点好几下,每一下都是一个子技能(也就是技能段)。
    2.一个子技能有多个打击点,也就是点一下普攻,当前子技能有多个动作,
      每个动作都可能做索敌判断。
    3.蓄力技能(需要长按才能释放),例如:玄冥教普攻,先点击一下普攻按钮,客户端上发
      601,这事长按普攻按钮一定时间,客户端上发609蓄力普攻技能(好几个打击点)。
      接着再按下普攻,客户端上发610蓄力派生技能。

二.派发技能效果:
   效果属性:时长、增益、减益,配置是否存库(有些buff存库,例如:30分钟内增加攻击力)
   效果参数:每个效果参数不同
   效果逻辑:逻辑代码(开放可视化重复利用云图编辑)

三.技能效果类:
   AI实现
   瞬发
   持续
   位移
   带条件
   ...

表分析

1.由于之前的buff表列数过多,将buff表拆分

1.buff基础表SkillImpactBase 主要是条件,类似如下:

1.死后是否保留该效果(0否、1是)

2.是否增益效果
    0:其他
    1:增益类
    2:负面类

3.跨场景是否消失(0否、1是)

4.下线是否消失(0否、1是)

5.开始移动时是否消失(0否、1是)

各种一大堆条件

2.SkillImpactParam参数表,由于特定效果的参数列非常多,故单独拆分出一张表:

状态影响效果产生的几率也是配在这里。

向目标发送一个效果,主要用于非技能模块的效果发送

1.计算派发概率

1.无成长

 // 是否是无成长的效果
DictSkillImpactParamData dictSkillImpactParamData = impactToNoGrowthImpactMap.get(impactId);
if(dictSkillImpactParamData != null)
{
    return dictSkillImpactParamData.getAddImpactOdds();
}
1.无成长的,直接表里配置概率(根据状态state是否大于0判断),效果产生几率SkillImpactBase表里配置参数名为addImpactOdds

2.所有的非技能模块都是无成长的,例如:
   药品使用加buff
   任何奖励
   称号

3.注意目前陷阱派发走的是技能派发。

2.有成长

TLongIntHashMap stateLevelProbablityMap = impactOnStateLevelToProbablityMap.get(impactId);
if(CollectionUtils.isBlank(stateLevelProbablityMap))
{
    return 0;
}

int noEntryValue = stateLevelProbablityMap.getNoEntryValue();

boolean hasState = false;
int rs = 0;

// 此处遍历非常非常频繁,所以不用迭代器,掏出来map的内部数组进行遍历
byte[] _state = stateLevelMap._states;
int[] _set = stateLevelMap._set;

int size = stateLevelMap.size();

for (int i = 0, iSize = _state.length; i < iSize; i++)
{
    ...
}

效果ID -> 技能状态 -> 状态等级 -> 概率

技能状态和状态等级会影响派发概率。

这里遍历非常频繁,所以不用迭代器遍历,直接掏出数组进行遍历。

3.属性修正概率

// 实际概率=表中对目标概率+攻击者对应ID命中属性-被攻击者对应ID抵抗属性
targetProb = targetProb + enchanceValue - declineValue;

SkillImpactBase表里会配置,属性修正,例如:派生中毒添加 217号属性会影响概率命中

4.随机概率

 // 随机概率
if(targetProb < GameConstant.TEN_THOUSAND && RandomUtil.randomInt(GameConstant.TEN_THOUSAND) > targetProb)
{
    return BPErrorCodeEnum.SKILL_IMPACT_PROBABLITY_FAIL;
}

如果当前概率小于10000,并且从0~10000随机取一个整数值 大于 当前概率, 那么属性派发概率失败。

这里有疑问,像任务这种其它系统派发buff, 那表里配置的概率都是超过10000吗?不然的话,有几率buff派发不上?

一般这种其它模块buff,没有特定需求需要概率派发,数值配置的概率都会大于10000,保证能派发上。

相当于,不同模块派发的buff概率类型会不同。

2.判断能否施加效果

1.普通判断

1.能否作用到死亡玩家身上

2.判断buff生效目标(玩家,npc,宠物,镜像,傀儡…)

3.判断目标细分类型

ALL(0,true,true),               //  所有               (友好 √   敌对 √ )
SELF(1,true,false),             //  仅自己             (友好 √   敌对 × )
ENEMY(2,false,true),            //  敌对方             (友好 ×   敌对 √ )
FRIENDLY_NO_ME(3,true,false),   //  友方(不包括自己)    (友好 √   敌对 × )
FRIENDLY(4,true,false),         //  友方(包括自己)      (友好 √   敌对 × )

2.大招受创绑定,不能上效果

1.代表受创的效果类型ID集合(指策划认为的播放受创动画),配置在GameCofig表里的beHitImpactTypeID列

int[] beHitImpactTypeIDs = getBeHitImpactTypeID();
if(!CollectionUtils.isBlank(beHitImpactTypeIDs))
{
    for (int i = 0, iSize = beHitImpactTypeIDs.length; i < iSize; i++)
    {
        //index为表里配置的逻辑id索引,例如24&&30&&32&&33&&34&&35&&48
        int index = beHitImpactTypeIDs[i];
        if(index >= 0)
        {
            beHitImpactTypeSet[index] = true;
        }
    }
}

2.SkillImpactBase配置逻辑id参数名为logicID

3.如果当前处于大招绑定受创,那么不能上效果

// 大招受创绑定
int beBeatLockBindSender = getBeBeatLockBindSender();
if(beBeatLockBindSender > 0)
{
    if(sender.getObjectID() != beBeatLockBindSender)
    {
        boolean flag = DictGameConfigData.isBeHitImpactType(impactData.getLogicID());
        if(flag)
        {
            return BPErrorCodeEnum.SKILL_CANNOT_IMPCAT_BE_HIT_BIND;
        }
    }
}

3.击退转换

1.如果SkillImpactBase表配置的是击退效果,那么SkillImpactParam不能配置成长。

// 击退转换效果,目前写死
if(dictSkillImpactBase.getLogicID() == 24)
{
    if(!paramNoGrowthImpactSet.contains(dictSkillImpactBase.getId()))
    {
        checkOk &= false;
        BPLog.BP_LOGIC.error("【技能表加载错误】SkillImpactParam加载错误,SkillImpactBase表中配置的是击退效果,但是在SkillImpactParam出现了成长! [param-id] {} [impactID] {}",dic.getId(),impactId);
        continue;
    }

    int transferID = dic.impactParams[10];
    if (transferID > 0)
    {

        beatMoveTransferMap.put(dic.getId(), transferID);
    }
}

如果SkillImpactBase表配置了逻辑id为24,需要将当前的效果转为对应的转换效果id(击退转换击飞)。

4.buff类的基本设计

1.抽象的技能效果基类AbstractSkillImpact

1.抽象类实现接口

/**
 * 技能的效果抽象基类
 * Created by wangqiang on 2017/7/19.
 */
public abstract class AbstractSkillImpact implements Reusable
{

}

效果抽象基类,实现Reusable接口,接口里有清除方法

2.主要的类成员变量,一些buff公用的特性提到抽象层面

/**
* 派发的技能相关信息
*/
protected SkillEffectInfo skillEffectInfo;

/**
* 技能效果配置
*/
protected SkillImpactConfigData configData;

/**
* 效果发送者的objectID
*/
private int senderObjectID;

/**
* 是否是自己发送给自己的
*/
private boolean isSelfSend;

/**
* 效果逻辑剩余时间
*/
protected long remainTime;

/**
* 效果总持续时间
*/
protected long remainTimeAll;

省略------------------

2.给属性归类,各属性再抽出一层抽象

1.改变属性buff的基类

/**
 * 属性改变类BUFF的基类
 * Created by Wang Lizhi on 2018/4/10.
 */
public abstract class AbstractSkillImpactAttrBase extends AbstractSkillImpact
  1. 受击位移效果的基类
/**
 * 受击位移效果的基类
 * Created by Wang Lizhi on 2018/12/6.
 */
public abstract class AbstractSkillImpactBeatMoveBase extends AbstractSkillImpact

3.强控类效果逻辑 基类(持续性buff)

/**
 * 强控类效果逻辑 基类(持续性buff)
 * Created by Wang Lizhi on 2017/8/18.
 */
public abstract class AbstractSkillImpactControlBase extends AbstractSkillImpact

主要是给相近的属性类型再抽出一层。

5.创建效果实例

1.初始化技能效果缓存

1.AbstractScene场景基类下,init()方法

sceneCache.initSkillCache(dictSceneDefine);

2.初始化缓存的基数为,指定长的对象队列长度:

1. 每条线最大承载人数 + 20 缓存基数

2. 如果缓存基数 >= 10, 那么去1 /2 

3. 如果缓存基数小于1,那么缓存基数等于1

3.根据buff逻辑id,每个逻辑new一个ArrayDeque队列,加入缓存

ObjectQueue objectQueue = new ObjectQueue(baseNumber, new CreateImpact000());
skillImpactCache.add(objectQueue);

objectQueue = new ObjectQueue(baseNumber, new CreateImpact001());
skillImpactCache.add(objectQueue);

2.定长的队列设计

/**
 * 定长的对象队列,设计初衷是为了技能逻辑.技能效果的对象缓存
 * 如果poll的时候池内为空,增新new一个,如果add的时候池已满
 * 则直接丢弃,等待垃圾回收
 * 非线程安全
 */
public class ObjectQueue <T extends Reusable>
{
    private ArrayDeque<T> queue;

    /**
     * 当前长度
     */
    private int size;

    /**
     * 总容量
     */
    private int capacity;

    /**
     * 对象实例化的方法
     */
    private CreateTemplateInstance<T> function;


    /**
     * 构造函数初始化,容量和模板
     * @param capacity
     * @param func 所有实例都需要实现该接口,buff效果实例也需要实现该接口
     */
    public ObjectQueue(int capacity, CreateTemplateInstance func)
    {
        queue = new ArrayDeque<>(capacity);
        size = 0;
        this.capacity = capacity;
        function = func;
    }

    public ObjectQueue(T[] t)
    {
        this.capacity = t.length;
        queue = new ArrayDeque<>(capacity);
        size = 0;
        for (int i = 0; i < capacity; i++)
        {
            size++;
            queue.addLast(t[i]);
        }
    }

    /**
     * 如果队列已经为空则新new一个对象,否则弹出,队列长度减1
     * @return
     */
    public T poll()
    {
        if (size == 0)
        {
            if (function == null)
            {
                return null;
            }
            else
            {
                //队列为空时则新new一个对象
                return function.createTemplateInstance();
            }
        }

        size--;
        return queue.poll();
    }

    /**
     * T extends Reusable
     *
     * 添加进来的对象T实现了Reusable接口
     *
     * T会统一调用clear()方法,各个对象在重新加入对象池时,
     *
     * 自己覆写clear()方法,先进行清除操作,再添加进来
     *
     * 队列长度增加
     * @param object
     */
    public void add(T object)
    {
        if (object == null)
        {
            return;
        }

        if (size < capacity)
        {
            //调用clear()方法,各个对象自己覆写方法
            object.clear();
            queue.addLast(object);
            size++;
        }
    }

}
  1. 定长的对象队列,设计初衷是为了技能逻辑.技能效果的对象缓存,如果poll的时候池内为空,增新new一个,如果add的时候池已满

则直接丢弃,等待垃圾回收,非线程安全。

2.添加进来时需要先清除数据。

3.根据效果逻辑id去池里拿技能效果实例

// 创建效果实例
AbstractSkillImpact skillImpact = SkillHelper.createSkillImpact(impactData.getLogicID(), sender.getScene());
if (skillImpact == null)
{
    return BPErrorCodeEnum.SKILL_SKILL_IMPACT_ID_IS_NOT_EXISTED;
}

根据逻辑id,从效果池里拿出实例。

逻辑id可以理解为buff类型,是从0开始的,逻辑上抽象。

4.初始化技能效果

/**
* 根据 效果状态等级 获取 效果参数集合
* @param impactId
* @param stateLevelMap 派发该技能效果的技能的状态和等级 如果带成长的效果吧不为空或者null
* @param paramDataList 所有的技能效果的参数配置
*/
public static void getImpactParamsOnStateLevel(int impactId, TIntIntHashMap stateLevelMap, ArrayList<DictSkillImpactParamData> paramDataList)

当前效果id,有状态和状态等级,根据传进来的stateLevelMap,计算一些初始化数据,例如:当前效果持续总时长,是从低状态对应的时长,一直累加到当前状态等级对应的持续时长,

为什么需要从头开始累加?每次都计算?而不是当前状态等级对应当前持续时长?

主要初始化一些:

/**
* 技能效果的参数配置  (index为状态和状态等级处理后的索引)
*/
private ArrayList<DictSkillImpactParamData> impactParamDataArrayList;

初始化做大操作,效果id对应的状态和状态等级所有行,例如:效果id为1010,对应状态id为1,状态有1~30等级,所有行数据。

/**
* 总时长(对怪)
*/
private int durationTime;

/**
* 总时长(对玩家)
*/
private int durationTimeSpecial;

/**
* 互斥等级
*/
private int mutexLevel;

/**
* 叠加等级
*/
private int repeatLevel;

总时长,逻辑id对应所有参数配置,对怪的持续时长累加。总时长(对玩家),类似。

mutexLevel总buff互斥等级,buff互斥等级高的会覆盖低的等级。

repeatLevel状态叠加评级,作为BUFF是否能够叠加的判断,当同ID的impact所有状态之和相等时,BUFF能够叠加。

/**
* 驱散等级
*/
private int buffLevel;

buffLevel 驱散效果带有驱散等级,低驱散等级的效果无法驱散高状态等级的BUFF

 // 计算次数
this.totalEffectCount = 1;
if (impactBaseData.getImpactType() == SkillImpactTypeEnum.PERSISTENT.getValue())
{
    int durationTime = this.durationTime;
    int intervalTime = impactBaseData.getIntervalTime();
    this.totalEffectCount = durationTime / intervalTime;
    this.totalEffectCount = (this.totalEffectCount <= 0 ? 1 : this.totalEffectCount);
}

效果的总生效次数

/**
* 效果实际总持续时间
*/
protected long remainTimeAll;

判断下如果接受者不是玩具那么取durationTime,如果是玩家durationTimeSpecial

/**
* 激活的剩余延迟时间
*/
protected int delayRemainTime;

效果的首次生效延迟生效时间(毫秒)

5.如果是技能派发效果初始化操作

 设置技能打击点信息技能被动主动信息,霸体信息

// 技能段的伤害系数、技能段扣除的霸体值,从这里设置进效果
if(impactData.getLogicID() == 0)
{
    if(skillImpact instanceof SkillImpact000)
    {
        SkillImpact000 skillImpact000 = (SkillImpact000)skillImpact;

        skillImpact000.setMinusEndure(senderCurrentSkillLogic.getEndureMinus());

        ////通过 子技能ID 和 段数获取 数据
        DictSkillDataAssitData skillDataAssitData = DictSkillDataAssitData.getRecordBySubSkillAndEffect(subSkillID, senderCurrentSkillLogic.getTakeEffectCount());
        if(skillDataAssitData != null)
        {
            int damageImpact = skillDataAssitData.getDamageImpact();
            int damageRatio = skillDataAssitData.getDamageRatio();
            if(damageImpact > 0 && damageRatio > 0 && damageImpact == impactData.getId())
            {
                skillImpact000.setExternalRatio(damageRatio);
            }
        }
    }
    else
    {
        BPLog.BP_FIGHT.warn("【技能效果错误】技能效果logicID为0,但是技能效果类不是 SkillImpact000 [impactId] {}",impactData.getId());
    }
}

skillImpact.setHitIndex(hitIndex);
skillImpact.setEndure(isEndure);

6.补充一下,什么是段数?什么是打击点?

1.段数概念

可以简单理解为,一个技能可以连续点击多少下,例如:技能轮盘里,有些技能是可以连续点击多下,通常这种技能界面展示会与其它没有断数的技能区分出来

类似上图,单段的是一个圆圈,多断可以看到一个圆圈切分好几段代表可以连续点击四次。

那为什么设计游戏多段攻击和单段攻击?

单次伤害怕格挡,多次不怕。

单次伤害爆发高 伤害来的快。

多段伤害有一个好处就是在游戏中后期不会太怕格挡这个技能。
2.打击点概念

简单理解为,点一下技能,改技能可以释放多少下。

6.派发逻辑

/**
* 接收一个impact效果
*
* @param skillImpact
* @return
*/
public int receivedImpact(AbstractSkillImpact skillImpact)

1.收到不良效果,触发攻击事件

例如:npc受到攻击做一些状态的切换

2.判断能否上检测

受创(指播放动画被攻击的画面),绝学,轻功…不能上检测。

3.效果反弹处理

// 自己给自己上的,不反弹,不然也会出现死循环
if(skillImpact.getSenderObjectID() == character.getObjectID())
{
    return false;
}

不能反弹给自己,不然,自己再反弹自己,出现死循环。

// 处理反弹事件
List<AbstractSkillImpact> listenReboundImpacts = getEventListenBuffsByEvent(BuffListenEventEnum.EVENT_TYPE_IMPACT_REBOUND.getType());
if (!CollectionUtils.isBlank(listenReboundImpacts))
{
    for(int i = 0 , size =  listenReboundImpacts.size(); i < size; i++)
    {
        int flag = listenReboundImpacts.get(i).handleEvent(BuffListenEventEnum.EVENT_TYPE_IMPACT_REBOUND,character, 0,0,0,0,0,0,0,0,skillImpact,null);

        if (flag < 0)
        {
            return true;
        }
    }
}

传入反弹监听类型,迭代当前所有的反弹buff监听列表,判断是否需要处理反弹事件。

 * <pre>
 *     39号效果逻辑(持续性BUFF)
 *
 * 描述:
 *
 *      反弹状态
 *
 * 功能说明:
 *
 *      给目标加一个持续性的监听事件的效果,效果会根据弹射参数,将受到的效果进行反弹给周围符合条件的人或者发送者。
 *      (遇到反伤,返还给最初施法者。也就是说反弹出去的效果的发送者都是自己)
 *
 * 参数说明:
 *      参数1: 反弹效果类型 : 1.正面效果 2.负面效果 3.全部效果
 *      参数2: 反弹方式 : 1.反弹给施法者 2.周围敌方 3.周围友方 4.周围所有目标

39号效果,是处理反弹的

4.玩家自身是否有对该效果的抵抗

// 玩家自身是否有对该效果的抵抗
boolean resistFlag = dealImpactResist(skillImpact);
if(resistFlag)
{
    //玩家对技能效果有抵抗,技能效果不被施加
    return BPErrorCodeEnum.SKILL_SELF_HAS_RESIST_IMPACT;
}

如果玩家处于39号霸体,或50号状态免疫效果…, 当前要上的效果驱散等级不满足,那么效果无法派发,被免疫。

/**
* 处理效果抵抗
* @param skillImpact
* @return
*/
private boolean dealImpactResist(AbstractSkillImpact skillImpact)
{
    List<AbstractSkillImpact> listenResistImpacts = getEventListenBuffsByEvent(BuffListenEventEnum.EVENT_TYPE_IS_SHOULD_RESIST_IMPACT.getType());
    if (!CollectionUtils.isBlank(listenResistImpacts))
    {
        for(int i = 0 , size =  listenResistImpacts.size(); i < size; i++)
        {
            SkillImpactConfigData configData = skillImpact.getConfigData();
            DictSkillImpactBaseData impactBaseData = configData.getImpactBaseData();
            //传入BUFF组ID(关联IDCollection表),  驱散等级
            boolean resistFlag = listenResistImpacts.get(i).isResistImpact(impactBaseData.getGroupID(), configData.getBuffLevel());

            if (resistFlag)
            {
                return true;
            }
        }
    }
    return false;
}

同样根据类型获取监听列表,传入BUFF组ID(关联IDCollection表), 驱散等级。

这里我们只看38号效果逻辑,其它同类型效果类似。

 *     38号效果逻辑(持续性BUFF)
 *
 * 描述:
 *
 *       霸体效果
 *
 * 功能说明:
 *
 *      霸体效果是将身上一系列的不良技能效果移除,并且一段时间免疫这些不良效果,不能加到身上来
 *
 * 参数说明:
 *      参数1: 霸体等级
 *      参数2: 驱散免疫效果集合1
 *      参数3: 驱散免疫效果集合2
 *      参数4: 驱散免疫效果集合3
 *      参数5: 驱散免疫效果集合4
 @Override
public boolean startImpactImpl(AbstractCharacter character)
{
    int[] params = configData.getImpactParams();
    level = params[0];
    int levelStep = configData.getImpactSteps()[0];

    // 等级
    if (level < 0)
    {
        BPLog.BP_FIGHT.error("【技能效果038派发错误】 参数 效果等级错误 [参数值] {} [impactId] {}", level, configData.getImpactBaseData().getId());
        return false;
    }
    //注册可以作用的等级
    level += getActualSkillLevel() * (levelStep / GameConstant.TEN_THOUSAND);

    // 注册可以集合
    for (int i = 1; i < 15; i++)
    {
        int idCollectionParam = params[i];
        if (idCollectionParam > 0)
        {
            set.add(idCollectionParam);
        }
    }

    if (CollectionUtils.isBlank(set))
    {
        BPLog.BP_FIGHT.error("【技能效果038派发错误】 参数 驱散集合Id都没有配置  [impactId] {}", configData.getImpactBaseData().getId());
        return false;
    }

    // 清除逻辑
    ArrayList<AbstractSkillImpact> buffList = character.getSkillModule().getBuffList();
    if (CollectionUtils.isBlank(buffList))
    {
        return true;
    }

    for (int i = 0, size = buffList.size(); i < size; i++)
    {
        AbstractSkillImpact buff = buffList.get(i);

        if(!isImpactSuit(buff,level))
        {
            continue;
        }

        toCleanList.add(buff);

    }

    if (!CollectionUtils.isBlank(toCleanList))
    {
        for (int i = 0, size = toCleanList.size(); i < size; i++)
        {
            toCleanList.get(i).breakImpactOnlyForCannotDispel(character);
        }
        toCleanList.clear();
    }

    return true;
}

注册可以作用的等级,注册可以清除霸体的集合。

这里作用等级计算暂时还没搞明白,为什么这么计算?

level += getActualSkillLevel() * (levelStep / GameConstant.TEN_THOUSAND);

@Override
public boolean isResistImpact(int impactGroupId, int impactLevel)
{
    if(super.isResistImpact(impactGroupId, impactLevel))
    {
        return true;
    }

    // 先判断等级能减少计算量

    //如果传进来要上的buff传入的驱散等级,大于霸体驱散等级,
    //霸体的抵抗效果失效,该效果可以派发,返回false
    if(impactLevel > level)
    {
        return false;
    }

    //如果当前要派发的效果,刚好是属于可以清除霸体集合里的buff,那么霸体的抵抗效果失效,返回false
    boolean hasInCollection = false;
    for(TIntIterator iterator = set.iterator(); iterator.hasNext();)
    {
        //技能组ID是否属于某集合ID下
        boolean flag = DictIDCollectionsData.isImpactGroupIdInIdCollection(iterator.next(),impactGroupId);
        if(flag)
        {
            hasInCollection = true;
        }
    }

    if (!hasInCollection)
    {
        return false;
    }

    return true;
}

38号霸体默认是对当前要派发的buff有抵抗,默认返回true。

如果传进来要上的buff传入的驱散等级或者叫作用等级,大于霸体作用等级,霸体的抵抗效果失效,该效果可以派发,返回false。

如果当前要派发的效果,刚好是属于可以清除霸体集合里的buff,那么霸体的抵抗效果失效,返回false。

4.处理BUFF转移

5.处理BUFF 状态互斥,主要应用于控制状态类buff

* 处理效果控制状态互斥
* <p>
* 方法内部对控制状态的互斥关系进行了处理: 互斥(不能上) / 共存(可以上) / 顶替(顶旧BUFF)
* </p>
*

状态互斥技能不会是瞬发技能

6.往buff列表中增加一个效果

* 往buff列表中增加一个效果
* ( 添加到waittingBuffList中下一帧tickBuff列表前会被增加到buff列表中)
 private int addSkillImpact(AbstractSkillImpact skillImpact)
 {
     ...
    if (buffSize + addOnForeachSize >= SkillCommonConstant.BUFF_LIST_MAX_SIZE)
    {
        // 当技能效果过大时,可能是连续多次升级导致的被动效果高级别顶低级别导致的,可以加个过滤finish状态效果的容忍
        for (int i = 0,iSize = buffList.size();i < iSize; i++)
        {
            AbstractSkillImpact impact = buffList.get(i);
            if(impact == null)
            {
                continue;
            }
            if(impact.isFinished())
            {
                buffSize--;
                continue;
            }
        }

        if(buffSize + addOnForeachSize >= SkillCommonConstant.BUFF_LIST_MAX_SIZE)
        {
            return BPErrorCodeEnum.SKILL_IMPACT_TARGET_BUFF_SIZE_MAX;
        }
    }

    // 效果加到身上
    int rs = 0;
    if (buffForeachFlag)
    {
        // 此时正在遍历BUFF集合,先加入BUFF添加缓冲队列
        addOnForeachBuffList.add(skillImpact);
    }
    else
    {
        rs = doAddSkillImpact(skillImpact);

    }

 }

判断效果列表是否超出上限,当前如果正在tick buff,那么添加到临时列表,下一帧合并到buff列表。

一般不会出现这种情况,预防跨线程派发buff?

添加到buff列表,添加buff事件。

到此派发逻辑就结束了。

7.ticK buff生效

1.先清一遍结束的效果

1.先清一遍结束的效果,然后执行onFinish方法,防止buff顶替的时候,新BUFF先tick,旧BUFF再执行删除导致的执行顺序错误

结束效果时要做的事:

1.解绑BUFF事件监听

2.各個BUFF结束该做的事,只有BUFF生效才会调用(每个效果执行完的回调)

3.持续性需要下掉buff

4.从buff列表中移除
buffList.remove(i);

5.放入技能池回收
scene.getSceneCache().recycleSkillImpact(impact.getConfigData().getImpactBaseData().getLogicID(), impact);
2.效果tick
1.注册事件监听
character.getSkillModule().registeToEventListenMap(this);

2.发送客户端

3.效果正式激活前各自需要做的事

4.效果触发的tick逻辑
tickTrigger(character, interval):
    1.瞬发效果
    立即调用toStartImpact()生效

    2.持续性效果
    是否是继续BUFF效果,可间隔作用BUFF

    3.处理叠加(例如:持续性掉血)

5.检查是否结束
3.举个例子2号治疗效果逻辑(瞬发 + 持续性)
/**
* 效果开始生效
*
* @param character
*/
private void toStartImpact(AbstractCharacter character)
{
    // 自己如果在使用绝学过程中,身上的所有impact都不生效
    if (!character.isUsingJueXueStatus())
    {
        boolean flag = startImpact(character);

        // 作用成功标志默认false,作用成功一次就算成功
        this.impactEffective |= flag;
    }

    // 查找下一个效果需要派发的目标,只针对单体技能
    // 默认为不实现
    findSingleTarget();
}

每个效果调用各自的stratImpact,效果生效。

@Override
public boolean startImpactImpl(AbstractCharacter character)
{
    ...
     // 固定治疗值
    int fixedValue = impactParams[0];
    int fixedValueStep = impactSteps[0];
    int realFixedValue = (int) (getActualSkillLevel() * (fixedValueStep / GameConstant.TEN_THOUSAND_DOUBLE) + fixedValue);

    // 附加攻击者魔法攻击(内功)的万分比治疗量
    int magicAttackPercent = impactParams[1];
    int magicAttackPercentStep = impactSteps[1];
    int realmagicAttackPercent = magicAttackPercent + magicAttackPercentStep * getActualSkillLevel();

     // 附加攻击者血上限的万分比治疗量
    int senderPercent =impactParams[2];
    int senderPercentStep = impactSteps[2];
    int realSenderPercent = senderPercent + senderPercentStep * getActualSkillLevel();


    // 附加受击者血上限的万分比治疗量
    int selfPercent =impactParams[3];
    int selfPercentStep = impactSteps[3];
    int realSelfPercent = selfPercent + selfPercentStep * getActualSkillLevel();

    // 从公式获取加血值
    int hpAddtion = FormulaFacade.countHealValue(sender, character, realFixedValue, realSenderPercent, realmagicAttackPercent,realSelfPercent);

    // 加血
    character.getAttributeLogic().addOneAttribute(AttributeTypeEnum.HP, hpAddtion);

    // 发送加血通知
    doHealing(character, SkillCommonConstant.HEAL_TYPE_HP, hpAddtion, getSubSkillId(),getSkillSign(), configData.getImpactBaseData().getId());
}

加血操作

8.战斗计算公式类设计

1.战斗公式计算基础类

/**
 * 战斗公式计算基础类
 */
public abstract class AbstractFormulaBase

2.子类

1.会心

/**
 * 会心战斗公式
 */
public class FormulaCrit extends AbstractFormulaBase

2.元素类

/**
 * 元素类战斗公式
 */
public class FormulaElement extends AbstractFormulaBase

3.治疗类

public class FormulaHeal extends AbstractFormulaBase

3.命中类

public class FormulaHit extends AbstractFormulaBase

省略。。。

Search

    Table of Contents