掉落系统学习

概况

掉落系统是RPG类游戏的重要组成部分,直接影响玩家的核心游戏体验。掉落配置表繁多,配置参数复杂。如果策划配错数值,服务端必须对整个系统极为了解才能快速找到出错的地方。

配置

掉落系统拥有基础的五张表,这五张表之间层层相关,一步步决定了最终掉落的物品。

掉落表关系图:

ObjectResource: 怪物表dropResourceIds字段配置下面的掉落ID,同一个怪可以有多种掉落方式

DropResource: gropIds索引到 DropItemGroupResource的Id,确定

DropGropResource: itemGroupId索引到DropItemGroupResource的Id,确定掉落的掉落组

DropItemGroupResource: dropItemId索引到DropItemResource的id,确定掉落里面有哪些掉落项 PS:这张表不是配物品ID!是下面表的ID!!!!

DropItemResource: code索引到物品ID,确定掉落项的各种属性

当需要给一个怪物配置掉落时,我们需要在怪物ObjectResource的dropResourceIds添加一个掉落id,然后在DropResource决定掉落策略(掉地上或者飞包)、掉落触发条件、掉落归属策略、玩家和怪物的掉落条件,之后在DropGropResource决定掉落次数限制、掉落随机范围、随机方式(是否放回)、各个掉落物组的权重,之后在DropItemGroupResource填写各掉落物的权重,最后在DropItemResourc配置掉落物的物品id、数量、类型、是否绑定、是否专用、记录描述等。

对象表 -> 掉落方式表 -> 掉落组表 -> 掉落物组表 -> 掉落物表

额外的相关联表:

DropGroupLimitResource: 掉落限制表,可以配置个人每人每次限制、全服每日次数限制、个人永久次数限制

掉落的业务方法

掉落逻辑处理

@Component
public class DropService {

    // 幸运大爆
    public boolean luckyValueDrop(Player player, int bossId, int x, int y) {
        // ...
    }

    // 幸运大爆是否触发
    public boolean isLuckyDropHit(Player player, PlayerDropEntity playerDropEntity, int bossId) {
        // ...
    }

    // 计算出超级幸运大爆物品奖励
    private Reward getSuperLuckDropReward(Player player) {
        // ...
    }

    // 创建掉落物的配置信息
    public void createDropToGroundItems(Player player, PlayerKillBossDropEvent envet) {
        // ...
    }

    // 战斗服游戏服统一执行飞包掉落物的逻辑
    public void dropToBag(Player player, PlayerDropToBagEvent event) {
        List<DropItemResource> result = calcuteDropItems(player, event.getBossDropInfo(), event.getDropId());
        if(result == null || result.isEmpty()) {
            return;
        }
        DropStrategy.dropToBag(event.getBossDropInfo(), player, result);
    }



    // 根据掉落ID计算出掉落物的逻辑
    private List<DropItemResource> calcutrDropItems(Player player, BossDropInfo bossDropInfo, int dropId) {
        DropResource dropResource = DropManager.getInstance().getDropResource(dropId);
        MapResource mapResource = MapManger.self().getMapResource(bossDropInfo.getMapId());
        Map<Object, Object> params = null;
        if(mapResource.isTransferMap()) {
            // 如果是跨服地图,则对去开服天数取战区平均天数
            params = new HashMap<>();
            params.put("openDay", CenterManager.self().getServerGroupOpenDay());
        }
        // 检验玩家是否符合掉落条件(DropResource中配置的playerConditionsDefs和开服天数)
        Result result = dropResource.getPlayerConditions().verify(player, 1, params);
        if(result.isFali()) {
            return null;
        }
        // 判断是否触发击杀幸运次数掉落
        boolean isPlayerLuckyHit = dropResource.isPlayerLuckyNumHit(player);
        if(!isPlayerLuckyHit) {
            return null;
        }
        boolean isServerLuckyHit = dropResource.isServeLuckyNumHit();
        if(!isServerLuckyHit) {
            return null;
        }
        return DropStrategy.getDropItemAndAddRecord(player, dropResource.getGrouoIds(), params, bossDropInfo);
    }



}

掉落归属策略


public enum DropBelongStrategy {

