属性系统

2018/09/23 Mmo-Game

属性系统设计

目录

属性系统设计考虑

1.初始化表数据到内存

1.属性归类(Attribute表)

AttributeTypeDefine.init(reloadDictClazzs);

1.初始化 当前值 - 最大值的对应
public static void initCurrentMaxAttr()
{
    ...
    ...
    ...
}
public static int[] hasMaxAttrCurrentArray;                         //  当前值集合,有最大值的当前值集合

private static boolean[] currentAttrFlagArray;                      //  当前值集合

//这里的最大值还是指的attribute表的主键id,例如气血主键1,对应最大气血主键id等于2
//那么currentAttrToMaxAttrArray[1] = 2
public static int[] currentAttrToMaxAttrArray;                      //  当前-最大值 的映射,主要用于当前值不能大于最大值的计算

public static int[] maxAttrToCurrentAttrArray;                      //  最大值-当前 的映射,主要用于当前值不能大于最大值的计算

private static boolean[] maxAttrFlagArray;                          //  最大值集合

初始化当前值 - 最大值的对应,主要用于当前值不能大于最大值的计算。

这里的当前值指的是当前attribute表里配置的主键id,最大值指的表里配置的maxAttr列(attribute表里配置的主键id,只是和当前值不同id)

2.初始化一级属性

// 当前值集合,有最大值的当前值集合

// 索引对应Attribute表主键id private static boolean firstFlagArray[];

private static void initFirstrAttr()
{
    firstFlagArray = new boolean[count];

    for (TIntObjectIterator<DictAttributes> iterator = DictAttributes.map.iterator(); iterator.hasNext(); )
    {
        iterator.advance();

        int attr = iterator.key();

        DictAttributes dict = iterator.value();

        if (dict.getIsFirstAttr() == 1)
        {
            //置为true
            firstFlagArray[attr] = true;
        }
    }
}

初始化表,如果配置的是一级属性组,那么对应firstFlagArray数组索引置为true。

索引对应Attribute表主键id。

目前暂时认为1级属性是有最大值的属性。

3.初始化组属性,主属性和各个因子ABCD
public static void initGroupMianFactorAttr()
{
    ...
    ...
    //就是groupAttrA()...有配置的attribute表主键id集合
    groupMainAttrList = groupMapping.keys();                                // 组属性集合
    groupMainAttrFlagArray = new boolean[AttributeTypeEnum.count];          // 组属性主属性标识集合
    //映射
    groupMainToFactorList = new TIntArrayList[AttributeTypeEnum.count];     // 组属性 主属性 -> 各个因子ABCD
    groupFactorToMainArray = new int[AttributeTypeEnum.count];              // 组属性 各个因子 -> 主属性
    Arrays.fill(groupFactorToMainArray, -1);
    for (TIntObjectIterator<int[]> iterator = groupMapping.iterator(); iterator.hasNext(); )
    {
        iterator.advance();
        //attribute表主键id
        int mianAttr = iterator.key();
        //ABCDEF值集合
        int[] factorArray = iterator.value();

        //当前是组属性的根据主键id做索引置为true
        groupMainAttrFlagArray[mianAttr] = true;

        groupMainToFactorList[mianAttr] = new TIntArrayList(factorArray.length);

        for (int i = 0, size = factorArray.length; i < size; i++)
        {
            //主属性(attribute表主键id)做数组索引,ABCDEF(该值也是表主键id)做数组值
            //主属性 -> 各个因子ABCD
            groupMainToFactorList[mianAttr].add(factorArray[i]);

            //各个因子 -> 主属性
            groupFactorToMainArray[factorArray[i]] = mianAttr;
        }
    }
    ...
    ...

}

什么是组属性?组属性ABCDEF有配置的就是组属性。

初始化:

/////////////////////////////// //////// 组 属 性 ///////// ///////////////////////////////

private static int[] groupMainAttrList; // 组属性主属性集合

private static boolean[] groupMainAttrFlagArray; // 组属性主属性标识集合

private static TIntArrayList[] groupMainToFactorList; // 组属性 主属性 -> 各个因子ABCD

private static int[] groupFactorToMainArray; // 组属性 各个因子 -> 主属性

