掉落系统学习
概况
掉落系统是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;
// 各种处理属性的方法 。。。
}