1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.replica.replicaisland;
18
19import android.view.KeyEvent;
20
21public class InputGameInterface extends BaseObject {
22	private static final float ORIENTATION_DEAD_ZONE_MIN = 0.03f;
23	private static final float ORIENTATION_DEAD_ZONE_MAX = 0.1f;
24	private static final float ORIENTATION_DEAD_ZONE_SCALE = 0.75f;
25
26	private final static float ROLL_TIMEOUT = 0.1f;
27	private final static float ROLL_RESET_DELAY = 0.075f;
28
29    // Raw trackball input is filtered by this value. Increasing it will
30    // make the control more twitchy, while decreasing it will make the control more precise.
31    private final static float ROLL_FILTER = 0.4f;
32    private final static float ROLL_DECAY = 8.0f;
33
34    private final static float KEY_FILTER = 0.25f;
35    private final static float SLIDER_FILTER = 0.25f;
36
37
38	private InputButton mJumpButton = new InputButton();
39	private InputButton mAttackButton = new InputButton();
40	private InputXY mDirectionalPad = new InputXY();
41	private InputXY mTilt = new InputXY();
42
43	private int mLeftKeyCode = KeyEvent.KEYCODE_DPAD_LEFT;
44	private int mRightKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
45	private int mJumpKeyCode = KeyEvent.KEYCODE_SPACE;
46	private int mAttackKeyCode = KeyEvent.KEYCODE_SHIFT_LEFT;
47
48	private float mOrientationDeadZoneMin = ORIENTATION_DEAD_ZONE_MIN;
49	private float mOrientationDeadZoneMax = ORIENTATION_DEAD_ZONE_MAX;
50	private float mOrientationDeadZoneScale = ORIENTATION_DEAD_ZONE_SCALE;
51	private float mOrientationSensitivity = 1.0f;
52	private float mOrientationSensitivityFactor = 1.0f;
53	private float mMovementSensitivity = 1.0f;
54
55	private boolean mUseClickButtonForAttack = true;
56	private boolean mUseOrientationForMovement = false;
57	private boolean mUseOnScreenControls = false;
58
59	private float mLastRollTime;
60
61	public InputGameInterface() {
62		super();
63		reset();
64	}
65
66	@Override
67	public void reset() {
68		mJumpButton.release();
69		mAttackButton.release();
70		mDirectionalPad.release();
71		mTilt.release();
72	}
73
74
75
76	@Override
77    public void update(float timeDelta, BaseObject parent) {
78		InputSystem input = sSystemRegistry.inputSystem;
79		final InputButton[] keys = input.getKeyboard().getKeys();
80		final InputXY orientation = input.getOrientationSensor();
81
82		// tilt is easy
83		mTilt.clone(orientation);
84
85		final InputTouchScreen touch = input.getTouchScreen();
86		final float gameTime = sSystemRegistry.timeSystem.getGameTime();
87
88		float sliderOffset = 0;
89
90		// update movement inputs
91		if (mUseOnScreenControls) {
92			final InputXY sliderTouch = touch.findPointerInRegion(
93					ButtonConstants.MOVEMENT_SLIDER_REGION_X,
94                    ButtonConstants.MOVEMENT_SLIDER_REGION_Y,
95                    ButtonConstants.MOVEMENT_SLIDER_REGION_WIDTH,
96                    ButtonConstants.MOVEMENT_SLIDER_REGION_HEIGHT);
97
98			if (sliderTouch != null) {
99				final float halfWidth = ButtonConstants.MOVEMENT_SLIDER_BAR_WIDTH / 2.0f;
100				final float center = ButtonConstants.MOVEMENT_SLIDER_X + halfWidth;
101				final float offset = sliderTouch.getX() - center;
102				float magnitudeRamp = Math.abs(offset) > halfWidth ? 1.0f : (Math.abs(offset) / halfWidth);
103
104				final float magnitude = magnitudeRamp * Utils.sign(offset) * SLIDER_FILTER * mMovementSensitivity;
105				sliderOffset = magnitudeRamp * Utils.sign(offset);
106				mDirectionalPad.press(gameTime, magnitude, 0.0f);
107			} else {
108				mDirectionalPad.release();
109			}
110		} else if (mUseOrientationForMovement) {
111			mDirectionalPad.clone(orientation);
112			mDirectionalPad.setMagnitude(
113					filterOrientationForMovement(orientation.getX()),
114					filterOrientationForMovement(orientation.getY()));
115		} else {
116			// keys or trackball
117			final InputXY trackball = input.getTrackball();
118			final InputButton left = keys[mLeftKeyCode];
119			final InputButton right = keys[mRightKeyCode];
120			final float leftPressedTime = left.getLastPressedTime();
121			final float rightPressedTime = right.getLastPressedTime();
122
123
124			if (trackball.getLastPressedTime() > Math.max(leftPressedTime, rightPressedTime)) {
125				// The trackball never goes "up", so force it to turn off if it wasn't triggered in the last frame.
126				// What follows is a bunch of code to filter trackball events into something like a dpad event.
127				// The goals here are:
128				// 	- For roll events that occur in quick succession to accumulate.
129				//	- For roll events that occur with more time between them, lessen the impact of older events
130				//	- In the absence of roll events, fade the roll out over time.
131				if (gameTime - trackball.getLastPressedTime() < ROLL_TIMEOUT) {
132					float newX;
133					float newY;
134					final float delay = Math.max(ROLL_RESET_DELAY, timeDelta);
135					if (gameTime - mLastRollTime <= delay) {
136						newX = mDirectionalPad.getX() + (trackball.getX() * ROLL_FILTER * mMovementSensitivity);
137						newY = mDirectionalPad.getY() + (trackball.getY() * ROLL_FILTER * mMovementSensitivity);
138					} else {
139						float oldX = mDirectionalPad.getX() != 0.0f ? mDirectionalPad.getX() / 2.0f : 0.0f;
140						float oldY = mDirectionalPad.getX() != 0.0f ? mDirectionalPad.getX() / 2.0f : 0.0f;
141						newX = oldX + (trackball.getX() * ROLL_FILTER * mMovementSensitivity);
142						newY = oldY + (trackball.getX() * ROLL_FILTER * mMovementSensitivity);
143					}
144
145					mDirectionalPad.press(gameTime, newX, newY);
146					mLastRollTime = gameTime;
147					trackball.release();
148				} else {
149					float x = mDirectionalPad.getX();
150					float y = mDirectionalPad.getY();
151					if (x != 0.0f) {
152						int sign = Utils.sign(x);
153						x = x - (sign * ROLL_DECAY * timeDelta);
154						if (Utils.sign(x) != sign) {
155							x = 0.0f;
156						}
157					}
158
159					if (y != 0.0f) {
160						int sign = Utils.sign(y);
161						y = y - (sign * ROLL_DECAY * timeDelta);
162						if (Utils.sign(x) != sign) {
163							y = 0.0f;
164						}
165					}
166
167
168					if (x == 0 && y == 0) {
169						mDirectionalPad.release();
170					} else {
171						mDirectionalPad.setMagnitude(x, y);
172					}
173				}
174
175			} else {
176				float xMagnitude = 0.0f;
177				float yMagnitude = 0.0f;
178				float pressTime = 0.0f;
179				// left and right are mutually exclusive
180				if (leftPressedTime > rightPressedTime) {
181					xMagnitude = -left.getMagnitude() * KEY_FILTER * mMovementSensitivity;
182					pressTime = leftPressedTime;
183				} else {
184					xMagnitude = right.getMagnitude() * KEY_FILTER * mMovementSensitivity;
185					pressTime = rightPressedTime;
186				}
187
188				if (xMagnitude != 0.0f) {
189					mDirectionalPad.press(pressTime, xMagnitude, yMagnitude);
190				} else {
191					mDirectionalPad.release();
192				}
193			}
194		}
195
196		// update other buttons
197		final InputButton jumpKey = keys[mJumpKeyCode];
198
199		// when on-screen movement controls are on, the fly and attack buttons are flipped.
200		float flyButtonRegionX = ButtonConstants.FLY_BUTTON_REGION_X;
201		float stompButtonRegionX = ButtonConstants.STOMP_BUTTON_REGION_X;
202
203		if (mUseOnScreenControls) {
204			ContextParameters params = sSystemRegistry.contextParameters;
205			flyButtonRegionX = params.gameWidth - ButtonConstants.FLY_BUTTON_REGION_WIDTH - ButtonConstants.FLY_BUTTON_REGION_X;
206			stompButtonRegionX = params.gameWidth - ButtonConstants.STOMP_BUTTON_REGION_WIDTH - ButtonConstants.STOMP_BUTTON_REGION_X;
207		}
208
209		final InputXY jumpTouch = touch.findPointerInRegion(
210				flyButtonRegionX,
211                ButtonConstants.FLY_BUTTON_REGION_Y,
212                ButtonConstants.FLY_BUTTON_REGION_WIDTH,
213                ButtonConstants.FLY_BUTTON_REGION_HEIGHT);
214
215		if (jumpKey.getPressed()) {
216			mJumpButton.press(jumpKey.getLastPressedTime(), jumpKey.getMagnitude());
217		} else if (jumpTouch != null) {
218			if (!mJumpButton.getPressed()) {
219				mJumpButton.press(jumpTouch.getLastPressedTime(), 1.0f);
220			}
221		} else {
222			mJumpButton.release();
223		}
224
225		final InputButton attackKey = keys[mAttackKeyCode];
226		final InputButton clickButton = keys[KeyEvent.KEYCODE_DPAD_CENTER]; // special case
227
228		final InputXY stompTouch = touch.findPointerInRegion(
229				stompButtonRegionX,
230                ButtonConstants.STOMP_BUTTON_REGION_Y,
231                ButtonConstants.STOMP_BUTTON_REGION_WIDTH,
232                ButtonConstants.STOMP_BUTTON_REGION_HEIGHT);
233
234		if (mUseClickButtonForAttack && clickButton.getPressed()) {
235			mAttackButton.press(clickButton.getLastPressedTime(), clickButton.getMagnitude());
236		} else if (attackKey.getPressed()) {
237			mAttackButton.press(attackKey.getLastPressedTime(), attackKey.getMagnitude());
238		} else if (stompTouch != null) {
239			// Since touch events come in constantly, we only want to press the attack button
240			// here if it's not already down.  That makes it act like the other buttons (down once then up).
241			if (!mAttackButton.getPressed()) {
242				mAttackButton.press(stompTouch.getLastPressedTime(), 1.0f);
243			}
244		} else {
245			mAttackButton.release();
246		}
247
248		// This doesn't seem like exactly the right place to write to the HUD, but on the other hand,
249		// putting this code elsewhere causes dependencies between exact HUD content and physics, which
250		// we sometimes wish to avoid.
251		final HudSystem hud = sSystemRegistry.hudSystem;
252        if (hud != null) {
253            hud.setButtonState(mJumpButton.getPressed(), mAttackButton.getPressed(), mDirectionalPad.getPressed());
254            hud.setMovementSliderOffset(sliderOffset);
255        }
256	}
257
258
259	private float filterOrientationForMovement(float magnitude) {
260		float scaledMagnitude = magnitude * mOrientationSensitivityFactor;
261
262		return deadZoneFilter(scaledMagnitude, mOrientationDeadZoneMin, mOrientationDeadZoneMax, mOrientationDeadZoneScale);
263	}
264
265	private float deadZoneFilter(float magnitude, float min, float max, float scale) {
266		float smoothedMagnatude = magnitude;
267    	if (Math.abs(magnitude) < min) {
268    		smoothedMagnatude = 0.0f;	// dead zone
269    	} else if (Math.abs(magnitude) < max) {
270    		smoothedMagnatude *= scale;
271    	}
272
273    	return smoothedMagnatude;
274	}
275
276
277	public final InputXY getDirectionalPad() {
278		return mDirectionalPad;
279	}
280
281	public final InputXY getTilt() {
282		return mTilt;
283	}
284
285	public final InputButton getJumpButton() {
286		return mJumpButton;
287	}
288
289	public final InputButton getAttackButton() {
290		return mAttackButton;
291	}
292
293	public void setKeys(int left, int right, int jump, int attack) {
294		mLeftKeyCode = left;
295		mRightKeyCode = right;
296		mJumpKeyCode = jump;
297		mAttackKeyCode = attack;
298	}
299
300	public void setUseClickForAttack(boolean click) {
301		mUseClickButtonForAttack = click;
302	}
303
304	public void setUseOrientationForMovement(boolean orientation) {
305		mUseOrientationForMovement = orientation;
306	}
307
308	public void setOrientationMovementSensitivity(float sensitivity) {
309		mOrientationSensitivity = sensitivity;
310		mOrientationSensitivityFactor = 2.9f * sensitivity + 0.1f;
311	}
312
313	public void setMovementSensitivity(float sensitivity) {
314		mMovementSensitivity  = sensitivity;
315	}
316
317	public void setUseOnScreenControls(boolean onscreen) {
318		mUseOnScreenControls = onscreen;
319	}
320
321}
322