4.初始化元素攻击属性
public static void initElementAttr()
{
    ...
    ...
     //冰火雷毒玄对应数组索引23456,对Attribute表主键id值21 24 27 30 33
    formulaAttackTypeToAttackAttrArray[FormulaAttackTypeEnum.ICE_ATTACK.getIndex()] = AttributeTypeEnum.ICE_ATTACK.getIndex();
    formulaAttackTypeToAttackAttrArray[FormulaAttackTypeEnum.FIRE_ATTACK.getIndex()] = AttributeTypeEnum.FIRE_ATTACK.getIndex();
    formulaAttackTypeToAttackAttrArray[FormulaAttackTypeEnum.THUNDER_ATTACK.getIndex()] = AttributeTypeEnum.THUNDER_ATTACK.getIndex();
    formulaAttackTypeToAttackAttrArray[FormulaAttackTypeEnum.POISON_ATTACK.getIndex()] = AttributeTypeEnum.POISON_ATTACK.getIndex();
    formulaAttackTypeToAttackAttrArray[FormulaAttackTypeEnum.DARK_ATTACK.getIndex()] = AttributeTypeEnum.DARK_ATTACK.getIndex();

    for (int attackType = FormulaAttackTypeEnum.ICE_ATTACK.getIndex(); attackType <= FormulaAttackTypeEnum.DARK_ATTACK.getIndex(); attackType++)
    {
        //对Attribute表主键id值21 24 27 30 33
        int attackAttr = formulaAttackTypeToAttackAttrArray[attackType];

        DictAttributes attactAttrDict = DictAttributes.getRecordById(attackAttr);

        //元素攻击对应元素防御  例如:21冰攻--元素攻击对应元素防御22--忽略冰防23--148冰伤害减免(受击万分比)--159冰伤害回血率(受击万分比)
        formulaAttackTypeToToDefenceArray[attackType] = attactAttrDict.getElementDefence();
        formulaAttackTypeToToIgnoreDefenceArray[attackType] = attactAttrDict.getElementIgnoreDefence();
        formulaAttackTypeToToAmplifyArray[attackType] = attactAttrDict.getElementAmplify();
        formulaAttackTypeToToDerateArray[attackType] = attactAttrDict.getElementDerate();
        formulaAttackTypeToToBeatHealArray[attackType] = attactAttrDict.getElementBeHeal();
    }
    ...
    ...
}

例如21冰攻--元素攻击对应元素防御22--忽略冰防23--148冰伤害减免受击万分比--159冰伤害回血率(受击万分比)

5.初始取值正负
public static void initValuePositive()
{
//// 取值必须为正的属性集合
attrValuePositiveFlagArray = new boolean[AttributeTypeEnum.count];

for(int i = 0; i < AttributeTypeEnum.count; i++)
{
    attrValuePositiveFlagArray[i] = true;
}

for (TIntObjectIterator<DictAttributes> iterator = DictAttributes.map.iterator(); iterator.hasNext(); )
{
    iterator.advance();

    int attr = iterator.key();

    DictAttributes dict = iterator.value();

    // 先初始化为true, 因为大部分属性都必须为正.
    // 表中的注释是(是否可以为负数(1可以为负 0必须为正))
    attrValuePositiveFlagArray[attr] = true;

    // 特殊的可以为负值的, 设置为false
    if (dict.getIsCanNegative() == 1)
    {
        attrValuePositiveFlagArray[attr] = false;
    }
}
}

记录取值必须为正的属性集合和可以为负的属性集合(走表配置)

6.初始化广播属性

根据Attribute表里配置的广播类型初始化到对应推送组里。

2.初始化属性控制类

AttributeControl.init(reloadDictClazzs);

1.初始化属性被影响组(AttrFactorConfig表)
private static void initAttributeBeInfluence()
{
    attributeFirstInfluenceSecondDic = new int[DictAttrFactorConfig.map.size()][AttributeTypeEnum.count][];
    attributeSecondBeInfluenceFirstDic = new int[DictAttrFactorConfig.map.size()][AttributeTypeEnum.count][];

    //把表里的属性获取方法拿到(secondKey->firstKey->Method)
    Map<Integer, Map<Integer, Method>> map = new HashMap<>();
    //(firstKey->secondKey的HashSet集合)
    Map<Integer, Set<Integer>> map2 = new HashMap<>();

    Method[] methods = DictAttrFactorConfig.class.getMethods();

    for (Method m : methods)
    {
        if (m.getName().startsWith("getD_"))
        {
            String[] ar = m.getName().split("_");

            //一级属性
            int firstAttr = Integer.parseInt(ar[1]);//1
            //二级属性
            int secondAttr = Integer.parseInt(ar[2]);//2

            Map<Integer, Method> dic = map.get(secondAttr);

            if (dic == null)
            {
                dic = new HashMap<>();
                map.put(secondAttr, dic);
            }

            dic.put(firstAttr, m);

            Set<Integer> dic2 = map2.get(firstAttr);

            if (dic2 == null)
            {
                dic2 = new HashSet<>();
                map2.put(firstAttr, dic2);
            }

            dic2.add(secondAttr);
        }
    }

    TIntObjectIterator<DictAttrFactorConfig> it = DictAttrFactorConfig.map.iterator();

    while (it.hasNext())
    {
        it.advance();

        DictAttrFactorConfig dictAttrFactorConfig = it.value();

        //根据职业序号AttrFactorConfig主键获取二维数组
        int[][] tempInfluenceDic = attributeSecondBeInfluenceFirstDic[dictAttrFactorConfig.getId()];

        for (Entry<Integer, Map<Integer, Method>> entry : map.entrySet())
        {
            //初始化Map<Integer, Method>的长度 * 2
            int[] tempInfluenceArr = new int[entry.getValue().size() * 2];

            int i = 0;

            for (Entry<Integer, Method> kv2 : entry.getValue().entrySet())
            {
                //firstKey一级属性
                tempInfluenceArr[i] = kv2.getKey();

                try
                {
                    //通过反射获取影响的值
                    tempInfluenceArr[i + 1] = (int) (kv2.getValue().invoke(dictAttrFactorConfig));
                } catch (Exception e)
                {
                    e.printStackTrace();
                }

                i += 2;
            }

            // int[secondKey][] = int[firstKey, 影响值]
            tempInfluenceDic[entry.getKey()] = tempInfluenceArr;
        }

        //根据职业序号(AttrFactorConfig主键)获取二维数组
        int[][] firstToSecondArray = attributeFirstInfluenceSecondDic[dictAttrFactorConfig.getId()];

        for (Entry<Integer, Set<Integer>> kv : map2.entrySet())
        {
            int[] tempArr = new int[kv.getValue().size()];

            int i = 0;

            //v为二级属性
            for (int v : kv.getValue())
            {
                //通过组属性 各因子ABCD获取主属性(也就是二级属性的组属性)
                tempArr[i] = AttributeTypeDefine.getMainAttrByFactor(v);

                i++;
            }

            firstToSecondArray[kv.getKey()] = tempArr;
        }
    }
}
/**
* 二级属性被影响组(每个二级属性受那些一级属性影响)(vocation->secondKey->[firstKey0,firstValue0,firstKey1,firstValue1])
*/
private static int[][][] attributeSecondBeInfluenceFirstDic;

