手游服务端框架之GM金手指的设计
玩过单机游戏的朋友,应该对金山游侠这个软件很熟悉把。当初我经常嫌刷怪升级非常辛苦,很多时候都是直接用金山游侠来修改游戏的经验或者等级内存,直接把角色调得很牛逼。
游戏开发也非常需要这些可以修改玩家数据的金手指。在游戏里,它有个更加专业的名称,叫GM(GameMaster)命令。
有了GM命令,我们就看好很方便让角色快速升级或者直接获取极品道具。当然,只要是数据,都可以有对应的GM命令,只要项目有需要。
下面,我们就来看一下GM系统的一种实现方式。
假设我们需要有一种指令,比如输入playerLv加一个数字表示要达到的等级,只要服务端受到这条指令,就直接将当前角色升到目标等级。不同的GM命令,指令的前缀和参数都是不同的,也就是说,每个指令都有自己固定的格式。在Java里,我们可以用正则表达式来定义这样的格式。
1.首先来看一些我们的gm命令抽象类,该类有几个作用,例如,定制具体的gm命令参数的格式,解析参数的方法,以及执行逻辑的方法。
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.kingston.game.database.user.player.Player;
import com.kingston.game.gm.message.ResGmResultMessage;
/**
* 抽象gm命令
* @author kingston
*/
public abstract class AbstractGmCommand {
/**
* 正则表达式模式
* @return
*/
public abstract String getPattern();
/**
* 帮助文档
* @return
*/
public abstract String help();
/**
* 是否匹配
* @param pattern
* @param content
* @return
*/
public boolean isMatch(Pattern pattern, Matcher matcher, String content) {
return matcher.matches();
}
/**
* 返回正则表达式解析的一系列参数
* @param matcher
* @param message
* @return
*/
public List<String> params(Matcher matcher, String message) {
List<String> params = new ArrayList<>();
for (int i=1; i<matcher.groupCount()+1; i++) {
params.add(matcher.group(i));
}
return params;
}
/**
* 执行逻辑
* @param player
* @param params
* @return
*/
public abstract ResGmResultMessage execute(Player player, List<String> params);
}
2.定义一个gm命令的管理工具,负责缓存所有的gm命令。当收到玩家的gm指令后,如果判断当前玩家的IP地址或者角色有执行的权限,那么再通过枚举的方式,找到能够处理请求的具体gm命令。从而执行对应的逻辑。
public class GmManager {
private static volatile GmManager instance;
private GmManager() {}
/** 缓存gm指令的模式与对应的逻辑处理者 */
private Map<Pattern, AbstractGmCommand> commands = new HashMap<>();
private final String SCAN_PATH = "com.kingston.game.gm.command";
public static GmManager getInstance() {
if (instance == null) {
synchronized(GmManager.class) {
if (instance == null) {
instance = new GmManager();
instance.init();
}
}
}
return instance;
}
private void init() {
Set<Class<?>> clazzs = ClassScanner.getClasses(SCAN_PATH, new ClassFilter() {
@Override
public boolean accept(Class<?> clazz) {
return AbstractGmCommand.class.isAssignableFrom(clazz)
&& !Modifier.isAbstract(clazz.getModifiers());
}
});
for (Class<?> clazz:clazzs) {
try{
AbstractGmCommand command = (AbstractGmCommand) clazz.newInstance();
String regex = command.getPattern();
commands.put(Pattern.compile(regex), command);
}catch(Exception e) {
e.printStackTrace();
}
}
}
/**
* 处理gm入口
* @param playerId
* @param content
* @return
*/
public void receiveCommand(long playerId, String content) {
Player player = PlayerManager.getInstance().get(playerId);
//判断权限
if (!hasExecPower(player)) {
return;
}
for (Map.Entry<Pattern, AbstractGmCommand> entry:commands.entrySet()) {
Pattern pattern = entry.getKey();
AbstractGmCommand command = entry.getValue();
Matcher matcher = pattern.matcher(content);
if (command.isMatch(pattern, matcher, content)) {
List<String> params = command.params(matcher, content);
ResGmResultMessage result = command.execute(player, params);
MessagePusher.pushMessage(playerId, result);
return;
}
}
ResGmResultMessage failedMessage = ResGmResultMessage.buildFailResult("找不到对应的gm命令");
MessagePusher.pushMessage(playerId, failedMessage);
}
/**
* 是否有执行权限
* @param player
* @return
*/
private boolean hasExecPower(Player player) {
//这里根据具体业务进行拦截
return true;
}
public static void main(String[] args) {
Pattern pattern = Pattern.compile("^reloadConfig\\s+([a-zA-Z_]+)");
String expr = "reloadConfig CofingPlayer_Level";
Matcher matcher = pattern.matcher(expr);
if (matcher.matches()) {
List<String> params = new ArrayList<>();
for (int i=1; i<matcher.groupCount()+1; i++) {
params.add(matcher.group(i));
}
System.err.println(params);
}
}
}
3.举个例子,上面的修改玩家等级的gm命令。格式为:playerLv [level],对应的正则表达式模式为
^playerLv\\s+(\\d+)
该命令的完整代码
/**
* 修改玩家等级的gm
* @author kingston
*/
public class GmPlayerLevelCommand extends AbstractGmCommand {
@Override
public String getPattern() {
return "^playerLv\\s+(\\d+)";
}
@Override
public String help() {
return "修改玩家等级(playerLv [level])";
}
@Override
public ResGmResultMessage execute(Player player, List<String> params) {
int newLevel = Integer.parseInt(params.get(0));
ConfigPlayerLevel configLevel = ConfigDatasPool.getInstance()
.configPlayerLevelContainer.getConfigBy(newLevel);
if (configLevel == null) {
return ResGmResultMessage.buildFailResult("目标等级参数无效");
}
player.setLevel(newLevel);
player.setUpdate();
DbService.getInstance().add2Queue(player);
return ResGmResultMessage.buildSuccResult("修改玩家等级成功");
}
}
4.还有一个指令也很常见。比如游戏运行过程中,策划童鞋发现数值填错了(他们经常这么干)。我们不想重启服务但又希望内存里的策划配置能刷新。所以,我们需要有热更新配置的gm命令。回顾一下我们前一篇文章,关于配置数据库的设计。里面将策划的所有配置统一由ConfigDatasPool管理。如此,我们很容易通过反射的方式,动态实例化新的配置容器,读取新的配置并替换原有的container。
重载配置的gm指令格式为
^reloadConfig [tableName]
对应的正则表达式为
^reloadConfig\\s+([a-zA-Z_]+)
完整代码如下:
/**
* 修改配置表的gm
* @author kingston
*/
public class GmReloadConfigCommand extends AbstractGmCommand {
@Override
public String getPattern() {
return "^reloadConfig\\s+([a-zA-Z_]+)";
}
@Override
public String help() {
return "修改配置表(^reloadConfig [tableName])";
}
@Override
public ResGmResultMessage execute(Player player, List<String> params) {
String tableName = params.get(0);
String containerName = tableName + "Container";
try {
Field field = ConfigDatasPool.class.getDeclaredField(containerName);
Class<?> type = field.getType();
Reloadable newContainer = (Reloadable) type.newInstance();
newContainer.reload();
field.set(ConfigDatasPool.getInstance(), newContainer);
return ResGmResultMessage.buildSuccResult("重载["+tableName+"]表成功");
} catch (Exception e) {
e.printStackTrace();
}
return ResGmResultMessage.buildFailResult("找不到目标配置表");
}
}
5.GM命令也属于玩家请求,那么也是需要对应的Controller,Message,RequestMapper。都是很简单的申明,这里就不贴代码了。
手游服务端框架之GM金手指的设计的更多相关文章
- Pomelo:网易开源基于 Node.js 的游戏服务端框架
Pomelo:网易开源基于 Node.js 的游戏服务端框架 https://github.com/NetEase/pomelo/wiki/Home-in-Chinese
- XGoServer 一个基础性、模块完整且安全可靠的服务端框架
作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...
- 用“MEAN”技术栈开发web应用(二)express搭建服务端框架
上一篇我们讲了如何使用angular搭建起项目的前端框架,前端抽象出一个service层来向后端发送请求,后端则返回相应的json数据.本篇我们来介绍一下,如何在nodejs环境下利用express来 ...
- 手游client思考框架
手游新公司新项目client我不太同意框架.虽然我也终于让步,当他居然问老板,使这个幼稚的行为而悔恨. 然而,就在最近我写了一些代码视图,我更坚定了自己的想法和思想.和思路不一定适合其它人,所以我并不 ...
- 基于 xorm 的服务端框架 XGoServer
作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...
- Mina学习+手写服务端+通过telnet连接服务端
1. 2. 3. 4.MinaServer.java package com.mina; import java.io.IOException;import java.net.InetSocketAd ...
- 我要多开梦幻手游PC端(梦幻手游PC端多开的简单分析及实现办法)(二)
上一篇,多开方法,适用于一年前的版本 http://www.cnblogs.com/suanguade/p/5646776.html 前言: 一转眼一年过去了,日子越来越无聊了,于是,准备再玩一玩梦幻 ...
- 手游服务端框架之使用Guava构建缓存系统
缓存的作用与应用场景 缓存,在项目中的应用非常之广泛.诸如这样的场景,某些对象计算或者获取的代码比较昂贵,并且在程序里你不止一次要用到这些对象,那么,你就应该使用缓存. 缓存跟java的Coucurr ...
- 用.Net打造一个移动客户端(Android/IOS)的服务端框架NHM(四)——Android端Http访问类(转)
本章目的 在上一章中,我们利用Hibernate Tools完成了Android Model层的建立,依赖Hibernate Tools的强大功能,自动生成了Model层.在本章,我们将继续我们的项目 ...
随机推荐
- 5309 《Java程序设计》第6周学习总结
教材学习内容总结 输入与输出 InputStream与OutputStream 从应用程序角度来看,如果要将数据从来源取出,可以使用输入串流:如果要将数据写入目的地,可以使用输出串流.在Java中,输 ...
- 2017杭电ACM集训队单人排位赛 - 6
2017杭电ACM集训队单人排位赛 - 6 排名 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 59 1 X X 1 1 X X 0 1 ...
- yield生成器函数
生成器有主要有四种方法: next() 执行函数,直到遇到下一个yield为止,并返回值 send(value) 为生成器发送一个数值,next()方法就相当于send(None) close() 终 ...
- cogs 896. 圈奶牛
★★☆ 输入文件:fc.in 输出文件:fc.out 简单对比 时间限制:1 s 内存限制:128 MB 描述 农夫约翰想要建造一个围栏用来围住他的奶牛,可是他资金匮乏.他建造的围栏必 ...
- Feign PathVariable annotation was empty on param 0.
使用Feign的时候,如果参数中带有 @PathVariable形式的参数,则要用value=""标明对应的参数,否则会抛出IllegalStateException异常 如 @P ...
- hdu1009 - 贪心
2017-07-14 18:18:31 writer:pprp 介绍:hdu1009 题目介绍,详见hdu1009 代码如下 #include <iostream> #include &l ...
- PAT1070. Mooncake (25)
#include #include #include <stdio.h> #include <math.h> using namespace std; struct SS{ d ...
- DLL注入之SHELLCODE数据转换
#include "stdafx.h" #include <stdio.h> #include <string.h> #include <conio. ...
- js从一个select选择数据添加到另一个select(包括移除)
一.实现效果 二.要求 1.选中左侧的菜单,点击“>>”,该菜单(1项或多项选中的)将添加到右侧菜单 2.选中右侧菜单,点击“<<”,则移除选中的菜单 3.点击“>> ...
- ZeroMq实现跨线程通信
ZeroMq实现跨线程通信 之前在技术崇拜的技术经理指导下阅读了ZeroMq的基础代码,现在就将阅读的心得与成果记录一下,并重新模仿实现了一下经理的异步队列. 1.对外接口 //主要接口(1)void ...