mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-19 21:00:29 +02:00
An overlay over the game, from JoshRob297's halo-ce-touch, which ported theLlamaNet's halo-ce-android: a stick, the controller's buttons with word labels, swipe aiming, optional gyroscope aiming, the phone's vibration, and an editor to move, resize, hide, copy, export and import the buttons. It is player 1's controller, merged into port 0's state as the keyboard is. Changes from the fork: - The controls show only in a game. In the menus and during cinematics they hide and let the fingers through, so HiIAmMoot's menu touch (upstream #50) and its tap to skip work under them. The game tells the overlay through a new host import, host_touch_scene. - They hide while a controller with a stick is connected, such as a handheld's built-in one, and never show without a touchscreen (a TV). The new setting input.touch_controls ("auto", "on", "off"; default "auto") overrides this. Hiding with a controller follows diegotorres00's upstream #58. - Swipes that start in Android's edge-gesture zones do not turn the view, and a press shorter than a frame is held long enough for the game to see it, as HiIAmMoot suggested in the review of #58. - The swipe is not the mouse's aiming, so it keeps the controller's aim assist. - The phone vibrates from port 0's XInputSetState, so the game's own vibration setting applies and no game source changes. It vibrates only while the touch controls show. - The fork's cheats menu and its built-in cheat buttons are left out. Co-authored-by: JoshRob297 <99092299+JoshRob297@users.noreply.github.com> Co-authored-by: theLlamaNet <107216990+theLlamaNet@users.noreply.github.com> (cherry picked from commit 7bd7481bdede370558a3c93d286e37a9c24237bd) (cherry picked from commit 5911e2312b6960de58dfa1cad2fefce1a17b97f7)
110 lines
6.8 KiB
Java
110 lines
6.8 KiB
Java
package com.halo.decomp;
|
|
|
|
/** Standalone geometry/persistence regression checks; no phone required. */
|
|
public final class TouchLayoutTest {
|
|
private static void check(boolean condition, String message) {
|
|
if (!condition) throw new AssertionError(message);
|
|
}
|
|
public static void main(String[] args) {
|
|
TouchLayout layout = new TouchLayout();
|
|
check(layout.size() == 18, "All 17 buttons and the movement stick must be editable");
|
|
check(layout.x(TouchLayout.LEFT) == 115 && layout.y(TouchLayout.FIRE_LEFT) == 239,
|
|
"Existing layouts must start with the original controls");
|
|
layout.move(TouchLayout.LEFT, -500, -500);
|
|
check(layout.x(TouchLayout.LEFT) >= 64 && layout.y(TouchLayout.LEFT) == 64,
|
|
"Dragging must keep the full stick inside the screen without a toolbar exclusion");
|
|
layout.move(TouchLayout.FIRE_LEFT, 10000, 10000);
|
|
check(layout.x(TouchLayout.FIRE_LEFT)+39 <= 960 && layout.y(TouchLayout.FIRE_LEFT)+39 <= 540,
|
|
"Dragging beyond the bottom/right edge must keep the stick reachable");
|
|
layout.move(0, 230, 320);
|
|
layout.move(5, 440, 390);
|
|
TouchLayout reopened = new TouchLayout();
|
|
for (int i = 0; i < layout.size(); i++) reopened.restore(i, layout.x(i), layout.y(i));
|
|
for (int i = 0; i < layout.size(); i++) {
|
|
check(layout.x(i) == reopened.x(i) && layout.y(i) == reopened.y(i),
|
|
"Saved coordinates must round-trip for control "+i);
|
|
}
|
|
float before = reopened.x(0);
|
|
reopened.restore(0, Float.NaN, 200);
|
|
reopened.restore(0, Float.POSITIVE_INFINITY, 200);
|
|
reopened.restore(0, -20, 200);
|
|
check(reopened.x(0) == before, "Invalid saved coordinates must not corrupt the layout");
|
|
reopened.move(0, Float.NaN, 200);
|
|
check(reopened.x(0) == before, "Invalid drag coordinates must not corrupt the layout");
|
|
// A saved top-row Pause button is valid even before it has been moved.
|
|
check(reopened.y(10) == 36, "Reopening must preserve unmoved top-row controls");
|
|
reopened.bounds(1200, 540);
|
|
reopened.move(0, 9999, 1);
|
|
check(reopened.x(0) == 1164 && reopened.y(0) == 36,
|
|
"Controls must reach the full widescreen edge and top edge");
|
|
TouchLayout wide = new TouchLayout();
|
|
wide.restore(0, reopened.savedX(0), reopened.savedY(0));
|
|
wide.bounds(1200, 540);
|
|
check(wide.x(0) == reopened.x(0), "Widescreen positions must survive reload");
|
|
// Duplicates retain their action and are independently placed and hidden.
|
|
int copy = wide.duplicate(4);
|
|
check(copy == 18 && wide.type(copy) == 4, "Fire copies must retain the fire action");
|
|
wide.setShown(4, false);
|
|
wide.move(copy, 300, 100);
|
|
check(!wide.shown(4) && wide.shown(copy), "Hiding an original must not hide its copy");
|
|
check(wide.duplicate(TouchLayout.LEFT) == -1, "There can only be one movement stick");
|
|
String exported = wide.exportConfiguration(2.25f);
|
|
TouchLayout.Configuration imported = TouchLayout.importConfiguration(exported);
|
|
imported.layout.bounds(1200, 540);
|
|
check(imported.sensitivity == 2.25f && imported.layout.size() == 19,
|
|
"Configuration must preserve sensitivity and duplicates");
|
|
for (int i = 0; i < wide.size(); i++) {
|
|
check(imported.layout.type(i) == wide.type(i) && imported.layout.shown(i) == wide.shown(i)
|
|
&& Math.abs(imported.layout.x(i)-wide.x(i)) < 0.001f
|
|
&& Math.abs(imported.layout.y(i)-wide.y(i)) < 0.001f,
|
|
"Export/import must preserve each control on widescreen");
|
|
}
|
|
check(wide.add(4) == 4 && wide.shown(4), "Add must restore a hidden button first");
|
|
wide.setShown(TouchLayout.LEFT, false);
|
|
check(wide.add(TouchLayout.LEFT) == TouchLayout.LEFT, "Hidden move stick must be restorable");
|
|
reject(exported.replace("version=2", "version=9"));
|
|
reject(exported.replace("control.18.type=4", "control.18.type=16"));
|
|
reject(exported.replace("control.18.x=240.0", "control.18.x=NaN"));
|
|
reject(exported.replace("count=19", "count=10000"));
|
|
reject(exported.replace("sensitivity=2.25", "sensitivity=Infinity"));
|
|
reject(exported.replace("control.0.visible=false", "control.0.visible=maybe")
|
|
.replace("control.0.visible=true", "control.0.visible=maybe"));
|
|
reject("not a layout");
|
|
wide.setSize(copy, 1.6f);
|
|
wide.rumbleEnabled = false; wide.gyroscopeEnabled = true;
|
|
TouchLayout.Configuration sized = TouchLayout.importConfiguration(wide.exportConfiguration(2.25f));
|
|
check(sized.layout.sizeScale(copy) == 1.6f && !sized.layout.rumbleEnabled && sized.layout.gyroscopeEnabled,
|
|
"Sizes, rumble and gyro settings must survive export/import");
|
|
int sizedCopy = wide.duplicate(copy);
|
|
check(wide.sizeScale(sizedCopy) == 1.6f, "Duplicates inherit their source size");
|
|
wide.move(copy, 99999, 99999);
|
|
wide.setSize(copy, 2f);
|
|
check(wide.x(copy)+wide.radius(copy) <= 1200 && wide.y(copy)+wide.radius(copy) <= 540,
|
|
"Growing a button at the edge must keep it reachable");
|
|
String sizedText = sized.layout.exportConfiguration(2.25f);
|
|
reject(sizedText.replace("control.18.size=1.6", "control.18.size=NaN"));
|
|
reject(sizedText.replace("control.18.size=1.6", "control.18.size=3.0"));
|
|
reject(sizedText.replace("rumble=false", "rumble=invalid"));
|
|
reject(sizedText.replace("gyroscope=true", "gyroscope=invalid"));
|
|
String legacy = sizedText.replace("version=2", "version=1").replaceAll("(?m)^.*\\.size=.*\\R", "")
|
|
.replaceAll("(?m)^(rumble|gyroscope)=.*\\R", "");
|
|
TouchLayout.Configuration old = TouchLayout.importConfiguration(legacy);
|
|
check(old.layout.sizeScale(copy) == 1 && old.layout.rumbleEnabled && !old.layout.gyroscopeEnabled,
|
|
"Legacy layouts must load with default sizes and gyro disabled");
|
|
wide.resetDefaults();
|
|
check(wide.size() == 18 && wide.shown(4) && wide.x(4) == 915*1200f/960,
|
|
"Reset restores defaults on the current display and removes all copies");
|
|
check(wide.sizeScale(4) == 1f && !wide.rumbleEnabled && wide.gyroscopeEnabled,
|
|
"Button reset must restore sizes without changing General settings");
|
|
while (wide.duplicate(4) >= 0) {}
|
|
check(wide.size() == TouchLayout.MAX_CONTROLS, "Duplicate count must be bounded");
|
|
System.out.println("Touch layout, visibility, duplication and import/export checks passed");
|
|
}
|
|
|
|
private static void reject(String text) {
|
|
try { TouchLayout.importConfiguration(text); }
|
|
catch (IllegalArgumentException expected) { return; }
|
|
throw new AssertionError("Invalid configuration was accepted");
|
|
}
|
|
}
|