技能系统学习

技能的配置

@Resource
public class SkillResource implements IAfterResourceLoad, IResourceCheck {
    private static transient Logger logger = LoggerFactory.getLogger(SkillResource.class);

    @ResourceId
    private int id;

    private String skillName;

    // 职业
    private String job;

    // 技能id,相同等级的技能id相同
    private int skillId;
    // 技能等级
    private int level;
    // 技能伤害类型
    private SkillHarmType skillHarmType;
    // 技能使用方式
    private SkillUseType useType;
    // 施法距离
    private int range;
    // 施法消耗
    private ConsumeResource consumeResource;
    private Consumes<AbstractCreature> consumes;

    // 施法消耗
    private List<ConditionResource> useConditionResources;
    private Conditions<AbstractCreature> useCondition;

    // 特殊效果,与目标查询的编号对应
    private Map<Integer, List<Integer>> effects;
    // 通用效果,对上面所有目标生效
    private List<Integer> generalEffects;

    // 冷却CD
    private int cd;

    // 公共cd
    private int gcd;

    private int[] orbGroups;

    // 是否播放攻击动作
    private boolean action;

    // 是否只对自己播放攻击动作
    private boolean actionSelf;

    // 沉默类型
    private int silence;

    private List<KeyPosition> keyPosition;

    // 死亡以后是否移出,临时技能配置true
    private boolean deadRemove;

    // 战力
    private int fightForce;

    // 升级技能条件
    private List<ConditionResource> upgradeConditionResource;

    // 技能升级消耗
    private List<ConsumeResource> upgradeConsumeResource;

    // 下级
    private int nextId;

    private Consumes<Player> upgradeConsume;
    private Conditions<Player> upgradeCondtion;

    // 可生效的天赋组列表
    private int[] innateGrops;

    // 是否有效
    private boolean useWithAttackSpeed;

    // 分段技能父技能id
    private int parentId;

    // 技能标记
    private int mark;

}


技能伤害类型