/**
* 二级属性影响组(每个一级属性影响那些二级属性)(vocation->firstKey->[secondMainKey0,secondMainKey1])
*
* secondMainKey0的主属性
*/
private static int[][][] attributeFirstInfluenceSecondDic;

这里特别要注意:secondMainKey0的主属性,是通过组映射或取主属性的。

表主键属性类型id其实也就是职业序号。

d_10_43 = 120000:
10是一级属性,43是二级属性,一级属性影响二级属性的值120000。

体质对生命的影响系数,万分比。
2.通过配置表计算出每个职业,每个等级的基础属性(人物部分)

initAttributeLevel();

private static void initAttributeLevel()
{
    ...
    ...
    TIntObjectIterator<DictAttrFactorConfig> it = DictAttrFactorConfig.map.iterator();
    while (it.hasNext())
    {
        it.advance();

        DictAttrFactorConfig dictAttrFactorConfig = it.value();

         //侠客职业系数统计等级字典(vocation->属性key->value) 万分比
        double[] knightBaseAttrArray = knightAttributeBaseDic[dictAttrFactorConfig.getId()];
        //主角属性职业统计等级字典(vocation->level->key->value)
        int[][] actorAttributeCoubtLevelArray = actorAttributeCountLevelDic[dictAttrFactorConfig.getId()];
        //等级-》属性
        double[][] tempArr = dLevelDic[dictAttrFactorConfig.getId()];

         // s组,初始属性
        for (Entry<Integer, Method> baseSEntry : baseSMap.entrySet())
        {
            int value = 0;

            try
            {
                Method baseSMethod = baseSEntry.getValue();
                value = (int) (baseSMethod.invoke(dictAttrFactorConfig));
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }

            //基础属性key值(对应Attribute表主键id)
            int attrIndex = baseSEntry.getKey();

            //迭代最大等级
            for (int i = 0; i < levelLen; ++i)
            {
                //累加上一个等级的基础值
                //tempArr[等级][key] = value累加上一个等级
                tempArr[i][attrIndex] += value;

                //先赋值一次,以防等级组里没有该属性
                actorAttributeCoubtLevelArray[i][attrIndex] = (int) (Math.ceil(tempArr[i][attrIndex]));
            }
        }

        // 侠客属性率
        for (Entry<Integer, Method> knightAttrRatioKEntry : knightAttrRatioKMap.entrySet())
        {
            int value = 0;

            try
            {
                Method method = knightAttrRatioKEntry.getValue();
                value = (int) (method.invoke(dictAttrFactorConfig));
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }

            int attrIndex = knightAttrRatioKEntry.getKey();

            // 统一万分比
            double valuedouble = value / GameConstant.TEN_THOUSAND_DOUBLE;

            knightBaseAttrArray[attrIndex] = valuedouble;

        }

        ...
        ...

        //计算战力
        actorFightForceCountLevelDic = new int[DictAttrFactorConfig.map.size()][levelLen];

        for (int i = 0; i < DictAttrFactorConfig.map.size(); ++i)
        {
            //tempArr[等级][key]  level->key->value
            int[][] tempArr = actorAttributeCountLevelDic[i];

            for (int j = 0; j < levelLen; ++j)
            {
                //tempArr[等级][key]对应的战力
                actorFightForceCountLevelDic[i][j] = getAttributeFightForceForOnlySecond(tempArr[j], i, defaultAptitudes);
            }
        }
}
}

根据AttrFactorConfig表:

//主角属性职业统计等级字典(vocation->level->key->value)
//当前等级基础等于累加上一个等级的基础值
//初始属性组,这里的key是表里配置的s_121的数字部分121
int[][] actorAttributeCoubtLevelArray = actorAttributeCountLevelDic[dictAttrFactorConfig.getId()];

//侠客职业系数统计字典(vocation->属性key->value) 万分比
double[] knightBaseAttrArray = knightAttributeBaseDic[dictAttrFactorConfig.getId()];

