Compare commits

...
Author SHA1 Message Date
Stefan MaierandClaude Opus 5.5 493713b0a6 Steam Frame: the refresh rates offered in the log; its rate and speed noted
The runtime offers only the rate SteamOS is set to. At 72 Hz the game holds
it with multiview (about 11.6 ms a frame); at 90 Hz it cannot, and the
frames SteamVR makes up showed as a doubled picture when the head turned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:47:56 +02:00
Stefan Maier bf356fc58a Merge branch 'fix/vr-shot-origin' into frame/controllers 2026-10-04 21:42:21 +02:00
Stefan MaierandClaude Opus 5.5 6a7b919522 VR: shots fly along the aim, not above it
A shot fired from the muzzle of the weapon in the hand was kept inside the
level by the game's test of the line from the eyes to it, which in VR runs
down through the player's own body: the test hit it and moved the shot up
towards the eyes, from where it flew parallel to the aim, a few centimetres
above the crosshair and the laser (measured: up to 7 cm at 3 m). In VR the
line is tested against the level alone (a hand through a wall still does
not fire through it); measured after: the impacts on their rays, on
average within a millimetre.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:42:07 +02:00
Stefan MaierandClaude Opus 5.5 c1305fa94f Steam Frame: its controllers as themselves, with the Xbox's layout
SteamVR offers the Frame's controllers by their own profile
(XR_VALVE_frame_controller_interaction); without bindings for it, it mapped
the Quest's onto them, and X and Y landed on the left one's D-pad. Now: the
right one's A, B, X, Y and menu button are the game's A, B, X, Y and Start
(the Xbox's layout), the left one's View is Back and its D-pad the D-pad,
the bumpers switch grenades and the flashlight. The VR options' page of
buttons shows the Frame's layout there. The runtime's extensions and the
controllers' profile are in the log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:28:13 +02:00
Stefan MaierandClaude Opus 5.5 0fc85d6c67 Steam Frame: the overlays' alpha from their colour on SteamVR
SteamVR draws a quad's texels by their alpha alone, and the game's menus,
HUD and text have none in their additive parts: on the Frame nothing showed.
On SteamVR (the runtime's name says) the copy into the quads' images takes
their alpha from their colour where it is higher; HALO_XR_QUAD_ALPHA
(colour, keep) overrides, for trying. The Frame's notes updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:17:43 +02:00
Stefan MaierandClaude Opus 5.5 c0a08f395e Steam Frame: the game's data in the SteamOS user's Documents when it is there
Lepton empties the app's own folders (and uninstalls the app) whenever it
starts again; Documents is the SteamOS user's and kept. When
/home/steamos/Documents/HaloCE holds the maps and is writable, the launcher
and the game take it as the data folder (maps, config.toml, saves, logs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 19:51:08 +02:00
10 changed files with 344 additions and 7 deletions

No files matched your search

+52 -6
View File
@@ -32,18 +32,64 @@ What differs is found at run time, not by a build of its own.
| # | Stage | State |
| --- | --- | --- |
| 1 | Base: the fixes above on the current main | in progress |
| 2 | The game's data where Lepton keeps it | |
| 3 | Menus, HUD and text visible (quad layers) | |
| 4 | The freeze at the cryo bay's calibration | |
| 5 | The Frame's controllers | |
| 6 | Frame rate, resolution | |
| 1 | Base: the fixes above on the current main | done |
| 2 | The game's data where Lepton keeps it | done: `/home/steamos/Documents/HaloCE` |
| 3 | Menus, HUD and text visible (quad layers) | done: their alpha from their colour |
| 4 | The freeze at the cryo bay's calibration | not seen again (2026-10-04) |
| 5 | The Frame's controllers | done: their own profile, the Xbox's layout |
| 6 | Frame rate, resolution | done: 72 Hz steady, multiview on |
| 7 | Multiplayer, voice, cross-play with the Quest | |
| 8 | Installing it, for others | |
## The game's data
Lepton uninstalls the app and empties its folders whenever it starts again.
The game's data lives in the SteamOS user's Documents instead: when
`/home/steamos/Documents/HaloCE` holds `maps/ui.map` and is writable, the
launcher and the game take it as their data folder (config.toml, saves and
debug.txt go there too). Once:
adb -s <frame-ip>:5555 shell mkdir -p /home/steamos/Documents/HaloCE/maps
adb -s <frame-ip>:5555 push assets/maps/. /home/steamos/Documents/HaloCE/maps/
adb -s <frame-ip>:5555 shell chmod -R 777 /home/steamos/Documents/HaloCE
After a Lepton restart only the APK is installed again. (Use the path under
/home, not /sdcard/Documents: Android 11's scoped storage stands between the
app and the latter.)
## The controllers
SteamVR offers the Frame's controllers as themselves
(`XR_VALVE_frame_controller_interaction`,
`/interaction_profiles/valve/frame_controller_valve`; Valve's "Steam Frame
Input" page). Without bindings for it, SteamVR maps the Quest's onto them,
and the left Touch controller's X and Y land on the D-pad. With them the
game gets the Xbox's layout, as the right controller has it: A jump, B
flashlight (held: switch grenades), X action and reload, Y switch weapon,
menu pause; the left one's View is Back (scores), its D-pad the D-pad, the
bumpers switch grenades (left) and the flashlight (right). The VR options'
page of buttons shows this layout on the Frame.
## Findings
- 2026-10-02: runs flat and in VR (GLES 3.2 by Mesa zink on Turnip, eyes
1728x1728, 72 Hz, XR_FB_display_refresh_rate absent). Quad layers
(menus, HUD, instructions) are not shown; the projection layer is. Froze
at the cryo bay's look calibration; its log was lost to Lepton's wipe.
- 2026-10-04: SteamVR draws a quad's texels by their alpha alone; the
game's menus and HUD have none in their additive parts, so nothing showed.
On SteamVR (by the runtime's name) the overlays' alpha now comes from their
colour (host_xr.c; `HALO_XR_QUAD_ALPHA=colour|keep` overrides). With it,
menus, HUD and the campaign ran well (the user: "lief 1a"); no freeze.
- 2026-10-04: no online games found: the public MQTT brokers had banned the
home connection for a while (too many connections from tests that day);
broker.emqx.io came back within minutes. Lepton's network is the Frame's
own (same IP), DNS works.
- 2026-10-04: the refresh rate is SteamOS's setting; the runtime offers
only that one (the game asks for the highest at or below
`vr.refresh_rate`, and logs the rates offered). At 72 Hz the game holds
72 fps (about 11.6 ms a frame with multiview, eyes 7.6 ms; without it
eyes about 9 ms). At 90 Hz it cannot (11.1 ms a frame), and SteamVR's
making up of the missed frames showed as a doubled picture when turning
the head. Keep the Frame at 72 Hz, or lower `vr.resolution_scale` (about
0.85) for 90.
@@ -35,6 +35,7 @@ import java.nio.channels.FileChannel;
*/
public class LauncherActivity extends Activity {
private static final int PICK_IMAGE = 1;
private static final String SHARED_DATA_ROOT = "/home/steamos/Documents/HaloCE";
private File dataRoot;
private TextView status;
@@ -46,6 +47,12 @@ public class LauncherActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dataRoot = getExternalFilesDir(null);
// the Steam Frame: Lepton empties the app's own folders whenever it
// starts again; the game's data lives in the SteamOS user's
// Documents instead, when it is there (port/android/STEAM_FRAME.md;
// the same choice as host_main.c's)
if (new File(SHARED_DATA_ROOT, "maps/ui.map").isFile() && new File(SHARED_DATA_ROOT).canWrite())
dataRoot = new File(SHARED_DATA_ROOT);
// created by the app, so that files pushed into it with adb stay
// readable (a directory adb creates there belongs to the shell user)
if (dataRoot != null)
+8
View File
@@ -90,6 +90,8 @@ int host_errno(void)
static char data_root[512];
static char save_root[512];
/* where a device that empties the app's folders keeps the game's data */
#define SHARED_DATA_ROOT "/home/steamos/Documents/HaloCE"
void host_android_path(int which, char *buffer, uint32_t size)
{
@@ -269,6 +271,12 @@ static void *game_main(void *unused)
external = SDL_GetAndroidExternalStoragePath();
if (!external)
host_fatal("Android storage is unavailable: %s", SDL_GetError());
/* the Steam Frame: Lepton empties the app's own folders whenever it
starts again; the game's data lives in the SteamOS user's Documents
instead, when it is there (port/android/STEAM_FRAME.md; the same choice
as LauncherActivity's) */
if (directory_has_maps(SHARED_DATA_ROOT) && access(SHARED_DATA_ROOT, W_OK) == 0)
external = SHARED_DATA_ROOT;
snprintf(data_root, sizeof(data_root), "%s", external);
snprintf(save_root, sizeof(save_root), "%s/save", external);
/* readable by adb (the shell user), for managing saves */
+157 -1
View File
@@ -102,6 +102,14 @@ static struct
XrAction button_b; /* B on the right controller, Y on the left */
XrAction menu;
XrAction thumbrest;
/* the Steam Frame's controllers' own (XR_VALVE_frame_controller_interaction) */
int has_frame_controller;
int frame_layout;
XrAction button_x;
XrAction button_y;
XrAction view;
XrAction bumper;
XrAction dpad[4];
XrAction haptic;
struct xr_hand_actions hands[HALO_XR_HAND_COUNT];
@@ -122,6 +130,11 @@ static struct
GLint rect_uniform;
GLint cut_disc_uniform;
GLint vignette_uniform;
GLint alpha_from_colour_uniform;
/* the runtime draws a quad's texels by their alpha alone, ignoring the
colour of those with none (SteamVR on the Steam Frame): the game's
overlays, whose additive parts have none, get alpha from their colour */
int quad_alpha_from_colour;
GLint tangents_uniform;
float vignette;
/* the same copy from a layer of a texture array (the eyes drawn at
@@ -223,6 +236,7 @@ static const char copy_fragment_shader[] =
"precision highp float;\n"
"uniform sampler2D source;\n"
"uniform float cut_disc;\n"
"uniform float alpha_from_colour; /* 1: as opaque as it is bright (a quad's, SteamVR) */\n"
"uniform float vignette; /* 0..1: the eye images darken from their edges */\n"
"uniform vec4 tangents; /* the eye's edges: left, right, down, up */\n"
"in vec2 coordinate;\n"
@@ -236,6 +250,8 @@ static const char copy_fragment_shader[] =
"{\n"
" vec4 texel = texture(source, coordinate);\n"
" color = vec4(to_linear(texel.rgb), texel.a);\n"
" if (alpha_from_colour > 0.5)\n"
" color.a = max(color.a, max(color.r, max(color.g, color.b)));\n"
" if (cut_disc > 0.5)\n"
" {\n"
" float inside = 1.0 - smoothstep(0.95, 1.0, length(disc));\n"
@@ -326,6 +342,7 @@ static int copy_initialize(void)
xr.rect_uniform = glGetUniformLocation(xr.program, "rect");
xr.cut_disc_uniform = glGetUniformLocation(xr.program, "cut_disc");
xr.vignette_uniform = glGetUniformLocation(xr.program, "vignette");
xr.alpha_from_colour_uniform = glGetUniformLocation(xr.program, "alpha_from_colour");
xr.tangents_uniform = glGetUniformLocation(xr.program, "tangents");
xr.copy_array_layer = -1;
{
@@ -580,6 +597,14 @@ static int actions_initialize(void)
xr.button_b = action_create("button_upper", "B / Y", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.menu = action_create("menu", "Menu", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.thumbrest = action_create("thumbrest", "Thumb rest", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.button_x = action_create("button_x", "X", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.button_y = action_create("button_y", "Y", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.view = action_create("view", "View", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.bumper = action_create("bumper", "Bumper", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.dpad[0] = action_create("dpad_up", "D-pad up", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.dpad[1] = action_create("dpad_down", "D-pad down", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.dpad[2] = action_create("dpad_left", "D-pad left", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.dpad[3] = action_create("dpad_right", "D-pad right", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.haptic = action_create("haptic", "Rumble", XR_ACTION_TYPE_VIBRATION_OUTPUT);
{
@@ -644,6 +669,44 @@ static int actions_initialize(void)
{ &xr.haptic, "/user/hand/right/output/haptic" },
};
/* the Steam Frame's controllers, as themselves: the right one has
A, B, X, Y and a menu button, the left one a D-pad and a View button
(port/android/STEAM_FRAME.md) */
static const struct binding frame_controller[] =
{
{ &xr.grip_pose, "/user/hand/left/input/grip/pose" },
{ &xr.grip_pose, "/user/hand/right/input/grip/pose" },
{ &xr.aim_pose, "/user/hand/left/input/aim/pose" },
{ &xr.aim_pose, "/user/hand/right/input/aim/pose" },
{ &xr.trigger, "/user/hand/left/input/trigger/value" },
{ &xr.trigger, "/user/hand/right/input/trigger/value" },
{ &xr.squeeze, "/user/hand/left/input/squeeze/value" },
{ &xr.squeeze, "/user/hand/right/input/squeeze/value" },
{ &xr.thumbstick, "/user/hand/left/input/thumbstick" },
{ &xr.thumbstick, "/user/hand/right/input/thumbstick" },
{ &xr.thumbstick_click, "/user/hand/left/input/thumbstick/click" },
{ &xr.thumbstick_click, "/user/hand/right/input/thumbstick/click" },
{ &xr.button_a, "/user/hand/right/input/a/click" },
{ &xr.button_b, "/user/hand/right/input/b/click" },
{ &xr.button_x, "/user/hand/right/input/x/click" },
{ &xr.button_y, "/user/hand/right/input/y/click" },
{ &xr.menu, "/user/hand/right/input/menu/click" },
{ &xr.view, "/user/hand/left/input/view/click" },
{ &xr.bumper, "/user/hand/left/input/bumper/click" },
{ &xr.bumper, "/user/hand/right/input/bumper/click" },
{ &xr.dpad[0], "/user/hand/left/input/dpad_up/click" },
{ &xr.dpad[1], "/user/hand/left/input/dpad_down/click" },
{ &xr.dpad[2], "/user/hand/left/input/dpad_left/click" },
{ &xr.dpad[3], "/user/hand/left/input/dpad_right/click" },
{ &xr.haptic, "/user/hand/left/output/haptic" },
{ &xr.haptic, "/user/hand/right/output/haptic" },
};
if (xr.has_frame_controller)
{
suggest("/interaction_profiles/valve/frame_controller_valve", frame_controller,
sizeof(frame_controller) / sizeof(frame_controller[0]));
}
suggest("/interaction_profiles/oculus/touch_controller", touch, sizeof(touch) / sizeof(touch[0]));
suggest("/interaction_profiles/valve/index_controller", index, sizeof(index) / sizeof(index[0]));
suggest("/interaction_profiles/khr/simple_controller", simple, sizeof(simple) / sizeof(simple[0]));
@@ -713,6 +776,30 @@ static void hands_read(struct halo_xr_frame *frame)
sync.activeActionSets = &active;
if (!XR_SUCCEEDED(xrSyncActions(xr.session, &sync)))
return;
{
/* (the controllers' profile the runtime chose, in the log as it
changes) */
static XrPath logged[HALO_XR_HAND_COUNT];
for (hand = 0; hand < HALO_XR_HAND_COUNT; hand++)
{
XrInteractionProfileState state = { XR_TYPE_INTERACTION_PROFILE_STATE };
if (XR_SUCCEEDED(xrGetCurrentInteractionProfile(xr.session, xr.hands[hand].path, &state)) &&
state.interactionProfile != logged[hand])
{
char name[XR_MAX_PATH_LENGTH] = "none";
uint32_t length = 0;
logged[hand] = state.interactionProfile;
if (state.interactionProfile != XR_NULL_PATH)
xrPathToString(xr.instance, state.interactionProfile, sizeof(name), &length, name);
if (hand == HALO_XR_HAND_RIGHT)
xr.frame_layout = strstr(name, "frame_controller") != NULL;
host_logf(HOST_LOG_INFO, "xr: the %s hand's controller: %s", hand ? "right" : "left", name);
}
}
}
for (hand = 0; hand < HALO_XR_HAND_COUNT; hand++)
{
struct halo_xr_hand *out = &frame->hands[hand];
@@ -757,7 +844,31 @@ static void hands_read(struct halo_xr_frame *frame)
(action_boolean(xr.menu, hand) ? HALO_XR_BUTTON_MENU : 0) |
(action_boolean(xr.thumbrest, hand) ? HALO_XR_BUTTON_THUMBREST : 0) |
(out->trigger > 0.5f ? HALO_XR_BUTTON_TRIGGER : 0) |
(out->squeeze > 0.5f ? HALO_XR_BUTTON_SQUEEZE : 0);
(out->squeeze > 0.5f ? HALO_XR_BUTTON_SQUEEZE : 0) |
(action_boolean(xr.view, hand) ? HALO_XR_BUTTON_VIEW : 0) |
(action_boolean(xr.bumper, hand) ? HALO_XR_BUTTON_BUMPER : 0) |
(action_boolean(xr.dpad[0], hand) ? HALO_XR_BUTTON_DPAD_UP : 0) |
(action_boolean(xr.dpad[1], hand) ? HALO_XR_BUTTON_DPAD_DOWN : 0) |
(action_boolean(xr.dpad[2], hand) ? HALO_XR_BUTTON_DPAD_LEFT : 0) |
(action_boolean(xr.dpad[3], hand) ? HALO_XR_BUTTON_DPAD_RIGHT : 0);
}
frame->controller_layout = xr.frame_layout ? HALO_XR_LAYOUT_FRAME : HALO_XR_LAYOUT_TOUCH;
if (xr.frame_layout)
{
/* (the Frame's right X, Y and menu button: the game's X, Y and Start,
which the Quest has on the left) */
struct halo_xr_hand *left = &frame->hands[HALO_XR_HAND_LEFT];
struct halo_xr_hand *right = &frame->hands[HALO_XR_HAND_RIGHT];
if (action_boolean(xr.button_x, HALO_XR_HAND_RIGHT))
left->buttons |= HALO_XR_BUTTON_A;
if (action_boolean(xr.button_y, HALO_XR_HAND_RIGHT))
left->buttons |= HALO_XR_BUTTON_B;
if (right->buttons & HALO_XR_BUTTON_MENU)
{
right->buttons &= ~HALO_XR_BUTTON_MENU;
left->buttons |= HALO_XR_BUTTON_MENU;
}
}
}
@@ -774,6 +885,13 @@ static void refresh_rate_choose(float wanted)
{
return;
}
{
char line[128] = "";
for (index = 0; index < count; index++)
snprintf(line + strlen(line), sizeof(line) - strlen(line), " %.0f", rates[index]);
host_logf(HOST_LOG_INFO, "xr: refresh rates offered:%s Hz", line);
}
/* the highest rate not above the one wanted (0: the highest) */
for (index = 0; index < count; index++)
{
@@ -928,6 +1046,23 @@ int host_xr_probe(float resolution_scale, int *eye_width, int *eye_height)
extensions[index].next = NULL;
}
xrEnumerateInstanceExtensionProperties(NULL, extension_count, &extension_count, extensions);
{
/* (the runtime's extensions, in the log: what a headset offers) */
char line[512] = "";
for (index = 0; index < extension_count; index++)
{
if (strlen(line) + strlen(extensions[index].extensionName) + 2 >= sizeof(line))
{
host_logf(HOST_LOG_INFO, "xr: extensions %s", line);
line[0] = 0;
}
strcat(line, " ");
strcat(line, extensions[index].extensionName);
}
if (line[0])
host_logf(HOST_LOG_INFO, "xr: extensions %s", line);
}
if (!has_extension(extensions, extension_count, XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME))
{
host_logf(HOST_LOG_ERROR, "xr: the runtime has no OpenGL ES support");
@@ -939,6 +1074,9 @@ int host_xr_probe(float resolution_scale, int *eye_width, int *eye_height)
xr.has_performance_settings = has_extension(extensions, extension_count, XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME);
if (xr.has_performance_settings)
enabled[enabled_count++] = XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME;
xr.has_frame_controller = has_extension(extensions, extension_count, "XR_VALVE_frame_controller_interaction");
if (xr.has_frame_controller)
enabled[enabled_count++] = "XR_VALVE_frame_controller_interaction";
xr.has_refresh_rate = has_extension(extensions, extension_count, XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME);
if (xr.has_refresh_rate)
enabled[enabled_count++] = XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME;
@@ -966,6 +1104,22 @@ int host_xr_probe(float resolution_scale, int *eye_width, int *eye_height)
}
xrGetSystemProperties(xr.instance, xr.system, &properties);
strncpy(xr.system_name, properties.systemName, sizeof(xr.system_name) - 1);
{
/* (SteamVR: the quads' alpha from their colour; HALO_XR_QUAD_ALPHA
"colour" or "keep" says otherwise, for trying) */
XrInstanceProperties instance = { XR_TYPE_INSTANCE_PROPERTIES };
const char *choice = getenv("HALO_XR_QUAD_ALPHA");
if (XR_SUCCEEDED(xrGetInstanceProperties(xr.instance, &instance)))
{
xr.quad_alpha_from_colour = strstr(instance.runtimeName, "SteamVR") != NULL;
host_logf(HOST_LOG_INFO, "xr: runtime %s", instance.runtimeName);
}
if (choice)
xr.quad_alpha_from_colour = !strcmp(choice, "colour");
if (xr.quad_alpha_from_colour)
host_logf(HOST_LOG_INFO, "xr: the overlays' alpha from their colour");
}
views[0].type = views[1].type = XR_TYPE_VIEW_CONFIGURATION_VIEW;
views[0].next = views[1].next = NULL;
@@ -1322,6 +1476,8 @@ int host_xr_copy_layer_region(int layer, uint32_t texture, int clear_alpha, floa
glUniform1i(xr.source_uniform, 0);
glUniform4f(xr.rect_uniform, left, top, right, bottom);
glUniform1f(xr.cut_disc_uniform, clear_alpha == HALO_XR_COPY_DISC ? 1.0f : 0.0f);
glUniform1f(xr.alpha_from_colour_uniform, xr.quad_alpha_from_colour && clear_alpha == HALO_XR_COPY_ALPHA &&
layer != HALO_XR_LAYER_EYE_LEFT && layer != HALO_XR_LAYER_EYE_RIGHT ? 1.0f : 0.0f);
if ((layer == HALO_XR_LAYER_EYE_LEFT || layer == HALO_XR_LAYER_EYE_RIGHT) && xr.vignette > 0.001f)
{
const XrFovf *fov = &xr.views[layer - HALO_XR_LAYER_EYE_LEFT].fov;
+18
View File
@@ -53,6 +53,23 @@ enum
HALO_XR_BUTTON_TRIGGER = 1 << 4, /* trigger past its click point */
HALO_XR_BUTTON_SQUEEZE = 1 << 5, /* grip past its click point */
HALO_XR_BUTTON_THUMBREST = 1 << 6, /* thumb resting (touch) */
/* the Steam Frame's controllers' (HALO_XR_LAYOUT_FRAME): the left one's
View button and D-pad, either one's bumper (their right one's X, Y and
menu button come as the left hand's A, B and MENU: the game's X, Y and
Start, as on the Quest) */
HALO_XR_BUTTON_VIEW = 1 << 7,
HALO_XR_BUTTON_BUMPER = 1 << 8,
HALO_XR_BUTTON_DPAD_UP = 1 << 9,
HALO_XR_BUTTON_DPAD_DOWN = 1 << 10,
HALO_XR_BUTTON_DPAD_LEFT = 1 << 11,
HALO_XR_BUTTON_DPAD_RIGHT = 1 << 12,
};
/* the controllers' layout, for what the game tells the player */
enum
{
HALO_XR_LAYOUT_TOUCH = 0, /* the Quest's: X, Y and menu on the left */
HALO_XR_LAYOUT_FRAME = 1, /* the Steam Frame's: A, B, X, Y and menu on the right, a D-pad on the left */
};
/* halo_xr_hand.flags, halo_xr_view.flags, halo_xr_frame.head_flags */
@@ -112,6 +129,7 @@ struct halo_xr_frame
struct halo_xr_pose head;
struct halo_xr_view views[HALO_XR_EYE_COUNT];
struct halo_xr_hand hands[HALO_XR_HAND_COUNT];
int controller_layout; /* HALO_XR_LAYOUT_* */
};
/* halo_xr_submit.overlay_mode */
+3
View File
@@ -201,6 +201,9 @@ driver's hands on it as drawn (world units, from the game's camera), or
NULL; the player's own hands turn it. And whether they hold it. */
void halo_vr_set_wheel(short local_player_index, const float left[3], const float right[3]);
int halo_vr_wheel_held(void);
/* the controllers' layout (HALO_XR_LAYOUT_*: 0 the Quest's, 1 the Steam
Frame's), for what the game tells the player */
int halo_vr_controller_layout(void);
/* whether a seated player's view stays in first person (director.c) */
int halo_vr_vehicle_first_person(void);
/* VR test aid (debug.vehicle_spawn): both sticks held in for a moment asks
+6
View File
@@ -502,6 +502,11 @@ int halo_vr_active(void)
return vr.active;
}
int halo_vr_controller_layout(void)
{
return vr.active ? vr.frame.controller_layout : HALO_XR_LAYOUT_TOUCH;
}
int halo_vr_eye_size(int *width, int *height)
{
const char *eye_width = getenv("HALO_VR_EYE_WIDTH");
@@ -2479,6 +2484,7 @@ int halo_vr_controllers(struct halo_xr_hand hands[HALO_XR_HAND_COUNT], int *menu
#else
int halo_vr_controller_layout(void) { return 0; }
int halo_vr_active(void) { return 0; }
int halo_vr_stereo_frame(void) { return 0; }
int halo_vr_stereo_player(short local_player_index, int cinematic) { (void)local_player_index; (void)cinematic; return 0; }
+10
View File
@@ -862,6 +862,12 @@ static void vr_gamepad_state(XINPUT_GAMEPAD *pad)
value = vr_stick(left->thumbstick[1]);
if (abs(value) > abs(pad->sThumbLY)) pad->sThumbLY = value;
if (left->buttons & HALO_XR_BUTTON_MENU) pad->wButtons |= XINPUT_GAMEPAD_START;
/* (the Steam Frame's controllers: the left one's View button and D-pad) */
if (left->buttons & HALO_XR_BUTTON_VIEW) pad->wButtons |= XINPUT_GAMEPAD_BACK;
if (left->buttons & HALO_XR_BUTTON_DPAD_UP) pad->wButtons |= XINPUT_GAMEPAD_DPAD_UP;
if (left->buttons & HALO_XR_BUTTON_DPAD_DOWN) pad->wButtons |= XINPUT_GAMEPAD_DPAD_DOWN;
if (left->buttons & HALO_XR_BUTTON_DPAD_LEFT) pad->wButtons |= XINPUT_GAMEPAD_DPAD_LEFT;
if (left->buttons & HALO_XR_BUTTON_DPAD_RIGHT) pad->wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT;
if (left->buttons & HALO_XR_BUTTON_THUMBSTICK) pad->wButtons |= XINPUT_GAMEPAD_LEFT_THUMB;
if (right->buttons & HALO_XR_BUTTON_THUMBSTICK) pad->wButtons |= XINPUT_GAMEPAD_RIGHT_THUMB;
/* (in the menus the triggers click the pointer: halo_vr_menu_pointer) */
@@ -897,6 +903,10 @@ static void vr_gamepad_state(XINPUT_GAMEPAD *pad)
merge_button(pad, XINPUT_GAMEPAD_B,
((right->buttons & HALO_XR_BUTTON_SQUEEZE) != 0 && !halo_vr_wheel_held()) || halo_vr_melee());
merge_button(pad, XINPUT_GAMEPAD_Y, (left->buttons & HALO_XR_BUTTON_B) != 0);
/* (the Steam Frame's bumpers: the left switches grenades, the right is
the flashlight) */
merge_button(pad, XINPUT_GAMEPAD_BLACK, (left->buttons & HALO_XR_BUTTON_BUMPER) != 0);
merge_button(pad, XINPUT_GAMEPAD_WHITE, (right->buttons & HALO_XR_BUTTON_BUMPER) != 0);
{
/* B: a tap is the flashlight, holding it switches grenades; the
game sees a short press of either */
+57
View File
@@ -6875,6 +6875,63 @@ static void ui_vr_controls_render(
ui_join_code_draw_text(left, (short)(left + 270), 86, "LEFT CONTROLLER", FALSE, 0, &title_color);
ui_join_code_draw_text(right, (short)(right + 270), 86, "RIGHT CONTROLLER", FALSE, 0, &title_color);
ui_join_code_draw_quad(320, 86, 321, 300, 0xFF3A6EA5);
if (halo_vr_controller_layout() == 1)
{
/* the Steam Frame's controllers: A, B, X, Y and a menu button on the
right one, a D-pad and a View button on the left one */
switch (ui_vr_options_globals.page)
{
case _vr_controls_on_foot:
ui_vr_controls_line(left, y, "Stick", "Move; click: crouch");
ui_vr_controls_line(left, y + 22, "Trigger", "Grenade: hold it,");
ui_vr_controls_line(left, y + 40, "", "let go as you throw");
ui_vr_controls_line(left, y + 62, "Grip", "Weapon in both hands");
ui_vr_controls_line(left, y + 84, "Bumper", "Switch grenades");
ui_vr_controls_line(left, y + 106, "View", "Scores");
ui_vr_controls_line(left, y + 128, "D-pad", "In the menus");
ui_vr_controls_line(right, y, "Stick", "Turn; click: zoom");
ui_vr_controls_line(right, y + 22, "Trigger", "Fire");
ui_vr_controls_line(right, y + 44, "Grip", "Melee (or punch)");
ui_vr_controls_line(right, y + 66, "A", "Jump");
ui_vr_controls_line(right, y + 88, "B", "Light; held: grenades");
ui_vr_controls_line(right, y + 110, "X", "Action, reload");
ui_vr_controls_line(right, y + 132, "Y", "Switch weapon");
ui_vr_controls_line(right, y + 154, "Menu", "Pause");
ui_vr_controls_note(320, "Motion tracker: turn the back of your left wrist towards you.");
ui_vr_controls_note(340, "Scope: zoom, and look through the disc above the weapon.");
ui_vr_controls_note(360, "Right bumper: the flashlight too.");
break;
case _vr_controls_vehicles:
ui_vr_controls_line(left, y, "Stick", "Drive");
ui_vr_controls_line(left, y + 22, "Trigger", "Second weapon");
ui_vr_controls_line(left, y + 40, "", "(Banshee, tank)");
ui_vr_controls_line(right, y, "Stick", "Steer; up and down:");
ui_vr_controls_line(right, y + 18, "", "climb, dive");
ui_vr_controls_line(right, y + 40, "Trigger", "Fire");
ui_vr_controls_line(right, y + 62, "X", "Get in, get out");
ui_vr_controls_line(right, y + 84, "B", "Flashlight");
ui_vr_controls_note(300, "Warthog: grip the wheel with one hand or both and turn it.");
ui_vr_controls_note(320, "Holding it, the right trigger accelerates, the left one brakes");
ui_vr_controls_note(338, "and reverses. Let go, and the right stick steers.");
ui_vr_controls_note(360, "At a gun or as a passenger: aim with the right controller.");
break;
default:
ui_vr_controls_line(left, y, "Stick", "Choose");
ui_vr_controls_line(left, y + 22, "D-pad", "Choose");
ui_vr_controls_line(left, y + 44, "Trigger", "Click where it points");
ui_vr_controls_line(right, y, "Stick", "Choose, scroll");
ui_vr_controls_line(right, y + 22, "A", "Accept");
ui_vr_controls_line(right, y + 44, "B", "Back");
ui_vr_controls_line(right, y + 66, "X, Y", "As the screen shows");
ui_vr_controls_line(right, y + 88, "Trigger", "Click where it points");
ui_vr_controls_note(300, "Point at the menus with either controller; its trigger clicks.");
ui_vr_controls_note(320, "On the keyboard, BACK and ENTER under it can be clicked too.");
ui_vr_controls_note(340, "Y on the main menu or the pause menu: these VR options.");
break;
}
ui_join_code_draw_text(44, 596, 412, "%b-button Back Left, right: the other pages", TRUE, 0, &tab_color);
return;
}
switch (ui_vr_options_globals.page)
{
case _vr_controls_on_foot:
+26
View File
@@ -231,6 +231,7 @@ symbols in this file:
#include "units/units.h"
#ifdef HALO_ANDROID
#include "halo_vr.h"
#include "physics/collisions.h"
#endif
/* port/linux/game/pal_tags.c's */
@@ -2205,6 +2206,10 @@ static void trigger_create_projectiles(
long target_object_index= NONE;
long projectile_definition_index;
short projectile_count;
#ifdef HALO_ANDROID
/* VR: fired from the muzzle of the weapon in the hand */
boolean vr_from_muzzle= FALSE;
#endif
if (!TEST_FLAG(trigger_definition->flags, _weapon_trigger_projectiles_cannot_be_aimed_bit) &&
unit &&
@@ -2282,6 +2287,7 @@ static void trigger_create_projectiles(
origin.x = vr_origin[0];
origin.y = vr_origin[1];
origin.z = vr_origin[2];
vr_from_muzzle = TRUE;
}
}
#endif
@@ -2383,6 +2389,26 @@ static void trigger_create_projectiles(
real_point3d camera_position;
unit_get_camera_position(owner_object_index, &camera_position);
#ifdef HALO_ANDROID
if (vr_from_muzzle)
{
/* VR: from the eyes down to the hand the line runs through
the player's own body, which the game's test hits, moving the
shot up towards the eyes (it then flies parallel to the aim,
above it): only the level stops it, a hand through a wall */
struct collision_result collision;
struct object_datum *projectile_object= object_get(projectile_object_index);
if (collision_test_line(FLAG(_collision_test_structure_bit) | FLAG(_collision_test_front_facing_surfaces_bit),
&camera_position, &projectile_object->object.position, owner_object_index, &collision) &&
collision.location.cluster_index!=NONE)
{
object_translate(projectile_object_index, &collision.point, &collision.location);
object_compute_node_matrices(projectile_object_index);
}
}
else
#endif
object_force_inside_bsp(projectile_object_index, &camera_position);
}
if (target_object_index!=NONE)