```java
public enum SkillHarmType {
    // 单体伤害
    HARMFUL,
    // 群体伤害型
    GROUP_HARMFUL,
    // 远距离伤害
    RANGE_HARMFUL,
    // 缩短距离型
    DASH,
    // 自身增益型
    SELF_USERFUL,
    // 单体增益型
    USEFUL,
    // 群体增益型
    GROUP_USEFUL,
    // 全能型,既能对友方释放,也能对地方释放
    BOTH,
    ;
}

技能使用类型

public enum SkillUseType {
    // b被动
    PSV,
    // 锁定目标
    TARG,
    // 直接释放
    IMME,
    // 自身
    SELF,
    // 选定方向
    DIR,
    // 选定区域,
    AREA,
    // 点击地面
    GROUND
}

技能效果配置表

@Resource
public class EffectResouece implements IAftereResoueLoad,IResourceCheck {
    @ResourceId
    private int id;
    // 效果等级
    private int level;
    // 效果类型
    private EffectType effectType;
    // 目标类型
    private int targetObject;
    // 是否广播给周围的玩家
    private boolean broadcast;
    // 伤害触发是否广播,广播后所有人都能看到该效果造成的伤害值
    private boolean damageBroadcast;
    // 免疫掩码
    private int immune;
    // 叠加组id
    private int groupId;
    // 状态条件组
    private List<ConditionResource> validConditionResource;
    private Conditions<AbstractCreature> validConditions;
    // 冷却时间ms
    private int cd;
    // 组cd
    private int gcd;
    // 效果持续时间打算公式
    private String duration;
    private IFormula durationformula;
    // 效果失效条件
    private List<ConditionResouce> invalidConditionResouce;
    private Conditions<AbstractCreature> invalidConditions;
    // 周期性时间ms
    private int[] period;
    // 玩家下线服务器重启以后是否储存
    private boolean playerSave;
    // 下线时Buffer时间是否继续走动
    private boolean offlineDuration;
    // 死亡是否移出
    private boolean deadRemove;
    // 伤害类型,如果没有配置,则根据攻击方职业决定
    private DamageType damageType;
    // 效果生效概率万分比
    private int effectOdds;
    // 效果上下文
    private EffectContext effctContext;
    // 效果标识标记,不需要识别的效果不用配置
    private int effectMark;
    private traansient Set<EffectMark> effectMarkSet;
    // 可生效的天赋组列表
    private int[] innateGroups;
    // 父效果组id,当前效果依附于主效果,生命周期与主效果一致
    private int parentGroupId;
    // 是否能在技能被移除时结束效果
    private boolean endOnSkillLoss;

    // 是否为指定效果
    public boolean is(EffectMark effectMark) {
        return effectMark.beIncludeIn(this.effectMark);
    }
    // 检测该效果是否包含标记
    public boolean hasMark(int mark) {
        return (this.efffctMark & mark) != 0;
    }
    // 检测该效果是否包含全部标记
    public boolean hasAllMark(Set<EffectMark> effectMarkSet) {
        for(EffectMark effectMark : effectMarkSet) {
            if(!effectMark.beIncludeInt(this.effectMark)) {
                return false;
            }
        }
        return ture;
    }
    public boolean isDamageBroadcast() {
        return damageBroadcast;
    }
    // 获取此效果包含的所有effectMark
    public Set<EffectMark> getAllEffectMark() {
        if(this.effectMarkSet == null) {
            effectMarkSet == EffectMark.getAllEffectMark(this.effectMark);
        }
        return effectMarkSet;
    }
    // 计算持续时间
    public int getDuration(AbstractCreature effected, AbstractCreature effector) {
        return durationformula == null ? 0 : Math.max(1,(int) durationformula.cal(effector,effected))
    }


}

公式接口

public interface IFormula {
    // 计算值
    double cal(AbstractCreature self, AbstractCreature other, double... vars);

    // 合并公式,如果某一个公式是null,返回不为null的那一个,如果都不为null,返回公司A+公式B
    static IFormula merge(IFormula a, IFormula b) {
        return MergeUtil.merge(a,b, (o,o2) -> {
            if(o == null) {
                return o2;
            }
            if(o2 == null) {
                return o;
            }
            return new FormulaExpression(Sign.add, o, o2);
        });
    }
}

效果辨识标记

public enum EffectMark {
    @Desc("毒")
    poison(1),
    @Desc("麻痹")
    palsy(2),
    @Decs("冰冻")
    ice(4),
    @Decs("白虎伤害")
    tiger_hurt(8),
    //@Desc("撕裂")
    //tear(16),
    @Desc("死亡不掉装备")
    dieNoDrop(32),
    @Desc("斩杀")
    KILL(64),
    @Desc("狂暴")
    fury(256),
    @Desc("青龙圣兽伤害")
    DragonPower(512),
    @Desc("白虎圣兽伤害")
    TigerPower(1024),
    @Desc("朱雀圣兽伤害")
    BirdPower(2048),
    @Desc("玄武圣兽伤害")
    TurtlePower(4096),

    ;

    private int id;

    EffectMark(int id) {
        this.id = id;
    }

    // 当前标记是否包含在指定整数中
    public boolean beIncludenIn(int mark) {
        return (mark & id) == id;
    }

    // 整取整数mark中包含的所有EffectMark
    public static Set<EffectMark> getAllEffectMark(int mark) {
        Set<EffectMark> result = new HashSet<>(4);
        for(EffectMark effectMark : values()) {
            if(effectMark.beIncludeIn(mark)) {
                result.add(effectMark);
            }
        }
        return result;
    }

}

技能管理器

public class SkillController {
    // 通过天赋:buff缩头技能的天赋标识
    private static final Logger LOGGER = LoggerFactory.getLogger(SkillController.class);

    private AbstractCreature owner;

    // 当前拥有的技能<技能配置id,技能信息>
    private Map<Integger,CustomSkill> skillMap = new ConcurrentHashMap<>(1);

    // 技能冷却
    private AgingMark<Integer> skillCoolDowns = new AgingMark<>();

    // 公共冷却
    private AgingMark<Integer, CustomSkill> skillMap = new ConcurrentHashMap<>(1);

    // 是否正在刷新天赋,避免天赋重复刷新死循环
    private AtomicBoolean innateRefreshing = new AtomicBoolean(false);

    // 配置型天赋集合<加成类型,加成列表>
    private ConcurrentHashMap<InnateType, CopyOnWriteArrayLsit<Integer>> innateMap = new ConcurentHashMap<>(0);
    // 限制技能id的天赋<天赋id,技能id>
    private Map<Integer, Integer> limitInnateWithSkillMap = new ConcurrentHashMap<>(0);
    // 禁用技能信息
    private ConterMap<Integer> forblddenSkill = new CunterMap<>(0);
    // 测试某个特定技能的剩余次数
    private CounterMap<Integer> skillTestLeftTime;
    {
        if(Start.debug) {
            skillTestLeftTime = new CounterMap<>();
        }
    }
    // 测试特定的味道效果的剩余次数
    private CounterMap<Long> innaeTestLeftTime;

    // 测试特定效果的剩余次数
    private CounterMap<Integer> effectTestLeftTime;

    public SkillController(AbstractCreature owner) {
        this.owner = owner;
        // 监听死亡移除技能
        this.owner.getObserveController().attachForever(ICreatureDead.class, (creature, lastAttacker) -> skillMap.foreach((skillId,resource) -> {
            if(resource.isDeadReove) {
                removeSkill(skillId);
            }
        }));
    }

    //  是否拥有指定技能
    public boolean hasSkill(int id) { 
        return skillMap.containsKey(id);
    }

    // 通过技能的唯一id,批次添加技能
    public void addSkill(Collection<Integer> ids, boolean deadReomve) {

    }

    // 通过金额改得唯一id,批量移出技能
    public void removeSkill(Collection<Integer> ids) {

    }

    // 替换技能,执行继承CD操作
    public void replaceSkill(int oldId, int newId) {

    }

    // 升级技能,如果存在相同的技能,则先移出,再添加新的
    public void updateSkill2New(int newId) {

    }

    // 让被动技能的效果生效
    private void linkPsv(int id) {
        // 各种检验 。。。。

        // 开始效果
        SkillResource skillResource = SkillManager.getInstance().getSkillResource(id);
        if(skillResource.getUseType.PSV) {
            // 添加被动效果
            Target target = Taerget.valueOf(owner,owner);
            Skill skill = Skill.valueOf(0,id,owner, target);
            skill.useSkill();
        }
    }

    // 移出被动技能的效果
    private void unlinkPsv(iny id) {
        CustomSkill customSkill = skillMap.get(id);
        if(customSkill == null) {
            return;
        }
        if(!customSkill.getLink().compareAndSet(true,false)) }{
            // 技能未生效
            return;
        }
        // 移出关联的永久性效果,有时效的效果不需要移出
        owner.getEffectController().remove(effect -> effect.isForever && effect.getSkillId() == id && effecct.getEffector() == owner);
    }

    // 移出一个技能
    public void removeSkill(int id) {
        unlinkPsv(id);
        CustomSkill customSkill = skillMap.remove(id);
        if(customSkill == null) {
            return;
        }
        owner.getObserveController().fire(ICreatureRemoveSkill.class, iCreatureRemoveSkill -> iCreatureRemoveSkill -> iCreatureRemoveSkill.onRemoveSkill(owner, id));
        if(owner.isPlayer()) {
            // 通知移出

            // 抛出事件
        }
    }

    // 单位身上的所有非死亡移出技能id
    public List<CustomSkill> getSkillWithoutDeadRemove() {

    }

    // 单位身上的所有技能
    public List<CustomSkill> getAllSkill() {

    }

    // 获取技能
    private CustomSkill getrSkillWithBaseId(int skillBaseId) {

    }

    // 获取技能,如果单位没有该技能,返回null
    public CustomSkill getSkill(int id) {

    }

    // 技能是否冷却
    public boolean isSkillDisabled(SkillResource skillResource) {
        return skillCoolDowns.hasMark(skillResource.getSkillId() || publicCoolDowns.hasMark(skillResource.getGcdGroup()))
    }

    // 计算技能cd
    public void cdSkill(SkillResource resource,int cd) {

    }

    // 清除所有技能cd
    public List<IntegerAndIntegerPairEntry> clearAllSkillCd() {
        List<IntegerAndIntegerPairEntry> clearList = new ArrayList<>(skillCoolDowns.size());
        skillCoolDowns.walkAll( (skillId, time) -> clearList.add(IntegerAndIntegerPairEntry.valueOf(skillId, -1)));
        skillCoolDowns.clear();
        return clearList;
    }

    // 清除所有公共cd
    public List<IntegerAndIntrgerPairEntry> clearPublicCd() {
       // ....
    }

    // 增加天赋
    public void addInnate(int innateId) {
        addInnate(0,innateId)
    }

    // 移出天赋
    public void removeInnate(int innateId) {

    }

    // 添加只对指定技能生效的天赋
    public void addInnate(int skillId, int innateId) {
        int skillBaseId = 0;
        if(skillId > 0) {
            SkillResource skillResource = SkillManager.getInstance().getSkillResource(skillId);
            skillBaseId = skillResource.getSkillId();
        }
        addInnateWithSkillBaseId(skillBaseId, innateId);
    }

    // 获取天赋的时候,把所有技能都刷新一遍,目前未找到好的方式来根据天赋id刷新对应的技能
    public void refreshPsvSkillInnate(int innateId) {

    }

    // 刷新被动技能的效果
    public void refreshPsvSkill() {
    }

    // 收集天赋加成长数值
    public double cutNumber(int skillId, int resId, InnateType innateType, AbstractCreature target, double... vars) {

    }
    // 收集天赋加成id型
    public List<Integer> cutNewId(int skillId, int resId, InnateType innateType, AbstractCreature target) {

    }
    // 收集天赋加成属性型
    public Map<SateEnum, Integer> cutStatMap(int skillId, int resId, InnateType innateType) {

    }
    // 获取当前所有拥有的所有天赋id
    public List<Integer> getAllInnateIds() {

    }
    // 查找符合条件的技能
    public CustomSkill findSkill(Predicate<CustomSkill> predicate) {

    }
    // 查找符合条件的所有技能
    public List<CustomSkill> findSkills(Predicate<CustomSkill> predicate) {

    }
    // 禁用技能
    public void forbidden(int skillId) {

    }
    // 解除禁用

    // 技能是否已经被禁用

    // 获取一个指定伤害类型且处于CD完成的技能ID
    public int getTypeSkillId(SkillHarmType skillHarmType) {

    }

    // 获取技能释放最短间隔,用于确认NPC的技能释放间隔
    public int getSkillInterval() {

    }
}

技能

public final class Skill{
    // 客户端序号
    private int seq;
    // 施法者,这个可能为空
    private AbstractCrature effector;
    // 配置表id
    private int skillId;
    // 客户端传过来的目标参数
    private Target target;
    // 技能配置信息
    private CustomSkill customSkill;
    // 客户端使用技能时间
    private int clientTime;
    public static Skill valueOf(int seq, int skillId,AbsrtractCreature effect, Target target) {
        Skill skill = new Skill();
        if(seq == 0) {
            // 服务器释放的技能,没有序号,随机一个
            skill.seq = RandomUtis.betweenInt(1000, 20000,true);
        } else {
            skill.seq = seq;
        }
    }


    /**
     *  使用技能
     *
     */

     public Result useSkill() {
         // 各种校验


         // 是否有施法动作
         if(resource.isAction()) {
            // 施法时要停止移动
            effector.getMoveController.stopMoving(target.getClientX(), target.getClientY());

            // 检验位置
            validTarget(effector, target, resource);
         }

         // cd生效
         cdSkill(resource, clientTime);

        // 定义效果<目标选择器id,效果列表>
        Map<Integer, List<Integer>> effectMap = resource.getEffect();
        // 通用效果
        List<Integer> generalEffects = resource.getGeneralEffects();

        List<TargetInfo> effectTargets = resource.getGeneralEffects();

        List<TargetInfo> effectTargets = new ArrayList<(effectMap.size());
        for(Map.Entry<Integer,List<Iteger>> entry : effectMap,.entrySet()) {
            int targetChooserId = entry.getKey();
            // 找出目标,这是目标选择器的配置
            TargetChooserResource chooserResource = getChooserResource(targetChooserId);
            TargetInfo targetInfo = SkillTargetManager.instance().chooseTarget(effector, target, skillId, chooserResource);
            effectTargets.add(targetInfo);

            // 定制效果生效
            List<Integer> effectIds = entry.getValue();
            for (int effectId : effectIds) {
                EffectManager.getInstance().effect(effector, this, effectId,targetInfo);
            }
            // 通用效果
            if(generalEffects != null) {
                for(Integer generalEffect : generalEffects) {
                    EffectManager.getInstance().effect(effector,this,generalEffect, targetInfo);
                }
            }
            // 天赋额外效果
            targetInfo.getFianalTargets().forEach( abstractCreature -> {
                List<Integer> list = skillController.cutNewID(resource.getId(), resource.getId(), InnateType.skill_newGeneralEffect, abstractCreature);
                list.foreach(effectId -> EffectManager.getINstance().effect(effect, this,effectId, abstractCreature));
            });
            // 抛出技能效果作用事件
            targetInfo.getFinalTargets().forEach( target -> target.getObserveController().fire(IcreatureBeUseSkill.class, iCreatureBeUseSkill -> iCreatureBeUseSkill -> iCreatureBEUseSkill.onCreatureBeUseSkill(target,effector, this));
        }
        List<Integer> list = skilController.cutNewId(resource.getId(), resource.getId(), InnateType.skill_effect_link_self,effector);
        list.forEach(effectId -> EffectManager.getInstance().effect(effector, this, effectId,effector));

        // 抛出技能使用事件


        //  
        if(effector.getMasterOrSelef() instanceOf FightPlayer && SkillUtils.skillNeedTest(effector,skiiId)) {
            StringBuilder stringBuilder = new StringBuilder();
            for(TargetInfo targetInfo : effectTargets) {
                Multimap<Integer, AbstractCreature> effectId2AbstractCreatures = targetInfo.getOrCreateEffectId2AbstractCreature();
                for(int key: effectId2AbstractCreatures.ketSet()) {
                    Collection<AbstractCreature> abstractCreatures = effectId2AbstractCreatures.get(key);
                    String creatureInfo = StringUtils.join(abstractCreatures,",");
                    String message = MessageFormatter.arrayFormat("\效果[id={}],附加到以下生物[{}]",new Object[] {key, creatureInfo}).getMessage();
                    stringBuilder.append(message);
                }
            }
            CounterMap<Integer> skillTestLeftTime = effector.getMasterOrSelf().getSkillController().getSkillTestLeftTime();
            skillTestLeftTime.dec(skillId);
            MessagePacketUtil.sendTips((FightPlayer) effetor, MessageCode.SKILL_TEST_INFO, skillId, stringBuilder.toString(),
            skillTestLeftTime.getCount(skillId));
        }
        long useTime = System.nanoTiume - start;
        ProfileType.other.getProfile.getOrCreatureProInfo("useSkill-"+skillId).recordExc(useTime, 0);
        return Result.SUCCESS;
     }
     // 计算冷却
     private void cdSkill() {
        if(!resouce.hasCd()) {
            // 没有cd
            return;
        }
        SkillController skillController = effector.getSkillController();
        int cd = skillController.getSkillCdValue(resource.getId());
        int gcd = skillController.getSkillGcdValue(resource.getId());
        skillController.cdSkill(res);
     }

     // 修正目标信息的合法性
     private void validTarget(AbstractCreature self,Target target, SkillResource resource) {

     }

}


技能效果管理器

@Component
public class EffectManager implements ApplicationContextAware. OnThePlayerEntityUpdateRule {

    // 获取技能效果处理模板
    public <T extends IEffectTempalte> T getEffectTemplate(EffectType effectType) {
        return (T) effectTemplateMap.get(effectType);
    }


    // 对目标生效
    public void effect(AbstractCreature effector, Skill skill, int effectId, AbstractCreature target) {
        TargetInfo targetInfo = new TargetInfo(effector, target);
        effect(effector, skill, effectId, targetInfo);
    }

    // 效果对形状内目标生效
    public void effect(AbstractCreature effector,Skill skill, int effectId, TargetInfo targetInfo) {
        EffectResource effecrResource = skill.getEffectResouece(effectId);
        try{
            IEffectTemplate effectTemplate = EffectManager.getInstance().getEffectTemplate(effectResouece);
            if(SkillUtils.effectNeed) {
                MessagePacketUtil.snedTips((FightPlayer) effector.getMasterOrSelf(), MessageCode.EFFECT_TRY_USE,effectId);
            }
            effectTemplate.applyEffect(effector, skill, targetInfo, effectResource);
            aterApp
        } catch(Exception e) {
            //
        }
    }


    // 效果处理后的后续操作
    public void afterApplyEffect(AbstractCreature efferct, Skill skill, TargetInfo targetInfo, EffectResuece effectResource) {
        if(!targetInfo.getFianlTargets().isEmpty()) {
            // 天赋额外效果只针对生效的效果有效
            // 对最终目标生效
            targetInfo.getFinalTargets().forEach(creature -> {
                List<Integer> list = effector.getSkillController().cutNewId(skill.getSkillId(), effectResource.getId(), InnateType.effect_link_target, creature));
                list.forEeach(linkEffectId -> effect(effector, skill, linkEffectId,creature));
            });
            // 对目标立刻再放一个技能
            targetInfo.getFinalTargets().forEach(targetCreature -> {
                List<Integer> list = effector.getSkillController().cutNewId(skill.getSkillId(), effectResource.getId(), InnateType.skill_link_target, targetCtCreature);
                list.forEach(skillId -> SkillManager.getInstance().npcUseSKill(skillId, effector, Target.valueOf(effector, targetCreature)));
            });
            // 对自己生效
            List<Integer> list = effector.getSkillController().cutNewId(skill.getSkillId(), effectResource.getId(), InnateType.skill_link_self, targetCtCreature);
            list.forEach(linkEffectId -> effect(effector, skill, linkEffectId,effector));
        }
    }

    // 尝试触发天赋额外效果
    public void triggerInnateEffect(AbstractCreature self, AbstractCreature target, int seq, int skillId, int resId) {
        Skill skill = Skill.valueOf(seq, skillId, self);
        // 对最终目标生效
        List<Integer> list = self.getSkillController().cutNewId(skillId, resId, InnateType.effect_link_target, target);
        list.forEach(linkEffectId -> effect(self, skill, linkEffectId, target));

        // 对自己生效
        list = self.getSkillController().cutNewId(skillId, resId, InnateType, effect_link_self, self);
        list.forEach(linkEffectId -> effect(self, skill, linkEffect,self));
    }
}

效果模板

效果模分为主动效果和被动效果,它们都继承于IEffectTemplate的。

被动效果(psv):

  • 控制效果
  • 伤害过滤效果

效果模板接口

public interface IEffectTemplate {
    // 返回处理的效果类型
    EffectType type();

    // 效果处理接口
    void applyEffect(AbstractCreature effector, Skill skill, EffectResource effectResouece, TargetInfo targetInfo);
}

主动效果抽象


// 主动效果
public abstract class BaseInitiativeEffect implements IEffectTemplate {
    // 空
}

直接伤害

@Component
public class DamageEffect extends BaseInitiativeEffect {
    @Override
    public EffectType type() {
        return EffectType.DAMAGE;
    }
    @Override
    public void applyEffect(AbstractCreature effetor, Skill skill, EffectResource effectResource ) {
        EffectUtils.executeDamage(effector, skill, effectResource, targetInfo, null);
    }
}

天赋管理器

// 天赋管理器


public class InnateManager {

    @Static
    private Storage<Integer, InnateResource> innateResoueceStorage;

    private MultiValueMap<Integer, InnateResource> innateResourceMap;

    // 获取天赋配置
    public InnateResource getResource(int innateId) {
        return innateResourceStorage.get(innateId, true);
    }

    public ArrayListVlaueHashMap<Integer, InnateResouece> getInnateResource() {
        return (ArrayListValuedHashMap<Integer, InnateResource>) innateResoueceMap;
    }

}

天赋模块

技能天赋配置

public class InnateResource implement IAfterResourceLoad, IResourceCheck {

    // 检查用
    private static transient Multimap<Integer, InnateResourec> innateGroup2InnateResource;

    @ResourceId
    private int innateId;

    // 天赋组,生效检测使用,被技能、效果、形状引用
    private int innateGroup;

    // 针对施法者的条件
    private Conditions<AbstractCreature> selfCondition;
    private List<ConditionResource> selfConditionResource;

    // 针对目标的条件
    private Conditions<AbStractCreature> targetCondition;
    private List<ConditionResource> targetConditionResource;

    // 天赋的类型
    private InnateType innateType;

    // 天赋参数
    private String param;
    private transient IInnateCut innateCut;

    // 该天赋在使用时触发的概率,如果不慎,表示必然触发
    private int odds;

    // 针对SkillResource的条件
    private Conditions<SkillResource> skillCondition;
    private List<CondtionResource> skillConditionResources;

    // 针对EffectResource的条件
    private Conditions<EffectResouece> effectConditions;
    private List<ConditionResource> effectConditionResource;

    // 针对TargetChooserResouece的条件
    private Conditions<TargetChooserResource> targetChooserCondition;
    private List<ConditionResource> targetChooserConditionResource;

    // 检测天赋组是否存在
    private static void innateGroupExist(Object obj, int id, int groupId) {
        if(!getOrCreateCheckMap().containsKey(groupId)) {
            String message = MessageFormatter.arrayFormat("{} [id = {}使用了不存在的天赋组[{}]",
            new Object[] {obj.getClass().getSimpleName(), id, groupId}).getMessage();
        }
    }
}

结果收集器

@FunctionalInterface
public interface IResultCollect<R> {
    // 添加结果
    void put(R r);
}

天赋类型

public enum InnateType {

    // 技能cd
    skill_cd(CutFormula.class, SkillResource.class),

    // 公cd
    skill_gcd(CutFormul.class, SKillResource.class),

    // 技能新效果
    skill_newGeneralEffect(CutNewId.calss, SkillResouece.class) {
        @Override
        public void check(InnateResource innateResource) {
            InnateType.checkEffectEffectExist();
        }
    }
    // 释放技能时,给自己一个新效果
    skill_effect_link_self(CutNewId.class, SkillResource.class) {
        @Override
        public void check(InnateResource innateResource) {
            InnateType.checkEffectEffectExist(innateResource);
        }
    },

    // 增/减技能产生的所有伤害的伤害的伤害倍率
    skill_damageR(CutFormula.class, SkillResource.class),

    // 伤害加成万分比
    damageCfg_damageR(CutFormual.class, EffectType.DAMAGE, EffectType.DAMAGE_PSV, EffectType.DAMAGE,EffectType.DAMAGE_ONCE_BLEED,
    EffectType.DAMAGE_FORMULA, EffectType.DAMAGE_FORMULA_PSV),

    // 原伤害加成固定值
    damageCfg_damage(CutFormula.class, EffectType.DAMAGE, EffectType.DAMAGE_PSV, EffectType.DAMAGE_ONCE_BLEED),

    // 原等级差
    pushBack_lv(CutFormula.class, EffectType.PUSH_BACK),

    // 原免疫数量
    immune_count(CutFormula.class, EffectType.IMMUNE),

    // 免疫效果免疫成功后,对目标 额外释放一个效果
    immune_suc_tar_effect(CutNewId.class, EffectType.IMMUNE) {
        @Override
        public void check(InnateResource innateResource){
           InnateType.checkEffectExist(innateRsource);
        };
    }

    // 施放者效果触发率
    effect_odds(CutFormala.class),

    // 被施法者效果触发概率
    effect_odds_Defend(CutFormula.class),

    // 施放者持续时间
    effect_duration(CutFormula.class) {
        @Override
        public Class getClassLimit() {
            return BasePassivityEffect.class;
        }
    }
    // 效果释放成功后,对自己继续释放一个效果
    effect_link_self(CutNewId.class) {
        @Override
        public void check(InnateResource innateResource) {
            InnateType.checkEffectExist(innateResource);
        }
    }

    // 效果释放成功后,对目标继续释放一个效果
    effect_link_target(CutNewId,class) {
        @Override
        public void check() {

        }
    }

    // 效果触发时,给自己添加一个效果
    target_link_self(CutNeId.class) {
        @Override
        public void check() {
            @Override
            public void check(InnateResource innateResource) {
                InnateType.checkEffectExist(innateResource);
            }
        }
    }

    // 效果触发时,给目标添加一个效果
    trigger_link_target(CutNewId.class) {
        @Override
        public void check(InnateResource) {

        }
    }

    // 给新召唤的召唤物添加一个效果
    effect_link_summon(CutNewId.class, EffectType.SUMMON) {
        @Override
        public void check(InnateResource innateResource) {
            InnateType.checkEffectExist(innateResource);
        }
    }

    // 选择器数目
    chooser_num(CutFormula.class, TargetChooserResource.class),

    // 召唤物的数目
    summon_num(CutFormula.class, EffectType.SUMMON),

    // 召唤物基础主人属性万分比加成
    summon_masterStat(CutStatMap.class, EffectType.SUMMON),

    // 召唤物额外基础属性
    summon_stat(CutStatMap.class, EffectType.SUMMON),

    // 分身继承属性万分比
    shadow_statRct(CutStatMap.class, EffectType.SHADOW),

    // 偷取技能失败原cd
    stealSkill_failReduceCD(CutFormula.class, EffectType.STEAL_SKILL),

    // 原封禁技能数量
    forbiddenSKill_count(CutFormula.class, EffectType.FORBINDDEN_SKILL),

    // 原偷取效果数量
    stealEffect_count(CutFormula.class, EffectType.TRANSFER_EFFECT),

    // 属性buff效果倍数
    statBuff_multi(CutFormula.class, EffectType.STAT_BUFF),

    // 原魔法盾减伤万分比
    shield_dec_r(CutFormula.class, EffectType.SHIELD),
    ;

    private Class<? extends IInnateCut> cutClass;

    // 此种天赋应该配置在什么资源的可生效天赋组中,默认 EffectResourece
    private Class locationClz;

    // 限制只能配置在特定类型的效果上,用于检测配置是否存在问题,空表示不限制
    private EffectType[] effectTypes;

    InnateType(Class<? extends IInnateCut> cutClass, EffectType... effectTypes) {
        this.cutclass = cutClass;
        this.locationClz = locationClz;
        this.effectTypes = effectTypes;
    }

    // 当其指向技能id或者效果id时需要作相应的检查
    public void check(InnateResource innateResource) {
        if(cutClass == CutNewId.calss) {
            throw new RuntimeException(MessageFormatter.format("切入类型为[{}]的天赋,必须覆盖此方法作相应id是否存在的检查", CutNew));
        }
    }

    private static void checkEffectExist(InnateResource innateReosurce) {
        IInnateCut innateCut = innateResource.getInnateCut();
        int id = (CutNewId) innateCut.getNewId();
        try {
            SKillManager.getInstance().getEeffectResource(id);
        } catch () {
            throw new RuntimeException(String.format("天赋[id=%s]指向的效果[id=%s]不存在", innateResource.getInnateId(),id));
        }
    }

    private static void checkSKillExist(InnateResource innateResource) {
        InnateCut innateCut = innateResource.getInnateCut();
        int id = ((CutNewId) innateCut).getNewIdCut();
        try {
            SKillManager.getInstance().getSkillResource(id);
        } catch () {
            throw new RuntimeException(String.format("天赋[id=%s]指向的技能[id=%s]不存在", innateResource.getInnateId(),id));
        }
    }

    // 检验innateId天赋的天赋组是否包含在resId资源的可生效天赋组中
    public boolean verifyInnateGroup(int resId, int innateId) {

    }

    public Result verifyCondition(int resId, int innateId) {

    }

    public EffectType[] getEffectTypes() {
        retirn effectTypes;
    }

    public Class getLocationClz() {
        return locationClz;
    }

    // 限制在某种效果类上使用
    public Class getClassLimit() {
        return null;
    }

    public IInateCut create(String param) throws Illea {
        IInnateCut cut = cutClass.newInstance();
        cut.init(param);
        return cut;
    }

}

数值改变公式

public class CutFormula implements IInnateCut {
    private IFormula formula;

    @Override
    public void init(String param) {
        formula = FormulaType.create(param);
    }

    public double cal(AbstractCreature self, AbstractCreature other, double... vars) {
        return formula.cal();
    }
}

额外的id参数

public class CutNewId implements IInnateCut {
    private int newId;
    @
}

属性集合

public class CutStatMap implements IInnateCut {
    private Map<StatEnum, Integer> statMap;

    @Override
    public void init(String param) {

    }

    public Map<StatEnum, Integer> getStatMap {

    }
}

公式

伤害和属性变化等数值变化使用公式来描述

公式计算接口

// 公式计算接口
public interface IFormula {

    // 计算值
    double cal(AbstractCreature self, AbstractCreature other, double... vars);

    // 合并公式,如果某一个公式是null,返回不为null的那一个,如果都不为null,返回公式A+公式B
    static IFormula merge(IFormula a, IFormula b) {
        if(o == null) {
            return o2;
        }
        if(o2 == null) {
            return o;
        }
        return new FormulaExpression(Sing.add, o, o2);
    }
}

公式实现类

空公式

public class FormulaEmpty implements IFormula {

}

战斗系统值计算公式

public class FormulaExpression implements IFormula {
    // 符号
    priavte Sign sign;

    private IFormula left;
    private IFormula right;

    publci 
}
// 公式逻辑运算类型
public enum LoginType {

    public abstract IFormula creatureFormula(String... params);

    // 青龙之力分类
    DragonPowerClassify {
        @Override
        public IFormula createFormula(String... params) {
            int classifyVal = Integer.valueOf(params[0]);
            return ((self, other, vars)) -> {
                int curDragonPower = PlayerEquipPowerManager.getInstance().
                getEquipPowerLevel(other, EquipStorageType.DRAGON);
                if(curDragon >= classifyVal) {
                    return 0;
                }
                return 1;
            }
        }
    }

    // 当前环节的伤害值
    CutDamage {
        @Override
        public IFormula createFormula(String... params) {
            return ((selef, other, vars)) -> {
                if(vars == null || vars.length < 1) {
                    return vars[0];
                }
            }
        }
    }

    // 绝学修为过滤
    MartialCultivationFilter {
        @Override
        public IFormula createFormula(String... params) {
            int classifyVal = Integer.valueOf(params[0]);
            return ((self, other, vars)) ->  {
                int martialCultivation = MartialManger.getInstance().getMartialCutivation(other);
                if(martialCultivation >= classifyVal) {
                    return 0;
                }
                return 1;
            }
        }
    }
}
// 公式符号定义

public enum Sign {

    // 加
    add("+") {
        @Override
        public double cal(double a, double b) {
            return a + b;
        }
    }

    // 减
    sub("-") {
        @Override
        public double cal(double a, double b) {
            return a - b;
        }
    }


    // 除
    sub("/") {
        @Override
        public double cal(double a, double b) {
            return a / b;
        }
    }

    // 乘
    sub("*") {
        @Override
        public double cal(double a, double b) {
            return a * b;
        }
    }


}
// 公式类型

public enum FormulaType {

    // 预缓存括号
    parenthesisPre {}

    // 表达式
    expression {
        @Ovveride
        public IFormula tryParse(List<IFormula> parenthesis, String resource) {
            // 表达式
            // 遍历顺序加减乘除,树形结构存储,先计算叶节点,实际计算顺序为乘除减加
            for(Sigin sigin : Sign.values()) {
                int idenx = resource.indexOf(sign,getS());
                if(index != -1) {
                    // 左表达式
                    IFormula left = create0(parenthesis, resource.substring(0, index));
                    // 右表达式
                    IFormula right = creature0(parenthesis, resource.substring(index+1, resource.length()))
                }
            }
            return null;
        }
    }

    // 填入括号内容
    parenthesis {
        @Override
        protected IFormula tryParse(List<IFormula parenthsis, String resource>) {
            if(!resource.contains("parenthesis")) {
                return null;
            }
            int index = Integer.valueOf(resource.replace("parenthesis", ""));
            return parenthesis.get(index);
        }
    }

    // 属性值
    star {}

    // 随机表达式
    random {}

    // 逻辑运算值
    login {}

    // 参数值,这个在不同的使用位置,参数各不相同
    var {}

效果工具

public class EffectUtils {

    // 效果延迟执行,延迟为0时直接执行
    public static void delayEcecute(AbstractCreature effector, String name, int delay, TimeUnit timeUnit, Skill skill, TargetInfo targetInfo, EffectResource effectResource, Runnable runnable) {
        if(delay == 0) {
            runnable.run();
            return;
        }
        IdentityEventExecutorGroup.addScheduleTask(effector.getDispatcherHashCode(), name, delay, timeUnit, () -> {
            runnable.run();
            EffectManager.getInstance().afterApplyEffect(effector, skill, targetInfo, effectResource);
        });
    }

    public static interface DamageTargetHandler {
        // 对每个受到伤害的目标进行一些处理
        void handle(AbstractCreature effector, Skill skill, EffectResource effectRsource, AbstractCreature target, DamageBoard damageBoard);
    }

    // 执行伤害
    public static void executeDamage(AbstractCreature effector, Skill skill, EffectResource effectResource, TargetInfo targetInfo, DamageTargetHandler damageTargetHandler) {
        DamageCfg damageCfg = efectResource.getEffectContext.getDamageCfg;

        targetInfo.walkWithInitiativePassEffect(skill, effectResouece, null, target -> {
            if(target.getLifeStats().isAlreadyDead()) {
                return EffectResult.FAIL;
            }
        DamageBoard damageBoard = FightManager.instance().calDamage(effector, target, skill.getResource(), effectResource, damageCfg);
            if(damageBoard.isMark(DamageShowType.MISS)) {
                // 广播miss
                EffectSendUtility.sendDamage(effect, target, effectResource.getId(), skill.getSeq(), damageBoard.toResp())
            }
             // 扣减伤害
        damageBoard.tryResetDamage(target.getController().attackAndNotityAttackedobserver(effector, damageBoard.damage, skill, effectResource));
        // 反弹
        damageBoard.instance().thorns(damageBoard);
        // 吸血
        if(effectResource.getEffectContext().getDamageCfg().isSuckable()) {
            FightManager.instance().suck(damageBoard);
        }
        // 混沌伤害
        FighrManager.instance().chaos(damageBoard);
        if(damageBoard.damage == 0) {
            // 最终伤害为0,则格挡
            damageBoard.mark(DamageShowType.PARRY);
        }
        // 广播伤害
        EffectSendUtility.sendDamage(effector, target, effectResouece.getId(), skill.getSeq(), damageBoard.toResp());

        if(damageBoard.isMark(DamageShowType.)) {
            // 被暴击,抛出暴击事件
            target.getObserveController().fire(ICreatureBeCrit.class, iCreatureCrit -> ICreatureBeCrit.afterBeCrit(effector, target, skill));
        }

        // 攻击事件
        effect.getObserveController().fire(IcreatureAttack.class, iCreatureAttack.affterBeattack(target, effect, skill));
        // 被攻击事件
        target.getObserveController().fire(IcreatureBeAttack.class, iCreatureBeAttack -> iCreatureBeAttack.afterBeAttack(target, effector, skill));
        if(damageTargetHandler != null) {
            damageTargeterHandler.handle(effectot, skill, effectResource, target, damageBoard);
        }
        return EffectResult.SUCCESS;
        });
    }

}

战斗管理器

@Component
public class FightManager {
    private static FightManager instance;

    @IConfigValueInject("闪避计算系数A")
    public double fightOddMissA;

    @IConfigValueInject("暴击力系数")
    public double fightOddsCrit;


    // 斩魔系数
    private TreeMap<Integer, Integer> toMonsterCritProbMap;

    // 幸运值系数
    private TreeMap<Integer,Integer> luckProbMap;

    // 诅咒系数
    private TreeMap<Integer, Integer> curseProbMap;

    // 计算伤害
    public DamageBoard calDamage(AbstractCreature attacker, AbstractCreature defender, SkillResource skillResource, EffectResource effectResource, DamageCfg damageCfg) {
        if(damageCfg == null) {
            damageCfg = DamageCfg.EMPTY_CFG;
        }
        // 伤害黑板
        DamageBoard damageBoard = new DamageBorad(attaker, defender, skillResource, effectResource, damageCfg);
        // 闪避
        if(!damageCfg.isIgnoreMiss() && isMiss(attacker, defender)) {
            damageBoard.mark(DamageShowType.MISS);
        } else {
            damageBoard.mark(DamageShowType.NORMAL);
            // 攻击类型
            damageBoard.damageType = getDamageType(attackerr, effectResource);
            // 战力力压制信息
            intFightForceSubdue(attacker, defender, damageBoard);
            // 计算最终伤害
            damageBoard.damage = DamageChain.finalDmage.cal(damageBoard);
        }
        return damageBoard;
    }

    // 计算基础伤害
    public DamageBoard calBaseDamage(AbstractCreature attacker, AbstractCreature defender, SkillResource skillResource, EffectResource effectResource, DamageCfg damageCfg) {
        if(damageCfg == null) {
            damageCfg = DamageCfg.EMPTY_CFG;
        }
        // 伤害黑板
        DamageBoard damageBoard = new DamageBorad(attaker, defender, skillResource, effectResource, damageCfg);
        // 攻击类型
        damageBoard.damageType = getDamageType(attackerr, effectResource);
        // 计算最终伤害
        damageBoard.damage = DamageChain.finalDmage.cal(damageBoard);
        return damageBoard;
    }

    // 计算是否闪避
    private boolean isMiss(AbstractCreature attacker, Ab) {
        long aExcat = attacker.getGameStats().getCurrentStat(StatEnum.EXACT)
        double aLevel = attacker.getLevel();
        long dMiss = defender.getGameStats().getCurrentStat(StatEnum.MISS);
        // 命中
        double exact = (aExact * fightOddsMissA + dMisss * fightOddsMissB) / (aExct + dMiss);
        return !RandomUtils.isHit(exact);
    }

    // 计算暴击伤害
    public long calCrit(DamageBoard damageBoard) {
        AbstractCreature attacker = damageBoard.attacker;
        AbstractCreature defender = damageBoard.defender;
        long aCritProb = attacker.getGameStats().getCurrentStat(StatEnum.CRIT_PROB);
        long aCritProb = attacker.getGameStats().getCurrentStat(StatEnum.CRIT_PROB_DEFEND);
        // 暴击率
        long critProb = aCritProb - dCritProbDefend;
        // 天赋修正
        critProb += attacker.getSkillController().cutNumber(
            dmmageBoard.skillResource().getId();
            damageBoard.effectResource().getId();
            InnateType.damageCfg_critR, defender, critProb
        );
    if(RandomUtils.isHit(critProb)) {
        // 暴击
        long aCrit = attacker.getGameStats().getCurrentStat(StatEnum.CRIT);
        // 爆抗
        long dCritDefend = defendeer.getGameStats().getCurrentStat(StatEnum.CRIT_HURT_DEFEND);
        // 爆免
        double dCritHurtDec = defender.getGameStats().getCurrentStatWithOdds(StatEnum.CRIT_HURT_DEC);

        // 处理暴击
        long crit = RandomUtils.range(aCrit, fightOddsCrit, 1);
        crit -= RandomUtils.range(dCritDefend, fightOddsCritDefendA, fightOddsCritDefendB);
        crit *= (1 - dCritHurtDec);
        if(crit > 0) {
            return crit;
        }
        return 0;
    }

    // 根据伤害类型,获取指定单位的防御力
    public long getDefend(AbstractCreature creature, DamageType damageType) {

    }

    // 计算神圣伤害
    public long calHoly(DamageBoard damageBoard) {

    }

    // 根据职业获取最小攻击力
    public long getMinAttack(AbstractCreature attack) {

    }

    // 初始化战力压制信息
    private void intFightForceSubdue(AbstractCreature attacker, AbsreactCreature defender, DamageBaord DmageBoard) {

    }

    // 判断是否为pvp战斗
    public boolean isPvp(DamageBoard damageBoard) {

    }

    // 计算斩魔伤害
    public long calToMonsterCrit(AbcstactCreature attacker, AbstractCreature defender) {

    }

    // 吸血
    public void suck(DamageBoard damageBoard) {

    }

    // 对自己混乱伤害
    public void chaos(DamageBoard damageBoard) {

    }

}

伤害板,用于记录伤害计算中的各种数据,均为当前值


public class DamageBoars {
    // 攻击方
    public AbstractCreature attacker;
    // 防御者
    public AbstractCreature defender;
    // 技能配置
    public SkillResouece skillResource;
    // 效果配置
    public EffectReeouce effectResource;
    // 技能伤害信息配置
    public DamageCfg damageCfg;
    // 战斗力压制信息
    public FightForceSubdueResource subdueResource;
    // 压制倍数
    public int subdueMulti;
    // 伤害类型
    public DamageType damageType;
    // 缓存计算公式各阶段的伤害
    public long[] damage;
    // 最终伤害
    public long damage;
    // 记录各种伤害类型标记
    private int damageMark;
    // 反弹伤害
    private long thorns;
    // 吸血
    public long suckHp;
    // 混乱伤害
    public long chaosDmg;
    // 守护装备:玄灵盾降低的伤害量
    public long xuanLingDec;

    // 构造.....

    // 转换为伤害协议
    public DamageResp toResp() {
        //....
    }

    // 伤害显示类型
    public void mark(DamageShowType damageShowType) {
        damageMark |= damageShowType.getMark();
    }
    // 记录伤害
    public void addDamage(DamageChain damageChain, long damage) {
        damages[damageChain.ordinal()] = damage];
    }

    // 获取指定阶段的伤害值
    public long getDamage(DmageChain damageChain) {
        return damages[damageChain.ordinal()];
    }
    // 获取多个阶段的总伤害值
    public long getDamageSum(DamageChain ) {
        long value = 0;
        for(DamageChain damageChain : damageChains) {
            vlaue += getDamage(damageChain);
        }
        return vlaue;
    }

    // 根据新的伤害总值,重新设置各阶段的伤害值,同比例方法缩小
    public void tryRestDamage(long newDmage) {
        if(newDamage == damage) {
            return;
        }
        if(damage == 0) {
            // 如果原来的伤害为0,则则将伤害值设置基础值
            addDamage(DamageChain.baseDamage, newDamage);
        } else {
            // 原伤害不为0,同比例放大缩小
            double multi = newDamage / (double) damage;
            for(int i=0; i<damage.length; i++) {
                damages[i] *= multi;
            }
        }
        this.damage = newDamage;
    }


}


属性系统学习笔记

概述

持有属性物品抽象

生物属性管理器

public class CreatureGameStats implements IStatRoot  {
    protected static Logger logger = LoggerFactoty.getLogger(CreatureGameStats.class);
    /**
    *   总属性
    */
    protected Map<StatEnum, StatValue> stats;
    /**
    *  属性根节点
    */
    protected StatNode statRoot;

    protected AbstractCreature owner;
    protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    // 属性拥有者
    public CreatureGameStats(AbstractCreature owner) {
        this.owner = owner;
        this.stats = new ConcurrentHashMap<>();
        this.statRoot = StatNode.crateRoot(this);
    }

    @Override
    public void changeStats() {
        Map<StatEnum, Long> diffMap = StatUtils.diffWithNewMap(oldStats, bewStats);
        if(diffMap.isEmpty()) {
            return;
        }
        Map<StatEnum, Long> changeMap = new HashMap<>(diffMap.size());
        lock.writeLock().lock();
        try {
            // 添加
            change(childNode, diffMap, changeMap)
        } finally {
            lock.writeLock().unlock();
        }
        if(Start.debug) {
            // 抽烟检查
            checkException(childNode);
        }
        // 通知属性变化
        notifyChange(childNode, changeMap);
    }

    // 该属性,溜子类执行空间,但是注意这个方法在锁中
    protected void change(StatNode childNode, Map<StatEnum, Long> diffMap, Map<StatEnum, Long> changeMap) {
        StatUtils.mergeToMap(stats, childNode.isToBase(), diffMap, changeMap);
    }

    // 属性变化后的逻辑
    protected void notifyChange(StatNode childNode, Map<StatEnum, Long> changeStatMap) { 
        lock.readLock().lock();
        try {
            if (changeStatMap.containsKey(StatEnum.MAX_HP)) {
                // 最大血量变化
                owner.getLifeStats().changeMaxHp(stats.get(StatEnum.MAX_HP).getTotal());
            }
            if (changeStatMap.containsKey(StatEnum.MAX_MP)) {
                // 最大蓝量变化
                owner.getLifeStats().changeMaxMp(stats.get(StatEnum.MAX_MP).getTotal());
            }
            if (changeStatsMap.containsKey(StatEnum.MAX_ENERGY)) {
                // 最大内功变化
                owner.getLifeStats().changeMaxEnergy(stats.get(StatEnum.MAX_ENERGY).getTotal());
            }
            if(owner.isSpawned())  {
                // 已经出生状态下的属性同步
                syncStat(changeStatsMap);
            }
        } finally {
            lock.readLock().unlock();
        }
    }

    private void syncStat(Map<StatEnum, Long> changeStatsMap) {
        List<Stat> list = new ArrayList<>(2);
        if(changeStatsMap.containsKey(StatEnum.ATTACK_SPEED)) {
            list.add(Stat.valueOf(StateEnum.SPEED, getCurrentStat(StatEnum.SPEED)));
        }
        if(changeStatMap.containsKey(StatEnum.ATTACK_SPEED)) {
            list.add(Stat.valueOf(StatEnum.ATTACK_SPEED, getCurrentStat(StatEnum.ATTACK_SPEED)));
        }
        if(!list.isEmpty()) {
            PacketSendUtility.broadcastPacketImmediately(owner, CreatureStatChangeResp.valueOf(owner.getObjectId(), list), true);
        }
    }

    @Override
    public void removeChild(Serializable key) {
        throw new RuntimeException("stat root can not remove!");
    }

    @Override
    public void fillParentKey(List<String> list) {

    }

    // 获取属性树显示信息
    public String getPrintStatTree() {
        return JSON.toJSONString(statRoot, JsonUtils.SERIALIZE_CONFIG, StatNodeFilter.INSTANCE, JsonUtils.SERIALIZER_FEATURES);
    }
    // 获取属性树显示信息
    public StatNode getPrintStatTreeStatNode() {
        return statRoot;
    }
    // 属性来源
    public StatNode getOrCreateStatNode(StatSource statSource) {
        return statRoot.getOrCreateChild(statSource);
    }
    // 获取子节点
    public StatNode getChildNode(List<String> childKeys) {
        StatNode node = statRoot;
        lock.readLock().lock();
        try {
            for(String childKey : childKeys) {
                node = node.getOrCreateChild(childKey);
            }
        } finally {
            lock.readLock().unlock();
        }
        return node;
    }
    // 属性集合
    public Map<StatEunm, StatValue> getStatTotal() {
        return stats;
    }
    // 所有属性集合
    public Map<StatEnum, Long> getAllStat() {
        lock.readLock().lock();
        try {
            return StatUtils.toSimpleMap(stats);
        } finally {
            lock.readLock().unlock();
        }
    }

    // 清空属性
    public void clear() {
        lock.writeLock().lock();
        try {
            stats.clear();
            this.statRoot = StatNode.createRoot(this);
        } finally {
            lock.writeLock().unlock();
        }
    }

    // 指定类型的属性值
    public long getCurrentStat(StatEnum stat) {
        switch(stat) {
            case HP_CUR:
                return owener.getLifeStats().getCurrrentHp();
            case MP_CUR:
                return owner.getLifeStats().getCurrentMp();
            case ENERGY_CUR:
                return owner.getLifeStats().getCurrentEnergy();
            case HP_CUR_R :
                return owner.getLifeStats().getHpRct();
            default : {
                long value = 0;
                lock.readLock().lock();
                try {
                    if(stats.contiansKey(stat)) {
                        value = stats.get(stat).getTotal();
                    }
                } finally {
                    lock.readLock().unlock();
                }
                // 属性使用时,不能为负数
                return Math.max(value, 0);
            }
        }
    }

    // 获取万分比属性类型的小数形式,比如储存时是5000,表示概率/比值为0.5,返回值就为0.5
    public double getCurrentStatWithOdds(StatEnum stat) {
        double value = getCurrentStat(stat);
        return value / GameMathUtil.ODDS_BASE;
    }
    // 获取属性集合List,包含基础值
    public List<Stat> getStatList() {
        lock.readList().lock();
        try {
            return StatUtils.toList(stats);
        } finally {
            lock.readLock().unlock();
        }
    }
    // 增强版本,获取属性集合List,包含基础值
    public List<StatExInfo> getStatExList() {
        lock.readLock().lock();
        try {
            return StatUtils.toStatExList(stats);
        } finally {
            lock.readLock().unlock();
        }
    }

    // 获取属性集合Map
    public Map<StatEnum, Long> getStatMap() {
        lock.readLock().lock();
        try {
            return StatUtils.toSimpleMap(stats);
        } finally {
            lock.radLock().unlock();
        }
    }
    // 通过属性列表进行初始化
    public void initWithStatMap(Map<StatEnum, Long> initStats) {
        initStats.forEach( (statEnum, value) -> {
            StatValue statValue = new StarValue();
            statValue.alter(true, value);
            stats.put(statEnum, statValue);
        });
    }
    // 检测#{@link #stats} 与 statRoot. # {@link #statRoot::getStatTotal()} 是否一致
    public void checkException(StatNode childNode) {

    }

    // 创建跨服传输属性数据
    public StatNode buildStatMoedel() {
        return statRoot.buildMedel();
    }
    // 解析跨服传输的属性数据
    public void parseStatModel(StatNodeModel model) {
        StatNode.parseModelOnlyChild(statRoot, model);
    }
}

玩家属性管理器

```java
public class PlayerGameStats extends CreatureGameStats {
    // 可计算战力的总属性
    protected Map<StatEnum, StatValue> fightForceStats;
    // 用于合并玩家属性变化信息
    private Set<StatEnum> changeStats = new ConcurrentSet();
    // 记录变化的节点
    private Map<String, Map<StatEnum, Long>> changeStatsNode = new ConcurrentHashMap<>();
    // 属性拥有者
    public PlayerGameStats(AbstractCreature owner) {
        super(owner);
        this.fightForceStats = new ConcurrentHashMap<>();
    }
    @Override
    protected void change(StatNode childNode, Map<StatEnum, Long> diffMap, Map<StatEnum, Long> changeStatsMap) {
        super.change(childNode, diffMap, changeSatsMap);
        if(childNode.isToFightForce()) {
            StatUtils.mergeToMap(fightForceStats, childNode.isToBase(), diffMap, null);
        }
        FightPlayer fightPlayer = (FightPlayer) this.owner;
        if(PlayerManager.getInstace().isOnline(this.owner.getObjectId())) {
            fightPlayer.excPlayer(player -> {
                // 本服玩家属性变化,尝试抛属性变化事件到跨服
                PlayerSyncManger.self().trySubmitTransferPlayerEvent(player, G2tStatNodeChangeEvent.valueOf(childNode));
            }, transferPlayer -> {
                // 跨服玩家属性变化
            });
        }
        if(fightPlayer.hasRefreshAgingMark(CreatureAgingMark.statsChange) {
            ArrayList<String> statKeys = new ArrayList<>();
            childNode.fillParentKey(statKeys);
            // .......
        }
    }
    @Override
    protected void notifyChange(StatNode childNode, Map<StatEnum, Long> changeStatsMap) {
        super.notifyChange(childNode, changeStatsMap);
        lock.readLock().lock();
        try {
            // 记录变化的节点key
            Map<StatEnum, Long> map = this.changeStatsNode.computeIfAbsent(childNode.getFullKeyString(), s -> ConcurrentHashMap<>());
            // 记录变化的属性
            StatUtils.mergeHashMap(map, changeStatMap);
        } finally {
            lock.readLock().unlock();
        }
        changeStats.addAll(changeStatsMap.keySet());
        // 如果是玩家,延迟合并通知玩家自身属性变化
        owner.addSelfMergeDelayTaskWithMills(CreatureUpdateType.UPDATE_STAT, 200, () -> notifyPlayerChange(StatEnum.SPEED, StatEnum.ATTACK_SPEED));
    }

    // 获取所有可对战力加成的属性
    public Map<StatEnum, Long> getAllFightForceStat() {
        lock.readLock().lock();
        try {
            return StatUtils.toSimpleMap(fighrForceStats);
        } finally {
            lock.readLock().unlock();
        }
    }

    // 获取可加成战斗的属性值
    public long getFightForceStat(StatEnum statEnum) {
        lock.readLock().lock();
        try {
            StatValue statValue = fightForceStats.get(statEnum);
            retrun statValue == null ? 0 : statValue.getTotal();
        } finally {
            lock.readLock().unlock();
        }
    }

    // 通知玩家属性变化情况
    public void notifyPlayerChange(StatEnum... igonre) {
        if(changeStats.isEmpty()) {
            return;
        }
        Set<StatEnum> ignoreSet = new HashSet<>();
        if(ignore != null) {
            ignoreSet.addAll(Arrays.asList(ignore));
        }
        lock.readLock().lock();
        try {
            // 通知属性变化细信息
            List<Stat> changeStats = new ArrayList<>(this.changeStats.size());
            for(StatEnum statEnum : this.changeStats) {
                if(!igonreSet.contains(statEnum)) {
                    changeStats.add(Stat.valueOf(statEnum, getCurrentStat(statEnum)));
                }
                if(!changeStats.isEmpty()) {
                    CreatureStatChangeResp resp = CreatureChangeResp.valueOf(owner.getObjectId(), changeStats);
                    PacketSendUtility.sendPacket(owner, resp);
                }
                // 抛出属性变化事件
                ((FightPlayer) owner).submitEvent(PlayerStatChangeEvent.valueOf(new HashMap<>(this.changeStatsNode)));

                // 清除属性变化事件
                this.changeStatsNode.clear();
                this.changeStats.clear();
            } finally {
                lock.readLock().unlock();
            }
        }
    }

    @Override
    public StatNodeModel buildStatModel() {
        StatNodeModel statNodeModel = statRoot.buildModel();
        notifyPlayerChange();
        return statNodeMoedel;
    }
}

属性的抽象

属性值

public class StatValue {
    private static final int RATIO_DENOMINATOR = 10000;
    // 总值
    private long total;
    // 基础值
    private long base;
    // 额外加成固定值,不算入基础值
    private long addtional;
    // 额外加成比例
    private int ratio;