//初始化战力tempArr[等级][key]对应的战力
getAttributeFightForceForOnlySecond(tempArr[j], i, defaultAptitudes);
/**
* 将组中所有一级属性转化为二级属性,然后计算战力(不考虑百分比,只初始化(角色/宠物)基础用)
* <p>
*     等级对应的基础属性组int[] attributes int[key] = value (例如:等级10对应的所有基础属性105、197、120...值,
*      * 那么int[105,197,120] = xxx)
* </p>
* int vocation 职业
* int[] aptitudes通用权重分母值 (千分之)组
*/
public static int getAttributeFightForceForOnlySecond(int[] attributes, int vocation, int[] aptitudes)
{
    if(aptitudes == null)
    {
        BPLog.BP_LOGIC.error("【评分计算错误】资质不存在,这里应该只有宠物评分调用",new RuntimeException("该异常只是为了打印堆栈"));
        return -1;
    }
    //二级属性被影响组(每个二级属性受那些一级属性影响)secondKey->[firstKey0,firstValue0,firstKey1,firstValue1])
    int[][] beInfluences = attributeSecondBeInfluenceFirstDic[vocation];

    //int[主属性] = 累加后端基础属性值
    int[] firstTs = new int[AttributeTypeEnum.count];

    //遍历当前等级的基础属性组值
    for (int i = attributes.length - 1; i >= 0; --i)
    {
        if (i < AttributeTypeEnum.count && attributes[i] != 0)
        {
            //获取主属性
            int groupKey = AttributeTypeDefine.getMainAttrByFactor(i);

            //如果有主属性并且是一级属性并且自身的key和主属性不相等
            if (groupKey > 0 && AttributeTypeDefine.isFirstAttr(groupKey) && i != groupKey)
            {
                // XXX !!!  属性的B,D也按A,C一样处理了,因为策划说不会配B,D
                //主属性为索引,累加所有的基础属性值
                firstTs[groupKey] += attributes[i];
            }
        }
    }

    //计算战力
    double re = 0.0;

    double v;

    for (int i = attributes.length - 1; i >= 0; --i)
    {
        v = attributes[i];

        if (i < AttributeTypeEnum.count)
        {
            //跳过1级属性
            int groupM = AttributeTypeDefine.getMainAttrByFactor(i);
            if (groupM > 0 && AttributeTypeDefine.isFirstAttr(groupM))
            {
                continue;
            }

            //二级属性被影响组(每个二级属性受那些一级属性影响)secondKey->[firstKey0,firstValue0,firstKey1,firstValue1])
            //所以ks代表会影响当前基础属性的所有一级属性
            int[] ks = beInfluences[i];

            if (ks != null)
            {
                int len = ks.length;

                for (int j = 0; j < len; j += 2)
                {
                    //key
                    int fType = ks[j];

                    //ks[j + 1]即对应的值

                    //根据公式累加战力
                    v += firstTs[fType] * (ks[j + 1] / GameConstant.TEN_THOUSAND_DOUBLE) * (aptitudes[fType] / GameConstant.THOUSAND_DOUBLE);
                }
            }
        }

        if (v != 0.0)
        {
            re += DictAttributes.getRecordById(i).getValue() / GameConstant.TEN_THOUSAND_DOUBLE * v;
        }
    }

    return (int) re;
}

初始化战力,tempArr[等级][key]对应的战力。

现在最新的侠客机制,已经不使用这个战力组了。

最新的机制,根据出战侠客计算玩家战力,也就是玩家战力等于出战侠客(队伍战力)。

2.计算每个侠客星级带来的属性中B部分的加成(KnightLevelAttribute侠客属性表)

initAttributePerStar();

侠客星级和B属性统计等级字典(vocation->星级->属性key->value) 万分比,也就是初始化,星级改变引起的属性加成值

/**
* 计算每个侠客星级带来的属性中B部分的加成 
*/
private static void initAttributePerStar()
{
    //B组,除了173外, 其他的属性变成+1的B属性
    //    力量加1从53-A属性变成54-B属性
    //    STRENGTH_A(53),
    //    STRENGTH_B(54),
    // 对应key索引数组值置为true
    boolean[] BList = new boolean[AttributeTypeEnum.count];
    Method[] methods = DictKnightLevelAttribute.class.getMethods();
    for (Method m : methods)
    {
        if (m.getName().startsWith("getS_"))
        {
            String[] ar = m.getName().split("_");

            int key = Integer.parseInt(ar[1]);// 属性id

            if(key != AttributeTypeEnum.RECOVERY_MP.getIndex())
            {
                // 除了173(回蓝)外, 其他的都会加1
                BList[key + 1] = true;
            }
        }
    }

    //侠客最大星级
    int maxStar = DictKnightStarUpData.getMaxStar() + 1;
    //侠客星级和B属性统计等级字典(vocation->星级->属性key->value) 万分比
    knightStarAttributeBDic = new double[DictAttrFactorConfig.map.size()][maxStar][AttributeTypeEnum.count];
    
    TIntObjectIterator<DictAttrFactorConfig> it = DictAttrFactorConfig.map.iterator();

    while (it.hasNext())
    {
        it.advance();

        DictAttrFactorConfig dd = it.value();

        //星级->属性key->value
        double[][] starB = knightStarAttributeBDic[dd.getId()];
        
        for(int star = 1; star< maxStar; star++)
        {
            //根据侠客id和星级获取数据
            DictKnightStarUp dataByKnightIdAndStar = DictKnightStarUpData.getDataByKnightIdAndStar(dd.getId(), star);
            if(dataByKnightIdAndStar == null)
            {
                continue;
            }
            
            double[] ds = starB[star];
            //长度等于枚举长度AttributeTypeEnum.count
            int len = ds.length;
            for(int i = 0; i< len; i++)
            {
                if(BList[i])
                {
                    //成长系数(万分比)
                    //    力量加1从53-A属性变成54-B属性
                    //    STRENGTH_A(53),
                    //    STRENGTH_B(54),
                    // 假设遍历刚好到了54,那么星级改变,这个54作为key对应的成长系数会改变
                    ds[i] = dataByKnightIdAndStar.getGrowRatio();
                }
            }
        }
    }
}

