引言:最近在玩完美时空的诛仙Online(不知道这里有没人有共同爱好的),这个游戏每晚七点会出现一个任务“新科试炼”。这个任务简单地说就是做选择题,范围小到柴米油盐,大到世界大千,所以多玩的YY上出现一个频道叫“诛仙答题频道”,这个频道会即时为玩家提供正确答案,所以当大家都上YY的时候,最终出来的成绩的高低并不取决于你的知识面,而是取决你家的网速及你的反应速度(答题越早,所获得的成绩越高)。我家的网速很好,可惜我的反应速度一般,所以从来没上过一次前十,因为每次听说YY上的答案后还要移动鼠标去点击相应的答案,这个过程平均需要0.5秒,这无疑是我成绩不高的根本所在。所以我想到了通过按下键盘上的某些按键实现将鼠标移动到指定位置并模拟鼠标键按下事件(以下简称模拟)。也许你会问:这还不简单,直接加个KeyListener不就完了?但是你想过没有,在模拟的时候,窗口的焦点是在游戏窗口,而不是Java程序的窗口(甚至连窗口都没有),所以我们需要一个监听所有进程的接口,这就是我接下要说的“Hook(钩子)”(了解外挂制作的人应该知道是什么东西,没看过?百度之)。不废话,进入正题:
首先,编写之前,我们要使用到一个第三方类库,它实现的功能就是让你直接调用API,而将对Window的调用交给它处理,下载地址是:http://feeling.sourceforge.net/
将包下载完后解压,并创建项目,结构如左图,lib下的三个dll文件要拷到window/system32下,下面就是编码了:首先我们定义一个抽象类来拦截键盘事件,如下:
import org.sf.feeling.swt.win32.extension.hook.data.HookData;
import org.sf.feeling.swt.win32.extension.hook.data.KeyboardHookData;
import org.sf.feeling.swt.win32.extension.hook.listener.HookEventListener;
public abstract class KeyboardHookEventListener implements HookEventListener{
public void acceptHookData(HookData arg0) {
KeyboardHookData khd = ((KeyboardHookData) arg0);
{
if(khd.getTransitionState()) //处理按下事件
{
doPress(khd.getWParam());
}
else
{
doReleased(khd.getWParam()); //处理释放事件
}
}
}
public abstract void doPress(int keyNum);
public abstract void doReleased(int keyNum);
}
接着再定义一个抽象类到拦截鼠标事件,如下:
import org.sf.feeling.swt.win32.extension.hook.data.HookData;
import org.sf.feeling.swt.win32.extension.hook.data.MouseHookData;
import org.sf.feeling.swt.win32.extension.hook.listener.HookEventListener;
public abstract class MouseHookEventListener implements HookEventListener{
public void acceptHookData(HookData hookData) {
int x=((MouseHookData) hookData).getPointX();
int y=((MouseHookData) hookData).getPointY();
switch(hookData.getWParam())
{
case 513:
doLeftPressed(x,y);
break;
case 514:
doLeftReleased(x,y);
break;
case 516:
doRightPressed(x,y);
break;
case 517:
doRightReleased(x,y);
break;
case 519:
doMiddlePressed(x,y);
break;
case 520:
doMiddleReleased(x,y);
break;
default:
}
}
protected abstract void doLeftPressed(int x,int y);
protected abstract void doLeftReleased(int x,int y);
protected abstract void doRightPressed(int x,int y);
protected abstract void doRightReleased(int x,int y);
protected abstract void doMiddlePressed(int x,int y);
protected abstract void doMiddleReleased(int x,int y);
}
至此,我们的项目底层架构已经完成,下面就是业务流程的控制问题了,在贴上我的代码之前,我觉得有必要先做一下诛仙答题跟项目的介绍(又要废话了,顺便帮老池免费作下广告,遇上我是他的福分,^-^)。
答题是这样的:首先,诛仙这个游戏是支持窗口化(且提供几个固定窗口大小供选择),而其中的答题窗口就是窗口中的窗口了(可拖动的)。7点,答题系统开启,每个玩家可选择进入答题窗口,等下一分钟才真正开始,这一分钟中,页面会显示出3个幸运星,但是没有题目........经过一番分析,可以确定用户要输入的有:当前使用的窗口大小及幸运星的位置(幸运星跟选项的位置不固定的,幸运星一确定,选项的位置也就知道了)。以下是关于这个业务的代码:
定义一个特定的鼠标事件响应,如下:
import java.awt.Dimension;
import java.util.LinkedList;
import java.util.List;
public class MyMouseHookEventListener extends MouseHookEventListener {
private Dimension zeroDimension;
private List<Dimension> dimensions=new LinkedList<Dimension>();
private boolean needFetchZeroDimension=false;
private String currentOffsetSeries="";
public void resetZeroDimension()
{
this.needFetchZeroDimension=true;
}
public void resetDimensions(String dimensionSeries)
{
this.dimensions.clear();
String[] dimStrs=dimensionSeries.split(",");
for(int i=0;dimStrs!=null&&i<dimStrs.length/2;i++)
{
int width=Integer.parseInt(dimStrs[i*2])+(int)zeroDimension.getWidth();
int height=Integer.parseInt(dimStrs[i*2+1])+(int)zeroDimension.getHeight();
dimensions.add(new Dimension(width,height));
}
}
public String getDimensionSeries()
{
String dimSeries="";
for(Dimension dim:this.dimensions)
{
dimSeries=dimSeries+","+(int)(dim.getWidth()-zeroDimension.getWidth())+","+(int)(dim.getHeight()-zeroDimension.getHeight());
}
if(dimSeries.length()>0)
{
dimSeries=dimSeries.substring(1);
}
return dimSeries;
}
@Override
protected void doLeftPressed(int x, int y) {}
@Override
protected void doLeftReleased(int x, int y) {}
@Override
protected void doMiddlePressed(int x, int y) {}
@Override
protected void doMiddleReleased(int x, int y) {}
@Override
protected void doRightPressed(int x, int y) {
if(this.needFetchZeroDimension)
{
this.zeroDimension=new Dimension(x,y);
resetDimensions(currentOffsetSeries);
this.needFetchZeroDimension=false;
System.out.println("幸运星位置已获取,关闭重置模式,\r\n现在你可以使用小键盘上的12345来实现鼠标事件模拟,如果你需要重新选择请按F11");
}
}
@Override
protected void doRightReleased(int x, int y) {}
public void setCurrentOffsetSeries(String currentOffsetSeries) {
this.currentOffsetSeries = currentOffsetSeries;
}
public List<Dimension> getDimensions() {
return dimensions;
}
}
再定义一个运行类:
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.InputEvent;
import org.sf.feeling.swt.win32.extension.hook.Hook;
public class ZhuXianSwifter {
public static final int NUM_1=97;
public static final int NUM_2=98;
public static final int NUM_3=99;
public static final int NUM_4=100;
public static final int NUM_5=101;
public static final int F_11=122;
public static final int F_12=123;
private static final String OFFSET_SERIES_640_480="-125,84,-125,107,-125,130,-125,152,44,0,20,0,0,0";
private static final String OFFSET_SERIES_800_600="-156,105,-156,134,-156,163,-156,190,55,0,25,0,0,0";
private static final String OFFSET_SERIES_1024_768="-200,138,-200,172,-200,211,-200,248,70,0,32,0,0,0";
/**
* 使用说明:
* 1、启动后先选择所使用的分辨率,目前只支持640*480,800*600,1024*768;
* 2、然后使用鼠标右键点击一下试炼答题窗口的第一个幸运星的中心点即可;
* 3、使用小键盘的1234选择答案,使用5点星星(第一个使用完会自动用第二个),
* 4、只支持命令行模式
* 5、F11为取坐标模式,按F11开始,再次按F11结束,并将零坐标跟之前的偏移坐标复制到系统剪贴板
* 6、按F12退出程序
* @throws AWTException
*/
public static void main(String[] args) throws AWTException {
/*注册鼠标Hook*/
final MyMouseHookEventListener mouseListener=new MyMouseHookEventListener();
Hook.MOUSE.addListener(mouseListener);
Hook.MOUSE.install();
/*系统剪贴板*/
final Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
final Robot robot=new Robot();
/*键盘监听器*/
final KeyboardHookEventListener keyboardListener=new KeyboardHookEventListener(){
private boolean haveChooseMode=false;
private int count=0;
@Override
public void doPress(int keyNum) {
String mode="";
if(keyNum==F_12)
{
if(!mouseListener.getDimensionSeries().equals(""))
{
System.out.println("内容已经复制到系统剪贴板");
Transferable text = new StringSelection(mouseListener.getDimensionSeries());
systemClipboard.setContents(text,null);
}
System.out.println("------程序退出------");
System.exit(0);
}
else if(keyNum==F_11)
{
haveChooseMode=false;
count=0;
System.out.println("启动重置模式");
printChooseMode();
}
else
{
if(haveChooseMode==false)
{
switch(keyNum)
{
case NUM_1:
mode="640*480";
mouseListener.setCurrentOffsetSeries(OFFSET_SERIES_640_480);
break;
case NUM_2:
mode="800*600";
mouseListener.setCurrentOffsetSeries(OFFSET_SERIES_800_600);
break;
case NUM_3:
mode="1024*768";
mouseListener.setCurrentOffsetSeries(OFFSET_SERIES_1024_768);
break;
default:
System.out.println("请重新选择:");
printChooseMode();
return;
}
System.out.println("您选择了"+mode+"分辨率模式");
haveChooseMode=true;
mouseListener.resetZeroDimension();
printFetchZeroCoordinate();
}
else
{
switch (keyNum) {
case NUM_1:
robot.mouseMove((int)mouseListener.getDimensions().get(0).getWidth(),(int)mouseListener.getDimensions().get(0).getHeight());
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
break;
case NUM_2:
robot.mouseMove((int)mouseListener.getDimensions().get(1).getWidth(),(int)mouseListener.getDimensions().get(1).getHeight());
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
break;
case NUM_3:
robot.mouseMove((int)mouseListener.getDimensions().get(2).getWidth(),(int)mouseListener.getDimensions().get(2).getHeight());
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
break;
case NUM_4:
robot.mouseMove((int)mouseListener.getDimensions().get(3).getWidth(),(int)mouseListener.getDimensions().get(3).getHeight());
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
break;
case NUM_5:
robot.mouseMove((int)mouseListener.getDimensions().get(4+count).getWidth(),(int)mouseListener.getDimensions().get(4+count).getHeight());
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
count++;
if(count==3)
{
count=0;
}
break;
default:
break;
}
}
}
}
@Override
public void doReleased(int keyNum) {}
};
Hook.KEYBOARD.addListener(keyboardListener);
Hook.KEYBOARD.install(); // 註冊事件
printChooseMode();
}
private static void printChooseMode()
{
System.out.println("请选择窗口大小:");
System.out.println("NUM1:640*480");
System.out.println("NUM2:800*600");
System.out.println("NUM3:1024*768");
}
private static void printFetchZeroCoordinate()
{
System.out.println("请在第一个幸运星的中心上点击鼠标右键");
}
}
以上就是本项目的所以代码,运行时要先按小键盘的1/2/3选择使用的窗口大小,然后在第一个幸运星的中心点击下右键鼠标就可以了,之后你就可以用小键盘的1/2/3/4/5(5是幸运星)来选择你的答案了。