和战斗相关的模块有mod_battle.erl

当玩家进程和怪物进程被创建的时候都会通过mod_battle:start_link()创建一个战斗进程。

该战斗进程的state,用于保存玩家上次出手或者使用技能的时机,用于cd的判断

-record(state, {
        last_attack_time=0,   % 上次出手时间
        last_skill_time = []  % [{技能id,上次时间}]
}).

开始战斗的时候,生成攻击方和防守方的#battle_status记录实例,战斗中的伤害通过这两个记录实例来计算

战斗存在三种情况:玩家打怪,怪打玩家,玩家打玩家

战斗的大致流程:
    攻击方指定防守方Id,指定使用的技能Id,向服务器发送攻击请求(20001,20003)
    进入攻击方的战斗进程内,通过ETS获取防守方信息,进行攻击距离,cd,技能配置等检查
    检查通过,计算双方持续效果buff,计算技能buff,计算伤害
    更新持续buff列表,给防守方玩家(怪物)进程发送消息(Hp,Mp,坐标变换),更新双方状态,回写ETS,给客户端广播战斗结果
    
代码流程:

mod_battle:battle(Pid, Data) ->        发到战斗进程处理
start([Aer,Der,SkillId,State]) ->          初始化双方#battle_status
attack([Aer1,Der1,SkillId],[Aer,State]) ->        判断攻击是否有效,保存新的攻击者状态
skill(Aer,Der,SkillId,SkillLv,State) ->               判断技能cd,距离,群攻单攻
    double_active_skill(Aer,Der,SkillData,State)            群体技能
    /single_active_skill(Aer,Der,SKillData,State)    ->    单体技能
        cale_aer/der_last_effect/4    计算自身持续buff
        + cale_active_effect/6          计算技能buff
        + cale_hurt/2                        计算伤害

具体战斗流程:

先进行出手频率检查:Time - State#state.last_attack_time >= Aer#player_status.att_speed

出手频率检查通过,进入start([Aer, Der, SkillId, State]).

%%开启一个战斗服务
start([Aer, Der, SkillId, State])->
    Aer1 = init_data(Aer),
    Der1 = init_data(Der),
    attack([Aer1, Der1, SkillId], [Aer, State]).

将#player_status或者#ets_mon转换为#battle_status,传入attack([Aer, Der, SkillId], [AerInit, State])

attack([Aer, Der, SkillId], [AerInit, State])
获取玩家技能详细数据,在skill(Aer, Der, SkillId, SkillLv, State)中使用技能
技能使用结果分为两种情况:使用成功和使用失败
    
skill(Aer, Der, SkillId, Lv, State)
使用主动技能,进行如下几项判断:
    技能配置是否存在:获取#ets_skill
    技能cd是否已达到:State#state{last_skill_time = [{SkillId, Time}]
    玩家MP是否足够:#ets_skill.data内的{mp_out, MpOut}
    攻击距离是否足够:#ets_skill.attarea
    主动还是被动技能,被动技能则使用失败:#ets_skill.type
    判断是群攻还是单攻:#ets_skill.mod,分别调用double/single_active_skill/2
    
single_acvite_skill(Aer, Der, SkillData, State)
单体攻击
计算攻击方原有加成buff:buff:cale_aer_last_effect(Aer#battle_status.batle_status, Aer, [], NowTime)
其中#batle_status.battle_status为[{K,V,T}]的列表结构,K为加成的属性名称,V为加成属性的值,T为加成过期时间,与当前时间比较,如果没过期则给#battle_status计算加成,如果过期了则把该加成去掉,最后返回一个#battle_status
防守方加成buff计算同理
之后计算技能加成:cale_active_effect(Data , Aer1, Der1, Aer#battle_status.battle_status, Der#battle_status.battle_status, Time)
其中Data为技能效果列表,在#ets_skill.data中,格式为{K,V}
除了直接加成数值的效果,有一些值得注意的:
    drug:加毒,需要定时给防守一方的战斗进程发送消息,造成持续掉血效果
    shield:法盾,持续一定效果,给防御方的#battle_status.battle_status加效果
    last_def_del:持续减防,带有概率,在加持续效果之前先进行随机

通过随机数值判断是否打退,若打退则返还新的防守方坐标[X, Y]

计算伤害:将攻击方和防守方的二级属性传入cale_hurt,返还[Hpb,Mpb,Hurt,Status]
伤害计算公式:

    命中:Hit = (0.25 + Hita / (Hita + Dodgeb) * 1.3)
    暴击:Crit = Crita/(Crita + Tenb)
    基本伤害:Att = (Atta*Atta) div (Atta + Defb)
    最终伤害:没暴击trunc(Att/3),暴击trunc(Att*(1+Critical)/3)

给防御方发送战斗结果
    防御方是怪物:在怪物进程内接收,修改#ets_mon的Hp,Mp,X,Y并写回?ETS_MON,触发人物加经验,任务杀怪,副本杀怪,怪物掉落事件
    防御方是人物:在玩家进程内接收,修改#player_status,如果玩家死亡则触发死亡事件

生成战斗结果:
    战斗成功:调用send_msg,发送20001协议,将攻击者新状态写回
    战斗失败:调用battle_fail,发送20005协议

群攻的情况:double_active_skill(Aer,Der,SkillData,State)
先计算攻击方的持续buff效果和技能buff
获取攻击者附近的人物和怪:get_user_for_battle和get_mon_for_battle
    从?ETS_ONLINE和?ETS_MON中取
其他计算防御方的buff,计算伤害同单攻

使用辅助技能:use_assist_skill([Aer,Der,SkillId,State])
    玩家是否有该技能
    技能配置是否存在
    判断技能cd:#ets_skill.cd
    判断MP:从#ets_skill.data中取{mp_out, MpOut}
    判断释放目标:#ets_skill.obj,或者传入的Der={}
        以自己为目标:
        判断单攻还是群攻:#ets_skill.mod
            单攻:single_assist_skill(Aer,Der,SkillData) 作用于自己
            判断是否持续性类型:#ets_skill.lastime > 0
                持续性类型:cale_assist_last_effect,并将新的持续效果发送给玩家进程并写回,发送20006协议
                一次性使用类型:cale_assist_one_effect,目前只有加血,将血量写回玩家进程,发送20006协议
            群攻:double_assist_skill(Aer,_Der,SkillData),这里是作用于队伍成员,从?ETS_ONLINE中找,其他同单攻
        以他人为目标:先用check_attarea/2判断攻击距离,之后同上
    修改完毕的#player_status.battle_status,通过'BATTLE_STATUS'发送回玩家进程并写回

04-08 13:05