|
1 /* |
|
2 * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game |
|
3 * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com> |
|
4 * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com> |
|
5 * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com> |
|
6 * |
|
7 * This program is free software; you can redistribute it and/or |
|
8 * modify it under the terms of the GNU General Public License |
|
9 * as published by the Free Software Foundation; either version 2 |
|
10 * of the License, or (at your option) any later version. |
|
11 * |
|
12 * This program is distributed in the hope that it will be useful, |
|
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
15 * GNU General Public License for more details. |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License |
|
18 * along with this program; if not, write to the Free Software |
|
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
20 */ |
|
21 |
1 package org.hedgewars.hedgeroid; |
22 package org.hedgewars.hedgeroid; |
|
23 |
|
24 import java.io.IOException; |
|
25 import java.io.UnsupportedEncodingException; |
|
26 import java.net.ConnectException; |
2 |
27 |
3 import javax.microedition.khronos.egl.EGL10; |
28 import javax.microedition.khronos.egl.EGL10; |
4 import javax.microedition.khronos.egl.EGLConfig; |
29 import javax.microedition.khronos.egl.EGLConfig; |
5 import javax.microedition.khronos.egl.EGLContext; |
30 import javax.microedition.khronos.egl.EGLContext; |
6 import javax.microedition.khronos.egl.EGLDisplay; |
31 import javax.microedition.khronos.egl.EGLDisplay; |
7 import javax.microedition.khronos.egl.EGLSurface; |
32 import javax.microedition.khronos.egl.EGLSurface; |
8 |
33 |
9 import org.hedgewars.hedgeroid.EngineProtocol.EngineProtocolNetwork; |
34 import org.hedgewars.hedgeroid.Datastructures.GameConfig; |
10 import org.hedgewars.hedgeroid.EngineProtocol.GameConfig; |
|
11 import org.hedgewars.hedgeroid.EngineProtocol.PascalExports; |
35 import org.hedgewars.hedgeroid.EngineProtocol.PascalExports; |
|
36 import org.hedgewars.hedgeroid.netplay.Netplay; |
|
37 import org.hedgewars.hedgeroid.util.FileUtils; |
12 |
38 |
13 import android.app.Activity; |
39 import android.app.Activity; |
14 import android.content.Context; |
40 import android.content.Context; |
15 import android.graphics.Canvas; |
41 import android.graphics.Canvas; |
16 import android.graphics.PixelFormat; |
42 import android.graphics.PixelFormat; |
20 import android.hardware.SensorManager; |
46 import android.hardware.SensorManager; |
21 import android.media.AudioFormat; |
47 import android.media.AudioFormat; |
22 import android.media.AudioManager; |
48 import android.media.AudioManager; |
23 import android.media.AudioTrack; |
49 import android.media.AudioTrack; |
24 import android.os.Bundle; |
50 import android.os.Bundle; |
25 import android.os.Handler; |
51 import android.util.Base64; |
26 import android.os.Message; |
|
27 import android.util.DisplayMetrics; |
52 import android.util.DisplayMetrics; |
28 import android.util.Log; |
53 import android.util.Log; |
29 import android.view.KeyEvent; |
54 import android.view.KeyEvent; |
30 import android.view.MotionEvent; |
55 import android.view.MotionEvent; |
31 import android.view.SurfaceHolder; |
56 import android.view.SurfaceHolder; |
35 |
60 |
36 /** |
61 /** |
37 SDL Activity |
62 SDL Activity |
38 */ |
63 */ |
39 public class SDLActivity extends Activity { |
64 public class SDLActivity extends Activity { |
40 |
65 /** |
|
66 * Set startConfig to the desired config when starting this activity. This avoids having to parcel all |
|
67 * the config objects into the Intent. Not particularly elegant, but it's actually a recommended |
|
68 * way to do this (http://developer.android.com/guide/faq/framework.html#3) |
|
69 */ |
|
70 public static volatile GameConfig startConfig; |
|
71 public static volatile boolean startNetgame; |
|
72 |
41 // Main components |
73 // Main components |
42 public static SDLActivity mSingleton; |
74 public static SDLActivity mSingleton; |
43 private static SDLSurface mSurface; |
75 private static SDLSurface mSurface; |
44 |
|
45 // This is what SDL runs in. It invokes SDL_main(), eventually |
|
46 private static Thread mSDLThread; |
76 private static Thread mSDLThread; |
47 |
77 |
48 // Audio |
78 // Audio |
49 private static Thread mAudioThread; |
79 private static Thread mAudioThread; |
50 private static AudioTrack mAudioTrack; |
80 private static AudioTrack mAudioTrack; |
57 private static int mGLMajor, mGLMinor; |
87 private static int mGLMajor, mGLMinor; |
58 |
88 |
59 // Load the .so |
89 // Load the .so |
60 static { |
90 static { |
61 System.loadLibrary("SDL"); |
91 System.loadLibrary("SDL"); |
62 //System.loadLibrary("SDL_image"); |
|
63 //System.loadLibrary("SDL_mixer"); |
|
64 //System.loadLibrary("SDL_ttf"); |
|
65 System.loadLibrary("main"); |
92 System.loadLibrary("main"); |
66 } |
93 } |
67 |
94 |
68 // Setup |
95 // Setup |
69 protected void onCreate(Bundle savedInstanceState) { |
96 protected void onCreate(Bundle savedInstanceState) { |
70 //Log.v("SDL", "onCreate()"); |
|
71 super.onCreate(savedInstanceState); |
97 super.onCreate(savedInstanceState); |
72 |
98 |
73 // So we can call stuff from static callbacks |
99 // So we can call stuff from static callbacks |
74 mSingleton = this; |
100 mSingleton = this; |
75 |
101 |
76 // Set up the surface |
102 // Set up the surface |
77 GameConfig config = getIntent().getParcelableExtra("config"); |
103 mSurface = new SDLSurface(getApplication(), startConfig, startNetgame); |
78 |
104 startConfig = null; |
79 mSurface = new SDLSurface(getApplication(), config); |
|
80 setContentView(mSurface); |
105 setContentView(mSurface); |
81 SurfaceHolder holder = mSurface.getHolder(); |
|
82 } |
106 } |
83 |
107 |
84 // Events |
108 // Events |
85 protected void onPause() { |
109 protected void onPause() { |
86 Log.v("SDL", "onPause()"); |
110 Log.v("SDL", "onPause()"); |
104 protected void onDestroy() { |
128 protected void onDestroy() { |
105 super.onDestroy(); |
129 super.onDestroy(); |
106 Log.v("SDL", "onDestroy()"); |
130 Log.v("SDL", "onDestroy()"); |
107 // Send a quit message to the application |
131 // Send a quit message to the application |
108 SDLActivity.nativeQuit(); |
132 SDLActivity.nativeQuit(); |
109 |
|
110 // Now wait for the SDL thread to quit |
133 // Now wait for the SDL thread to quit |
111 if (mSDLThread != null) { |
134 if (mSDLThread != null) { |
112 try { |
135 try { |
113 mSDLThread.join(); |
136 mSDLThread.join(); |
114 } catch(Exception e) { |
137 } catch(Exception e) { |
115 Log.v("SDL", "Problem stopping thread: " + e); |
138 Log.w("SDL", "Problem stopping thread: " + e); |
116 } |
139 } |
117 mSDLThread = null; |
140 mSDLThread = null; |
118 |
141 } |
119 //Log.v("SDL", "Finished waiting for SDL thread"); |
142 mSingleton = null; |
120 } |
143 } |
121 } |
144 |
122 |
145 public static void synchronizedNativeInit(String...args) { |
123 // Messages from the SDLMain thread |
146 synchronized(PascalExports.engineMutex) { |
124 static int COMMAND_CHANGE_TITLE = 1; |
147 nativeInit(args); |
125 |
148 } |
126 // Handler for the messages |
149 } |
127 Handler commandHandler = new Handler() { |
150 |
128 public void handleMessage(Message msg) { |
|
129 if (msg.arg1 == COMMAND_CHANGE_TITLE) { |
|
130 setTitle((String)msg.obj); |
|
131 } |
|
132 } |
|
133 }; |
|
134 |
|
135 // Send a message from the SDLMain thread |
|
136 void sendCommand(int command, Object data) { |
|
137 Message msg = commandHandler.obtainMessage(); |
|
138 msg.arg1 = command; |
|
139 msg.obj = data; |
|
140 commandHandler.sendMessage(msg); |
|
141 } |
|
142 |
|
143 // C functions we call |
151 // C functions we call |
144 public static native void nativeInit(String...args); |
152 private static native void nativeInit(String...args); |
145 public static native void nativeQuit(); |
153 public static native void nativeQuit(); |
146 public static native void nativePause(); |
154 public static native void nativePause(); |
147 public static native void nativeResume(); |
155 public static native void nativeResume(); |
148 public static native void onNativeResize(int x, int y, int format); |
156 public static native void onNativeResize(int x, int y, int format); |
149 public static native void onNativeKeyDown(int keycode); |
157 public static native void onNativeKeyDown(int keycode); |
163 |
171 |
164 public static void flipBuffers() { |
172 public static void flipBuffers() { |
165 flipEGL(); |
173 flipEGL(); |
166 } |
174 } |
167 |
175 |
168 public static void setActivityTitle(String title) { |
176 public static void setActivityTitle(final String title) { |
169 // Called from SDLMain() thread and can't directly affect the view |
177 // Called from SDLMain() thread and can't directly affect the view |
170 mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); |
178 mSingleton.runOnUiThread(new Runnable() { |
|
179 public void run() { |
|
180 mSingleton.setTitle(title); |
|
181 } |
|
182 }); |
171 } |
183 } |
172 |
184 |
173 public static Context getContext() { |
185 public static Context getContext() { |
174 return mSingleton; |
186 return mSingleton; |
175 } |
187 } |
176 |
188 |
177 public static void startApp(int width, int height, GameConfig config) { |
189 public static void startApp(final int width, final int height, GameConfig config, boolean netgame) { |
178 // Start up the C app thread |
190 // Start up the C app thread |
179 if (mSDLThread == null) { |
191 if (mSDLThread == null) { |
180 mSDLThread = new Thread(new SDLMain(width, height, config), "SDLThread"); |
192 mSDLThread = new Thread(new SDLMain(width, height, config, netgame)); |
181 mSDLThread.start(); |
193 mSDLThread.start(); |
182 } |
194 } else { |
183 else { |
|
184 SDLActivity.nativeResume(); |
195 SDLActivity.nativeResume(); |
185 } |
196 } |
186 } |
197 } |
187 |
198 |
188 // EGL functions |
199 // EGL functions |
189 public static boolean initEGL(int majorVersion, int minorVersion) { |
200 public static boolean initEGL(int majorVersion, int minorVersion) { |
190 if (SDLActivity.mEGLDisplay == null) { |
201 if (SDLActivity.mEGLDisplay == null) { |
191 //Log.v("SDL", "Starting up OpenGL ES " + majorVersion + "." + minorVersion); |
|
192 |
|
193 try { |
202 try { |
194 EGL10 egl = (EGL10)EGLContext.getEGL(); |
203 EGL10 egl = (EGL10)EGLContext.getEGL(); |
195 |
204 |
196 EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); |
205 EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); |
197 |
206 |
217 Log.e("SDL", "No EGL config available"); |
225 Log.e("SDL", "No EGL config available"); |
218 return false; |
226 return false; |
219 } |
227 } |
220 EGLConfig config = configs[0]; |
228 EGLConfig config = configs[0]; |
221 |
229 |
222 /*int EGL_CONTEXT_CLIENT_VERSION=0x3098; |
|
223 int contextAttrs[] = new int[] { EGL_CONTEXT_CLIENT_VERSION, majorVersion, EGL10.EGL_NONE }; |
|
224 EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, contextAttrs); |
|
225 |
|
226 if (ctx == EGL10.EGL_NO_CONTEXT) { |
|
227 Log.e("SDL", "Couldn't create context"); |
|
228 return false; |
|
229 } |
|
230 SDLActivity.mEGLContext = ctx;*/ |
|
231 SDLActivity.mEGLDisplay = dpy; |
230 SDLActivity.mEGLDisplay = dpy; |
232 SDLActivity.mEGLConfig = config; |
231 SDLActivity.mEGLConfig = config; |
233 SDLActivity.mGLMajor = majorVersion; |
232 SDLActivity.mGLMajor = majorVersion; |
234 SDLActivity.mGLMinor = minorVersion; |
233 SDLActivity.mGLMinor = minorVersion; |
235 |
234 |
411 |
410 |
412 /** |
411 /** |
413 Simple nativeInit() runnable |
412 Simple nativeInit() runnable |
414 */ |
413 */ |
415 class SDLMain implements Runnable { |
414 class SDLMain implements Runnable { |
416 |
415 public static final String TAG = "SDLMain"; |
417 private int surfaceWidth, surfaceHeight; |
416 |
418 private GameConfig config; |
417 public static final int RQ_LOWRES = 0x00000001; // use half land array |
419 |
418 public static final int RQ_BLURRY_LAND = 0x00000002; // downscaled terrain |
420 public SDLMain(int width, int height, GameConfig _config) { |
419 public static final int RQ_NO_BACKGROUND = 0x00000004; // don't draw background |
421 config = _config; |
420 public static final int RQ_SIMPLE_ROPE = 0x00000008; // avoid drawing rope |
|
421 public static final int RQ_2D_WATER = 0x00000010; // disabe 3D water effect |
|
422 public static final int RQ_SIMPLE_EXPLOSIONS = 0x00000020; // no fancy explosion effects |
|
423 public static final int RQ_NO_FLAKES = 0x00000040; // no flakes |
|
424 public static final int RQ_NO_MENU_ANIM = 0x00000080; // ammomenu appears with no animation |
|
425 public static final int RQ_NO_DROPLETS = 0x00000100; // no droplets |
|
426 public static final int RQ_NO_CLAMPING = 0x00000200; // don't clamp textures |
|
427 public static final int RQ_NO_TOOLTIPS = 0x00000400; // tooltips are not drawn |
|
428 public static final int RQ_NO_VSYNC = 0x00000800; // don't sync on vblank |
|
429 |
|
430 private final int surfaceWidth, surfaceHeight; |
|
431 private final String playerName; |
|
432 private final GameConfig config; |
|
433 private final boolean netgame; |
|
434 |
|
435 public SDLMain(int width, int height, GameConfig config, boolean netgame) { |
422 surfaceWidth = width; |
436 surfaceWidth = width; |
423 surfaceHeight = height; |
437 surfaceHeight = height; |
|
438 if(netgame) { |
|
439 playerName = Netplay.getAppInstance(SDLActivity.getContext().getApplicationContext()).getPlayerName(); |
|
440 } else { |
|
441 playerName = "Player"; |
|
442 } |
|
443 this.config = config; |
|
444 this.netgame = netgame; |
424 } |
445 } |
425 |
446 |
426 public void run() { |
447 public void run() { |
427 //Set up the IPC socket server to communicate with the engine |
448 //Set up the IPC socket server to communicate with the engine |
428 EngineProtocolNetwork ipc = new EngineProtocolNetwork(config); |
449 GameConnection gameConn; |
429 |
450 String path; |
430 String path = Utils.getDataPath(SDLActivity.mSingleton);//This represents the data directory |
|
431 path = path.substring(0, path.length()-1);//remove the trailing '/' |
|
432 |
|
433 |
|
434 // Runs SDL_main() with added parameters |
|
435 SDLActivity.nativeInit(new String[] { String.valueOf(ipc.port), |
|
436 String.valueOf(surfaceWidth), String.valueOf(surfaceHeight), |
|
437 "0", "en.txt", "xeli", "1", "1", "1", path, "" }); |
|
438 |
|
439 try { |
451 try { |
440 ipc.quitIPC(); |
452 if(netgame) { |
441 ipc.join(); |
453 Netplay netplay = Netplay.getAppInstance(SDLActivity.mSingleton.getApplicationContext()); |
442 } catch (InterruptedException e) { |
454 gameConn = GameConnection.forNetgame(config, netplay); |
443 e.printStackTrace(); |
455 } else { |
444 } |
456 gameConn = GameConnection.forLocalGame(config); |
445 Log.v("SDL", "SDL thread terminated"); |
457 } |
446 //Log.v("SDL", "SDL thread terminated"); |
458 |
|
459 path = FileUtils.getDataPathFile(SDLActivity.mSingleton).getAbsolutePath(); |
|
460 Log.d(TAG, "Starting engine"); |
|
461 // Runs SDL_main() with added parameters |
|
462 try { |
|
463 String pPort = String.valueOf(gameConn.port); |
|
464 String pWidth = String.valueOf(surfaceWidth); |
|
465 String pHeight = String.valueOf(surfaceHeight); |
|
466 String pQuality = Integer.toString(RQ_NO_FLAKES|RQ_NO_DROPLETS|RQ_SIMPLE_EXPLOSIONS); |
|
467 String pPlayerName = Base64.encodeToString(playerName.getBytes("UTF-8"), 0); |
|
468 SDLActivity.synchronizedNativeInit(new String[] { pPort, pWidth, pHeight, pQuality, "en.txt", pPlayerName, "1", "1", "1", path, "" }); |
|
469 } catch (UnsupportedEncodingException e) { |
|
470 throw new AssertionError(e); // never happens |
|
471 } |
|
472 Log.d(TAG, "Engine stopped"); |
|
473 } catch(ConnectException e) { |
|
474 Log.e(TAG, "Error starting IPC connection"); |
|
475 } catch (IOException e) { |
|
476 Log.e(TAG, "Missing SDCard"); |
|
477 } |
|
478 SDLActivity.mSingleton.runOnUiThread(new Runnable() { public void run() { |
|
479 if(SDLActivity.mSingleton != null) { |
|
480 SDLActivity.mSingleton.finish(); |
|
481 } |
|
482 }}); |
447 } |
483 } |
448 } |
484 } |
449 |
485 |
450 |
486 |
451 /** |
487 /** |
456 */ |
492 */ |
457 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, |
493 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, |
458 View.OnKeyListener, View.OnTouchListener, SensorEventListener { |
494 View.OnKeyListener, View.OnTouchListener, SensorEventListener { |
459 |
495 |
460 private GameConfig config; |
496 private GameConfig config; |
461 |
497 private boolean netgame; |
|
498 |
462 // Sensors |
499 // Sensors |
463 private static SensorManager mSensorManager; |
500 private static SensorManager mSensorManager; |
464 |
501 |
465 // Startup |
502 // Startup |
466 public SDLSurface(Context context, GameConfig _config) { |
503 public SDLSurface(Context context, GameConfig _config, boolean netgame) { |
467 super(context); |
504 super(context); |
468 getHolder().addCallback(this); |
505 getHolder().addCallback(this); |
469 |
506 |
470 setFocusable(true); |
507 setFocusable(true); |
471 setFocusableInTouchMode(true); |
508 setFocusableInTouchMode(true); |
578 return false; |
617 return false; |
579 } |
618 } |
580 |
619 |
581 // Touch events |
620 // Touch events |
582 public boolean onTouch(View v, MotionEvent event) { |
621 public boolean onTouch(View v, MotionEvent event) { |
583 { |
622 final int action = event.getAction() & MotionEvent.ACTION_MASK; |
584 final int touchDevId = event.getDeviceId(); |
623 final int actionPointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; |
585 final int pointerCount = event.getPointerCount(); |
624 |
586 // touchId, pointerId, action, x, y, pressure |
625 if (action == MotionEvent.ACTION_MOVE) { |
587 int actionPointerIndex = event.getActionIndex(); |
626 // TODO send motion to every pointer if its position has |
588 int pointerFingerId = event.getPointerId(actionPointerIndex); |
627 // changed since prev event. |
589 int action = event.getActionMasked(); |
628 for (int i = 0; i < event.getPointerCount(); i++) { |
590 |
629 sendNativeTouch(event, action, i); |
591 float x = event.getX(actionPointerIndex); |
630 } |
592 float y = event.getY(actionPointerIndex); |
631 } else { |
593 float p = event.getPressure(actionPointerIndex); |
632 sendNativeTouch(event, action, actionPointerIndex); |
594 |
|
595 if (action == MotionEvent.ACTION_MOVE && pointerCount > 1) { |
|
596 // TODO send motion to every pointer if its position has |
|
597 // changed since prev event. |
|
598 for (int i = 0; i < pointerCount; i++) { |
|
599 pointerFingerId = event.getPointerId(i); |
|
600 x = event.getX(i); |
|
601 y = event.getY(i); |
|
602 p = event.getPressure(i); |
|
603 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); |
|
604 } |
|
605 } else { |
|
606 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); |
|
607 } |
|
608 } |
633 } |
609 return true; |
634 return true; |
610 } |
635 } |
|
636 |
|
637 private static void sendNativeTouch(MotionEvent event, int action, int pointerIndex) { |
|
638 int touchDevId = event.getDeviceId(); |
|
639 int pointerFingerId = event.getPointerId(pointerIndex); |
|
640 float x = event.getX(pointerIndex); |
|
641 float y = event.getY(pointerIndex); |
|
642 float pressure = event.getPressure(pointerIndex); |
|
643 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, pressure); |
|
644 } |
611 |
645 |
612 // Sensor events |
646 // Sensor events |
613 public void enableSensor(int sensortype, boolean enabled) { |
647 public void enableSensor(int sensortype, boolean enabled) { |
614 // TODO: This uses getDefaultSensor - what if we have >1 accels? |
648 // TODO: This uses getDefaultSensor - what if we have >1 accels? |
615 if (enabled) { |
649 if (enabled) { |