diff --git a/Android/app/build.gradle b/Android/app/build.gradle
index 860145cb5..12d75aaa8 100644
--- a/Android/app/build.gradle
+++ b/Android/app/build.gradle
@@ -75,7 +75,7 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.core:core:1.7.0' // Use the latest version
-
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
}
task wrapper(type: Wrapper) {
diff --git a/Android/app/src/main/java/com/dishii/mm/ControllerButtons.java b/Android/app/src/main/java/com/dishii/mm/ControllerButtons.java
new file mode 100644
index 000000000..12c494c78
--- /dev/null
+++ b/Android/app/src/main/java/com/dishii/mm/ControllerButtons.java
@@ -0,0 +1,26 @@
+package com.dishii.mm;
+
+public class ControllerButtons {
+ // Xbox Buttons
+ public static final int BUTTON_A = 0;
+ public static final int BUTTON_B = 1;
+ public static final int BUTTON_X = 2;
+ public static final int BUTTON_Y = 3;
+ public static final int BUTTON_LB = 9; // Left Bumper
+ public static final int BUTTON_RB = -5; // Right Bumper
+ public static final int BUTTON_BACK = 4;
+ public static final int BUTTON_START = 6;
+ public static final int BUTTON_DPAD_UP = 11;
+ public static final int BUTTON_DPAD_DOWN = 12;
+ public static final int BUTTON_DPAD_LEFT = 13;
+ public static final int BUTTON_DPAD_RIGHT = 14;
+
+ // Xbox Axis (Joysticks and Triggers)
+ public static final int AXIS_LX = 0; // Left stick X
+ public static final int AXIS_LY = 1; // Left stick Y
+ public static final int AXIS_RX = 2; // Right stick X
+ public static final int AXIS_RY = 3; // Right stick Y
+ public static final int AXIS_LT = -2; // Left Trigger (negative axis)
+ public static final int AXIS_RT = -4; // Right Trigger (negative axis)
+}
+
diff --git a/Android/app/src/main/java/com/dishii/mm/MainActivity.java b/Android/app/src/main/java/com/dishii/mm/MainActivity.java
index 8fc7da59b..658a556bc 100644
--- a/Android/app/src/main/java/com/dishii/mm/MainActivity.java
+++ b/Android/app/src/main/java/com/dishii/mm/MainActivity.java
@@ -20,15 +20,29 @@ import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.util.Log;
-//This class is the main SDLActivity and just sets up a bunch of default files
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+
+//This class is the main SDLActivity and just sets up a bunch of default files and the input overlay
public class MainActivity extends SDLActivity{
private static final int STORAGE_PERMISSION_REQUEST_CODE = 1;
+ SharedPreferences preferences;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ preferences = getSharedPreferences("com.dishii.mm.prefs",Context.MODE_PRIVATE);
+
+ setupControllerOverlay();
+
// Check if storage permissions are granted
if (hasStoragePermission()) {
doVersionCheck();
@@ -36,10 +50,10 @@ public class MainActivity extends SDLActivity{
} else {
requestStoragePermission();
}
+ attachController();
}
private void doVersionCheck(){
- SharedPreferences preferences = getSharedPreferences("com.dishii.mm.prefs",Context.MODE_PRIVATE);
int currentVersion = BuildConfig.VERSION_CODE; // Use your app's version code
int storedVersion = preferences.getInt("appVersion", 1);
@@ -184,4 +198,258 @@ public class MainActivity extends SDLActivity{
return Environment.MEDIA_MOUNTED.equals(state);
}
+
+ public native void attachController();
+ // Native method for setting button state
+ public native void setButton(int button, boolean value);
+ public native void setCameraState(int axis, float value);
+
+ // Native method for setting joystick axis value
+ public native void setAxis(int axis, short value);
+
+ private Button button1, button2, button3, button4;
+ private Button buttonA, buttonB, buttonX, buttonY;
+ private Button buttonDpadUp, buttonDpadDown, buttonDpadLeft, buttonDpadRight;
+ private Button buttonLB, buttonRB, buttonZ, buttonStart, buttonBack;
+ private Button buttonToggle;
+ private FrameLayout leftJoystick;
+ private ImageView leftJoystickKnob;
+ private View overlayView;
+
+ // Function to set up the controller overlay (inflate layout and initialize buttons)
+ private void setupControllerOverlay() {
+ // Inflate the touchcontrol_overlay layout
+ LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
+ overlayView = inflater.inflate(R.layout.touchcontrol_overlay, null);
+
+ // Set layout params for overlayView to control positioning and sizing
+ FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT
+ );
+ overlayView.setLayoutParams(layoutParams);
+ // Add overlay view to the main layout (you may need to add it to a container like FrameLayout)
+ ViewGroup view = (ViewGroup) getContentView();
+ view.addView(overlayView);
+
+ final ViewGroup buttonGroup = overlayView.findViewById(R.id.button_group);
+
+ buttonA = overlayView.findViewById(R.id.buttonA);
+ buttonB = overlayView.findViewById(R.id.buttonB);
+ buttonX = overlayView.findViewById(R.id.buttonX);
+ buttonY = overlayView.findViewById(R.id.buttonY);
+
+ buttonDpadUp = overlayView.findViewById(R.id.buttonDpadUp);
+ buttonDpadDown = overlayView.findViewById(R.id.buttonDpadDown);
+ buttonDpadLeft = overlayView.findViewById(R.id.buttonDpadLeft);
+ buttonDpadRight = overlayView.findViewById(R.id.buttonDpadRight);
+
+ buttonLB = overlayView.findViewById(R.id.buttonLB);
+ buttonRB = overlayView.findViewById(R.id.buttonRB);
+ buttonZ = overlayView.findViewById(R.id.buttonZ);
+
+ buttonStart = overlayView.findViewById(R.id.buttonStart);
+ buttonBack = overlayView.findViewById(R.id.buttonBack);
+
+ buttonToggle = overlayView.findViewById(R.id.buttonToggle);
+
+ // Initialize joysticks and joystick knobs from the inflated layout
+ leftJoystick = overlayView.findViewById(R.id.left_joystick);
+ leftJoystickKnob = overlayView.findViewById(R.id.left_joystick_knob);
+
+ FrameLayout rightScreenArea = overlayView.findViewById(R.id.right_screen_area);
+
+ // Set OnTouchListeners for the Xbox controller buttons
+ addTouchListener(buttonA, ControllerButtons.BUTTON_A); // SDL Button 0 (A)
+ addTouchListener(buttonB, ControllerButtons.BUTTON_B); // SDL Button 1 (B)
+ addTouchListener(buttonX, ControllerButtons.BUTTON_X); // SDL Button 2 (X)
+ addTouchListener(buttonY, ControllerButtons.BUTTON_Y); // SDL Button 3 (Y)
+
+ addTouchListener(buttonDpadUp, ControllerButtons.BUTTON_DPAD_UP); // SDL Button 10 (D-Pad Up)
+ addTouchListener(buttonDpadDown, ControllerButtons.BUTTON_DPAD_DOWN); // SDL Button 11 (D-Pad Down)
+ addTouchListener(buttonDpadLeft, ControllerButtons.BUTTON_DPAD_LEFT); // SDL Button 12 (D-Pad Left)
+ addTouchListener(buttonDpadRight, ControllerButtons.BUTTON_DPAD_RIGHT); // SDL Button 13 (D-Pad Right)
+
+ addTouchListener(buttonLB, ControllerButtons.BUTTON_LB); // SDL Button 4 (LB)
+ addTouchListener(buttonRB, ControllerButtons.BUTTON_RB); // SDL Button 5 (RB)
+ addTouchListener(buttonZ, ControllerButtons.AXIS_RT); // SDL Button 5 (Z)
+
+ addTouchListener(buttonStart, ControllerButtons.BUTTON_START); // SDL Button 7 (Start)
+ addTouchListener(buttonBack, ControllerButtons.BUTTON_BACK); // SDL Button 6 (Back)
+
+
+ // Setup joystick movement
+ setupJoystick(leftJoystick, leftJoystickKnob, true); // Left joystick
+
+ setupLookAround(rightScreenArea);
+
+ setupToggleButton(buttonToggle,buttonGroup);
+
+ }
+
+ private void setupToggleButton(Button button, ViewGroup uiGroup){
+ boolean isHidden = preferences.getBoolean("controlsVisible", false); // Default to 'false' (visible)
+ uiGroup.setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE); // Set the initial visibility based on the saved state
+ button.setOnClickListener(new View.OnClickListener() {
+ boolean isHidden = false;
+ @Override
+ public void onClick(View v) {
+ if (isHidden) {
+ uiGroup.setVisibility(View.VISIBLE); // Show UI elements
+ } else {
+ uiGroup.setVisibility(View.INVISIBLE); // Hide UI elements
+ }
+ preferences.edit().putBoolean("controlsVisible", !isHidden).apply();
+ isHidden = !isHidden; // Toggle state
+ }
+ });
+ }
+
+ // Function to set a touch listener for each button
+ private void addTouchListener(Button button, int buttonNum) {
+ button.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ setButton(buttonNum, true);
+ button.setPressed(true);
+ return true;
+ case MotionEvent.ACTION_UP:
+ setButton(buttonNum, false);
+ button.setPressed(false);
+ return true;
+ case MotionEvent.ACTION_CANCEL:
+ setButton(buttonNum, false);
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ boolean TouchAreaEnabled = true;
+
+ void DisableTouchArea(){
+ TouchAreaEnabled = false;
+ }
+ void EnableTouchArea(){
+ TouchAreaEnabled = true;
+ }
+
+ private void setupLookAround(FrameLayout rightScreenArea) {
+ rightScreenArea.setOnTouchListener(new View.OnTouchListener() {
+ private float lastX = 0;
+ private float lastY = 0;
+ private boolean isTouching = false;
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ // Start tracking the finger's position
+ lastX = event.getX();
+ lastY = event.getY();
+ isTouching = true;
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (isTouching) {
+ // Calculate the change in position (delta)
+ float deltaX = event.getX() - lastX;
+ float deltaY = event.getY() - lastY;
+
+ // Update the last position
+ lastX = event.getX();
+ lastY = event.getY();
+
+ // Increase sensitivity by using a larger multiplier
+ // Adjust these multipliers to suit your needs
+ float sensitivityMultiplier = 15; // Higher value for more sensitivity
+ float rx = (deltaX * sensitivityMultiplier);
+ float ry = (deltaY * sensitivityMultiplier);
+
+ // Send the mapped values to the joystick axes
+ setCameraState(0, rx); // Right stick X axis
+ setCameraState(1, ry); // Right stick Y axis
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ // Stop tracking the finger's position and reset joystick input
+ isTouching = false;
+ setCameraState(0, 0.0f); // Reset right stick X axis
+ setCameraState(1, 0.0f); // Reset right stick Y axis
+ break;
+ }
+ return TouchAreaEnabled; // Event full handled
+ }
+ });
+ }
+
+
+
+
+
+ // Function to set joystick movement with reset to center when not touched
+ private void setupJoystick(FrameLayout joystickLayout, ImageView joystickKnob, boolean isLeft) {
+ joystickLayout.post(() -> {
+ // Calculate the joystick center once, before any events
+ final float joystickCenterX = joystickLayout.getWidth() / 2f;
+ final float joystickCenterY = joystickLayout.getHeight() / 2f;
+
+ joystickLayout.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ // Calculate the joystick movement and move the knob
+ float deltaX = event.getX() - joystickCenterX;
+ float deltaY = event.getY() - joystickCenterY;
+
+ // Clamp the joystick movement to prevent it from going outside the area
+ float maxRadius = joystickLayout.getWidth() / 2f - joystickKnob.getWidth() / 2f;
+ float distance = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ if (distance > maxRadius) {
+ float scale = maxRadius / distance;
+ deltaX *= scale;
+ deltaY *= scale;
+ }
+
+ joystickKnob.setX(joystickCenterX + deltaX - joystickKnob.getWidth() / 2f);
+ joystickKnob.setY(joystickCenterY + deltaY - joystickKnob.getHeight() / 2f);
+
+ // Send joystick values to native C code
+ short x = (short) (deltaX / maxRadius * Short.MAX_VALUE);
+ short y = (short) (deltaY / maxRadius * Short.MAX_VALUE);
+
+ // Send X-axis and Y-axis values
+ setAxis(isLeft ? ControllerButtons.AXIS_LX : ControllerButtons.AXIS_RX, x); // X-axis
+ setAxis(isLeft ? ControllerButtons.AXIS_LY : ControllerButtons.AXIS_RY, y); // Y-axis
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ // Reset joystick knob to the center position (ensure it's placed correctly)
+ joystickKnob.setX(joystickCenterX - joystickKnob.getWidth() / 2f);
+ joystickKnob.setY(joystickCenterY - joystickKnob.getHeight() / 2f);
+
+ // Reset joystick values to 0 when released or canceled
+ setAxis(isLeft ? ControllerButtons.AXIS_LX : ControllerButtons.AXIS_RX, (short) 0); // X-axis
+ setAxis(isLeft ? ControllerButtons.AXIS_LY : ControllerButtons.AXIS_RY, (short) 0); // Y-axis
+ break;
+ }
+ return true;
+ }
+ });
+ });
+
+
+
+}
+
+
+
}
diff --git a/Android/app/src/main/res/drawable/ic_button.xml b/Android/app/src/main/res/drawable/ic_button.xml
new file mode 100644
index 000000000..f9499102b
--- /dev/null
+++ b/Android/app/src/main/res/drawable/ic_button.xml
@@ -0,0 +1,22 @@
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/Android/app/src/main/res/drawable/ic_cross.xml b/Android/app/src/main/res/drawable/ic_cross.xml
new file mode 100644
index 000000000..4ba63a2b4
--- /dev/null
+++ b/Android/app/src/main/res/drawable/ic_cross.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/Android/app/src/main/res/drawable/ic_rectangular_button.xml b/Android/app/src/main/res/drawable/ic_rectangular_button.xml
new file mode 100644
index 000000000..9d83fb4ee
--- /dev/null
+++ b/Android/app/src/main/res/drawable/ic_rectangular_button.xml
@@ -0,0 +1,38 @@
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+
+
+
+
diff --git a/Android/app/src/main/res/drawable/ic_show.xml b/Android/app/src/main/res/drawable/ic_show.xml
new file mode 100644
index 000000000..418a45676
--- /dev/null
+++ b/Android/app/src/main/res/drawable/ic_show.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/Android/app/src/main/res/drawable/ic_stick.xml b/Android/app/src/main/res/drawable/ic_stick.xml
new file mode 100644
index 000000000..486b2b083
--- /dev/null
+++ b/Android/app/src/main/res/drawable/ic_stick.xml
@@ -0,0 +1,27 @@
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/app/src/main/res/drawable/ic_trigger_button_left.xml b/Android/app/src/main/res/drawable/ic_trigger_button_left.xml
new file mode 100644
index 000000000..3b7840b2e
--- /dev/null
+++ b/Android/app/src/main/res/drawable/ic_trigger_button_left.xml
@@ -0,0 +1,46 @@
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+
+
+
+
diff --git a/Android/app/src/main/res/drawable/ic_trigger_button_right.xml b/Android/app/src/main/res/drawable/ic_trigger_button_right.xml
new file mode 100644
index 000000000..4c21d6bb6
--- /dev/null
+++ b/Android/app/src/main/res/drawable/ic_trigger_button_right.xml
@@ -0,0 +1,46 @@
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+
+
+
+
diff --git a/Android/app/src/main/res/layout/touchcontrol_overlay.xml b/Android/app/src/main/res/layout/touchcontrol_overlay.xml
new file mode 100644
index 000000000..e573f73ef
--- /dev/null
+++ b/Android/app/src/main/res/layout/touchcontrol_overlay.xml
@@ -0,0 +1,213 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libultraship b/libultraship
index 7a303818b..2860d3df9 160000
--- a/libultraship
+++ b/libultraship
@@ -1 +1 @@
-Subproject commit 7a303818b3ff3a2fe4acc7379124425583ad8445
+Subproject commit 2860d3df9cc4f21e1e97470831b47d99833d3d8a
diff --git a/mm/2s2h/Enhancements/Camera/FreeLook.cpp b/mm/2s2h/Enhancements/Camera/FreeLook.cpp
index 3431f4abb..d89de6499 100644
--- a/mm/2s2h/Enhancements/Camera/FreeLook.cpp
+++ b/mm/2s2h/Enhancements/Camera/FreeLook.cpp
@@ -1,6 +1,9 @@
#include
#include "2s2h/GameInteractor/GameInteractor.h"
#include "CameraUtils.h"
+#ifdef __ANDROID__
+#include "port/mobile/MobileImpl.h"
+#endif
extern "C" {
#include
@@ -42,7 +45,7 @@ void UpdateFreeLookState(Camera* camera) {
sCanFreeLook = false;
}
}
-
+#include
// Function based on several camera functions, including Camera_Parallel1
bool Camera_FreeLook(Camera* camera) {
Vec3f* eye = &camera->eye;
@@ -96,6 +99,13 @@ bool Camera_FreeLook(Camera* camera) {
f32 pitchDiff = sCamPlayState->state.input[0].cur.right_stick_y * 10.0f *
(CVarGetFloat("gEnhancements.Camera.RightStick.CameraSensitivity.Y", 1.0f));
+#ifdef __ANDROID__
+ if(Ship::Mobile::IsUsingTouchscreenControls()) {
+ yawDiff = -Ship::Mobile::GetCameraYaw()*10.0f;
+ pitchDiff = Ship::Mobile::GetCameraPitch()*10.0f;
+ }
+#endif
+
yaw += yawDiff * GameInteractor_InvertControl(GI_INVERT_CAMERA_RIGHT_STICK_X);
pitch += pitchDiff * -GameInteractor_InvertControl(GI_INVERT_CAMERA_RIGHT_STICK_Y);
@@ -139,6 +149,13 @@ bool Camera_FreeLook(Camera* camera) {
bool Camera_CanFreeLook(Camera* camera) {
f32 camX = sCamPlayState->state.input[0].cur.right_stick_x * 10.0f;
f32 camY = sCamPlayState->state.input[0].cur.right_stick_y * 10.0f;
+
+#ifdef __ANDROID__
+ if(!sCanFreeLook && Ship::Mobile::IsUsingTouchscreenControls() && (Ship::Mobile::GetCameraYaw()>0||Ship::Mobile::GetCameraPitch()>0)) {
+ sCanFreeLook=true;
+ }
+#endif
+
if (!sCanFreeLook && (fabsf(camX) >= 15.0f || fabsf(camY) >= 15.0f)) {
sCanFreeLook = true;
}