说白了,就是KnightLevelAttribute表配置的key值,除了173回蓝外, 其他的属性变成+1的B属性,

侠客随着星级改变成长系数会改变-成长系数值(万分比)对应KnightStarUp表,

然后职业序号-》星级-》B属性key-》成长系数,做映射。

更通俗的讲:

double[] starBValue = AttributeControl.getKnightStarAttributeBDic()[fightVocation][star];

根据侠客职业和星级就可以获取所有星级改变然后属性会变的值。

3.总结

这里的侠客职业,现在修改了,其实就是侠客的属性类型ID(侠客表的attrTypeID)。

当前值就是Attribute表里配置了maxAttr,例如:当前主键id等于1动态当前生命,对应的最大值maxAttr = 2 生命上限(总)。

其它的就是初始化属性组字典:

1.初始化当前值和最大值—AttributeTypeDefine.init(reloadDictClazzs);

2.初始化基础属性(初始化属性控制类),为后面升级,升星,添加,减少,改变侠客基础属性做准备–AttributeControl.init(reloadDictClazzs);

2.属性推送基本流程

1.创建新号属性推送过程

public Actor createActor(BPLoginObject loginObject, String accountID, long actorID, ActorDataCache actorDataCache, int birthX, int birthY, int birthZ, int rotation, int defaultKnightID) { … actor.afterLoad();

...
int result = actor.getKnightModule().addAndUnlockKnight(defaultKnightID);

}

1.actor.afterLoad()下attribute.afterLoad()

ActorAttributeModule.java

@Override
public void afterLoad()
{
    ...
    ...

    //设置设置战斗职业,属性影响组,资质,都是从缓存在内存里的字典里拿取
    getActor().getAttributeLogic().setFightVocation(commonModule.getFightVocationIndex());
    
    //根据职业和等级从内存中添加基础属性(怒气啥的,AttrFactorConfig表的S组)
    getActor().getAttributeLogic().addAttributes(AttributeControl.getActorAttributeCountLevelDic()[commonModule.getFightVocationIndex()][commonModule.getLevel()]);

    //刷新属性
	getActor().getAttributeLogic().refreshAttributes();

    //刷新战力
	refreshFightForce();	
    ...
}

设置设置战斗职业,属性影响组。

刷新战力。

添加基础属性和刷新属性其实不用在这里操作,因为后面添加侠客了,会重新计算。

2.addAndUnlockKnight(defaultKnightID)添加侠客属性计算

actor.getKnightModule().addAndUnlockKnight(defaultKnightID);

/**
* 添加并解锁侠客
* @param knightID 要添加的侠客id
* @return
*/
public int addAndUnlockKnight(int knightID)
{
    //这里新new了一个Knight实体,并初始化挂在AbstractCharacter类下的模块(创建属性模块并初始化属性组)
    Knight knight = addKnight(knightID);

    //添加天赋技能,计算战力,更新基础属性。
    //回满蓝血霸体(当前属性,需要存库)
    int result = unlockKnight(knight);
}

这里新new了一个Knight实体,并初始化挂在AbstractCharacter类下的模块(所以需要重新创建属性模块并初始化属性组)。

添加天赋技能,计算战力,更新基础属性。回满蓝血霸体(当前属性,需要存库)。

@Override
public void init()
{
    ...
    attributeModule = createAttributeLogic();

    this.getAttributeLogic().init(hasAptitude());
    ...
}

创建属性模块并初始化属性组长度。

/**
* 解锁侠客
*
* @param knight     侠客实体
*/
private int unlockKnight(Knight knight)
{
    ...
    ...
    // 计算战力
    calcKnightFightForce(knight, true);

     //更新基础属性
    updateBaseAttributeByAddKnight(knightId);

     //回满蓝血霸体
    knight.getAttributeLogic().fillHpMp();
    ...
    ...
}

所以每次侠客增加会进行如下操作:

更新基础属性(相当于侠客增加),也可以算是总的属性会随等级变化而变化。

回满蓝血霸体,当前属性,下线需要存库。

2.侠客基础属性的计算和推送

1.等级改变的时候, 基础属性变化
/**
* 等级改变的时候, 基础属性变化
*
* @param beforeLevel 之前的等级
* @param afterLevel 现在的等级
*/
public void baseAttrOnLevelChange(int beforeLevel, int afterLevel)
{
    ...
    //升级重置侠客等级的基本属性
    resetKnightLevelAttr(beforeLevel, afterLevel);

    if (beforeLevel > 0)
    {
        changeKnightBaseAttr_LevelChange(beforeLevel, star, KnightConstant.KNIGHT_MINUS_ATTR_TYPE);
    }
    //等级变化综合属性
    changeKnightBaseAttr_LevelChange(afterLevel, star, KnightConstant.KNIGHT_ADD_ATTR_TYPE);

    //计算并刷新推送属性组
    getAttributeLogic().refreshAttributes();
}