    // 无归属
    EVERYONE(true),
    // 击杀者归属
    KILLER(false) {
        @Override
        public BelongContext createContext(Npc death, AbstractCreature killer) {
            FightPlayer fightPlayer = killer.getPlayerMasterOrNull();
            if(fightPlayer == null) {
                return BelongContext.valueOf(DropBelongStrategy.EVERYONE);
            }  
            BelongContext belongContext = BelongContext.valueOf(this);
            belongContext.setKillerId(fightPlayer.getObjectId());
            belongContext.setDropBelongStrategy(this);
            return belongContext;
        }

        @Override
        public IBelongStrategy getBelongStrategy(BelongContext belongContext) {
            // 若击杀者不是玩家或者玩家的召唤物,则默认所有人拾取
            return new KillerBelongStrategy(belongContext.getKillerId());
        }

        @Override
        public List<FightPlayer> getBelongFightPlayers(Npc death, AbstractCreature killer) {
            ArrayList<FightPlayer> players = new ArrayList<>(1);
            if(killer != null) {
                AbstractCreature master  = killer.getMasterOrSelf();
                if(master.isPlayer()) {
                    players.add((FightPlayer) master);
                }
            }
            return players;
        }
    }

    // 仇恨最高者归属
    HATRED(true) {
        @Override
        public BelongContext createContext(Npc death,AbstractCreature killer) {
            AbstractCreature abstractCreature = death.getAggroBelong();
            FightPlayer fightPlayer = abstractCreature.getPlayerMasterOrNull();
            if(fightPlayer == null) {
                return BelongContext.valueOf(DropBelongStrategy.EVERYONE);
            }
            BelongContext belongContext = BelongContext.valueOf(this);
            belongContext.setBelongId(death.getAggroBelong.getOjectId());
            return belongContext;
        }
        @Override
        public IBelongStrategy(BelongContext belongContext) {
            return new AggroBelongStrategy(belongContext.getBelongId());
        }
        @Override
        public List<FightPlayer> getBelongFightPlayers(Npc death, AbstractCreature killer){
            AbstractCreature aggroBeong = death.getAggroBelong();
            ArrayList<FightPlayer> players = new ArrayList<>(1);
            if(aggroBelong != null) {
                FightPlayer master = aggroBelong.getPlayerMasterOrNull();
                if(master != null) {
                    players.add(master);
                }
                return palyers;
            }
        }
    }

    // 击杀者公会归属
    KILLER_GUILD(false),

    // 仇恨最高者归者公会归属
     HATRED_GUILD(true) {

     }

     // 击杀者队伍归属
     KILLER_TEAM(ture) {

     }

     // 仇恨最高者公会归属,且需工会玩家在仇恨列表
     HATRED_GUILD_IN_AGGER(ture) {

     }

     // 是否属于玩家
     private boolean isToBelongPlayer;

     DropBelongStrategy(boolean isToBelongPlayer) {
         this.isToBelongPlayer = isToBelongPlayer;
     }

    // 返回掉落拾取策略
    public IBelongStrategy getBelongStrategy() {
        retuen new EveryoneBelongStatergy();
    }

    // 返回掉落归属玩家Id
    public List<FightPlayer> getBelongFightPlayers(Npc death, AbstractCreautre killer) {
        return new ArrayList<FightPlayer>;
    }

    // 创建掉落内容
    public BelongContext createContext() {
        return BrlongContext.valueOf(DropBelongStrategy.EVERYONE);
    }

    public boolean isToBelongPlayer() {
        return isTobelongPlayer;
    }
}

掉落策略


public enum DropStrategy {

    // 掉落到地上
    DropToGroupStrategy {

        @Override
        public void doDrop(Npc death, AbstractCreature killer, DropRsource dropResource) {
            // 确定归属策略影响到的主体,用于决定向谁抛事件
            FightPlayer fightPlayer = null;
            if(dropRsource.getBelongStrategy().isToBelongPlayer()) {
                AbstractCreature aggroBelong = death.getAggroBelong();
                if(aggroBelong == null) {
                    return null;
                }
                fightPlayer = aggroBelong.getPlayerMasterOrNull();
            } else {
                fightPlayer = killer.getPlayerMasterOrNull();
            }
            if(fightPlayer == null) {
                return;
            }
            // 向掉落主体抛掉落事件,掉落主体接受事件之后,进行各种玩家掉落条件以及掉落次数的验证
            fightPlayer.getPlayerBridge().submitEvent(
                PlayerKillBossDropEvent.valueOf(dropResource.getId(), dropResource.getBelongStrategy().createContext(death,killer),
                BossDropInfo.valueOf(death));
            );
        }
    },

