Files
Stefan MaierandClaude Opus 5.5 b6a943f1a6 Merge upstream 2026-10-10: network version 25, touch controls, in-game settings, FOV options, voice chat, votekick
248 OpenCE commits (network version 25): touch controls and touch menus for
phones, single-player in-game Settings, opt-in FOV/viewmodel settings, voice
chat and votes to kick, lens flare culling, memory leak fixes (render
targets, gamepads, the Android audio stream), profiling builds, the
HALO_ANDROID split into HALO_ARM64_GUEST / HALO_GLES / HALO_ANDROID.

Conflicts and how they were resolved:
- README.md: ours kept whole. Upstream's README changes (portable build,
  profiling builds) went into docs/OPENCE_README.md: upstream's new text
  with our header note, links rewritten to ../ and our "made from your own
  copy" wording.
- port/android/README.md: both merged; upstream's touch, WSL and save
  notes taken, with our application id in every adb path; our VR section,
  signing.properties and wording kept; the stale "How it works" list
  dropped as upstream dropped it.
- Launcher icons (mipmap-*/ic_launcher_foreground.png, art/android-icon.png):
  ours (the Ringfall art). Upstream's helmet monochrome layers,
  drawable/ic_launcher_background.xml and its ic_launcher.xml were left out;
  our ic_launcher.xml, values/ic_launcher_background.xml and
  tools/android_icon.py stay. (port/assets/icon and tools/app_icon.py come
  in for the desktop builds' window icon only; do not run app_icon.py, it
  would write the helmet into the Android resources.)
- port/android/host/host_main.c: both config helpers kept, and upstream's
  emulator write tracking (native_bridge_active); upstream's display width
  block dropped in favour of ours (640 in VR) and its display density for
  the touch controls moved into our flat (non-VR) branch.
- port/linux/game/menu_functions.c: platform_text_field(typing, password)
  from upstream; the VR keyboard launch for text fields kept after it.
- port/linux/game/network_distributed.c: upstream's profile_scope wrappers,
  with network_vr_tick and bots_tick kept in their places.
- port/linux/src/dsound_sdl.c: both (our debug.audio_log, upstream's
  audio.output_device).
- port/linux/src/port_config.c: our temp-and-rename config write kept,
  under upstream's HALO_ARM64_GUEST (it is guest file I/O).
- port/linux/src/sdl_platform.c/.h: upstream's (Android menu pointer from
  the touchscreen, Steam keyboard, scoreboard pointer); our quit marker for
  the Steam Frame launcher, clipboard_seen and VR frame hooks kept. Our
  flat-mouse menu pointer (an emulator aid) is replaced by upstream's touch
  pointer, which also drives the menus on the emulator.
- port/linux/src/d3d8_gl.c: halo_ui_pointer_update is upstream's, with the
  VR controller ray taking over while VR is active (the touchscreen then
  is not the pointer); our persistent-buffer writes kept for stream and
  index uploads, now writing upstream's exact length and under HALO_GLES
  like the code around them (stream_reserve, index_upload, mirror_refresh
  too); multiview uniforms stay under HALO_ANDROID, the counter binding
  under HALO_GLES. Visibility counters: ours kept (the snapshot ring with
  multiview views, host_gl_counters_snapshot) over upstream's per-frame
  glCopyBufferSubData into counter_snapshots: ours reads finished frames
  without stalling and counts both eyes' samples once; upstream's
  desktop query batches come in unchanged.
- port/linux/src/xinput_sdl.c: both (VR controllers and touch controls).
  In VR the touch controls neither show nor act (touch_input_gamepad,
  touch_input_controls, touch move/look skipped); rumble goes to the
  controllers in VR, to the phone otherwise.
- source/interface/first_person_weapons.c: both (VR scope pass check and
  display.viewmodel_visible).