升级重置侠客等级的基本属性是根据主角基础属性职业S组,也就是计算DictAttrFactorConfig中的属性。

等级变化综合属性KnightLevelAttribute表,DictAttrFactorConfig表中K组。。。等等

计算并刷新推送属性组。

/**
* 升级重置侠客等级的基本属性
*
* @param beforeLevel 之前的等级
* @param afterLevel 之后的等级
*/
private void resetKnightLevelAttr(int beforeLevel, int afterLevel)
{
    ...
    ...
    if(beforeLevel > 0)
    {
        int[] attr = AttributeControl.getActorAttributeCountLevelDic()[fightVocation][beforeLevel];
        getAttributeLogic().minusAttributes(attr);
    }

    if(afterLevel > 0)
    {
        int[] attr = AttributeControl.getActorAttributeCountLevelDic()[fightVocation][afterLevel];
        getAttributeLogic().addAttributes(attr);
    }
    ...
    ...
}

升级重置侠客等级的基本属性是根据主角基础属性职业S组,也就是计算DictAttrFactorConfig中的属性。

先减去之前等级的,加上当前等级的。

/**
* 等级变化
* @param level
* @param star 
* @param type 1减少,2增加
*/
private void changeKnightBaseAttr_LevelChange(int level, int star, int type)
{
    // 侠客基础属性 = attrFactor + 基础属性(等级X职业系数)*(1+(星级系数+收集加成)/10000.0), 只需要各自算出A的总和, B的总和. 然后带入AttributeTypeEnum的公式计算 
    // (ABCD含义,Total=(A*(1+B/10000.0)+C)*(1+D/10000.0))   
    // 其中 attrFactor 部分是策划默认加的, 公式中并未给出
    int fightVocation = getFightVocation();

    //根据侠客和等级获取KnightLevelAttribute初始属性
    int[] attr = DictKnightLevelAttributeData.getInitAttrByLevel(level);

    double[] levelRatio =  AttributeControl.getKnightAttributeBaseDic()[fightVocation];
    double[] starBValue = AttributeControl.getKnightStarAttributeBDic()[fightVocation][star];
    for(int attrType = 0; attrType < AttributeTypeEnum.count; attrType++)
    {
        //根据公式计算值
        int value = calBaseAttr(attr[attrType], levelRatio[attrType], starBValue[attrType]);
        
        int collectType = DictKnightCollectAttrData.getCollectTypeByAttribute(attrType);
        if(collectType > 0)
        {
            int collectRatioValue = getMasterActor().getKnightModule().getCollectAttrRatio(collectType);
            value += KnightModule.calMaxAndCollectTypePartValue(collectRatioValue);
        }
        
        if(value > 0)
        {
            if (type == KnightConstant.KNIGHT_MINUS_ATTR_TYPE)
            {
                getAttributeLogic().minusOneAttribute(attrType, value);
            }
            else
            {
                getAttributeLogic().addOneAttribute(attrType, value);
            }
        }
    }
}

等级变化综合属性KnightLevelAttribute表,DictAttrFactorConfig表中K组。。。等等

2.设置属性值

设置当个属性值

/**
* 设置单个属性值
*/
public void setOneAttribute(int type, int value)
{
    if(value < 0 && AttributeTypeDefine.isMustAttrValuePositive(type))
    {
        value = 0;
    }

    this.attributes[type] = value;

    //是否需要置脏推送标志
    makeOneDirty(type);
}

增加、减少属性都和设置当个属性值类似,会根据属性类型type判断是否需要置脏推送。