    // 合并
    public static StatValue merge(StatValue s1, StatValue s2) {
        StatValue statValue = new StatValue();
        statValue.base = s1.base + s2.base;
        statValue.ratio = s1.ratio + s2.ratiio;
        statValue.calTotal();
        return statValue;
    }

    // 获取总值
    public long getTotal() {
        return total;
    }

    // 除去不算入基础值的总值
    public long getTotalWithoutAdditional() {
        return total - additional;
    }
    // 重置
    public void clear() {
        this.total = base = additional = 0;
    }
    // 增减属性
    public long alter(boolean toBase, long alter) {
        if(toBase) {
            this.base += alter;
        } else {
            this.addtional += alter;
        }
        return calTotal();
    }
    // 修改属性加成比例
    public long aleterRatio(long alterRatio) {
        ratio += alterRatio;
        return calTotal();
    }

    // 计算总值
    private long calTotal() {
        totaol = base + addtional + base * ratio / RATIO_DENOMINATOR;
        return total;
    }

    // 获取基础值
    public long getBase() {
        return base;
    }

    // 重写Object几个基础方法

}

战斗属性定义

@Desc("战斗属性定义")
public enum StatEnum implements EnumReadable {

    @Desc("最大气血")
    MAX_HP(0,"最大气血"),
    @Desc("最大蓝量")
    MAX_HP(1,"最大蓝量"),