- source/interface/ui_widget.c: upstream's reworked menu targets taken
  (ui_mouse_fit_button_targets, merged rows, widened values), which do
  what our icon_x0 key picking and the 12-unit value arrows did; our VR
  label distance (25) kept, and a VR ray counts as a coarse pointer, so it
  gets the taller legend areas and the widened values (not the merged
  rows: a row's label still presses A). Our join code and VR options
  panels kept; their early return now forgets the frame's targets too.
  The on-screen keyboard: upstream's virtual_keyboard_click for taps and
  the mouse, ours (hover highlights the key) for the VR ray only.
- source/main/main.c: both (VR phases around upstream's profile scopes).

VR guards for new upstream features:
- view_fov.c: display.fov, display.viewmodel_fov and
  display.viewmodel_visible do nothing while VR is active (the headset
  gives each eye's frustum; the weapon is in the hand).
- Touch controls: off in VR (xinput_sdl.c), the menus' touch pointer too
  (d3d8_gl.c); the overlay stays hidden as the game never reports a scene.
- Lens flare culling (744040c8, b1a71709) uses global_window_parameters'
  frustum, which in VR is each eye's (per-eye passes) or the union frustum
  of the multiview centre camera: no change needed.
- In-game Settings: VR options still open with Y on the main and pause
  menus (checked flat with debug.vr_options).
- The back button change (enableOnBackInvokedCallback=false) keeps the
  legacy KEYCODE_BACK path, which targetSdk 35 already had: no change on
  Quest or Frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 08:51:46 +02:00

526 lines
13 KiB
C

/*
TERMINAL.C
symbols in this file:
000D2B20 0040:
_terminal_initialize (0000)
000D2B60 0030:
_terminal_dispose (0000)
000D2B90 0090:
_code_000d2b90 (0000)
000D2C20 0030:
_terminal_clear (0000)
000D2C50 0010:
_terminal_gets_active (0000)
000D2C60 0080:
_terminal_gets_begin (0000)
000D2CE0 0020:
_terminal_gets_end (0000)
000D2D00 00c0:
_code_000d2d00 (0000)
000D2DC0 0050:
_code_000d2dc0 (0000)
000D2E10 02b0:
_terminal_draw (0000)
000D30C0 00a0:
_code_000d30c0 (0000)
000D3160 0030:
_terminal_update (0000)
000D3190 0130:
_terminal_printf (0000)
002712D0 0006:
_terminal_tab_stops (0000)
002712D8 0010:
??_C@_0BA@LJDMLJDP@terminal?5output?$AA@ (0000)
002712E8 0006:
??_C@_05HDBKIDE@state?$AA@ (0000)
002712F0 0024:
??_C@_0CE@JGPOFDIE@c?3?2halo?2SOURCE?2interface?2termina@ (0000)
00271314 0015:
??_C@_0BF@NFLIKMOD@new_line_index?$CB?$DNNONE?$AA@ (0000)
00271330 0050:
??_C@_0FA@BEBNLNOA@terminal_printf?5call?5generated?5?$CF@ (0000)
00301D99 0001:
_terminal_render_enable (0000)
00454224 001c:
_bss_00454224 (0000)
*/
/* ---------- headers */
#include "cseries.h"
#include "terminal.h"
#include "cache_files.h"
#include "game.h"
#include "data.h"
#include "interface.h"
#include "console.h"
#include "telnet_console.h"
#include "rasterizer.h"
#include "render.h"
#include "main/main.h"
#include "draw_string.h"
#include "byte_swapping.h"
#include "tag_groups.h"
#include "text_group.h"
#include "font_group.h"
#ifdef HALO_ANDROID
#include "halo_vr.h"
#endif
/* ---------- constants */
enum
{
OUTPUT_LINGER_TIME = 120,
OUTPUT_FADE_TIME = 30,
OUTPUT_TOTAL_TIME = 150,
OUTPUT_MAXIMUM_LINES = 32,
INSERTION_POINT_FLASH_PERIOD = 500,
};
/* ---------- macros */
#define output_line_get(index) ((struct output_line_datum *)datum_get(terminal_globals.output_lines, (index)))
/* ---------- structures */
struct terminal_globals
{
boolean initialized;
struct data_array *output_lines;
long newest_output_line_index;
long oldest_output_line_index;
struct terminal_gets_state *input_state;
boolean insertion_point_visible;
long last_insertion_point_toggle;
};
struct output_line_datum
{
short identifier;
long newer_line_index;
long older_line_index;
boolean tabstop;
char buffer[256];
real_argb_color color;
long timer;
};
/* ---------- prototypes */
static void terminal_remove_line(long line_index);
static boolean terminal_update_input(void);
static void terminal_update_output(void);
static long terminal_new_line(void);
/* ---------- globals */
boolean terminal_render_enable = TRUE;
/* name from the 2003 PC demo PDB and the HCEX PDB (terminal file static const short[3]); January's 6 bytes
* are identical to the demo's and it has no public for it (static) */
static const short terminal_tab_stops[] = {160, 320, 470};
static struct terminal_globals terminal_globals = {0};
/* ---------- public code */
void terminal_initialize(
void)
{
terminal_globals.output_lines = data_new("terminal output", OUTPUT_MAXIMUM_LINES, sizeof(struct output_line_datum));
terminal_globals.initialized = TRUE;
data_make_valid(terminal_globals.output_lines);
terminal_globals.input_state = NULL;
terminal_globals.newest_output_line_index = NONE;
terminal_globals.oldest_output_line_index = NONE;
return;
}
void terminal_dispose(
void)
{
if (terminal_globals.output_lines)
{
if (terminal_globals.output_lines->valid)
{
data_make_invalid(terminal_globals.output_lines);
}
data_dispose(terminal_globals.output_lines);
}
terminal_globals.initialized = FALSE;
return;
}
void terminal_clear(
void)
{
if (terminal_globals.initialized)
{
terminal_globals.newest_output_line_index = NONE;
terminal_globals.oldest_output_line_index = NONE;
data_delete_all(terminal_globals.output_lines);
}
return;
}
boolean terminal_gets_active(
void)
{
return terminal_globals.input_state != NULL;
}
boolean terminal_gets_begin(
struct terminal_gets_state *state)
{
boolean result = FALSE;
match_assert("c:\\halo\\SOURCE\\interface\\terminal.c", 195, state);
if (!terminal_globals.input_state)
{
terminal_globals.input_state = state;
terminal_globals.input_state->edit.buffer = terminal_globals.input_state->result;
terminal_globals.input_state->edit.maximum_length = TERMINAL_GETS_MAXIMUM_LINE_LENGTH;
edit_text_new(&terminal_globals.input_state->edit);
state->key_count = 0;
result = TRUE;
}
return result;
}
void terminal_gets_end(
struct terminal_gets_state *state)
{
if (state==terminal_globals.input_state)
{
terminal_globals.input_state = NULL;
}
return;
}
void terminal_draw(
void)
{
char buffer[288];
rectangle2d terminal_gets_bounds;
rectangle2d terminal_render_bounds;
rectangle2d line_bounds;
real_argb_color color;
long font_tag_index = interface_get_tag_index(_interface_font_terminal);
#ifdef HALO_ANDROID
/* VR: the log's lines would hang before the eyes; the console, when
open, still shows */
if (halo_vr_active() && !terminal_globals.input_state)
return;
#endif
if (terminal_globals.initialized)
{
struct font_header *font = font_definition_get(font_tag_index);
short line_height = font->ascending_height + font->descending_height + font->leading_height;
if (terminal_globals.input_state)
{
short buff_len;
buffer[0] = '\0';
terminal_globals.input_state->prompt[TERMINAL_GETS_MAXIMUM_PROMPT_LENGTH] = '\0';
strcat(buffer, terminal_globals.input_state->prompt);
terminal_globals.input_state->result[TERMINAL_PRINTF_MAXIMUM_LINE_LENGTH] = '\0';
buff_len = csstrlen(buffer);
strcpy(&buffer[buff_len], terminal_globals.input_state->result);
terminal_gets_bounds.x0 = render.camera.window_bounds.x0;
terminal_gets_bounds.x1 = render.camera.window_bounds.x1;
terminal_gets_bounds.y0 = render.camera.window_bounds.y1 - line_height;
terminal_gets_bounds.y1 = render.camera.window_bounds.y1;
offset_rectangle2d(&terminal_gets_bounds, -render.camera.viewport_bounds.x0, -render.camera.viewport_bounds.y0);
draw_string_set_draw_mode(font_tag_index, NONE, 0, 0, &terminal_globals.input_state->color);
if (terminal_globals.insertion_point_visible)
{
short cursor_index = (buff_len + terminal_globals.input_state->edit.insertion_point_index);
if (buffer[cursor_index]=='\0')
{
buffer[cursor_index+1] = '\0';
}
buffer[cursor_index] = '_';
}
rasterizer_draw_string(&terminal_gets_bounds, NULL, NULL, 0, buffer);
}
if (terminal_render_enable)
{
struct output_line_datum *line;
long line_index;
short height = render.camera.window_bounds.y1 - line_height;
for (line_index = terminal_globals.newest_output_line_index;
line_index!=NONE && height - line_height > 0;
line_index = line->older_line_index)
{
real fade_progress;
line = output_line_get(line_index);
fade_progress = 4.f - line->timer * 0.033333335f;
color = line->color;
color.alpha*= PIN(fade_progress, 0.f, 1.f);
terminal_gets_bounds.x0 = render.camera.window_bounds.x0;
terminal_gets_bounds.x1 = render.camera.window_bounds.x1;
terminal_gets_bounds.y1 = height;
height -= line_height;
terminal_gets_bounds.y0 = height;
offset_rectangle2d(
&terminal_gets_bounds,
-render.camera.viewport_bounds.x0,
-render.camera.viewport_bounds.y0
);
if (line->tabstop)
{
draw_string_set_tab_stops(
terminal_tab_stops,
NUMBEROF(terminal_tab_stops)
);
}
draw_string_set_draw_mode(font_tag_index, NONE, 0, 0, &color);
rasterizer_draw_string(&terminal_gets_bounds, NULL, NULL, 0, line->buffer);
draw_string_set_tab_stops(terminal_tab_stops, 0);
}
}
}
return;
}
boolean terminal_update(
void)
{
boolean result = FALSE;
if (terminal_globals.initialized)
{
result = terminal_update_input();
if (!console_is_active())
{
terminal_update_output();
}
}
return result;
}
/* port/linux/src/port_config.c's */
const char *config_string(const char *name);
boolean terminal_command_running = FALSE;
/* port: whether the console shows a kind of what is logged on screen, as
config.toml's game.console_log says: "all", everything; "important" (and
anything else), the serious and the important; "none", the serious only
(the asserts that stop the game). debug.txt has every line whatever it is */
boolean terminal_shows(
short kind)
{
static short level = NONE;
if (level == NONE)
{
const char *setting = config_string("game.console_log");
level = _terminal_message_important;
if (setting && !csstrcmp(setting, "all"))
level = _terminal_message_chatter;
else if (setting && !csstrcmp(setting, "none"))
level = _terminal_message_serious;
}
return kind <= level;
}
void terminal_printf(
real_argb_color const *color,
char const *format,
...)
{
real_argb_color default_terminal_printf_color;
va_list arglist;
va_start(arglist, format);
default_terminal_printf_color.alpha = 1.f;
default_terminal_printf_color.red = 0.7f;
default_terminal_printf_color.green = 0.7f;
default_terminal_printf_color.blue = 0.7f;
if (terminal_globals.initialized)
{
long line_index = terminal_new_line();
match_assert("c:\\halo\\SOURCE\\interface\\terminal.c", 397, format);
if (line_index!=NONE)
{
int count;
struct output_line_datum *line = output_line_get(line_index);
line->timer = 0;
line->color = !color ? default_terminal_printf_color : *color;
/* port: a line longer than the on-screen buffer is cut to fit (it
halted here, from inside error(), so the real halt was never
written down) */
count = _vsnprintf(line->buffer, NUMBEROF(line->buffer)-2, format, arglist);
if (count < 0 || count >= (int)(NUMBEROF(line->buffer)-2))
line->buffer[NUMBEROF(line->buffer)-2] = 0;
line->tabstop = strstr(line->buffer, "|t") != 0;
telnet_console_print(line->buffer);
}
}
va_end(arglist);
return;
}
/* ---------- private code */
static void terminal_remove_line(
long line_index)
{
struct output_line_datum *output_line = output_line_get(line_index);
if (output_line->older_line_index!=NONE)
{
output_line_get(output_line->older_line_index)->newer_line_index = output_line->newer_line_index;
}
else
{
terminal_globals.oldest_output_line_index = output_line->newer_line_index;
}
if (output_line->newer_line_index!=NONE)
{
output_line_get(output_line->newer_line_index)->older_line_index = output_line->older_line_index;
}
else
{
terminal_globals.newest_output_line_index = output_line->older_line_index;
}
datum_delete(terminal_globals.output_lines, line_index);
return;
}
static boolean terminal_update_input(
void)
{
struct key_stroke key;
boolean result = FALSE;
if (terminal_globals.input_state)
{
long time = local_time_get();
for (terminal_globals.input_state->key_count = 0;
input_get_key(&key);
terminal_globals.last_insertion_point_toggle = time)
{
if (terminal_globals.input_state->key_count < TERMINAL_GETS_MAXIMUM_KEYS)
{
terminal_globals.input_state->keys[terminal_globals.input_state->key_count] = key;
terminal_globals.input_state->key_count++;
}
edit_text_handle_key(&terminal_globals.input_state->edit, &key);
terminal_globals.insertion_point_visible = TRUE;
}
if (time > terminal_globals.last_insertion_point_toggle + OUTPUT_FADE_TIME)
{
terminal_globals.last_insertion_point_toggle = time;
terminal_globals.insertion_point_visible = terminal_globals.insertion_point_visible==FALSE;
}
result = TRUE;
}
return result;
}
static void terminal_update_output(
void)
{
struct output_line_datum *line;
long line_index = terminal_globals.newest_output_line_index;
/* This runs once a frame, several frames per tick on the native builds
(port/linux/game/render_interpolation.c): count the line timers in 30 Hz
ticks of real time, as they counted on the Xbox, not in frames. */
static real leftover_ticks = 0.f;
long ticks;
leftover_ticks += main_get_seconds_elapsed() * TICKS_PER_SECOND;
ticks = (long)leftover_ticks;
leftover_ticks -= (real)ticks;
while (line_index!=NONE)
{
long older_line_index;
line = output_line_get(line_index);
older_line_index = line->older_line_index;
line->timer += ticks;
if (line->timer>OUTPUT_TOTAL_TIME)
{
terminal_remove_line(line_index);
}
line_index = older_line_index;
}
return;
}
static long terminal_new_line(
void)
{
long new_line_index;
struct output_line_datum *line;
if (terminal_globals.output_lines->count==OUTPUT_MAXIMUM_LINES)
{
terminal_remove_line(terminal_globals.oldest_output_line_index);
}
new_line_index = datum_new(terminal_globals.output_lines);
match_assert("c:\\halo\\SOURCE\\interface\\terminal.c", 122, new_line_index!=NONE);
line = output_line_get(new_line_index);
line->newer_line_index = NONE;
line->older_line_index = terminal_globals.newest_output_line_index;
terminal_globals.newest_output_line_index = new_line_index;
if (line->older_line_index!=NONE)
{
output_line_get(line->older_line_index)->newer_line_index = new_line_index;
}
else
{
terminal_globals.oldest_output_line_index = new_line_index;
}
return new_line_index;
}