private void makeOneDirty(int type)
{
    ...
     // 是否需要置脏推送标志,表类配置了通知自己、通知他人、npc广播字段为1,当前属性需要广播
    boolean[] allMaybeSendSet = AttributeTypeDefine.needDispatchAttrAllSet;
    if (allMaybeSendSet[type])
    {
        this.dispatchDirty = true;
    }

    // 是否当前属性,如果是当前属性变化了,置相关状态并且return返回,不进行组属性操作。
    // 那么在getAttributeLogic().refreshAttributes()时会调用countAttributes()计算
    if (AttributeTypeDefine.isCurrentAttr(type))
    {
        this.attributeModifications[type] = true;
        this.attributeModified = true;
        return;
    }

      // 如果当前设置的type属性改变了,type属于组属性,因子变了主属性也需要修改,那么组属性需要重新计算
    // 如果是组属性和当前属性互斥(组属性就是ABCDEF字段有值)
    // 通过因子ABCDEF取主属性
    int groupMainAttr = AttributeTypeDefine.getMainAttrByFactor(type);
    if (groupMainAttr > 0)
    {
        //主属性刚好等于当前type属性,主属性不能set设置,一定是通过其他因子计算出来
        if (groupMainAttr == type)
        {
            //【属性错误】不能设置组属性主属性也就是总值
            if(BPGlobals.getInstance().isDebugVersion())
            {
                BPLog.BP_LOGIC.warn("【属性错误】不能设置组属性主属性也就是总值 [mianAttr] {}",groupMainAttr,new RuntimeException("该异常用于打印堆栈"));
                return;
            }
        }

        // 需要推送,当前属性需要广播
        if (allMaybeSendSet[groupMainAttr])
        {
            this.dispatchDirty = true;
        }

        // 置脏,那么在getAttributeLogic().refreshAttributes()时会调用countAttributes()计算
        this.attributeModifications[groupMainAttr] = true;
        this.attributeModified = true;

        // 如果最大值属性(五项值结果中有一部分是最大值)变了,那么同样修改最大值影响的当前值
        // type变化导致上面主属性置脏了说明主属性也改变了,那么主属性影响的当前属性也要修改所以置脏
        int curAttr = AttributeTypeDefine.getCurrentAttrByMaxAttr(groupMainAttr);
        if (curAttr > 0)
        {
            this.attributeModifications[curAttr] = true;
        }

        // 置脏一级属性影响的二级属性
        // 如果主属性是一级属性影响的二级属性不为空
        int[] firstInfluenceSecondArray = this.attributeInfluenceDic[groupMainAttr];

        if (firstInfluenceSecondArray != null)
        {
            for (int i = firstInfluenceSecondArray.length - 1; i >= 0; --i)
            {
                int secondAttr = firstInfluenceSecondArray[i];

                //二级属性被一级属性影响,所以置脏重新计算
                this.attributeModifications[secondAttr] = true;

                // 如果二级属性最大值属性(五项值结果中有一部分是最大值)变了,那么同样修改最大值影响的当前值
                // 上面置脏说明二级属性修改了,那么二级属性影响也会影响当前属性也要计算
                int secondCurrentAttr = AttributeTypeDefine.getCurrentAttrByMaxAttr(secondAttr);
                if (secondCurrentAttr != 0)
                {
                    //当前属性为curAttr
                    this.attributeModifications[curAttr] = true;
                }
            }
        }
    }
    ...

}

是否需要置脏推送标志,表类配置了通知自己、通知他人、npc广播字段为1,当前属性需要广播

是否当前属性,如果是当前属性变化了,置相关状态并且return返回,不进行组属性操作。 那么在getAttributeLogic().refreshAttributes()时会调用countAttributes()计算。

当前修改的type是组属性,那么主属性需要重新计算,并且主属性影响的当前属性重新计算,

如果主属性又是一级属性,那么受一级属性影响的二级属性需要重新计算,二级属性影响的当前属性也要重新计算,

全部置脏,等待调用getAttributeLogic().refreshAttributes()时重新计算。

3.计算并刷新推送属性组

getAttributeLogic().refreshAttributes()

/**
* 计算并刷新推送属性组
*/
public void refreshAttributes()
{
    //属性修改标记要先计算
    if (this.attributeModified)
    {
        countAttributes();
    }

    //需要推送
    if (!this.dispatchDirty)
    {
        return;
    }

    this.dispatchDirty = false;

    //属性改变回调,同时推送
    dispatchAttributeChange();
}
/**
* 计算属性(不推送)
*/
public void countAttributes()
{
    if (!this.attributeModified)
    {
        return;
    }

    this.attributeModified = false;

    //修改标记(只用于当前属性和组属性)
    boolean[] attributeModifications = this.attributeModifications;

    // 获取组属性主属性集合
    int[] groupList = AttributeTypeDefine.getGroupMainAttrList();
    for (int i = 0, iSize = groupList.length; i < iSize; ++i)
    {
        int type = groupList[i];

        //修改了
        if (attributeModifications[type])
        {
            attributeModifications[type] = false;

            // 根据公式计算单个的组属性
            countOneGroupAttribute(type);
        }
    }

    //刷当前属性
    int[] attributes = this.attributes;
    //当前值集合,有最大值的当前值集合
    int[] hasMaxAttrCurrentArray = AttributeTypeDefine.hasMaxAttrCurrentArray;
    for(int i = 0 , size = hasMaxAttrCurrentArray.length; i < size; i++)
    {
        //当前属性索引
        int curAttr = hasMaxAttrCurrentArray[i];

        int maxAttr = AttributeTypeDefine.getMaxAttrByCurrent(curAttr);

        //修改了
        if (attributeModifications[curAttr])
        {
            //最新的当前值
            int nowValue = attributes[curAttr];
            //允许最大值
            int maxValue = attributes[maxAttr];

            //范围
            if (nowValue > maxValue)
            {
                nowValue = maxValue;
                attributes[curAttr] = nowValue;
            }

            if (nowValue < 0  && AttributeTypeDefine.isMustAttrValuePositive(curAttr))
            {
                attributes[curAttr] = 0;
            }
        }
    }
}

迭代获取组属性主属性集合,迭代当前值集合(有最大值的属性),然后根据置脏数据判断是否修改了,如果修改了,

重新计算值,这个方法是不进行推送给前端的。