    @Desc("内功值加成比例")
    ENERGY_R(124,"内功值加成比例", MAX_ENERGY),
    @Desc("精灵速度加成比例")
    SPIRIT_SPEED_R(503,"精灵速度加成比例"),
    @Desc("血量万分比伤害")
    HP_HURT_R(504, "血量万分比伤害"),

    private final int value;

    // 属性名称
    private final String name;

    // 如果该数组不为null
    // 表示是按比例加成
    // 数组内容为加成的基本属性列表
    private final StatEnum[] ratioBase;

    StatEnum(int value, String name, StatEnum... ratioBase) {
        this.value = value;
        this.name = name;
        this.ratioBase = ratioBase;
    }

    // ....Object基础方法


    public String getName() {
        return name;
    }

    // 是否为比例加成类型
    public boolean isRatio() {
        return ratioBase != null &&
        ratioBase.length > 0;
    }

    // 比例加成基础属性列表
    public StatEnum[] getRatioBase() {
        return ratioBase;
    }

}

属性跟节点接口

public interface IStatRoot {

    // 属性发生变更
    void changeStats(StatNode lastNode, StatNode childNode, Map<StatEnum, Long> oldStats, Map<StatEnum, Long> newStats);

    // 移出属性节点,不推荐使用者直接使用该方法移出节点,请使用clear方法
    void removeChild(Serializable childKey);

