Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9304daf2e8 | ||
|
|
8e8713681d | ||
|
|
d7099f3e40 | ||
|
|
4ba36d7305 | ||
|
|
0a62ad4610 | ||
|
|
855f083117 | ||
|
|
6f3786de91 | ||
|
|
493713b0a6 | ||
|
|
bf356fc58a | ||
|
|
6a7b919522 | ||
|
|
c1305fa94f | ||
|
|
0fc85d6c67 | ||
|
|
c0a08f395e | ||
|
|
06cb70fb58 | ||
|
|
1709698053 | ||
|
|
42dc1f546f | ||
|
|
cb72dca40f | ||
|
|
79a6482c16 | ||
|
|
bbea2a2bff | ||
|
|
bfd92ec17d | ||
|
|
57cdbd8f80 | ||
|
|
c5c5a8eaeb | ||
|
|
a3ee36b5ef | ||
|
|
30a1e0f40e | ||
|
|
327cf96d6f | ||
|
|
852ee322cc | ||
|
|
f0d4fb2608 | ||
|
|
867855d388 | ||
|
|
4af570bc08 | ||
|
|
736c217347 | ||
|
|
7a1cf79396 | ||
|
|
30edb22f36 | ||
|
|
abc1c12f1e | ||
|
|
9e7fd75f57 | ||
|
|
6754403d68 | ||
|
|
aedf5ff9bc | ||
|
|
193cbf59c7 | ||
|
|
3f02527c68 | ||
|
|
fe160e9dbe | ||
|
|
d578f88bd0 | ||
|
|
d3fc997fdc | ||
|
|
a227872ccb | ||
|
|
812ffeea33 | ||
|
|
c05864b3fc | ||
|
|
ce77db8404 | ||
|
|
39350fb81f | ||
|
|
1a15a171ac | ||
|
|
2e3841ff7d | ||
|
|
46edf53657 | ||
|
|
c3adcfe5bf | ||
|
|
3304965653 | ||
|
|
c04765d7f4 | ||
|
|
933aac6175 | ||
|
|
809408c663 | ||
|
|
601a4c1f50 | ||
|
|
133d6a5dc1 | ||
|
|
ceecb039e3 | ||
|
|
12c97ffa85 | ||
|
|
23b542601f | ||
|
|
62b630a261 | ||
|
|
a34685574f | ||
|
|
0e6327cadf | ||
|
|
eb83e9d6d1 | ||
|
|
144063871a | ||
|
|
5f82df71f0 | ||
|
|
cfdd821b45 | ||
|
|
7165d40f90 | ||
|
|
a9bbce5f7a | ||
|
|
e8e0c2215a | ||
|
|
383355381c | ||
|
|
e94906100c | ||
|
|
57add5ed71 | ||
|
|
2b0103a0ef | ||
|
|
c9ee319ab5 | ||
|
|
65d81ad1ba | ||
|
|
770f8881e0 | ||
|
|
d356c0abab | ||
|
|
38ab192749 | ||
|
|
1536c30870 | ||
|
|
827eb0fc1d | ||
|
|
458b4642b6 | ||
|
|
a482a5f5bb | ||
|
|
2af2702bfa | ||
|
|
81f321f959 | ||
|
|
7a1c5e6239 | ||
|
|
f97ff9219f | ||
|
|
a9f3ce2647 | ||
|
|
85e79a067a | ||
|
|
1cebe431d1 | ||
|
|
ec2ab73acc | ||
|
|
effeda1b7c | ||
|
|
6299503191 | ||
|
|
2f5fa771f5 | ||
|
|
c5d86c8d8b | ||
|
|
7242151017 | ||
|
|
3aa88244fc | ||
|
|
1c8e651bb1 | ||
|
|
4b8f8dde8b | ||
|
|
920b1bfea9 | ||
|
|
09f869e44c | ||
|
|
a4e1bdb80d | ||
|
|
bc45338214 | ||
|
|
ceb0260389 | ||
|
|
a663d1b100 | ||
|
|
d8034d37fd | ||
|
|
f8a39df0cd | ||
|
|
bdfdef9bd4 | ||
|
|
6057146244 | ||
|
|
f02317ca25 | ||
|
|
94b17a7913 | ||
|
|
d7fa443718 | ||
|
|
30f362ff3d | ||
|
|
4414d7606d | ||
|
|
23d14319a7 | ||
|
|
824f79aa70 | ||
|
|
25a947faac | ||
|
|
ff5b91f120 | ||
|
|
f6c2322df9 | ||
|
|
39fbaefaac | ||
|
|
bf545439b4 | ||
|
|
201f69a35a | ||
|
|
c6397cb515 | ||
|
|
80d30410c8 | ||
|
|
d1c7243cb2 | ||
|
|
fd3d62f960 | ||
|
|
5ba6c13097 | ||
|
|
b438cea479 | ||
|
|
634b4197f9 | ||
|
|
ab96597321 | ||
|
|
f0dfb58c94 | ||
|
|
9f3e8c92de | ||
|
|
f2ba71d9af | ||
|
|
0182da8172 | ||
|
|
c55e4e2b9d | ||
|
|
7cd1724fe5 | ||
|
|
bd8ec3c798 | ||
|
|
4799980e8a | ||
|
|
f8937c6179 |
No files matched your search
@@ -242,10 +242,7 @@ jobs:
|
||||
mkdir release
|
||||
for build in artifacts/*/; do
|
||||
name="$(basename "$build")"
|
||||
# stored, not deflated: the updater in builds up to 57 waits for
|
||||
# an end of stream the game's zlib 1.1 does not always report,
|
||||
# and fails on a deflated entry that unpacked correctly
|
||||
(cd "$build" && zip -0 -qr "$GITHUB_WORKSPACE/release/$name.zip" .)
|
||||
(cd "$build" && zip -qr "$GITHUB_WORKSPACE/release/$name.zip" .)
|
||||
done
|
||||
ls -l release
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ xbox/
|
||||
|
||||
# Locally extracted proprietary game data (never commit)
|
||||
assets/
|
||||
# (but the high-res assets, drawn for the port: tools/hud_assets.py)
|
||||
!port/assets/
|
||||
original/
|
||||
|
||||
# CI build outputs (tools/ci_build.py)
|
||||
|
||||
@@ -168,7 +168,8 @@ the Xbox:
|
||||
- The HUD stays at the edges of the screen.
|
||||
- The menus, the loading bar and the screens after a game have 640
|
||||
columns, at the center of the screen.
|
||||
- Black bars and fades cover all of the screen.
|
||||
- Black bars and fades cover all of the screen, and so do the menus' dims
|
||||
and backgrounds (the pause menu's dim, dialogs, the menus' gradient).
|
||||
|
||||
## VR
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# Steam Frame
|
||||
|
||||
The same APK as for the Quest runs on the Steam Frame, in Lepton (SteamOS's
|
||||
Android, a Waydroid fork: Android 11, arm64), with SteamVR's OpenXR runtime.
|
||||
What differs is found at run time, not by a build of its own.
|
||||
|
||||
## The device
|
||||
|
||||
- Steam Settings > System > Enable Developer Mode; then start "Lepton
|
||||
Development" from the library. While the headset sleeps Lepton stops, and
|
||||
adb with it.
|
||||
- adb over the network: `adb connect <frame-ip>:5555`; over USB:
|
||||
`adb forward tcp:5555 tcp:5555` then `adb connect localhost:5555`.
|
||||
Always name the device (`adb -s <frame-ip>:5555 ...`): a Quest may be
|
||||
connected as well.
|
||||
- Install with `adb install -r --no-streaming`. It prints "Lepton post
|
||||
installation failed due to APP_PATH being unset", but installs. Start the
|
||||
game with `am start -n com.halo.decomp/.LauncherActivity`.
|
||||
- Lepton empties `/sdcard/Android/data` whenever it starts again: the game's
|
||||
data must live elsewhere (stage 2). `/sdcard/Documents` and
|
||||
`/sdcard/Download` are the SteamOS user's folders and are kept.
|
||||
|
||||
## What the port does for it
|
||||
|
||||
- SDL3's clipboard handler takes a clipboard service for granted, which
|
||||
Lepton has not: `tools/android_build.py` patches it to do without.
|
||||
- SteamVR lists an implicit OpenXR layer for Vulkan apps whose library an
|
||||
app cannot load: the APK carries a pass-through layer under its name
|
||||
(`port/android/xr_layer/fdm_injection_stub.c`).
|
||||
|
||||
## Stages
|
||||
|
||||
| # | Stage | State |
|
||||
| --- | --- | --- |
|
||||
| 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.
|
||||
@@ -39,6 +39,7 @@ import java.nio.channels.FileChannel;
|
||||
public class LauncherActivity extends Activity {
|
||||
private static final int PICK_IMAGE = 1;
|
||||
private static final int ASK_MICROPHONE = 2;
|
||||
private static final String SHARED_DATA_ROOT = "/home/steamos/Documents/HaloCE";
|
||||
|
||||
private File dataRoot;
|
||||
private TextView status;
|
||||
@@ -50,6 +51,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)
|
||||
|
||||
@@ -68,6 +68,9 @@ int host_sdl_gl_swap_window(unsigned int window);
|
||||
int host_sdl_poll_event(void *event);
|
||||
int host_sdl_set_clipboard_text(const char *text);
|
||||
void host_sdl_get_clipboard_text(char *buffer, unsigned int size);
|
||||
/* the keyboard's keys by name (the controls' bindings, xinput_sdl.c) */
|
||||
void host_sdl_scancode_name(int scancode, char *buffer, unsigned int size);
|
||||
int host_sdl_scancode_from_name(const char *name);
|
||||
int host_sdl_show_toast(const char *message, int duration, int gravity, int x, int y);
|
||||
int host_sdl_show_simple_message_box(unsigned int flags, const char *title, const char *message);
|
||||
int host_sdl_get_gamepads(unsigned int *ids, int capacity);
|
||||
|
||||
@@ -39,6 +39,19 @@ const char *SDL_GetError(void)
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const char *SDL_GetScancodeName(SDL_Scancode scancode)
|
||||
{
|
||||
static __thread char buffer[64];
|
||||
|
||||
host_sdl_scancode_name((int)scancode, buffer, sizeof(buffer));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
SDL_Scancode SDL_GetScancodeFromName(const char *name)
|
||||
{
|
||||
return (SDL_Scancode)host_sdl_scancode_from_name(name);
|
||||
}
|
||||
|
||||
Uint64 SDL_GetTicks(void)
|
||||
{
|
||||
return (Uint64)host_sdl_ticks();
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -98,6 +98,16 @@ void host_sdl_get_error(char *buffer, uint32_t size)
|
||||
SDL_strlcpy(buffer, SDL_GetError(), size);
|
||||
}
|
||||
|
||||
void host_sdl_scancode_name(int32_t scancode, char *buffer, uint32_t size)
|
||||
{
|
||||
SDL_strlcpy(buffer, SDL_GetScancodeName((SDL_Scancode)scancode), size);
|
||||
}
|
||||
|
||||
int32_t host_sdl_scancode_from_name(const char *name)
|
||||
{
|
||||
return (int32_t)SDL_GetScancodeFromName(name);
|
||||
}
|
||||
|
||||
int64_t host_sdl_ticks(void)
|
||||
{
|
||||
return (int64_t)SDL_GetTicks();
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
@@ -794,6 +912,12 @@ the eyes reads it. Shown only while it is. */
|
||||
|
||||
#define WRIST_IMAGE_SIZE 512
|
||||
#define CROSSHAIR_IMAGE_SIZE 256
|
||||
/* the names layer: HALO_XR_MAXIMUM_NAMES slots of 4:1, one over another */
|
||||
#define NAMES_IMAGE_WIDTH 512
|
||||
#define NAMES_IMAGE_HEIGHT (NAMES_IMAGE_WIDTH / 4 * HALO_XR_MAXIMUM_NAMES)
|
||||
/* a name's slot, as the game draws it: wider than its image's 4:1 (the
|
||||
game's 640x480 drawn square): the height a slot shows at, to its width */
|
||||
#define NAME_SLOT_ASPECT (30.0f / 160.0f)
|
||||
#define WRIST_BEHIND_PALM 0.05f
|
||||
#define WRIST_DOWN_HANDLE 0.02f
|
||||
#define WRIST_ABOVE_HAND 0.04f
|
||||
@@ -889,6 +1013,12 @@ int host_xr_probe(float resolution_scale, int *eye_width, int *eye_height)
|
||||
/* the loader and the runtime keep the activity */
|
||||
activity = (*environment)->NewGlobalRef(environment, activity);
|
||||
|
||||
/* (The Steam Frame lists an implicit API layer of its own for Vulkan
|
||||
apps, XR_APILAYER_VALVE_fdm_injection, whose library an app cannot load
|
||||
from its namespace, failing xrCreateInstance; the Android loader reads
|
||||
no variable to turn it off, so the app carries a pass-through layer
|
||||
under that library's name: port/android/xr_layer/fdm_injection_stub.c) */
|
||||
|
||||
if (!XR_SUCCEEDED(xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR",
|
||||
(PFN_xrVoidFunction *)&initialize_loader)) || !initialize_loader)
|
||||
{
|
||||
@@ -916,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");
|
||||
@@ -927,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;
|
||||
@@ -954,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;
|
||||
@@ -1032,8 +1198,15 @@ int host_xr_initialize(struct halo_xr_info *info, float wanted_refresh_rate)
|
||||
{
|
||||
int size = layer == HALO_XR_LAYER_WRIST || layer == HALO_XR_LAYER_SCOPE ? WRIST_IMAGE_SIZE :
|
||||
layer == HALO_XR_LAYER_CROSSHAIR ? CROSSHAIR_IMAGE_SIZE : 0;
|
||||
int width = size ? size : xr.eye_width;
|
||||
int height = size ? size : xr.eye_height;
|
||||
|
||||
if (!swapchain_create(&xr.swapchains[layer], size ? size : xr.eye_width, size ? size : xr.eye_height))
|
||||
if (layer == HALO_XR_LAYER_NAMES)
|
||||
{
|
||||
width = NAMES_IMAGE_WIDTH;
|
||||
height = NAMES_IMAGE_HEIGHT;
|
||||
}
|
||||
if (!swapchain_create(&xr.swapchains[layer], width, height))
|
||||
return 0;
|
||||
}
|
||||
if (!copy_initialize() || !actions_initialize())
|
||||
@@ -1303,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;
|
||||
@@ -1354,7 +1529,8 @@ int host_xr_end_frame(const struct halo_xr_submit *submit)
|
||||
XrCompositionLayerQuad wrist = { XR_TYPE_COMPOSITION_LAYER_QUAD };
|
||||
XrCompositionLayerQuad scope = { XR_TYPE_COMPOSITION_LAYER_QUAD };
|
||||
XrCompositionLayerQuad crosshair = { XR_TYPE_COMPOSITION_LAYER_QUAD };
|
||||
const XrCompositionLayerBaseHeader *layers[5];
|
||||
XrCompositionLayerQuad names[HALO_XR_MAXIMUM_NAMES];
|
||||
const XrCompositionLayerBaseHeader *layers[5 + HALO_XR_MAXIMUM_NAMES];
|
||||
uint32_t layer_count = 0;
|
||||
int eye;
|
||||
|
||||
@@ -1458,6 +1634,34 @@ int host_xr_end_frame(const struct halo_xr_submit *submit)
|
||||
layers[layer_count++] = (const XrCompositionLayerBaseHeader *)&crosshair;
|
||||
}
|
||||
|
||||
if (xr.frame_state.shouldRender && submit->name_count > 0 && xr.swapchains[HALO_XR_LAYER_NAMES].filled)
|
||||
{
|
||||
struct xr_swapchain *swapchain = &xr.swapchains[HALO_XR_LAYER_NAMES];
|
||||
int slot_height = swapchain->height / HALO_XR_MAXIMUM_NAMES;
|
||||
int name;
|
||||
|
||||
for (name = 0; name < submit->name_count && name < HALO_XR_MAXIMUM_NAMES; name++)
|
||||
{
|
||||
XrCompositionLayerQuad *quad_name = &names[name];
|
||||
|
||||
memset(quad_name, 0, sizeof(*quad_name));
|
||||
quad_name->type = XR_TYPE_COMPOSITION_LAYER_QUAD;
|
||||
quad_name->layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
||||
quad_name->space = xr.local_space;
|
||||
quad_name->eyeVisibility = XR_EYE_VISIBILITY_BOTH;
|
||||
quad_name->subImage.swapchain = swapchain->handle;
|
||||
/* (slot 0 at the image's top: GL's rows run bottom up) */
|
||||
quad_name->subImage.imageRect.offset.x = 0;
|
||||
quad_name->subImage.imageRect.offset.y = swapchain->height - (name + 1) * slot_height;
|
||||
quad_name->subImage.imageRect.extent.width = swapchain->width;
|
||||
quad_name->subImage.imageRect.extent.height = slot_height;
|
||||
quad_name->size.width = submit->name_widths[name];
|
||||
quad_name->size.height = submit->name_widths[name] * NAME_SLOT_ASPECT;
|
||||
pose_to_xr(&quad_name->pose, &submit->name_poses[name]);
|
||||
layers[layer_count++] = (const XrCompositionLayerBaseHeader *)quad_name;
|
||||
}
|
||||
}
|
||||
|
||||
end.displayTime = xr.frame_state.predictedDisplayTime;
|
||||
end.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
|
||||
end.layerCount = layer_count;
|
||||
|
||||
@@ -38,6 +38,8 @@ host_sdl_gamepad_axis
|
||||
host_sdl_gamepad_button
|
||||
host_sdl_gamepad_type
|
||||
host_sdl_rumble_gamepad
|
||||
host_sdl_scancode_name
|
||||
host_sdl_scancode_from_name
|
||||
host_sdl_open_audio_stream
|
||||
host_sdl_put_audio_stream_data
|
||||
host_sdl_resume_audio_stream_device
|
||||
|
||||
@@ -27,7 +27,10 @@ enum
|
||||
HALO_XR_LAYER_WRIST = 3, /* the motion tracker, on the left wrist */
|
||||
HALO_XR_LAYER_SCOPE = 4, /* the zoomed view, a disc over the weapon */
|
||||
HALO_XR_LAYER_CROSSHAIR = 5, /* the weapon's crosshair, where it aims */
|
||||
HALO_XR_LAYER_COUNT = 6,
|
||||
HALO_XR_LAYER_NAMES = 6, /* players' names, each a quad over a head */
|
||||
HALO_XR_LAYER_COUNT = 7,
|
||||
/* the names layer's slots, one a name, stacked from its top */
|
||||
HALO_XR_MAXIMUM_NAMES = 8,
|
||||
|
||||
/* host_xr_copy_layer's clear_alpha: the source's alpha kept, made
|
||||
opaque, or cut to a disc (premultiplied) */
|
||||
@@ -50,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 */
|
||||
@@ -109,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 */
|
||||
@@ -147,6 +168,12 @@ struct halo_xr_submit
|
||||
int crosshair;
|
||||
float crosshair_size;
|
||||
struct halo_xr_pose crosshair_pose;
|
||||
/* the names layer was filled: its first name_count slots, each shown
|
||||
this wide (metres, a slot's height following from its shape), here in
|
||||
LOCAL space, facing the eyes */
|
||||
int name_count;
|
||||
float name_widths[HALO_XR_MAXIMUM_NAMES];
|
||||
struct halo_xr_pose name_poses[HALO_XR_MAXIMUM_NAMES];
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
FDM_INJECTION_STUB.C
|
||||
|
||||
An OpenXR API layer that does nothing, named as the Steam Frame's
|
||||
XR_APILAYER_VALVE_fdm_injection names its library
|
||||
(libVkLayer_VALVE_fdm_injection.so). The Frame lists that layer as implicit
|
||||
for every OpenXR app (/vendor/etc/openxr/1/api_layers/implicit.d), but an
|
||||
Android app (in Lepton) cannot load the system's library from its own
|
||||
namespace, and the Khronos loader then fails xrCreateInstance altogether;
|
||||
nor does the Android loader read the variable that would turn the layer
|
||||
off. With this one in the app's libraries the loader finds a layer of that
|
||||
name, which hands every call on to the next. (The real layer adds fragment
|
||||
density maps to Vulkan apps; this game draws with OpenGL ES.) On a Quest no
|
||||
manifest names it and it is never loaded.
|
||||
*/
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
#include <openxr/openxr_loader_negotiation.h>
|
||||
#include <stddef.h>
|
||||
|
||||
static PFN_xrGetInstanceProcAddr next_get_instance_proc_addr;
|
||||
|
||||
static XRAPI_ATTR XrResult XRAPI_CALL layer_get_instance_proc_addr(XrInstance instance, const char *name,
|
||||
PFN_xrVoidFunction *function)
|
||||
{
|
||||
if (!next_get_instance_proc_addr)
|
||||
return XR_ERROR_HANDLE_INVALID;
|
||||
return next_get_instance_proc_addr(instance, name, function);
|
||||
}
|
||||
|
||||
static XRAPI_ATTR XrResult XRAPI_CALL layer_create_api_layer_instance(const XrInstanceCreateInfo *info,
|
||||
const XrApiLayerCreateInfo *layer_info, XrInstance *instance)
|
||||
{
|
||||
XrApiLayerCreateInfo next_layer_info;
|
||||
|
||||
if (!layer_info || !layer_info->nextInfo)
|
||||
return XR_ERROR_INITIALIZATION_FAILED;
|
||||
next_get_instance_proc_addr = layer_info->nextInfo->nextGetInstanceProcAddr;
|
||||
next_layer_info = *layer_info;
|
||||
next_layer_info.nextInfo = layer_info->nextInfo->next;
|
||||
return layer_info->nextInfo->nextCreateApiLayerInstance(info, &next_layer_info, instance);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
XRAPI_ATTR XrResult XRAPI_CALL xrNegotiateLoaderApiLayerInterface(const XrNegotiateLoaderInfo *loader_info,
|
||||
const char *layer_name, XrNegotiateApiLayerRequest *request)
|
||||
{
|
||||
(void)layer_name;
|
||||
if (!loader_info || !request || loader_info->structType != XR_LOADER_INTERFACE_STRUCT_LOADER_INFO ||
|
||||
request->structType != XR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST ||
|
||||
loader_info->minInterfaceVersion > XR_CURRENT_LOADER_API_LAYER_VERSION ||
|
||||
loader_info->maxInterfaceVersion < XR_CURRENT_LOADER_API_LAYER_VERSION)
|
||||
{
|
||||
return XR_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
request->layerInterfaceVersion = XR_CURRENT_LOADER_API_LAYER_VERSION;
|
||||
request->layerApiVersion = loader_info->maxApiVersion < XR_CURRENT_API_VERSION ? loader_info->maxApiVersion :
|
||||
XR_CURRENT_API_VERSION;
|
||||
request->getInstanceProcAddr = layer_get_instance_proc_addr;
|
||||
request->createApiLayerInstance = layer_create_api_layer_instance;
|
||||
return XR_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
Newtown
|
||||
Copyright (c) Roger White, 1994. Released by its author into the public domain.
|
||||
|
||||
Newtown-Regular.ttf is the TrueType file of Newtown, as Roger White
|
||||
distributed it (NEWTOW__.TTF, version 1.00, SHA-256
|
||||
376d82c5b660c059c182d848d0a96da9dbe7277d7c8820f6e9a2c99b1a256c65), here
|
||||
renamed. OpenCE (OpenCE-Regular.ttf) is made from it by tools/title_font.py.
|
||||
|
||||
The file's copyright notice ("All Rights Reserved - see .txt file for terms
|
||||
for distribution and use") and the readme it was first distributed with
|
||||
(personal use free, commercial use for a fee) date from 1994. Its author
|
||||
later released all of his fonts into the public domain. His website,
|
||||
rogersfonts.org.uk, said so from 2008 until it closed:
|
||||
|
||||
"Despite what is said in the readme files associated with the fonts all
|
||||
fonts on this site are now public domain, whether used for personal use
|
||||
or for commercial purposes. All that I ask is that the source is
|
||||
acknowledged if you are using any for commercial purposes."
|
||||
|
||||
The statement is in the Internet Archive's copies of the site's home page,
|
||||
the first of them from 24 July 2008:
|
||||
|
||||
https://web.archive.org/web/20080724224405/http://www.rogersfonts.org.uk/body_index.html
|
||||
https://web.archive.org/web/20110724011202/http://www.rogersfonts.org.uk/body_index.html
|
||||
|
||||
and Newtown was one of the site's fonts (its samples are archived from 2004,
|
||||
https://web.archive.org/web/20040909032418/http://www.rogersfonts.org.uk/assets/images/Newtown.gif).
|
||||
The site says its fonts were made from the author's own hand-drawn artwork,
|
||||
scanned and cleaned up.
|
||||
|
||||
As he asked, the source is acknowledged: OpenCE is based on Newtown by
|
||||
Roger White.
|
||||
@@ -0,0 +1,95 @@
|
||||
Copyright 2026 The OpenCE Project Authors, for its changes to Newtown's spacing.
|
||||
OpenCE is based on Newtown, by Roger White (1994), which its author released
|
||||
into the public domain (Newtown-LICENSE.txt).
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2021 The Overpass Project Authors (https://github.com/RedHatOfficial/Overpass)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Fonts
|
||||
|
||||
The fonts the game's text is drawn with, at the display's resolution, in
|
||||
place of the bitmap fonts of the maps (`port/linux/src/text_hires.c`).
|
||||
`fonts.json` says which font draws each of the maps' font tags.
|
||||
|
||||
| File | Draws | From |
|
||||
| --- | --- | --- |
|
||||
| `Overpass-900.ttf` | `ui\large_ui`, `ui\interstate` | Overpass, weight 900 (Black) |
|
||||
| `Overpass-750.ttf` | `ui\small_ui` | Overpass, weight 750 |
|
||||
| `OpenCE-Regular.ttf` | the menus' titles, as pictures (`port/assets/titles`) | OpenCE: Newtown, respaced for this project (`tools/title_font.py`) |
|
||||
| `Newtown-Regular.ttf` | (OpenCE's source) | Newtown by Roger White, unmodified |
|
||||
|
||||
The maps' fonts are Interstate (Tobias Frere-Jones, The Font Bureau), a
|
||||
commercial typeface that cannot be shipped. Overpass (Red Hat, Delve
|
||||
Withrington) follows the same Highway Gothic road-sign alphabet and is
|
||||
under the SIL Open Font License 1.1 (`Overpass-OFL.txt`), without a
|
||||
Reserved Font Name. Matched against the maps' glyphs, its weights 900 and 750 overlap
|
||||
them as much as Interstate Bold does, at the same letter widths.
|
||||
|
||||
The menus' titles are pictures of text in the maps, set in another
|
||||
commercial typeface. OpenCE (Open Community Edition) stands in for it:
|
||||
Newtown, a typeface in the same style by Roger White (1994), which its author
|
||||
released into the public domain (`Newtown-LICENSE.txt` quotes his statement
|
||||
and where it is archived), respaced for this project. Only its spacing is
|
||||
changed, its outlines are Newtown's: each letter's side spaces and the
|
||||
kerning pairs are measured from where the letters sit in the maps' titles.
|
||||
OpenCE is under the SIL Open Font License 1.1 (`OpenCE-OFL.txt`), without a
|
||||
Reserved Font Name. It is not embedded: `tools/title_assets.py` draws the
|
||||
titles with it, and the builds embed those pictures. To measure its spacing
|
||||
again and rebuild it:
|
||||
|
||||
python3 tools/title_font.py --measure # paste the values into tools/title_font.py
|
||||
python3 tools/title_font.py
|
||||
python3 tools/title_assets.py --map assets/maps/ui.map
|
||||
|
||||
The Overpass files are static instances of Google Fonts' variable Overpass
|
||||
(`ofl/overpass/Overpass[wght].ttf`, google/fonts commit
|
||||
9710da1eacb3be272583c3224dcb70f9da6eadbb), made with fontTools:
|
||||
|
||||
python3 -m fontTools.varLib.instancer 'Overpass[wght].ttf' wght=900 -o Overpass-900.ttf
|
||||
python3 -m fontTools.varLib.instancer 'Overpass[wght].ttf' wght=750 -o Overpass-750.ttf
|
||||
|
||||
The builds embed the Overpass files (`tools/embed_assets.py`), and the
|
||||
release packages carry `Overpass-OFL.txt`, as the license asks.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"comment": "Which font draws each of the maps' font tags (port/linux/src/text_hires.c).",
|
||||
"fonts": [
|
||||
{"tag": "ui\\large_ui", "file": "Overpass-900.ttf"},
|
||||
{"tag": "ui\\interstate", "file": "Overpass-900.ttf"},
|
||||
{"tag": "ui\\small_ui", "file": "Overpass-750.ttf"}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 369 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 701 B |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 846 B |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 199 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 502 B |
|
After Width: | Height: | Size: 504 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 275 B |
|
After Width: | Height: | Size: 268 B |
|
After Width: | Height: | Size: 361 B |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- ammo meter fill alpha: slanted bar whose top sweeps up into a tab on the right -->
|
||||
<path d="M7.56,32 H209.2 C289.7,32 340.8,7.2 353.7,0 H406.9 A8.3,8.3 0 0 1 414.1,11.4 L393.2,56 H5 A5,5 0 0 1 0,51 V48.1 Z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 323 B |
@@ -0,0 +1,66 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="128" viewBox="0 0 512 128">
|
||||
<!-- 3 rows x 20 slanted bullet rounds (alpha mask) -->
|
||||
<defs>
|
||||
<path id="b" d="M51.34,0 H51.79 L54.02,6 L42.83,30 H32.47 L43.71,5.89 Z" fill="#fff"/>
|
||||
</defs>
|
||||
<use href="#b"/>
|
||||
<use href="#b" x="20.63" y="0"/>
|
||||
<use href="#b" x="41.26" y="0"/>
|
||||
<use href="#b" x="61.89" y="0"/>
|
||||
<use href="#b" x="82.52" y="0"/>
|
||||
<use href="#b" x="103.16" y="0"/>
|
||||
<use href="#b" x="123.79" y="0"/>
|
||||
<use href="#b" x="144.42" y="0"/>
|
||||
<use href="#b" x="165.05" y="0"/>
|
||||
<use href="#b" x="185.68" y="0"/>
|
||||
<use href="#b" x="206.31" y="0"/>
|
||||
<use href="#b" x="226.94" y="0"/>
|
||||
<use href="#b" x="247.57" y="0"/>
|
||||
<use href="#b" x="268.20" y="0"/>
|
||||
<use href="#b" x="288.83" y="0"/>
|
||||
<use href="#b" x="309.47" y="0"/>
|
||||
<use href="#b" x="330.10" y="0"/>
|
||||
<use href="#b" x="350.73" y="0"/>
|
||||
<use href="#b" x="371.36" y="0"/>
|
||||
<use href="#b" x="391.99" y="0"/>
|
||||
<use href="#b" x="-15.85" y="34"/>
|
||||
<use href="#b" x="4.78" y="34"/>
|
||||
<use href="#b" x="25.41" y="34"/>
|
||||
<use href="#b" x="46.04" y="34"/>
|
||||
<use href="#b" x="66.67" y="34"/>
|
||||
<use href="#b" x="87.30" y="34"/>
|
||||
<use href="#b" x="107.93" y="34"/>
|
||||
<use href="#b" x="128.56" y="34"/>
|
||||
<use href="#b" x="149.19" y="34"/>
|
||||
<use href="#b" x="169.82" y="34"/>
|
||||
<use href="#b" x="190.46" y="34"/>
|
||||
<use href="#b" x="211.09" y="34"/>
|
||||
<use href="#b" x="231.72" y="34"/>
|
||||
<use href="#b" x="252.35" y="34"/>
|
||||
<use href="#b" x="272.98" y="34"/>
|
||||
<use href="#b" x="293.61" y="34"/>
|
||||
<use href="#b" x="314.24" y="34"/>
|
||||
<use href="#b" x="334.87" y="34"/>
|
||||
<use href="#b" x="355.50" y="34"/>
|
||||
<use href="#b" x="376.14" y="34"/>
|
||||
<use href="#b" x="-31.71" y="68"/>
|
||||
<use href="#b" x="-11.08" y="68"/>
|
||||
<use href="#b" x="9.55" y="68"/>
|
||||
<use href="#b" x="30.19" y="68"/>
|
||||
<use href="#b" x="50.82" y="68"/>
|
||||
<use href="#b" x="71.45" y="68"/>
|
||||
<use href="#b" x="92.08" y="68"/>
|
||||
<use href="#b" x="112.71" y="68"/>
|
||||
<use href="#b" x="133.34" y="68"/>
|
||||
<use href="#b" x="153.97" y="68"/>
|
||||
<use href="#b" x="174.60" y="68"/>
|
||||
<use href="#b" x="195.23" y="68"/>
|
||||
<use href="#b" x="215.86" y="68"/>
|
||||
<use href="#b" x="236.49" y="68"/>
|
||||
<use href="#b" x="257.13" y="68"/>
|
||||
<use href="#b" x="277.76" y="68"/>
|
||||
<use href="#b" x="298.39" y="68"/>
|
||||
<use href="#b" x="319.02" y="68"/>
|
||||
<use href="#b" x="339.65" y="68"/>
|
||||
<use href="#b" x="360.28" y="68"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- rocket ammo: tail fin + body, grey translucent fill with white outline; pair repeated -->
|
||||
<defs>
|
||||
<g id="rocket" fill="#a6a6a6" fill-opacity=".651" stroke="#fff" stroke-width="4.1" stroke-miterlimit="10">
|
||||
<path d="M30.7,1.35 H51.4 L58.4,22 H90.6 L79.5,46 H48.1 L24.5,62.2 H3.5 L18.4,46.2 H11.6 L22.65,22 H29.35 Z"/>
|
||||
<path d="M88.5,48.4 L124.3,52 L191.9,46.1 L210.8,32.8 L202.7,21.9 L142.6,16 L101.8,19.8 Z"/>
|
||||
</g>
|
||||
</defs>
|
||||
<use href="#rocket"/>
|
||||
<use href="#rocket" x="214"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 644 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- slanted meter bar, grey translucent fill fading slightly darker downwards (rgb = alpha) -->
|
||||
<defs>
|
||||
<linearGradient id="g" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="0" y2="44">
|
||||
<stop offset="0" stop-color="#c7c7c7" stop-opacity=".781"/>
|
||||
<stop offset="1" stop-color="#b4b4b4" stop-opacity=".705"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M20,0 H412 L392,44 H0 Z" fill="url(#g)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 506 B |
@@ -0,0 +1,26 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- 20 slanted blade ticks (alpha mask) -->
|
||||
<defs>
|
||||
<path id="t" d="M28.03,0 H40.39 L17.73,48.6 L0.58,59.39 L1.03,57.87 Z" fill="#fff"/>
|
||||
</defs>
|
||||
<use href="#t" x="0.000"/>
|
||||
<use href="#t" x="20.704"/>
|
||||
<use href="#t" x="41.408"/>
|
||||
<use href="#t" x="62.112"/>
|
||||
<use href="#t" x="82.816"/>
|
||||
<use href="#t" x="103.520"/>
|
||||
<use href="#t" x="124.224"/>
|
||||
<use href="#t" x="144.928"/>
|
||||
<use href="#t" x="165.632"/>
|
||||
<use href="#t" x="186.336"/>
|
||||
<use href="#t" x="207.040"/>
|
||||
<use href="#t" x="227.744"/>
|
||||
<use href="#t" x="248.448"/>
|
||||
<use href="#t" x="269.152"/>
|
||||
<use href="#t" x="289.856"/>
|
||||
<use href="#t" x="310.560"/>
|
||||
<use href="#t" x="331.264"/>
|
||||
<use href="#t" x="351.968"/>
|
||||
<use href="#t" x="372.672"/>
|
||||
<use href="#t" x="393.376"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 905 B |
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- 12 tilted shotgun shells (alpha mask): tapered body with rim + separate tip -->
|
||||
<defs>
|
||||
<path id="s" transform="translate(15.87,38.77) rotate(25)" fill="#fff"
|
||||
d="M-8.04,-18.2 H8.04 V4.82 L6.23,12.94 H9.78 V17.06 H-9.78 V12.94 H-6.23 L-8.04,4.82 Z
|
||||
M-8.08,-21.95 H8.08 L4.93,-40.72 H-4.93 Z"/>
|
||||
</defs>
|
||||
<use href="#s" x="0.000"/>
|
||||
<use href="#s" x="35.273"/>
|
||||
<use href="#s" x="70.546"/>
|
||||
<use href="#s" x="105.819"/>
|
||||
<use href="#s" x="141.092"/>
|
||||
<use href="#s" x="176.365"/>
|
||||
<use href="#s" x="211.638"/>
|
||||
<use href="#s" x="246.911"/>
|
||||
<use href="#s" x="282.184"/>
|
||||
<use href="#s" x="317.457"/>
|
||||
<use href="#s" x="352.730"/>
|
||||
<use href="#s" x="388.003"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 841 B |
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="128" viewBox="0 0 512 128">
|
||||
<!-- 2x2 rifle rounds (alpha mask): rimmed case with rounded shoulder + ogive bullet -->
|
||||
<defs>
|
||||
<path id="r" fill="#fff" d="M20,0 H28 V5.5 H101.8 V6.4 H108.5 A7.5,7.5 0 0 1 116,13.9 V22.1 A7.5,7.5 0 0 1 108.5,29.6 H101.8 V30.5 H28 V36 H20 Z
|
||||
M120,8 H135 C163.6,9 194.5,13.65 212.3,18 C194.5,22.35 163.6,27 135,28 H120 Z"/>
|
||||
</defs>
|
||||
<use href="#r"/>
|
||||
<use href="#r" x="208"/>
|
||||
<use href="#r" x="-20" y="44"/>
|
||||
<use href="#r" x="188" y="44"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 598 B |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- 12 two-part slanted ammo ticks (alpha mask); lower part has a flared foot -->
|
||||
<defs>
|
||||
<path id="t" fill="#fff" d="M33.4,0 h19 L33.76,40 h-19 Z
|
||||
M12.89,44 h19 L24.43,60 h2.7 L25.03,64.5 H0.66 L2.75,60 h2.68 Z"/>
|
||||
</defs>
|
||||
<use href="#t" x="0.000"/>
|
||||
<use href="#t" x="34.973"/>
|
||||
<use href="#t" x="69.946"/>
|
||||
<use href="#t" x="104.919"/>
|
||||
<use href="#t" x="139.892"/>
|
||||
<use href="#t" x="174.865"/>
|
||||
<use href="#t" x="209.838"/>
|
||||
<use href="#t" x="244.811"/>
|
||||
<use href="#t" x="279.784"/>
|
||||
<use href="#t" x="314.757"/>
|
||||
<use href="#t" x="349.730"/>
|
||||
<use href="#t" x="384.703"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 742 B |