    // 掉落到玩家背包
    DropToPlayerBagStrategy {
        @Override
        public void doDrop(Npc death, AbstractCreature killer, DropRsource dropResource) {
            // 确定归属策略
            List<FightPlayer> players = dropResource.getBelongFightPlayers(death,killer);
            for(FightPlayer player : players) {
                player.getPlayerBridge().submitEvent(PlayerDropToBagEvent.valueOf(dropResource.getId(), BossDropInfo.valueOf(death)));
            }
        }
    }

    // ================掉落到仓库,等可后续加类型==============



    public void doDrop(Npc death, AbstractCreature killer, DropResource dropResource) {
        // ...
    }

    // 根据掉落组随机出掉落物信息,根据掉落信息可以转化成掉落到地上的对象,或者掉落到背包的物品
    public static List<DropItemResource> rollGoods(Player player, List<Integer> groupIds) {
        return rollGoods(player, groupIds, null);
    }

    // 随机出掉落物
    public static List<DropItemResource> rollGoods(Player player, List<Integer> groupIds, Map<Object, Object    >) { 
        List<DropItemResource> result = new ArrayList<>();
        for(Integer groupId : groupIds) {
            // 获取掉落组配置
            DropGroupResource groupResource = DropManager.getInstance().getDropGroupResource(groupId);
            if(groupResource == null) {
                // 配了不存在的掉落组说明随机不到东西
                if(!groupResource.checkLimit(player)) {
                    continue;
                }
                CHooser chooser = groupResource.getChooser(plyaer,params);
                List<ChooserItem> itemGroups = ChooserManager.getInstance().chooser(chooser);
                if(itemGroups.isEmpty()) {
                    continue;
                }
                itemGroups.forEach(itemGroup -> {
                    int itemGroupId = Integer.valueOf(itemGroup.getResult());
                    if(itemGroupId != 0) {
                        // 获取掉落物组配置
                        DropItemGroupResource itemGroupResource = DropManager.getInstance().getDropItemGroupResource(itemGroupId);
                        ChooserItem dropItem = ChooaweManager.getInstance().chooseOne(itemGroupResource.getChooser());
                        if(dropItem != null) {
                            int dropItemId = Integer.valueOf(dropItem.getResult());
                            if(dropItemId > 0) {
                                result.add(DropManager.getInstance().getDropItemResource(dropItemId));
                            }
                        }
                    }
                });

            }
        }
        return result;
    }

    // 将物品掉落到地图上
    public static dropToGround0(Grid grid, int dropItemId, IBelongStrategy, WorldMapInstance mapInstance) {
        if(dropItemId == 0) {
            return;
        }
        Map<String, Object> args = new HashMap<>();
        args.put("x",grid.getX());
        args.put("y", grid.getY());
        args.put("dropItemId", dropItemId);
        args.put("belongStrategy", belongStrategy;
        args.put("strartClearTask", true);
        DropObject dropObject = (DropObject) DropObjectCreator.getCreator(ObjectType.DROPOBJECT).create(null, null, mapInstance,args);
        World.getInstance().spawn(dropObject);
    }

    // 掉落到地方的方法
    public static void dropToGround(int centerX, int centerY, List<DropItemResource> dropItemResources,
    IBelongStrategy belongStrategy, WorldMapInstance mapInstance) {
        // ...
    }

    // 掉落到背包
    public static List<DropItemReosource> dropToBag(BossDropInfo bossDropInfo, Player player, List<DropItemResource> rsult) {
        // ...
    }

    //  生成掉落记录
    public static BossDropRecord createDropRecord(int mapId, int objectId, long mapInstanceId, String name, int dropItemId) {
        // ...
    }

    // 获取掉落物
    public static List<DropItemResource> getDropItemAndAddRecord(Player player, List<Integer> groupIds, ) {
        List<DropItemResource> result = new ArrayList<>();
        if(groupIds.isEmpty()) {
            return result;
        }
        List<DropItemResource> rollGoods = rolGoods(player, groupIds,params);
        // 检测广播
        broadcastDropItems(plyaer, rollGoods,bossDropInfo);\
        // 检测掉落记录
        checkDropRecord(player, rollGoods, bossDropInfo);
        return rollGoods;
    }

    // 检测珍惜掉落广播
    public static void broadcastItems(Player player, List<DropItemReosurce> rollGoods, BossDropINfo bossDropInfo) {

    }

    // 检测掉落到背包的嗲罗记录是否需要广播到其他游戏服
    // 掉落到背包是在游戏服进行掉落,获取groupIndex的方式是在游戏服的门票信息里面去获取
    public static void checkDropRecord(Player palyer, List<DropItemReosurce> rollGoods, BossDropINfo bossDropInfo) {
        // ...
    }

}

门面类和掉落的触发

掉落外观类

@Component
@SocketClass
public class DropFacade {