    // 移出属性节点,不推荐使用者直接使用该方法移出节点,请使用clear方法
    void removeChild(Serializable childKey);
    // 自顶向下
    void fillParentKey(List<String> list);

}


属性效果集合

public class StatNode implements IStatRoot {
    // 当前属性节点的key
    @JSONField(serialize = false)
    private String selfKey;

    // 属性改变接口
    @JSONField(serialize = false)
    private IStatRoot statRoot;
    // 当前节点属性列表
    @JSONField(serializeUsing = StatEnumMapSerializer.class)
    private Map<StateEnum, Long> stats;
    // 属性子节点
    private final ConcueentHashMap<String, StatNode> childMap = new ConcurrentHashMap<>(0);
    // 万分比加成节点
    private final ConcurrnetHashMap<String, RctNode> rctNodeMap = new ConcurrentHashMap<>(0);
    // 当前节点万分比加成属性列表
    @JSONField(serialzeUsing = StateEnumMapSerializer.class)
    private Map<StatEnum, Long> rctStats;

    // 该节点属性是否算入战斗力
    private boolean toFightForce = true;
    // 该节点属性是否计入基本值
    private boolean toBase = true;
    // 是否已经被移除
    private boolean remove;

    // 节点锁,同一个父节点都会持有同一把锁
    @JSONField(serialize = false) 
    private ReentrantReadWriteLock lock;
    StatNode() {

    }
    // 创建属性节点
    private boolean StatNode createRoot(IStatRoot statRoot) {
        StatNode vo = new StatNode();
        vo.selfKey = "root";
        vo.statRoot = statRoot;
        vo.lock = new ReentrantReadWriteLock();
        return vo;
    }

