战斗系统设计
目录
- 类的继承关系
- 技能和其它模块最相关是buff的派发,所以我们先看buff派发。
- 技能系统图示
- 技能释放流程
- 技能学习流程
类的继承关系
/**
* 抽象的模块基类,挂在character上的模块都需要继承此模块
*/
public abstract class AbstractCharacterModule implements ModuleInteface
{
...
protected AbstractCharacter character;
public AbstractCharacter getCharacter()
{
return character;
}
...
}
挂在character上的模块都需要继承AbstractCharacterModule此模块,因为该模块存放着AbstractCharacter的引用,
我们通过getCharacter.getXXMoudle就可以拿到对应需要的挂在AbstractCharacter下的模块。
所以AbstractCharacterModule相当于一个中转站。
/**
* 技能模块基类
*/
public abstract class AbstractSkillModule extends AbstractCharacterModule
技能抽象模块基类需要继承AbstractCharacterModule,以便拿到对应需要的挂在AbstractCharacter下的模块。
/**
* 生物类的技能模块,是技能系统的基础,有逻辑也有数据
*/
public class CharacterSkillModule extends AbstractSkillModule
CharacterSkillModule继承技能模块基类,并且CharacterSkillModule需要挂在AbstractCharacter生物特性基类。
/**
* 生物基类 玩家、npc、宠物都继承与此基类 Created by wangqiang on 2017/7/6.
*/
public abstract class AbstractCharacter extends BPObject
{
/**
* 技能模块
*/
protected CharacterSkillModule skillModule = new CharacterSkillModule(this);
}
CharacterSkillModule技能模块需要怪在生物基类下,因为不只玩家有技能,npc、侠客、宠物…都会有技能。
玩家,npc、侠客、宠物…都是生物,所以继承自生物特性基类AbstractCharacter。
技能和其它模块最相关是buff的派发,所以我们先看buff派发。
任务派发buff,场景事件派发buff,各种道具派发buff…
1.向目标发送一个效果,主要用于非技能模块的效果发送
/**
* 向目标发送一个效果,主要用于技能模块的效果发送
*
* @param sender 效果的发送者
* @param receiver 效果接受者
* @param impactData 效果配置
* @param skillId 技能Id( * 没有填-1) ,可使用 {@link SkillCommonConstant#NO_SKILL_ID} 常量
* @param skillSign 技能标识 ( * 没有填 0),可使用 {@link SkillCommonConstant#NO_SKILL_SIGN} 常量
* @param skillLevel 技能等级 ( * 没有填 0),可使用 {@link SkillCommonConstant#NO_SKILL_LEVEL} 常量
* @param actualSkillLevel 效果步长,( * 没有填 0),可使用 {@link SkillCommonConstant#NO_SKILL_ACTUAL_LEVEL} 常量
*/
public int sendImpactToForSkill(AbstractCharacter sender, AbstractCharacter receiver, DictStandardImpactData impactData, int skillId,int skillSign, int skillLevel,int actualSkillLevel)
{
if (impactData == null)
{
return BPErrorCodeEnum.SKILL_SKILL_IMPACT_ID_IS_NOT_EXISTED;
}
return doSendImpactTo(sender, receiver, impactData, skillId, skillSign, skillLevel, actualSkillLevel);
}
向目标发送一个效果,主要用于技能模块的效果发送接口。
/**
* 向目标效果
*
* @param sender 效果的发送者
* @param receiver 效果接受者
* @param impactData 效果配置
* @param skillId 技能Id( * 没有填-1) ,可使用 {@link SkillCommonConstant#NO_SKILL_ID} 常量
* @param skillSign 技能标识 ( * 没有填 0),可使用 {@link SkillCommonConstant#NO_SKILL_SIGN} 常量
* @param skillLevel 技能等级 ( * 没有填 0),可使用 {@link SkillCommonConstant#NO_SKILL_LEVEL} 常量
* @param actualSkillLevel 效果步长,( * 没有填 0),可使用 {@link SkillCommonConstant#NO_SKILL_ACTUAL_LEVEL} 常量
* @return
*/
private int doSendImpactTo(AbstractCharacter sender, AbstractCharacter receiver, DictStandardImpactData impactData, int skillId,int skillSign, int skillLevel,int actualSkillLevel)
{
...
// 判断能否施加效果
int rs = canImpactTo(sender, receiver, impactData, actualSkillLevel);
...
// 创建效果实例,例如无敌buff配的逻辑id为1,即持续性技能
int impactLogicID = impactData.getLogicID();
AbstractSkillImpact skillImpact = SkillHelper.createSkillImpact(impactLogicID, sender.getScene());
//初始化buff时长,根据StandardImpact表里配置durationTimeSpecial时长
skillImpact.init(sender.getObjectID(), receiver,impactData);
...
//计算buff时长,其它修改引起修正
skillImpact.calcBuffTime(receiver, sender);
...
// 派发逻辑
int receiveRS = receiver.getSkillModule().receivedImpact(skillImpact);
}
列举了些重要的处理,buff时长读表,派发逻辑receivedImpact
/**
* 接收一个impact效果
*
* @param skillImpact
* @return
*/
public int receivedImpact(AbstractSkillImpact skillImpact)
{
...
// 能否接受效果检测
int canRs = canReceiveImpact(skillImpact);
if(canRs < 0)
{
return canRs;
}
...
// 处理互斥
int rs = dealImpactMutexAndRepeat(skillImpact);
if (rs < 0)
{
return rs;
}
...
}
能否接受效果检测,例如:之前上了一个增益类型的buff无敌,那么此时接收一个负面类的buff不通过。
处理互斥,走表配置。
1.非持续性不处理互斥。
2.来源于同一个单位的同一BUFF组的叠加规则:(可叠加的BUFF在层数满之后只刷新BUFF时间)
0:替换,互斥优先级低的无法替换高级的
1:刷新BUFF时间(刷新BUFF时间的效果不会充值周期触发效果的周期循环)
2:添加不影响现有
3:无法添加新BUFF
(前两种情况下,BUFF等级低于目标身上同组BUFF等级的无法生效)
3.不同源BUFF叠加规则:
0:直接替换(低级BUFF不可替换高级的)
1:刷新BUFF时间(同等级BUFF刷新BUFF时间,BUFF来源不变,高级的BUFF覆盖低级的,BUFF来源改为高级BUFF的施加者)
2:各自添加不互相影响
现在无敌buff配的是,互斥组配的是1也就是自身互斥,同源叠加规则配的是0,即替换。
那么假设同时给自身上两个无敌buff,第一个无敌buff直接被第二个无敌buff替换掉。
/**
* 处理BUFF的互斥
* @param mutexType 互斥类型
* @param buff 身上的BUFF
* @param addImpact 要添加的BUFF效果
* @return
*/
private int doMutexImpact(int mutexType,AbstractSkillImpact buff,AbstractSkillImpact addImpact)
{
...
...
else
{
// 中断互斥顶替掉的buff
buff.onBeMutexBreeak(getCharacter(),addImpact);
buff.breakImpact(getCharacter());
return 0;
}
...
...
}
同时给自身上两个无敌buff,可以根据配置规则让第一个无敌buff直接被第二个无敌buff替换掉。
需要先调用breakImpact(AbstractCharacter character)中断第一个buff效果。
然后将新来的无敌buff添加到buff列表里。
技能系统图示
技能系统服务器客户端流程:
技能系统控制流程抽象:
技能释放流程
1.前端使用技能请求
// 使用技能请求
message CSUseSkillRequest{//20300
required int32 skillID = 1; // 技能id
required int32 targetObjectID = 2; // 选择的目标id
required int32 rotation = 3; // 朝向, 传0->360
required int32 sign = 4; // 技能标识
required int32 type = 5; // 类型:0玩家 1宠物 2傀儡 3侠客
optional int32 dummyObjectID = 6; // 傀儡ObjectID(用于傀儡释放技能);当type为3时,该值为侠客的KnightID
repeated int32 paths = 7; //x,y,z坐标结合(位移技能路径)
repeated int32 hitPoints = 8; // x,y,z生效的打击点坐标集合(目前主要给侠客大招使用)
optional bool isStandalone = 9; // 是否是单机释放技能(目前主要给侠客大招使用)
}
2.玩家技能释放
目前版本很多废弃了,例如:宠物、轻功,所以我们直接从玩家技能释放开始看。
/**
* 玩家使用技能
*
* @param message
* @param response
* @param actor
* @param isStandalone 是否是单机释放
*/
private void acoterUseSkill(BPFight.CSUseSkillRequest message, BPFight.SCUseSkillResponse.Builder response,
Actor actor,boolean isManualMoveSkill, boolean isStandalone)
{
...
...
//旧的目标
int lastTargetID = actor.getAbstractCharacterCommonModule().getTargetObjectID();
//旧的朝向
int lastRotation = actor.getRotation();
//技能id
int skillID = message.getSkillID();
//目标id
int targetID = message.getTargetObjectID();
//技能标识
int skillSign = message.getSign();
...
...
//检测是否坐骑,坐骑不能使用技能
//检测变身后能否使用技能
//检查移动状态能否使用技能
//设置客户端上传的最新目标和朝向
//调用useSkill
result = actor.getSkillModule().useSkill(actor, skillID, skillSign, isStandalone, true);
//设置技能使用响应客户端
//技能使用事件
//客户端使用技能成功广播
}
/**
* 使用技能
*
* @param character 使用者
* @param skillID 技能id
* @param sign 技能标识(客户端释放时指定,服务器主动释放填-1,内部会自动生成)
* @param isStandalone 是否是单机释放
* @param cltUse 是否客户端释放
* @return
*/
public int useSkill(AbstractCharacter character, int skillID, int sign, boolean isStandalone, boolean cltUse)
{
//检查当前场景能否使用技能
//是否正在使用技能
boolean isContinuityUse = isUsingSkill();
//根据技能id获取,技能模板表、技能编辑表、技能数据表数据
//根据技能模板表,检查是否释放被动技能错误
if (!skillTemplate.isActive())
{
return BPErrorCodeEnum.SKILL_USE_PASSIVE_SKILL_USE_REPEAT;
}
// 获取技能组
DictSkillGroupData skillGroup = null;
if (skillTemplate.getSkillGroupType() > 0)
{
skillGroup = (DictSkillGroupData) DictSkillGroupData.getRecordById(skillTemplate.getSkillGroupType());
if (skillGroup == null)
{
return BPErrorCodeEnum.SKILL_SKILL_GROUP_NOT_MATCH;
}
}
// 如果当前正在使用技能
if(isContinuityUse)
{
//如果当前正在使用技能,做一个临时缓存
tempSkillInfo.copyFrom(skillInfo);
}
//设置当前技能释放的技能信息
skillInfo.setSkillGroup(skillGroup);
skillInfo.setSkillEditorData(skillEditorData);
skillInfo.setSkillData(skillData);
skillInfo.setSkillTemplate(skillTemplate);
// 检测技能使用条件(传入是否客户端释放)
// 检查cd、消耗、技能组和单体技能检测、位移、自身、目标。
int result = checkUseSkill(cltUse);
...
// 连携释放,先打断当前技能(正在使用当前技能)
if(isContinuityUse)
{
// 必须先将skillInfo设置回当前技能的,再进行中断操作
skillInfo.copyFrom(tempSkillInfo);
tempSkillInfo.clear();
// 中断当前技能
doFinishCurrentSkillLogic(true,true);
// 再将skillInfo设置回要使用的技能
skillInfo.setSkillGroup(skillGroup);
skillInfo.setSkillEditorData(skillEditorData);
skillInfo.setSkillData(skillData);
skillInfo.setSkillTemplate(skillTemplate);
}
// 创建技能逻辑实例
result = createSkillLogic(character, skillTemplate);
if (result != 0)
{
// 创建技能逻辑失败,返回错误码
return result;
}
// 扣除消耗
skillConsume();
// 激活技能逻辑,开始tick
currentSkillLogic.init();
currentSkillLogic.setSkillId(skillTemplate.getId());
currentSkillLogic.setSkillSign(sign);
currentSkillLogic.setClientUse(cltUse);
currentSkillLogic.setSkillModule(this);
currentSkillLogic.setActualSkillLevel(actualSkillLevel);
// 增加cd时间
addCooldownForCurrentSkillLogic();
// 将预使用技能主动位移路径设置到当前使用技能路径中
if(skillEditorData.getChaseNum() > 0)
{
if(cltUse)
{
setAllChasePath(this.prepareUseChasePath);
}
else
{
preCalcChasePath(skillEditorData,this.chasePath);
}
}
...
// 技能使用事件
...
}
检查当前场景能否使用技能。
根据技能id获取,技能模板表、技能编辑表、技能数据表数据。
根据技能模板表,检查是否释放被动技能错误。
根据技能模板表获取技能组。
如果当前正在使用技能,将当前使用的技能信息做一个临时缓存。
设置当前技能释放的技能信息。
检测技能使用条件(传入是否客户端释放)。检查cd、消耗、技能组和单体技能检测、位移、自身、目标。
连携释放,先打断当前技能(正在使用当前技能)。
创建技能逻辑实例。
扣除消耗。
激活技能逻辑,开始tick。
增加cd时间。
将预使用技能主动位移路径设置到当前使用技能路径中。
技能使用事件(切换战斗状态…)。
3.玩家技能释放过程各个方法调用
1.检查cd、消耗、技能组和单体技能检测、位移、自身、目标
* 使用技能检测
*
* @param isCltUse 是否客户端使用
*/
@Override
public int checkUseSkill(boolean isCltUse)
{
// 此技能是否CD
int result = checkCooldown(skillTemplate,skillData);
...
// 检测消耗
result = checkConsume();
...
// 检测技能释放位移
if(isCltUse)
{
// 客户端释放的检测客户端发来的位移点
result = checkChase(skillEditorData,prepareUseChasePath);
if (result != 0)
{
clearPrepareUseChasePath();
return result;
}
}
else
{
// 服务器释放的检测 冲向瞄准目标的位移类型是否有瞄准目标
result = checkChaseAim(skillEditorData);
if (result != 0)
{
return result;
}
}
...
// 检测自身条件
result = checkSelf();
...
// 选择目标检测
result = checkTarget();
}
1.检查技能是否在冷却(也检查公共冷却),根据技能模板表配置skillTemplate
// 此技能是否CD
int result = checkCooldown(skillTemplate,skillData);
/**
* 检查技能是否在冷却(也检查公共冷却)
*/
public int checkCooldown(DictSkillTemplate skillTemplate,DictSkillDataData skillData)
{
//commonCoolDownRemian公共冷却时间优先级最大,类似:放了一个大招导致某段时间内所有技能都不能使用的情况。
if(skillTemplate.getIsCommonCD() == 1 && commonCoolDownRemian > 0)
{
return BPErrorCodeEnum.SKILL_IN_COOLDOWN;
}
...
}
根据skillTemplate表配置的检测是否受公共冷却影响。
检查表配置的冷却id是否大于0。
迭代缓存的冷却的列表,检测当前的技能是否冷却完毕。
2.检测消耗(只是检测不扣除,根据DictSkillDataData表)
// 检测消耗
result = checkConsume();
**
* 检查技能消耗
*
* @param character 角色类
* @param skillTemplate 技能主表
* @param skillData 技能效果表
* @param skillGroup 技能组
* @return <0 不够消耗的理由 =0足够消耗
*/
public static int checkConsume(AbstractCharacter character, DictSkillTemplateData skillTemplate, DictSkillDataData skillData, DictSkillGroupData skillGroup)
{
//DictSkillDataData表里配置的节能消耗类型有两列:conditionLogicID_0和conditionLogicID_1
for(int i = 0 , size = skillData.getConditionCount(); i < size; i++)
{
SkillConsumeTypeEnum consumeType = skillData.getConditions()[i];
if (consumeType == null)
{
continue;
}
int valueBase = skillData.getConditionConsumeBases()[i];
// 计算一次消耗
int value;
// 根据类型扣除数值
switch (consumeType)
{
case CONSUME_HP_VALUE:
case CONDITION_HP_VALUE:
{
int need = valueBase;
value = character.getAttributeLogic().getAttribute(AttributeTypeEnum.HP);
if (value < need)
{
return BPErrorCodeEnum.SKILL_USE_SKILL_HP_NOT_ENOUGH;
}
break;
}
...
...
}
...
...
}
}
根据DictSkillDataData表配置,只检测没有扣除。
3.客户端使用技能位移检测(SkillDataEditorData技能编辑表)
/**
* 检查技能位移使用
* @param dict
* @return
*/
private int checkChase(DictSkillDataEditorData dict,TIntArrayList pathList)
{
// 检查主动位移, SkillDataEditor技能编辑表里配置的chaseLength_0、chaseLength_1...chaseLength_20
// 表里配置的主动位移大于0,那么chaseNum数量累加
int configChaseNum = dict.getChaseNum();
if(configChaseNum <= 0)
{
return 0;
}
// 客户端上发的路径是否为空
if(CollectionUtils.isBlank(pathList))
{
return BPErrorCodeEnum.SKILL_MANUAL_MOVE_SKILL_NO_PATH;
}
int pathSize = pathList.size();
//校验客户端发上来的路径段数是否与表里配置主动位移路段数一致
//客户端上发的是xyz
if(pathSize != configChaseNum * 3)
{
return BPErrorCodeEnum.SKILL_MANUAL_MOVE_SKILL_PATH_PARAM_ERROR;
}
//取最后路径的X坐标
int finalX = pathList.get(pathSize-3);
//取最后路径的Z坐标
int finalZ = pathList.get(pathSize-1);
// 判断终点是否是阻挡区域
boolean bolckFlag = getCharacter().getScene().getSceneGirdModule().checkWalkableForClientMove(finalX,finalZ);
if(!bolckFlag)
{
if(BPGlobals.getInstance().isDebugVersion())
{
// TODO 修改日志级别,适应压测。压测后重新改成warn日志
BPLog.BP_FIGHT.debug("【主动位移技能错误】客户端发来的位置是阻挡 [objectID] {} [skillID] {} [pos] ({},{})"
, character.getObjectID(), dict.getId(), finalX,finalZ);
}
return BPErrorCodeEnum.SKILL_MANUAL_MOVE_DESTINATION_BLOCK;
}
...
// 校验距离是不是过大
int[] chaseLengths = dict.getChaseLengths();
int index = 0;
//注意这里是将值赋给了临时变量pre,数组才会是地址
int preX = character.getX();
int preZ = character.getZ();
for (int i = 0; i < chaseLengths.length; i++)
{
//依次拿出每次移动的值
int chaseLength = chaseLengths[i];
if(chaseLength <= 0)
{
continue;
}
// 增加3米容错
chaseLength += 300;
int lengthSQ = chaseLength * chaseLength;
int x = pathList.get(index * 3);
int z = pathList.get(index * 3 + 2);
//下一次循环preX,preZ是当前的最新值
float disSQ = MapUtils.distanceSqBetweenPos(preX, preZ, x, z);
if(disSQ > lengthSQ)
{
if(BPGlobals.getInstance().isDebugVersion())
{
// TODO 修改日志级别,适应压测。压测后重新改成warn日志
BPLog.BP_FIGHT.debug("【主动位移技能错误】客户端发来的位移距离超过技能配置最大距离限制 [objectID] {} [skillID] {} [派发次数] {} [实际距离] {} [最大距离] {}"
, character.getObjectID(), dict.getId(), i + 1, (int)Math.sqrt(disSQ),chaseLengths[i]);
}
//主动位移类技能的目的地超过范围
return BPErrorCodeEnum.SKILL_MANUAL_MOVE_SKILL_DESTINATION_TOO_FAR;
}
preX = x;
preZ = z;
index++;
if(index >= configChaseNum)
{
break;
}
}
...
}
SkillDataEditor技能编辑表里配置的chaseLength_0、chaseLength_1…chaseLength_20,校验当前技能位移是否合法。
例如袁天罡有些技能是带位移的,那么表里配置的chaseLength值会大于0。客户端在请求使用技能时会发送技能的路径点组到服务端。
服务端会校验路径点组长度是否和表里配置的长度相等。并校验终点坐标的是否阻挡区域。
校验客户端上发的点组移动距离是不是和配置的表距离过大。
4.服务端自身使用技能位移检测(SkillDataEditorData技能编辑表)
/**
* 检测服务器释放的技能,是否有冲向目标的技能
* @param skillDataEditorData
* @return
*/
private int checkChaseAim(DictSkillDataEditorData skillDataEditorData)
{
//表配置不用校验安全距离
if(!skillDataEditorData.isChaseLockTarget())
{
return 0;
}
AbstractCharacter character = getCharacter();
//当前瞄准的目标object id
int aimObjectID = character.getAbstractCharacterCommonModule().getAimObjectID();
if(aimObjectID < 0)
{
if(BPGlobals.getInstance().isDebugVersion())
{
// TODO 修改日志级别,适应压测。压测后重新改成warn日志
BPLog.BP_FIGHT.debug("【技能释放错误】首段位移是冲向瞄准目标的技能,释放的时候没有设置瞄准目标 [objectId] {} [objectType] {} [skllId] {}",
character.getObjectID(),character.getObjectType().name(),skillDataEditorData.getId());
}
//服务器使用的冲向目标技能没有设置瞄准目标
return BPErrorCodeEnum.SKILL_CHASE_SERVER_USE_NO_AIM_OBJECT;
}
return 0;
}
表配置是否校验安全距离。
服务器释放技能必须有当前瞄准的目标object id。
5.检测自身条件
/**
* 技能释放检查自身条件是否满足
*
* @return
*/
public int checkSelf()
{
DictSkillTemplateData skillTemplate = skillInfo.getSkillTemplate();
DictSkillDataData skillData = skillInfo.getSkillData();
...
...
// 是否携带条件BUFF
int buffRs = checkConditionBuff(skillTemplate);
if(buffRs < 0)
{
return buffRs;
}
// 是否受限于BUFF
buffRs = checkLimitBuff(skillTemplate);
if (buffRs < 0)
{
return buffRs;
}
}
是否禁止释放技能
自己是否活着
受身技能的释放限制
是否在移动中(正常移动和特殊移动)
如果正在使用轻功
正在使用绝学
跳跃中
当前等级小于技能需要的最低等级
检测当前玩家身上挂有需要的buff。
是否有控制该技能释放的BUFF,中buff了有些技能不用。
检查玩家特殊条件,变身和读条。
6.技能选择目标检测
/**
* 技能释放目标检测
*
* @return
*/
public int checkTarget()
{
...
...
// AOE 不选敌
boolean isAOE = SkillHelper.isAOE(skillEditorData);
if (isAOE)
{
if(!skillEditorData.isLockTargetFirstOnAOE())
{
// AOE不优先索敌目标则清空目标
character.getAbstractCharacterCommonModule().setTargetObjectID(SkillCommonConstant.TARGET_ID_NONE);
}
return 0;
}
// 如果技能目标是自己,就将自己设置为目标
if (skillTemplate.getEffectTargetEnum() == SkillEffectTargetEnum.SELF)
{
character.getAbstractCharacterCommonModule().setTargetObjectID(character.getObjectID());
return 0;
}
...
...
}
这里如果当前技能是aoe技能,则清空目标。
如果技能目标是自己,就将自己设置为目标。
2.连携释放,先打断当前技能(正在使用当前技能)
**
* 技能结束
* @param isBreak 是否中断
* @param isSelfBreak 是否自己主动中断(例如连携释放新技能中断当前技能)
*/
private void doFinishCurrentSkillLogic(boolean isBreak, boolean isSelfBreak)
{
//各种清空
//打断通知客户端
}
3.创建技能逻辑实例
// 创建技能逻辑实例(现在去掉所有的技能逻辑,只保留17号技能逻辑)
result = createSkillLogic(character, skillTemplate);
/**
* 创建技能逻辑实例,之创建出logic类
*
* @param skillTemplate
* @param skillData
* @param scene 当前所在场景
* @return
*/
public static AbstractSkillLogic createSkillLogic(DictSkillTemplateData skillTemplate, DictSkillDataEditorData dictSkillDataEditorData,DictSkillDataData skillData, AbstractBPScene scene)
{
...
...
AbstractSkillLogic skillLogic = scene.getSkillCache().createSkillLogic(17);
if (skillLogic != null)
{
SkillLogicTypeEnum logicTypeEnum = skillTemplate.getLogicTypeEnum();
//设置技能逻辑类型(顺发,引导,持续,特殊逻辑控制)
skillLogic.setLogicType(logicTypeEnum);
skillLogic.setDictSkillTemplateData(skillTemplate);
skillLogic.setDictSkillDataEditorData(dictSkillDataEditorData);
skillLogic.setDictSkillDataData(skillData);
}
...
...s
}
创建技能逻辑类,设置技能逻辑类型(顺发,引导,持续,特殊逻辑控制)
4.扣除消耗(实际扣除消耗)
/**
* 扣除技能消耗
*
* @return
*/
private int skillConsume()
{
//DictSkillDataData表里配置的节能消耗类型有两列:conditionLogicID_0和conditionLogicID_1
for(int i = 0 , size = skillData.getConditionCount(); i < size; i++)
{
SkillConsumeTypeEnum consumeType = skillData.getConditions()[i];
if (consumeType == null)
{
continue;
}
int valueBase = skillData.getConditionConsumeBases()[i];
// 计算一次消耗
int value;
// 根据类型扣除数值
switch (consumeType)
{
case CONSUME_HP_VALUE:
{
int need = valueBase;
character.getAttributeLogic().minusOneAttribute(AttributeTypeEnum.HP, need);
break;
}
...
...
}
...
...
}
}
DictSkillDataData表里配置的节能消耗类型有两列:conditionLogicID_0和conditionLogicID_1
根据表配置扣除消耗,实际扣除了!
5.技能标识
// 技能sign
if (sign <= 0)
{
// 如果释放技能时没有sign,则主动生成一个sign
sign = getAndIncreaseSkillSign();
}
/**
* 计算当前技能的技能标识
*
* @return
*/
private int getAndIncreaseSkillSign()
{
//技能标识,主要发送给客户端用于前端伤害识别、技能效果匹配等用途
skillSignCounter++;
// 防止有0,客户端要求skill_sign不能为0,所以加1
return (skillSignCounter & SkillCommonConstant.SKILL_SIGN_MOD) + 1;
}
6.激活技能逻辑,开始tick
// 激活技能逻辑,开始tick
currentSkillLogic.init();
currentSkillLogic.setSkillId(skillTemplate.getId());
currentSkillLogic.setSkillSign(sign);
currentSkillLogic.setClientUse(cltUse);
currentSkillLogic.setSkillModule(this);
currentSkillLogic.setActualSkillLevel(actualSkillLevel);
当前技能逻辑类的一些初始化。
7.增加cd时间
// 增加cd时间
addCooldownForCurrentSkillLogic();
8.将预使用技能主动位移路径设置到当前使用技能路径中
if(skillEditorData.getChaseNum() > 0)
{
if(cltUse)
{
setAllChasePath(this.prepareUseChasePath);
}
else
{
preCalcChasePath(skillEditorData,this.chasePath);
}
}
9.使用技能事件,清楚相关身上的buff
// 使用技能事件,清楚相关身上的buff
onStatChange(character, SkillStateChangeEnum.START_ATTACK);
/**
* 相关状态变化时调用
*
* @param sender 发生变化的发送者。
* 比如玩家受击,sender为发出攻击的玩家
* 切换场景由于是玩家自己主动驱动,所以sender为玩家自己
* 1:下线
* 2:切换场景
* 3:开始移动
* 4:玩家受到伤害
* 5: 开始攻击
*/
public void onStatChange(AbstractCharacter sender, SkillStateChangeEnum stateChangeEnum)
{
...
...
switch (stateChangeEnum)
{
case START_MOVE:
{
if (currentSkillLogic != null)
{
currentSkillLogic.onStartMove(sender);
}
int size = buffList.size();
for (int i = 0; i < size; i++)
{
AbstractSkillImpact impact = buffList.get(i);
impact.onStartMove(sender);
}
break;
}
...
...
}
}
使用技能就会调用onStatchange,然后迭代玩家身上所有的效果,根据standardimpact表配置使用技能是否打断buff列状态来判断是否打断。
/**
* 中断效果
* (非计时结束导致的buff结束,都要调用buff中断结束)
* @param character
* @param canBreakDispel 是否能驱散 不可驱散 特性BUFF| true = 能驱散不可驱散buff false = 不可驱散buff不能被驱散
*/
private void doBreakImpact(AbstractCharacter character,boolean canBreakDispel)
{
if (isFinished)
{
return;
}
//如果不是持续性buff效果,直接返回
if(impactData.getImpactType() != SkillImpactTypeEnum.PERSISTENT.getValue())
{
return;
}
//传入不是驱散, 并且当前buff表里配置不能被中断
if(!canBreakDispel && !getImpactData().isBeDispel())
{
return;
}
// notice client,
// 效果已经激活并且不是位移效果打断
if (isActive() && !isBeBreakBeatBackImpact())
{
}
...
...
}
中断效果。是否通知并选择通知类型。
10.根据释放技能事件,处理相应的buff逻辑
private void onUseSkill(DictSkillTemplateData skillTemplate)
{
// 释放技能事件
handleBuffEventOnBatch(BuffListenEventEnum.EVENT_TYPE_USE_SKILL,skillTemplate.getId(),0,0,0,0,0,0,0,null,null);
}
释放技能事件,传入释放技能枚举和技能id。
/**
* 批量处理处理监听buff事件
* @param eventEnum
* @param intParam0
* @param intParam1
* @param intParam2
* @param intParam3
* @param intParam4
* @param intParam5
* @param longParam0
* @param longParam1
* @param objectParam0
* @param objectParam1
*/
public void handleBuffEventOnBatch(BuffListenEventEnum eventEnum,int intParam0, int intParam1, int intParam2, int intParam3, int intParam4, int intParam5, long longParam0, long longParam1, Object objectParam0, Object objectParam1)
{
//传入的枚举为释放技能
List<AbstractSkillImpact> listenBuffList = getEventListenBuffsByEvent(eventEnum.getType());
if(!CollectionUtils.isBlank(listenBuffList))
{
for (int i = 0,iSize = listenBuffList.size(); i < iSize; i++)
{
listenBuffList.get(i).handleEvent(eventEnum,character, intParam0, intParam1, intParam2, intParam3, intParam4, intParam5, longParam0, longParam1, objectParam0, objectParam1);
}
}
...
...
}
根据释放技能枚举,获取所有与释放技能有关的buff列表,迭代列表依次处理逻辑。
/**
* 为效果注册到BUFF事件监听
*
* @param toAddImpact
*/
public void registeToEventListenMap(AbstractSkillImpact toAddImpact)
{
...
//不是持续性,返回
if (toAddImpact.getImpactData().getImpactType() != SkillImpactTypeEnum.PERSISTENT.getValue())
{
return;
}
//获取当前buff的事件集合, 例如46号技能效果buff,监听血量上限会有多个事件,所以是事件组
BuffEventHandleEnum[] events = toAddImpact.getBuffEventHandles();
if (events == null || events.length < 1)
{
return;
}
OUTER_LOOP:
for(int i = 0 , size = events.length; i < size; i++)
{
//BUFF事件监听处理类型
BuffEventHandleEnum event = events[i];
if (event.getType() < 0)
{
continue;
}
//根据事件类型加入到 BUFF事件监听map
ArrayList<AbstractSkillImpact> listenImpacts = eventListenMap.get(event.getType());
...
}
...
}
每个buff都有它关心的事件类型,每个buff在初始化和添加时,会根据类型注册到缓存。
例如buff如果和释放技能事件有关,那么释放技能时需要处理对应buff逻辑。
11.切换战斗状态
/**
* 使用技能
*/
public void onSkillUse()
{
isUseSkill = true;
lastFightInterval = 0;
if(!this.isFightStatus)
{
switchFightStatus(true);
}
}
使用技能,切换战斗状态
4.tick当前技能逻辑
@Override
public void tickSkillLogic(int interval)
{
if (currentSkillLogic == null)
{
lastSkillEndInterval += interval;
return;
}
currentSkillLogic.tick(interval);
if (currentSkillLogic.isFinished())
{
BPLog.BP_FIGHT.debug("技能结束, id:{}", currentSkillLogic.getSkillID());
// 结束技能
boolean isBreak = currentSkillLogic.isBreak();
doFinishCurrentSkillLogic(isBreak,false);
return;
}
}
1.激活后会进行tick
/**
* 激活后会进行tick
*
* @param interval
*/
public void tick(int interval)
{
...
...
if (!isActive)
{
//初始化是否AOI
//根据skillDataEditor表配置的技能打击形状判断是否AOE
//初始化持续时间
//初始化延迟生效时间
onActivate();
interval = 0;
isActive = true;
}
...
...
// 判断血量大于零
if (!isHpPositive())
{
breakSkill();
return;
}
...
// tick技能逻辑
tickSkillLogic(interval);
...
}
初始化是否AOI
根据skillDataEditor表配置的技能打击形状判断是否AOE
初始化持续时间
初始化延迟生效时间
tick技能逻辑
2.瞬发技能的tick处理
/**
* 瞬发技能的tick处理
*
* @param interval
*/
public void tickInstantSkillLogic(int interval)
{
if (!isTakeEffect)
{
startEffect();
//是否生效
isTakeEffect = true;
}
}
技能开始生效。
3.持续技能的tick处理
4.引导技能的tick处理
5.技能开始生效
/**
* 技能开始生效
*/
public void startEffect()
{
//技能生效的次数
takeEffectCount++;
...
...
// 处理主动位移
int chaseLength = skillEditorData.getChaseLengthByIndex(takeEffectCount - 1);
if(chaseLength > 0)
{
TIntArrayList chasePath = skillModule.getChasePath();
int chaseX = chasePath.get(0);
int chaseY = chasePath.get(1);
int chaseZ = chasePath.get(2);
chasePath.remove(0,3);
// 设置玩家的位置(!!! 暂时只改变位置,不改变朝向!!! -- 20190117)
self.getMoveModule().specialMoveTo(SpecialMoveType.TRANSPORT_SKILL, chaseX, chaseY, chaseZ, self.getRotation());
}
...
...
//技能效果组ID,调用skillassit表中的组ID
DictSkillDataAssitData skillDataAssitData = DictSkillDataAssitData.getParamsByGroupIdAndEffectNum(skillData.getEffectGroupID(), takeEffectCount);
if(skillDataAssitData == null)
{
BPLog.BP_FIGHT.error("【技能错误】技能逻辑作用时,找不到技能段SkillDataAssit配置 [skillID] {} [level] {} [effectNum] {} [EffectGroupID] {}",skillTemplateData.getId(),skillData.getLevel(),takeEffectCount,skillData.getEffectGroupID());
return;
}
// 固定对自己作用 skillDataAssitData表里配置了技能会固定给自己上效果
effectToSelf(skillDataAssitData);
// 空打击点 根据当前技能释放的次数获取当前技能打击的形状
int scope = skillEditorData.getScopeByIndex(takeEffectCount - 1);
if(scope == SkillScopeConstant.VOID)
{
return;
}
...
...
//单体技能逻辑
//AOE技能逻辑
}
根据技能组id和技能生效的次数,根据SkillDataAssit表配置固定给自己上效果。
处理主动位移,根据技能点位移调用特殊移动方法直接设置玩家位置,暂不改变朝向。
根据技能生效次数,从skillEditor表里获取当前段数的打击行状。如果是空打击点直接返回(空打击点(用于补足主动位移的打击点,枚举类型5))。
对自己固定 概率 效果(无论是否命中,都上效果) skillDataAssitData表里配置了技能会固定给自己上效果
单体技能逻辑。
AOE技能逻辑。
6.技能作用逻辑 - 单体
// 技能作用逻辑 - 单体
if (!isAOE)
{
...
// 检查目标
int rs = checkTarget();
...
//计算命中率和霸体
if(skillTemplateData.getSkillType() == SkillTypeConstant.NORMAL_ATTACK && selfCommonModule.isWarState(WarStateEnum.BLIND_NORMAL_SKILL_NO_HURT.getId()))
{
// 普攻技能 && 被致盲了,那么普攻就miss
this.isHit = false;
}
else
{
//计算是否命中
this.isHit = calculateHit(target,skillDataAssitData);
}
// 计算霸体
calcAttackLevelAndEndure(target,skillDataAssitData);
/执行技能逻辑
doStartEffect(skillDataAssitData);
//技能作用事件
self.onSkillEffect(skillTemplateData, getTargetObjectID(),skillSign,getHitIndex(),isHit(),isEndure());
//技能每段命中目标(AOE打中多个人只调用一次)
onSkillEachEffectHit(self,skillTemplateData,skillDataAssitData,1);
//pvp使用技能的处理(玩家对玩家)
SkillHelper.pvpUseSkill(self,target , skillTemplateData);
// 确定下一次目标,因为有些技能需要在logic的不同触发阶段选择不同的目标
// 派生类根据具体的技能逻辑,找单体目标
// 默认不改变目标
int result = findSingleTarget();
}
1.检查目标
// 检查目标
int rs = checkTarget();
/**
* 检查目标
* @return =0 通过 <0 失败原因
*/
private int doCheckTarget(AbstractCharacter character,AbstractCharacter target,DictSkillTemplateData skillTemplate, DictSkillDataEditorData skillDataEditorData, boolean isActorAndInSafeRegion)
{
...
...
目标是否在安全区
是否正在使用轻功
是否隐身不可见
目标是否需要存活
不可被攻击
不可被选中(不能被使用单体技能)
目标细分
//第一次技能作用距离
int distance = skillDataEditorData.getFirstAngle();
distance += SkillCommonConstant.SKILL_TOLERATE_DISTANCE; // 增加一定距离的容错
float distanceSQ = (long)distance * (long)distance;
// 判断两个实体之间的距离
float actualDistanceSQ = MapUtils.distanceSqBetweenPos(character.getX(), character.getZ(), target.getX(), target.getZ());
if (actualDistanceSQ > distanceSQ)
{
if(BPGlobals.getInstance().isDebugVersion())
{
int diffDis = (int)(Math.sqrt(actualDistanceSQ) - distance);
// TODO 修改日志级别,适应压测。压测后重新改成info日志
BPLog.BP_FIGHT.debug("【技能效果检查目标失败】原因: 目标距离过远 [skillId] {} [myPos] ({},{},{}) [targetPos] ({},{},{}) [ 配置距离 ] {} [比配置距离远] {}"
,skillTemplate.getId(),character.getX(),character.getY(),character.getZ(),target.getX(),target.getY(),target.getZ(),distance,diffDis);
}
return BPErrorCodeEnum.SKILL_TARGET_DISTANCE_NOT_ENOUGH;
}
// 高度差
// 技能作用的高度范围
int effectHight = skillTemplate.getEffectHight();
if(effectHight > 0)
{
int sampleHeightDiff = character.getScene().getSceneGirdModule().calcHeightDiff(character, target);
if(sampleHeightDiff == Integer.MIN_VALUE)
{
// 玩家不在可行走区域内,认为技能高度无限制,防止出现卡阻挡无敌的问题
// return BPErrorCodeEnum.SKILL_TARGET_HEIGHT_ERROR;
}
else
{
if(Math.abs(sampleHeightDiff) > effectHight)
{
return BPErrorCodeEnum.SKILL_TARGET_HEIGHT_DIFF_NOT_SUIT;
}
}
}
// 目标选择是友好,玩家阵营也是友好,但是PVP规则中是敌对关系,也是不符合条件的
if (skillTemplate.getEffectTargetEnum().isFrindly())
{
if (SkillHelper.isPvP(character, target, skillTemplate))
{
Actor selfActor = SkillHelper.getObjectActor(character);
Actor targetActor = SkillHelper.getObjectActor(target);
if (selfActor != null && targetActor != null)
{
ActorPvPModule pvPModule = selfActor.getPvPModule();
if (!pvPModule.canAttack(targetActor))
{
return BPErrorCodeEnum.SKILL_TARGET_FORCE_RELATIONSHIP_NOT_MATCH;
}
}
}
}
...
...
...
}
单体技能,检查目标主要是:
判断两个实体之间的距离
技能作用的高度范围
目标选择是友好,玩家阵营也是友好,但是PVP规则中是敌对关系,也是不符合条件的
2.单体技能作用-计算命中率
int targetID = selfCommonModule.getTargetObjectID();
target = getCharacter(targetID);
//当前是普攻并且玩家被致盲了
if(skillTemplateData.getSkillType() == SkillTypeConstant.NORMAL_ATTACK && selfCommonModule.isWarState(WarStateEnum.BLIND_NORMAL_SKILL_NO_HURT.getId()))
{
// 普攻技能 && 被致盲了,那么普攻就miss
this.isHit = false;
}
else
{
//计算命中率
this.isHit = calculateHit(target,skillDataAssitData);
}
if (!this.isHit)
{
//如果没有命中,发送未命中通知客户端
sendNoHitNotice(self,target,targetID);
}
2.单体技能作用-计算攻击等级和霸体
/**
* 计算攻击等级和霸体
* @param target
* @param skillAssitData
*/
public void calcAttackLevelAndEndure(AbstractCharacter target,DictSkillDataAssitData skillAssitData)
{
...
...
//判断攻击对象是npc或者玩家
int baseAttackLevel = 0;
if(target.getObjectType() == ObjectTypeEnum.NPC)
{
//攻击等级(对怪物)
baseAttackLevel = skillAssitData.getAttackLevelNpc();
}
else
{
//攻击等级(对玩家
baseAttackLevel = skillAssitData.getAttackLevel();
}
// 攻击等级
this.realAttackLevel = FormulaFacade.countAttackLevel(attacker, target,baseAttackLevel);
// 是否霸体
this.isEndure = target.getAbstractCharacterCommonModule().isEndure();
// 扣除霸体值(传入实际攻击等级)
target.getAbstractCharacterCommonModule().decreaseEndure(realAttackLevel);
..
}
3.单体技能作用-操作
技能作用事件
self.onSkillEffect(skillTemplateData, skillDataAssitData,getTargetObjectID(),skillSign,getHitIndex(),isHit(),isEndure());
技能每段命中目标(AOE打中多个人只调用一次)
self.onSkillEffect(skillTemplateData, skillDataAssitData,getTargetObjectID(),skillSign,getHitIndex(),isHit(),isEndure());
6.技能作用逻辑 - AOE
1.普攻技能 && 被致盲了,那么就不扫敌
// 技能作用逻辑 - AOE
this.isHit = true;
if(skillTemplateData.getSkillType() == SkillTypeConstant.NORMAL_ATTACK && self.getAbstractCharacterCommonModule().isWarState(WarStateEnum.BLIND_NORMAL_SKILL_NO_HURT.getId()))
{
// 普攻技能 && 被致盲了,那么就不扫敌也不作用了
this.isHit = false;
}
如果是普攻并且被致盲,那么不作用
2.扫描周围可作为目标的对象
// 当前生效次数
int arrayIndex = takeEffectCount - 1;
//获取当前生效次数的技能范围形状
int scope = skillEditorData.getScopeByIndex(arrayIndex);
//偏移长度
int offsetLength = skillEditorData.getOffsetLengthByIndex(arrayIndex);
//偏移角度
int offsetAngle = skillEditorData.getOffsetAngleByIndex(arrayIndex);
// 索敌中心点偏移
int currentRotation = self.getRotation();
int currentX = self.getX();
int currentZ = self.getZ();
int rotation = self.getRotation() + offsetAngle % 361;
self.setRotation(rotation);
//计算偏移长度
if (offsetLength != 0)
{
// Math.toRadians(rotation) 将度转换为弧度
// Math.cos()再取余弦
int x = (int) (currentX + offsetLength * Math.cos(Math.toRadians(rotation)));
int z = (int) (currentZ + offsetLength * Math.sin(Math.toRadians(rotation)));
//不置脏标记位置(也就是不执行视野更新)
self.setPosNoDirty(x, self.getY(), z);
}
获取技能形状,计算位置和朝向。
不置脏标记设置位置(也就是不执行视野更新)。
switch (scope)
{
case SkillScopeConstant.SINGLE:
case SkillScopeConstant.VOID:
{
break;
}
...
...
}
根据形状,遍历单位。
// 恢复正确的朝向
self.setRotation(currentRotation);
// 恢复正确的坐标
if (offsetLength != 0)
{
self.setPosNoDirty(currentX, self.getY(), currentZ);
}
恢复到使用技能之前的位置。
// 首选目标筛选出加入队列头
if(tendencyObjectID != SkillCommonConstant.TARGET_ID_NONE)
{
//猜想是list有玩家自己,所以需要长度大于1
if(list.size() > 1)
{
for(int i = 0 , size = list.size(); i < size; i++)
{
BPObject object = list.get(i);
if(tendencyObjectID == object.getObjectID())
{
//调换位置
BPObject firstObj = list.set(0,object);
list.set(i,firstObj);
break;
}
}
}
}
//迭代所有目标,校验是否符合,加入外部传入的目标targetList列表,并清楚用于aoi扫描的list列表!!
int size = list.size();
for (int i = 0; i < size; i++)
{
if (sendedCountForNpc >= skillDataAssitData.getMaxMonsterNum() || sendedCountForPlayer >= skillDataAssitData.getMaxPlayerNum())
{
// 人数/怪物数到达上限
break;
}
BPObject bpObject = list.get(i);
if(bpObject == null)
{
continue;
}
if (!bpObject.getObjectType().isCharacter())
{
continue;
}
AbstractCharacter target = (AbstractCharacter) bpObject;
//检查AOE扫描的目标是否合法
int rs = checkAOEScanedTarget(scene, self, target, skillTemplate,skillEditorData, skillDataAssitData, isActorAndInSafeRegion);
if(mapViewModule != null)
{
mapViewModule.recordSkillEffecScanPlayer(skillId,takeEffectCount,target.getObjectID(),rs);
}
if(rs < 0)
{
continue;
}
targetList.add(target);
if(bpObject.getObjectType() == ObjectTypeEnum.NPC)
{
sendedCountForNpc++;
}
else
{
sendedCountForPlayer++;
}
}
...
list.clear();
首选目标筛选出加入队列头。
迭代所有目标,校验是否符合,加入外部传入的目标targetList列表,并清楚用于aoi扫描的list列表!!
这里个人认为两个list的迭代可以合并为1次迭代,提高效率。
3.迭代目标对象,技能作用
int size = list.size();
for (int i = 0; i < size; i++)
{
}
迭代对象
//设置目标
selfCommonModule.setTargetObjectID(target.getObjectID());
// 计算攻击等级和霸体
calcAttackLevelAndEndure(target,skillDataAssitData);
// 作用逻辑
doStartEffect(skillDataAssitData);
// 技能派发事件
self.onSkillEffect(skillTemplateData,skillDataAssitData, target.getObjectID(),skillSign,getHitIndex(),isHit(),isEndure());
// 被技能派发事件
if(isHit() && target != null)
{
target.onBeSkillEffect(self,skillTemplateData,realAttackLevel,isEndure);
}
SkillHelper.pvpUseSkill(self,target , skillTemplateData);
...
// 技能命中事件(AOE打中多个人只调用一次)
if(size > 0)
{
onSkillEachEffectHit(self,skillTemplateData,skillDataAssitData,size);
}
1.作用逻辑
// 作用逻辑
doStartEffect(skillDataAssitData);
private void doStartEffect(DictSkillDataAssitData skillDataAssitData)
{
// XXX 记录技能对单个角色作用,用于后台地图工具
CharacterMapViewModule mapViewModule = getSkillModule().getCharacter().getCharacterMapViewModule();
if(mapViewModule != null)
{
mapViewModule.recordSkillEffectHitPlayer(skillId,takeEffectCount,getTargetObjectID(),isHit,isEndure,realAttackLevel);
}
startEffectImpl(skillDataAssitData);
}
技能作用逻辑,现在只保留17号技能作用逻辑。也就是说所有技能逻辑都是17号。
public class SkillLogic017 extends AbstractSkillLogic
{
@Override
protected void startEffectImpl(DictSkillDataAssitData skillDataAssitData)
{
...
if(isHit() &&effectNumSelfOnceMap.get(effectCount) == effectNumSelfOnceMap.getNoEntryValue())
{
effectNumSelfOnceMap.put(effectCount,1);
//对自己单次 概率 效果(命中多个敌人只上一次,未命中不上)
TIntArrayList selfOnceImpactIds = skillDataAssitData.getSelfOnceImpactIds();
if(!CollectionUtils.isBlank(selfOnceImpactIds))
{
TIntArrayList selfOnceProbablitity = skillDataAssitData.getSelfOnceProbablitity();
for(int i = 0 , size = selfOnceImpactIds.size(); i < size; i++)
{
int selfOnceProb = selfOnceProbablitity.get(i);
int selfOnceImpactID = selfOnceImpactIds.get(i);
if(selfOnceImpactID <= 0 || selfOnceProb <= 0)
{
continue;
}
if(selfOnceProb >= GameConstant.TEN_THOUSAND || RandomUtil.randomInt(GameConstant.TEN_THOUSAND) <= selfOnceProb)
{
//向自己派发效果
sendImpactToSelf(selfOnceImpactID);
}
}
}
}
...
// 二 、 给自己多次,命中才给自己加
TIntArrayList selfRepeatImpactIds = skillDataAssitData.getSelfRepeatImpactIds();
if(!CollectionUtils.isBlank(selfRepeatImpactIds) && isHit())
{
//对自己多次 概率 效果 (命中多个敌人上多次次,未命中不上)
TIntArrayList selfRepeatProbablitity = skillDataAssitData.getSelfRepeatProbablitity();
for(int i = 0 , size = selfRepeatImpactIds.size(); i < size; i++)
{
int selfRepeatProb = selfRepeatProbablitity.get(i);
int selfRepeatImpactID = selfRepeatImpactIds.get(i);
if(selfRepeatImpactID <= 0 || selfRepeatProb <= 0)
{
continue;
}
if(selfRepeatProb >= GameConstant.TEN_THOUSAND || RandomUtil.randomInt(GameConstant.TEN_THOUSAND) <= selfRepeatProb)
{
//向自己派发效果
sendImpactToSelf(selfRepeatImpactID);
}
}
}
...
...
// 三 、给敌方的固定伤害效果
// 根据技能辅助表SkillDataAssit获取固定伤害效果
int damageImpactId = skillDataAssitData.getDamageImpact();
if(damageImpactId > 0)
{
//派发效果,根据技能的配置,来确定派发给单体还是周围
sendImpactToTarget(damageImpactId);
}
// 四、 给敌方上的条件效果
// 判断是否满足条件
boolean isInCondtion = false;
//条件Idcollection中的所有元素的集合(一般来讲是impact的groupId)
TIntHashSet conditionCollectionIdSet = skillDataAssitData.getConditionCollectionIdSet();
...
...
// 上效果逻辑
TIntArrayList targetImpactIds = null;
TIntArrayList targetProbablitity = null;
TIntArrayList targetEnchanceAttrIds = null;
TIntArrayList targetDeclineAttrIds = null;
if(isInCondtion)
{
//满足条件 对目标 概率 / 效果 / 增强属性 / 抵抗属性
targetImpactIds = skillDataAssitData.getConditionTargetImpactIds();
targetProbablitity = skillDataAssitData.getConditionTargetProbablitity();
targetEnchanceAttrIds = skillDataAssitData.getConditionTargetEnchanceAttrIds();
targetDeclineAttrIds = skillDataAssitData.getConditionTargetDeclineAttrIds();
}
else
{
//满足条件 对目标 概率 / 效果 / 增强属性 / 抵抗属性
targetImpactIds = skillDataAssitData.getInconformityTargetImpactIds();
targetProbablitity = skillDataAssitData.getInconformityTargetProbablitity();
targetEnchanceAttrIds = skillDataAssitData.getInconformityTargetEnchanceAttrIds();
targetDeclineAttrIds = skillDataAssitData.getInconformityTargetDeclineAttrIds();
}
...
...
// 五 、技能编辑器 skillDataEditor对敌人效果,后发技能效果编辑器的效果
DictSkillDataEditorData dictSkillDataEditorData = getDictSkillDataEditorData();
int skillEditorImpcatId = dictSkillDataEditorData.getImpcatByIndex(effectCount - 1);
if(skillEditorImpcatId > 0)
{
sendImpactToTarget(skillEditorImpcatId);
}
...
}
}
1.对自己单次 概率 效果(命中多个敌人只上一次,未命中不上)
2.对自己多次 概率 效果 (命中多个敌人上多次次,未命中不上)
3.给敌方的固定伤害效果
4.给敌方上的条件效果
5.技能编辑器 skillDataEditor对敌人效果,后发技能效果编辑器的效果
2.单独看下给敌方的固定伤害效果
// 三 、给敌方的固定伤害效果
// 根据技能辅助表SkillDataAssit获取固定伤害效果
int damageImpactId = skillDataAssitData.getDamageImpact();
if(damageImpactId > 0)
{
//派发效果,根据技能的配置,来确定派发给单体还是周围
sendImpactToTarget(damageImpactId);
}
/**
* 向目标派发效果
*
* @param targetID 目标在场景内的object id
* @param impactID
* @return
*/
private int doSendImpactTo(int targetID, int impactID)
{
...
...
boolean unBeneficial = impactData.getBuffFriendly() == SkillFriendlyEnum.UNBENEFICIAL.getVal();
if (unBeneficial)
{
// 负面buff,战斗处理
character.getFightModule().addObjectToFightSet(targetID,impactData.getId(),skillId);
target.getFightModule().addObjectToFightSet(character.getObjectID(),impactData.getId(),skillId);
}
...
...
// 派发效果 向目标发送一个效果(仅仅用于当前技能发送,其他处请勿使用!!!)
int result = skillModule.sendImpactToOnlyForCurrentSkillLogic(character, target, impactData);
...
...
}
战斗状态处理,当前技能派发效果。派发效果和开头派发buff逻辑是一致的。