//属性改变回调,同时推送
private void dispatchAttributeChange()
{
    int type;
    int value;

    //改变属性数量
    int num = 0;

    //最新的属性组(最新值已经计算过了)
    int[] attributes = this.attributes;
    //上次的属性组(推送用)
    int[] lastAttributes = this.lastDispatches;
    //上次的属性组(用于记录两次属性变化事件)
    int[] lastAttributesRecord = this.lastAttributes;
    //改变组(也做临时组)
    int[] changeList = this.changeList;
    // 锁定属性组(不能锁计算因子)
    int[] lockAttributes = this.lockAttributes;
    //锁定属性状态组(不能锁计算因子)(这里是指例如帮派货运这种的,调用锁定方法,锁住了速度等等)
    boolean[] lockAttributesStatus = this.lockAttributesStatus;

    //所有有关推送的属性
    int[] needDispatchList = AttributeTypeDefine.needDispatchAttrAllList;
    //迭代需要推送的所有属性
    for (int i = needDispatchList.length - 1; i >= 0; --i)
    {
        //需要推送的属性索引
        type = needDispatchList[i];
        //当前值
        value = attributes[type];
        if (lockAttributesStatus[type])
        {
            //直接让当前值等于之前缓存的锁定属性值(覆盖当前值)
            value = lockAttributes[type];
        }

        //如果上次推送给客户端的值和当前value不相等,说明改变了
        if (lastAttributes[type] != value)
        {
            //更新上一次推送组
            lastAttributes[type] = value;
            //并记录到改变组changeList
            changeList[num++] = type;
        }
    }

    if (num > 0)
    {
        //改变组changeList,数量,上次的属性组(用于记录两次属性变化事件)
        //属性改变回调
        onAttributesChange(changeList, num,lastAttributesRecord);
    }

    //上次的属性组(推送用),拷贝给上次的属性组(用于记录两次属性变化事件)
    System.arraycopy(lastAttributes,0,lastAttributesRecord,0,lastAttributes.length);

}

迭代需要推送的所有属性。

判断是否锁定属性,锁定属性不变采用锁定的值。

将改变的并且需要推送的属性添加到改变组changeList。

先看CharacterAttributeUseModule.java下的属性改变回调:

 @Override
protected void onAttributesChange(int[] changeList,int length, int[] lastAttributes)
{
    ...
    血量比例变化事件
    ...

    // 推送属性变化
    dispatchAttributesChange(changeList,length,lastAttributes);
}
/**
* 推送属性变化
* @param changeList
* @param length
* @param lastAttributes
*/
private void dispatchAttributesChange(int[] changeList,int length, int[] lastAttributes)
{
    ...
    ...
    //是否需要广播
    boolean isRadio = true;
    switch (character.getObjectType())
    {
        case KNIGHT:
        {
            if(objectID < 0)
            {
                //非出战侠客不广播
                isRadio = false;
                Knight knight = (Knight) character;
                knightId = knight.getKnightID();
            }
            sets = AttributeTypeDefine.actorOtherRadioSet;
            selfSets = AttributeTypeDefine.actorSelfRadioSet;
            break;
        }

        for (int i = length - 1; i >= 0; --i)
        {
            ...
            ...
            /需要推送, objectId 小于0的不推送
            if(isRadio)
            {
                //需要广播给其他人(蓝、生命、霸体,速度, 游泳速度)
                if (sets != null && sets[changeList[i]])
                {
                    list.add(changeList[i]);
                    list.add(getAttribute(changeList[i]));
                }
            }

            //需要推送自己
            if (selfSets != null && selfSets[changeList[i]])
            {
                selfList.add(changeList[i]);
                selfList.add(getAttribute(changeList[i]));
            }
            ...
            ...
        }
    }
    ...
    ...
    迭代changeList推送其他人或者自己
    ...
    ...
}

这里特别要注意,非出战侠客(objectID < 0)不广播!!!!!!

如果不是出战侠客,那么不会广播给其他人,但是还是会发送自己。(所以,现在服务端自定义属性组,没有意义了是个多余操作。)

出战侠客迭代属性改变组,推送其他人或自己。

再看CharacterAttributeUseModule子类侠客属性展示类KnightAttributeUseModule:

/**
* 属性改变回调(子类没有推送,调用父类的 super.onAttributesChange()方法推送)
* @param changeList
* @param length
* @param lastAttributes
*/
@Override
protected void onAttributesChange(int[] changeList, int length, int[] lastAttributes)
{
    //执行父类的逻辑,但是非出战侠客还是不会推送属性变化
    super.onAttributesChange(changeList, length, lastAttributes);
    ...
    for (int i = length - 1; i >= 0; --i)
    {
        //添加到自己的变化的属性组
        changedAttrs.add(changeList[i]);
    }
}

虽然子类调用了super父类方法,但是非出战侠客还是不会推送属性变化。

将属性变化组添加到自己的变化的属性组,供自己获取调用。

4.自定义属性推送组(因为非出战侠客上面不推送,侠客收集会影响所有侠客,需要自定义属性推送)
// 给客户端发送一个自定义的属性变化消息
TIntHashSet changedAttrs = currentKnight.getAttributeLogic().getChangedAttrs();
if(!changedAttrs.isEmpty())
{
    BPStruct.PBKnightAttrChange.Builder b = BPStruct.PBKnightAttrChange.newBuilder();
    b.setKnightId(currentKnight.getKnightID());
    TIntIterator iter = changedAttrs.iterator();
    while(iter.hasNext())
    {
        int type = iter.next();
        b.addValues(type);
        b.addValues(currentKnight.getAttributeLogic().getAttribute(type));
    }
    builder.addKnightAttrChange(b);
}

走自己单独的协议,推送属性。

这是一个多余操作,待删除。

Search

    Table of Contents