    // 构建key值,构建为String类型,是为了方便跨服传输
    private String buildKey(Object key, boolean check) {
        if(check) {
            checkKey() {
                checkey(key.getClass());
            }
            return key.toString();
        }
    }

    private void checkKey(Class keyClass) {
        if(String.class == keyClass) {
            // 允许为字符串
            return;
        }
        if(Enum.class.isAssignableFrom(keyClass)) {
            // 允许key为枚举
            return;
        }
        if(ClassUtil.isPrimitiveOrWrapper(keyClass)) {
            // 允许key值为基础类型或者基本类型的包装类型
            return;
        }
        throw new RuntimeException("statNode key must be String/Enum/Primitive/PrimitiveWrapper, keyClass is" + keyClass);
    }

    @Override
    public void changeStats(StatNode lastNode, StatNode childNode childNode, Map<StatEnum, Long> oldStats, Map<StatEunm, Long>
    newStats) {
        if(!childMap.containsKey(lastNode.selfKey)) {
            throw new RuntimeException(String.format("lastNode %s remove form %s before", lastNode, this));
        }
        // 基础属性节点改变
        statRoot.changeStats(this, childNode, oldStats, newStats);
        // 刷新万分比加成属性
        refreshRctNode();
    }

    // 移除属性加成的子节点
    @Override
    public void removeChild(Serializable k) {
        String key = buildKey(k, false);
        lock.writeLock().lock();
        try {
            // 移除该子节点
            StatNode childNode = childMap.get(key);
            if(chilfNode == null) {
                return;
            }
            chilfNode.clear();
            childMap.remove(key);
        } finally {
            lock.writeLock().unlock();
        }
    }

    @Override
    public void fillParentKey(List<String> list) {
        if(statRoot != null) {
            statRoot.fillParentKey(list);
        }
    }

    // 获取包含全路径的key集合
    public String getFullKeyString() {
        ArrayList<String> keys = new ArrayList<>();
        fillParentKey(keys);
        return keys.toString();
    }

    // 重置该节点的属性值
    public void replace(StatEnum statEnum, long value) {
        Map<StatEnum, Long> stats = new HashMap<>(1);
        stats.put(statEnum, value);
        replace(stats);
    }

    // 重置该节点的属性值
    public void replace(Map<StatEnum, Long> newStats) {
        lock.writeLock().lock();
        try {
            Map<StatEnum, Long> oldStats = this.stats;
            if(newStats == null) {
                this.stats = null;
            } else {
                this.stats = StatUtils.cloneStaeMap(newStats);
            }
            statRoot.changeStats(this, this, oldStats, this.stats);
        } finally  {
            lock.writeLock().unlock();
        }
        // 当前节点属性变化后,刷新万分比加成
        refreshRctNode();
    }
    // 清除当前节点的数据
    public void clear() {
        lock.writeLock().lock();
        try {
            remove = true;
            Map<StatEnum, Long> oldStats = this.stats;
            this.stats = null;
            statRoot.changeStats(this, this, oldStats, null);
            childMap.clear(); 
            // 当前节点被清除,清除万分比加成
            refershRctNode();
            rctNodeMap.clear();
        } finally {
            lock.writeLock().unlock();
        }
    }
    // 获取该节点的总属性加成,包含万分比加成属性,包含不加入战斗力的属性
    @JSONField(serialize = false)
    public Map<StatEnum, StatValue> getTotal() {
        return getTotal(true);
    }

    // 获取该节点的总属性加成
    Map<StatEnum, StatValue> getTotal(boolean includeRct) {
        lock.readLock().lock();
        try {
            Map<StatEnum, StatValue> totalMap = StatUtils.toMap(this, includeRct);
            for (StatNode statNode : childMap.values()) {
                StatUtils.mergoToMap(totalMap, statNode.getTotal());
            }
            return;
        } finally {
            lock.readLock().unlock();
        }
    }

    // 重置/创建一个属性节点,如果节点已经存在,则重置该节点的属性,如果该节点不存在,则创建该节点
    // 默认该节点的所有属性,包括下属节点,算入战斗力
    public StatNode getOrCreateChild(Serializable k) {
        return getOrCreateChild(k, true);
    }

    // 重置/创建一个属性节点,如果节点已存在,则重置该节点的属性,如果该节点不存在,则创建该节点
    // 如果当前节点已经设置为不计算入战斗力,则创建出的子节点也为不计算战斗力
    public StatNode getOrCreateChild(Serializable k, boolean toFightForce) {
        String key = buildKey(k, true);
        StatNode statNode = childMap.get(key);
        if(statNode == null) {
            statNode = childMap.computeIfAbsent( key,o -> {
                StatNode node = new StatNode();
                node.selfKey = key;
                node.toFightForce = toFightForce && this.toFightForce;

                // 记录父节点
                node.statRoot = this;
                // 继承属性锁
                node.lock = lock;
                return node;
            });
        }
        return statNode;
    }

    // 快速设置指定节点的属性
    public StatNode fastSet(Serializable k, StatEnum statEnum, long value) {
        Map<StatEnum,Long> stats = new HashMap<>(1);
        stats.put(statEnum, value);
        return fastSet(k, stats);
    }

    // 获取子节点
    public StatNode getChild(Serializable k) {
        String key = buildKey(k, false);
        return childMap.get(key);
    }
    // 是否拥有指定节点
    public boolean hashChild(Serializable k) {
        String key = buildKey(k, false);
        return childMap.containsKey(key);
    }

    // 当前节点的key
    @JSONField(serialize = false)
    public String getKey() {
        return selfKey;
    }

    // 该节点的所有属性,包括下属节点,是否算入战斗力
    public boolean isToFightForce() {
        return toFightForce;
    }
    // 获取当前节点属性,安全起见不要暴露给外部调用,避免被直接修改
    Map<StatEnum, Long> getStats() {
        return stats;
    }
    // 获取当前节点加成的万分比属性,安全弃考吗不要暴露给外部调用,避免被直接修改
    Map<StatEnum, Long> getRctStats() {
        return rctStats;
    }
    // 只读,获取当前节点加成的万分比属性
    public Map<StatEnum, Long> getRctStatsOnlyRead() {
        return rctStats;
    }
    // 标记该加成不计入战斗力
    public void notToFightForce() {
        this.toFightForce = false;
    }
    // 标记该加成不计入基础值
    public void notToBase() {
        this.toBase = false;
    }
    // 该节点的所有属性,包括下属节点,是否算入基础值
    public boolean isToBase() {
        return toBase;
    }
    // 是否已经被移除
    public boolean isRemove() {
        return remove;
    }

    @Override
    public String toString() {
        return statRoot.toString() + "-" + selfKey + "-";
    }

    // 获取或创建一个万分比加成节点
    public RctNode getOrCreateRctNode(Serializable k) {
        String key = buildKey(k, true);
        RctNode rctNode = rctNodeMap.get(key);
        if(rctNode == null) {
            rctNode = rctNodeMap.computeIfAbsent(key,o -> RctNode.valueOf(this));
        }
        return rctNode;
    }
    // 移出万分比加成节点
    public void removeRctNode(Serializable k) {
        String key = buildKey(k, false);
        lock.writeLock().lock();
        try {
            // 移出该万分比加成节点
            RctNode rctNode = rctNodeMap.remove(key);
            if(rctNode == null) {
                return;
            }
            refreshRctNode();
        } finally {
            lock.writeLock().unlock();
        }
    }

    // 刷新万分比加成的属性
    public void refreshRctNode() {
        if(MapUtils.imEmpty(rctStats) && rctNodeMap.isEmpty()) {
            // 历史没有加成,现在也没有加成
            return;
        }
        lock.writeLock().lock();
        try {
            Map<StatEnum, StatValue> baseTotal = getToal(false);
             Map<StatEnum, StatValue> newRctStats = new HashMap<>(baseTotal.size());
             rctNodeMap.values().forEach(rctNode -> StatUtils.mergeMap(newRctStats, rctNode.calRctAdd(baseTatal)));
        } finally {
            lock.writeLcok().unlock();
        }
    }

    // 所有子节点,慎用
    public ConcurrentHashMap<String, StatNdode> getChildMap() {
        return childMap;
    }

    // 构建跨服传输数据
    public StatNodeModel buildModel() {
        StatNodeModel model = StatNodeModel.valueOf(selfKey, toFightForce, toBase);
        if(this.rctMap != null) {
            // 当前节点属性
            List<Stat> stats = StatUtils.toListWithInt(this.stats);
            model.setStats(stats);
        }
        if(!childMap.isEmpty()) {
            // 子节点
            List<StatNode> childList = new ArrayList<>(childMap.size());
            for(StatNode node : childMap.values()) {
                childList.add(node.buildModel());
            }
            model.setChildNodeList(childList);
        }
        if(!rctNodeMap.isEmpty()) {
            // 万分比节点
            List<StatRctNodeMedel> rctList = new ArrayList<>(rctNodeMap.size());
            for(StatNode node : childMap.values()) {
                childList.add(node.buildModel());
            }
            model.setChildNodeList(childList);
        }
        if(!rctNodeMap.isEmpty()) {
            // 万分比节点
            List<StatRctNodeModel> rctList = new ArrayList<>(rctNodeMap.size());
            for(RctNode node : rctNodeMap.values()) {
                rctList.add(node.buildModel());
            }
            model.setRctNodeList(rctList);
        }
        return model;
    }

    // 解析跨服传输的属性
    public static void parseModel(StatNode root, StatNodeModel model ) {
        StatNode node = root.getOrCreateChild(model.getKey());
        parseModelOnlyChild(node, model);
    }