    // ...

    @ReceiverAnno
    public void onKillBoss(KillBossEvent event) {
        onKillBoss(event.getPlayer(), event.getMapId(), event.getBossId(), event.getX(), event.getY());
    }

    private void onKillBoss(Player player, int mapId, int bossId, int x, int y) {
        // Vip大爆
        MapResource mapRsource = MapManager.self().getMapResourceStorage().get(mapId, true);
        if(mapResource.getType() == MapType.FIELD || mapResource.getType() == MapType.TRANSFER_FIELD) {
            dropService.vipDrop(player, bossId);
        }
        // 幸运大爆
        if(ModuleOpenManger.getInstance().isOpen(player, ModuleOpenType.LUCKY_DROP)) {
            dropService.luckyValueDrop(player, bossId, x, y);
        }
    }


    // 游戏服,玩家接受掉落事件,进行玩家条件检测以及次数判断
    @ReceiverAnno
    public void onPlayerKillBossDrop(PlayerKillBossDropEvent event) {
        dropSerive.createDropToGroundItems(event.getPlayer(), event);
    }

    // 游戏服,处理掉落物到场景事件
    @ReceiverAnno
    public void onDropToGround(DropToGroundEvent event) {
        dropService.doDropToGround(event.getPlayer().getFightPlayer(), event.getDropItemIds(),
        event.getBelongContext(), event.getBossDropInfo());
    }

    // 战斗服,处理掉落物掉到场景事件
    @ReceiverAnno
    public void onTransferDropToGround(onTransferDropToGroundEvent event) {
        dropService.doDropToGround(event.getPlayer().getFightPlayer(), event.getDropItemIds(),
        event.getBelongContext(), event.getBossDropInfo());
    }

    // 战斗服,统一处理飞背包掉落物事件
    @ReceiverAnno
    public void onDropToBag(PlayerDropToBagEvent event) {
        dropService.dropToBa(event.getPlayer(), event);
    }

}

掉落数据的持久化

玩家掉落数据

@Entity
@Cached
public class PlayerDropEntity extends AbstractEntity<Long> {

    @Id
    private long playerId;

    // ======= 击杀怪物记录 ========
    @Lob
    private String dailyKillMonsterTimesStr;
    // 与dropId关联的怪物的击杀次数<dropId, times>
    private transinet ConcurrentHashMap<Integer, Integer> dailyKillMonsterTimes;

    @Lob
    private String dailyKillMonsterLuckyNumStr;
    // 当日击杀幸运数<掉落ID, 幸运击杀数组>
    private transient ConcurrentHashMap<Integer, List<Integer>> dailyKillMonsterLuckyNums;

    // ========= 掉落组掉落记录 ==========

    // 当日掉落物品组次数 <dropGroupId, times>,key为DropGroupLimit的id
    @Lob
    private String dailyDropGroupTimesStr;
    private transient ConcurrentHashMap<Integer, Integer> dailyDropGroupTimes;

    // 永久掉落物品次数 <dropGroupId, times>,key为DropGroupLimit的id
    @Lob
    private String dropGroupTimesStr;
    private transient ConcurrentHashMap<Integer, Integer> dropGroupTimes;

    // ======== 幸运值相关 ============

    // 当前幸运值
    private int luckyValue;
    // 今日回收装备数量
    private transient Map<Intger, Integer> dailyRecycleEquipNum;
    @Lob
    private String dailyRecycleEquipNumData;
    // 今日幸运大爆奖励记录
    private transient List<LuckyDropRecord> luckyDropRecords;
    @Lob
    private String luckyDropRecordsData;
    // 今日获得幸运值
    private int dailyAddLuckyValue;
    // 历史获得幸运值
    private int totalAddLuckyValue;
    // 今日幸运大爆次数
    private int dailyLuckDropTimes;
    // 历史幸运大爆次数
    private int totalLuckyDropTimes;

    private transient Map<Integer, QuestLuckyDropData> questLuckyDropDataMap;
    @Lob
    private String questLuckyDropDataStr;

    // 每日生成的超级幸运掉落次数列表
    private transient List<Integer> dailySuperLuckyDropValueList;
    @Lob
    private String dailySuperLuckyDropValueDataStr;

    private transient Map<ObjectSubType, BossLuckyTimeData> bossLuckyTimesMap;
    @Lob
    private String bossLuckyTimesData;

    // 各种处理属性的方法 。。。


}