商店系统学习

商店系统几乎是RPG游戏里的标配,玩家可以通过NPC、游戏界面用各种代币购买各种物品。商店有各种各样的表现形式,可以是指定某个NPC,也可以游戏界面直达。一个通用的商店可以减少大量的重复代码。

业务抽象

商城分类

public enum ShopType {

    // 普通商城
    COMMONSHOP {
        @Override
        public boolean canBuy(Player player) {
            return true;
        }
    }
    // 公会商店
    GANGSHOP {
        @Override
        public boolean canBuy(Player player) {
            return player.hasGang();
        }
    }

    public abstract boolean canBuy(Player player);
}

配置

商店配置其实是对商店中商品的配置,因为商店本身就是有商品组成的。而购买商品的过程,其实与合成、升级物品一样,都是”代价-回报”的过程,有消耗ConsumeResource有奖励RewardResource`,然后再辅以进行这个过程先决条件(环境的、玩家的)。

@Resource
public class ShopResource implements IAferResouce, IResouceCheck {
    @ResourceId
    private int id;
    // 购买条件
    private ConditionsOrAndResource conditionResource;
    private ConditionsOrAnd<Player> buyConditions;

    // 展示条件
    private ConditionsOrAndResource showCondition;
    private ConditionsOrAnd<Player> showConditions;

    // 购买消耗
    private List<ConsumeResource> consumeResources;
    private stepConsumee<Player> consumes;

    // 购买奖励
    private List<RewardResource> rewardResource;
    private Rewaerd reward;

    // 购买消耗
    private List<RewardResource> rewardResources;
    private Reward reward;

    // 快速购买
    private List<RewardResource> rewardResources;
    private Reward reward;

    // 商城页签
    private ShopTab tab;

    // 单次可购买最大数量
    private int maxCount;

    // 限购刷新策略
    private TimeResource flushResource;
    private TimeStrategy flushStrategy;

    // 限制,<涉及限制的类型,限制的数目>
    private Map<LimiteBuyType, Integer>  limitContents;

    // 关联商品id集合,有些商品的限购是与同类商品相关的
    private  List<Integer> attachIds;

    // 赠送的东西
    private Reward givingResouce;

    // 启服是否刷新限购次数
    private boolean openServerRefreshLimitBuy;

    // 省略......

    // 是否是全服或公会限购
    public boolean isServerOrGangLimit() {
        return limitContents != null &&
        limitContents.containsKey(LimitBuyType.GangLimitBuy); ||
        limitContents.containsKey(LimitBuy);
    }

    // 是否跨服限购
    public boolean isTransferLimit() {
        return limitContents.containsKey(LimitBuyType.TransferLimitBuy);
    }

    // ......
}

商城限购类型

@Desc("商城限购类型")
public enum LimitBuyType {
    // 玩家限购
    PlayerLimitBuy {
        @Override
        public boolean isNotLimited(Player player, int shopId, int limitCount, int mount) {
            ShopRecordEntity entity = ShopManager.getInstance().getShopRecordEntity(shopId);
            Map<Long, Integer> record = entity.getLimitBuyRecord();
            int count = record.getOrDefault(player.getPlayerId(), 0);
            ShopResource resource = ShopManger.getInstance().getShopRecordEntity(shopId);
            if(resource.getArracheIds() != null) {
                for(int attachId : resource.getAttachIds()) {
                    if(attachId == shopId) {
                        continue;
                    }
                    count += ShopManager.getInstance().getPlayerBuyNum(plyaer, attachId);
                }
                count += mount;
                return count <= limitCount;
            }
        }

        @Override
        public void record(Plyaer player, int shopId, int amount) {
            ShopRecordEntity entity = ShopManager.getInstance().getShopRecordEntity(shopId);
            Map<Long, Integer> limitBuyRecord = entity.getLimitBuyRecord();
            limitBuyRecord.put(player.getPlayerId(), limitBuyReCord.getOrDefault(player.getPlayerId(),0) + amount);
            ShopManager.getInstance().update(entity);
        }
    },

    // 全服限购
    @Desc("全服限购")
    ServerLimitBuy {
        @Override
        public boolean isNotLimited(Player player, int shopId, int limitCount, int mount) {
            ShopRecordEntity entity = ShopManager.getInstance().getShopRecordEntity(shopId);
            int buyCount = mount;
            for(Collection<Integer> itemBuyRecords : entity.getLimitBuyRecord()) {
                for(int record : itemBuyRecords) {
                    buyCount += record;
                }
            }
             return buyCount <= limitCount;
        }
        @Override
        public void record(Player player, int shopId, int amount) {
            // nothing
        }
    }

    // 公会限购
    @Desc("公会限购")
    GangLimitBuy{

    },

    // 跨服分组限购
    @Desc("跨服分组限购")
    TransferLimitBuy {

    };

    public abstract boolean isNotLimited(Player player, int shopId, int limitCOunt, int mount);
    public abstract void record(Player player, int shopId, int amount);
}

购买信息的持久化

玩家的购买记录需要持久化,特别是对需要进行限购的商品。

玩家商城购买记录

@Entity
@Cached(type = CacheType.MANUAL)
@InitialConfig(type = InitialType.ALL)
@IPublicEntity
public class ShopRecordEntity  extends AbstractEntity<Integer> {
    // 商品id
    @Id
    private int id;
    // 玩家周期购买记录<玩家id,购买数量>
    @Lob
    private String gangContent;
    // 公会周期否买记录<公会id,购买数量>
     private transient Map<Long, Integer> limitBuyRecord = new ConcurrentHashMap<>(0);
     private transient Map<Long, Integer> gangLimitBuyRecord = new ConcurrentHashMap<>(0);

    @Lob
    private String gangContext;
    // 商品下次刷新时间
    private long flushTime;

    // ......

}

商店的业务逻辑

  • ShopManager中有初始化商城,定时刷新商品信息集合、重置商品限购信息等方法,涵盖了商城的公共方法。

  • ShopService中定义了商品的购买、发送商品信息的方法。

总结

  • 商店商品应该被抽象到很通用。
  • 由于商店物品可能被限购,处于跨服环境时更为复杂,所以限购逻辑的设计很重要。