    // 解析跨服传输的属性
    public static void parseModelOnlyChild() {
        node.toFightForce = model.isToFightForce();
        node.toBase = model.isToBae();
        if(model.getStats() != null) {
            // 当前节点属性
            Map<StatEnum, Long> statMap = StatUtil.toMap2(model.getStats());
            node.replace(statMap);
        }
        if(model.getChildNodeList() != null) {
            // 子节点
            model.getChildNodeList().forEach(childModel -> parseModel(node, childModel));
        }
        if(model.getRctNodeList() != null) {
            // 万分比节点
            model.getRctNodeList().forEach(rctModel -> RctNode.parseModel(node, rctModel));
        }
    }

}

属性树信息

public class StatNodeModel() {
    // 当前属性节点的Key
    @Protobuf(description = "属性节点key")
    private String key;
    @Protobuf(description = "该节点属性是否算入战斗力")
    private boolean toFightForce;
    @Protobuf(descrption = "该节点属性是否计入基础值")
    private boolean toBase;

    @Protobuf(description = "属性")
    private List<Stat> stats;
    @Protobuf(description = "属性子节点")
    private List<StatNodeModel> childNodeList;
    @Protobuf(description = "属性子节点")
    private List<StatRctNodeModel> rctNodeList;

    public static StatNodeModel valueOf(String key, boolean toFightForce, boolean toBase) {
        StatNodeModel model = new StatNodeModel();
        model.key = key;
        model.toFightForce = toFightForce;
        model.toBase = toBase;
        return model;
    }
    // ......

}

属性来源


@Desc("属性来源")
public enum StatSource {
    @Desc("等级属性")
    level("等级属性"),
    @Desc("装备")
    equip("装备"),
    @Desc("装备基础属性")
    equip_base("装备基础属性"),
    ;

    // 节点中文名
    private String name;

    StatSource(String name) {
        this.name = nam;
    }

    public static String replaceSourceName(String statTree) {
        List<StatSource> statSources = Arrays.asList(StatSource.values());
        statSources.sort((o1, o2) -> o2.getName().length() - o1.getName().length());
        for(StatSource value : statSources) {
            String replaceKey = value.name();
            statTree = statTree.replace(replaceKey, value.getName());
        }
        return statTree;
    }

}

属性处理工具

public class StatUtils {
    // 属性比例基数
    public static final STAT_PATE = 10000;

    // 属性列表取负
    public static List<Stat> negative(List<Stat> stats) {
        if(stats == null) {
            return null;
        }
        List<stat> negativeStats = new ArrayList<>(stats.size());
        stats.forEach(stat -> negativeStats.add(Stat.valueOf(stat.getType(), -stat.getValue())));
        return negativeStats;
    }

    // 是否包含该节点的万分比属性
    public static Map<StatEnum, StatValue> toMap(StatNode staNode, boolean ) {
        Map<StatEnum, StatValue> map = new HashMap<>();
        // 基础属性
        Map<StatEnum, Long> stats = statNode.getStats();
        if (stats != null) {
            mergeToMap(map, statNode.isToBase(), stats, null);
        }
        if(includeRct) {
            // 万分比加成属性
            Map<StatEnum, Long> rctStats = statNode.getRctStats();
            if(rctStats != null) {
                mergeToMap(map, statNode.isToBase(), rctStats, null);
            }
        }
        return map;
    }

    // 属性map -> 属性简单map
    public static List<Stat> toListWithLong(Map<StatEnum, Long> map) {
        if(map == null) {
            return new ArrayList<>(0);
        }
        List<Stat> list = new ArrayList<>(0);
        map.forEach((key, value) -> list.add(Stat.valueOf(key, value)));
        return list;
    }

    // 属性map ->属性Stat List
    public satatic List<Stat> toListWithInt(Map<StatEnum, Long> map) {
        if(map == null) {
            return new ArrayList<>(0);
        }
        List<Stat> list = new ArrayList<>(map.size());
        map.forEach((key, value) -> list.add(Stat.valueOf(key, value)));
        return list;
    }

    // 属性map -> 属性Stat List
    public static List<Stat> toList(Map<StatEnum, StatValue> map) {
        if(map == null) {
            return new ArrayList<>(0);
        }
        List<Stat> list = new ArrayList<>(map.size());
        map.forEach((key, value) -> list.add(Stat.valueOf(key, value.getToatal())));
        return list;
    }

    // 包含基础值
        public static List<Stat> toStatExList(Map<StatEnum, StatValue> map) {
        if(map == null) {
            return new ArrayList<>(0);
        }
        List<Stat> list = new ArrayList<>(map.size());
        map.forEach((key, value) -> list.add(Stat.valueOf(key, value.getToatal(), value.getBase())));
        return list;
    }

    // 合并属性List到属性Map中,采用加法
    public static void mergeToMap(Map<StatEnum, StatValue> map, boolean isToBase, Map<StatEnum, Long> stats, Map<StatEnum, Long> changeMap) {
        mergeToMap(map, isToBase, stats, 1, changeMap);
    }

    // 合并属性List到属性Map中,采用减法
    public static void diffFromMap(Map<StatEnum, StatValue> map, boolean isToBase, Map<StatEnum, Long> stats, Map<StatEnum, Long> changeMap) {
        mergeToMap(map, isToBase, stats, -1, changeMap);
    }

    private static void mergeToMap(Map<StatEnum, StatValue> map, boolean isToBase, Map<StatEnum, Long> stats, int sign, Map<StatEnum, Long> changeMap) {
        if(stats == null) {
            return;
        }
        stats.forEach((statEnum, value) -> {
            value *= sign;
            // 数值加成
            StatValue statValue = getOrCreateStatValue(map, statEnum);
            long oldValue = statValue.getTotal();
            long newValue = statValue.alter(isToBase, value);
            if (changeMap != null) {
                changeMap.merge(statEnum, newValue - oldValue, Long::sum);
            }
            if(statEnum.isRatio()) {
                // 按比例加成
                for(StatEnum anEnum : statEnum.getRatioBase()) {
                    statValue = getOrCreateStatValue(map, anEnum);
                    oldValue = statValue.getTotal();
                    newValue = statValue.alterRatio(value);
                    if(changeMap != null) {
                        changeMap.merge(anEnum, newValue -oldValue, Long::sum);
                    }
                }
            }
        });
    }

    public static StatValue getOrCreateStatValue(Map<StatEnum, StatValue> map, StatEnum type) {
        StatValue statValue = map.get(type);
        if(statValue == null) {
            statValue = new SataValue();
            map.put(type, statValue);
        }
        return statValue;
    }

    // 合并2个map的值
    public static void mergeToMap(Map<StatEnum, StatValue> targetMap, Map<StatEnum, StatValue> sourceMap) {
        sourceMap.forEach((key, value) -> targetMap.merge(key, value, StatValue::merge));
    }

    // ......

    // 合并属性fromStats到属性map中
    public static void mergeMap(Map<StatEnum, Long> toStats, List<Stat> formStats) {
        fromStats.forEach((stat -> toStats.merge(stat.getType(), stat.getValue(), (value, value2) -> value1 + value2)));
    }

    // ......

    // 便捷的设置属性方法,如果需要设置statSrc1/child2/grandchild2的属性,可以这里写
    public static StatNode fastSet(AbstractCreature creature, Map<StatEnum, Long> statMap, StatSource statSrc, Serializable... keys) {
        CreatureGameStats stats = creature.getGameStats();
        StatNode node = stats.getOrCreateStatNode(statSrc);
        for(int i=0; i<keys.length; i++) {
            node = node.getOrCrateChild(keys[i]);
        }
        node.replace(statMap);
        return node;
    }

    // 设置属性
    public static StatNode fastSet(AbstractCreature creature, Map<StatEnum> statMap, StatSource statSrc, Serializable... keys) {
        GametureGameStats stats = creature.getGameStats();
        StatNode node = stats.getOrCreateStatNode(statSrc);
        for(int i=0; i< keys.leghth; i++) {
            node = node.getOrCreateChild(keys[i]);
        }
        node.notToFightForce();
        node.replace(statMap);
        return node;
    }

    // 便捷的设置属性方法
    public static StaNode fastSet(Player player, Map<StatEnum, Long> statMap, StatSource statSrc, Serializable... keys) {
        return fasrSet(player.getFightPlayer(), statMap, statSrc, keys);
    }

    public static void setRct(AbstractCreature creature, int baseRct, Serializable rctKey, StatSource statSrc, Serializable... keys) {
        StatNode statNode = getStatNode(creature, statSrc, keys);  
        RctNode rctNode = statNode.getOrCreateRctNode(rctKey);
        rctNode.replaceBaseRct(baseRct);
    }

    public static void setRct(Player player, int baseRct, Serializable rctKey, StatSource statSrc, Serializable... keys) {
        setRct(player.getFightPlayer(), baseRct, rctKey, statSrc, keys);
    }

    // 以指定的基础属性为总值,计算传入的万分比属性增加的属性值
    public static Map<StatEnum,Long> calRctStat(Map<StatEnum, Long> baseMap, Map<StatEnum, Long> rctMap) {
        Map<StatEnum, Long> result = new HashMap<>(baseMap.size());
        Map<StatEnum, Long> rctMerge = new HashMap<>(baseMap.size());
        rctMap.forEach((statEnum, value) -> {
            if(statEnum.isRatio()) {
                // 按比例加成
                for(StatEnum anEnum : statEnum.getRatioBase()) {
                    rctMerge.merge(anEnum, value, Long::sum);
                }
            } else {
                result.merge(statEnum, value, Long::sum);
            }
        });
        rctMerge.forEach((statEnum, value) -> {
            Long total = baseMap.get(statEnum);
            if(total != null) {
                result.merge(statEnum, total * value / STAT_RATE, Long::sum);
            }
        });
        return result;
    }

}

总结

  1. 游戏中的生物拥有GametureGameStats属性,其中挂载着生物所有具体属性。玩家则是拥有PlayerGameStats

  2. GametureGameStats持有总属性stats和根节点StatRoot

定时任务系统学习笔记


游戏许多业务会用到一些需要能修改配置的定时任务,例如凌晨重置、boss定时刷新、副本/战场/竞技场定时开启、拍卖行/交易的心跳等,这个时候需要通用的定时任务系统。

配置资源

定时业务配置类

@Resource
public class ScheduledResource implement IAfterResourceLoad {
    @ResourceId
    private ScheduledType id;

    // 时间cron
    private String cron;
    private String trigger;
    private CronSequenceGenerator CronSequenceGenerator;

    @Override
    public void afterLoad() {
        trigger = new CronTrigger(cron);
        cromSequenceGenerator = new CromSequenceGenerator(cron, TimeZone.getDefault());
    }

    // 获取当前时间之后的下一次执行时间
    public long getNextTime(long lastRunTime) {
        retrun cromSequenceGenerator.next(new Date(lastRunTime)).getTime();
    }
}

定时任务类型

public enum ScheduleType {
    // 凌晨刷新
    midnight(RunScope.all),
    // 公会申请过期检查
    GANG_APPLICATION_CHECK(RunScope.game),
    // 沙巴克分组
    SHABAKE_SPLIT_GROUP(RunScope.center),
    // 灭神塔普通模式结束时间
    GOD_KILL_TOWER_GANG_START(RunScope.game),

    // ......接下来省略...

}

代码运行范围,用于区分游戏服代码与跨服代码


public enum RunScope {

    // 只在游戏服运行
    game {
        @Override
        public boolean canRun() {
            return ServerType.isGameServer();
        }
    }

    // 只在中心服运行
    center {
        @Override
        public boolean canRun() {
            return ServerType.isCenterServer();
        }
    }

    // 当前服务器类型是否允许运行
    public abstract boolean canRun();

    // 游戏服与中心服都运行
    all {
        @Override
        public boolean canRun() {
            return !canRun();
        }
    }

    // 当前服务器类类型是否不允许运行
    public boolean canNotRun() {
        return !canRun();
    }
}

在添加一个新类型的定时任务类型时,需要分别在ScheduledResource.xlsx和ScheduleType里同时加入。


定时任务管理的抽象

// 服务器刷新时,这里定义的定时任务,在服务器重启后,如果错过了刷新点,会进行一次补偿

public abstract class BaseFixScheduleManager<Process extends Comparble<Process>, Run> {

    @Autowird
    private ScheduledManager scheduledManager;

    // 已注册的定时业务
    private Map<ScheduledType TreeMap<Process, Lsit<Run>>> fixSchedleMap = new HashMap<>();

