引言:最近在玩完美時空的誅仙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是幸運星)來選擇你的答案了。