    // 开始定时
    public void start() {
        if(ServerType.isTransferServer()) {
            // 战斗服不运行全局定时器
            return;
        }
        for(ScheduledType scheduledType : fixScheduleMap.keySet()) {
            if(scheduledType.getRunScope().canNotRun()) {
                // 与服务器类型匹配的才运行
                continue;
            }
            // 定时刷新
            scheduledManager.addScheduled(scheduled, () -> scheduledRefresh(scheduledType));
        }
    }

    // 时间到了,开始刷新
    protected abstract void scheduledRefresh(ScheduledType scheduleType);

    // 定时任务启动前
    public synchronized void registerBefore(ScheduledType type, Run runnable) {
        register(ScheduledProcess.before, type, runnable);
    }


    // 定时任务启动中
    public synchronized void registerOn(ScheduledType type, Run runnable) {
        register(ScheduledProcess.on, type, runnable);
    }

    // 注册监听服务器数据刷新
   public synchronized void register(Proccess process, ScheduledType type, Run runnable) {
       TreeMap<Process, List<Run>> map = fixScheduleMap.computeIfAbsent(type, k -> new TreeMap<>());
       List<Run> list = map.computeIfAbsent(process, k -> ArrayLsit<>());
       list.add(runnable);
   }

   // 获取所有已注册的定时器类型
   public Collection<ScheduleType>  getAllTypes() {
       return fixScheduleMap.keySet();
   }

   // 尝试批量刷新,保证执行顺序
   protected void tryBatchRefresh(ScheduledData scheduledData, Collection<ScheduledType> scheduledTypes, Consumer<ScheduleddData> saver, BiConsumer<Run, Long> run) {
       List<WaitSchedule> list = new ArrayList<>(5);
       for(ScheduledType scheduledType : scheduledTypes) {
           long nextShceduledTime = nextSheduledTime(scheduleData, scheduledType, saver);
           if(nextScheduledTime <= System.currentTimeMillis()) {
               list.add(WaitSchedule.valueOf(scheduledType,nextScheduledTime));
           }
           if(list.isEmpty()) {
               return;
           }
       }
           // 按照时间排序,保证定时器执行的顺序
        Collections.sort(list);
        list.forEach(waitSchedule -> runScheduled(scheduledData, waitSchedule.getScheduledType(),saver,run));
   }

   // 尝试刷新
   protected void tryRefresh(ScheduledData shceduledData, ScheduledType scheduledType) {
       if(nextScheduledTime(scheduledData, scheduledType, saver) <= System.currentTimeMillis()) {
           runShceduled(scheduleData, scheduledType, savaer, run);
       }
   }

   // 计算一下次的运算时间
   private long nextScheduledTime(ScheduledData scheduledData, ScheduledType scheduledType, Consumer<ScheduledData> saver) {
       Long lastScheduledTime = scheduledData.getLastScheduledTime(scheduledType, lastScheduledTime);
       if(lastScheduled == null) {
           // 这里记录当前时间,是跳过玩家数据第一次初始化不需要刷新
           lastScheduledTime = System.currentTimeMills();
           scheduledData.recordScheduled(scheduledType, lastScheduledTime);
           saver.accept(scheduledData);
       }
       return scheduledManager.getNextTime(scheduleType, lastScheduledTime);
   }

   // 执行定时任务
   private void runScheduled(ScheduledData scheduledData,
                            ScheduledType scheduledType,
                            Consumer<ScheduledData> saver,
                            BiConsumer<Run, Long> run) {
        long lastScheduledTime = scheduledData.getLastScheduledTime(scheduledType);
        // 记录刷新时间
        scheduledData.recordScheduled(scheduledType, System.currentTimeMills());
        // 刷新
        TreeMap<Process, List<Run>> map = fixScheduleMap.get(sheduledType);
        for(List<Run> list: map.values()) {
            try {
                run.accept(scheduledRun, lastScheduledTime);
            } catch (Exception e) {

            }
        }
        saver.accept(scheduledData);
   }

}

定时任务属性记录

public class ScheduledData {

    // 定时任务刷新记录
    private ConcurrentHashMap<ScheduledType, Long> scheduledRecord;

    public static ScheduledData valueOf() {
        ScheduleData vo = new ScheduleData();
        vo.scheduledRecord = new ConcurrentHashMap<>(0);
        return vo;
    }

    // 获取最后一次刷新的时间
    public Long getLastScheduledTime(ScheduledType scheduleType) {
        return scheduledRecord.get(scheduledType);
    }

    // 记录定时任务定时任务
    public void recordScheduled(ScheduledType scheduledType, Long time) {
        scheduledRecord.put(scheduledType, time);
    }
}

定时业务员管理,定时业务初始化的逻辑


@Component
public class ScheduleManager extends InstantiationAwareBeanPostProcessorAdpater {

    private static ScheduledManager instance;

    public ScheduledManager() { instance = this; }

    public static ScheduledManger self() {
        return instance;
    }
    @Static
    private Strorage<ScheduledType, ScheduleddResource> strorage;

    @Autowired
    private Scheduler scheduler;

    // 记录所有定时业务
    private Map<ScheduledType, RunMethod> existTasks = new ConcurrentHashMap<>();

    // 定时业务是否已经启动
    private boolean init = false;

    @RessourceReload(ScheduledResource.class)
    public void onLoad() {
        init();
    }

    // 初始所有定时业务
    public synchronized void init() {
        existTasks.forEach(this::schedule);
        init = true;
    }

     @Override
     public boolean postProcessAfeterInstantiation(Object bean , String beanName) throws BeansException {
          // 通过反射工具找到找到`ScheduledByResource`注解,
        ReflectionUtils.doWithMethods(AopUtils.getTargetClass(bean), method -> {
             ScheduledByResource annotation = AnnotationUtils.getAnnotation(method, ScheduledByResource.class);
             if(annotation == null) {
                 return;
             }
             ScheduledType scheduledType = annotation.value();
             final MethodInvokingRunnable runnable = new MethodInvokingRunnable();
             runnable.setTargetObject(bean);
             runnable.setTargetMethod(method.getName());
             try {
                 runnable.prepare();
             } catch(Exception e) {
                 throw new IllegalSateException("无法创建定时任务,类型:"+ annotation.value(),e);
             }
             addScheduled(scheduledType, runnable);
        });
        return super.postProcessAfterInstantiation(bean, beanName);
    }

    // 添加定时任务,如果服务器已启动则启动定时业务
    // 如果服务器没有启动则等待服务器启动完成以后,再启动定时业务
    public sychronizedvoid addScheduled(ScheduledType scheduledType, Runnable runnable) {
        if(SreverType.isTransferServer()) {
            // 战斗服不启动全服定时器
            return;
        }
        RunMethod runMethod = existTasks.get(scheduledType);
        if(runMethod == null) {
            runMethod = new RunMethod();
            existTasks.put(scheduledType, runMethod);
        }
        runMenthod.addRunnable(runnable);
        existTasks.put(scheduledType, runMethod);
        if(init) {
            // 服务器启动后,定时业务已经初始结束,则直接启动新的定时业务
            schedule(scheduledType, runMethod);
        }
    }

    // 启动定时业务
    private void schedule(ScheduledType scheduledType, RunMethod runMethod) {
        if(runMethod.future != null) {
            // 取消原来的定时业务
            runMethod.future.cancel(false);
        }
        ScheduledResource resource = storage.get(scheduledType, true);
        runMethod.future = scheduler.schedule(new ScheduledTask() {
            @Override
            public String getName() {
                return scheduleType.name();
            }
            @Override
            public void run() {
                runMethod.run();
            }
        }, resource.getTrigger());
        LOGGER.debug("启动定时业务{}",scheduledType);
    }

    // 获取指定定时任务下一次的执行时间点
    public long getNextTime() {
        return storage.get(scheduledType, true).getNextTime(lastRunTime);
    }
    // 检查指定时间是否在两个时间检查之间
    public boolean inTime(ScheduledType start, ScheduledType end, long lastRunTime) {
        long nextStartTime = storage.get(start, true).getNextTime(lastRunTime);
        long nextEndTime = storage.get(end,true).getNextTime(lastRunTime);
        retrun nextStartTime >= nextEndTime;
    }

    // 检查当前时间是否在两个时间点之间
    public boolean inTime(ScheduleType start, ScheduledType end) {
        return inTime(start, end, System.currentTimeMillis());
    }

    public ScheduleResource getResourceNullable(ScheduledType type) {
        return storage.get(tyoe,false)
    }

    // 执行方法的抽象
    private class RunMethod {
        ScheduledFuture future;
        private List<Runnable> runnableList = new CopyOnWriteArrayList<>();

        public void addRunnable(Runnable runnable) {
            runnableList.add(runnable);
        }

        public void run() {
            runnableList.forEach(runnable -> {
                try {
                    runnable.run();
                } catch (Exception e) {
                    LOGGER.error("定时业务执行出错",e);
                }
            });
        }
    }
}


定时模块

玩家定时任务执行接口

@FunctionalInterface
public interface ScheduledPlayerRun {
    /**
    *   定时任务执行接口
    *   @param player
    *   @param lastRunTime 上次执行时间
    */
    void run(Player player, long lastRunTime);
}

玩家刷新定时,这里定义的定时任务,玩家在线会马上刷新,玩家不在线会在玩家重新上线时进行补偿刷新

public class PlayerFixScheduleManager extends BaseFixScheduleManager<ScheduledPlayerRun> {

    @Override
    protected void scheduledRefresh(ScheduledType scheduledType) {
        PlayerManager.getInstance().walkOnlinePlayer(
            player -> IdentitityEventExecutorGroup.addTask(player.getDispatcherHashCode(),scheduledType.name(),
            () -> playerRefresh(player,scheduledType))
        );
    }


    private void playerRefresh(Player player, ScheduleType scheduledType) {
        ScheduledData scheduledData = player.getPlayerLoginInfo().getScheduledData();
        tryBatchRefresh(scheduledData,getAllType);
    }


    // 玩家上线时,检测刷新情况,没有刷新的执行刷新
    public void checkOnPlayerLogin(Player player) {
        ScheduledData scheduledData = player.getPlayerLoginInfo().getScheduledData();
        tryBathchRefresh(scheduledData,getAllTypes(),
        (sd) -> PlayerLoginManager.getSelf().update(),
        (scheduledPlayerRun,lastRunTime) -> scheduledPlayerRun.run(player,lastRunTime);
        );
    }

    public registerBefore(ScheduledType type, ScheduledRun runnable) {
        super.register(ServerScheduledProcess.before, type, runnable);
    }

    public void register(ScheduledType type, ScheduledRun runnable) {
         super.register(ServerScheduledProcess.on, type, runnable);
    }
}

玩家刷新定时,这里定义的定时任务,服务器重启以后,如果错过了刷新点,会进行一次补偿

public class ServerFixScheduleManager extends BaseFixScheduleManager<ScheduledRun> {

    @OVerride
    public void start() {
        super.start();
        // 启动时尝试一次刷新
        ScheduledData scheduledData = serverManager.getData(ServerDataKey.SCHEDULED);
        tryBatchRefresh(scheduledData, getAllTypes(),
        (sd) -> serverManager.update(ServerDataKey.SCHEDULED),
        ScheduledRun:run
        );
    }

    @Override
    protected void scheduledRefresh(ScheduledType scheduledType) {
        ScheduledData scheduledData = serverManager.getData(ServerDataKey.SCHEDULED);
        tryBatchRefresh(scheduledData, scheduledType,
        (sd) -> serverManager.update(ServerDataKey.SCHEDULED),
        ScheduledRun:run
        );
    }

    public registerBefore(ScheduledType type, ScheduledRun runnable) {
        super.register(ServerScheduledProcess.before, type, runnable);
    }

    public void register(ScheduledType type, ScheduledRun runnable) {
         super.register(ServerScheduledProcess.on, type, runnable);
    }
}

定时系统的使用

  1. ScheduleResource.xml中定义定时任务类型和Corn字符串,在ScheduleType枚举中添加定时任务类型

  2. 在需要启动定时任务的地方使用registerOn或者registerBefore方法,例如

    • PlayerFixScheduleManager.self().registerOn(ScheduledType.midnight, () -> { // 业务逻辑 })
    • ServerFixScheduleManager.self().registerBefore(ScheduledType.midnight, () -> { // 业务逻辑 })