Compare commits

..
Author SHA1 Message Date
Stefan MaierandClaude Opus 5.5 3aad697ebe VR multiview: a driver self-test, views turned apart drawn one by one, driver variables
On the Steam Frame the eyes drawn at once did not fuse (each alone sharp)
while drawn one by one they did; the corrections agree with the projection
and fuse on the Quest, so the Frame's GL driver (Mesa zink on Turnip) is
the suspect: a right eye given the left one's correction shows there, where
the eyes' views are wider and unlike each other.
- d3d8_gl.c: when the layers are first made, a point drawn into two 2x1
  layers by rows picked with gl_ViewID_OVR, as the game's vertex programs
  pick them, and read back; failed, the eyes are drawn one by one
  (logcat "multiview: self-test passed/FAILED")
- vr.c: a frame whose eyes' views are turned apart (more than 0.05
  degrees, which one camera cannot serve) draws the eyes one by one; the
  views' angle and each eye's edges go to the log as they change
- debug.gl_environment: "NAME=value ..." set before the driver starts
  (TU_DEBUG=nomultipos, ZINK_DEBUG=nobgc, MESA_SHADER_CACHE_DISABLE=true)
SteamVR still draws the eyes one by one unless debug.multiview_steamvr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:09:42 +02:00
132 changed files with 1431 additions and 12697 deletions

No files matched your search

+1 -6
View File
@@ -67,9 +67,7 @@ jobs:
run: python tools/ci_build.py linux release
- name: Test
run: |
python -m pytest -q tools/test_linux_port.py
python tools/test_light_storage.py
run: python -m pytest -q tools/test_linux_port.py
- name: Compile cache
run: ccache --show-stats
@@ -202,9 +200,6 @@ jobs:
- name: Release
run: python tools/ci_build.py windows release
- name: Test light storage
run: python tools/test_light_storage.py
- name: Compile cache
run: ccache --show-stats
+13
View File
@@ -136,3 +136,16 @@ page of buttons shows this layout on the Frame.
`debug.xr_deferred_copies` = 0 brings back the old copies for comparing,
1 defers them on any runtime; `vr.log_performance` logs the frame's parts
and, with the compositor's counters, each copy's time.
- 2026-10-05 later: the doubling was the eyes not fusing (each eye sharp
alone) with multiview, and gone drawing the eyes one by one, which is now
SteamVR's default (`debug.multiview_steamvr` for multiview). The per-eye
corrections were checked against the projection (x, y from the top after
the flip, the eyes' offsets in clip units) and agree with the Quest, where
multiview fuses; the Frame's eyes are wider and differ from each other
more, so a driver giving the right eye the left one's correction (zink on
Turnip: ViewIndex, Turnip's multi-position output) would show there. For
finding it: a self-test of the driver's views when the layers are made
(logcat "multiview: self-test"), a frame whose views are turned apart
drawn one by one, the views' shape in the log as it changes ("VR views:"),
and `debug.gl_environment` ("TU_DEBUG=nomultipos", "ZINK_DEBUG=nobgc",
"MESA_SHADER_CACHE_DISABLE=true") set before the driver starts.
-325
View File
@@ -1,325 +0,0 @@
# VR roadmap
What the VR port (Quest 3, Steam Frame) has, and what comes next, in the
agreed order. Updated with every step: a step is **done** once the user has
tried it in the headset and it is merged into `main`; **built** means in a
branch, not yet tried in the headset.
Several of the next steps follow
[HaloCE-Quest-VR](https://github.com/moistman42069/HaloCE-Quest-VR) and the
Steam Frame work it grew from,
[astromaddie/HaloCE-VR](https://github.com/astromaddie/HaloCE-VR) (both
CC0): their ideas, rebuilt into this port's own structure, with the source
named where code is taken over.
## Done (in `main`)
| Area | What |
| --- | --- |
| Headsets | Quest 3; Steam Frame (Lepton): its controllers' layout, a Steam library tile that installs, starts, updates and quits the game, data kept in `~/Documents/HaloCE` |
| Rendering | Both eyes in one pass (multiview) on the Quest; one by one on SteamVR, where multiview's eyes did not fuse; lens flares at their depth in stereo; HUD, menus, scope, crosshair and names as layers of their own |
| Aiming | The weapon in the hand, its shots from the muzzle, laser and crosshair options |
| Body | Physical crouch (`vr.crouch_limit`) beside the crouch button, leaning (`vr.lean_limit`), melee by swinging, grenades thrown with the left hand, two-handed weapons |
| Vehicles | The Warthog's wheel turned with the hands, right trigger throttle, left brake; first-person seats; turrets with handles |
| Comfort | Snap or smooth turning, a comfort vignette in vehicles, VR options in the main and pause menus (Y), a page of every controller button |
| Menus | The PC version's menus in VR (`vr.menus`), clicked with a laser; text typed on the on-screen keyboard |
| Multiplayer | Up to date with OpenCE (network version 20): cross-play, its server browser, co-op campaign, password lobbies; join codes and the VR online list |
| Bots | Bots in multiplayer: on foot (paths, items, objectives), in vehicles and at turrets; they make room for a joining person |
| Controls | Left-handed mode (`vr.handedness`): the weapon in the left hand, the arms and grips mirrored, sticks and buttons mirrored on both controllers (`vr.mirror_controls`), the assault rifle's counter read the right way round (2026-10-06) |
| Graphics | Presets low / medium / high / max (`vr.graphics`), refresh rate and resolution in the VR options, all kept over a restart (2026-10-06) |
| Arms and hands | Both arms by IK from shoulders that follow the torso, the elbow led by the hand's roll; fingers from trigger, grip and touch; `vr.body` arms / hands / off, `vr.fingers` (2026-10-06) |
## Next, in order
| # | Step | State |
| --- | --- | --- |
| 1 | Steam Frame CPU time: Mesa's trace markers off (after astromaddie/HaloCE-VR), no `glGet` stalls in the visibility counters' copies | **done** (in `main` 2026-10-06): markers quieted, no problems; the frame's work about unchanged (11-12.5 ms of 13.9 in a bot game) |
| 3a | Visible arms and hands: the arms by IK to the controllers, fingers by trigger and grip | **done** (in `main` 2026-10-06; Quest 3 and Steam Frame good; 0.02 ms a frame) |
| 2 | Controls and comfort: left-handed mode, buttons remapped, flashlight by tapping the head, finer turning and vignette choices, graphics presets and refresh rate in the VR options | first part **done** (in `main` 2026-10-06; Quest 3 and Steam Frame good): left-handed mode, graphics presets, refresh rate and resolution in the VR options. Second part **built** (branch `feature/vr-comfort`, headset test pending): buttons remapped, flashlight by tapping the head, finer turning and vignette choices |
| 3b | The whole body: torso from head and hands, legs and feet following in the room; full / arms only / hands only | |
| 3c | Weapons held physically, holsters at shoulder and hip (the buttons stay) | |
| 3d | Other VR players shown with their heads, hands and bodies (today their bodies move as flat players' do: only their aim and throws come from their hands) | |
| 4 | Later: disc image import in the launcher, sprint by arm swing (offline) | |
## Step 2, first part (done)
Left-handed mode and graphics presets, after HaloCE-Quest-VR (CC0): its
mirrored first-person model, its graphics switches.
Left-handed (`vr.handedness = "left"`):
- The weapon in the left hand: aim, shots, muzzle, laser, crosshair,
scope disc, turret aim. The first-person model is posed as the right
hand's in a mirror image of the room ("model space", `halo_vr.h`) and
mirrored back; a model whose node matrices mirror has its triangles'
winding turned over (`rasterizer_set_model_skinning`, `d3d8_gl.c`). The
arms reach to the other shoulders, the foregrip is gripped with the
right hand, the right hand's fingers follow its controller.
- Grenades held and thrown with the right trigger and hand; melee by the
left grip (a swing of either hand, as before); the motion tracker on the
right wrist (`halo_xr_submit.wrist_hand`); the rumble's light motor in
the weapon hand.
- The left trigger fires, the right one throws; at the Warthog's wheel the
left trigger accelerates, the right one brakes.
- With `vr.mirror_controls` (default): the right stick moves, the left
turns and steers; their clicks, the bumpers and the face buttons change
sides (Quest: X jumps, Y light / held grenades, A action and reload, B
switch weapon; Steam Frame: the left D-pad does A, B, X, Y as their
mirror image, down jump, left light, right action, up switch weapon,
and A, B, X, Y do the D-pad's). The menus keep their buttons; the
laser pointer works from either hand as before.
- Other machines see the shots leave the left hand (the network message
is unchanged: aim and muzzle from the camera).
- The assault rifle's ammo counter and compass read the right way round
on the mirrored model (2026-10-06): their geometry stays mirrored, each
digit's bitmap is drawn turned over in u and the digits' order across
the display swapped (numeric shaders drawn mirrored,
`rasterizer_xbox_transparent_geometry.c`). Checked flat in the emulator
with `debug.mirror_first_person` (the first-person weapon mirrored off
the headset): 60, 48, 36 ... 06 read correctly. Steam Frame good
(2026-10-06).
- Kept as they are (accepted 2026-10-06): the small static icons under the
counter (round and "x") stay mirror images (a texture atlas, not
numeric). The game's prompts still name the Xbox's buttons.
- Settings kept: on Android `config.toml` is written beside itself and
renamed over (`port_config.c`), so one pushed by adb (the shell's, which
the app could not open for writing) no longer loses the VR options'
changes at a restart.
- Headset tests 2026-10-06: Quest 3 and Steam Frame good (left hand,
controls mirrored and not, counter, presets, refresh rates, resolution,
settings kept over a restart).
Graphics (`vr.graphics`, `port/linux/game/vr_graphics.c`), set each frame
on the game's own switches:
| Preset | Leaves out |
| --- | --- |
| `"high"` (default) | nothing: as before |
| `"medium"` | object shadows, environment reflections, lens flares |
| `"low"` | also specular light, bump maps, grass and other detail objects, the screen's fog layer, contrails, weather, camouflage's second pass |
| `"max"` | nothing; models lit for each pixel, shadows at 512x512 |
A client in another's game keeps grass, lens flares and the fog layer as
the host's rules have them (`cheats.c`). Shadows, specular passes,
reflections and detail objects are draws (CPU, which the Frame's GL
driver is short of); bump maps, fog, camouflage and per-pixel lighting
the GPU's fill.
VR options (Y): two pages, PLAY (the comfort rows, "Weapon hand",
"Mirrored controls") and MORE ("Graphics", "Refresh rate", "Resolution",
the controller buttons page, Quit); four since the second part (below). The refresh rate steps through what
the runtime offers and changes at once; on the Steam Frame the row shows
SteamOS's rate. The resolution (`vr.resolution_scale`, 70-120%) takes a
restart. The controller buttons page shows the layout as set.
| Setting | Values |
| --- | --- |
| `vr.handedness` | `"right"` (default), `"left"` |
| `vr.mirror_controls` | `true` (default): sticks and buttons mirrored when left-handed; `false`: as right-handed (triggers and grips still follow the hands) |
| `vr.graphics` | `"low"`, `"medium"`, `"high"` (default), `"max"` |
| `vr.refresh_rate` | Hz, as offered (Quest 3: 72, 80, 90, 120) |
| `vr.resolution_scale` | 0.7 to 1.2, on restart |
To try in the headset (Quest 3 and Steam Frame):
1. Left-handed: aim, laser, crosshair, scope of sniper and rocket
launcher; weapon held right in the left hand, arms from the right
shoulders, nothing inside out (faces culled the wrong way would show
the inside of the gun or the glove).
2. Two-handed grip with the right hand; grenades in the right hand (held
and thrown); melee by grip and by swing; motion tracker on the right
wrist.
3. Buttons and sticks mirrored on the Touch and the Frame controllers
(every function reachable), then `Mirrored controls: Off`.
4. Vehicles: wheel, throttle and brake, steering stick, a gunner's aim;
menus and the laser pointer; a network game (shots from the left hand
on the other machine).
5. Graphics: each preset's look and smoothness (`vr.log_performance`),
in a bot game; Max on the Quest at 90 Hz; refresh rates 72/80/90/120
on the Quest; the Frame's row.
## Step 2, second part (built)
On branch `feature/vr-comfort`, not yet tried in the headset. The VR
options (Y) have four pages: PLAY ("Weapon hand", "Mirrored controls",
laser, crosshair, motion tracker, body, fingers, the Warthog's wheel),
COMFORT (turning, the vignettes), BUTTONS (the actions' buttons, the
flashlight's tap, "Reset buttons to defaults", the controller buttons
page) and MORE (graphics, refresh rate, resolution, Quit).
Buttons remapped (BUTTONS page; `vr.c` `controls_remapped`):
- Jump, the flashlight (held: switch grenades), action/reload, switch
weapon, crouch, zoom and melee each go on one of seven buttons: A, B,
X, Y, the move stick's click, the turning stick's, the weapon hand's
grip. Moving an action to a button swaps it with the action there, so
none is ever left without one.
- The triggers, the free hand's grip, the menu button, the Steam Frame's
bumpers, D-pad and View button keep theirs; the menus keep the buttons
as they are.
- Applied after the left-handed mirroring, on the gamepad's sides: the
same on the Touch and the Frame controllers, and a left-handed player's
buttons are the mirror image of the right-handed ones (on the Frame,
mirrored, the left D-pad's). The defaults are the old layout, the
host's bindings and the ABI unchanged.
- The melee's aim and jab follow its button; the grips on a Warthog's
wheel press nothing, whatever the grip does.
- The controller buttons page shows each action where its button is.
Flashlight by tapping the head (`vr.c` `flashlight_gesture_update`,
after HaloCE-Quest-VR, CC0): either hand to the side or the top of the
head and away, as a tap, toggles it; the button still does. A zone in
the head's axes beside or over the head, never more than 6 cm in front
of the eyes (a scope at the eye, a two-handed weapon, a punch and the
wrist's tracker are in front of the face); the hand comes from 35 cm
away, slower than 3 m/s, stays 40 to 600 ms, alone, and leaves; a second
between toggles. None in the menus, with a grenade held or just thrown,
just after a melee, with both hands on the weapon, the weapon hand while
the scope is drawn, a hand on the wheel. A short rumble in the tapping
hand. Its logic was run in a harness off the headset; the feel is the
headset's to judge.
Comfort (COMFORT page): snap turns 10 to 45 degrees by 5, 60, 75, 90;
smooth turning 30 to 150 degrees a second by 15, then to 300 by 30; the
vehicle vignette Off, 15 to 100%; the on-foot vignette (off by default)
while the stick turns smoothly, or also while the move stick moves, as
far as the sticks are pushed, with its own strength. The old values are
among the steps; defaults unchanged.
Transparent geometry: room for 2048 groups a window (the Xbox's 384,
below).
| Setting | Values |
| --- | --- |
| `vr.button_jump` | `"a"` (default), `"b"`, `"x"`, `"y"`, `"move_stick"`, `"turn_stick"`, `"grip"`: as right-handed, mirrored with `vr.mirror_controls` |
| `vr.button_light` | the flashlight, held switch grenades; `"b"` (default) |
| `vr.button_action` | action, reload; `"x"` (default) |
| `vr.button_weapon` | switch weapon; `"y"` (default) |
| `vr.button_crouch` | `"move_stick"` (default) |
| `vr.button_zoom` | `"turn_stick"` (default) |
| `vr.button_melee` | `"grip"` (default) |
| `vr.flashlight_gesture` | `true` (default): a tap of the head toggles the flashlight |
| `vr.foot_vignette` | `"off"` (default), `"turning"` (smooth turns), `"moving"` (and moving) |
| `vr.foot_vignette_strength` | 0.15 to 1, `0.7` (default) |
| `debug.vr_options` | `false` (default); `true` shows the VR options off the headset (the emulator) |
Checked in the emulator (`debug.vr_options`): the four pages without
overlaps, each row's values, swaps and reset, the controller buttons page
right- and left-handed after swaps, the settings kept over a restart, a
15-bot game as before.
To try in the headset (Quest 3 and Steam Frame):
1. Defaults: every button as before (jump, light/held grenades, action,
switch weapon, crouch, zoom, melee by grip and by swing), right- and
left-handed, mirrored and not; the menus' A and B.
2. BUTTONS: swap jump and melee (jump on the grip, melee on A): both work,
the melee jabs and aims where the head looks; light on a stick click
(tap: light, hold: grenades); the controller buttons page says so.
At the Warthog's wheel with jump on the grip: no jump while holding
the wheel. Reset: all back.
3. The same left-handed with mirrored controls (on the Frame the left
D-pad), and with `Mirrored controls: Off`.
4. Flashlight tap with either hand, beside the head and over it; the
rumble; the flashlight button still works. No toggle while: aiming
through the sniper's and the pistol's scope, holding the weapon with
both hands, holding and throwing a grenade (overhand past the ear),
punching and swinging, putting on or adjusting the headset with both
hands or one, looking at the wrist's tracker, at the wheel.
5. COMFORT: snap 10 and 90, smooth 30 and 300; vehicle vignette 15% and
100%; on-foot vignette "Smooth turns" (nothing with snap turns),
"Moving, turning", strength 15% and 100%; no vignette in a vehicle
from the on-foot one, none in the menus.
6. A game of 30 and more players: effects, glass and water stay; the log
(`adb logcat -s halo`) says `rasterizer: N transparent geometry groups
in a window` once a window passes 384; frame time as before.
## Step 3a (done)
Tried on the Quest 3 and the Steam Frame, in `main`. The arm IK,
the forearm's twist and the fingers' poses follow HaloCE-Quest-VR (CC0),
in `port/linux/game/vr_arms.c`.
- The first-person pose (weapon, arms) is built once a frame instead of
per eye and scope view (SteamVR: two to three times before).
- `vr.log_performance` reports its cost (`VR: first person: ... ms a
frame in ... builds (arms ... ms), ... kept`) and, once, how the hands'
skin hangs from its bones (none from the root: no triangle filter
needed).
- Both arms bend from shoulders below the neck, square to the torso. The
torso follows the head beyond 15 degrees and turns towards the hands;
snap and smooth turns carry it at once.
- The free left arm bends at the elbow to the controller (before: carried
rigidly). The hand's roll is shared along forearm and upper arm.
- The free hand's fingers follow the controller: index from the trigger,
the others from the grip, the thumb from where it rests (touch on the
Quest's, Index and Steam Frame controllers; half curled where unknown),
closed round a held grenade. The weapon hand keeps its grip; its index
finger lifts off the trigger when the controller says it is off.
- host_xr: touch actions for thumb and index; `suggest()` takes 64
bindings (was 32, silently cut) and retries a profile without the
touch paths should the runtime refuse them.
Settings (`[vr]` in config.toml; the first two also in the VR options,
Y on the main or pause menu):
| Setting | Values |
| --- | --- |
| `vr.body` | `"arms"` (default), `"hands"` (hands alone, the arms shrunk behind the wrists), `"off"` (the weapon alone) |
| `vr.fingers` | `true` (default): fingers follow the controller; `false`: as the weapon's animation has them |
| `vr.arm_ik` | `true` (default): the free arm bends at the elbow; `false`: rigid as before, should its skin tear |
After the first Quest tries (2026-10-06):
- The shoulders 13 cm behind the eyes and 20 cm below the neck: the open
end of the sleeve was in view. Looking hard down to the side one still
sees into the arm (the model ends at the shoulder): left as it is.
- The elbow bends in the arm's plane (`vr_align_elbow`): it had turned in
with the elbow out to the side; the upper arm takes none of the hand's
roll.
- The free arm is solved first and its hand turned after (as
HaloCE-Quest-VR does): turned first, its skin stretched like a rubber
band.
- The elbow follows the hand's roll (lifted out, the controller rolls in):
before, it bent only towards a fixed direction.
- Arm length, shoulder width, fingers, grenade grip, body modes, scope,
vehicles: fine.
Not in it (3a.7): the glove fitted to each weapon's grip (gun anchor) and
the left hand animated through reloads.
## Open points outside the order
- Multiview on the Steam Frame: why the eyes do not fuse there (suspected
Mesa zink/Turnip); tools in branch `frame/doubled-images` (86335828),
not merged.
- Transparent geometry groups: the Xbox's 384 a window overflowed in
games of 30 and more players (the last submitted, glass, water,
particles, contrails, vanished). Raised to 2048 (320 KB) on
`feature/vr-comfort`, to check in a large game (step 2, second part,
test 6); upstream still has 384.
## Headset tests (2026-10-06, all three in `main`)
Quest 3, 2026-10-06: menus, laser, VR options, head turning, arms and
hands, fingers, body modes, scope, vehicles, a bot game (bots spread and
leave their bases) and a large online game: all good.
Steam Frame, 2026-10-06: buttons, picture (no doubled image), arms, hands,
fingers, wheel: all good; SteamVR reprojected 18% of the frames, unseen.
What follows is left for the two headsets together.
Upstream merge (`merge/upstream-2026-10-06`):
- Menus, a bot game, head turning (no doubled image), co-op briefly.
- A password game hosted on the Frame, joined from the Quest.
- A bot making room for a person joining a full lobby.
Performance (`frame/performance-2`, and the merge's upstream speed-ups),
with `vr.log_performance = true`:
- Frame: the log says `trace markers: N descriptors to /dev/null`; frame
time ("working") against before (about 12.5 ms in a bot game on Blood
Gulch); SteamVR's reprojected share (vrcompositor.txt) against before
(2 to 17 %); no doubled image.
- Lens flares still appear and hide as before (their visibility counters'
copies changed).
- The pose cache (3a.1): `first person: ... builds` about one a frame;
scope, crosshair, laser and the weapon in the hand as before.
- Quest: 90 Hz held as before in a bot game and in a large online game;
nothing slower than the last build.
Arms and hands (3a): on the Steam Frame, as on the Quest (fingers on its
controllers, every button still working).
-5
View File
@@ -135,11 +135,6 @@ into a layer's image, opaque */
int host_xr_copy_layer_array(int layer, unsigned int texture, int array_layer);
/* darkens the eye images from their edges, strength 0..1 (0: off) */
void host_xr_set_vignette(float strength);
/* the refresh rates the headset offers (Hz, at most maximum of them; 0 when
it lets none be chosen), and the one it runs at (0: unknown); and asking
for the highest offered at or below wanted (0: the highest) */
int host_xr_refresh_rates(float *rates, int maximum, float *current);
void host_xr_set_refresh_rate(float wanted);
/* ---------- Android */
-1
View File
@@ -92,7 +92,6 @@ void *host_resolve_import(const char *name);
/* ---------- SDL / GL (host_sdl.c, host_gl.c) */
void *host_gl_resolve(const char *name);
int host_gl_quiet_trace_markers(void);
/* ---------- VR (host_xr.c) */
+9 -50
View File
@@ -11,52 +11,8 @@ already have host types by then. Only strings need copying back.
#include <EGL/egl.h>
#include <GLES3/gl32.h>
#include <GLES2/gl2ext.h>
#include <dirent.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* Valve's Mesa (the Steam Frame's Lepton) writes a marker to the kernel's
trace_marker around every flush and context switch, a couple of hundred a
frame, whether or not anything traces. The descriptors it holds open on that
file are pointed at /dev/null instead (not closed: a reused descriptor would
take the markers elsewhere). After astromaddie/HaloCE-VR (CC0). Returns how
many were. */
int host_gl_quiet_trace_markers(void)
{
DIR *directory = opendir("/proc/self/fd");
struct dirent *entry;
int quieted = 0, null_fd;
if (!directory)
return 0;
null_fd = open("/dev/null", O_WRONLY | O_CLOEXEC);
while (null_fd >= 0 && (entry = readdir(directory)) != NULL)
{
char link[64], target[256];
ssize_t length;
int fd = atoi(entry->d_name);
if (entry->d_name[0] < '0' || entry->d_name[0] > '9' || fd == null_fd)
continue;
snprintf(link, sizeof(link), "/proc/self/fd/%d", fd);
length = readlink(link, target, sizeof(target) - 1);
if (length <= 0)
continue;
target[length] = 0;
if (strstr(target, "tracing/trace_marker") && dup3(null_fd, fd, O_CLOEXEC) == fd)
quieted++;
}
if (null_fd >= 0)
close(null_fd);
closedir(directory);
if (quieted)
host_logf(HOST_LOG_INFO, "trace markers: %d descriptors to /dev/null", quieted);
return quieted;
}
void *host_gl_resolve(const char *name)
{
@@ -132,13 +88,14 @@ half-drawn targets out of tile memory, for every lens flare. */
static GLuint counter_snapshots[COUNTER_SNAPSHOTS];
static uint32_t counter_snapshot_sizes[COUNTER_SNAPSHOTS];
/* (the copy bindings are left unbound after: the renderer binds them itself
for each copy. Asking GL what they were would wait for Mesa's GL thread, on
the Frame, to run everything queued) */
void host_gl_counters_snapshot(uint32_t buffer, uint32_t size, uint32_t slot)
{
GLint read_binding = 0, write_binding = 0;
if (slot >= COUNTER_SNAPSHOTS || !buffer || !size)
return;
glGetIntegerv(GL_COPY_READ_BUFFER_BINDING, &read_binding);
glGetIntegerv(GL_COPY_WRITE_BUFFER_BINDING, &write_binding);
if (!counter_snapshots[slot])
glGenBuffers(1, &counter_snapshots[slot]);
glBindBuffer(GL_COPY_WRITE_BUFFER, counter_snapshots[slot]);
@@ -151,17 +108,19 @@ void host_gl_counters_snapshot(uint32_t buffer, uint32_t size, uint32_t slot)
glMemoryBarrier(GL_ATOMIC_COUNTER_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
glBindBuffer(GL_COPY_READ_BUFFER, buffer);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, size);
glBindBuffer(GL_COPY_READ_BUFFER, 0);
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
glBindBuffer(GL_COPY_READ_BUFFER, (GLuint)read_binding);
glBindBuffer(GL_COPY_WRITE_BUFFER, (GLuint)write_binding);
}
/* copies a snapshot out; its frame must be finished (host_gl_wait_frame) */
int host_gl_counters_read(uint32_t slot, void *out, uint32_t size)
{
GLint write_binding = 0;
const void *mapping;
if (slot >= COUNTER_SNAPSHOTS || !counter_snapshots[slot] || size > counter_snapshot_sizes[slot])
return 0;
glGetIntegerv(GL_COPY_WRITE_BUFFER_BINDING, &write_binding);
glBindBuffer(GL_COPY_WRITE_BUFFER, counter_snapshots[slot]);
mapping = glMapBufferRange(GL_COPY_WRITE_BUFFER, 0, size, GL_MAP_READ_BIT);
if (mapping)
@@ -169,7 +128,7 @@ int host_gl_counters_read(uint32_t slot, void *out, uint32_t size)
memcpy(out, mapping, size);
glUnmapBuffer(GL_COPY_WRITE_BUFFER);
}
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
glBindBuffer(GL_COPY_WRITE_BUFFER, (GLuint)write_binding);
return mapping != NULL;
}
+37
View File
@@ -205,6 +205,24 @@ static double config_number(const char *path, const char *key, double fallback)
return value;
}
/* a string from config.toml into text (size bytes), or "" when it is not
set */
static void config_text(const char *path, const char *key, char *text, size_t size)
{
toml_result_t result = toml_parse_file_ex(path);
text[0] = 0;
if (!result.ok)
return;
{
toml_datum_t datum = toml_seek(result.toptab, key);
if (datum.type == TOML_STRING && datum.u.s)
snprintf(text, size, "%s", datum.u.s);
}
toml_free(result);
}
/* POSIX TZ for the current local offset (the guest's musl has no zone
database) */
static void time_zone(char *buffer, size_t size)
@@ -346,6 +364,25 @@ static void *game_main(void *unused)
if (deferred >= 0.0)
setenv("HALO_XR_DEFERRED", deferred != 0.0 ? "1" : "0", 1);
}
{
/* (debug.gl_environment: "NAME=value ..." for the GL driver, set
before it starts: the Steam Frame's Mesa (zink on Turnip) reads
ZINK_DEBUG, TU_DEBUG, MESA_SHADER_CACHE_DISABLE and the like) */
char text[512];
char *item, *rest = NULL;
config_text(path, "debug.gl_environment", text, sizeof(text));
for (item = strtok_r(text, " ;", &rest); item; item = strtok_r(NULL, " ;", &rest))
{
char *equals = strchr(item, '=');
if (!equals || equals == item)
continue;
*equals = 0;
setenv(item, equals + 1, 1);
host_logf(HOST_LOG_INFO, "GL driver environment: %s=%s (debug.gl_environment)", item, equals + 1);
}
}
{
int eye_width, eye_height;
+1 -5
View File
@@ -151,11 +151,7 @@ uint32_t host_sdl_gl_create_context(uint32_t window)
{
SDL_Window *object = handle_get(window, _handle_window);
uint32_t context = object ? handle_new(_handle_context, SDL_GL_CreateContext(object)) : 0;
/* (the driver opens its trace markers with the context) */
host_gl_quiet_trace_markers();
return context;
return object ? handle_new(_handle_context, SDL_GL_CreateContext(object)) : 0;
}
int host_sdl_gl_make_current(uint32_t window, uint32_t context)
+36 -183
View File
@@ -105,10 +105,6 @@ static struct
XrAction button_b; /* B on the right controller, Y on the left */
XrAction menu;
XrAction thumbrest;
/* the fingers' touch, for the hands drawn (where the runtime has it):
the thumb on the stick, a button or the rest, the index on the trigger */
XrAction thumb_touch;
XrAction index_touch;
/* the Steam Frame's controllers' own (XR_VALVE_frame_controller_interaction) */
int has_frame_controller;
int frame_layout;
@@ -120,10 +116,10 @@ static struct
XrAction haptic;
struct xr_hand_actions hands[HALO_XR_HAND_COUNT];
/* where the head and the hands are this frame (the wrist layer) */
/* where the head and the left hand are this frame (the wrist layer) */
XrPosef head;
XrPosef grips[HALO_XR_HAND_COUNT];
int grips_valid[HALO_XR_HAND_COUNT];
XrPosef left_grip;
int left_grip_valid;
int has_performance_settings;
/* (XR_META_performance_metrics: the compositor's counts, in the log,
@@ -589,51 +585,22 @@ struct binding
const char *path;
};
/* a profile's bindings: those it has to have, and those it may (a path the
runtime does not know refuses the whole suggestion, which leaves the
profile with none: then the first alone, as before). Nonzero if taken. */
#define MAXIMUM_SUGGESTED_BINDINGS 64
static int suggest_bindings(const char *profile, const struct binding *bindings, int count,
const struct binding *optional, int optional_count)
static void suggest(const char *profile, const struct binding *bindings, int count)
{
XrActionSuggestedBinding suggested[MAXIMUM_SUGGESTED_BINDINGS];
XrActionSuggestedBinding suggested[32];
XrInteractionProfileSuggestedBinding information = { XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
XrResult result;
int index, total = 0;
int index;
if (count + optional_count > MAXIMUM_SUGGESTED_BINDINGS)
for (index = 0; index < count && index < 32; index++)
{
host_logf(HOST_LOG_ERROR, "xr: %d bindings for %s, room for %d: the rest left out", count + optional_count,
profile, MAXIMUM_SUGGESTED_BINDINGS);
}
for (index = 0; index < count + optional_count && total < MAXIMUM_SUGGESTED_BINDINGS; index++)
{
const struct binding *binding = index < count ? &bindings[index] : &optional[index - count];
suggested[total].action = *binding->action;
suggested[total].binding = path_from_string(binding->path);
total++;
suggested[index].action = *bindings[index].action;
suggested[index].binding = path_from_string(bindings[index].path);
}
information.interactionProfile = path_from_string(profile);
information.suggestedBindings = suggested;
information.countSuggestedBindings = (uint32_t)total;
result = xrSuggestInteractionProfileBindings(xr.instance, &information);
if (XR_SUCCEEDED(result))
return 1;
if (optional_count > 0)
{
host_logf(HOST_LOG_WARN, "xr: %s refused with its optional bindings (%d); suggested without them", profile,
(int)result);
return suggest_bindings(profile, bindings, count, NULL, 0);
}
host_logf(HOST_LOG_WARN, "xr: no bindings for %s (%d)", profile, (int)result);
return 0;
}
static void suggest(const char *profile, const struct binding *bindings, int count)
{
suggest_bindings(profile, bindings, count, NULL, 0);
information.countSuggestedBindings = (uint32_t)index;
if (!XR_SUCCEEDED(xrSuggestInteractionProfileBindings(xr.instance, &information)))
host_logf(HOST_LOG_WARN, "xr: no bindings for %s", profile);
}
static int actions_initialize(void)
@@ -660,8 +627,6 @@ 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.thumb_touch = action_create("thumb_touch", "Thumb resting", XR_ACTION_TYPE_BOOLEAN_INPUT);
xr.index_touch = action_create("index_touch", "Finger on the trigger", 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);
@@ -767,62 +732,13 @@ static int actions_initialize(void)
{ &xr.haptic, "/user/hand/right/output/haptic" },
};
/* the fingers' touch, as each profile has it (after HaloCE-Quest-VR,
CC0; the Frame's from Valve's "Steam Frame Input" page): optional, a
profile refusing them keeps its buttons */
static const struct binding touch_fingers[] =
{
{ &xr.thumb_touch, "/user/hand/left/input/thumbstick/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/thumbstick/touch" },
{ &xr.thumb_touch, "/user/hand/left/input/thumbrest/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/thumbrest/touch" },
{ &xr.thumb_touch, "/user/hand/left/input/x/touch" },
{ &xr.thumb_touch, "/user/hand/left/input/y/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/a/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/b/touch" },
{ &xr.index_touch, "/user/hand/left/input/trigger/touch" },
{ &xr.index_touch, "/user/hand/right/input/trigger/touch" },
};
static const struct binding index_fingers[] =
{
{ &xr.thumb_touch, "/user/hand/left/input/thumbstick/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/thumbstick/touch" },
{ &xr.thumb_touch, "/user/hand/left/input/trackpad/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/trackpad/touch" },
{ &xr.thumb_touch, "/user/hand/left/input/a/touch" },
{ &xr.thumb_touch, "/user/hand/left/input/b/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/a/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/b/touch" },
{ &xr.index_touch, "/user/hand/left/input/trigger/touch" },
{ &xr.index_touch, "/user/hand/right/input/trigger/touch" },
};
static const struct binding frame_fingers[] =
{
{ &xr.thumb_touch, "/user/hand/left/input/thumbstick/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/thumbstick/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/a/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/b/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/x/touch" },
{ &xr.thumb_touch, "/user/hand/right/input/y/touch" },
{ &xr.thumb_touch, "/user/hand/left/input/dpad_up/touch" },
{ &xr.thumb_touch, "/user/hand/left/input/dpad_down/touch" },
{ &xr.thumb_touch, "/user/hand/left/input/dpad_left/touch" },
{ &xr.thumb_touch, "/user/hand/left/input/dpad_right/touch" },
{ &xr.thumb_touch, "/user/hand/left/input/view/touch" },
{ &xr.index_touch, "/user/hand/left/input/trigger/touch" },
{ &xr.index_touch, "/user/hand/right/input/trigger/touch" },
};
if (xr.has_frame_controller)
{
suggest_bindings("/interaction_profiles/valve/frame_controller_valve", frame_controller,
sizeof(frame_controller) / sizeof(frame_controller[0]), frame_fingers,
sizeof(frame_fingers) / sizeof(frame_fingers[0]));
suggest("/interaction_profiles/valve/frame_controller_valve", frame_controller,
sizeof(frame_controller) / sizeof(frame_controller[0]));
}
suggest_bindings("/interaction_profiles/oculus/touch_controller", touch, sizeof(touch) / sizeof(touch[0]),
touch_fingers, sizeof(touch_fingers) / sizeof(touch_fingers[0]));
suggest_bindings("/interaction_profiles/valve/index_controller", index, sizeof(index) / sizeof(index[0]),
index_fingers, sizeof(index_fingers) / sizeof(index_fingers[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]));
}
@@ -865,21 +781,6 @@ static int action_boolean(XrAction action, int hand)
return state.currentState != XR_FALSE;
}
/* (and whether the runtime gives the action for the hand at all) */
static int action_boolean_active(XrAction action, int hand, int *active)
{
XrActionStateGetInfo information = { XR_TYPE_ACTION_STATE_GET_INFO };
XrActionStateBoolean state = { XR_TYPE_ACTION_STATE_BOOLEAN };
*active = 0;
information.action = action;
information.subactionPath = xr.hands[hand].path;
if (!XR_SUCCEEDED(xrGetActionStateBoolean(xr.session, &information, &state)) || !state.isActive)
return 0;
*active = 1;
return state.currentState != XR_FALSE;
}
static int action_vector(XrAction action, int hand, float vector[2])
{
XrActionStateGetInfo information = { XR_TYPE_ACTION_STATE_GET_INFO };
@@ -946,8 +847,11 @@ static void hands_read(struct halo_xr_frame *frame)
{
out->flags = flags_from_location(location.locationFlags);
pose_from_xr(&out->grip, &location.pose);
xr.grips[hand] = location.pose;
xr.grips_valid[hand] = (out->flags & HALO_XR_POSE_VALID) != 0;
if (hand == HALO_XR_HAND_LEFT)
{
xr.left_grip = location.pose;
xr.left_grip_valid = (out->flags & HALO_XR_POSE_VALID) != 0;
}
}
location.locationFlags = 0;
if (XR_SUCCEEDED(xrLocateSpace(xr.hands[hand].aim_space, xr.local_space,
@@ -977,17 +881,6 @@ static void hands_read(struct halo_xr_frame *frame)
(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);
{
/* the fingers' touch, and whether there is any */
int thumb_active = 0, index_active = 0;
if (action_boolean_active(xr.thumb_touch, hand, &thumb_active))
out->buttons |= HALO_XR_BUTTON_THUMB_TOUCH;
if (action_boolean_active(xr.index_touch, hand, &index_active))
out->buttons |= HALO_XR_BUTTON_INDEX_TOUCH;
if (thumb_active)
out->buttons |= HALO_XR_BUTTON_TOUCH_KNOWN;
}
}
frame->controller_layout = xr.frame_layout ? HALO_XR_LAYOUT_FRAME : HALO_XR_LAYOUT_TOUCH;
if (xr.frame_layout)
@@ -1045,9 +938,7 @@ On the back of the left wrist, like a watch: behind the palm, where the
grip pose is, and out of the back of the hand. In the left grip pose -Z
runs to the fingers, +Y up the handle and -X out of the back of the hand;
the layer faces -X with its top towards the elbow, as the wrist turned to
the eyes reads it. Shown only while it is. Left-handed (wrist_hand), on the
right wrist, as the mirror image: there +X runs out of the back of the
hand. */
the eyes reads it. Shown only while it is. */
#define WRIST_IMAGE_SIZE 512
#define CROSSHAIR_IMAGE_SIZE 256
@@ -1087,32 +978,26 @@ static void quaternion_rotate_vector(const XrQuaternionf *q, const XrVector3f *v
static int wrist_pose(const struct halo_xr_submit *submit, XrPosef *pose)
{
/* the layer's axes in the left grip's: x = -Y, y = +Z, z = -X, so the
layer's right and down, as it is read, are the grip's -Y and -Z; in the
right grip's x = +Y, y = +Z, z = +X (the mirror image, read the right
way round: its x turned back) */
static const XrQuaternionf layer_in_left_grip = { 0.5f, -0.5f, -0.5f, 0.5f };
static const XrQuaternionf layer_in_right_grip = { 0.5f, 0.5f, 0.5f, 0.5f };
int right = submit->wrist_hand == HALO_XR_HAND_RIGHT;
const XrPosef *grip = &xr.grips[right ? HALO_XR_HAND_RIGHT : HALO_XR_HAND_LEFT];
const XrVector3f offset = { right ? WRIST_ABOVE_HAND : -WRIST_ABOVE_HAND,
-WRIST_DOWN_HANDLE - submit->wrist_right, WRIST_BEHIND_PALM - submit->wrist_down };
/* the layer's axes in the grip's: x = -Y, y = +Z, z = -X, so the
layer's right and down, as it is read, are the grip's -Y and -Z */
static const XrQuaternionf layer_in_grip = { 0.5f, -0.5f, -0.5f, 0.5f };
const XrVector3f offset = { -WRIST_ABOVE_HAND, -WRIST_DOWN_HANDLE - submit->wrist_right,
WRIST_BEHIND_PALM - submit->wrist_down };
const XrVector3f layer_normal = { 0.0f, 0.0f, 1.0f };
/* clockwise as read: about the layer's normal, negatively (the mirror
image's the other way) */
float half_turn = (right ? 1.0f : -1.0f) * submit->wrist_turn * WRIST_PI / 360.0f;
/* clockwise as read: about the layer's normal, negatively */
float half_turn = -submit->wrist_turn * WRIST_PI / 360.0f;
XrQuaternionf turn = { 0.0f, 0.0f, sinf(half_turn), cosf(half_turn) };
XrQuaternionf unturned;
XrVector3f moved, normal, to_eyes;
float distance;
if (!xr.grips_valid[right ? HALO_XR_HAND_RIGHT : HALO_XR_HAND_LEFT])
if (!xr.left_grip_valid)
return 0;
quaternion_rotate_vector(&grip->orientation, &offset, &moved);
pose->position.x = grip->position.x + moved.x;
pose->position.y = grip->position.y + moved.y;
pose->position.z = grip->position.z + moved.z;
quaternion_multiply(&grip->orientation, right ? &layer_in_right_grip : &layer_in_left_grip, &unturned);
quaternion_rotate_vector(&xr.left_grip.orientation, &offset, &moved);
pose->position.x = xr.left_grip.position.x + moved.x;
pose->position.y = xr.left_grip.position.y + moved.y;
pose->position.z = xr.left_grip.position.z + moved.z;
quaternion_multiply(&xr.left_grip.orientation, &layer_in_grip, &unturned);
quaternion_multiply(&unturned, &turn, &pose->orientation);
quaternion_rotate_vector(&pose->orientation, &layer_normal, &normal);
@@ -1375,10 +1260,6 @@ int host_xr_initialize(struct halo_xr_info *info, float wanted_refresh_rate)
xr.get_refresh_rate(xr.session, &info->refresh_rate);
}
/* (the runtime's GL binding brings contexts of its own, with their own
trace markers: host_gl.c) */
host_gl_quiet_trace_markers();
if (xr.has_metrics)
{
PFN_xrEnumeratePerformanceMetricsCounterPathsMETA enumerate = NULL;
@@ -1560,7 +1441,7 @@ int host_xr_begin_frame(struct halo_xr_frame *frame)
pose_from_xr(&frame->head, &head.pose);
xr.head = head.pose;
}
xr.grips_valid[HALO_XR_HAND_LEFT] = xr.grips_valid[HALO_XR_HAND_RIGHT] = 0;
xr.left_grip_valid = 0;
if (xr.focused)
hands_read(frame);
return 1;
@@ -2052,34 +1933,6 @@ void host_xr_set_vignette(float strength)
xr.vignette = strength < 0.0f ? 0.0f : strength > 1.0f ? 1.0f : strength;
}
/* the VR options' refresh rates: those the runtime offers (none where it
lets none be chosen, as SteamVR, whose rate is SteamOS's), and the one it
runs at, by XR_FB_display_refresh_rate or else by its frames' period */
int host_xr_refresh_rates(float *rates, int maximum, float *current)
{
float offered[16];
uint32_t count = 0, index;
*current = 0.0f;
if (!xr.initialized)
return 0;
if (xr.has_refresh_rate)
xr.get_refresh_rate(xr.session, current);
if (*current <= 0.0f && xr.frame_state.predictedDisplayPeriod > 0)
*current = 1e9f / (float)xr.frame_state.predictedDisplayPeriod;
if (!xr.has_refresh_rate || !XR_SUCCEEDED(xr.enumerate_refresh_rates(xr.session, 16, &count, offered)))
return 0;
for (index = 0; index < count && (int)index < maximum; index++)
rates[index] = offered[index];
return (int)index;
}
void host_xr_set_refresh_rate(float wanted)
{
if (xr.initialized)
refresh_rate_choose(wanted);
}
void host_xr_haptic(int hand, float amplitude, float seconds)
{
XrHapticActionInfo information = { XR_TYPE_HAPTIC_ACTION_INFO };
-2
View File
@@ -61,8 +61,6 @@ host_xr_end_frame
host_xr_haptic
host_xr_set_vignette
host_xr_copy_layer_array
host_xr_refresh_rates
host_xr_set_refresh_rate
host_android_path
host_errno
+5 -14
View File
@@ -24,7 +24,7 @@ enum
HALO_XR_LAYER_EYE_LEFT = 0,
HALO_XR_LAYER_EYE_RIGHT = 1,
HALO_XR_LAYER_OVERLAY = 2, /* HUD, menus, cinematics: a flat quad */
HALO_XR_LAYER_WRIST = 3, /* the motion tracker, on the free hand's wrist */
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_NAMES = 6, /* players' names, each a quad over a head */
@@ -63,12 +63,6 @@ enum
HALO_XR_BUTTON_DPAD_DOWN = 1 << 10,
HALO_XR_BUTTON_DPAD_LEFT = 1 << 11,
HALO_XR_BUTTON_DPAD_RIGHT = 1 << 12,
/* the fingers' touch (the hands drawn: vr.c): the thumb on the stick, a
button or the rest; the index finger on the trigger; and whether the
controller tells the thumb's at all */
HALO_XR_BUTTON_THUMB_TOUCH = 1 << 13,
HALO_XR_BUTTON_INDEX_TOUCH = 1 << 14,
HALO_XR_BUTTON_TOUCH_KNOWN = 1 << 15,
};
/* the controllers' layout, for what the game tells the player */
@@ -155,18 +149,15 @@ struct halo_xr_submit
float overlay_width; /* metres; the height follows from 4:3 */
/* the screen's placement in LOCAL space (HALO_XR_OVERLAY_SCREEN) */
struct halo_xr_pose screen;
/* the wrist layer was filled: shown on the back of the left wrist (of
the right one, left-handed: wrist_hand, a HALO_XR_HAND_*), this wide
(metres), while the wrist is turned to the eyes */
/* the wrist layer was filled: shown on the back of the left wrist,
this wide (metres), while the wrist is turned to the eyes */
int wrist;
float wrist_size;
/* its placement as the left wrist turned to the eyes reads it: turned
this many degrees clockwise, and moved this far right and down (metres;
on the right wrist, the mirror image) */
/* its placement as the wrist turned to the eyes reads it: turned this
many degrees clockwise, and moved this far right and down (metres) */
float wrist_turn;
float wrist_right;
float wrist_down;
int wrist_hand;
/* the scope layer was filled: a disc this wide (metres), here in LOCAL
space, facing back along the weapon */
int scope;
-3
View File
@@ -315,9 +315,6 @@
<bitmap name="main_menu/multiplayer_type_select/join_game/header_direct_link">
<frame png="ce/port/main_menu/multiplayer_type_select/join_game/header_direct_link.png" width="512" height="64"/>
</bitmap>
<bitmap name="main_menu/multiplayer_type_select/join_game/header_password">
<frame png="ce/port/main_menu/multiplayer_type_select/join_game/header_password.png" width="512" height="64"/>
</bitmap>
<bitmap name="main_menu/multiplayer_type_select/join_game/header_server_browser">
<frame png="ce/port/main_menu/multiplayer_type_select/join_game/header_server_browser.png" width="512" height="64"/>
</bitmap>
@@ -1,57 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- The port's multiplayer screens, in the PC version's style (tools/port_settings.py) -->
<menus>
<widget name="main_menu/multiplayer_type_select/join_game/password/password_screen" width="640" height="480" flags="pass_unhandled_to_focused_child pause_game" bitmap="bitmaps/gradient">
<on event="b" run="port password back"/>
<on event="back" run="port password back"/>
<child widget="main_menu/multiplayer_type_select/join_game/password/header_password"/>
<child widget="main_menu/multiplayer_type_select/join_game/password/options_menu"/>
</widget>
<widget name="main_menu/multiplayer_type_select/join_game/password/header_password" controller="1" left="35" top="11" width="605" height="59" bitmap="main_menu/multiplayer_type_select/join_game/header_password">
</widget>
<widget name="main_menu/multiplayer_type_select/join_game/password/help" type="text" controller="1" left="68" top="120" width="482" height="60" string_list="main_menu/multiplayer_type_select/join_game/password/help_strings" font="ui\large_ui" color="#FFFFFFFF">
</widget>
<widget name="main_menu/multiplayer_type_select/join_game/password/options_menu" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/multiplayer_type_select/join_game/password/help">
<data input="port password update"/>
<on event="created" run="port password init"/>
<child widget="main_menu/multiplayer_type_select/join_game/password/op_password" x="54" y="73"/>
<child widget="main_menu/multiplayer_type_select/join_game/password/button_bar" y="414"/>
</widget>
<widget name="main_menu/multiplayer_type_select/join_game/password/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
<child widget="main_menu/multiplayer_type_select/join_game/password/button_ok" x="380" y="1"/>
<child widget="common_button_cancel" x="510" y="1"/>
</widget>
<widget name="main_menu/multiplayer_type_select/join_game/password/op_password" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<on event="a" run="port password edit"/>
<on event="start" run="port password edit"/>
<on event="b"/>
<on event="back"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/multiplayer_type_select/join_game/password/password_label"/>
<child widget="main_menu/multiplayer_type_select/join_game/password/password_value" x="230" y="1"/>
</widget>
<widget name="main_menu/multiplayer_type_select/join_game/password/password_label" type="text" controller="1" width="230" height="22" string_list="main_menu/multiplayer_type_select/join_game/password/labels" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/multiplayer_type_select/join_game/password/password_value" type="text" controller="1" width="280" height="22" font="ui\small_ui" color="#FF2896FF" align="center" text_y="5" text_flags="no_focus_test">
</widget>
<widget name="main_menu/multiplayer_type_select/join_game/password/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<widget name="main_menu/multiplayer_type_select/join_game/password/button_ok" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" text="JOIN GAME" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port password join"/>
<on event="start" run="port password join"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<strings name="main_menu/multiplayer_type_select/join_game/password/labels">
<string text="PASSWORD:"/>
</strings>
<strings name="main_menu/multiplayer_type_select/join_game/password/help_strings">
<string text=""/>
<string text="This game has a password. Type it, then press\nEnter to join."/>
<string text="That is not the game's password. Try again."/>
<string text="Could not join the game."/>
</strings>
<widget name="main_menu/multiplayer_type_select/join_game/header_server_browser" controller="1" left="35" top="11" width="363" height="59" bitmap="main_menu/multiplayer_type_select/join_game/header_server_browser">
</widget>
<widget name="main_menu/multiplayer_type_select/join_game/header_direct_link" controller="1" left="35" top="11" width="363" height="59" bitmap="main_menu/multiplayer_type_select/join_game/header_direct_link">
@@ -9,27 +9,25 @@
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/header_server_settings" controller="1" left="35" top="11" width="605" height="59" bitmap="main_menu/multiplayer_type_select/server_settings/header_server_settings">
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/help" type="text" controller="1" left="68" top="366" width="482" height="60" string_list="main_menu/multiplayer_type_select/server_settings/help_strings" font="ui\large_ui" color="#FFFFFFFF">
<widget name="main_menu/multiplayer_type_select/server_settings/help" type="text" controller="1" left="68" top="358" width="482" height="60" string_list="main_menu/multiplayer_type_select/server_settings/help_strings" font="ui\large_ui" color="#FFFFFFFF">
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/options_menu" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/multiplayer_type_select/server_settings/help">
<data input="server settings update"/>
<on event="created" run="server settings init"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_server_name" x="54" y="73"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_max_players" x="54" y="99"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_invite" x="54" y="125"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_listing" x="54" y="151"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_password" x="54" y="177"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_friendly_fire" x="54" y="203"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_extra_enemies" x="54" y="229"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_enemies_per_player" x="54" y="255"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_enemies_multiplier" x="54" y="255"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_player_collisions" x="54" y="281"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_game_type" x="54" y="203"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_player_options" x="54" y="229"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_item_options" x="54" y="255"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_vehicle_options" x="54" y="281"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_indicator_options" x="54" y="307"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_team_options" x="54" y="333"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_max_players" x="54" y="101"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_invite" x="54" y="129"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_listing" x="54" y="157"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_friendly_fire" x="54" y="185"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_extra_enemies" x="54" y="213"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_enemies_per_player" x="54" y="241"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_enemies_multiplier" x="54" y="241"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_game_type" x="54" y="185"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_player_options" x="54" y="213"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_item_options" x="54" y="241"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_vehicle_options" x="54" y="269"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_indicator_options" x="54" y="297"/>
<child widget="main_menu/multiplayer_type_select/server_settings/op_team_options" x="54" y="325"/>
<child widget="main_menu/multiplayer_type_select/server_settings/button_bar" y="414"/>
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
@@ -80,19 +78,6 @@
<widget name="main_menu/multiplayer_type_select/server_settings/listing_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="PUBLIC|PRIVATE" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="left_mouse" run="mouse spinner 1wide click"/>
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/op_password" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<on event="a" run="ss edit server password"/>
<on event="start" run="ss edit server password"/>
<on event="b"/>
<on event="back"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/multiplayer_type_select/server_settings/password_label"/>
<child widget="main_menu/multiplayer_type_select/server_settings/password_value" x="230" y="1"/>
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/password_label" type="text" controller="1" width="230" height="22" string_list="main_menu/multiplayer_type_select/server_settings/labels" string_index="15" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/password_value" type="text" controller="1" width="280" height="22" font="ui\small_ui" color="#FF2896FF" align="center" text_y="5" text_flags="no_focus_test">
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/op_friendly_fire" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/multiplayer_type_select/server_settings/friendly_fire_label"/>
<child widget="main_menu/multiplayer_type_select/server_settings/friendly_fire_spinner" x="286" y="1"/>
@@ -137,17 +122,6 @@
<on event="deleted" run="port setting save"/>
<on event="left_mouse" run="mouse spinner 1wide click"/>
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/op_player_collisions" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/multiplayer_type_select/server_settings/player_collisions_label"/>
<child widget="main_menu/multiplayer_type_select/server_settings/player_collisions_spinner" x="286" y="1"/>
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/player_collisions_label" type="text" controller="1" width="300" height="22" string_list="main_menu/multiplayer_type_select/server_settings/labels" string_index="14" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/player_collisions_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="network.coop_player_collisions" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
<on event="created" run="port setting load"/>
<on event="deleted" run="port setting save"/>
<on event="left_mouse" run="mouse spinner 1wide click"/>
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/op_game_type" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<on event="a" run="port setup edit" open="main_menu/settings_select/multiplayer_setup/playlist_edit/game_type_select/gametype_select_screen"/>
<on event="start" run="port setup edit" open="main_menu/settings_select/multiplayer_setup/playlist_edit/game_type_select/gametype_select_screen"/>
@@ -237,8 +211,6 @@
<string text="EXTRA ENEMIES:"/>
<string text="PER PLAYER:"/>
<string text="MULTIPLIER:"/>
<string text="PLAYER COLLISIONS:"/>
<string text="PASSWORD:"/>
</strings>
<strings name="main_menu/multiplayer_type_select/server_settings/help_strings">
<string text=""/>
@@ -262,8 +234,5 @@
<string text="Enemy squads are the amount below times as large,\nhowever many players there are."/>
<string text="For each player past the first, enemy squads get\nthis much more of themselves (100%: as many again)."/>
<string text="Each enemy squad is this many times as large."/>
<string text="Players bump into each other, as in the campaign."/>
<string text="Players walk through each other, so that no one\nblocks a doorway. The AI's characters still block."/>
<string text="Players from the Server Browser must type it to\njoin (an invite link needs none). Enter sets it."/>
</strings>
</menus>
@@ -16,8 +16,7 @@
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_volume" x="54" y="73"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_music_volume" x="54" y="103"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_effects_volume" x="54" y="133"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_reverb" x="54" y="163"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_enabled" x="54" y="193"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_enabled" x="54" y="163"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/button_bar" y="414"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
@@ -52,20 +51,11 @@
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/effects_volume_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="0|1|2|3|4|5|6|7|8|9|10" setting="audio.effects_volume" values="0|0.1|0.2|0.3|0.4|0.5|0.6|0.7|0.8|0.9|1" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_reverb" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/reverb_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/reverb_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/reverb_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/labels" string_index="3" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/reverb_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="audio.reverb" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_enabled" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/enabled_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/enabled_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/enabled_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/labels" string_index="4" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/enabled_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/labels" string_index="3" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/enabled_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="audio.enabled" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
@@ -84,7 +74,6 @@
<string text="MASTER VOLUME:"/>
<string text="MUSIC VOLUME:"/>
<string text="EFFECTS VOLUME:"/>
<string text="REVERB:"/>
<string text="SOUND:"/>
</strings>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/help_strings">
@@ -92,7 +81,6 @@
<string text="The volume of everything."/>
<string text="The music's volume."/>
<string text="The volume of every other sound: effects and\nspeech."/>
<string text="Echo sounds as the place you are in does, and\nmuffle those behind walls, as the Xbox did."/>
<string text="Play sound at all; from the next time the game\nstarts."/>
</strings>
</menus>
@@ -9,24 +9,20 @@
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/header_profile_video_settings" controller="1" left="35" top="11" width="605" height="59" bitmap="main_menu/settings_select/player_setup/player_profile_edit/video_settings/header_profile_video_settings">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/help" type="text" controller="1" left="68" top="364" width="482" height="60" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/help_strings" font="ui\large_ui" color="#FFFFFFFF">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/help" type="text" controller="1" left="68" top="350" width="482" height="60" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/help_strings" font="ui\large_ui" color="#FFFFFFFF">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/options_menu" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/settings_select/player_setup/player_profile_edit/video_settings/help">
<data input="port settings help"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_mode" x="54" y="73" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_resolution" x="54" y="97" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_window_size" x="54" y="97" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_resolution_scaling" x="54" y="121" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_vsync" x="54" y="145"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_max_fps" x="54" y="169" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_interpolation" x="54" y="193"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_direct_camera" x="54" y="217" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_high_res_hud" x="54" y="241"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_high_res_text" x="54" y="265"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_anti_aliasing" x="54" y="289" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_anti_aliasing_android" x="54" y="289" platform="android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_shadow_resolution" x="54" y="313"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_per_pixel_lighting" x="54" y="337"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_resolution" x="54" y="99" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_window_size" x="54" y="99" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_resolution_scaling" x="54" y="125" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_vsync" x="54" y="151"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_max_fps" x="54" y="177" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_interpolation" x="54" y="203"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_direct_camera" x="54" y="229" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_high_res_hud" x="54" y="255"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_high_res_text" x="54" y="281"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/button_bar" y="414"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
@@ -124,42 +120,6 @@
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/high_res_text_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.high_res_text" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_anti_aliasing" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF" platform="desktop">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/anti_aliasing_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/anti_aliasing_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/anti_aliasing_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="10" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/anti_aliasing_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="OFF|FXAA|SMAA|SSAA 2X|MSAA 2X|MSAA 4X|MSAA 8X" setting="display.anti_aliasing" values="off|fxaa|smaa|ssaa2x|msaa2x|msaa4x|msaa8x" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_anti_aliasing_android" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF" platform="android">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/anti_aliasing_android_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/anti_aliasing_android_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/anti_aliasing_android_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="11" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/anti_aliasing_android_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="OFF|FXAA|MSAA 2X|MSAA 4X" setting="display.anti_aliasing" values="off|fxaa|msaa2x|msaa4x" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_shadow_resolution" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/shadow_resolution_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/shadow_resolution_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/shadow_resolution_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="12" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/shadow_resolution_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="128|256|512|1024" setting="display.shadow_resolution" values="128|256|512|1024" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_per_pixel_lighting" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/per_pixel_lighting_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/per_pixel_lighting_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/per_pixel_lighting_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="13" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/per_pixel_lighting_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.per_pixel_lighting" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port settings defaults"/>
<on event="start" run="port settings defaults"/>
@@ -181,10 +141,6 @@
<string text="INSTANT AIM:"/>
<string text="HIGH-RES HUD:"/>
<string text="HIGH-RES TEXT:"/>
<string text="ANTI-ALIASING:"/>
<string text="ANTI-ALIASING:"/>
<string text="SHADOW RESOLUTION:"/>
<string text="PER-PIXEL LIGHTING:"/>
</strings>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/help_strings">
<string text=""/>
@@ -198,9 +154,5 @@
<string text="In first person, turn the view the moment the\nmouse moves, not up to two ticks later."/>
<string text="Draw the HUD from the high-res redraws; off\ndraws the game's own pictures."/>
<string text="Draw text and titles with high-res fonts; off\ndraws the game's own."/>
<string text="Smooth jagged edges, which the Xbox did not. FXAA\nand SMAA are cheap; SSAA and MSAA are sharper."/>
<string text="Smooth jagged edges, which the Xbox did not. FXAA\nis cheap; MSAA is sharper."/>
<string text="The size objects' shadows are drawn at: 128 as on\nthe Xbox; larger for smoother, as soft, edges."/>
<string text="Light models for each pixel, without the facets\nof the Xbox's lighting for each vertex."/>
</strings>
</menus>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

-1
View File
@@ -54,7 +54,6 @@
"ce/port/blank__0.png",
"ce/port/main_menu/multiplayer_type_select/coop/header_player_2.png",
"ce/port/main_menu/multiplayer_type_select/join_game/header_direct_link.png",
"ce/port/main_menu/multiplayer_type_select/join_game/header_password.png",
"ce/port/main_menu/multiplayer_type_select/join_game/header_server_browser.png",
"ce/port/main_menu/multiplayer_type_select/lobby/header_add_player.png",
"ce/port/main_menu/multiplayer_type_select/lobby/header_lobby.png",
@@ -1,13 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="64" viewBox="0 0 512 64">
<defs>
<filter id="soft" filterUnits="userSpaceOnUse" x="-60" y="-60" width="632" height="184">
<feGaussianBlur stdDeviation="14.23 13.06"/>
</filter>
<mask id="cut" maskUnits="userSpaceOnUse" x="0" y="0" width="512" height="64">
<rect width="512" height="59" fill="#fff"/>
</mask>
</defs>
<g mask="url(#cut)">
<rect x="29.08" y="28.05" width="216.45" height="40.95" fill="#021931" fill-opacity="0.893" filter="url(#soft)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 563 B

+18 -37
View File
@@ -56,17 +56,13 @@ with ideas from VALORANT's netcode articles, keeping the 30 Hz tick:
BSP the team hasn't been in brings every player to whoever crossed it,
as split screen does; one back into a BSP it has been in switches only
with two thirds of the living players at it (in the trigger, or within
15 world units of the player in it, about 45 metres), so one player can't
drag the team back through the level; a player held back is told how
many are there and how many it needs. A player outside the loaded BSP
and falling for two seconds is brought back beside a teammate. A dead
player watches a living teammate (`coop_spectate.c`) and comes back
beside one once it is safe. With everyone dead they come back where they
were at the last checkpoint, without a revert. A mission the scripts fail
with players still alive reverts to the last checkpoint on the host, as a
skipped cutscene does (below); a client never reverts on its own. A level
won ends the round as in multiplayer, and the next round is the
campaign's next level.
4 world units of the player in it), so one player can't drag the team
back through the level. A player outside the loaded BSP and falling for
two seconds is brought back beside a teammate. A dead player watches a
living teammate (`coop_spectate.c`) and comes back beside one once it is
safe. With everyone dead they come back where they were at the last
checkpoint, since only the host could revert. A level won ends the round
as in multiplayer, and the next round is the campaign's next level.
Cutscenes are skipped by vote: more than half the machines must press
skip. The host then reverts as single player does, but keeps its clock
@@ -77,13 +73,12 @@ with ideas from VALORANT's netcode articles, keeping the 30 Hz tick:
The host's EXTRA ENEMIES (`coop_enemies.c`, `network.coop_enemies_mode`)
give each squad of enemies a level places more of itself: PER PLAYER, a
percentage of itself for each player past the first; STATIC MULTIPLIER,
that many times itself for any number of players. They stand around its
starting locations on free ground (the same floor, clear of crates and
other actors, with room to stand), or where none is left on rings about
them as before, and never take the actors a level needs for its own (the
actor pool, `halo_port_capacity.h`, holds 1024). Riders a dropship has no
seats for are kept, and placed beside its riders once they get out. Only
the host runs the AI, so the clients see them as the host's other actors.
that many times itself for any number of players. They stand in rings
about its starting locations where the ground is open, and never take the
actors a level needs for its own (the actor pool, `halo_port_capacity.h`,
holds 1024). Riders a dropship has no seats for are kept, and placed
beside its riders once they get out. Only the host runs the AI, so the
clients see them as the host's other actors.
- **Host authoritative.** The host alone decides damage, deaths, spawns,
pickups, scores and the game's objects; clients do not decide them but
apply what the host sends.
@@ -164,16 +159,7 @@ version 13 drives up to 1056 of the host's AI units on its clients (co-op's
extra enemies), where 12 drove 288; version 14 sends co-op's device positions
and its units opening and closing; version 15 sends co-op's allegiances with
its presentation, a message of another size; version 16 has a client's input
say which structure BSP it has loaded (co-op); version 17 breaks the host's
glass and destructible scenery on every machine (and takes a client's hits on
scenery), sends the cluster a co-op cutscene keeps active, and leaves a
failed co-op mission's revert to the host; version 18 sends with an object
the bitmap of its shaders it draws with when its actor variant set one (co-op:
the Elite major's and commander's armor); version 19 sends with the game's
settings whether co-op's players collide with each other (Server Setup's PLAYER
COLLISIONS: each machine's players then pass through the others'); version 20
lists a public game with a password with its invite's token sealed with the
password's key (`p2p_lobby.c`), a listing of another layout.
say which structure BSP it has loaded (co-op).
A client plays by its host's rules: in another's game (searching for it,
in its lobby, or playing it) the developer console, the telnet console
@@ -195,13 +181,9 @@ only ever jumps forward to it when behind, so an honest one is never ahead
while going faster (one that caught up, or a host that stalled, is one or
the other, not both). One more than a tenth faster and half a second ahead
has its players' predictions refused at once (the host's copies go as its
own ticks have them), and after ten seconds of it is dropped, and every
machine is told who, in red on its console and in its `debug.txt`. Its
address is kept out of the host's games while the host runs only when a
message that came over its connection's stream was that far ahead too: a
datagram is known to be the machine's only by the address it came from,
which another machine can send one as, so on its datagrams alone it is
dropped, logged, and may join again
own ticks have them), and after ten seconds of it is dropped, its address
kept out of the host's games while the host runs, and every machine is
told who, in red on its console and in its `debug.txt`
(`distributed_note_client_clock`, `network_game_server_kick_machine`,
`_distributed_message_notice`). The host also adds a line to
`cheaters.txt` beside its `debug.txt`: when, the player's address (an
@@ -216,8 +198,7 @@ A joining machine tells the host its hardware id: a keyed hash (HMAC-SHA-256,
16 bytes as hex) of what its machine is known by (Windows' SMBIOS UUID, else
its MachineGuid; Linux's `/etc/machine-id`; Android's `ANDROID_ID`, which the
launcher writes to `hardware_id.txt`: `p2p_hardware_id`), kept by the host
as hex only. A player dropped for cheating (on its stream's word, as
above), and one the host bans with the
as hex only. A player dropped for cheating, and one the host bans with the
console's `ban <player name>` (Tab completes the name; the host's alone), is
added to `bans.txt` beside `debug.txt` (a line each, as in `cheaters.txt`,
with `ip=` and `hwid=`): the host refuses a machine joining whose address or
+8 -35
View File
@@ -228,25 +228,21 @@ the setting for one start of the game. It has priority over the file.
| `display.window_scale` | `2` | `HALO_WINDOW_SCALE` | Used when `display.window_size` is empty: the size of the window, as a multiple of 640x480. |
| `display.vsync` | `true` | `HALO_NO_VSYNC=1` sets `false` | `true`: each frame waits for the display. |
| `display.max_fps` | `0` | `HALO_MAX_FPS` | With vsync off, the most frames each second. `0`: twice the display's refresh rate. `-1`: no limit, which can hang some Intel graphics (Raptor Lake), resetting the desktop's graphics too. |
| `display.anti_aliasing` | `"off"` | `HALO_ANTI_ALIASING` | The smoothing of jagged edges, which the Xbox did not have. `"off"`: none, as on the Xbox. `"fxaa"` or `"smaa"`: a pass over the 3D view after the game draws it. The HUD and the menus stay sharp. SMAA is the sharper and costs more. `"ssaa2x"`: the game draws at two times the resolution in each direction (at most the GPU's largest texture), and the picture is scaled down. The GPU does four times the work. Not with `display.resolution_scaling = "original"`. `"msaa2x"`, `"msaa4x"` or `"msaa8x"`: each pixel of the 3D view has that many samples (at most the GPU's). On Android, `"smaa"` gives FXAA and `"ssaa2x"` none. A change applies from the next frame. Refer to "Anti-aliasing" in "What operates". |
| `debug.gpu_flush_draws` | `-1` | `HALO_GPU_FLUSH_DRAWS` | Flush the GPU's pipeline every this many draws. `-1`: every 3 on Intel graphics with Mesa's driver, which can otherwise hang in the game's long runs of small draws and reset the desktop's graphics too. `0`: never. |
| `display.interpolation` | `true` | `HALO_INTERPOLATION` | `true`: one frame for each refresh of the display. `false`: 30 frames each second, as on the Xbox. Refer to "Frame rate". |
| `display.direct_camera` | `true` | `HALO_DIRECT_CAMERA` | `true`: in first person, on foot, the view points where the player aims in each frame, not where the last tick left it. Refer to "Frame rate". |
| `display.high_res_hud` | `true` | `HALO_HIGH_RES_HUD` | `true`: the HUD (meters, counters, panels and their outlines, the motion sensor, reticles, waypoints, scopes) is drawn from the high-res assets in `port/assets/hud`, 8x the size of the maps' bitmaps. The bitmaps with English text keep the maps' own. `false`: the maps' own bitmaps. |
| `display.high_res_text` | `true` | `HALO_HIGH_RES_TEXT` | `true`: the menus' and HUD's text is drawn with the fonts in `port/assets/fonts` (Overpass, in place of the maps' Interstate) at the resolution the game draws at, laid out as before, and the menus' titles are drawn from the high-res pictures in `port/assets/titles`. `false`: the maps' bitmap fonts and titles. |
| `display.shadow_resolution` | `128` | `HALO_SHADOW_RESOLUTION` | The size of the maps that the shadows of the objects are drawn in, in pixels each way: `128`, `256`, `512` or `1024` (other values go down to one of these). The game draws the shadow of each object into a map of 128x128 pixels, blurs it and projects it onto the ground. On a large screen, the edges of these shadows show steps that move when the object moves. A larger map makes the edges smooth; the blur is made wider to match, so the shadows are as soft as on the Xbox. Each doubling adds two passes of the blur. `128`: as on the Xbox. |
| `display.menus` | `"pc"` | `HALO_MENUS` | `"pc"`: the PC version's menus, from the files in `port/assets/menus` and a `menus` folder next to `config.toml`. Refer to "Menus". `"xbox"`: the Xbox's menus. |
| `display.player_names` | `"all"` | `HALO_PLAYER_NAMES` | In multiplayer, whose names are drawn above their heads: `"all"`, `"allies"`, `"enemies"` or `"none"`. An ally's name is drawn above the triangle the game shows over teammates. An enemy's name shows only within the motion sensor's reach, while the enemy is in sight and not camouflaged, so it never shows where an enemy hides. The gametype's motion tracker setting also applies: no names if it shows no players, only allies' if it shows only friends. |
| `display.player_name_scale` | `1.0` | `HALO_PLAYER_NAME_SCALE` | How large the players' names are drawn: `1.0` is three quarters of the size of the HUD's text, from `0.25` to `4`. With high-res text, larger names are rasterized at their size, so they stay sharp. |
| `display.scoreboard_team_layout` | `"teams"` | `HALO_SCOREBOARD_TEAM_LAYOUT` | How the multiplayer scoreboard (hold BACK, or tab) lists a team game's players. `"teams"`: a column for each team, red on the left and blue on the right. `"score"`: all the players in order of score. With more players than fit, the mouse wheel and Page Up / Page Down scroll the scoreboard. |
| `display.scoreboard_background` | `true` | `HALO_SCOREBOARD_BACKGROUND` | `true`: the multiplayer scoreboard (hold BACK, or tab) has a panel behind its text, for clearer text. |
| `display.scoreboard_background_color` | `"16, 16, 16, 150"` | `HALO_SCOREBOARD_BACKGROUND_COLOR` | The colour of the scoreboard's panel: `"red, green, blue, alpha"`, each from `0` to `255`. Alpha `0` is see-through, `255` is solid. |
| `display.per_pixel_lighting` | `false` | `HALO_PER_PIXEL_LIGHTING` | `false`: the models (characters, weapons, vehicles, scenery) are lit at each vertex and the light is blended between them, as on the Xbox. The light across a curved surface then shows facets, and a point light that passes close lights only the vertices it reaches. `true`: the models are lit at each pixel by the same lights (the ambient light, two distant lights and two point lights), which changes their look. |
| `audio.enabled` | `true` | `HALO_NO_AUDIO=1` sets `false` | `false`: no audio device. The sound continues without output. |
| `audio.volume` | `1.0` | `HALO_VOLUME` | The master volume. |
| `audio.music_volume` | `1.0` | `HALO_MUSIC_VOLUME` | The music's volume, of the master volume. |
| `audio.effects_volume` | `1.0` | `HALO_EFFECTS_VOLUME` | The volume of the other sounds (effects and speech), of the master volume. |
| `audio.reverb` | `true` | `HALO_REVERB` | `true`: the sounds of the world reverberate as the place the player is in does: the sound environments of the maps (a corridor, a cave, a large hall, outdoors) set the reverberation, as the I3DL2 reverb of the Xbox did. A sound behind a wall or a door is muffled in it too. `false`: no reverberation (sounds behind a wall are still muffled). |
| `input.mouse_sensitivity` | `1.0` | `HALO_MOUSE_SENSITIVITY` | The multiplier for the mouse aim. |
| `input.mouse_vertical_sensitivity` | `0.0` | `HALO_MOUSE_VERTICAL_SENSITIVITY` | The multiplier for the vertical mouse aim. `0`: the same as `input.mouse_sensitivity`. |
| `input.invert_mouse` | `false` | `HALO_MOUSE_INVERT=1` sets `true` | `true`: the vertical mouse aim is inverted. |
@@ -265,7 +261,6 @@ the setting for one start of the game. It has priority over the file.
| `network.public_lobby` | `true` | `HALO_NET_PUBLIC_LOBBY` | `true`: the server browser. Public games are listed, and Join Game > Server Browser shows them. `false`: no games are listed or shown. Refer to "Server browser". |
| `network.host_public` | `true` | `HALO_NET_HOST_PUBLIC` | `true`: a new game of Create Game > Internet starts as PUBLIC. `false`: it starts as PRIVATE. LISTING in Server Setup changes it for each game. Refer to "Server browser". |
| `network.coop_friendly_fire` | `"on"` | `HALO_NET_COOP_FRIENDLY_FIRE` | Whether the players of an online co-op game hurt each other: `"off"`, `"on"`, `"shields_only"` or `"explosives_only"`. FRIENDLY FIRE in co-op's Server Setup writes its choice here. Their AI allies they always can, as in the campaign. |
| `network.coop_player_collisions` | `true` | `HALO_NET_COOP_PLAYER_COLLISIONS` | Whether the players of an online co-op game bump into each other. `false`: they walk through each other, so that one cannot block a doorway or stand on another; they still bump into the AI's characters. PLAYER COLLISIONS in co-op's Server Setup writes its choice here. |
| `network.coop_enemies_mode` | `"per_player"` | `HALO_NET_COOP_ENEMIES_MODE` | Online co-op's extra enemies: `"none"`; `"per_player"`, each squad of enemies that a level places grows by `network.coop_enemies` for each player past the first; or `"multiplier"`, each squad is `network.coop_enemies_multiplier` times as large, for any number of players. The extra enemies stand around the squad's places, and those that a dropship has no seats for drop out of it after its passengers. EXTRA ENEMIES in co-op's Server Setup writes its choice here. |
| `network.coop_enemies` | `50` | `HALO_NET_COOP_ENEMIES` | The extra enemies per player, a percentage from `25` to `200`: for each player past the first, each squad of enemies gets this much of itself more (`100`: as many again, so four players meet four times the squad). PER PLAYER in co-op's Server Setup writes its choice here. |
| `network.coop_enemies_multiplier` | `2` | `HALO_NET_COOP_ENEMIES_MULTIPLIER` | The static multiplier of the enemies, `2` to `32`: each squad of enemies is this many times as large. MULTIPLIER in co-op's Server Setup writes its choice here. |
@@ -465,18 +460,10 @@ Each new game starts as PUBLIC (`network.host_public = false` makes new
games start as PRIVATE). An online co-op game starts as PRIVATE, and keeps
the last choice of its LISTING (`network.coop_public`). A LAN game is never listed. `network.public_lobby = false` turns the server browser off.
A PUBLIC game can also have a PASSWORD (a row of Server Setup, below
LISTING). The Server Browser shows a lock at the left of a game with a
password. A player who selects that game must type the password, and JOIN
GAME joins only with the correct password. The invite link of the game joins
it without the password. The host keeps the password only while the game
runs.
In the Server Browser, select a game to join it. The game joins the invite
of the game, as for a link. When it reaches the host, it opens the lobby.
If it cannot reach the host in 30 seconds, it marks the game FAILED. A game
that is full or starting shows CLOSED. REFRESH asks the hosts for their
listings again.
If it cannot reach the host in 30 seconds, it marks the game FAILED. REFRESH
asks the hosts for their listings again.
How it operates (`src/p2p_lobby.c`):
@@ -498,14 +485,6 @@ How it operates (`src/p2p_lobby.c`):
seconds.
- When a public game becomes private, the host makes a new invite. Thus a
player who saw the listing cannot join with the old invite.
- The listing of a game with a password does not hold the invite in clear
text. The secret part of the invite (its token) is encrypted with a key
from the password (Argon2id, salted with the key of the host, then
XChaCha20-Poly1305). The browser makes the key from the password that the
player types, and opens the invite only if the password is correct. When
the host sets or changes the password, it makes a new invite. A player who
has the listing can try passwords on their own machine without the host,
so use a long password.
A public game does not publish the address of the host. But any machine
with the invite can ask the host to connect, and the host then sends its
@@ -567,15 +546,12 @@ Only machines with the invite can find the game:
flood of requests can make players join more slowly. A player asks again
for 90 seconds.
- The host drops a player whose game runs faster than time (a speed hack)
for ten seconds. Each player sees who in red on the console. The host
adds a line to `cheaters.txt` (beside `debug.txt`) with the address and
hardware id of the player, and the Discord name and id that the game of
the player told it (a player can change these). If the messages on the
player's connection were also ahead, the host keeps that address out of
its games and bans the player: it adds the line to `bans.txt`, and refuses
a machine whose address or hardware id is in it. If only the player's
datagrams were ahead, the player can join again: another machine can send
datagrams with the player's address.
for ten seconds, and keeps that address out of its games. Each player
sees who in red on the console. The host adds a line to `cheaters.txt`
(beside `debug.txt`) with the address and hardware id of the player, and
the Discord name and id that the game of the player told it (a player can
change these). The host also bans the player: it adds the line to
`bans.txt`, and refuses a machine whose address or hardware id is in it.
- The host can ban a player with `ban <player name>` in the developer
console (Tab completes the name). Remove a line from `bans.txt` to unban.
Refer to `NETCODE.md`. `kick <player name>` drops the player the same
@@ -651,7 +627,6 @@ The game sends the activity only to a Discord client of the same user.
| Graphics | Direct3D 8 on OpenGL 4.5 core through SDL3 (`src/d3d8_gl.c`). The port translates the NV2A vertex shaders and register combiners to GLSL. It decodes all the Xbox texture formats. The vertex and index buffers come from a GL copy of the Xbox memory. |
| High-res HUD | The HUD is drawn from high-res assets: redraws at 8x the size of the maps' bitmaps (4x for the largest), in `port/assets/hud`. They cover the meters, counters, panels and their outlines, the motion sensor, reticles, waypoints and scopes, but no bitmap with English text. `tools/hud_assets.py` makes them from the SVG redraws, and the build embeds them in the executable. When the game uploads one of those bitmaps, `src/hud_hires.c` gives the high-res texture in its place, if the bitmap's pixels are those of the English maps: another language's maps keep their own. The game sizes and places the HUD from its tags as before. `display.high_res_hud = false` turns this off. |
| High-res text | The menus' and HUD's text is drawn with Overpass (`port/assets/fonts`, SIL Open Font License) in place of the maps' bitmap fonts, which are Interstate. `src/text_hires.c` rasterizes each glyph with stb_truetype (`port/third_party/stb`) at the resolution the game draws at, into an atlas that a placeholder bitmap of the game stands for. The game lays the text out from its font tags as before. The menus' titles (the screens' headers and the main menu's items) are pictures of text in the maps, so they are drawn as the high-res HUD is: `tools/title_assets.py` sets each one again in OpenCE, Roger White's public-domain Newtown respaced to match the maps' commercial title typeface (`tools/title_font.py`), at 4x the bitmap's size over its own plate or glow, each letter placed where the map's letter is, in `port/assets/titles`. The postgame carnage report's title is set over a hand-made SVG redraw of its panel (`port/assets/titles/svg`) instead. `display.high_res_text = false` turns it off. |
| Anti-aliasing | Off unless `display.anti_aliasing` is set (`src/d3d8_gl.c`, `src/xgpu_post.c`). FXAA (written in the port) and SMAA (`port/third_party/smaa`, MIT licensed, at its HIGH preset, compiled as GLSL) are passes over the 3D view of each window, after the lens flares and before the HUD and the menus (`render/render.c`). Their programs are built when the setting is chosen. Supersampling draws the render targets the size of the screen at two times the resolution in each direction, and the display blit scales them down. Multisampling draws the back buffer and its depth buffer into multisampled renderbuffers, and with them any target that is drawn together with one of them (a mirror's view, in the secondary target with the back buffer's depth buffer), so that the attachments of a framebuffer are all multisampled or none is. A target's pixels are resolved into its texture before something reads the texture (as a texture, or at the display blit). Visibility tests count samples, divided by the samples of a pixel. An alpha-tested surface (foliage, grates) covers the samples of a pixel in proportion to its alpha past the reference (`gl_SampleMask`, not on Android). |
| Sound | Xbox DirectSound on SDL3 audio (`src/dsound_sdl.c`): PCM and Xbox ADPCM, mixed at 48 kHz, with volume, pitch, mix bins, distance, stereo pan, occlusion and obstruction. There is no Doppler effect, no cones and no reverb. |
| Input | XInput on SDL3 (`src/xinput_sdl.c`): keyboard, mouse, gamepads with rumble, and the debug keyboard for the console. |
| Files | The Win32 file functions and the MSVC file functions on POSIX, with the translation of Xbox paths. |
@@ -740,8 +715,6 @@ Other changes:
| `interface/ui_widget.c`, `interface/ui_widget_event_handler_functions.c`, `interface/ui_widget_game_data_input_functions.c` | The main menu is the PC version's from `port/assets/menus` (`display.menus`); the menus' widgets can call the port's functions (`game/menu_functions.c`) and send the PC version's custom activation event; the widgets' memory is 256 KB, not 16 KB; the main menu and Multiplayer clear co-op's controllers, so that a gamepad going back to controller 1 is not a controller unplugged; the lobby's split screen players leave alone, and one who quits in game is not joined to the next game (`interface/player_ui.c`). |
| `input/input_abstraction.c`, `game/player_control.c`, `game/players.c`, `game/player_queues_new.c`, `units/units.h` | The keyboard and mouse's actions (`src/xinput_sdl.c`, `include/halo_keyboard.h`) join controller 1's game controls; their reload key reloads on its own, and their action key only acts (a control flag of the port's, sent with the player's action, stops the reload the controller's X falls back to). |
| `sound/sound_manager.c`, `interface/hud.c`, `game/game_engine.c` | The music's and the other sounds' volumes; the HUD's and the scoreboard's settings are read again when Settings changes them. |
| `rasterizer/xbox/rasterizer_xbox_shadows.c` | With larger shadow maps (`display.shadow_resolution`), the blur's taps stay half a texel apart, and more passes widen it to cover the same part of the map as on the Xbox. |
| `render/render.c` | The 3D view of each window is antialiased before the HUD is drawn (`display.anti_aliasing`). |
| `interface/hud.c` | In multiplayer, players' names are drawn above their heads (`display.player_names`, `display.player_name_scale`). |
| `rasterizer/rasterizer_text.c`, `text/draw_string.c` | Text is drawn from an atlas of the fonts' glyphs, rasterized at the resolution the game draws at (`src/text_hires.c`), when the font has every character of the string. Text can be drawn scaled about a point (`rasterizer_text_set_scale`), as the players' names are. Each glyph's advance is centred on the font tag character's, so the layout is the same, and a glyph is cut at a text box only where the font tag's character visibly was. |
+30 -184
View File
@@ -7,15 +7,9 @@ percentage): for each player past the first, each squad of the players'
enemies that a level places (encounters.c's encounter_create) gets that
much of its count more; at 100%, two players meet twice the squad, four
players four times it. STATIC MULTIPLIER (network.coop_enemies_multiplier):
each squad is that many times as large, for any number of players.
Extra enemies are placed in widening rings around the squad's starting
locations, on a spot on the same floor, reachable without passing through
a wall, crate or machine, with room to stand and nobody already there. A
starting location with no room left passes its enemy to the squad's next
one; if none has room, the enemy goes where extra enemies went before free
ground was looked for (rings around the starting location, whoever stands
there), so a squad still gets every enemy the setting asks for.
each squad is that many times as large, for any number of players. The
extra enemies stand in rings about the squad's starting locations, on
open ground, so they do not start inside each other.
A squad a script loads into a dropship (vehicle_load_magic) can be larger
than the dropship's seats. The riders left without a seat are erased, and
@@ -66,12 +60,9 @@ enum
COOP_ENEMIES_MAXIMUM_PERCENT = 200,
COOP_ENEMIES_MINIMUM_MULTIPLIER = 2,
COOP_ENEMIES_MAXIMUM_MULTIPLIER = 32,
/* the rings tried around a starting location, the first holding
SPREAD_PLACES_PER_RING places and each further one that many more */
SPREAD_RINGS = 5,
/* the places tried for an extra enemy, a ring further out each */
SPREAD_ATTEMPTS = 8,
SPREAD_PLACES_PER_RING = 6,
/* the rings coop_enemies_fallback_position tries, a place on each */
FALLBACK_RINGS = 8,
MAXIMUM_RIDING_VEHICLES = 32,
MAXIMUM_SEATED_RIDERS = 16,
MAXIMUM_KEPT_RIDERS = 512,
@@ -80,25 +71,11 @@ enum
RIDER_RELEASE_TICKS = 4,
};
/* (coop_enemies.h's count of the places on all the rings) */
typedef char coop_enemies_spread_spots_assert[
COOP_ENEMIES_SPREAD_SPOTS == SPREAD_PLACES_PER_RING * SPREAD_RINGS * (SPREAD_RINGS + 1) / 2 ? 1 : -1];
/* distance between rings */
/* the rings' spacing, and the height over the ground the way to a place is
looked along, and the ground looked for below it */
#define SPREAD_SPACING 0.8f
/* height above the floor at which the way to a spot is checked */
#define SPREAD_STEP_HEIGHT 0.6f
/* how far a spot's floor may be above or below the starting location's:
a step, not a ledge */
#define SPREAD_FLOOR_DIFFERENCE 0.5f
/* room a spot needs above its floor */
#define SPREAD_HEADROOM 1.6f
/* how close across the ground a spot may be to a biped already there
(about two bipeds' collision radius), and how far around it to look */
#define SPREAD_CLEARANCE 0.6f
#define SPREAD_SEARCH_RADIUS 1.0f
/* how far below a fallback place the ground is looked for */
#define FALLBACK_GROUND_DEPTH 2.0f
#define SPREAD_GROUND_DEPTH 2.0f
/* a kept rider's place beside a rider who got out, each a little apart */
#define RIDER_SPACING 0.5f
@@ -183,105 +160,35 @@ static short coop_enemies_player_count(
return count;
}
/* what a spot is tested against: the level, and the objects that block
the way (crates and other scenery, machines, vehicles) */
#define SPREAD_COLLISION_FLAGS (FLAG(_collision_test_structure_bit) | FLAG(_collision_test_objects_bit) | \
_collision_test_objects_sight_blocking_flags | FLAG(_collision_test_front_facing_surfaces_bit) | \
FLAG(_collision_test_back_facing_surfaces_bit))
/* whether anything is in the way from `from`, along `vector` */
static boolean coop_enemies_blocked(
real_point3d const *from,
real_vector3d const *vector)
/* whether the way from a to b is open (no structure between them) */
static boolean coop_enemies_open(
real_point3d const *a,
real_point3d const *b)
{
struct collision_result collision;
real_vector3d vector;
return collision_test_vector(SPREAD_COLLISION_FLAGS, from, vector, NONE, &collision);
vector.i = b->x - a->x;
vector.j = b->y - a->y;
vector.k = b->z - a->z;
return !collision_test_vector(FLAG(_collision_test_structure_bit) | FLAG(_collision_test_front_facing_surfaces_bit) |
FLAG(_collision_test_back_facing_surfaces_bit), a, &vector, NONE, &collision);
}
/* the floor under a point, looked for from SPREAD_STEP_HEIGHT above it to
SPREAD_FLOOR_DIFFERENCE below it */
static boolean coop_enemies_floor(
/* the ground below a point, within SPREAD_GROUND_DEPTH of it */
static boolean coop_enemies_ground(
real_point3d const *point,
real_point3d *floor)
real_point3d *ground)
{
struct collision_result collision;
real_point3d from = *point;
real_vector3d down = { 0.0f, 0.0f, -(2.0f * SPREAD_STEP_HEIGHT + SPREAD_FLOOR_DIFFERENCE) };
real_vector3d vector = { 0.0f, 0.0f, -(SPREAD_STEP_HEIGHT + SPREAD_GROUND_DEPTH) };
from.z += SPREAD_STEP_HEIGHT;
if (!collision_test_vector(SPREAD_COLLISION_FLAGS, &from, &down, NONE, &collision))
return FALSE;
*floor = collision.point;
return TRUE;
}
/* whether a biped stands within SPREAD_CLEARANCE of a spot on the floor,
or the spot is inside a vehicle */
static boolean coop_enemies_occupied(
real_point3d const *floor)
{
real_point3d body = { floor->x, floor->y, floor->z + SPREAD_STEP_HEIGHT };
struct location location;
long object_indices[32];
short object_count;
short index;
scenario_location_from_point(&location, &body);
if (location.cluster_index == NONE)
return TRUE;
object_count = objects_in_sphere(0, _object_mask_biped | _object_mask_vehicle, &location, &body,
SPREAD_SEARCH_RADIUS, object_indices, NUMBEROF(object_indices));
for (index = 0; index < object_count; index++)
{
struct object_datum *object = object_get(object_indices[index]);
if (object->object.type == _object_type_vehicle)
{
if (point_in_sphere(&body, &object->object.bounding_sphere_center, object->object.bounding_sphere_radius))
return TRUE;
}
else
{
real dx = object->object.position.x - floor->x;
real dy = object->object.position.y - floor->y;
if (dx * dx + dy * dy < SPREAD_CLEARANCE * SPREAD_CLEARANCE)
return TRUE;
}
}
return FALSE;
}
/* Whether an enemy can stand at `spot`: reachable from `from` (beside the
starting location) with nothing in the way, a floor within a step of
`floor_height` (the starting location's), room above it, and nobody there.
The floor goes in `position`. */
static boolean coop_enemies_spot_free(
real_point3d const *from,
real_point3d const *spot,
real floor_height,
real_point3d *position)
{
/* (level, at step height: down to the starting location's floor, a spot
whose floor is a little higher would end the look inside it) */
real_vector3d way = { spot->x - from->x, spot->y - from->y, 0.0f };
real_vector3d up = { 0.0f, 0.0f, SPREAD_HEADROOM };
real_point3d floor;
real_point3d feet;
if (coop_enemies_blocked(from, &way) || !coop_enemies_floor(spot, &floor) ||
fabsf(floor.z - floor_height) > SPREAD_FLOOR_DIFFERENCE)
if (!collision_test_vector(FLAG(_collision_test_structure_bit) | FLAG(_collision_test_front_facing_surfaces_bit),
point, &vector, NONE, &collision))
{
return FALSE;
}
/* (headroom is checked from just above the floor, so the floor itself
doesn't count as in the way) */
feet = floor;
feet.z += 0.1f;
if (coop_enemies_blocked(&feet, &up) || coop_enemies_occupied(&floor))
return FALSE;
*position = floor;
*ground = collision.point;
return TRUE;
}
@@ -356,7 +263,6 @@ static void coop_enemies_rider_place(
short out_count = 0;
short index, round;
real angle;
real_point3d spread;
*position = object->object.position;
*facing = (real)atan2(object->object.forward.j, object->object.forward.i);
@@ -393,13 +299,7 @@ static void coop_enemies_rider_place(
}
}
}
/* free ground around that spot, as the squad's own extra enemies get;
failing that, a ring around it, further out each round */
if (coop_enemies_spread_position(position, (short)(number + 1), NULL, &spread))
{
*position = spread;
return;
}
/* (never on the rider's own place: a ring about it, further each round) */
angle = (real)number * (_pi * 2.0f / SPREAD_PLACES_PER_RING);
position->x += (real)cos(angle) * RIDER_SPACING * (real)(round / SPREAD_PLACES_PER_RING + 1);
position->y += (real)sin(angle) * RIDER_SPACING * (real)(round / SPREAD_PLACES_PER_RING + 1);
@@ -457,48 +357,6 @@ short coop_enemies_extra_count(
}
boolean coop_enemies_spread_position(
real_point3d const *origin,
short number,
long *taken,
real_point3d *position)
{
real_point3d from = *origin;
real_point3d floor;
real floor_height = coop_enemies_floor(origin, &floor) ? floor.z : origin->z;
short ring, first_spot = 0;
from.z = floor_height + SPREAD_STEP_HEIGHT;
for (ring = 0; ring < SPREAD_RINGS; ring++)
{
short place_count = (short)(SPREAD_PLACES_PER_RING * (ring + 1));
real radius = SPREAD_SPACING * (real)(ring + 1);
short place;
/* (each enemy starts at a different place on the ring, so a squad
spreads all the way round rather than filling one side first) */
for (place = 0; place < place_count; place++)
{
short around = (short)((number + place) % place_count);
short spot_index = (short)(first_spot + around);
real angle = (real)around * (_pi * 2.0f / (real)place_count);
real_point3d spot = from;
if (taken && BIT_VECTOR_TEST_FLAG(taken, spot_index))
continue;
spot.x += (real)cos(angle) * radius;
spot.y += (real)sin(angle) * radius;
spot.z = floor_height;
if (coop_enemies_spot_free(&from, &spot, floor_height, position))
return TRUE;
if (taken)
BIT_VECTOR_SET_FLAG(taken, spot_index, TRUE);
}
first_spot = (short)(first_spot + place_count);
}
return FALSE;
}
boolean coop_enemies_fallback_position(
real_point3d const *origin,
short number,
real_point3d *position)
@@ -509,30 +367,18 @@ boolean coop_enemies_fallback_position(
short attempt;
from.z += SPREAD_STEP_HEIGHT;
for (attempt = 0; attempt < FALLBACK_RINGS; attempt++, ring++)
for (attempt = 0; attempt < SPREAD_ATTEMPTS; attempt++, ring++)
{
/* (each ring turned half a place from the last, so its places fall
between the last's) */
real angle = ((real)place + 0.5f * (real)(ring % 2)) * (_pi * 2.0f / SPREAD_PLACES_PER_RING);
real radius = SPREAD_SPACING * (real)(ring + 1);
real_point3d to = from;
real_vector3d way = { 0.0f, 0.0f, 0.0f };
real_vector3d down = { 0.0f, 0.0f, -(SPREAD_STEP_HEIGHT + FALLBACK_GROUND_DEPTH) };
struct collision_result collision;
way.i = (real)cos(angle) * radius;
way.j = (real)sin(angle) * radius;
to.x += way.i;
to.y += way.j;
/* (the level only: the way open, and ground below) */
if (!collision_test_vector(FLAG(_collision_test_structure_bit) | FLAG(_collision_test_front_facing_surfaces_bit) |
FLAG(_collision_test_back_facing_surfaces_bit), &from, &way, NONE, &collision) &&
collision_test_vector(FLAG(_collision_test_structure_bit) | FLAG(_collision_test_front_facing_surfaces_bit),
&to, &down, NONE, &collision))
{
*position = collision.point;
to.x += (real)cos(angle) * radius;
to.y += (real)sin(angle) * radius;
if (coop_enemies_open(&from, &to) && coop_enemies_ground(&to, position))
return TRUE;
}
}
return FALSE;
}
+3 -20
View File
@@ -21,27 +21,10 @@ short coop_enemies_extra_count(
long encounter_index,
short count);
/* the places coop_enemies_spread_position tries around a starting location */
#define COOP_ENEMIES_SPREAD_SPOTS 90
/* encounters.c: finds a spot for a squad's `number`th extra enemy
(counting from 1) around the starting location `origin`, on its floor,
clear of walls and objects, with room to stand and nobody there. FALSE if
there's no room. `taken` (COOP_ENEMIES_SPREAD_SPOTS bits, or NULL) marks the
places found taken, which stay so while the squad's enemies are placed: they
aren't tried again. */
/* encounters.c: the place of a squad's `number`th extra enemy (from 1) at
its starting location `origin`: rings about it, on open ground; FALSE if
none is (the starting location itself then) */
boolean coop_enemies_spread_position(
real_point3d const *origin,
short number,
long *taken,
real_point3d *position);
/* encounters.c: where the `number`th extra enemy (counting from 1) goes
when no starting location has room: a place on rings around `origin` with
the way open and ground below, whoever stands there (as extra enemies were
placed before free ground was looked for), so the squad still gets every
enemy the setting asks for. FALSE if none (the starting location itself
then) */
boolean coop_enemies_fallback_position(
real_point3d const *origin,
short number,
real_point3d *position);
-234
View File
@@ -1,234 +0,0 @@
/*
COOP_SCRIPTS.C
Campaign scripts made to work for every player in network co-op.
The campaign's scripts were written for one or two players. Only the host
runs them (game.c), against every player's unit, so most of what they do
already happens for everyone: a trigger volume tested against (players)
fires for whoever walks into it, and what it starts is sent to the clients
(network_coop.c). Three habits of the scripts don't carry over:
- Waiting for every player at once (volume_test_objects_all on the
players): the Maw's run to the Pillar of Autumn's bridge, a30's Pelican
pickups. Split-screen players stand together; network players don't, so
the wait would never end. In co-op one player arriving is enough.
- Waiting until it's safe (game_safe_to_save) before a cutscene: the
game's test fails while any enemy sees any player. In co-op one player
out of danger is enough.
- Naming players one at a time: the scripts reach the players through
player0 and player1 (the first two in the players list), to put them in
a Pelican, take them out of it, stage them for a cutscene or give them a
starting profile. Everyone past the second would be left out, so they
follow player0: wherever the scripts put player0, they go too.
*/
/* ---------- headers */
#include "cseries.h"
#include "ai/ai.h"
#include "game/game.h"
#include "game/players.h"
#include "hs/object_lists.h"
#include "objects/objects.h"
#include "objects/object_types.h"
#include "units/bipeds.h"
#include "units/units.h"
#include "coop_scripts.h"
#include "network_coop.h"
#include <string.h>
/* ---------- constants */
/* players the scripts name: player0 and player1 */
#define NAMED_PLAYERS 2
/* ---------- private code */
static boolean coop_scripts_host(
void)
{
return game_connection() == _game_connection_network_server && network_coop_active();
}
/* the seat label to look for when boarding a follower: player0's own up to
and including "rider" (Pelicans have "p-riderlf", "p-riderrf", "p-riderlb"
and so on), else up to the '-' ("b-driver" gives "b-") */
static void follower_seat_name(
char const *seat_name,
char *follower_seat,
size_t size)
{
char *rider;
char *dash;
/* (lower case: vehicle_scripting_find_available_seats compares it with
lowered seat labels) */
snprintf(follower_seat, size, "%s", seat_name);
strlwr(follower_seat);
rider = strstr(follower_seat, "rider");
dash = strchr(follower_seat, '-');
if (rider)
rider[strlen("rider")] = 0;
else if (dash)
dash[1] = 0;
}
/* ---------- public code */
boolean coop_scripts_any_player_will_do(
long object_list_index)
{
long reference_index;
long object_index;
boolean any = FALSE;
if (!coop_scripts_host())
return FALSE;
for (object_index = object_list_get_first(object_list_index, &reference_index);
object_index != NONE;
object_index = object_list_get_next(object_list_index, &reference_index))
{
if (player_index_from_unit_index(object_index) == NONE)
return FALSE;
any = TRUE;
}
return any;
}
/* the units of the players following player0, if `unit_index` is
player0's; how many */
static short players_following(
long unit_index,
long *unit_indices,
short maximum_count)
{
struct data_iterator iterator;
struct player_datum *player;
short position = 0;
short count = 0;
if (unit_index == NONE || !coop_scripts_host())
return 0;
/* (in the order (players) lists them: player0 is the first with a unit) */
data_iterator_new(&iterator, player_data);
while ((player = data_iterator_next(&iterator)) != NULL)
{
if (player->unit_index == NONE)
continue;
if (position == 0 && player->unit_index != unit_index)
return 0;
if (position >= NAMED_PLAYERS && count < maximum_count)
unit_indices[count++] = player->unit_index;
position++;
}
return count;
}
void coop_scripts_teleport_followers(
long unit_index)
{
long followers[HALO_PORT_MAXIMUM_NETWORK_PLAYERS];
short count = players_following(unit_index, followers, NUMBEROF(followers));
short index;
for (index = 0; index < count; index++)
{
player_teleport(player_index_from_unit_index(followers[index]), unit_index,
&object_get(unit_index)->object.position);
}
}
void coop_scripts_suspend_followers(
long unit_index,
boolean suspended)
{
long followers[HALO_PORT_MAXIMUM_NETWORK_PLAYERS];
short count = players_following(unit_index, followers, NUMBEROF(followers));
short index;
for (index = 0; index < count; index++)
unit_scripting_suspended(followers[index], suspended);
}
void coop_scripts_exit_followers(
long unit_index,
long vehicle_index)
{
long followers[HALO_PORT_MAXIMUM_NETWORK_PLAYERS];
short count = players_following(unit_index, followers, NUMBEROF(followers));
short index;
for (index = 0; index < count; index++)
{
if (object_get(followers[index])->object.parent_object_index == vehicle_index)
unit_scripting_exit_vehicle(followers[index]);
}
}
void coop_scripts_board_followers(
long unit_index,
long vehicle_index,
char const *seat_name)
{
long followers[HALO_PORT_MAXIMUM_NETWORK_PLAYERS];
short count = players_following(unit_index, followers, NUMBEROF(followers));
char follower_seat[64];
short index;
if (count == 0 || vehicle_index == NONE)
return;
follower_seat_name(seat_name, follower_seat, sizeof(follower_seat));
/* (each list freed at once: there are few, and the scripts' own are only
collected after the tick's threads have run) */
for (index = 0; index < count; index++)
{
long list_index = object_list_new();
if (list_index == NONE)
break;
object_list_add(list_index, followers[index]);
vehicle_scripting_load_magic(vehicle_index, follower_seat, list_index);
object_list_delete(list_index);
}
}
boolean coop_scripts_safe_to_save(
void)
{
struct data_iterator iterator;
struct player_datum *player;
if (game_safe_to_save())
return TRUE;
if (!coop_scripts_host())
return FALSE;
data_iterator_new(&iterator, player_data);
while ((player = data_iterator_next(&iterator)) != NULL)
{
struct biped_datum *biped = biped_try_and_get(player->unit_index);
if (biped && !TEST_FLAG(biped->object.damage_flags, _object_dead_bit) &&
!TEST_FLAG(biped->biped.flags, _biped_airborne_bit) && !ai_port_enemies_can_see_unit(player->unit_index))
{
return TRUE;
}
}
return FALSE;
}
void coop_scripts_player_add_equipment(
long unit_index,
short starting_profile_index,
boolean reset_equipment)
{
long followers[HALO_PORT_MAXIMUM_NETWORK_PLAYERS];
short count = players_following(unit_index, followers, NUMBEROF(followers));
short index;
player_add_equipment(unit_index, starting_profile_index, reset_equipment);
for (index = 0; index < count; index++)
player_add_equipment(followers[index], starting_profile_index, reset_equipment);
}
-55
View File
@@ -1,55 +0,0 @@
/*
COOP_SCRIPTS.H
Campaign scripts made to work for every player in network co-op
(coop_scripts.c).
*/
#ifndef __COOP_SCRIPTS_H
#define __COOP_SCRIPTS_H
/* ---------- prototypes */
/* hs_library_external.c's volume_test_objects_all: whether to count the
list as inside once any of it is, because every object in it is a player */
boolean coop_scripts_any_player_will_do(
long object_list_index);
/* What the scripts do to player0, done to the players they can't name too
(the "followers"). Each does nothing unless `unit_index` is player0's in a
co-op host's game. */
/* hs_library_external.c's object_teleport: around player0 */
void coop_scripts_teleport_followers(
long unit_index);
/* units.c's unit_suspended */
void coop_scripts_suspend_followers(
long unit_index,
boolean suspended);
/* units.c's unit_exit_vehicle: those riding player0's vehicle get out */
void coop_scripts_exit_followers(
long unit_index,
long vehicle_index);
/* units.c's vehicle_load_magic and unit_enter_vehicle: into the vehicle,
each in a free seat of the same kind as player0's (`seat_name`) */
void coop_scripts_board_followers(
long unit_index,
long vehicle_index,
char const *seat_name);
/* hs.c's game_safe_to_save, for the scripts: the game's own test, or in
co-op, any one player alive, on the ground and unseen by enemies. The
scripts wait on it before a cutscene, and with players spread out a fight
somewhere else would hold up the one a player has reached. Checkpoints
keep the game's own test. */
boolean coop_scripts_safe_to_save(
void);
/* hs.c's player_add_equipment, for the scripts: gives the starting profile
to player0's followers too */
void coop_scripts_player_add_equipment(
long unit_index,
short starting_profile_index,
boolean reset_equipment);
#endif
+16 -250
View File
@@ -1970,10 +1970,6 @@ Xbox's networking, run by the engine's port entry points
#define KEY_MODIFIER_CONTROL_BIT 1
#define LOBBY_NAME "pc\\main_menu\\multiplayer_type_select\\lobby\\lobby_screen"
#define PREVIEW_NAME "pc\\main_menu\\multiplayer_type_select\\lobby\\preview_screen"
/* the server browser's password screen (tools/port_settings.py), and the
longest password */
#define PASSWORD_NAME "pc\\main_menu\\multiplayer_type_select\\join_game\\password\\password_screen"
#define PASSWORD_LENGTH 32
/* the lobby's panel's lines (the gametype, the players, the countdown, the
invite) */
#define LOBBY_TEXT_LENGTH (ROW_TEXT_LENGTH * 4)
@@ -2040,7 +2036,6 @@ boolean ui_widget_port_unjoin_player(struct widget_instance *widget, struct even
boolean *widget_deleted);
void network_game_server_port_set_settings(wchar_t const *name, long maximum_players);
void network_game_server_port_set_cooperative_friendly_fire(short friendly_fire);
void network_game_server_port_set_cooperative_player_collisions(boolean player_collisions);
void *global_network_game_client_get(void);
void *global_network_game_server_get(void);
boolean network_game_is_splitscreen_local(void);
@@ -2097,8 +2092,6 @@ static struct
boolean cooperative_maximum_players_set;
/* co-op's FRIENDLY FIRE shown (network.coop_friendly_fire's) */
short cooperative_friendly_fire;
/* co-op's PLAYER COLLISIONS shown, OFF (network.coop_player_collisions's) */
boolean cooperative_no_player_collisions;
/* the browser's games */
struct advertised_game *games[MAXIMUM_ADVERTISED_GAMES];
short game_count, game_chosen;
@@ -2111,9 +2104,6 @@ static struct
/* Server Setup's LISTING: PRIVATE (each new game starts with
network.host_public's choice: PUBLIC unless set otherwise) */
boolean game_private;
/* Server Setup's PASSWORD (a public internet game's; empty: none), kept
while the game runs, never written down */
char game_password[PASSWORD_LENGTH + 1];
} multiplayer = { 0, 0, 0, { 0 }, 0, { 0 }, 0, 0, 0, 0, { 0 }, NUMBEROF(maximum_players) - 1 };
/* ---- a text field (Direct Link's link, the game's name): the keyboard
@@ -2126,8 +2116,6 @@ static struct
char before[TEXT_FIELD_LENGTH];
short maximum;
void (*done)(char const *text);
/* a password's: shown as stars (text_field_begin_masked) */
boolean masked;
} text_field;
/* when the field was last shown (a field not shown for a while is let go
@@ -2173,7 +2161,6 @@ static void text_field_begin(struct widget_instance *row, char const *text, shor
snprintf(text_field.before, sizeof(text_field.before), "%s", text);
text_field.maximum = (short)MIN(maximum, TEXT_FIELD_LENGTH - 1);
text_field.done = done;
text_field.masked = FALSE;
text_field_shown_time = system_milliseconds();
while (input_get_key(&key))
;
@@ -2184,14 +2171,6 @@ static void text_field_begin(struct widget_instance *row, char const *text, shor
#endif
}
/* a password's field: as text_field_begin, its text shown as stars */
static void text_field_begin_masked(struct widget_instance *row, char const *text, short maximum,
void (*done)(char const *text))
{
text_field_begin(row, text, maximum, done);
text_field.masked = TRUE;
}
static void text_field_end(boolean keep)
{
void (*done)(char const *text) = text_field.done;
@@ -2280,7 +2259,7 @@ static void text_field_show(struct widget_instance *value, char const *text, boo
text = text_field.text;
}
for (index = 0; text[index] && index < TEXT_FIELD_LENGTH; index++)
shown[index] = editing && text_field.masked ? L'*' : (wchar_t)(unsigned char)text[index];
shown[index] = (wchar_t)(unsigned char)text[index];
if (editing && (system_milliseconds() / 500) % 2)
shown[index++] = L'_';
shown[index] = 0;
@@ -2664,8 +2643,6 @@ help_strings, tools/port_settings.py) */
#define COOPERATIVE_EXTRA_ENEMIES_HELP 16
#define COOPERATIVE_ENEMIES_PER_PLAYER_HELP 19
#define COOPERATIVE_ENEMIES_MULTIPLIER_HELP 20
#define COOPERATIVE_PLAYER_COLLISIONS_HELP 21
#define SERVER_PASSWORD_HELP 23
/* co-op's EXTRA ENEMIES' choices (port_settings.COOP_ENEMIES_MODES, in this
order): its amount's row is the choice's */
@@ -2829,18 +2806,6 @@ static void server_settings_update(struct widget_instance *list)
help->parameters.text_box.string_list_index = COOPERATIVE_ENEMIES_MULTIPLIER_HELP;
}
}
/* ... and its PLAYER COLLISIONS, below them: ON or OFF, its help its
choice's */
visible_set(named(list, "op_player_collisions", 0), cooperative);
if (cooperative && (spinner = named(list, "player_collisions_spinner", 0)) != NULL)
{
short choice = (short)PIN(spinner->parameters.list.selected_index, 0, 1);
struct widget_instance *help = list->parameters.list.extended_description;
multiplayer.cooperative_no_player_collisions = choice == 1;
if (help && list->focused_child == named(list, "op_player_collisions", 0))
help->parameters.text_box.string_list_index = (short)(COOPERATIVE_PLAYER_COLLISIONS_HELP + choice);
}
}
/* LISTING (an internet game's): PUBLIC, listed in everyone's server
browser, or PRIVATE, for this game. Its help is its choice's */
@@ -2856,41 +2821,6 @@ static void server_settings_update(struct widget_instance *list)
if (help && list->focused_child == named(list, "op_listing", 0))
help->parameters.text_box.string_list_index = (short)(10 + spinner->parameters.list.selected_index);
}
/* PASSWORD (a PUBLIC internet game's): its stars, NONE if it has none */
row = named(list, "op_password", 0);
visible_set(row, named(list, "op_listing", 0) && named(list, "op_listing", 0)->visible &&
!server_settings_private());
if (row && !row->visible && text_field_editing(row))
text_field_end(FALSE);
if (row && row->visible)
{
struct widget_instance *help = list->parameters.list.extended_description;
short index;
for (index = 0; multiplayer.game_password[index] && index < (short)sizeof(text) - 1; index++)
text[index] = '*';
text[index] = 0;
text_field_show(named(list, "password_value", 0), index ? text : "NONE", text_field_editing(row));
if (help && list->focused_child == row)
help->parameters.text_box.string_list_index = SERVER_PASSWORD_HELP;
}
}
static void server_password_done(char const *text)
{
snprintf(multiplayer.game_password, sizeof(multiplayer.game_password), "%s", text);
}
/* "ss edit server password" (its row's A: begins, or ends) */
static boolean server_password_edit(struct widget_instance *row)
{
if (text_field_editing(row))
{
text_field_end(TRUE);
return TRUE;
}
text_field_begin_masked(row, multiplayer.game_password, PASSWORD_LENGTH, server_password_done);
return TRUE;
}
/* "ss edit server name" (its row's A: begins, or ends) */
@@ -2927,16 +2857,12 @@ static boolean server_start(void)
text_field_end(TRUE);
network_game_server_port_set_settings(multiplayer.game_name,
maximum_players[PIN(*server_settings_maximum_players_index(), 0, NUMBEROF(maximum_players) - 1)]);
/* (its listing's password: a PUBLIC internet game's, else none) */
p2p_set_hosting_password(multiplayer.mode == _multiplayer_mode_host_internet && !server_settings_private() ?
multiplayer.game_password : NULL);
/* the gametype as Server Setup's options left it (co-op keeps its own,
and its FRIENDLY FIRE) */
if (hosting_cooperative())
{
gametype_setup_end();
network_game_server_port_set_cooperative_friendly_fire(multiplayer.cooperative_friendly_fire);
network_game_server_port_set_cooperative_player_collisions(!multiplayer.cooperative_no_player_collisions);
}
else if (!gametype_setup_apply())
return campaign_fail();
@@ -3197,11 +3123,6 @@ static struct
/* the last that failed, and when */
char failed_name[P2P_LISTING_NAME_SIZE + 1];
unsigned long failed_time;
/* a game with a password chosen: the password screen's (its own copy:
the rows' are read again each frame), and a join it began, which the
browser takes up again as it comes back */
struct p2p_listing password_game;
boolean password_joined;
} lobby_browser;
static void lobby_browser_begin(void)
@@ -3210,10 +3131,7 @@ static void lobby_browser_begin(void)
lobby_browser.first = 0;
lobby_browser.chosen = 0;
lobby_browser.shown = FALSE;
/* (going on with the join the password screen began) */
lobby_browser.joining = lobby_browser.password_joined;
lobby_browser.ready = FALSE;
lobby_browser.password_joined = FALSE;
lobby_browser.joining = lobby_browser.ready = FALSE;
lobby_browser.begin_time = system_milliseconds();
p2p_lobby_browse(TRUE);
}
@@ -3361,18 +3279,8 @@ static void lobby_browser_update(struct widget_instance *list)
usnprintf(text, ROW_TEXT_LENGTH - 1, L"%d/%d", game->player_count, game->maximum_player_count);
text_set(named(row, "server_item_players", 0), text);
/* (no ping yet: its host is reached only on joining) */
text_set(named(row, "server_item_ping", 0), game->failed ? L"FAILED" : !game->open ? L"CLOSED" :
game->in_progress ? L"LIVE" : L"-");
/* (the lock: a game with a password. Its bitmap's first frame is
empty, the PC version's for no lock; the next is the lock, in the
menus' blue) */
{
struct widget_instance *lock = named(row, "server_item_locked", 0);
visible_set(lock, game->locked);
if (lock)
lock->animation.current_frame_index = 1;
}
text_set(named(row, "server_item_ping", 0), game->failed ? L"FAILED" : game->in_progress ? L"LIVE" : L"-");
visible_set(named(row, "server_item_locked", 0), !game->open);
visible_set(named(row, "server_item_dedicated", 0), FALSE);
visible_set(named(row, "server_item_classic", 0), FALSE);
}
@@ -3447,10 +3355,9 @@ static void lobby_browser_update(struct widget_instance *list)
wchar_t gametype[P2P_LISTING_GAMETYPE_SIZE + 1];
text_to_wide(game->gametype, gametype, NUMBEROF(gametype));
usnprintf(text, NUMBEROF(text) - 1, L"%s: %d %s of %d%s%s", gametype, game->player_count,
usnprintf(text, NUMBEROF(text) - 1, L"%s: %d %s of %d%s", gametype, game->player_count,
game->player_count == 1 ? L"player" : L"players", game->maximum_player_count,
!game->open ? L", full or starting" : game->in_progress ? L", under way" : L"",
game->locked ? L", password" : L"");
!game->open ? L", full or starting" : game->in_progress ? L", under way" : L"");
}
else
text[0] = 0;
@@ -3480,26 +3387,9 @@ static void lobby_browser_update(struct widget_instance *list)
browser_focus(list);
}
/* the game's host reached by its invite (joined, once it is reached:
lobby_browser_update) */
static boolean lobby_browser_join(struct p2p_listing const *game, short controller)
{
if (!p2p_join_invite(game->invite))
return FALSE;
csmemcpy(lobby_browser.identifier, game->identifier, sizeof(lobby_browser.identifier));
csmemcpy(lobby_browser.name, game->name, sizeof(lobby_browser.name));
lobby_browser.joining = TRUE;
lobby_browser.ready = FALSE;
lobby_browser.join_time = system_milliseconds();
lobby_browser.controller = controller;
ui_play_audio_feedback_sound(SOUND_FORWARD);
return TRUE;
}
/* the server browser's rows and buttons: Refresh asks the hosts again; a
row (or Join) joins its game's invite, a game with a password once its
screen has it; the A press posted once its host is reached joins its game
(or shows its lobby, if under way) */
row (or Join) joins its game's invite; the A press posted once its host is
reached joins its game (or shows its lobby, if under way) */
static boolean lobby_browser_select(struct widget_instance *widget, short controller, short row,
boolean *widget_deleted)
{
@@ -3544,117 +3434,15 @@ static boolean lobby_browser_select(struct widget_instance *widget, short contro
if (index >= lobby_browser.count)
return campaign_fail();
game = &lobby_browser.games[index];
if (!game->open || !config_boolean("network.online"))
if (!game->open || !config_boolean("network.online") || !p2p_join_invite(game->invite))
return campaign_fail();
/* (a game with a password: its screen asks for it, then joins) */
if (game->locked)
{
lobby_browser.password_game = *game;
lobby_browser.controller = controller;
return ui_widget_port_open(widget, PASSWORD_NAME, widget_deleted);
}
return lobby_browser_join(game, controller) || campaign_fail();
}
/* ---- the server browser's password screen (a game with a password
chosen: lobby_browser_select, tools/port_settings.py): its password typed at
once, shown as stars; JOIN GAME, or Enter, joins the game with it if it is
the game's (the browser, back again, goes on with the join), else its help
says it is not and the typing begins again */
/* its help's lines (tools/port_settings.py's) */
enum
{
_password_help_ask = 1,
_password_help_wrong,
_password_help_failed,
};
static struct
{
char text[PASSWORD_LENGTH + 1];
short help;
} password_screen;
static void password_screen_done(char const *text)
{
snprintf(password_screen.text, sizeof(password_screen.text), "%s", text);
}
/* "port password init" */
static boolean password_screen_initialize(struct widget_instance *list)
{
struct widget_instance *row = named(list, "op_password", 0);
password_screen.text[0] = 0;
password_screen.help = _password_help_ask;
if (row)
text_field_begin_masked(row, "", PASSWORD_LENGTH, password_screen_done);
return TRUE;
}
/* "port password update" */
static void password_screen_update(struct widget_instance *list)
{
struct widget_instance *help = list->parameters.list.extended_description;
char stars[PASSWORD_LENGTH + 1];
short index;
for (index = 0; password_screen.text[index] && index < PASSWORD_LENGTH; index++)
stars[index] = '*';
stars[index] = 0;
text_field_show(named(list, "password_value", 0), stars, text_field_editing(named(list, "op_password", 0)));
if (help)
help->parameters.text_box.string_list_index = password_screen.help;
}
/* "port password join" (JOIN GAME, and Enter: password_screen_edit) */
static boolean password_screen_join(struct widget_instance *widget, boolean *widget_deleted)
{
struct p2p_listing game = lobby_browser.password_game;
struct widget_instance *row = named(screen_of(widget), "op_password", 0);
if (text_field_editing(NULL))
text_field_end(TRUE);
if (!p2p_listing_unlock(&game, password_screen.text))
{
password_screen.help = _password_help_wrong;
password_screen.text[0] = 0;
if (row)
text_field_begin_masked(row, "", PASSWORD_LENGTH, password_screen_done);
return campaign_fail();
}
if (!lobby_browser_join(&game, lobby_browser.controller))
{
password_screen.help = _password_help_failed;
return campaign_fail();
}
password_screen.text[0] = 0;
lobby_browser.password_joined = TRUE;
ui_widget_port_go_back(widget);
*widget_deleted = TRUE;
return TRUE;
}
/* "port password back" (B, Escape): back to the browser at once, though the
password is being typed */
static boolean password_screen_back(struct widget_instance *widget, boolean *widget_deleted)
{
if (text_field_editing(NULL))
text_field_end(FALSE);
password_screen.text[0] = 0;
ui_widget_port_go_back(widget);
*widget_deleted = TRUE;
return TRUE;
}
/* "port password edit" (the field's row's A: the typing begins; Enter, its
end, joins) */
static boolean password_screen_edit(struct widget_instance *row, boolean *widget_deleted)
{
if (text_field_editing(row))
return password_screen_join(row, widget_deleted);
text_field_begin_masked(row, password_screen.text, PASSWORD_LENGTH, password_screen_done);
csmemcpy(lobby_browser.identifier, game->identifier, sizeof(lobby_browser.identifier));
csmemcpy(lobby_browser.name, game->name, sizeof(lobby_browser.name));
lobby_browser.joining = TRUE;
lobby_browser.ready = FALSE;
lobby_browser.join_time = system_milliseconds();
lobby_browser.controller = controller;
ui_play_audio_feedback_sound(SOUND_FORWARD);
return TRUE;
}
@@ -5162,26 +4950,6 @@ boolean pc_menu_event_function_invoke(
{
return server_name_edit(widget);
}
else if (!strcmp(name, "ss edit server password"))
{
return server_password_edit(widget);
}
else if (!strcmp(name, "port password init"))
{
return password_screen_initialize(widget);
}
else if (!strcmp(name, "port password edit"))
{
return password_screen_edit(widget, widget_deleted);
}
else if (!strcmp(name, "port password join"))
{
return password_screen_join(widget, widget_deleted);
}
else if (!strcmp(name, "port password back"))
{
return password_screen_back(widget, widget_deleted);
}
else if (!strcmp(name, "ss copy invite"))
{
return invite_copy();
@@ -5361,8 +5129,6 @@ void pc_menu_game_data_function_invoke(
gametype_list_update(widget);
else if (!strcmp(name, "server settings update"))
server_settings_update(widget);
else if (!strcmp(name, "port password update"))
password_screen_update(widget);
else if (!strcmp(name, "gamespy screen update"))
browser_update(widget);
else if (!strcmp(name, "gt edit list update"))
-11
View File
@@ -361,8 +361,6 @@ static char const *const port_function_names[] =
/* (Settings' OK: the Xbox's fails when the profile has no changes, the
settings' screens having written theirs to config.toml) */
"player profile save changes",
/* (the server browser's password screen) */
"port password init", "port password edit", "port password join", "port password back",
};
/* the PC version's game data functions that the Xbox's have not, from
@@ -380,7 +378,6 @@ static char const *const port_game_data_input_names[] =
/* (the gametype editor's: the Xbox's stops the game on the buttons'
row; the Xbox's read only player_ui's gametype, not Server Setup's) */
"game settings lists text update", "get edit game settings name", "mp edit profile set rule text",
"port password update",
};
static struct
@@ -1249,14 +1246,6 @@ static struct cache_file_tag_instance *instances_grow(long count, long *first_in
if (!instances)
return NULL;
/* (every tag's absolute index fits a tag index's 16 bits, short of
NONE's, and the table's size cannot wrap: the map's count was checked
as it loaded, cache_files.c, and is again) */
if (existing < 0 || count < 0 || existing > UNSIGNED_SHORT_MAX - count)
{
platform_log("menus: %ld tags and the map's %ld are too many for a tag table", count, existing);
return NULL;
}
grown = allocate((existing + count) * sizeof(*grown));
if (!grown)
return NULL;
+10 -111
View File
@@ -29,8 +29,6 @@ that clients need to see is sent from this file:
every tick in rotation, those that have moved since the map loaded more
often. A scenario group is identified by its index, which is the same on
every machine; a device's own group by the device.
- Breakable surfaces (glass) the host breaks, each once as it breaks and
a few at a time again in rotation (for a late joiner or a lost message).
- Named objects (scenery and devices) the scripts create or destroy.
network_objects.c already handles units, vehicles, weapons and equipment.
Twice a second the host sends which object names currently exist. A
@@ -55,9 +53,7 @@ host skips once more than half the machines have voted. The skip itself is
the campaign's: the host reverts to the state saved when the cutscene began
and the script carries on past it. Clients catch up through the object,
device and name syncs. The host keeps its clock running through the revert,
because the netcode needs it to only go forward (network_coop_reverted).
A failed mission (game_lost, or the scripts' game_revert) reverts the same
way, rather than leaving the players stuck in the failure's cutscene.
because the netcode needs it to only go forward (network_coop_skip_reverted).
Objects (scenery and devices) that aren't synced by network_objects.c are
found on a client by their scenario name, or, unnamed, by their object
@@ -92,15 +88,11 @@ index and tag, since the map placed them at the same index everywhere.
#include "models/model_animation_definitions.h"
#include "models/model_definitions.h"
#include "models/models.h"
#include "networking/network_game_globals.h"
#include "networking/network_game_manager.h"
#include "objects/object_definitions.h"
#include "objects/objects.h"
#include "objects/object_types.h"
#include "objects/damage.h"
#include "objects/scenery.h"
#include "physics/breakable_surfaces.h"
#include "structures/structure_bsp_definitions.h"
#include "physics/collisions.h"
#include "rasterizer/rasterizer_cinematics.h"
#include "saved games/game_state.h"
@@ -192,13 +184,9 @@ enum
_coop_event_effect,
_coop_event_attach,
/* the host skipped the cutscene: a client stops its dialogue */
/* the host reverted to its last saved state */
_coop_event_reverted,
_coop_event_cutscene_skipped,
/* a unit opened (value TRUE) or closed: a dropship's doors */
_coop_event_unit_open,
/* a breakable surface broke: value its index, frame its BSP, reals the
damage's epicenter */
_coop_event_surface_broken,
};
/* distributed_coop_event.type of an effect: at a cutscene flag (value), or on
@@ -282,11 +270,7 @@ struct distributed_coop_presentation
/* whether the scripts have set the players' maximum vitality, and to
what (network_coop_set_players_vitality) */
byte players_vitality_set;
/* the cluster the scripts keep active (object_pvs_activate) plus one,
0 for none: a cutscene filmed far from every player (b30's Elite at the
shaft door) is active on the clients too. Plus one so the zeroed padding
of a host without it reads as none. */
short activating_cluster;
byte pad[2];
real players_maximum_body_vitality;
real players_maximum_shield_vitality;
/* which teams are allies and friends (game_allegiance.c): the scripts
@@ -1445,7 +1429,6 @@ static void host_presentation(
presentation->input_disabled = (byte)!player_input_enabled();
presentation->scripted_shake = (byte)player_effect_port_scripted_active();
presentation->players_vitality_set = (byte)players_vitality.set;
presentation->activating_cluster = (short)(objects_get_activating_cluster_index() + 1);
presentation->players_maximum_body_vitality = players_vitality.maximum_body;
presentation->players_maximum_shield_vitality = players_vitality.maximum_shield;
game_allegiance_get_teams(presentation->ally_teams, presentation->friendly_teams);
@@ -1561,10 +1544,7 @@ static void client_apply_player_effect(
switch (event->type)
{
case _coop_player_effect_translation:
/* (the camera's shake, which moves it no further than an observer
accepts) */
scripted_player_effect_set_translation(PIN(reals[0], -CAMERA_WORLD_BOUND, CAMERA_WORLD_BOUND),
PIN(reals[1], -CAMERA_WORLD_BOUND, CAMERA_WORLD_BOUND), PIN(reals[2], -CAMERA_WORLD_BOUND, CAMERA_WORLD_BOUND));
scripted_player_effect_set_translation(reals[0], reals[1], reals[2]);
break;
case _coop_player_effect_rotation:
scripted_player_effect_set_rotation(reals[0], reals[1], reals[2]);
@@ -1822,17 +1802,9 @@ static void client_apply_event(
case _coop_event_attach:
client_apply_attach(event);
break;
case _coop_event_reverted:
case _coop_event_cutscene_skipped:
client_stop_script_sounds();
break;
case _coop_event_surface_broken:
if (event->frame == global_structure_bsp_index)
{
real_point3d epicenter = { event->reals[0], event->reals[1], event->reals[2] };
breakable_surface_port_break(event->value, &epicenter);
}
break;
case _coop_event_unit_open:
if (distributed_object_index_valid(event->object_index) && network_objects_client_has(event->object_index) &&
object_try_and_get_and_verify_type(event->object_index, _object_mask_unit))
@@ -1963,16 +1935,6 @@ boolean network_coop_active(
global_scenario && global_scenario->type == _scenario_type_solo && !game_engine_running();
}
boolean network_coop_player_collisions(
void)
{
struct network_game *game;
if (!network_coop_active() || !(game = network_game_get_game()))
return TRUE;
return !TEST_FLAG(game->cooperative_flags, _network_game_cooperative_no_player_collisions_bit);
}
boolean network_coop_devices_remote(
void)
{
@@ -2468,51 +2430,6 @@ void network_coop_note_effect(
event->value = cutscene_flag_index;
}
void network_coop_note_surface_broken(
short breakable_surface_index,
real_point3d const *epicenter)
{
struct distributed_coop_event *event = event_new(_coop_event_surface_broken);
if (!event)
return;
event->value = breakable_surface_index;
event->frame = global_structure_bsp_index;
event->reals[0] = epicenter->x;
event->reals[1] = epicenter->y;
event->reals[2] = epicenter->z;
}
/* host: a few broken surfaces again each refresh, round them all, for a
machine that joined since or lost the message (broken from their middle) */
static void host_send_broken_surfaces(
void)
{
enum { SURFACES_PER_REFRESH = 8 };
static short cursor;
struct structure_bsp *structure_bsp = global_structure_bsp_get();
short count = (short)structure_bsp->breakable_surfaces.count;
short sent = 0;
short step;
if (!host_resend.refresh || count <= 0)
return;
for (step = 0; step < count && sent < SURFACES_PER_REFRESH; step++)
{
short index = (short)((cursor + step) % count);
if (!breakable_surface_extant(index))
{
struct structure_breakable_surface *surface = TAG_BLOCK_GET_ELEMENT(&structure_bsp->breakable_surfaces,
index, struct structure_breakable_surface);
network_coop_note_surface_broken(index, &surface->centroid);
sent++;
}
}
cursor = (short)((cursor + step) % count);
}
void network_coop_note_object_effect(
long effect_definition_index,
long object_index,
@@ -2548,7 +2465,7 @@ boolean network_coop_vote_skip(
return TRUE;
}
/* The host reverted its game state, clock included. The clock is
/* The skip reverted the host's game state, clock included. The clock is
put back, since clients and the netcode expect it to only go forward, and
the script threads' wake times move by the same amount so they still wake
when they would have. The revert also renumbered the input queues from the
@@ -2557,7 +2474,7 @@ from the restored one, or no tick would run until they caught up. And the
revert is stamped with the restored time, since the script's game_reverted
compares the stamp with the clock: otherwise it would play the cinematic
it was meant to skip. */
void network_coop_reverted(
void network_coop_skip_reverted(
long now)
{
long ticks;
@@ -2574,15 +2491,8 @@ void network_coop_reverted(
}
/* (the dropships' riders kept are of the game state reverted from) */
coop_enemies_reset();
error(_error_silent, "co-op: reverted %ld ticks, clock kept at %ld", ticks, now);
event_new(_coop_event_reverted);
}
void network_coop_skip_done(
void)
{
if (!coop_host())
return;
error(_error_silent, "co-op: cutscene skipped; reverted %ld ticks, clock kept at %ld", ticks, now);
event_new(_coop_event_cutscene_skipped);
skip_vote_clear();
skip_vote.requested = FALSE;
skip_vote.cooldown_until = game_time_get() + SKIP_COOLDOWN_TICKS;
@@ -2627,7 +2537,6 @@ void network_coop_host_tick(
host_send_attachments();
host_send_looping_sounds();
host_send_hud_state();
host_send_broken_surfaces();
host_send_events();
}
@@ -2689,16 +2598,7 @@ void network_coop_note_player_structure_bsp(
boolean network_coop_player_has_structure_bsp(
long player_index)
{
struct player_datum *player;
if (!network_coop_active())
return TRUE;
/* (no player, or none of the tracked: whose BSP is not known, which
does not keep their predictions out) */
player = player_index != NONE ? player_try_and_get(player_index) : NULL;
if (!player || DATUM_INDEX_TO_ABSOLUTE_INDEX(player_index) >= MAXIMUM_TRACKED_PLAYERS)
return TRUE;
return player->local_player_index != NONE ||
return !network_coop_active() || player_get(player_index)->local_player_index != NONE ||
host_player_structure_bsps[DATUM_INDEX_TO_ABSOLUTE_INDEX(player_index)] == global_structure_bsp_index_get();
}
@@ -3004,7 +2904,6 @@ static void client_presentation_apply(
!coop_spectate_watching_rider(0)) || coop_spectate_nothing_to_watch(0));
if (coop_presentation.cinematic_started)
cinematic_show_letterbox(TEST_FLAG(presentation->flags, _presentation_letterbox_bit));
objects_port_set_activating_cluster((short)(presentation->activating_cluster - 1));
if (coop_presentation.host_camera)
{
real_vector3d forward, up;
+3 -13
View File
@@ -45,9 +45,6 @@ enum
/* whether this is a network co-op game (not its lobby, whose menu scene also
has no game engine) */
boolean network_coop_active(void);
/* whether the players collide with each other: FALSE in a co-op game whose
host set Server Setup's PLAYER COLLISIONS off (bipeds.c, collisions.c) */
boolean network_coop_player_collisions(void);
/* whether this machine is a co-op client, whose devices only the host moves */
boolean network_coop_devices_remote(void);
/* devices.c: a group's devices were set straight to its value */
@@ -91,10 +88,6 @@ void network_coop_note_attach(long parent_index, char const *parent_marker_name,
char const *child_marker_name);
void network_coop_note_detach(long parent_index, long child_index);
void network_coop_note_object_effect(long effect_definition_index, long object_index, char const *marker_name);
/* breakable_surfaces.c: a breakable surface (glass) broke, from damage at
`epicenter`. Each machine's own shots break glass too, but with their own
random damage, so the host's breaks are the ones everyone keeps. */
void network_coop_note_surface_broken(short breakable_surface_index, real_point3d const *epicenter);
void network_coop_note_scenery_animation(long object_index, long animation_graph_index, short animation_index,
short frame_index);
@@ -103,12 +96,9 @@ network_coop_vote_skip returns FALSE outside network co-op, where the
cinematic is skipped as usual. */
boolean network_coop_skip_offered(void);
boolean network_coop_vote_skip(void);
/* main.c: after a co-op host reverts to its last saved state (to skip a
cinematic, or because the mission failed); now is the game time before the
revert */
void network_coop_reverted(long now);
/* main.c: the skip vote is over once the skip has reverted */
void network_coop_skip_done(void);
/* main.c: after a co-op host reverts to skip a cinematic; now is the game
time before the revert */
void network_coop_skip_reverted(long now);
/* the vote count to show, if a skippable cinematic is playing */
boolean network_coop_skip_vote_status(short *votes, short *voters, boolean *voted);
+13 -74
View File
@@ -34,10 +34,6 @@ being hit looks and feels like on the clients).
- The damage to a unit goes to the machines of the players it concerns
(the unit's, its riders', the damage's owner's) and those sent the
unit's player this tick, which see it; a killing blow to all.
- Scenery and devices that can be destroyed (lights, panels) are the
map's, at the same index on every machine. A client reports its hits on
them like any other, and every client deals the host's damage to its own
copy, which breaks and plays its effects as the host's did.
*/
#include "cseries.h"
@@ -63,8 +59,6 @@ being hit looks and feels like on the clients).
void damage_replay_player_effect(long player_index, struct damage_data *damage, real total_damage);
void damage_replay_aftermath(long object_index, struct damage_data *damage, unsigned long being_damaged_flags,
real shield_damage, real body_damage, real body_damage_multiplier, short body_part);
void damage_replay_static(long object_index, struct damage_data *damage, short node_index, short region_index,
short material_index);
void damage_replay_kill(long object_index, struct damage_data *damage, short node_index, short region_index,
short material_index);
/* network_distributed.c's */
@@ -148,14 +142,8 @@ enum
_damage_event_player_effect,
_damage_event_aftermath,
_damage_event_kill,
/* damage to destructible scenery or a device, to every machine */
_damage_event_static,
};
/* the objects every machine has from the map, at the same index, which
damage can break (static_target) */
#define STATIC_TARGET_TYPES (_object_mask_scenery | _object_mask_device)
/* what a player's damage is, by how the game deals it (the shape of their
report of it) */
enum
@@ -479,8 +467,7 @@ static boolean distributed_damage_to_data(
if (!tag_index_is_group(damage->definition_index, DAMAGE_EFFECT_DEFINITION_TAG))
return FALSE;
damage_data_new(result, damage->definition_index);
/* (the flags the game has) */
result->flags = damage->flags & (FLAG(NUMBER_OF_DAMAGE_DATA_FLAGS) - 1);
result->flags = damage->flags;
result->owner_player_index = distributed_player_from_byte(damage->owner_player_index);
result->owner_team_index = damage->owner_team_index;
result->owner_object_index = distributed_object_index_valid(damage->owner_object_index) &&
@@ -491,22 +478,11 @@ static boolean distributed_damage_to_data(
result->scale = damage->scale;
result->multiplier = damage->multiplier;
result->material_effect_scale = damage->material_effect_scale;
/* (a material the game has, else none, damage_data_new's: it indexes
the materials' modifiers) */
if (damage->material_type >= 0 && damage->material_type < NUMBER_OF_MATERIAL_TYPES)
result->material_type = damage->material_type;
result->material_type = damage->material_type;
scenario_location_from_point(&result->location, &result->epicenter);
return TRUE;
}
/* whether the object is scenery or a device that damage can destroy */
static boolean static_target(
long object_index)
{
return object_try_and_get_and_verify_type(object_index, STATIC_TARGET_TYPES) &&
object_get_maximum_body_vitality(object_index, TRUE) > 0.0f;
}
/* ---------- object_cause_damage (damage.c) */
/* whether the damage is a weapon's own at the unit that fires it (weapons.c:
@@ -653,8 +629,7 @@ boolean network_damage_deals(
damage_replay_player_effect(unit->unit.player_index, &effect, 0.0f);
}
/* a hit of this machine's own player's, on the host's object */
if (distributed_player_is_local(damage->owner_player_index) &&
(network_objects_client_has(object_index) || static_target(object_index)) &&
if (distributed_player_is_local(damage->owner_player_index) && network_objects_client_has(object_index) &&
damage_report_count < MAXIMUM_HIT_REPORTS_PER_TICK)
{
struct distributed_hit_report *report = &damage_reports[damage_report_count++];
@@ -742,33 +717,6 @@ void network_damage_player_effect(
/* the host: an object damaged, and what that did (object_damage_aftermath
done, so that a kill's killer is known) */
/* a damage event of this tick for the object, or NULL once the tick's are
spent (the last RESERVED_KILL_EVENTS are kept for killing blows) */
static struct distributed_damage_event *damage_event_new(
byte kind,
long object_index,
struct damage_data const *damage,
short node_index,
short region_index,
short material_index,
boolean kill)
{
struct distributed_damage_event *event;
if (damage_event_count >= MAXIMUM_DAMAGE_EVENTS_PER_TICK - (kill ? 0 : RESERVED_KILL_EVENTS))
return NULL;
event = &damage_events[damage_event_count++];
csmemset(event, 0, sizeof(*event));
event->kind = kind;
event->player_index = NO_PLAYER;
event->object_index = object_index;
distributed_damage_from_data(damage, &event->damage);
event->node_index = node_index;
event->region_index = region_index;
event->material_index = material_index;
return event;
}
void network_damage_aftermath(
long object_index,
struct damage_data const *damage,
@@ -788,11 +736,6 @@ void network_damage_aftermath(
if (game_connection() != _game_connection_network_server)
return;
if (static_target(object_index))
{
damage_event_new(_damage_event_static, object_index, damage, node_index, region_index, material_index, FALSE);
return;
}
/* (units only: items and the like the objects' states place) */
unit = (struct unit_datum *)object_try_and_get_and_verify_type(object_index, _object_mask_unit);
if (!unit)
@@ -806,15 +749,22 @@ void network_damage_aftermath(
(victim_player_index != NONE ||
(TEST_FLAG(_object_mask_biped, unit->object.type) &&
!TEST_FLAG(being_damaged_flags, _object_being_damaged_body_destroyed_bit)));
event = damage_event_new(_damage_event_aftermath, object_index, damage, node_index, region_index, material_index,
kill);
if (!event)
if (damage_event_count >= MAXIMUM_DAMAGE_EVENTS_PER_TICK - (kill ? 0 : RESERVED_KILL_EVENTS))
return;
event = &damage_events[damage_event_count++];
csmemset(event, 0, sizeof(*event));
event->kind = _damage_event_aftermath;
event->player_index = NO_PLAYER;
event->object_index = object_index;
distributed_damage_from_data(damage, &event->damage);
event->being_damaged_flags = being_damaged_flags;
event->shield_damage = shield_damage;
event->body_damage = body_damage;
event->body_damage_multiplier = body_damage_multiplier;
event->body_part = body_part;
event->node_index = node_index;
event->region_index = region_index;
event->material_index = material_index;
/* a killing blow, a player's with who the host says dealt it */
if (kill)
{
@@ -1769,8 +1719,6 @@ static boolean distributed_report_valid(
return FALSE;
target = (struct object_datum *)object_try_and_get_and_verify_type(report->object_index,
_object_mask_biped | _object_mask_vehicle | _object_mask_weapon | _object_mask_equipment);
if (!target && static_target(report->object_index))
target = object_get(report->object_index);
if (!target || !tag_index_is_group(report->damage.definition_index, DAMAGE_EFFECT_DEFINITION_TAG))
return FALSE;
/* numbers the game can take */
@@ -2212,15 +2160,6 @@ void network_damage_handle_events(
players_show_telefragged(victim_player_index);
}
break;
case _damage_event_static:
if (distributed_object_index_valid(event->object_index) && static_target(event->object_index) &&
distributed_damage_indices_valid(event->object_index, event->node_index, event->region_index,
event->material_index))
{
damage_replay_static(event->object_index, &damage, event->node_index, event->region_index,
event->material_index);
}
break;
}
}
}
+6 -54
View File
@@ -95,7 +95,7 @@ void game_engine_read_network_state(byte const *buffer, long size);
/* physics.c's (world units a tick, each tick) */
extern real global_gravity;
/* network_server_manager.c's, cseries_windows.c's, console.c's, p2p.c's */
void network_game_server_kick_machine(long machine_index, boolean kept_out);
void network_game_server_kick_machine(long machine_index);
unsigned long network_game_server_machine_address(long machine_index);
char const *network_game_server_machine_hardware_id(long machine_index);
void p2p_hardware_id_sanitize(char *destination, int size, const char *source);
@@ -588,10 +588,7 @@ static long distributed_received_times[MAXIMUM_SENDERS][NUMBER_OF_DISTRIBUTED_ME
/* the host: each client machine's clock, measured (CLIENT_CLOCK_WINDOW_MILLISECONDS):
its latest tick, and its tick and the host's time as the window began
(NONE: none begun); how many windows in a row it went fast, and whether
the last did; and whether, since they began, a message that came over its
stream was that far ahead too (a datagram's source address is all that
says which machine sent it, and anyone can send one as from another's: a
machine is banned only on its stream's word, distributed_note_client_clock) */
the last did */
/* the host: each client machine's Discord user, as it told it, the text
kept only of what is allowed (p2p_discord_sanitize) */
static struct distributed_client_identity distributed_client_identities[HALO_PORT_MAXIMUM_NETWORK_MACHINES];
@@ -605,11 +602,7 @@ static struct distributed_client_clock
unsigned long window_time;
short fast_windows;
boolean fast;
boolean ahead_on_stream;
} distributed_client_clocks[HALO_PORT_MAXIMUM_NETWORK_MACHINES];
/* the host: whether the message being handled came over its machine's
stream (network_distributed_handle_stream_message) */
static boolean distributed_handling_stream_message;
/* a client: the host's latest tick it has had a message of */
static long distributed_host_time = NONE;
/* the host: each client's round trip, in ticks, and its jitter */
@@ -1629,14 +1622,6 @@ static void distributed_correct_own_unit(
short local_player_index = player->local_player_index;
real_point3d position = state->position;
/* (co-op: the host has its player in a BSP this machine hasn't loaded
yet, the switch still on its way. Moved there, it would fall with no
floor under it, so it stays on the floor it has until the BSP loads.) */
if (network_coop_active() && scenario_leaf_index_from_point(&state->position) == NONE &&
scenario_leaf_index_from_point(&object_get(unit_index)->object.position) != NONE)
{
return;
}
if (TEST_FLAG(state->flags, _distributed_unit_predicted_bit) &&
local_player_index >= 0 && local_player_index < MAXIMUM_LOCAL_PLAYERS)
{
@@ -2104,7 +2089,6 @@ static void distributed_apply_predictions(
from a co-op client still loading the host's BSP, whose player
falls there with no floor under it) */
if (unit_index != NONE && unit_index == state->unit_index &&
unit_get(unit_index)->unit.player_index != NONE &&
network_coop_player_has_structure_bsp(unit_get(unit_index)->unit.player_index))
{
distributed_take_prediction(player_index, unit_index, &bound);
@@ -3725,8 +3709,6 @@ static void distributed_note_client_clock(
if (machine_index < 0 || machine_index >= HALO_PORT_MAXIMUM_NETWORK_MACHINES)
return;
clock = &distributed_client_clocks[machine_index];
if (distributed_handling_stream_message && tick - game_time_get() > CLIENT_CLOCK_AHEAD_TICKS)
clock->ahead_on_stream = TRUE;
if (clock->window_tick == NONE)
{
clock->latest_tick = tick;
@@ -3748,7 +3730,6 @@ static void distributed_note_client_clock(
if (!clock->fast)
{
clock->fast_windows = 0;
clock->ahead_on_stream = FALSE;
}
else if (++clock->fast_windows == 1)
{
@@ -3765,7 +3746,7 @@ static void distributed_note_client_clock(
char discord_id[DISCORD_ID_SIZE];
char discord_name[DISCORD_NAME_SIZE];
char discord[DISCORD_ID_SIZE + DISCORD_NAME_SIZE + 16] = "";
char reason[96];
char reason[64];
char text[MAXIMUM_NOTICE_LENGTH];
distributed_machine_player_names(machine_index, names, sizeof(names));
@@ -3775,30 +3756,14 @@ static void distributed_note_client_clock(
distributed_client_identities[machine_index].discord_name, 1);
if (discord_id[0] || discord_name[0])
snprintf(discord, sizeof(discord), " (Discord: %s, %s)", discord_name, discord_id);
snprintf(reason, sizeof(reason), "speed hack (game ran %.2f times as fast)%s", rate,
clock->ahead_on_stream ? "" : " (its datagrams only: not banned)");
snprintf(reason, sizeof(reason), "speed hack (game ran %.2f times as fast)", rate);
snprintf(text, sizeof(text), "%s%s kicked by the host: their game ran %.2f times as fast (a speed hack)",
names, discord, rate);
distributed_send_notice(text);
/* banned, and kept out, only when its stream said so
too; on its datagrams' word alone (which another machine
could have sent as from its address), logged and dropped,
and it may join again */
if (clock->ahead_on_stream)
{
distributed_log_cheater(machine_index, names, reason);
}
else
{
char address[32];
distributed_machine_address_text(machine_index, address, sizeof(address));
distributed_write_player_record(CHEATERS_FILE, address, machine_index, names, reason);
}
distributed_log_cheater(machine_index, names, reason);
}
network_game_server_kick_machine(machine_index, clock->ahead_on_stream);
network_game_server_kick_machine(machine_index);
clock->fast_windows = 0;
clock->ahead_on_stream = FALSE;
}
}
clock->window_tick = clock->latest_tick;
@@ -4136,16 +4101,3 @@ void network_distributed_handle_message(
}
}
}
/* (the host: network_server_message_handler.c) a client machine's message
of the distributed kind that came over its stream, which no other machine
can send as it: as network_distributed_handle_message */
void network_distributed_handle_stream_message(
long machine_index,
word const *message,
word size)
{
distributed_handling_stream_message = TRUE;
network_distributed_handle_message(machine_index, message, size);
distributed_handling_stream_message = FALSE;
}
+2 -22
View File
@@ -60,7 +60,6 @@ same datum index (identifier and all), so that any message can name one:
#include "items/weapons.h"
#include "items/weapon_definitions.h"
#include "items/equipment_definitions.h"
#include "camera/observer.h"
#include "cutscene/cinematics.h"
#include "network_coop.h"
#include "network_distributed.h"
@@ -245,11 +244,7 @@ struct distributed_object_change
byte change;
byte flags;
byte owner_player_index;
/* which bitmap of its shaders it draws with, when not its tag's (0). An
AI unit's actor variant sets it after the unit is made
(actor_customize_unit): the Elite major's and commander's armor are 1
and 2. Builds before it sent 0 here. */
byte forced_shader_permutation_index;
byte pad;
long object_index;
long definition_index;
short owner_team_index;
@@ -1008,7 +1003,6 @@ static void distributed_change_from_object(
change->owner_player_index = distributed_player_to_byte(object->object.owner_player_index);
change->owner_team_index = object->object.owner_team_index;
change->variant_number = object->object.variant_number;
change->forced_shader_permutation_index = (byte)PIN(object->object.forced_shader_permutation_index, 0, 0xFF);
change->position = object->object.position;
change->forward = object->object.forward;
change->up = object->object.up;
@@ -1298,20 +1292,8 @@ static short distributed_host_object_period(
count = objects_host_viewers.player_unit_count;
origins = objects_host_viewers.player_unit_origins;
}
/* in a co-op cutscene every client looks through the host's camera, so
what it films is sent as often as what's next to the client's players
(b30's sword Elite walking out of the door) */
if (network_coop_active() && cinematic_in_progress())
{
real_point3d const *camera = &observer_get_camera(0)->position;
real dx = position->x - camera->x;
real dy = position->y - camera->y;
real dz = position->z - camera->z;
nearest = dx * dx + dy * dy + dz * dz;
}
/* nobody is alive anywhere, so nothing needs to be sent often */
if (!count && nearest < 0.0f)
if (!count)
return MAXIMUM_OBJECT_PERIOD_TICKS;
for (index = 0; index < count; index++)
{
@@ -2160,8 +2142,6 @@ static void distributed_client_apply_change(
struct object_datum *object = object_get(object_index);
csmemcpy(object->object.base_change_colors, change->change_colors, sizeof(object->object.base_change_colors));
if (change->forced_shader_permutation_index)
object->object.forced_shader_permutation_index = change->forced_shader_permutation_index;
/* (and the colors drawn, which object_new chose from the tag: an AI unit's
are its variant's, set after it was made, actors.c) */
{
+10 -13
View File
@@ -3,22 +3,19 @@ NETWORK_VR.C
VR players' hands for the other machines: an extension of the distributed
netcode (port/linux/NETCODE.md, network_distributed.c). A VR player's
weapon aims where the weapon hand's controller points and fires from the
hand, and the free hand holds a grenade and throws it its own way
(port/linux/src/vr.c; the right hand and the left, or left-handed the other
way round); the input the netcode relays has only where the head looks, so
the other machines would show that player aiming and throwing where they
look.
weapon aims where the right controller points and fires from the hand, and
the left hand holds a grenade and throws it its own way (port/linux/src/
vr.c); the input the netcode relays has only where the head looks, so the
other machines would show that player aiming and throwing where they look.
Every tick, a machine with a VR player sends the host that player's hands
(_distributed_message_vr_poses, unreliable): where the weapon aims, where
its muzzle is from the unit's camera (whichever hand holds it: the others
see shots leave the left hand of a left-handed player), whether the free
hand holds a grenade, and the last throw (from where, which way, how hard)
for a second after it. The host keeps them, its own VR players' too, and
sends each client those of the players of other machines. A machine drives
a VR player of another (players.c, weapons.c, units.c) as long as their
hands keep coming, else as before.
its muzzle is from the unit's camera, whether the left hand holds a
grenade, and the last throw (from where, which way, how hard) for a second
after it. The host keeps them, its own VR players' too, and sends each
client those of the players of other machines. A machine drives a VR
player of another (players.c, weapons.c, units.c) as long as their hands
keep coming, else as before.
What the hands decide is only how the game looks: damage is the shooter's
report, which the host checks as any other (network_damage.c).
+19 -56
View File
@@ -16,10 +16,7 @@ also need the tick after the pair it spans: two ticks behind.)
Rotations are blended as quaternions (normalised lerp, taking the shorter
way round), positions and scales linearly. Anything that moves further than
a tick of motion plausibly allows (teleports, respawns, camera cuts) snaps
instead of sweeping across the world, and so does a pose whose nodes moved
too far for one tick: two snapshots of different poses (a model swapped,
another weapon's skeleton, a pose left from seconds before) blended node by
node stretch vertices across the screen.
instead of sweeping across the world.
Particles, contrails and other effects already move every frame
(game_frame), so they need nothing here.
@@ -59,17 +56,6 @@ unsigned long config_changes(void);
/* world units (10 feet each) a node may move in one tick before it snaps:
well beyond any vehicle, short of any teleport */
#define OBJECT_SNAP_DISTANCE 10.0f
/* ... a node may move in the object's root node's frame in one tick before
the pose is taken for a new one, not blended to: further than any limb or
part of a model moves in 33 ms (37 m/s), short of the game changing a pose
at once (an actor waking from dormancy, a model swapped), which blended
sweeps the vertices through poses it never had. (In the root's frame, so
that the whole object turning moves nothing: measured in the world, a fast
turn swung the head and weapon far enough to snap, and characters moved
like robots.) */
#define NODE_SNAP_DISTANCE 0.4f
/* ... a first-person node may move relative to the camera */
#define FIRST_PERSON_SNAP_DISTANCE 0.25f
/* a correction's difference left drawn after each tick (of 1) */
#define CORRECTION_DECAY 0.6f
/* ... and small enough to be none */
@@ -325,6 +311,16 @@ static void matrix_blend_rotations(
result->up.k = lerp(a->up.k, b->up.k, t);
}
/* a matrix a fraction t of the way from a to b */
static void matrix_blend(real_matrix4x3 const *a, real_matrix4x3 const *b, real t, real_matrix4x3 *result)
{
struct interpolation_rotation rotation_a, rotation_b;
rotation_from_matrix(a, &rotation_a);
rotation_from_matrix(b, &rotation_b);
matrix_blend_rotations(a, b, &rotation_a, &rotation_b, t, result);
}
/* the vector's parts' sum, large enough to be a correction (so written that
one not a number is none) */
static boolean correction_significant(real_vector3d const *correction)
@@ -534,22 +530,14 @@ real_matrix4x3 *render_interpolation_object_node_matrices(long object_index)
real_matrix4x3 const *latest = record->nodes + record->latest * record->node_capacity;
real_matrix4x3 *blended = record->nodes + 2 * record->node_capacity;
short node_index;
/* (so written that a position not a number snaps) */
boolean snap = !(distance_squared(&previous[0].position, &latest[0].position) <=
OBJECT_SNAP_DISTANCE * OBJECT_SNAP_DISTANCE);
/* a node moved further in the root's frame than a tick allows: the
two snapshots are different poses, not one moving (so written that
a position not a number snaps) */
for (node_index = 1; !snap && node_index < record->node_count; node_index++)
if (!(distance_squared(&previous[0].position, &latest[0].position) <=
OBJECT_SNAP_DISTANCE * OBJECT_SNAP_DISTANCE))
{
real_point3d previous_local, latest_local;
matrix4x3_inverse_transform_point(&previous[0], &previous[node_index].position, &previous_local);
matrix4x3_inverse_transform_point(&latest[0], &latest[node_index].position, &latest_local);
snap = !(distance_squared(&previous_local, &latest_local) <= NODE_SNAP_DISTANCE * NODE_SNAP_DISTANCE);
memcpy(blended, latest, record->node_count * sizeof(real_matrix4x3));
}
if (!snap)
else
{
struct interpolation_rotation *previous_rotations =
record->rotations + (record->latest ^ 1) * record->node_capacity;
@@ -578,8 +566,6 @@ real_matrix4x3 *render_interpolation_object_node_matrices(long object_index)
&latest_rotations[node_index], interpolation_fraction, &blended[node_index]);
}
}
if (snap)
memcpy(blended, latest, record->node_count * sizeof(real_matrix4x3));
if (correction_significant(&record->correction) || correction_significant(&record->correction_pending))
{
real_vector3d drawn;
@@ -818,8 +804,6 @@ void render_interpolation_first_person(
struct interpolated_first_person *first_person;
real_matrix4x3 camera_matrix;
real_matrix4x3 inverse_camera;
struct interpolation_rotation previous_rotations[MAXIMUM_INTERPOLATED_NODES];
struct interpolation_rotation latest_rotations[MAXIMUM_INTERPOLATED_NODES];
short node_index;
if (!interpolation_rendering ||
@@ -833,11 +817,8 @@ void render_interpolation_first_person(
matrix4x3_inverse(&camera_matrix, &inverse_camera);
if (first_person->tick != interpolation_tick)
{
/* the pose drawn last, if that was at the end of the tick just
before: not one left from before the view was last away from first
person (zoomed, in a vehicle, dead, in a cinematic), seconds old */
first_person->has_previous = first_person->tick == interpolation_tick - 1 &&
first_person->node_count == node_count;
/* the pose drawn last, at the end of the previous tick */
first_person->has_previous = first_person->node_count == node_count;
memcpy(first_person->previous, first_person->latest, sizeof(first_person->previous));
first_person->tick = interpolation_tick;
}
@@ -846,31 +827,13 @@ void render_interpolation_first_person(
first_person->node_count = node_count;
if (!first_person->has_previous)
return;
/* a node that jumped further in the camera's frame than a tick allows:
the last pose was another weapon's skeleton (of as many nodes), not this
one moving (so written that a position not a number snaps) */
for (node_index = 0; node_index < node_count; node_index++)
{
real_matrix4x3 const *previous = &first_person->previous[node_index];
real_matrix4x3 const *latest = &first_person->latest[node_index];
if (!(distance_squared(&previous->position, &latest->position) <=
FIRST_PERSON_SNAP_DISTANCE * FIRST_PERSON_SNAP_DISTANCE))
{
return;
}
rotation_from_matrix(previous, &previous_rotations[node_index]);
rotation_from_matrix(latest, &latest_rotations[node_index]);
}
for (node_index = 0; node_index < node_count; node_index++)
{
real_matrix4x3 blended;
matrix_blend_rotations(
matrix_blend(
&first_person->previous[node_index],
&first_person->latest[node_index],
&previous_rotations[node_index],
&latest_rotations[node_index],
interpolation_fraction,
&blended);
matrix4x3_multiply(&camera_matrix, &blended, &node_matrices[node_index]);
-875
View File
@@ -1,875 +0,0 @@
/*
VR_ARMS.C
The VR arms' bones (vr_arms.h): the first-person hands' arms bent from the
shoulders to the hands (interface/first_person_weapons.c poses them).
The solver, the forearm's twist and their helpers are after
moistman42069/HaloCE-Quest-VR (CC0), port/linux/game/vr_render.c, nearly
as they are there: the skeleton is read through its animation graph's
index (the nodes under each node found once a frame, not per node), and
the time is the headset's frame's (halo_vr_pose_time).
*/
#include "cseries.h"
#include "math/real_math.h"
#include "models/model_animation_definitions.h"
#include "halo_vr.h"
#include "vr_arms.h"
#include <math.h>
#include <string.h>
#ifdef HALO_ANDROID
#define VR_UNITS_PER_METRE (1.0f / 3.048f)
/* ---------- the skeleton */
boolean vr_skeleton_build(
struct vr_skeleton *skeleton,
long graph_index,
short node_count)
{
short node_index;
skeleton->graph_index = NONE;
if (graph_index == NONE || node_count <= 0 || node_count > MAXIMUM_NODES_PER_ANIMATION ||
node_count != animation_graph_node_count(graph_index))
{
return FALSE;
}
for (node_index = 0; node_index < node_count; node_index++)
{
short parent = animation_graph_node_parent(graph_index, node_index);
/* (parents before their children: else no parent) */
skeleton->parents[node_index] = parent >= 0 && parent < node_index ? parent : NONE;
skeleton->under[node_index] = 0;
}
/* each node under itself and all its ancestors */
for (node_index = 0; node_index < node_count; node_index++)
{
short ancestor = node_index;
while (ancestor != NONE)
{
skeleton->under[ancestor] |= 1ULL << node_index;
ancestor = skeleton->parents[ancestor];
}
}
skeleton->graph_index = graph_index;
skeleton->node_count = node_count;
return TRUE;
}
boolean vr_skeleton_under(
struct vr_skeleton const *skeleton,
short node_index,
short root)
{
return root >= 0 && root < skeleton->node_count && node_index >= 0 && node_index < skeleton->node_count &&
(skeleton->under[root] & (1ULL << node_index)) != 0;
}
/* ---------- rotations (column vectors) */
/* v += a * scale */
static void vr_vector_scale_add_local(
real_vector3d *v,
real_vector3d const *a,
real scale)
{
v->i += a->i * scale;
v->j += a->j * scale;
v->k += a->k * scale;
}
static real vr_length(
real_vector3d const *v)
{
return (real)sqrt(v->i * v->i + v->j * v->j + v->k * v->k);
}
static void vr_point_minus(
real_point3d const *a,
real_point3d const *b,
real_vector3d *out)
{
out->i = a->x - b->x;
out->j = a->y - b->y;
out->k = a->z - b->z;
}
static boolean vr_unit_vector(
real_vector3d *v)
{
real length = vr_length(v);
if (!(length > 1e-5f && length < 1e8f))
return FALSE;
v->i /= length;
v->j /= length;
v->k /= length;
return TRUE;
}
/* the rotation taking direction a to direction b, the shortest way */
static void vr_rotation_between(
real_vector3d const *a,
real_vector3d const *b,
real rotation[3][3])
{
real_vector3d u = *a, v = *b, axis;
real c, s, t;
normalize3d(&u);
normalize3d(&v);
cross_product3d(&u, &v, &axis);
s = vr_length(&axis);
c = u.i * v.i + u.j * v.j + u.k * v.k;
if (s < 1e-6f)
{
memset(rotation, 0, sizeof(real) * 9);
if (c >= 0.0f)
{
rotation[0][0] = rotation[1][1] = rotation[2][2] = 1.0f;
}
else
{
/* half a turn about an axis across */
real_vector3d basis;
int row, column;
basis.i = 1.0f;
basis.j = basis.k = 0.0f;
if (fabs(u.i) > 0.8f)
{
basis.i = 0.0f;
basis.j = 1.0f;
}
cross_product3d(&u, &basis, &axis);
normalize3d(&axis);
for (row = 0; row < 3; row++)
{
for (column = 0; column < 3; column++)
rotation[row][column] = 2.0f * axis.n[row] * axis.n[column] - (row == column ? 1.0f : 0.0f);
}
}
return;
}
axis.i /= s;
axis.j /= s;
axis.k /= s;
t = 1.0f - c;
rotation[0][0] = c + axis.i * axis.i * t;
rotation[0][1] = axis.i * axis.j * t - axis.k * s;
rotation[0][2] = axis.i * axis.k * t + axis.j * s;
rotation[1][0] = axis.j * axis.i * t + axis.k * s;
rotation[1][1] = c + axis.j * axis.j * t;
rotation[1][2] = axis.j * axis.k * t - axis.i * s;
rotation[2][0] = axis.k * axis.i * t - axis.j * s;
rotation[2][1] = axis.k * axis.j * t + axis.i * s;
rotation[2][2] = c + axis.k * axis.k * t;
}
/* a rotation of angle radians about the unit axis */
static void vr_axis_rotation(
real_vector3d const *axis,
real angle,
real rotation[3][3])
{
real c = (real)cos(angle), s = (real)sin(angle), t = 1.0f - c;
real x = axis->i, y = axis->j, z = axis->k;
rotation[0][0] = c + x * x * t;
rotation[0][1] = x * y * t - z * s;
rotation[0][2] = x * z * t + y * s;
rotation[1][0] = y * x * t + z * s;
rotation[1][1] = c + y * y * t;
rotation[1][2] = y * z * t - x * s;
rotation[2][0] = z * x * t - y * s;
rotation[2][1] = z * y * t + x * s;
rotation[2][2] = c + z * z * t;
}
static void vr_rotate_vector(
real rotation[3][3],
real_vector3d const *in,
real_vector3d *out)
{
real_vector3d v = *in;
out->i = rotation[0][0] * v.i + rotation[0][1] * v.j + rotation[0][2] * v.k;
out->j = rotation[1][0] * v.i + rotation[1][1] * v.j + rotation[1][2] * v.k;
out->k = rotation[2][0] * v.i + rotation[2][1] * v.j + rotation[2][2] * v.k;
}
static void vr_multiply_rotations(
real a[3][3],
real b[3][3],
real out[3][3])
{
real r[3][3];
int row, column;
for (row = 0; row < 3; row++)
{
for (column = 0; column < 3; column++)
r[row][column] = a[row][0] * b[0][column] + a[row][1] * b[1][column] + a[row][2] * b[2][column];
}
memcpy(out, r, sizeof(r));
}
/* a node moved from `from` to `to` and turned by rotation about itself: its
matrix, or that of a node it carries (whose old matrix is node) */
static void vr_carry(
real_matrix4x3 *node,
real_point3d const *from,
real_point3d const *to,
real rotation[3][3])
{
real_vector3d offset;
vr_point_minus(&node->position, from, &offset);
vr_rotate_vector(rotation, &offset, &offset);
node->position.x = to->x + offset.i;
node->position.y = to->y + offset.j;
node->position.z = to->z + offset.k;
vr_rotate_vector(rotation, &node->forward, &node->forward);
vr_rotate_vector(rotation, &node->left, &node->left);
vr_rotate_vector(rotation, &node->up, &node->up);
}
/* ---------- the arm */
void vr_solve_arm(
struct vr_skeleton const *skeleton,
real_matrix4x3 *matrices,
short const bones[NUMBER_OF_VR_ARM_BONES],
real_point3d shoulder,
real_point3d const *target,
real_vector3d const *pole,
boolean hand_stays,
struct vr_limb_history *history)
{
real_matrix4x3 old[NUMBER_OF_VR_ARM_BONES];
real rotation[NUMBER_OF_VR_ARM_BONES][3][3];
real_point3d moved_to[NUMBER_OF_VR_ARM_BONES];
real_vector3d upper, fore, reach, direction, bend, old_direction;
real upper_length, fore_length, distance, along, across;
real_point3d solved_target = *target;
short moved[MAXIMUM_NODES_PER_ANIMATION];
short index, bone;
for (bone = 0; bone < NUMBER_OF_VR_ARM_BONES; bone++)
{
if (bones[bone] < 0 || bones[bone] >= skeleton->node_count)
return;
old[bone] = matrices[bones[bone]];
}
vr_point_minus(&old[_vr_arm_fore].position, &old[_vr_arm_upper].position, &upper);
vr_point_minus(&old[_vr_arm_hand].position, &old[_vr_arm_fore].position, &fore);
upper_length = vr_length(&upper);
fore_length = vr_length(&fore);
if (!isfinite(upper_length) || !isfinite(fore_length) || upper_length < 1e-4f || fore_length < 1e-4f)
return;
vr_point_minus(target, &shoulder, &reach);
distance = vr_length(&reach);
if (!isfinite(distance) || distance < 1e-4f)
return;
direction = reach;
normalize3d(&direction);
/* out of reach: the shoulder reaches a little (8 cm), then the arm
stretches a little (18%); the hand stays where it is held */
if (distance > (upper_length + fore_length) * (history ? 0.996f : 0.999f))
{
real maximum = (upper_length + fore_length) * (history ? 0.996f : 0.999f);
real travel = distance - maximum;
travel = MIN(travel, 0.08f * VR_UNITS_PER_METRE);
shoulder.x += direction.i * travel;
shoulder.y += direction.j * travel;
shoulder.z += direction.k * travel;
distance -= travel;
if (history && distance > maximum)
{
real stretch = MIN(distance / maximum, 1.18f);
upper_length *= stretch;
fore_length *= stretch;
maximum *= stretch;
}
if (hand_stays && distance > maximum)
{
/* (further still, as this port had it: the shoulder follows the
hand, and the arm stays whole) */
shoulder.x += direction.i * (distance - maximum);
shoulder.y += direction.j * (distance - maximum);
shoulder.z += direction.k * (distance - maximum);
}
distance = MIN(distance, maximum);
}
if (history)
{
/* bent no further than 145 degrees: nearer, the bones shorten (to
65% at most) */
real minimum = (real)sqrt(upper_length * upper_length + fore_length * fore_length -
1.638304f * upper_length * fore_length);
if (distance < minimum)
{
real compress = MAX(0.65f, distance / minimum);
upper_length *= compress;
fore_length *= compress;
}
}
if (distance < (real)fabs(upper_length - fore_length) + 1e-3f)
distance = (real)fabs(upper_length - fore_length) + 1e-3f;
solved_target.x = shoulder.x + direction.i * distance;
solved_target.y = shoulder.y + direction.j * distance;
solved_target.z = shoulder.z + direction.k * distance;
/* the elbow: in the plane of the reach and the pole */
{
real dot = pole->i * direction.i + pole->j * direction.j + pole->k * direction.k;
bend.i = pole->i - direction.i * dot;
bend.j = pole->j - direction.j * dot;
bend.k = pole->k - direction.k * dot;
if (vr_length(&bend) < 1e-4f)
{
/* reaching along the pole: the old bend, else a steady side */
dot = dot_product3d(&upper, &direction);
bend = upper;
bend.i -= dot * direction.i;
bend.j -= dot * direction.j;
bend.k -= dot * direction.k;
if (vr_length(&bend) < 1e-4f)
{
real_vector3d basis;
basis.i = basis.j = 0.0f;
basis.k = 1.0f;
if (fabs(direction.k) > 0.9f)
{
basis.j = 1.0f;
basis.k = 0.0f;
}
cross_product3d(&direction, &basis, &bend);
}
}
if (history)
{
/* the elbow's swing about the reach, eased (18 a second, at most 8
radians a second) unless the arm was away or jumped */
double now = halo_vr_pose_time();
double dt = now - history->time;
real_vector3d previous = history->bend, travel;
real confidence = PIN(vr_length(&bend) / 0.35f, 0.0f, 1.0f);
vr_point_minus(&shoulder, &history->shoulder, &travel);
dot = dot_product3d(&previous, &direction);
previous.i -= dot * direction.i;
previous.j -= dot * direction.j;
previous.k -= dot * direction.k;
if (history->valid && history->graph_index == skeleton->graph_index && dt >= 0.0 && dt < 0.25 &&
vr_length(&travel) < 0.5f * VR_UNITS_PER_METRE && vr_unit_vector(&previous) && vr_unit_vector(&bend))
{
real_vector3d tangent;
real angle, change;
cross_product3d(&previous, &bend, &tangent);
angle = (real)atan2(dot_product3d(&tangent, &direction), dot_product3d(&previous, &bend));
change = angle * (1.0f - (real)exp(-18.0 * MIN(dt, 0.05))) * confidence;
change = PIN(change, (real)(-8.0 * dt), (real)(8.0 * dt));
cross_product3d(&direction, &previous, &tangent);
bend.i = previous.i * (real)cos(change) + tangent.i * (real)sin(change);
bend.j = previous.j * (real)cos(change) + tangent.j * (real)sin(change);
bend.k = previous.k * (real)cos(change) + tangent.k * (real)sin(change);
}
else
{
history->roll_valid = FALSE;
}
normalize3d(&bend);
history->valid = TRUE;
history->graph_index = skeleton->graph_index;
history->time = now;
history->shoulder = shoulder;
history->bend = bend;
}
else
{
normalize3d(&bend);
}
}
along = (upper_length * upper_length + distance * distance - fore_length * fore_length) / (2.0f * distance);
across = upper_length * upper_length - along * along;
across = across > 0.0f ? (real)sqrt(across) : 0.0f;
moved_to[_vr_arm_upper] = shoulder;
moved_to[_vr_arm_fore].x = shoulder.x + direction.i * along + bend.i * across;
moved_to[_vr_arm_fore].y = shoulder.y + direction.j * along + bend.j * across;
moved_to[_vr_arm_fore].z = shoulder.z + direction.k * along + bend.k * across;
moved_to[_vr_arm_hand] = solved_target;
/* each bone turned to point at the next joint, and the hand carried by
the forearm's turn */
vr_point_minus(&moved_to[_vr_arm_fore], &moved_to[_vr_arm_upper], &reach);
vr_rotation_between(&upper, &reach, rotation[_vr_arm_upper]);
vr_rotate_vector(rotation[_vr_arm_upper], &fore, &old_direction);
vr_point_minus(&moved_to[_vr_arm_hand], &moved_to[_vr_arm_fore], &reach);
vr_rotation_between(&old_direction, &reach, rotation[_vr_arm_fore]);
vr_multiply_rotations(rotation[_vr_arm_fore], rotation[_vr_arm_upper], rotation[_vr_arm_fore]);
if (hand_stays)
{
/* the hand stays as it was put, and what it holds */
memset(rotation[_vr_arm_hand], 0, sizeof(rotation[_vr_arm_hand]));
rotation[_vr_arm_hand][0][0] = rotation[_vr_arm_hand][1][1] = rotation[_vr_arm_hand][2][2] = 1.0f;
moved_to[_vr_arm_hand] = old[_vr_arm_hand].position;
}
else
{
memcpy(rotation[_vr_arm_hand], rotation[_vr_arm_fore], sizeof(rotation[_vr_arm_hand]));
}
/* every node under a moved bone goes with the nearest one above it
(parents come first) */
for (index = 0; index < skeleton->node_count; index++)
{
short parent = skeleton->parents[index];
moved[index] = NONE;
for (bone = 0; bone < NUMBER_OF_VR_ARM_BONES; bone++)
{
if (bones[bone] == index)
moved[index] = bone;
}
if (moved[index] == NONE && parent != NONE)
moved[index] = moved[parent];
if (moved[index] != NONE)
{
bone = moved[index];
vr_carry(&matrices[index], &old[bone].position, &moved_to[bone], rotation[bone]);
}
}
}
/* The controller's roll shared along the arm rather than all of it at the
wrist's skin: the hand's turn about the forearm from where the animation
has it against its forearm (old_fore, old_hand), 85% of it on the forearm
(none on the upper arm: vr_align_elbow rolls it), within 110 degrees, running on across half a
turn (history). The bones' places and the hand stay. */
void vr_distribute_arm_twist(
real_matrix4x3 *matrices,
short const bones[NUMBER_OF_VR_ARM_BONES],
real_matrix4x3 const *old_fore,
real_matrix4x3 const *old_hand,
struct vr_limb_history *history)
{
real_matrix4x3 *fore = &matrices[bones[_vr_arm_fore]];
real_matrix4x3 *hand = &matrices[bones[_vr_arm_hand]];
real_matrix3x3 delta;
real_vector3d expected[3], desired[3], axis;
real_quaternion q;
real angle, rotation[3][3];
int a, b, row, column;
vr_point_minus(&hand->position, &fore->position, &axis);
if (!vr_unit_vector(&axis))
return;
for (a = 0; a < 3; a++)
{
expected[a].i = expected[a].j = expected[a].k = 0.0f;
desired[a].i = hand->n[a][0];
desired[a].j = hand->n[a][1];
desired[a].k = hand->n[a][2];
for (b = 0; b < 3; b++)
{
real amount = old_fore->n[b][0] * old_hand->n[a][0] + old_fore->n[b][1] * old_hand->n[a][1] +
old_fore->n[b][2] * old_hand->n[a][2];
for (row = 0; row < 3; row++)
expected[a].n[row] += fore->n[b][row] * amount;
}
if (!vr_unit_vector(&expected[a]) || !vr_unit_vector(&desired[a]))
return;
}
for (row = 0; row < 3; row++)
{
for (column = 0; column < 3; column++)
{
delta.n[row][column] = 0.0f;
for (a = 0; a < 3; a++)
delta.n[row][column] += desired[a].n[row] * expected[a].n[column];
}
}
matrix3x3_rotation_to_quaternion(&delta, &q);
angle = 2.0f * (real)atan2(dot_product3d(&q.v, &axis), q.w);
if (!isfinite(angle))
return;
angle = (real)atan2(sin(angle), cos(angle));
/* (continuous across half a turn before the limit) */
if (history && history->roll_valid)
angle = history->roll + (real)atan2(sin(angle - history->roll), cos(angle - history->roll));
angle = PIN(angle, -1.92f, 1.92f);
if (history)
{
history->roll = angle;
history->roll_valid = TRUE;
}
vr_axis_rotation(&axis, angle * 0.85f, rotation);
vr_rotate_vector(rotation, &fore->forward, &fore->forward);
vr_rotate_vector(rotation, &fore->left, &fore->left);
vr_rotate_vector(rotation, &fore->up, &fore->up);
}
/* The upper arm rolled about its length so that the elbow bends in the
plane the arm now bends in, as the animation has it bend: the shortest
turn that took the upper arm to its new direction leaves its hinge where
it was, and with the elbow out to the side the elbow's skin turned in
(Quest, 2026-10-06). The hinge is the animation's bend (upper arm by
forearm, old_upper, old_fore, old_hand) in the upper arm's own axes. A
straight arm, then or now, has no bend: left as it is. */
void vr_align_elbow(
real_matrix4x3 *matrices,
short const bones[NUMBER_OF_VR_ARM_BONES],
real_matrix4x3 const *old_upper,
real_matrix4x3 const *old_fore,
real_matrix4x3 const *old_hand)
{
real_matrix4x3 *upper = &matrices[bones[_vr_arm_upper]];
real_matrix4x3 const *fore = &matrices[bones[_vr_arm_fore]];
real_matrix4x3 const *hand = &matrices[bones[_vr_arm_hand]];
real_vector3d old_along, old_below, old_bend, along, below, bend, hinge;
real c_forward, c_left, c_up, angle, rotation[3][3];
vr_point_minus(&old_fore->position, &old_upper->position, &old_along);
vr_point_minus(&old_hand->position, &old_fore->position, &old_below);
cross_product3d(&old_along, &old_below, &old_bend);
vr_point_minus(&fore->position, &upper->position, &along);
vr_point_minus(&hand->position, &fore->position, &below);
cross_product3d(&along, &below, &bend);
if (vr_length(&old_bend) < 1e-4f * vr_length(&old_along) * vr_length(&old_below) ||
vr_length(&bend) < 0.05f * vr_length(&along) * vr_length(&below) ||
!vr_unit_vector(&old_bend) || !vr_unit_vector(&bend) || !vr_unit_vector(&along))
{
return;
}
/* (the hinge in the upper arm's axes, then where those axes are now) */
c_forward = dot_product3d(&old_bend, &old_upper->forward);
c_left = dot_product3d(&old_bend, &old_upper->left);
c_up = dot_product3d(&old_bend, &old_upper->up);
hinge.i = upper->forward.i * c_forward + upper->left.i * c_left + upper->up.i * c_up;
hinge.j = upper->forward.j * c_forward + upper->left.j * c_left + upper->up.j * c_up;
hinge.k = upper->forward.k * c_forward + upper->left.k * c_left + upper->up.k * c_up;
/* (both across the arm: the turn about it between them) */
vr_vector_scale_add_local(&hinge, &along, -dot_product3d(&hinge, &along));
vr_vector_scale_add_local(&bend, &along, -dot_product3d(&bend, &along));
if (!vr_unit_vector(&hinge) || !vr_unit_vector(&bend))
return;
{
real_vector3d cross;
cross_product3d(&hinge, &bend, &cross);
angle = (real)atan2(dot_product3d(&cross, &along), dot_product3d(&hinge, &bend));
}
if (!isfinite(angle))
return;
vr_axis_rotation(&along, angle, rotation);
vr_rotate_vector(rotation, &upper->forward, &upper->forward);
vr_rotate_vector(rotation, &upper->left, &upper->left);
vr_rotate_vector(rotation, &upper->up, &upper->up);
}
/* ---------- the fingers
Each finger's joints are found by name: "finger" and its number (0 the
thumb to 4 the little finger), or "thumb", "index", "middle", "ring",
"pinky", on the hand's side ("l ", "r "). They are posed by absolute
directions in the controller's palm frame, each segment's subtree turned,
its length kept (after HaloCE-Quest-VR, CC0, nearly verbatim; its touch of
the world, a few rays a finger, is left out: it costs the Steam Frame's
processor too much). */
/* the finger (0 the thumb .. 4 the little finger) a node's name says it is
a joint of, on that side; NONE when not a finger's */
static short vr_finger_of(
char const *name,
char const *side)
{
static char const *const names[] = { "thumb", "index", "middle", "ring", "pinky" };
char const *finger;
short index;
if (!name || !strstr(name, side))
return NONE;
finger = strstr(name, "finger");
if (finger && finger[6] >= '0' && finger[6] <= '4')
return (short)(finger[6] - '0');
for (index = 0; index < 5; index++)
{
if (strstr(name, names[index]))
return index;
}
return NONE;
}
void vr_finger_nodes(
struct vr_skeleton const *skeleton,
short hand,
char const *side,
short joints[5][3])
{
short node_index, finger, depth;
for (finger = 0; finger < 5; finger++)
{
for (depth = 0; depth < 3; depth++)
joints[finger][depth] = NONE;
}
for (node_index = 0; node_index < skeleton->node_count; node_index++)
{
short at;
finger = vr_finger_of(animation_graph_node_name(skeleton->graph_index, node_index), side);
if (finger == NONE || !vr_skeleton_under(skeleton, node_index, hand))
continue;
/* its depth: how many of the same finger's joints above it */
depth = 0;
for (at = skeleton->parents[node_index]; at != NONE; at = skeleton->parents[at])
{
if (vr_finger_of(animation_graph_node_name(skeleton->graph_index, at), side) == finger)
depth++;
}
if (depth < 3)
joints[finger][depth] = node_index;
}
}
/* a node and all under it turned about the node */
static void vr_turn_subtree(
struct vr_skeleton const *skeleton,
real_matrix4x3 *matrices,
short root,
real rotation[3][3])
{
real_point3d pivot = matrices[root].position;
unsigned long long under = skeleton->under[root];
short node_index;
for (node_index = 0; node_index < skeleton->node_count; node_index++)
{
if (under & (1ULL << node_index))
vr_carry(&matrices[node_index], &pivot, &pivot, rotation);
}
}
/* a finger's joints (low, middle, tip) curled 0 (straight, a little bent
at rest) to 1 (a fist), in the palm's frame: forward along the fingers,
up the knuckles towards the index, palm out of the palm */
static boolean vr_pose_finger(
struct vr_skeleton const *skeleton,
real_matrix4x3 *matrices,
short hand,
short const joints[3],
int finger,
real curl,
real_vector3d const *palm,
real_vector3d const *forward,
real_vector3d const *up)
{
static real const rest[3] = { 0.12f, 0.15f, 0.10f };
static real const bend[3] = { 1.25f, 1.6f, 1.1f };
real_vector3d base, axis, direction, wanted;
real angle = 0.0f, dot, rotation[3][3];
int depth;
if (joints[0] == NONE || joints[1] == NONE || joints[2] == NONE)
return FALSE;
vr_point_minus(&matrices[joints[0]].position, &matrices[hand].position, &base);
dot = dot_product3d(&base, palm);
base.i -= dot * palm->i;
base.j -= dot * palm->j;
base.k -= dot * palm->k;
if (finger == 0)
{
/* the thumb: up the handle, or across the palm */
base.i = (up->i * 0.9f + forward->i * 0.3f) * (1.0f - curl) +
(forward->i * 0.75f + palm->i * 0.55f + up->i * 0.1f) * curl;
base.j = (up->j * 0.9f + forward->j * 0.3f) * (1.0f - curl) +
(forward->j * 0.75f + palm->j * 0.55f + up->j * 0.1f) * curl;
base.k = (up->k * 0.9f + forward->k * 0.3f) * (1.0f - curl) +
(forward->k * 0.75f + palm->k * 0.55f + up->k * 0.1f) * curl;
}
if (!vr_unit_vector(&base))
return FALSE;
cross_product3d(&base, palm, &axis);
if (!vr_unit_vector(&axis))
return FALSE;
for (depth = 0; depth < 3; depth++)
{
short joint = joints[depth];
if (depth < 2)
{
vr_point_minus(&matrices[joints[depth + 1]].position, &matrices[joint].position, &direction);
}
else
{
/* the tip: its own axis that the middle joint's bone runs along */
real_vector3d previous;
real_vector3d *axes[3];
real_vector3d *last[3];
real best = 0.8f, sign = 1.0f;
int a, found = -1;
axes[0] = &matrices[joints[1]].forward;
axes[1] = &matrices[joints[1]].left;
axes[2] = &matrices[joints[1]].up;
last[0] = &matrices[joint].forward;
last[1] = &matrices[joint].left;
last[2] = &matrices[joint].up;
vr_point_minus(&matrices[joint].position, &matrices[joints[1]].position, &previous);
if (!vr_unit_vector(&previous))
return FALSE;
for (a = 0; a < 3; a++)
{
real_vector3d unit = *axes[a];
if (!vr_unit_vector(&unit))
continue;
dot = dot_product3d(&unit, &previous);
if (fabs(dot) > best)
{
best = (real)fabs(dot);
found = a;
sign = dot < 0.0f ? -1.0f : 1.0f;
}
}
if (found < 0)
return TRUE;
direction.i = last[found]->i * sign;
direction.j = last[found]->j * sign;
direction.k = last[found]->k * sign;
}
if (!vr_unit_vector(&direction))
return FALSE;
angle += finger == 0 ? (depth == 0 ? 0.0f : (depth == 1 ? 0.35f : 0.3f) * curl) :
rest[depth] + bend[depth] * curl;
vr_axis_rotation(&axis, angle, rotation);
vr_rotate_vector(rotation, &base, &wanted);
vr_rotation_between(&direction, &wanted, rotation);
vr_turn_subtree(skeleton, matrices, joint, rotation);
}
return TRUE;
}
void vr_pose_fingers(
struct vr_skeleton const *skeleton,
real_matrix4x3 *matrices,
short hand,
short const joints[5][3],
float const curls[4],
real_vector3d const *palm,
real_vector3d const *forward,
real_vector3d const *up)
{
int finger;
for (finger = 0; finger < 5; finger++)
vr_pose_finger(skeleton, matrices, hand, joints[finger], finger, curls[finger < 3 ? finger : 3], palm, forward, up);
}
/* the finger's middle and tip turned this much (0..1) towards the line of
its low joint: a finger lifted off the trigger (the weapon hand's index,
whose low joint, and the rest of the hand, keep the weapon's grip) */
void vr_straighten_finger(
struct vr_skeleton const *skeleton,
real_matrix4x3 *matrices,
short const joints[3],
real amount)
{
real_vector3d low, middle, axis;
real rotation[3][3];
real angle;
if (amount <= 0.0f || joints[0] == NONE || joints[1] == NONE || joints[2] == NONE)
return;
vr_point_minus(&matrices[joints[1]].position, &matrices[joints[0]].position, &low);
vr_point_minus(&matrices[joints[2]].position, &matrices[joints[1]].position, &middle);
if (!vr_unit_vector(&low) || !vr_unit_vector(&middle))
return;
cross_product3d(&middle, &low, &axis);
if (!vr_unit_vector(&axis))
return;
angle = (real)acos(PIN(dot_product3d(&middle, &low), -1.0f, 1.0f)) * PIN(amount, 0.0f, 1.0f);
vr_axis_rotation(&axis, angle, rotation);
vr_turn_subtree(skeleton, matrices, joints[1], rotation);
}
/* ---------- hands alone (vr.body "hands")
The arm's bones (not the hand's, its fingers' or the weapon's) shrunk to
nothing at a point 3.5 cm back from the wrist along the hand's line,
knuckles to wrist: the cuff closes just behind the hand. Shrunk onto their
own places, the forearm drew the wrist's skin to the elbow and the hand
looked cut off (after HaloCE-Quest-VR, CC0, its vr_hide_forearms and
vr_hand_back_axis). */
void vr_hide_arm(
struct vr_skeleton const *skeleton,
real_matrix4x3 *matrices,
short const bones[NUMBER_OF_VR_ARM_BONES],
short const fingers[5][3],
short gun)
{
real_point3d centre = matrices[bones[_vr_arm_hand]].position;
real_point3d knuckles;
real_vector3d back;
boolean along = FALSE;
short finger, node_index, found = 0;
knuckles.x = knuckles.y = knuckles.z = 0.0f;
/* (the four fingers' bases: the thumb's sits off the line) */
for (finger = 1; finger < 5; finger++)
{
if (fingers[finger][0] == NONE)
continue;
knuckles.x += matrices[fingers[finger][0]].position.x;
knuckles.y += matrices[fingers[finger][0]].position.y;
knuckles.z += matrices[fingers[finger][0]].position.z;
found++;
}
if (found)
{
knuckles.x /= found;
knuckles.y /= found;
knuckles.z /= found;
vr_point_minus(&centre, &knuckles, &back);
along = vr_unit_vector(&back);
}
if (!along)
{
vr_point_minus(&matrices[bones[_vr_arm_fore]].position, &centre, &back);
along = vr_unit_vector(&back);
}
if (along)
{
centre.x += back.i * 0.035f * VR_UNITS_PER_METRE;
centre.y += back.j * 0.035f * VR_UNITS_PER_METRE;
centre.z += back.k * 0.035f * VR_UNITS_PER_METRE;
}
for (node_index = 0; node_index < skeleton->node_count; node_index++)
{
if (!vr_skeleton_under(skeleton, node_index, bones[_vr_arm_upper]) ||
vr_skeleton_under(skeleton, node_index, bones[_vr_arm_hand]) ||
(gun != NONE && vr_skeleton_under(skeleton, node_index, gun)))
{
continue;
}
matrices[node_index].scale = 0.0f;
matrices[node_index].position = centre;
}
}
#endif
-83
View File
@@ -1,83 +0,0 @@
/*
VR_ARMS.H
The VR arms' bones (vr_arms.c), for the first-person hands
(interface/first_person_weapons.c): the arms bent from the shoulders to the
hands. Include after the game's math and animation headers.
*/
#ifndef __VR_ARMS_H
#define __VR_ARMS_H
#ifdef HALO_ANDROID
enum
{
_vr_arm_upper,
_vr_arm_fore,
_vr_arm_hand,
NUMBER_OF_VR_ARM_BONES
};
/* a skeleton's parents, and the nodes under each node (it among them), from
its animation graph: parents come before their children */
struct vr_skeleton
{
long graph_index;
short node_count;
short parents[MAXIMUM_NODES_PER_ANIMATION];
unsigned long long under[MAXIMUM_NODES_PER_ANIMATION];
};
/* an arm's last frame, for its elbow's swing and its forearm's twist to run
on smoothly (one each arm) */
struct vr_limb_history
{
long graph_index;
double time;
real_point3d shoulder;
real_vector3d bend;
boolean valid;
boolean roll_valid;
real roll;
};
boolean vr_skeleton_build(struct vr_skeleton *skeleton, long graph_index, short node_count);
boolean vr_skeleton_under(struct vr_skeleton const *skeleton, short node_index, short root);
/* one arm (bones: upper arm, forearm, hand) bent from the shoulder for the
hand to reach target, its elbow towards the pole; the hand (and what it
holds) stays where it is, or turns with the forearm */
void vr_solve_arm(struct vr_skeleton const *skeleton, real_matrix4x3 *matrices,
short const bones[NUMBER_OF_VR_ARM_BONES], real_point3d shoulder, real_point3d const *target,
real_vector3d const *pole, boolean hand_stays, struct vr_limb_history *history);
/* the hand's roll from the animation (old_fore, old_hand: the arm as
animated) shared by the forearm and the upper arm */
void vr_align_elbow(real_matrix4x3 *matrices, short const bones[NUMBER_OF_VR_ARM_BONES],
real_matrix4x3 const *old_upper, real_matrix4x3 const *old_fore, real_matrix4x3 const *old_hand);
void vr_distribute_arm_twist(real_matrix4x3 *matrices, short const bones[NUMBER_OF_VR_ARM_BONES],
real_matrix4x3 const *old_fore, real_matrix4x3 const *old_hand, struct vr_limb_history *history);
/* a hand's fingers' joints (low, middle, tip; NONE where not found), by
their names on that side ("l ", "r "): the thumb, index, middle, ring and
little finger */
void vr_finger_nodes(struct vr_skeleton const *skeleton, short hand, char const *side, short joints[5][3]);
/* the fingers curled (halo_vr_finger_curls: the thumb, index, middle, the
last two) in the palm's frame: forward along the fingers, up the knuckles
towards the index, palm out of the palm */
void vr_pose_fingers(struct vr_skeleton const *skeleton, real_matrix4x3 *matrices, short hand,
short const joints[5][3], float const curls[4], real_vector3d const *palm, real_vector3d const *forward,
real_vector3d const *up);
/* a finger's middle and tip turned this much (0..1) into line with its low
joint */
void vr_straighten_finger(struct vr_skeleton const *skeleton, real_matrix4x3 *matrices, short const joints[3],
real amount);
/* the arm's bones shrunk to nothing just behind the wrist, the hand (with
its fingers) and the weapon left: the hand alone */
void vr_hide_arm(struct vr_skeleton const *skeleton, real_matrix4x3 *matrices,
short const bones[NUMBER_OF_VR_ARM_BONES], short const fingers[5][3], short gun);
#endif
#endif
-183
View File
@@ -1,183 +0,0 @@
/*
VR_GRAPHICS.C
The VR graphics presets (vr.graphics, the VR options' "Graphics" row): which
of the game's costlier effects are drawn, for headsets with little time to
spare (the Steam Frame spends most of its frame in its GL driver: fewer
draws help it most; the Quest 3 holds 90 Hz with all of them).
- "high", the default: everything, as before;
- "medium": no object shadows, environment reflections or lens flares;
- "low": nor specular light, bump maps, grass and other detail objects,
the screen's fog layer, contrails, weather or camouflage's second pass;
- "max": "high", the models lit for each pixel and the shadows drawn at
512x512 (d3d8_gl.c asks halo_vr_graphics).
Each effect is a switch the game has already: the rasterizer's console
variables (rasterizer_debug_options) and render.c's effect switches, after
HaloCE-Quest-VR's vr_graphics.c (CC0). Scripts and the console set some of
them (render_effects turns particles off in some cutscenes): a switch is
drawn only while both the game and the preset want it. A change the game
made since the last frame is taken as its wish, kept and combined with the
preset's. Playing in another's game, a client draws grass, lens flares and
the screen's fog as the host's rules have it (cheats.c), whatever the
preset; the reductions it does keep there, cheats.c leaves alone
(vr_graphics_reduces).
What each costs: shadows (each object's map, blur and projection: draws),
specular light and reflections (passes of the level's geometry: draws and
fill), detail objects (many small draws), lens flares (their visibility
tests), contrails and weather (particles), bump maps, the fog layer and
camouflage's pass (the GPU's fill).
*/
#ifdef HALO_ANDROID
#include "cseries.h"
#include "rasterizer/rasterizer_console_vars.h"
#include "networking/network_game_globals.h"
#include "halo_vr.h"
/* source/render/render.c, render_objects.c; source/effects/
weather_particle_systems.c */
extern boolean render_shadows;
extern boolean render_contrails_enabled;
extern boolean render_weather_particle_systems_enabled;
extern boolean weather;
void platform_log(char const *format, ...);
/* an effect: whether each preset draws it (low, medium; high and max draw
everything), whether a client in another's game draws it anyway, and the
game's switches it governs */
struct vr_graphics_effect
{
char const *name;
boolean preset[2];
boolean host_rules;
boolean *switches[2];
};
static struct vr_graphics_effect vr_graphics_effects[] =
{
{ "shadows", { FALSE, FALSE }, FALSE,
{ &render_shadows, &rasterizer_debug_options.draw_environment_shadows } },
{ "specular", { FALSE, TRUE }, FALSE,
{ &rasterizer_debug_options.draw_environment_specular_lights,
&rasterizer_debug_options.draw_environment_specular_lightmaps } },
{ "reflections", { FALSE, FALSE }, FALSE,
{ &rasterizer_debug_options.draw_environment_reflections, NULL } },
{ "bump maps", { FALSE, TRUE }, FALSE,
{ &rasterizer_debug_options.bump_mapping_enabled, NULL } },
{ "detail objects", { FALSE, TRUE }, TRUE,
{ &rasterizer_debug_options.draw_detail_objects, NULL } },
{ "lens flares", { FALSE, FALSE }, TRUE,
{ &rasterizer_debug_options.draw_lens_flares, NULL } },
{ "screen fog", { FALSE, TRUE }, TRUE,
{ &rasterizer_debug_options.draw_environment_fog_screen, NULL } },
{ "contrails", { FALSE, TRUE }, FALSE,
{ &render_contrails_enabled, NULL } },
{ "weather", { FALSE, TRUE }, FALSE,
{ &weather, &render_weather_particle_systems_enabled } },
{ "camouflage pass", { FALSE, TRUE }, FALSE,
{ &rasterizer_debug_options.active_camouflage_multipass_enabled, NULL } },
};
#define NUMBER_OF_VR_GRAPHICS_EFFECTS (sizeof(vr_graphics_effects) / sizeof(vr_graphics_effects[0]))
#define VR_GRAPHICS_SWITCHES (sizeof(vr_graphics_effects[0].switches) / sizeof(vr_graphics_effects[0].switches[0]))
static struct
{
boolean started;
int preset;
boolean client;
/* whether the preset allows each effect; what the game wants of each
switch, and what was written last frame */
boolean allowed[NUMBER_OF_VR_GRAPHICS_EFFECTS];
boolean wanted[NUMBER_OF_VR_GRAPHICS_EFFECTS][VR_GRAPHICS_SWITCHES];
boolean written[NUMBER_OF_VR_GRAPHICS_EFFECTS][VR_GRAPHICS_SWITCHES];
} vr_graphics;
static void vr_graphics_choose(
int preset,
boolean client)
{
static char const *const names[] = { "low", "medium", "high", "max" };
unsigned int index;
vr_graphics.preset = preset;
vr_graphics.client = client;
for (index = 0; index < NUMBER_OF_VR_GRAPHICS_EFFECTS; index++)
{
struct vr_graphics_effect const *effect = &vr_graphics_effects[index];
vr_graphics.allowed[index] = preset >= HALO_VR_GRAPHICS_HIGH || effect->preset[preset] ||
(client && effect->host_rules);
}
platform_log("VR: graphics %s%s", names[preset], client ? " (in another's game)" : "");
}
/* each frame, before it is drawn (render.c) */
void vr_graphics_update(
void)
{
int preset = halo_vr_graphics();
boolean client = network_game_distributed_client();
unsigned int index, which;
if (!vr_graphics.started)
{
/* the game's switches as it starts are its wishes */
for (index = 0; index < NUMBER_OF_VR_GRAPHICS_EFFECTS; index++)
{
for (which = 0; which < VR_GRAPHICS_SWITCHES && vr_graphics_effects[index].switches[which]; which++)
{
vr_graphics.wanted[index][which] = *vr_graphics_effects[index].switches[which];
vr_graphics.written[index][which] = *vr_graphics_effects[index].switches[which];
}
}
vr_graphics.started = TRUE;
vr_graphics_choose(preset, client);
}
else if (preset != vr_graphics.preset || client != vr_graphics.client)
{
vr_graphics_choose(preset, client);
}
for (index = 0; index < NUMBER_OF_VR_GRAPHICS_EFFECTS; index++)
{
for (which = 0; which < VR_GRAPHICS_SWITCHES && vr_graphics_effects[index].switches[which]; which++)
{
boolean *game_switch = vr_graphics_effects[index].switches[which];
boolean value;
/* (the game, a script, the console changed it since: its wish) */
if (*game_switch != vr_graphics.written[index][which])
vr_graphics.wanted[index][which] = *game_switch;
value = vr_graphics.wanted[index][which] && vr_graphics.allowed[index];
*game_switch = value;
vr_graphics.written[index][which] = value;
}
}
}
/* whether the preset keeps this switch of the game's off now (cheats.c, a
client's rules: those it may keep off) */
boolean vr_graphics_reduces(
boolean const *game_switch)
{
unsigned int index, which;
if (!vr_graphics.started)
return FALSE;
for (index = 0; index < NUMBER_OF_VR_GRAPHICS_EFFECTS; index++)
{
for (which = 0; which < VR_GRAPHICS_SWITCHES && vr_graphics_effects[index].switches[which]; which++)
{
if (vr_graphics_effects[index].switches[which] == game_switch)
return !vr_graphics.allowed[index];
}
}
return FALSE;
}
#endif
@@ -41,17 +41,8 @@ the display's shape, or 640 (port/linux/src/d3d8_gl.c) */
long halo_screen_width(void);
/* takes up a new width between frames (F11); returns the width */
long halo_screen_commit(void);
/* the shadow maps' pixels for each of their 128 texels each way, a power of
two (display.shadow_resolution) */
long halo_shadow_map_scale(void);
/* while TRUE, drawing shifts right to center 640-column layouts */
void halo_screen_ui_offset(unsigned char centered);
/* names a model lighting vertex program, whose draws can be lit for each
pixel (port/linux/src/d3d8_gl.c, display.per_pixel_lighting) */
void halo_vertex_shader_lighting(unsigned long handle);
/* display.anti_aliasing's pass over a window's 3D view, before the HUD and
menus (source/render/render.c): the window's bounds on the screen */
void halo_screen_anti_alias(short x0, short y0, short x1, short y1);
/* the mouse in the menus (source/interface/ui_widget.c) */
#include "halo_ui_pointer.h"
+1 -1
View File
@@ -66,7 +66,7 @@ is newer (network_client_manager.c). A host advertises it, with its netcode,
in its game's advertisement's reserved bytes (network_server_message_handler.c),
which hosts built before there was a version send as zeros: version 0.
Raise it with any change to what the machines send each other. */
#define HALO_PORT_NETWORK_VERSION 20
#define HALO_PORT_NETWORK_VERSION 16
/* ... the advertisement's reserved bytes: the version (a little-endian word),
then flags */
#define HALO_PORT_ADVERTISED_VERSION_OFFSET 0
+20 -135
View File
@@ -88,14 +88,13 @@ degrees on an axis (the other axis 0); nonzero if it has. The game counts
it as looking around (player_control.c's action tests). */
int halo_vr_look_delta(float *yaw, float *pitch);
/* The weapon in the right hand, or the left one (vr.handedness; vr.aim_with
= "controller"; each returns 0, and the game does as before, when the hand
is not tracked):
/* The weapon in the right hand (vr.aim_with = "controller"; each returns 0,
and the game does as before, when the hand is not tracked):
- the direction the player's weapon aims, where the controller points (the
body and the view keep facing where the head looks);
- where its shots start, as an offset from the unit's camera (world units);
- the camera the first-person weapon model is posed from, so that it lies
in the hand (in model space, below);
in the hand;
- the aiming laser's start and direction; */
int halo_vr_aim_vector(short local_player_index, float vector[3]);
int halo_vr_weapon_origin_offset(short local_player_index, float offset[3]);
@@ -105,78 +104,35 @@ int halo_vr_weapon_origin_offset(short local_player_index, float offset[3]);
int halo_vr_weapon_origin(short local_player_index, float origin[3]);
int halo_vr_weapon_camera(short local_player_index, float position[3], float forward[3], float up[3]);
int halo_vr_laser(short local_player_index, float start[3], float direction[3]);
/* Model space: the first-person model (first_person_weapons.c) is always
posed as the right hand's. Left-handed, it is posed in a mirror image of
the room and drawn mirrored back (its triangles' winding turned over); the
mirror: a point on it (world units) and its normal, or 0 when it is not
mirrored. What first_person_weapons.c and vr.c exchange about the model
is in model space: the weapon camera, the free hand, the foregrip, the
fingers (0 the model's left hand, the free one), the barrel and the laser
along it. */
int halo_vr_first_person_mirror(short local_player_index, float point[3], float normal[3]);
/* the laser for this frame, along the barrel (first_person_weapons.c: while
an animation turns the weapon, as in a reload; model space), and the laser
at the ready, where the controller aims (in the room) */
an animation turns the weapon, as in a reload), and the laser at the ready,
where the controller aims */
void halo_vr_set_laser(short local_player_index, const float start[3], const float direction[3]);
int halo_vr_ready_laser(short local_player_index, float start[3], float direction[3]);
/* The arms (interface/first_person_weapons.c):
- the body: the eyes' position (world units), the neck they turn about,
and the torso's flat heading (which follows the head beyond a few
degrees, and the hands; the stick's turns turn it at once), from which
the shoulders hang;
- the free hand's pose (the left one, left-handed the right; model space);
- the free hand on the weapon: given where the model's left hand holds it
- the body: the head's position (world units) and flat heading, from
which the shoulders hang;
- the left hand's pose;
- the left hand on the weapon: given where the model's left hand holds it
(world units) and the line from its right hand to its left in the
weapon's forward, right and up, whether the free hand grips it; then the
weapon's forward, right and up, whether the left hand grips it; then the
weapon aims along both hands; */
int halo_vr_body_frame(short local_player_index, float head[3], float neck[3], float forward[3]);
int halo_vr_free_hand(short local_player_index, float position[3], float forward[3], float up[3]);
int halo_vr_body_frame(short local_player_index, float head[3], float forward[3]);
int halo_vr_left_hand(short local_player_index, float position[3], float forward[3], float up[3]);
int halo_vr_two_hand(short local_player_index, const float foregrip[3], const float reference[3]);
/* - the fingers of a hand of the model (0 the left, the free hand; 1 the
right, the weapon's), from the controller's trigger, grip and touch: the thumb, index, middle finger and the last two, each
curled 0 (straight) to 1; 0 when the hand is not tracked or vr.fingers
is off, else HALO_VR_FINGERS_CURLED, and HALO_VR_FINGERS_TOUCH too when
the controller tells where the fingers rest (else a finger off the
trigger reads as one on it, unpressed); */
enum
{
HALO_VR_FINGERS_CURLED = 1,
HALO_VR_FINGERS_TOUCH = 2,
};
int halo_vr_finger_curls(short local_player_index, int hand, float curls[4]);
/* - whether the free hand's arm bends at the elbow to reach it from the
shoulder (vr.arm_ik); else it keeps the shape it has on the foregrip,
pointed at the shoulder;
- the time of the headset's frame (seconds, steady through the frame), by
which the arms' bending is eased */
int halo_vr_free_arm_bends(void);
double halo_vr_pose_time(void);
/* - what of the body is drawn (vr.body): the arms and hands, the hands
alone, or neither (the weapon alone) */
enum
{
HALO_VR_BODY_ARMS = 0,
HALO_VR_BODY_HANDS,
HALO_VR_BODY_OFF,
};
int halo_vr_body_mode(void);
/* a hand has just been thrust forward: a melee attack (xinput_sdl.c) */
int halo_vr_melee(void);
/* a hand has just tapped the head (vr.flashlight_gesture): the flashlight's
button, for a moment (xinput_sdl.c) */
int halo_vr_flashlight_tap(void);
/* the weight of the melee attack under way, from the swing's speed (0..1;
0 with none): the game scales melee damage by the player's run instead,
and in VR a player punches standing (units.c) */
float halo_vr_melee_strength(void);
/* The grenade (units.c): whether the free hand's trigger holds it in the
hand; and
/* The grenade (units.c): whether the left trigger holds it in the hand; and
as it let go, whether the hand threw it: from where (world units), which
way (the game's axes) and how hard (0..1 of the game's throw) */
int halo_vr_grenade_held(short local_player_index);
int halo_vr_grenade_throw(short local_player_index, float origin[3], float direction[3], float *strength);
/* where the held grenade is drawn (first_person_weapons.c): in the free
hand's palm, and at what size */
/* where the held grenade is drawn (render_objects.c): in the left palm, and
at what size */
int halo_vr_grenade_in_hand(short local_player_index, float position[3], float forward[3], float up[3],
float *scale);
/* The scope (render.c): zoomed in by magnification, the camera for the
@@ -207,9 +163,9 @@ void halo_vr_capture_names(void);
void halo_vr_set_scope_raise(float metres);
void halo_vr_capture_scope(float left, float top, float right, float bottom);
/* where the weapon's gun node is as drawn, at the ready (world units;
first_person_weapons.c, each frame; model space): shots, the crosshair and
the laser start there; and the direction its barrel points in the weapon
camera's frame (forward, left, up), which the camera is turned to aim */
first_person_weapons.c, each frame): shots, the crosshair and the laser
start there; and the direction its barrel points in the weapon camera's
frame (forward, left, up), which the camera is turned to aim */
void halo_vr_set_barrel(short local_player_index, const float world[3], const float direction[3]);
/* how far, in world units, the weapon's aim runs before it hits something
(render.c, each frame); the crosshair floats there */
@@ -225,8 +181,7 @@ void halo_vr_capture_crosshair(void);
/* Vehicles (player_control.c, each tick): the seat the player sits in and
its vehicle's heading (yaw, radians). Seated, the body turns with the
vehicle; a driver who steers by facing (HALO_VR_SEAT_STEER) steers with the
turning stick, else the weapon hand's controller aims (HALO_VR_SEAT_AIM).
And each
right stick, else the right controller aims (HALO_VR_SEAT_AIM). And each
frame (render.c) the heading as drawn, between ticks. */
enum
{
@@ -252,59 +207,6 @@ 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);
/* the hand that holds the weapon (HALO_XR_HAND_*: 1 the right, 0 the left,
vr.handedness) */
int halo_vr_weapon_hand(void);
/* The game's actions on the controllers' buttons (vr.button_*; the VR
options' BUTTONS page): each action on one of seven buttons, named as
right-handed (A and B on the right controller, X and Y, the Quest's, on
the left; the move stick's click, the turning stick's, the weapon hand's
grip); left-handed with vr.mirror_controls, their mirror images. The
actions are numbered by the button each has by default. The triggers, the
free hand's grip, the menu button and the Steam Frame's bumpers, D-pad and
View button keep theirs; the menus keep the buttons as they are. */
enum
{
HALO_VR_INPUT_A = 0,
HALO_VR_INPUT_B,
HALO_VR_INPUT_X,
HALO_VR_INPUT_Y,
HALO_VR_INPUT_MOVE_STICK,
HALO_VR_INPUT_TURN_STICK,
HALO_VR_INPUT_GRIP,
HALO_VR_INPUT_COUNT,
HALO_VR_ACTION_JUMP = HALO_VR_INPUT_A,
HALO_VR_ACTION_LIGHT = HALO_VR_INPUT_B, /* a tap the flashlight; held, switch grenades */
HALO_VR_ACTION_ACTION = HALO_VR_INPUT_X, /* action, reload */
HALO_VR_ACTION_WEAPON = HALO_VR_INPUT_Y, /* switch weapon */
HALO_VR_ACTION_CROUCH = HALO_VR_INPUT_MOVE_STICK,
HALO_VR_ACTION_ZOOM = HALO_VR_INPUT_TURN_STICK,
HALO_VR_ACTION_MELEE = HALO_VR_INPUT_GRIP,
HALO_VR_ACTION_COUNT = HALO_VR_INPUT_COUNT,
};
/* an action's setting ("vr.button_jump"), a button's value in it ("a"),
and the button an action is on, as set */
const char *halo_vr_action_setting(int action);
const char *halo_vr_input_value(int input);
int halo_vr_action_input(int action);
/* the graphics preset (vr.graphics; HALO_VR_GRAPHICS_HIGH outside VR): which
effects are drawn (port/linux/game/vr_graphics.c), and with "max" the
models lit for each pixel and larger shadows (d3d8_gl.c) */
enum
{
HALO_VR_GRAPHICS_LOW = 0,
HALO_VR_GRAPHICS_MEDIUM,
HALO_VR_GRAPHICS_HIGH,
HALO_VR_GRAPHICS_MAX,
};
int halo_vr_graphics(void);
/* the VR options: the refresh rates the headset offers (Hz, at most maximum;
0 when it lets none be chosen, as SteamVR), and the one it runs at (0:
unknown; vr.refresh_rate asks for one); and vr.resolution_scale as the app
started (it takes a restart) */
int halo_vr_refresh_rates(float *rates, int maximum, float *current);
float halo_vr_started_resolution_scale(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
@@ -334,7 +236,7 @@ coming (each returns 0 otherwise, and for this machine's own players, and
the game does as before):
- where the weapon aims, on foot;
- where its shots start (world units), from the unit's camera;
- whether the free hand holds the grenade;
- whether the left hand holds the grenade;
- the hand's throw, once, as the grenade goes (as halo_vr_grenade_throw);
and this machine's own VR player's throw, to send (units.c). */
int halo_vr_network_aim(long player_index, float vector[3]);
@@ -360,23 +262,6 @@ enum
HALO_VR_PHASE_COUNT
};
void halo_vr_phase(int phase, int ticks);
/* vr.log_performance: time taken by a part of the drawing, for the report
(first_person_weapons.c): its start (0 when not logged), and its end. The
first-person pose (its animation and the arms), the arms alone, and the
pose taken as built already this frame (counted) */
enum
{
HALO_VR_TIMING_FIRST_PERSON,
HALO_VR_TIMING_ARMS,
HALO_VR_TIMING_FIRST_PERSON_KEPT,
HALO_VR_TIMING_COUNT
};
unsigned long long halo_vr_timing_start(void);
void halo_vr_timing_end(int part, unsigned long long start);
/* the headset's frames, counted (0 before the first): what is drawn the same
for every eye, as the first-person pose posed from the controller, is built
once a frame */
unsigned long halo_vr_frame_serial(void);
void halo_vr_present(unsigned int back_buffer_texture);
/* the eye image size: the back buffer's size in pixels in VR */
int halo_vr_eye_size(int *width, int *height);
+190 -814
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-26
View File
@@ -86,10 +86,6 @@ this list to generate the guest's entry points */
X(glCheckFramebufferStatus) \
X(glBlitFramebuffer) \
X(glDrawBuffers) \
X(glGenRenderbuffers) \
X(glBindRenderbuffer) \
X(glRenderbufferStorageMultisample) \
X(glFramebufferRenderbuffer) \
X(glReadBuffer) \
X(glInvalidateFramebuffer) \
X(glGenBuffers) \
@@ -199,10 +195,6 @@ this list to generate the guest's entry points */
X(glCheckFramebufferStatus) \
X(glBlitFramebuffer) \
X(glDrawBuffers) \
X(glGenRenderbuffers) \
X(glBindRenderbuffer) \
X(glRenderbufferStorageMultisample) \
X(glFramebufferRenderbuffer) \
X(glGenBuffers) \
X(glDeleteBuffers) \
X(glBindBuffer) \
@@ -217,11 +209,6 @@ this list to generate the guest's entry points */
X(glDisableVertexAttribArray) \
X(glVertexAttribPointer) \
X(glVertexAttribIPointer) \
X(glVertexAttribFormat) \
X(glVertexAttribIFormat) \
X(glVertexAttribBinding) \
X(glBindVertexBuffer) \
X(glGetQueryBufferObjectuiv) \
X(glVertexAttrib4fv) \
X(glVertexAttribI4ui) \
X(glDrawArrays) \
@@ -319,10 +306,6 @@ pointers, sees the declarations without these aliases */
#define glCheckFramebufferStatus halo_glCheckFramebufferStatus
#define glBlitFramebuffer halo_glBlitFramebuffer
#define glDrawBuffers halo_glDrawBuffers
#define glGenRenderbuffers halo_glGenRenderbuffers
#define glBindRenderbuffer halo_glBindRenderbuffer
#define glRenderbufferStorageMultisample halo_glRenderbufferStorageMultisample
#define glFramebufferRenderbuffer halo_glFramebufferRenderbuffer
#define glReadBuffer halo_glReadBuffer
#define glInvalidateFramebuffer halo_glInvalidateFramebuffer
#define glGenBuffers halo_glGenBuffers
@@ -430,10 +413,6 @@ pointers, sees the declarations without these aliases */
#define glCheckFramebufferStatus halo_glCheckFramebufferStatus
#define glBlitFramebuffer halo_glBlitFramebuffer
#define glDrawBuffers halo_glDrawBuffers
#define glGenRenderbuffers halo_glGenRenderbuffers
#define glBindRenderbuffer halo_glBindRenderbuffer
#define glRenderbufferStorageMultisample halo_glRenderbufferStorageMultisample
#define glFramebufferRenderbuffer halo_glFramebufferRenderbuffer
#define glGenBuffers halo_glGenBuffers
#define glDeleteBuffers halo_glDeleteBuffers
#define glBindBuffer halo_glBindBuffer
@@ -448,11 +427,6 @@ pointers, sees the declarations without these aliases */
#define glDisableVertexAttribArray halo_glDisableVertexAttribArray
#define glVertexAttribPointer halo_glVertexAttribPointer
#define glVertexAttribIPointer halo_glVertexAttribIPointer
#define glVertexAttribFormat halo_glVertexAttribFormat
#define glVertexAttribIFormat halo_glVertexAttribIFormat
#define glVertexAttribBinding halo_glVertexAttribBinding
#define glBindVertexBuffer halo_glBindVertexBuffer
#define glGetQueryBufferObjectuiv halo_glGetQueryBufferObjectuiv
#define glVertexAttrib4fv halo_glVertexAttrib4fv
#define glVertexAttribI4ui halo_glVertexAttribI4ui
#define glDrawArrays halo_glDrawArrays
+1 -3
View File
@@ -125,9 +125,7 @@ HALO_COMMON(hs_global_data, 4); /* struct data_array *hs_global_data */
HALO_COMMON(hs_thread_data, 4); /* struct data_array *hs_thread_data */
HALO_COMMON(hud_msg_def, 4); /* struct hud_messaging_parameters_definition *hud_msg_def */
HALO_COMMON(interrupt_result, 24); /* HRESULT interrupt_result */
/* object_lights.c owns light_cluster_partition. On COFF a weak fallback
can override its tentative COMMON definition; let the owner allocate the
native struct cluster_partition, including its reference-cache fields. */
HALO_COMMON(light_cluster_partition, 12); /* struct cluster_partition light_cluster_partition */
HALO_COMMON(light_data, 4); /* struct data_array *light_data */
HALO_COMMON(local_player_index_for_draw_string_and_hack_in_icons, 4); /* short local_player_index_for_draw_string_and_hack_in_icons */
HALO_COMMON(looping_sound_data, 4); /* struct data_array *looping_sound_data */
-75
View File
@@ -518,59 +518,6 @@ static const char *comparison_operator(unsigned long function)
}
}
/* ---------- per-pixel model lighting
The game lights its models for each vertex: its model lighting programs
(nv2a_vsh.c nv2a_vertex_shader_lighting) sum the ambient light, two distant
lights and two point lights into the diffuse color v0. Halo's models have
few vertices, so the light across a curved surface comes out in facets, and
a point light that passes close lights only the vertices it reaches.
model_lighting computes the same sum for each pixel, from the normal and
world position the vertex program hands on and the same constants
(XGPU_MODEL_LIGHT_COUNT): the same falloff, facing, cone and translucency
terms, clamped where the programs clamp them. The diffuse alpha, which the
programs write apart (0, or planar fog's), is kept. */
static void model_lighting(struct xgpu_text *text, BOOL point_lights)
{
int light;
xgpu_text_append(text,
"uniform vec4 model_lights[%d];\n"
"in vec4 xWorldNormal;\n"
"%s"
"vec3 model_lighting()\n"
"{\n"
/* the normal's direction between the vertices, at the length the
vertices' normals have, as the programs light by it unnormalized
(shorter than 1 on some models: the first-person weapons) */
"\tvec3 n = xWorldNormal.xyz * (xWorldNormal.w * inversesqrt(max(dot(xWorldNormal.xyz, xWorldNormal.xyz), 1.0e-24)));\n"
/* the ambient light, the first distant light, which also lights the
back by the translucency, and the second */
"\tfloat facing = dot(n, -model_lights[7].xyz);\n"
"\tvec3 light = model_lights[11].xyz + max(max(facing, -facing * model_lights[0].z), 0.0) * model_lights[8].xyz +\n"
"\t\tmax(dot(n, -model_lights[9].xyz), 0.0) * model_lights[10].xyz;\n",
XGPU_MODEL_LIGHT_COUNT, point_lights ? "in vec3 xWorldPosition;\n" : "");
/* each point light: its position and 1 / radius squared, its cone's axis
and falloff scale, its color and falloff offset */
for (light = 0; point_lights && light < 2; light++)
{
int first = 1 + light * 3;
xgpu_text_append(text,
"\t{\n"
"\t\tvec3 to_light = model_lights[%d].xyz - xWorldPosition;\n"
"\t\tfloat distance_squared = dot(to_light, to_light);\n"
"\t\tvec3 l = to_light * inversesqrt(max(distance_squared, 1.0e-12));\n"
"\t\tlight += max(1.0 - distance_squared * model_lights[%d].w, 0.0) * clamp(dot(l, n), 0.0, 1.0) *\n"
"\t\t\tclamp(dot(l, -model_lights[%d].xyz) * model_lights[%d].w + model_lights[%d].w, 0.0, 1.0) *\n"
"\t\t\tmodel_lights[%d].xyz;\n"
"\t}\n",
first, first, first + 1, first + 1, first + 2, first + 2);
}
xgpu_text_append(text, "\treturn clamp(light, 0.0, 1.0);\n}\n");
}
char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key)
{
const DWORD *state = key->combiner_state;
@@ -609,8 +556,6 @@ char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key)
XGPU_PIXEL_UNIFORMS);
for (stage = 0; stage < 4; stage++)
xgpu_text_append(&text, "uniform %s tex%d;\n", sampler_declaration(key->sampler_type[stage]), stage);
if (key->per_pixel_lighting)
model_lighting(&text, key->per_pixel_lighting == 2);
xgpu_text_append(&text,
"float signed_byte(float x)\n"
"{\n"
@@ -627,8 +572,6 @@ char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key)
"\tvec4 v1 = xD1;\n"
"\tvec4 t0 = vec4(0.0), t1 = vec4(0.0), t2 = vec4(0.0), t3 = vec4(0.0);\n"
"\tfloat dot0 = 0.0, dot1 = 0.0, dot2 = 0.0, dot3 = 0.0;\n");
if (key->per_pixel_lighting)
xgpu_text_append(&text, "\tv0.rgb = model_lighting();\n");
for (stage = 0; stage < 4; stage++)
texture_stage(&text, key, stage);
@@ -702,27 +645,9 @@ char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key)
const char *comparison = comparison_operator(key->alpha_test_function);
if (!comparison)
{
xgpu_text_append(&text, "\tdiscard;\n");
}
else if (*comparison && key->alpha_test_samples && comparison[0] != '=' && comparison[0] != '!')
{
/* multisampled: the samples covered as alpha passes the reference
over the pixel (its change across the pixel from fwidth), the rest
left as they are; alpha itself is kept, the game keeping values of
its own in destination alpha */
xgpu_text_append(&text,
"\tfloat test_alpha = clamp(result.a, 0.0, 1.0) * 255.0;\n"
"\tfloat test_coverage = clamp(%s(test_alpha - alpha_reference) / max(fwidth(test_alpha), 1.0) + 0.5, 0.0, 1.0);\n"
"\tint test_samples = int(test_coverage * %d.0 + 0.5);\n"
"\tif (test_samples == 0) discard;\n"
"\tgl_SampleMask[0] = (1 << test_samples) - 1;\n",
comparison[0] == '<' ? "-" : "", (int)key->alpha_test_samples);
}
else if (*comparison)
{
xgpu_text_append(&text, "\tif (!(floor(clamp(result.a, 0.0, 1.0) * 255.0 + 0.5) %s alpha_reference)) discard;\n", comparison);
}
}
if (*config_string("debug.gpu_debug_expression"))
xgpu_text_append(&text, "\tresult = vec4(vec3(%s), 1.0);\n", config_string("debug.gpu_debug_expression"));
+35 -237
View File
@@ -7,7 +7,7 @@ GLSL.
Each instruction is four little-endian words. Word 1 to 3 hold a MAC
(vector) operation and an ILU (scalar) operation that execute in parallel
on the same three operands A, B and C, and the destinations of both; the
bit positions of every field are spelled out in operand_fields() and in
bit positions of every field are spelled out in operand() and in
nv2a_vertex_shader_to_glsl(). When both units run, the ILU result goes to
temporary r1, whatever the instruction's temporary register field says.
@@ -76,55 +76,43 @@ static void write_mask(unsigned long mask, char *out)
out[count] = 0;
}
struct operand_fields
{
unsigned long negate, swizzle[4], index, mux;
};
static void operand_fields(const DWORD *instruction, char which, struct operand_fields *fields)
{
switch (which)
{
case 'A':
fields->negate = field(instruction, 1, 8, 1);
fields->swizzle[0] = field(instruction, 1, 6, 2);
fields->swizzle[1] = field(instruction, 1, 4, 2);
fields->swizzle[2] = field(instruction, 1, 2, 2);
fields->swizzle[3] = field(instruction, 1, 0, 2);
fields->index = field(instruction, 2, 28, 4);
fields->mux = field(instruction, 2, 26, 2);
break;
case 'B':
fields->negate = field(instruction, 2, 25, 1);
fields->swizzle[0] = field(instruction, 2, 23, 2);
fields->swizzle[1] = field(instruction, 2, 21, 2);
fields->swizzle[2] = field(instruction, 2, 19, 2);
fields->swizzle[3] = field(instruction, 2, 17, 2);
fields->index = field(instruction, 2, 13, 4);
fields->mux = field(instruction, 2, 11, 2);
break;
default:
fields->negate = field(instruction, 2, 10, 1);
fields->swizzle[0] = field(instruction, 2, 8, 2);
fields->swizzle[1] = field(instruction, 2, 6, 2);
fields->swizzle[2] = field(instruction, 2, 4, 2);
fields->swizzle[3] = field(instruction, 2, 2, 2);
fields->index = (field(instruction, 2, 0, 2) << 2) | field(instruction, 3, 30, 2);
fields->mux = field(instruction, 3, 28, 2);
break;
}
}
static void operand(struct xgpu_text *text, const DWORD *instruction, char which, int relative)
{
static const char swizzle_names[] = "xyzw";
struct operand_fields fields;
unsigned long *swizzle = fields.swizzle, index, mux;
unsigned long negate, swizzle[4], index, mux;
operand_fields(instruction, which, &fields);
index = fields.index;
mux = fields.mux;
xgpu_text_append(text, "%s", fields.negate ? "-" : "");
switch (which)
{
case 'A':
negate = field(instruction, 1, 8, 1);
swizzle[0] = field(instruction, 1, 6, 2);
swizzle[1] = field(instruction, 1, 4, 2);
swizzle[2] = field(instruction, 1, 2, 2);
swizzle[3] = field(instruction, 1, 0, 2);
index = field(instruction, 2, 28, 4);
mux = field(instruction, 2, 26, 2);
break;
case 'B':
negate = field(instruction, 2, 25, 1);
swizzle[0] = field(instruction, 2, 23, 2);
swizzle[1] = field(instruction, 2, 21, 2);
swizzle[2] = field(instruction, 2, 19, 2);
swizzle[3] = field(instruction, 2, 17, 2);
index = field(instruction, 2, 13, 4);
mux = field(instruction, 2, 11, 2);
break;
default:
negate = field(instruction, 2, 10, 1);
swizzle[0] = field(instruction, 2, 8, 2);
swizzle[1] = field(instruction, 2, 6, 2);
swizzle[2] = field(instruction, 2, 4, 2);
swizzle[3] = field(instruction, 2, 2, 2);
index = (field(instruction, 2, 0, 2) << 2) | field(instruction, 3, 30, 2);
mux = field(instruction, 3, 28, 2);
break;
}
xgpu_text_append(text, "%s", negate ? "-" : "");
switch (mux)
{
case _mux_temporary:
@@ -151,183 +139,6 @@ static void operand(struct xgpu_text *text, const DWORD *instruction, char which
swizzle_names[swizzle[0]], swizzle_names[swizzle[1]], swizzle_names[swizzle[2]], swizzle_names[swizzle[3]]);
}
/* ---------- model lighting
The game's model lighting programs (rasterizer_xbox_vertex_shaders_data.inc
9, 10, 17 and 27) sum into the diffuse color oD0.xyz the ambient light
c[-69], two distant lights (direction c[-73] and c[-71], color c[-72] and
c[-70]; the first also lights the back by the translucency c[-82].z) and, in
10 and 17, two point lights (c[-79] to c[-77] and c[-76] to c[-74]: position
and 1 / radius squared, the cone's axis and falloff scale, the color and
falloff offset). The normal and world position they light by are
temporaries that the programs reuse (10 and 17 overwrite both after the
sum). In 9, 10 and 17 the normal is the skinned normal turned by c[-84].w
for the back faces' pass, r0 (written by instruction 17) and the position
the skinned position, r10 (by 12); in 27, of a single node, the normal is
r3 (by 5). They are taken as the first instruction that lights by them
reads them: in 9 the normal before instruction 19, in 10 and 17 the normal
before 24 and the position before 19, in 27 the normal before 7. A program
is taken only where its lighting is the sum nv2a_psh.c computes for each
pixel (the diffuse color written once, by the sum; both distant lights by
one normal; the translucency; each point light by one position, its cone
by its axis reversed) and the registers keep the same normal and position
through it. Nothing else of the programs goes into oD0.xyz; oD0.w is 0, or
in 17 the planar fog's. */
static BOOL xyz_unswizzled(const struct operand_fields *operand)
{
return operand->swizzle[0] == 0 && operand->swizzle[1] == 1 && operand->swizzle[2] == 2;
}
/* the operand is c[reg] (as the game numbers them: -73), its xyz unswizzled */
static BOOL light_constant(const DWORD *instruction, const struct operand_fields *operand, int reg, BOOL negated)
{
return operand->mux == _mux_constant && !field(instruction, 3, 1, 1) &&
field(instruction, 1, 13, 8) == (unsigned long)(XGPU_VERTEX_CONSTANT_BIAS + reg) &&
operand->negate == (negated ? 1UL : 0UL) && xyz_unswizzled(operand);
}
/* the operand is a temporary (not the position output), its xyz unswizzled */
static BOOL light_temporary(const struct operand_fields *operand, BOOL negated)
{
return operand->mux == _mux_temporary && operand->index < 12 &&
operand->negate == (negated ? 1UL : 0UL) && xyz_unswizzled(operand);
}
/* any of x, y and z of temporary reg written by the instructions in [first, last) */
static BOOL temporary_written(const DWORD *instructions, unsigned long first, unsigned long last, unsigned long reg)
{
unsigned long index;
for (index = first; index < last; index++)
{
const DWORD *instruction = instructions + index * 4;
unsigned long mac = field(instruction, 1, 21, 4);
unsigned long ilu = field(instruction, 1, 25, 3);
unsigned long temporary = field(instruction, 3, 20, 4);
if (mac != _mac_nop && mac != _mac_arl && temporary == reg && (field(instruction, 3, 24, 4) & 0xe))
return TRUE;
/* (the ILU writes r1 when both units run) */
if (ilu != _ilu_nop && (mac != _mac_nop ? 1 : temporary) == reg && (field(instruction, 3, 16, 4) & 0xe))
return TRUE;
}
return FALSE;
}
BOOL nv2a_vertex_shader_lighting(const DWORD *instructions, unsigned long instruction_count,
struct nv2a_vertex_lighting *lighting)
{
unsigned long count, index, diffuse;
unsigned long distant[2], distant_register[2] = { 0, 0 }, point[2], point_register[2] = { 0, 0 };
BOOL cone[2] = { FALSE, FALSE }, translucency = FALSE, point_lights = FALSE;
int light;
/* to the instruction that ends the program */
for (count = 0; count < instruction_count;)
{
if (field(instructions + count++ * 4, 3, 0, 1))
break;
}
diffuse = distant[0] = distant[1] = point[0] = point[1] = count;
for (index = 0; index < count; index++)
{
const DWORD *instruction = instructions + index * 4;
unsigned long mac = field(instruction, 1, 21, 4);
unsigned long output_mask = field(instruction, 3, 12, 4);
unsigned long constant = field(instruction, 1, 13, 8);
struct operand_fields a, b, c;
operand_fields(instruction, 'A', &a);
operand_fields(instruction, 'B', &b);
operand_fields(instruction, 'C', &c);
/* the diffuse color's x, y and z, written once and last by the sum:
the second distant light's term times its color, plus the rest */
if ((output_mask & 0xe) && field(instruction, 3, 11, 1) && field(instruction, 3, 3, 8) == 3)
{
if (diffuse != count || (output_mask & 0xe) != 0xe || field(instruction, 3, 2, 1) ||
mac != _mac_mad || !light_constant(instruction, &b, -70, FALSE) || c.mux != _mux_temporary)
{
return FALSE;
}
diffuse = index;
}
for (light = 0; light < 2; light++)
{
/* each distant light's facing: the normal's dot product with the
light's direction reversed */
if (mac == _mac_dp3 && light_constant(instruction, &b, light ? -71 : -73, TRUE))
{
if (distant[light] != count || !light_temporary(&a, FALSE))
return FALSE;
distant[light] = index;
distant_register[light] = a.index;
}
/* each point light's direction, from the world position */
if (mac == _mac_add && light_constant(instruction, &a, light ? -76 : -79, FALSE) &&
light_temporary(&c, TRUE))
{
if (point[light] != count)
return FALSE;
point[light] = index;
point_register[light] = c.index;
}
/* and its cone: the direction's dot product with the light's axis
reversed */
if (mac == _mac_dp3 && b.mux == _mux_constant && !field(instruction, 3, 1, 1) &&
constant == (unsigned long)(XGPU_VERTEX_CONSTANT_BIAS + (light ? -75 : -78)))
{
if (!light_constant(instruction, &b, light ? -75 : -78, TRUE))
return FALSE;
cone[light] = TRUE;
}
}
/* the back lit by the first distant light: its facing reversed, times
the translucency */
if (mac == _mac_mul && a.mux == _mux_temporary && a.negate && b.mux == _mux_constant &&
!field(instruction, 3, 1, 1) && constant == XGPU_VERTEX_CONSTANT_BIAS - 82 && b.swizzle[0] == 2)
{
translucency = TRUE;
}
if ((a.mux == _mux_constant || b.mux == _mux_constant || c.mux == _mux_constant) &&
!field(instruction, 3, 1, 1) && constant >= XGPU_VERTEX_CONSTANT_BIAS - 79 &&
constant <= XGPU_VERTEX_CONSTANT_BIAS - 74)
{
point_lights = TRUE;
}
}
if (diffuse == count || distant[0] >= diffuse || distant[1] >= diffuse ||
distant_register[0] != distant_register[1] || !translucency)
{
return FALSE;
}
lighting->lights = 1;
lighting->normal_register = distant_register[0];
lighting->normal_instruction = distant[0] < distant[1] ? distant[0] : distant[1];
if (temporary_written(instructions, lighting->normal_instruction, diffuse, lighting->normal_register))
return FALSE;
lighting->position_instruction = lighting->position_register = 0;
if (point_lights)
{
unsigned long last = point[0] > point[1] ? point[0] : point[1];
if (point[0] >= diffuse || point[1] >= diffuse || point_register[0] != point_register[1] ||
!cone[0] || !cone[1])
{
return FALSE;
}
lighting->lights = 2;
lighting->position_register = point_register[0];
lighting->position_instruction = point[0] < point[1] ? point[0] : point[1];
if (temporary_written(instructions, lighting->position_instruction, last, lighting->position_register))
return FALSE;
}
return TRUE;
}
/* ---------- translation */
static const char shader_prologue[] =
#ifdef HALO_ANDROID
/* the #version line comes first, from the context's capabilities */
@@ -384,7 +195,7 @@ static const char shader_prologue[] =
"}\n";
char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instruction_count,
unsigned long packed_attribute_mask, const struct nv2a_vertex_lighting *lighting, int multiview)
unsigned long packed_attribute_mask, int multiview)
{
struct xgpu_text text = { 0 };
unsigned long index;
@@ -410,11 +221,6 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru
(void)multiview;
#endif
xgpu_text_append(&text, "%s", shader_prologue);
/* (the pixel shader's model_lighting; the normal's length in w) */
if (lighting)
xgpu_text_append(&text, "out vec4 xWorldNormal;\n");
if (lighting && lighting->lights == 2)
xgpu_text_append(&text, "out vec3 xWorldPosition;\n");
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
{
if (packed_attribute_mask & (1UL << index))
@@ -461,14 +267,6 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru
char mask[5];
xgpu_text_append(&text, "\t/* %lu */\n", index);
/* the lighting's normal and position, before the program reuses them */
if (lighting && index == lighting->normal_instruction)
{
xgpu_text_append(&text, "\txWorldNormal = vec4(r%lu.xyz, length(r%lu.xyz));\n",
lighting->normal_register, lighting->normal_register);
}
if (lighting && lighting->lights == 2 && index == lighting->position_instruction)
xgpu_text_append(&text, "\txWorldPosition = r%lu.xyz;\n", lighting->position_register);
xgpu_text_append(&text, "\tA = "); operand(&text, instruction, 'A', relative); xgpu_text_append(&text, ";\n");
xgpu_text_append(&text, "\tB = "); operand(&text, instruction, 'B', relative); xgpu_text_append(&text, ";\n");
xgpu_text_append(&text, "\tC = "); operand(&text, instruction, 'C', relative); xgpu_text_append(&text, ";\n");
+2 -9
View File
@@ -2421,13 +2421,7 @@ int p2p_public_games(struct p2p_public_game *games, int maximum)
listing_count = p2p_lobby_games(listings, 32);
for (index = 0; index < listing_count && count < maximum; index++)
{
struct p2p_public_game *game;
/* (one with a password: joined from the server browser, which asks
for it, not from this list, which has no way to) */
if (listings[index].locked)
continue;
game = &games[count++];
struct p2p_public_game *game = &games[count++];
memset(game, 0, sizeof(*game));
snprintf(game->name, sizeof(game->name), "%s", listings[index].name);
@@ -2539,8 +2533,7 @@ void p2p_new_invite_if_listed(void)
if (!p2p.has_token || !p2p.token_listed)
return;
make_invite();
platform_log("Internet play: a new invite, so that the one listed lets no one in (private, or a new password): %s",
p2p.invite);
platform_log("Internet play: the game is private now, with a new invite: %s", p2p.invite);
if (p2p.hosting)
{
p2p_signal_host(p2p.token);
-20
View File
@@ -149,12 +149,6 @@ in everyone's server browser: Create Game's PUBLIC; a private game is
reached only by its invite. Going private makes a new invite, if the old one
was listed */
void p2p_set_hosting_public(int public);
/* the password of the game hosted for the internet (Server Setup's
PASSWORD; NULL or empty: none): its listing lets only those who know it
join from the server browser (its invite link still joins it). Setting or
changing it makes a new invite, if the old one was listed. It takes a few
milliseconds (the password's key) */
void p2p_set_hosting_password(const char *password);
/* the hosted game's details as listed (printable ASCII is kept; NULL leaves
one as it was): the game's server calls it as they change (calling it with
the same again costs little) */
@@ -173,16 +167,10 @@ enum
/* an invite link's text (P2P_LINK_SIZE) */
P2P_LISTING_INVITE_SIZE = 77,
/* (struct p2p_public_game's invite is as large) */
/* a password's game's: its host's key and its key's hash, and its token
sealed (P2P_KEY_SIZE, P2P_KEY_HASH_SIZE, P2P_SEALED_TOKEN_SIZE) */
P2P_LISTING_SIGNING_KEY_SIZE = 32,
P2P_LISTING_KEY_HASH_SIZE = 16,
P2P_LISTING_SEALED_TOKEN_SIZE = 56,
};
struct p2p_listing
{
/* (empty for a locked one until p2p_listing_unlock opens it) */
char invite[P2P_LISTING_INVITE_SIZE];
/* the host's (its XNADDR's abEnet once reached) */
unsigned char identifier[6];
@@ -195,20 +183,12 @@ struct p2p_listing
unsigned char failed;
/* milliseconds, -1 if not known */
short ping;
/* it has a password: its invite is sealed with it (p2p_listing_unlock) */
unsigned char locked;
unsigned char key_hash[P2P_LISTING_KEY_HASH_SIZE];
unsigned char signing_key[P2P_LISTING_SIGNING_KEY_SIZE];
unsigned char sealed_token[P2P_LISTING_SEALED_TOKEN_SIZE];
};
/* a copy of the public games found, in the order shown (the most players
first; then those not failed, the open ones, by name); returns their count */
int p2p_lobby_games(struct p2p_listing *games, int maximum_count);
/* a game the browser could not join: kept, marked failed, for this run */
void p2p_lobby_mark_failed(const unsigned char *identifier);
/* a locked game's invite (into its invite), if the password is its own:
FALSE if not; TRUE for a game with none. It takes a few milliseconds */
int p2p_listing_unlock(struct p2p_listing *listing, const char *password);
/* the sizes of a Discord user's id and name as kept (with their end), and
the text kept of either as told: only the characters allowed (digits in an
-45
View File
@@ -20,7 +20,6 @@ tunnel's packets are sealed with keys only its two machines have.
#include "monocypher.h"
#include "monocypher-ed25519.h"
#include <stdlib.h>
#include <string.h>
/* ---------- SHA-256 */
@@ -650,50 +649,6 @@ int p2p_ed25519_to_x25519(const unsigned char *public_key, unsigned char *x25519
return !p2p_equal(product, zero, P2P_KEY_SIZE);
}
/* ---------- passwords (a password-protected public game's listing holds
its invite's token sealed with the password's key: p2p_lobby.c) */
/* (Argon2id of 4 MiB and three passes: tens of milliseconds, once for the
host and once a guess for a joiner) */
#define PASSWORD_KEY_BLOCKS 4096
#define PASSWORD_KEY_PASSES 3
void p2p_password_key(const char *password, const unsigned char *salt, unsigned char *key)
{
crypto_argon2_config config = { CRYPTO_ARGON2_ID, PASSWORD_KEY_BLOCKS, PASSWORD_KEY_PASSES, 1 };
crypto_argon2_inputs inputs;
void *work_area = malloc((size_t)PASSWORD_KEY_BLOCKS * 1024);
inputs.pass = (const unsigned char *)password;
inputs.pass_size = (unsigned int)strlen(password);
inputs.salt = salt;
inputs.salt_size = P2P_KEY_SIZE;
if (!work_area)
{
/* (no key anyone could guess: nothing opens with it) */
posix_random_bytes(key, P2P_PASSWORD_KEY_SIZE);
return;
}
crypto_argon2(key, P2P_PASSWORD_KEY_SIZE, work_area, config, inputs, crypto_argon2_no_extras);
crypto_wipe(work_area, (size_t)PASSWORD_KEY_BLOCKS * 1024);
free(work_area);
}
void p2p_seal_token(const unsigned char *key, const unsigned char *signing_key, const unsigned char *token,
unsigned char *sealed)
{
/* (a nonce of its own each time: 24 random bytes never repeat) */
posix_random_bytes(sealed, 24);
crypto_aead_lock(sealed + 24 + 16, sealed + 24, key, sealed, signing_key, P2P_KEY_SIZE, token, P2P_TOKEN_SIZE);
}
int p2p_unseal_token(const unsigned char *key, const unsigned char *signing_key, const unsigned char *sealed,
unsigned char *token)
{
return crypto_aead_unlock(token, sealed + 24, key, sealed, signing_key, P2P_KEY_SIZE, sealed + 24 + 16,
P2P_TOKEN_SIZE) == 0;
}
int p2p_ed25519_verify(const unsigned char *public_key, const void *message, int size,
const unsigned char *signature)
{
-20
View File
@@ -246,26 +246,6 @@ int p2p_ed25519_verify(const unsigned char *public_key, const void *message, int
/* the X25519 public key of an Ed25519 one; 0 if it has a small order */
int p2p_ed25519_to_x25519(const unsigned char *public_key, unsigned char *x25519_public);
enum
{
/* a password-protected listing's token, sealed (p2p_seal_token): its
nonce (24), its tag (16), then the token sealed */
P2P_PASSWORD_KEY_SIZE = 32,
P2P_SEALED_TOKEN_SIZE = 24 + 16 + 16,
};
/* the key of a password (Argon2id: P2P_PASSWORD_KEY_SIZE bytes), for the
host whose Ed25519 key salt is (P2P_KEY_SIZE bytes): it takes a few
milliseconds, so that guessing passwords at a listing takes long */
void p2p_password_key(const char *password, const unsigned char *salt, unsigned char *key);
/* a token (P2P_TOKEN_SIZE bytes) sealed with a password's key, bound to the
host's Ed25519 key (P2P_SEALED_TOKEN_SIZE bytes); and opened: 0 if the key
is not the one it was sealed with (a wrong password), or it was altered */
void p2p_seal_token(const unsigned char *key, const unsigned char *signing_key, const unsigned char *token,
unsigned char *sealed);
int p2p_unseal_token(const unsigned char *key, const unsigned char *signing_key, const unsigned char *sealed,
unsigned char *token);
/* ---------- p2p_lobby.c: public games' listings */
/* the p2p thread's pass: the token of the game hosted for the internet
+13 -119
View File
@@ -12,11 +12,9 @@ the host can list its invite, alter its listing, or list another's under
false details:
"HL", format 1, the lobby version (2: HALO_PORT_NETWORK_VERSION; browsers
hide others), flags (open, under way, teams, closed, password), sequence
(4: newest wins), Unix time (4), the Ed25519 key (32), the invite's token
(16; a password's game's sealed with the password's key, 56:
p2p_seal_token; a tombstone's zero), players, most players, the
gametype's engine (1 each), the game's name,
hide others), flags (open, under way, teams, closed), sequence (4: newest
wins), Unix time (4), the Ed25519 key (32), the invite's token (16),
players, most players, the gametype's engine (1 each), the game's name,
map and gametype (a length byte, then up to 32, 32 and 24 characters of
printable ASCII), a stamp (8, reserved: zero), and the signature (64) of
"hceu-lobby-1" and all before it.
@@ -40,14 +38,6 @@ one deletes or keeps is no authority, so
A host that stops publishes a tombstone, then clears its slot. Going private
makes a new invite (p2p.c), so a listing seen before lets no one in.
A game with a password (Server Setup's PASSWORD: p2p_set_hosting_password)
is listed with its token sealed with the password's key (Argon2id of the
password, salted with the host's key: p2p_password_key), so that only who
knows the password can join it from the server browser
(p2p_listing_unlock); its invite link, which holds the token, still joins it
as any invite does. Setting or changing the password makes a new invite, as
going private does.
Checking signatures takes work, and anyone can send listings: at most
VERIFY_BUDGET milliseconds of it each pass of the p2p thread, from a queue
of MAXIMUM_QUEUED; the rest is dropped (hosts publish again).
@@ -72,11 +62,11 @@ enum
_listing_has_teams = 4,
/* a tombstone: the host stopped */
_listing_closed = 8,
/* its token sealed with a password's key */
/* (reserved) */
_listing_password = 16,
STAMP_SIZE = 8,
/* the signed part's end: everything but the signature */
MAXIMUM_LISTING_SIZE = 2 + 1 + 2 + 1 + 4 + 4 + P2P_KEY_SIZE + P2P_SEALED_TOKEN_SIZE + 3 +
MAXIMUM_LISTING_SIZE = 2 + 1 + 2 + 1 + 4 + 4 + P2P_KEY_SIZE + P2P_TOKEN_SIZE + 3 +
(1 + P2P_LISTING_NAME_SIZE) + (1 + P2P_LISTING_MAP_SIZE) + (1 + P2P_LISTING_GAMETYPE_SIZE) + STAMP_SIZE +
P2P_SIGNATURE_SIZE,
MINIMUM_LISTING_SIZE = 2 + 1 + 2 + 1 + 4 + 4 + P2P_KEY_SIZE + P2P_TOKEN_SIZE + 3 + 3 + STAMP_SIZE +
@@ -98,10 +88,6 @@ enum
static const char signature_label[] = "hceu-lobby-1";
/* (p2p.h's sizes of a locked listing's are p2p_internal.h's) */
typedef char check_listing_sizes[P2P_LISTING_SIGNING_KEY_SIZE == P2P_KEY_SIZE &&
P2P_LISTING_KEY_HASH_SIZE == P2P_KEY_HASH_SIZE && P2P_LISTING_SEALED_TOKEN_SIZE == P2P_SEALED_TOKEN_SIZE ? 1 : -1];
/* a listing, read */
struct listing
{
@@ -111,8 +97,6 @@ struct listing
unsigned long time;
unsigned char key[P2P_KEY_SIZE];
unsigned char token[P2P_TOKEN_SIZE];
/* (_listing_password: its token, sealed) */
unsigned char sealed_token[P2P_SEALED_TOKEN_SIZE];
int player_count, maximum_player_count, engine_type;
char name[P2P_LISTING_NAME_SIZE + 1];
char map[P2P_LISTING_MAP_SIZE + 1];
@@ -159,9 +143,6 @@ static struct
int engine_type;
int flags;
int player_count, maximum_player_count;
/* the password's key (p2p_set_hosting_password), if it has one */
int has_password;
unsigned char password_key[P2P_PASSWORD_KEY_SIZE];
/* listed: with this token; its listing as last published */
int listed;
unsigned char token[P2P_TOKEN_SIZE];
@@ -259,8 +240,6 @@ static int listing_make(unsigned char *bytes, int flags)
unsigned char data[sizeof(signature_label) - 1 + MAXIMUM_LISTING_SIZE];
int size = 0;
if (lobby.has_password && !(flags & _listing_closed))
flags |= _listing_password;
bytes[size++] = 'H';
bytes[size++] = 'L';
bytes[size++] = LISTING_FORMAT;
@@ -273,20 +252,8 @@ static int listing_make(unsigned char *bytes, int flags)
size += 4;
memcpy(bytes + size, p2p_signing_key(), P2P_KEY_SIZE);
size += P2P_KEY_SIZE;
if (flags & _listing_password)
{
p2p_seal_token(lobby.password_key, p2p_signing_key(), lobby.token, bytes + size);
size += P2P_SEALED_TOKEN_SIZE;
}
else
{
/* (a tombstone's none: it may end a game with a password) */
if (flags & _listing_closed)
memset(bytes + size, 0, P2P_TOKEN_SIZE);
else
memcpy(bytes + size, lobby.token, P2P_TOKEN_SIZE);
size += P2P_TOKEN_SIZE;
}
memcpy(bytes + size, lobby.token, P2P_TOKEN_SIZE);
size += P2P_TOKEN_SIZE;
bytes[size++] = (unsigned char)(lobby.player_count > 255 ? 255 : lobby.player_count);
bytes[size++] = (unsigned char)(lobby.maximum_player_count > 255 ? 255 : lobby.maximum_player_count);
bytes[size++] = (unsigned char)lobby.engine_type;
@@ -335,20 +302,8 @@ static int listing_read(const unsigned char *bytes, int size, struct listing *li
offset += 4;
memcpy(listing->key, bytes + offset, P2P_KEY_SIZE);
offset += P2P_KEY_SIZE;
if (listing->flags & _listing_password)
{
/* (its token sealed, which is longer: the counts after it too) */
if (offset + P2P_SEALED_TOKEN_SIZE + 3 > size)
return 0;
memset(listing->token, 0, P2P_TOKEN_SIZE);
memcpy(listing->sealed_token, bytes + offset, P2P_SEALED_TOKEN_SIZE);
offset += P2P_SEALED_TOKEN_SIZE;
}
else
{
memcpy(listing->token, bytes + offset, P2P_TOKEN_SIZE);
offset += P2P_TOKEN_SIZE;
}
memcpy(listing->token, bytes + offset, P2P_TOKEN_SIZE);
offset += P2P_TOKEN_SIZE;
listing->player_count = bytes[offset++];
listing->maximum_player_count = bytes[offset++];
listing->engine_type = bytes[offset++];
@@ -614,22 +569,10 @@ static void listing_take(const struct queued *queued, const struct listing *list
memcpy(game->payload, queued->payload, (size_t)queued->size);
game->payload_size = queued->size;
shown = &game->listing;
shown->locked = (listing->flags & _listing_password) != 0;
if (shown->locked)
{
/* (its invite once the password opens its token: p2p_listing_unlock) */
shown->invite[0] = 0;
memcpy(shown->key_hash, queued->key_hash, P2P_KEY_HASH_SIZE);
memcpy(shown->signing_key, listing->key, P2P_KEY_SIZE);
memcpy(shown->sealed_token, listing->sealed_token, P2P_SEALED_TOKEN_SIZE);
}
else
{
memcpy(bytes, queued->key_hash, P2P_KEY_HASH_SIZE);
memcpy(bytes + P2P_KEY_HASH_SIZE, listing->token, P2P_TOKEN_SIZE);
p2p_hex(bytes, sizeof(bytes), text);
snprintf(shown->invite, sizeof(shown->invite), "halo://join/%s", text);
}
memcpy(bytes, queued->key_hash, P2P_KEY_HASH_SIZE);
memcpy(bytes + P2P_KEY_HASH_SIZE, listing->token, P2P_TOKEN_SIZE);
p2p_hex(bytes, sizeof(bytes), text);
snprintf(shown->invite, sizeof(shown->invite), "halo://join/%s", text);
p2p_identifier_from_hash(queued->key_hash, shown->identifier);
memcpy(shown->name, listing->name, sizeof(shown->name));
memcpy(shown->map, listing->map, sizeof(shown->map));
@@ -726,33 +669,6 @@ void p2p_set_hosting_public(int public)
pthread_mutex_unlock(&p2p_lock);
}
void p2p_set_hosting_password(const char *password)
{
unsigned char key[P2P_PASSWORD_KEY_SIZE];
int has_password = password && *password;
/* (the key takes a while: not under the lock) */
if (has_password)
p2p_password_key(password, p2p_signing_key(), key);
pthread_mutex_lock(&p2p_lock);
if (has_password != lobby.has_password ||
(has_password && !p2p_equal(key, lobby.password_key, P2P_PASSWORD_KEY_SIZE)))
{
/* (a new invite, if one was listed: who saw it, with no password or
another, cannot join with it) */
p2p_new_invite_if_listed();
lobby.has_password = has_password;
if (has_password)
memcpy(lobby.password_key, key, sizeof(key));
else
memset(lobby.password_key, 0, sizeof(lobby.password_key));
if (lobby.listed)
republish_soon(0);
}
pthread_mutex_unlock(&p2p_lock);
memset(key, 0, sizeof(key));
}
void p2p_set_game_listing(const char *name, const char *map, const char *gametype, int engine_type, int open,
int in_progress, int has_teams)
{
@@ -849,28 +765,6 @@ int p2p_lobby_games(struct p2p_listing *games, int maximum_count)
return count;
}
int p2p_listing_unlock(struct p2p_listing *listing, const char *password)
{
unsigned char key[P2P_PASSWORD_KEY_SIZE];
unsigned char bytes[P2P_KEY_HASH_SIZE + P2P_TOKEN_SIZE];
char text[2 * (P2P_KEY_HASH_SIZE + P2P_TOKEN_SIZE) + 1];
int opened;
if (!listing->locked)
return 1;
p2p_password_key(password ? password : "", listing->signing_key, key);
memcpy(bytes, listing->key_hash, P2P_KEY_HASH_SIZE);
opened = p2p_unseal_token(key, listing->signing_key, listing->sealed_token, bytes + P2P_KEY_HASH_SIZE);
if (opened)
{
p2p_hex(bytes, sizeof(bytes), text);
snprintf(listing->invite, sizeof(listing->invite), "halo://join/%s", text);
}
memset(key, 0, sizeof(key));
memset(bytes, 0, sizeof(bytes));
return opened;
}
void p2p_lobby_mark_failed(const unsigned char *identifier)
{
pthread_mutex_lock(&p2p_lock);
+2 -3
View File
@@ -104,9 +104,8 @@ enum
PROOF_SIZE = NONCE_SIZE + TAG_SIZE,
BUFFER_SIZE = 4096,
MAXIMUM_MESSAGE_SIZE = 512,
/* a listing's most (p2p_lobby.c's MAXIMUM_LISTING_SIZE, 268 with a
password's sealed token, and some) */
MAXIMUM_LISTING_SIZE = 320,
/* a listing's most (p2p_lobby.c's MAXIMUM_LISTING_SIZE, and some) */
MAXIMUM_LISTING_SIZE = 256,
/* the publishes awaiting acknowledgement on a broker */
MAXIMUM_IN_FLIGHT = 4,
/* the topics a broker is subscribed to (_topic_*) */
+19 -128
View File
@@ -16,7 +16,6 @@ it exists, so that the player's edits and comments stay.
#include <SDL3/SDL.h>
#include <ctype.h>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
@@ -97,12 +96,6 @@ static const struct config_setting config_settings[] =
{ "display.max_fps", _config_integer, "0", "HALO_MAX_FPS", _environment_value, _platform_desktop,
"With vsync off, the most frames a second: 0 for twice the display's\n"
"refresh rate, -1 for no limit (which can hang some Intel graphics)." },
{ "display.anti_aliasing", _config_string, "\"off\"", "HALO_ANTI_ALIASING", _environment_value, _platform_all,
"Smoothing of jagged edges, which the Xbox did not have: \"off\"; \"fxaa\"\n"
"or \"smaa\" smooth the 3D view once it is drawn (the HUD and menus stay\n"
"sharp); \"ssaa2x\" draws at twice the resolution each way (four times\n"
"the work); \"msaa2x\", \"msaa4x\" or \"msaa8x\" draw with that many samples\n"
"a pixel. Android has \"fxaa\" for \"smaa\", and no \"ssaa2x\"." },
{ "display.interpolation", _config_boolean, "true", "HALO_INTERPOLATION", _environment_value, _platform_all,
"Draw a frame for every display refresh, blending between the game's 30\n"
"ticks a second; false keeps the original 30 frames a second." },
@@ -118,10 +111,6 @@ static const struct config_setting config_settings[] =
"Draw the menus' and HUD's text with the fonts in port/assets/fonts\n"
"(Overpass) at the resolution the game draws at, and the menus' titles\n"
"from port/assets/titles; false draws the maps' bitmap fonts and titles." },
{ "display.shadow_resolution", _config_integer, "128", "HALO_SHADOW_RESOLUTION", _environment_value,
_platform_all,
"The size the objects' shadows are drawn at, in pixels each way: 128 as\n"
"on the Xbox, or 256, 512 or 1024 for smoother edges, as soft." },
{ "display.menus", _config_string, "\"pc\"", "HALO_MENUS", _environment_value, _platform_all,
"The menus: \"pc\" for the PC version's main menu (port/assets/menus,\n"
"and a menus folder here for your own), \"xbox\" for the Xbox's." },
@@ -146,12 +135,6 @@ static const struct config_setting config_settings[] =
_environment_value, _platform_all,
"The scoreboard panel's colour: \"red, green, blue, alpha\", each 0 to 255\n"
"(alpha 0 is see-through, 255 solid)." },
{ "display.per_pixel_lighting", _config_boolean, "false", "HALO_PER_PIXEL_LIGHTING", _environment_value,
_platform_all,
"Light the models (characters, weapons, vehicles, scenery) for each\n"
"pixel by the lights the game gives them, without the facets the light\n"
"of each vertex shows across curved surfaces; false lights each vertex,\n"
"as the Xbox does." },
{ "vr.enabled", _config_boolean, "true", "HALO_VR_ENABLED", _environment_value, _platform_android,
"On a VR headset (Quest, Steam Frame), play in VR; false shows the game\n"
@@ -165,13 +148,6 @@ static const struct config_setting config_settings[] =
{ "vr.refresh_rate", _config_real, "90.0", "HALO_VR_REFRESH_RATE", _environment_value, _platform_android,
"The display refresh rate to ask the headset for, in Hz (72, 90, 120);\n"
"it picks the nearest it has at or below. 0 for the highest." },
{ "vr.graphics", _config_string, "\"high\"", "HALO_VR_GRAPHICS", _environment_value, _platform_android,
"Which of the costlier effects are drawn: \"high\" all of them; \"medium\"\n"
"no object shadows, environment reflections or lens flares; \"low\" nor\n"
"specular light, bump maps, grass, the screen's fog, contrails, weather\n"
"or camouflage's second pass (a client in another's game keeps grass,\n"
"lens flares and fog); \"max\" all of them, the models lit for each\n"
"pixel and shadows at 512x512." },
{ "vr.positional_tracking", _config_boolean, "true", "HALO_VR_POSITIONAL", _environment_value, _platform_android,
"Moving your head moves the view (leaning, ducking); false only turns it.\n"
"The view recentres when play starts or resumes after a cinematic, and\n"
@@ -188,41 +164,8 @@ static const struct config_setting config_settings[] =
"Log the frame rate and where the frame time goes every five seconds\n"
"(adb logcat -s halo)." },
{ "vr.aim_with", _config_string, "\"controller\"", "HALO_VR_AIM_WITH", _environment_value, _platform_android,
"\"controller\": the weapon is in your hand (vr.handedness) and aims where\n"
"it points, while you move and look with your head; \"head\": you aim\n"
"where you look." },
{ "vr.handedness", _config_string, "\"right\"", "HALO_VR_HANDEDNESS", _environment_value, _platform_android,
"The hand that holds the weapon: \"right\", or \"left\", which mirrors the\n"
"weapon and the arms, throws grenades and grips the foregrip with the\n"
"right hand, and fires with the left trigger." },
{ "vr.mirror_controls", _config_boolean, "true", "HALO_VR_MIRROR_CONTROLS", _environment_value,
_platform_android,
"Left-handed, the sticks and buttons swap sides too: the right stick moves\n"
"and the left one turns, X jumps and A reloads (on the Steam Frame the\n"
"D-pad takes A, B, X and Y as their mirror image); false keeps them as\n"
"right-handed. The menus keep their buttons either way." },
{ "vr.button_jump", _config_string, "\"a\"", "HALO_VR_BUTTON_JUMP", _environment_value, _platform_android,
"The button each of the game's actions is on (the VR options' BUTTONS\n"
"page swaps them): \"a\", \"b\", \"x\", \"y\" (A and B on the right\n"
"controller, X and Y the Quest's on the left; the Steam Frame has all\n"
"four on the right), \"move_stick\" and \"turn_stick\" (their clicks),\n"
"\"grip\" (the weapon hand's), named as right-handed: left-handed with\n"
"mirror_controls, their mirror images. Jump," },
{ "vr.button_light", _config_string, "\"b\"", "HALO_VR_BUTTON_LIGHT", _environment_value, _platform_android,
"the flashlight (held: switch grenades)," },
{ "vr.button_action", _config_string, "\"x\"", "HALO_VR_BUTTON_ACTION", _environment_value, _platform_android,
"action and reload," },
{ "vr.button_weapon", _config_string, "\"y\"", "HALO_VR_BUTTON_WEAPON", _environment_value, _platform_android,
"switch weapon," },
{ "vr.button_crouch", _config_string, "\"move_stick\"", "HALO_VR_BUTTON_CROUCH", _environment_value,
_platform_android,
"crouch," },
{ "vr.button_zoom", _config_string, "\"turn_stick\"", "HALO_VR_BUTTON_ZOOM", _environment_value,
_platform_android,
"zoom" },
{ "vr.button_melee", _config_string, "\"grip\"", "HALO_VR_BUTTON_MELEE", _environment_value, _platform_android,
"and melee. The triggers, the free hand's grip and the menu button keep\n"
"theirs, as do the menus." },
"\"controller\": the weapon is in your right hand and aims where it points,\n"
"while you move and look with your head; \"head\": you aim where you look." },
{ "vr.laser", _config_boolean, "false", "HALO_VR_LASER", _environment_value, _platform_android,
"A laser from the weapon to what it aims at (with aim_with = \"controller\")." },
{ "vr.crosshair", _config_boolean, "true", "HALO_VR_CROSSHAIR", _environment_value, _platform_android,
@@ -237,40 +180,21 @@ static const struct config_setting config_settings[] =
{ "vr.weapon_down", _config_real, "0.14", "HALO_VR_WEAPON_DOWN", _environment_value, _platform_android,
"See weapon_forward." },
{ "vr.two_hand_reach", _config_real, "0.2", "HALO_VR_TWO_HAND_REACH", _environment_value, _platform_android,
"How near, in metres, the free hand must be to the weapon's foregrip for\n"
"How near, in metres, the left hand must be to the weapon's foregrip for\n"
"its grip button to take hold of it with both hands." },
{ "vr.body", _config_string, "\"arms\"", "HALO_VR_BODY", _environment_value, _platform_android,
"What of your body is drawn with the weapon: \"arms\", the arms and hands\n"
"reaching from the shoulders to the controllers; \"hands\", the hands\n"
"alone; \"off\", neither (the weapon alone)." },
{ "vr.fingers", _config_boolean, "true", "HALO_VR_FINGERS", _environment_value, _platform_android,
"The free hand's fingers follow the controller: the index finger the\n"
"trigger, the others the grip, the thumb where it rests (on the Quest's\n"
"and the Steam Frame's controllers); false leaves the hand as the\n"
"weapon's animation has it." },
{ "vr.arm_ik", _config_boolean, "true", "HALO_VR_ARM_IK", _environment_value, _platform_android,
"The free hand's arm bends at the elbow to reach it from the shoulder;\n"
"false keeps the arm's shape as on the weapon, pointed at the shoulder." },
{ "vr.flashlight_gesture", _config_boolean, "true", "HALO_VR_FLASHLIGHT_GESTURE", _environment_value,
_platform_android,
"Tapping the side or the top of your head with either hand (to it and\n"
"away again) turns the flashlight on or off; the flashlight's button\n"
"still does. Not while aiming through a scope, holding the weapon with\n"
"both hands, holding or throwing a grenade, just after a melee, or with\n"
"both hands at the head." },
{ "vr.melee_swing_speed", _config_real, "2.0", "HALO_VR_MELEE_SWING", _environment_value, _platform_android,
"Thrusting a hand forward faster than this, in metres per second, is a\n"
"melee attack (the weapon hand's grip is too); 0 turns it off." },
"melee attack (the right grip is too); 0 turns it off." },
{ "vr.grenade_throw_speed", _config_real, "1.5", "HALO_VR_GRENADE_THROW", _environment_value, _platform_android,
"A grenade is held while the free hand's trigger is, and thrown as it\n"
"lets go: along the hand's throw, when the hand moved faster than this\n"
"(metres per second; full strength at 4), else as the game throws it. 0\n"
"turns the hand's throw off." },
"A grenade is held while the left trigger is, and thrown as it lets go:\n"
"along the hand's throw, when the hand moved faster than this (metres per\n"
"second; full strength at 4), else as the game throws it. 0 turns the\n"
"hand's throw off." },
{ "vr.grenade_scale", _config_real, "0.55", "HALO_VR_GRENADE_SCALE", _environment_value, _platform_android,
"The size the grenade held in the free hand is drawn at (1 as in the game)." },
"The size the grenade held in the left hand is drawn at (1 as in the game)." },
{ "vr.grenade_right", _config_real, "0.055", "HALO_VR_GRENADE_RIGHT", _environment_value, _platform_android,
"Where the held grenade is drawn, in metres from the free controller's\n"
"grip: towards the palm (the left hand's right),", },
"Where the held grenade is drawn, in metres from the left controller's\n"
"grip: towards the palm (right),", },
{ "vr.grenade_forward", _config_real, "0.01", "HALO_VR_GRENADE_FORWARD", _environment_value, _platform_android,
"the grip's forward (through the fist, towards the index finger),", },
{ "vr.grenade_down", _config_real, "0.01", "HALO_VR_GRENADE_DOWN", _environment_value, _platform_android,
@@ -333,13 +257,6 @@ static const struct config_setting config_settings[] =
{ "vr.comfort_vignette", _config_real, "0.7", "HALO_VR_COMFORT_VIGNETTE", _environment_value, _platform_android,
"How far the view darkens from its edges while a vehicle turns you\n"
"(0 never, 1 to a small window)." },
{ "vr.foot_vignette", _config_string, "\"off\"", "HALO_VR_FOOT_VIGNETTE", _environment_value, _platform_android,
"On foot, the view darkens from its edges too: \"turning\" while the stick\n"
"turns you smoothly (smooth_turn), \"moving\" while a stick turns or moves\n"
"you, as far as it is pushed; \"off\" never." },
{ "vr.foot_vignette_strength", _config_real, "0.7", "HALO_VR_FOOT_VIGNETTE_STRENGTH", _environment_value,
_platform_android,
"How far it darkens then (as comfort_vignette)." },
{ "vr.motion_tracker_on_wrist", _config_boolean, "true", "HALO_VR_MOTION_TRACKER_WRIST", _environment_value,
_platform_android,
"Show the motion tracker on the back of the left wrist, like a watch (turn\n"
@@ -360,11 +277,9 @@ static const struct config_setting config_settings[] =
{ "vr.smooth_turn", _config_boolean, "false", "HALO_VR_SMOOTH_TURN", _environment_value, _platform_android,
"The right stick turns smoothly; false turns in steps (snap_turn_degrees)." },
{ "vr.snap_turn_degrees", _config_real, "30.0", "HALO_VR_SNAP_TURN", _environment_value, _platform_android,
"How far a flick of the right stick turns, in degrees (the VR options step\n"
"through 10 to 45 by 5, 60, 75 and 90)." },
"How far a flick of the right stick turns." },
{ "vr.smooth_turn_speed", _config_real, "120.0", "HALO_VR_SMOOTH_TURN_SPEED", _environment_value, _platform_android,
"Degrees per second the right stick turns at, fully pushed, with smooth_turn\n"
"(the VR options step through 30 to 150 by 15, then to 300 by 30)." },
"Degrees per second the right stick turns at, fully pushed, with smooth_turn." },
{ "vr.cinematics_in_3d", _config_boolean, "false", "HALO_VR_CINEMATICS_3D", _environment_value, _platform_android,
"Show cinematics around you, from the cinematic camera; false shows them\n"
"on the screen in front of you (easier on the stomach)." },
@@ -386,10 +301,6 @@ static const struct config_setting config_settings[] =
{ "audio.effects_volume", _config_real, "1.0", "HALO_EFFECTS_VOLUME", _environment_value, _platform_all,
"The volume of every other sound (effects and speech), 0.0 to 1.0 (of\n"
"audio.volume)." },
{ "audio.reverb", _config_boolean, "true", "HALO_REVERB", _environment_value, _platform_all,
"Reverberate the world's sounds as the place the player is in does (the\n"
"maps' sound environments, as the Xbox's I3DL2 reverb did); false keeps\n"
"them dry." },
{ "input.mouse_sensitivity", _config_real, "1.0", "HALO_MOUSE_SENSITIVITY", _environment_value, _platform_desktop,
"How far the view turns for the mouse's movement." },
@@ -514,12 +425,6 @@ static const struct config_setting config_settings[] =
"\"on\", \"shields_only\" or \"explosives_only\" (Server Setup's FRIENDLY\n"
"FIRE in co-op, which writes its choice here). Their AI allies they\n"
"always can, as in the campaign." },
{ "network.coop_player_collisions", _config_boolean, "true", "HALO_NET_COOP_PLAYER_COLLISIONS", _environment_value,
_platform_all,
"Whether the players of an online co-op game bump into each other;\n"
"false, they walk through each other (the AI's characters they still\n"
"bump into). Server Setup's PLAYER COLLISIONS in co-op writes its\n"
"choice here." },
{ "network.coop_enemies_mode", _config_string, "\"per_player\"", "HALO_NET_COOP_ENEMIES_MODE", _environment_value,
_platform_all,
"Online co-op's extra enemies: \"none\", \"per_player\" (each squad of\n"
@@ -607,17 +512,14 @@ static const struct config_setting config_settings[] =
{ "debug.gl_thread", _config_integer, "-1", "HALO_GL_THREAD", _environment_value, _platform_android,
"Mesa's GL thread (its drivers: the Steam Frame's): 1 on, 0 off, -1 as the\n"
"driver chooses. Read when the app starts." },
{ "debug.gl_environment", _config_string, "\"\"", "HALO_GL_ENVIRONMENT", _environment_value, _platform_android,
"Variables for the GL driver, \"NAME=value NAME2=value\", set before it starts\n"
"(the Steam Frame's Mesa: ZINK_DEBUG, TU_DEBUG, ...), for trying. Read when\n"
"the app starts." },
{ "debug.xr_deferred_copies", _config_integer, "-1", "HALO_XR_DEFERRED", _environment_value, _platform_android,
"The headset's layer copies: 1 deferred (no waits for the GL thread or the\n"
"GPU in the middle of the frame), 0 each one restored and released at once,\n"
"-1 as the runtime needs (deferred on SteamVR). Read when the app starts." },
{ "debug.vr_options", _config_boolean, "false", "HALO_DEBUG_VR_OPTIONS", _environment_value, _platform_android,
"Off the headset (the emulator), show the VR options panel on the main\n"
"and pause menus all the same, for checking its pages." },
{ "debug.mirror_first_person", _config_boolean, "false", "HALO_MIRROR_FIRST_PERSON", _environment_value,
_platform_android,
"Off the headset (flat), the first-person weapon drawn mirrored, as the\n"
"left hand's is in VR: a test aid for what reads mirrored there. Read once." },
{ "debug.test_input", _config_string, "\"\"", "HALO_TEST_INPUT", _environment_value, _platform_all,
"\"bot:<seed>\" plays controller 1 with a scripted pattern (automated\n"
"network tests); \"look:<seed>\" stands still, only turning and looking\n"
@@ -759,24 +661,13 @@ static char *config_read_file(const char *path, size_t *size)
static int config_write_file(const char *path, const char *text)
{
#ifdef HALO_ANDROID
/* written beside it and renamed over it: a file the app may not write
(one pushed by adb belongs to the shell) is replaced all the same, as the
folder is the app's, and a write cut short leaves the old file whole */
char temporary[1100];
FILE *file;
FILE *file = fopen(path, "wb");
int written;
snprintf(temporary, sizeof(temporary), "%s.tmp", path);
file = fopen(temporary, "wb");
if (!file)
return 0;
written = fwrite(text, 1, strlen(text), file) == strlen(text);
written = fclose(file) == 0 && written;
if (written && rename(temporary, path) == 0)
return 1;
platform_log("settings: cannot replace %s (%s)", path, strerror(errno));
remove(temporary);
return 0;
return fclose(file) == 0 && written;
#else
return SDL_SaveFile(path, text, strlen(text));
#endif
+148 -911
View File
File diff suppressed because it is too large. Load diff
+13 -40
View File
@@ -656,10 +656,6 @@ struct texture_entry
unsigned long last_used_frame;
/* the high-res HUD texture drawn in its place (hud_hires.h), or -1 */
long override;
/* the newest generation of its pages (memory_watch_generation) as of the
memory watch serial read before it was found: the same while no watched
page has been written since (0: never found) */
unsigned long watched_serial, watched_generation;
};
#define TEXTURE_BUCKET_COUNT 4096
@@ -672,7 +668,7 @@ static struct texture_entry *texture_buckets[TEXTURE_BUCKET_COUNT];
texture that is not palettized is remembered with the memory watch serial it
started at: while no watched page has been written since, and no texture
has been dropped, the same lookup finds the same current texture. */
#define RECENT_TEXTURE_COUNT 512
#define RECENT_TEXTURE_COUNT 64
static struct
{
@@ -684,14 +680,9 @@ static struct
static unsigned long texture_drop_serial = 1;
static unsigned long texture_frame = 0;
/* (every bit of the three mixed into the top ones: textures are aligned,
and few sizes and formats are common) */
static unsigned long bucket_index(DWORD data, DWORD format_word, DWORD size_word)
{
unsigned long hash = (unsigned long)data * 2654435761UL ^ (unsigned long)format_word * 2246822519UL ^
(unsigned long)size_word * 3266489917UL;
return ((hash & 0xffffffffUL) >> 20) % TEXTURE_BUCKET_COUNT;
return ((data >> 7) ^ (format_word * 2654435761UL) ^ size_word) % TEXTURE_BUCKET_COUNT;
}
/* palettized textures are cached per palette contents: the game rewrites
@@ -766,33 +757,25 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum *
unsigned long recent = bucket_index(data, format_word, size_word) % RECENT_TEXTURE_COUNT;
unsigned long watch_serial = memory_watch_serial();
/* (a texture that is not palettized has the one entry, until one is
dropped: remembered, a page written since only means checking it) */
entry = NULL;
if (!palettized && recent_textures[recent].entry && recent_textures[recent].data == data &&
recent_textures[recent].format_word == format_word && recent_textures[recent].size_word == size_word &&
recent_textures[recent].watch_serial == watch_serial &&
recent_textures[recent].drop_serial == texture_drop_serial)
{
entry = recent_textures[recent].entry;
if (recent_textures[recent].watch_serial == watch_serial)
{
entry->last_used_frame = texture_frame;
return texture_entry_result(entry, target, description);
}
entry->last_used_frame = texture_frame;
return texture_entry_result(entry, target, description);
}
if (!entry)
for (entry = *bucket; entry; entry = entry->next)
{
for (entry = *bucket; entry; entry = entry->next)
if (entry->data == data && entry->format_word == format_word && entry->size_word == size_word)
{
if (entry->data == data && entry->format_word == format_word && entry->size_word == size_word)
{
if (entry->palette_hash == hash)
break;
variant_count++;
if (!oldest_variant || entry->last_used_frame < oldest_variant->last_used_frame)
oldest_variant = entry;
}
if (entry->palette_hash == hash)
break;
variant_count++;
if (!oldest_variant || entry->last_used_frame < oldest_variant->last_used_frame)
oldest_variant = entry;
}
}
if (!entry && variant_count >= MAXIMUM_PALETTE_VARIANTS)
@@ -823,17 +806,7 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum *
if (no_cache < 0)
no_cache = config_boolean("debug.texture_no_cache");
/* (its pages' newest generation, found again only once a watched page
has been written: a large texture's pages, scanned for every draw that
bound it, were much of a frame with many objects) */
if (entry->watched_serial && entry->watched_serial == watch_serial)
generation = entry->watched_generation;
else
{
generation = memory_watch_generation(entry->address, entry->size);
entry->watched_serial = watch_serial;
entry->watched_generation = generation;
}
generation = memory_watch_generation(entry->address, entry->size);
if (!entry->generation || generation > entry->generation || no_cache)
{
/* protect first, so a write racing with the upload is noticed */
+3 -68
View File
@@ -53,11 +53,6 @@ must call this afterwards. */
void xgpu_gl_state_invalidate(void);
/* a compiled shader, or a linked program of two, or 0 with the log
written */
GLuint xgpu_compile_shader(GLenum type, const char *code, const char *what);
GLuint xgpu_link_program(GLuint vertex_shader, GLuint fragment_shader, const char *what);
/* ---------- generated source text */
struct xgpu_text
@@ -76,34 +71,14 @@ void xgpu_text_append(struct xgpu_text *text, const char *format, ...) __attribu
/* D3D constant register -96 is hardware register 0 */
#define XGPU_VERTEX_CONSTANT_BIAS 96
/* where one of the game's model lighting programs (d3d8_gl.c
halo_vertex_shader_lighting) has the normal, and the world position, that it
lights the diffuse color by: the temporary register that holds each before
the instruction given */
struct nv2a_vertex_lighting
{
/* 1 by the ambient and distant lights, 2 by the point lights too */
int lights;
unsigned long normal_instruction, normal_register;
/* (with the point lights only) */
unsigned long position_instruction, position_register;
};
/* finds them in a model lighting program, checking that it lights its
diffuse color as nv2a_psh.c does for each pixel; FALSE if it does not */
BOOL nv2a_vertex_shader_lighting(const DWORD *instructions, unsigned long instruction_count,
struct nv2a_vertex_lighting *lighting);
/* GLSL for an NV2A vertex program (the instruction words after the program
header). Attributes whose bit is set in packed_attribute_mask are fed as
NORMPACKED3 32-bit integers and unpacked in the shader. With lighting (else
NULL), the normal and world position go to the pixel shader too, which
lights the diffuse color for each pixel (nv2a_pixel_shader_key
per_pixel_lighting). Returns a malloc'd string. */
NORMPACKED3 32-bit integers and unpacked in the shader. Returns a malloc'd
string. */
/* multiview: for a two-view (GL_OVR_multiview2) target, each view's
position corrected from the shared one by mv_rows (d3d8_gl.c, VR) */
char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instruction_count,
unsigned long packed_attribute_mask, const struct nv2a_vertex_lighting *lighting, int multiview);
unsigned long packed_attribute_mask, int multiview);
/* ---------- pixel shaders */
@@ -139,15 +114,6 @@ struct nv2a_pixel_shader_key
behind it only where it covers it (the Xbox's point-sampled meters stop
at their texels' edges; filtered ones have a fringe of faint texels) */
unsigned char coverage_alpha;
/* a model lighting program's draw lit for each pixel
(display.per_pixel_lighting): nv2a_vertex_lighting's lights, or 0 for
the diffuse color the vertex shader computed */
unsigned char per_pixel_lighting;
/* drawn into a multisampled target (display.anti_aliasing's
multisampling), its samples a pixel: the alpha test covers samples in
proportion to how far alpha is past the reference, not all of the pixel
or none of it, so that cut-out edges (foliage, grates) are smoothed too */
unsigned char alpha_test_samples;
};
char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key);
@@ -174,13 +140,6 @@ C0/C1 of each stage and the final combiner, and texture constants */
"uniform vec4 texture_scale[4];\n" \
XGPU_PIXEL_UNIFORMS_ES
/* the vertex constants the per-pixel model lighting reads, in a uniform of
their own (the vertex shader's 192 would pass OpenGL ES's least fragment
uniform space): [0] c[-82] (the translucency in z), [1] to [11] c[-79] to
c[-69] (rasterizer_set_model_lighting's two point lights, two distant
lights and the ambient light) */
#define XGPU_MODEL_LIGHT_COUNT 12
/* ---------- textures */
struct xgpu_texture_description
@@ -219,33 +178,9 @@ struct xgpu_render_target
targets when the game draws at the display's resolution (d3d8_gl.c) */
float scale[2];
unsigned long gl_width, gl_height;
/* with multisampling, the multisampled renderbuffer draws go to, its
samples a pixel (0 when it has none), and whether it has been drawn into
since the texture last had its pixels (d3d8_gl.c,
render_target_multisample) */
GLuint multisample;
int samples;
BOOL unresolved;
};
/* the GL texture holding a render target with this physical address, or 0 */
struct xgpu_render_target *xgpu_render_target_find(unsigned long data);
/* ---------- anti-aliasing
display.anti_aliasing's passes (xgpu_post.c): FXAA or SMAA antialias each
window's 3D view in place before the HUD and menus are drawn over it, so
that their text stays sharp. Supersampling and multisampling are the
device's (d3d8_gl.c). */
/* the programs and textures of FXAA, or of SMAA, made now; FALSE if they
cannot be (once FALSE, it stays so) */
BOOL xgpu_post_prepare(BOOL smaa);
/* FXAA, or SMAA, on the corners x0, y0 to x1, y1 (from row 0) of a render
target's framebuffer, width by height, GL_RGBA8; FALSE if its programs do
not build */
BOOL xgpu_post_anti_alias(BOOL smaa, GLuint framebuffer, unsigned long width, unsigned long height,
const GLint corners[4]);
#endif
-524
View File
@@ -1,524 +0,0 @@
/*
XGPU_POST.C
The post-process passes of display.anti_aliasing (xgpu.h): FXAA and SMAA,
run on a render target in place, over the rectangle of a window's 3D view.
Each pass draws one triangle covering the rectangle (its corners from
gl_VertexID, with no vertex data) and finds its texels from gl_FragCoord: a
target's rows are the picture's from the top, as the passes' own textures'
are, so no coordinate is turned over. The target's pixels are copied first,
so that no pass reads what it writes, and only colour is written back: the
game keeps values of its own in destination alpha. The programs are built
when the setting is chosen (xgpu_post_prepare), not in the middle of a frame.
FXAA is written here, after Timothy Lottes' description: the direction of an
edge from the luma around a pixel, the edge's ends found by stepping along
it, and the pixel blended across it by where it lies between them, or by how
much it stands out from its neighbours. SMAA is port/third_party/smaa's,
compiled as GLSL at its HIGH preset: edges from luma, blending weights from
its lookup textures, then the blend. Android has FXAA only (d3d8_gl.c's
anti_aliasing_values).
*/
#include "xgpu.h"
#include <stdlib.h>
#ifndef HALO_ANDROID
#include "memory/zlib/zlib.h"
/* port/third_party/smaa, embedded by tools/embed_assets.py: the shader's
text (ending in a NUL), and the lookup textures' bytes compressed with
zlib; each size 0 where the build had no such file */
extern const unsigned int xgpu_smaa_shader[];
extern const unsigned long xgpu_smaa_shader_size;
extern const unsigned int xgpu_smaa_area_texture[];
extern const unsigned long xgpu_smaa_area_texture_size;
extern const unsigned int xgpu_smaa_search_texture[];
extern const unsigned long xgpu_smaa_search_texture_size;
#endif
enum
{
_post_program_fxaa,
_post_program_smaa_edges,
_post_program_smaa_weights,
_post_program_smaa_blend,
NUMBER_OF_POST_PROGRAMS
};
static struct
{
GLuint vertex_shader;
GLuint programs[NUMBER_OF_POST_PROGRAMS];
/* each program's metrics uniform, and FXAA's bounds */
GLint metrics[NUMBER_OF_POST_PROGRAMS];
GLint bounds;
/* FXAA's, and SMAA's, programs or textures could not be made */
BOOL failed[2];
/* the size of the textures below */
unsigned long width, height;
/* the target's pixels before the pass, and SMAA's edges and blending
weights */
GLuint color, edges, weights;
GLuint color_framebuffer, edges_framebuffer, weights_framebuffer;
/* SMAA's lookup textures */
GLuint area, search;
/* linear and clamped, as FXAA and SMAA sample */
GLuint sampler;
/* no attributes: the triangle comes from gl_VertexID */
GLuint vertex_array;
} post;
/* ---------- programs */
static void shader_header(struct xgpu_text *text)
{
#ifdef HALO_ANDROID
xgpu_text_append(text,
"#version %s\n"
"precision highp float;\n"
"precision highp int;\n"
"precision highp sampler2D;\n",
xgpu_capabilities.shading_language);
#else
xgpu_text_append(text, "#version 450 core\n");
#endif
}
/* one triangle over the whole viewport */
static const char vertex_source[] =
"void main()\n"
"{\n"
"\tgl_Position = vec4(float((gl_VertexID & 1) << 2) - 1.0, float((gl_VertexID & 2) << 1) - 1.0, 0.0, 1.0);\n"
"}\n";
static const char fxaa_source[] =
"uniform sampler2D color_texture;\n"
"/* 1 / width, 1 / height, width, height */\n"
"uniform vec4 metrics;\n"
"/* the rectangle's outermost texel centres, as texture coordinates */\n"
"uniform vec4 bounds;\n"
"out vec4 result;\n"
"\n"
"const float EDGE_THRESHOLD = 0.125;\n"
"const float EDGE_THRESHOLD_MINIMUM = 0.0312;\n"
"const float SUBPIXEL_QUALITY = 0.75;\n"
"const int STEPS = 12;\n"
"const float STEP_LENGTHS[12] = float[12](1.0, 1.0, 1.0, 1.0, 1.0, 1.5, 2.0, 2.0, 2.0, 2.0, 4.0, 8.0);\n"
"\n"
"vec3 color_at(vec2 position)\n"
"{\n"
"\treturn textureLod(color_texture, clamp(position, bounds.xy, bounds.zw), 0.0).rgb;\n"
"}\n"
"\n"
"float luma_at(vec2 position)\n"
"{\n"
"\treturn dot(color_at(position), vec3(0.299, 0.587, 0.114));\n"
"}\n"
"\n"
"void main()\n"
"{\n"
"\tvec2 position = gl_FragCoord.xy * metrics.xy;\n"
"\tfloat luma = luma_at(position);\n"
"\tfloat up = luma_at(position + vec2(0.0, -metrics.y));\n"
"\tfloat down = luma_at(position + vec2(0.0, metrics.y));\n"
"\tfloat left = luma_at(position + vec2(-metrics.x, 0.0));\n"
"\tfloat right = luma_at(position + vec2(metrics.x, 0.0));\n"
"\tfloat highest = max(luma, max(max(up, down), max(left, right)));\n"
"\tfloat range = highest - min(luma, min(min(up, down), min(left, right)));\n"
"\n"
"\t/* no edge: the target has the pixel already */\n"
"\tif (range < max(EDGE_THRESHOLD_MINIMUM, highest * EDGE_THRESHOLD))\n"
"\t\tdiscard;\n"
"\tfloat up_left = luma_at(position + vec2(-metrics.x, -metrics.y));\n"
"\tfloat up_right = luma_at(position + vec2(metrics.x, -metrics.y));\n"
"\tfloat down_left = luma_at(position + vec2(-metrics.x, metrics.y));\n"
"\tfloat down_right = luma_at(position + vec2(metrics.x, metrics.y));\n"
"\n"
"\t/* how much the pixel stands out from its neighbourhood (a feature\n"
"\tthinner than a pixel) */\n"
"\tfloat average = (2.0 * (up + down + left + right) + up_left + up_right + down_left + down_right) / 12.0;\n"
"\tfloat subpixel = smoothstep(0.0, 1.0, clamp(abs(average - luma) / range, 0.0, 1.0));\n"
"\tsubpixel = subpixel * subpixel * SUBPIXEL_QUALITY;\n"
"\n"
"\t/* an edge along the rows where the luma changes more from row to row\n"
"\tthan from column to column; across it, the side it changes more to */\n"
"\tbool horizontal = abs(up_left + down_left - 2.0 * left) + 2.0 * abs(up + down - 2.0 * luma) +\n"
"\t\tabs(up_right + down_right - 2.0 * right) >=\n"
"\t\tabs(up_left + up_right - 2.0 * up) + 2.0 * abs(left + right - 2.0 * luma) +\n"
"\t\tabs(down_left + down_right - 2.0 * down);\n"
"\tfloat before = horizontal ? up : left;\n"
"\tfloat after = horizontal ? down : right;\n"
"\tbool before_steeper = abs(before - luma) >= abs(after - luma);\n"
"\tfloat gradient = 0.25 * max(abs(before - luma), abs(after - luma));\n"
"\tfloat edge_luma = 0.5 * ((before_steeper ? before : after) + luma);\n"
"\tvec2 across = (horizontal ? vec2(0.0, metrics.y) : vec2(metrics.x, 0.0)) * (before_steeper ? -1.0 : 1.0);\n"
"\tvec2 along = horizontal ? vec2(metrics.x, 0.0) : vec2(0.0, metrics.y);\n"
"\n"
"\t/* along the edge, half a pixel across, to where its luma ends either way */\n"
"\tvec2 end_before = position + 0.5 * across - along;\n"
"\tvec2 end_after = position + 0.5 * across + along;\n"
"\tfloat luma_before = luma_at(end_before) - edge_luma;\n"
"\tfloat luma_after = luma_at(end_after) - edge_luma;\n"
"\tbool reached_before = abs(luma_before) >= gradient;\n"
"\tbool reached_after = abs(luma_after) >= gradient;\n"
"\tfor (int index = 1; index < STEPS && !(reached_before && reached_after); index++)\n"
"\t{\n"
"\t\tif (!reached_before)\n"
"\t\t{\n"
"\t\t\tend_before -= along * STEP_LENGTHS[index];\n"
"\t\t\tluma_before = luma_at(end_before) - edge_luma;\n"
"\t\t\treached_before = abs(luma_before) >= gradient;\n"
"\t\t}\n"
"\t\tif (!reached_after)\n"
"\t\t{\n"
"\t\t\tend_after += along * STEP_LENGTHS[index];\n"
"\t\t\tluma_after = luma_at(end_after) - edge_luma;\n"
"\t\t\treached_after = abs(luma_after) >= gradient;\n"
"\t\t}\n"
"\t}\n"
"\n"
"\t/* blended across the edge the more, the nearer the pixel is to an end;\n"
"\tonly where the luma at that end changes the way the pixel's does */\n"
"\tfloat distance_before = horizontal ? position.x - end_before.x : position.y - end_before.y;\n"
"\tfloat distance_after = horizontal ? end_after.x - position.x : end_after.y - position.y;\n"
"\tfloat pixel_offset = 0.5 - min(distance_before, distance_after) / (distance_before + distance_after);\n"
"\tbool varies = ((distance_before < distance_after ? luma_before : luma_after) < 0.0) != (luma < edge_luma);\n"
"\tresult = vec4(color_at(position + max(varies ? pixel_offset : 0.0, subpixel) * across), 1.0);\n"
"}\n";
#ifndef HALO_ANDROID
/* the main functions of SMAA's passes, after SMAA.hlsl; its vertex
functions' offsets are found for each pixel */
static const char *const smaa_mains[] =
{
/* edges */
"uniform sampler2D color_texture;\n"
"out vec4 result;\n"
"\n"
"void main()\n"
"{\n"
"\tvec2 texcoord = gl_FragCoord.xy * metrics.xy;\n"
"\tvec4 offset[3];\n"
"\tSMAAEdgeDetectionVS(texcoord, offset);\n"
"\tresult = vec4(SMAALumaEdgeDetectionPS(texcoord, offset, color_texture), 0.0, 0.0);\n"
"}\n",
/* weights */
"uniform sampler2D edges_texture;\n"
"uniform sampler2D area_texture;\n"
"uniform sampler2D search_texture;\n"
"out vec4 result;\n"
"\n"
"void main()\n"
"{\n"
"\tvec2 texcoord = gl_FragCoord.xy * metrics.xy;\n"
"\tvec2 pixcoord;\n"
"\tvec4 offset[3];\n"
"\tSMAABlendingWeightCalculationVS(texcoord, pixcoord, offset);\n"
"\tresult = SMAABlendingWeightCalculationPS(texcoord, pixcoord, offset, edges_texture, area_texture,\n"
"\t\tsearch_texture, vec4(0.0));\n"
"}\n",
/* blend */
"uniform sampler2D color_texture;\n"
"uniform sampler2D weights_texture;\n"
"out vec4 result;\n"
"\n"
"void main()\n"
"{\n"
"\tvec2 texcoord = gl_FragCoord.xy * metrics.xy;\n"
"\tvec4 offset;\n"
"\tSMAANeighborhoodBlendingVS(texcoord, offset);\n"
"\tresult = SMAANeighborhoodBlendingPS(texcoord, offset, color_texture, weights_texture);\n"
"}\n",
};
#endif
/* a program, its samplers given their texture units; 0 if it does not
build */
static GLuint program_build(int which)
{
static const char *const names[NUMBER_OF_POST_PROGRAMS] =
{
"FXAA", "SMAA edge detection", "SMAA blending weight", "SMAA neighborhood blending"
};
static const struct
{
const char *name;
GLint unit;
} units[] =
{
{ "color_texture", 0 }, { "edges_texture", 0 }, { "area_texture", 1 },
{ "search_texture", 2 }, { "weights_texture", 1 },
};
struct xgpu_text text = { 0 };
GLuint fragment_shader, program;
unsigned long index;
if (!post.vertex_shader)
{
shader_header(&text);
xgpu_text_append(&text, "%s", vertex_source);
post.vertex_shader = xgpu_compile_shader(GL_VERTEX_SHADER, text.buffer, "post-process");
text.length = 0;
}
shader_header(&text);
if (which == _post_program_fxaa)
{
xgpu_text_append(&text, "%s", fxaa_source);
}
#ifndef HALO_ANDROID
else if (xgpu_smaa_shader_size)
{
xgpu_text_append(&text,
"#define SMAA_GLSL_4 1\n"
"uniform vec4 metrics;\n"
"#define SMAA_RT_METRICS metrics\n"
"#define SMAA_PRESET_HIGH 1\n"
"%s\n%s",
(const char *)xgpu_smaa_shader, smaa_mains[which - _post_program_smaa_edges]);
}
#endif
else
{
platform_log("anti-aliasing: the build has no SMAA");
free(text.buffer);
return 0;
}
fragment_shader = post.vertex_shader ?
xgpu_compile_shader(GL_FRAGMENT_SHADER, text.buffer, names[which]) :
0;
free(text.buffer);
if (!fragment_shader)
return 0;
program = xgpu_link_program(post.vertex_shader, fragment_shader, names[which]);
glDeleteShader(fragment_shader);
if (!program)
return 0;
glUseProgram(program);
/* (a name the program does not have is location -1, which is ignored) */
for (index = 0; index < sizeof(units) / sizeof(units[0]); index++)
glUniform1i(glGetUniformLocation(program, units[index].name), units[index].unit);
post.metrics[which] = glGetUniformLocation(program, "metrics");
if (which == _post_program_fxaa)
post.bounds = glGetUniformLocation(program, "bounds");
return program;
}
/* ---------- textures */
static GLuint texture_new(GLenum format, unsigned long width, unsigned long height, GLenum data_format,
const void *data)
{
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, (GLsizei)width, (GLsizei)height, 0, data_format,
GL_UNSIGNED_BYTE, data);
return texture;
}
static GLuint framebuffer_new(GLuint texture)
{
GLuint framebuffer;
GLenum draw_buffer = GL_COLOR_ATTACHMENT0;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
glDrawBuffers(1, &draw_buffer);
return framebuffer;
}
/* the passes' textures, of the target's size (SMAA's only for SMAA); color
is the target's format, so that the copy is exact */
static void textures_fit(BOOL smaa, unsigned long width, unsigned long height)
{
if (post.width != width || post.height != height)
{
GLuint textures[3] = { post.color, post.edges, post.weights };
GLuint framebuffers[3] = { post.color_framebuffer, post.edges_framebuffer, post.weights_framebuffer };
glDeleteFramebuffers(3, framebuffers);
glDeleteTextures(3, textures);
post.color = post.edges = post.weights = 0;
post.width = width;
post.height = height;
post.color = texture_new(GL_RGBA8, width, height, GL_RGBA, NULL);
post.color_framebuffer = framebuffer_new(post.color);
}
if (smaa && !post.edges)
{
post.edges = texture_new(GL_RG8, width, height, GL_RG, NULL);
post.weights = texture_new(GL_RGBA8, width, height, GL_RGBA, NULL);
post.edges_framebuffer = framebuffer_new(post.edges);
post.weights_framebuffer = framebuffer_new(post.weights);
}
}
#ifndef HALO_ANDROID
/* a lookup texture of SMAA's from its zlib stream; 0 if it does not
inflate */
static GLuint lookup_texture(const unsigned int *stream, unsigned long stream_size, GLenum format,
GLenum data_format, unsigned long width, unsigned long height, unsigned long texel_size)
{
uLongf size = (uLongf)(width * height * texel_size);
unsigned char *texels = malloc(size);
GLuint texture = 0;
int result;
if (!texels)
return 0;
result = uncompress(texels, &size, (const Bytef *)stream, (uLong)stream_size);
/* (the game's zlib is 1.1, which can stop short of saying the stream
has ended: the size says whether it is whole) */
if ((result == Z_OK || result == Z_BUF_ERROR) && size == (uLongf)(width * height * texel_size))
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
texture = texture_new(format, width, height, data_format, texels);
}
free(texels);
return texture;
}
#endif
/* ---------- the passes */
static void texture_bind(GLuint unit, GLuint texture)
{
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(GL_TEXTURE_2D, texture);
glBindSampler(unit, post.sampler);
}
/* a program's triangle drawn into a framebuffer over the rectangle;
color_only keeps the framebuffer's alpha */
static void pass_draw(int which, GLuint framebuffer, const GLint corners[4], BOOL color_only)
{
float metrics[4];
metrics[0] = 1.0f / (float)post.width;
metrics[1] = 1.0f / (float)post.height;
metrics[2] = (float)post.width;
metrics[3] = (float)post.height;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glViewport(corners[0], corners[1], corners[2] - corners[0], corners[3] - corners[1]);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, color_only ? GL_FALSE : GL_TRUE);
glUseProgram(post.programs[which]);
glUniform4fv(post.metrics[which], 1, metrics);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
/* an intermediate emptied, all of it, so that no edge of another window's
pass is found: SMAA's passes write only where there is an edge */
static void intermediate_clear(GLuint framebuffer)
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
BOOL xgpu_post_prepare(BOOL smaa)
{
int first = smaa ? _post_program_smaa_edges : _post_program_fxaa;
int last = smaa ? _post_program_smaa_blend : _post_program_fxaa;
int which;
if (post.failed[smaa != FALSE])
return FALSE;
for (which = first; which <= last; which++)
{
if (!post.programs[which] && !(post.programs[which] = program_build(which)))
{
post.failed[smaa != FALSE] = TRUE;
return FALSE;
}
}
#ifndef HALO_ANDROID
if (smaa && !post.area)
{
post.area = lookup_texture(xgpu_smaa_area_texture, xgpu_smaa_area_texture_size, GL_RG8, GL_RG,
160, 560, 2);
post.search = lookup_texture(xgpu_smaa_search_texture, xgpu_smaa_search_texture_size, GL_R8, GL_RED,
64, 16, 1);
if (!post.area || !post.search)
{
platform_log("anti-aliasing: cannot inflate SMAA's lookup textures");
post.failed[1] = TRUE;
return FALSE;
}
}
#endif
if (!post.sampler)
{
glGenSamplers(1, &post.sampler);
glSamplerParameteri(post.sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(post.sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(post.sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(post.sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenVertexArrays(1, &post.vertex_array);
}
return TRUE;
}
BOOL xgpu_post_anti_alias(BOOL smaa, GLuint framebuffer, unsigned long width, unsigned long height,
const GLint corners[4])
{
/* (made already, as the setting was chosen) */
if (!xgpu_post_prepare(smaa))
return FALSE;
textures_fit(smaa, width, height);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glDisable(GL_BLEND);
glDisable(GL_CULL_FACE);
glDisable(GL_POLYGON_OFFSET_FILL);
#ifndef HALO_ANDROID
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
glBindVertexArray(post.vertex_array);
/* the target's pixels, read while the target is drawn into */
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, post.color_framebuffer);
glBlitFramebuffer(corners[0], corners[1], corners[2], corners[3], corners[0], corners[1], corners[2], corners[3],
GL_COLOR_BUFFER_BIT, GL_NEAREST);
if (!smaa)
{
float bounds[4];
/* (split screen: no texel of the next window is read) */
bounds[0] = ((float)corners[0] + 0.5f) / (float)width;
bounds[1] = ((float)corners[1] + 0.5f) / (float)height;
bounds[2] = ((float)corners[2] - 0.5f) / (float)width;
bounds[3] = ((float)corners[3] - 0.5f) / (float)height;
texture_bind(0, post.color);
glUseProgram(post.programs[_post_program_fxaa]);
glUniform4fv(post.bounds, 1, bounds);
pass_draw(_post_program_fxaa, framebuffer, corners, TRUE);
}
else
{
intermediate_clear(post.edges_framebuffer);
texture_bind(0, post.color);
pass_draw(_post_program_smaa_edges, post.edges_framebuffer, corners, FALSE);
intermediate_clear(post.weights_framebuffer);
texture_bind(0, post.edges);
texture_bind(1, post.area);
texture_bind(2, post.search);
pass_draw(_post_program_smaa_weights, post.weights_framebuffer, corners, FALSE);
texture_bind(0, post.color);
texture_bind(1, post.weights);
pass_draw(_post_program_smaa_blend, framebuffer, corners, TRUE);
}
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
return TRUE;
}
+9 -29
View File
@@ -840,9 +840,7 @@ In play, laid out for VR shooters rather than by position:
right trigger fire left trigger grenade: held in the hand, thrown as it lets go (vr.c)
right grip melee (B) left grip hold the weapon with both hands (vr.c)
(a hand thrust forward, vr.melee_swing_speed, is a melee attack too;
a hand tapped on the side or top of the head, vr.flashlight_gesture,
the flashlight)
(a hand thrust forward, vr.melee_swing_speed, is a melee attack too)
A jump X action, reload (X), as the game's prompts say
B flashlight (white); held, switch grenade (black)
Y switch weapon (Y)
@@ -853,19 +851,6 @@ Holding a Warthog's wheel (vr.c), the right trigger is the throttle and the
left one the brake (backwards, once stopped): the left stick pushed up or
down as far, unless it is pushed further.
Left-handed (vr.handedness), vr.c hands the controllers over with the sides
swapped where the hands' jobs are (halo_vr_controllers): the left trigger
fires and the left grip strikes, the right ones hold and throw the grenade
and grip the foregrip; and with vr.mirror_controls the sticks, bumpers and
face buttons too (the right stick moves, X jumps, A reloads; on the Steam
Frame the left D-pad takes A, B, X and Y). Here "right" and "left" are the
sides so handed over.
The VR options' BUTTONS page (vr.button_*) moves jump, the flashlight,
action, switch weapon, crouch, zoom and melee between A, B, X, Y, the
stick clicks and the weapon hand's grip: vr.c hands each action's button
over as the one above, so "A" here is the jump wherever it is.
In the menus the buttons are the Xbox's by position: A accepts, B goes back. */
#include "guest_host.h"
@@ -873,7 +858,6 @@ In the menus the buttons are the Xbox's by position: A accepts, B goes back. */
int halo_vr_controllers(struct halo_xr_hand hands[HALO_XR_HAND_COUNT], int *menus);
int halo_vr_wheel_held(void);
int halo_vr_flashlight_tap(void);
/* a click that stands for the pad's A (ui_widget.c: the screen where
controllers join by pressing it), held a moment */
@@ -945,15 +929,14 @@ static void vr_gamepad_state(XINPUT_GAMEPAD *pad)
}
merge_button(pad, XINPUT_GAMEPAD_A, (right->buttons & HALO_XR_BUTTON_A) != 0);
merge_button(pad, XINPUT_GAMEPAD_X, (left->buttons & HALO_XR_BUTTON_A) != 0);
/* (the grips holding a Warthog's wheel are not a blow: vr.c) */
merge_button(pad, XINPUT_GAMEPAD_B, (right->buttons & HALO_XR_BUTTON_SQUEEZE) != 0 || halo_vr_melee());
/* (the grips holding a Warthog's wheel are not a blow) */
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);
/* (and a tap of the head, vr.flashlight_gesture: vr.c) */
merge_button(pad, XINPUT_GAMEPAD_WHITE,
(right->buttons & HALO_XR_BUTTON_BUMPER) != 0 || halo_vr_flashlight_tap());
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 */
@@ -1055,15 +1038,12 @@ DWORD WINAPI XInputSetState(HANDLE device, PXINPUT_FEEDBACK feedback)
if (port < 0)
return ERROR_DEVICE_NOT_CONNECTED;
#ifdef HALO_ANDROID
/* VR: the heavy (left) motor in the free hand, the light one in the
weapon's (left-handed, the other way round), as the game refreshes them
every frame */
/* VR: the heavy (left) motor in the left hand, the light one in the
right, as the game refreshes them every frame */
if (port == 0 && halo_vr_active())
{
int weapon = halo_vr_weapon_hand();
host_xr_haptic(HALO_XR_HAND_COUNT - 1 - weapon, feedback->Rumble.wLeftMotorSpeed / 65535.0f, 0.1f);
host_xr_haptic(weapon, feedback->Rumble.wRightMotorSpeed / 65535.0f, 0.1f);
host_xr_haptic(HALO_XR_HAND_LEFT, feedback->Rumble.wLeftMotorSpeed / 65535.0f, 0.1f);
host_xr_haptic(HALO_XR_HAND_RIGHT, feedback->Rumble.wRightMotorSpeed / 65535.0f, 0.1f);
}
#endif
count = sdl_gamepads(gamepads);
-24
View File
@@ -1,24 +0,0 @@
Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com)
Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com)
Copyright (C) 2013 Belen Masia (bmasia@unizar.es)
Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com)
Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es)
Permission is hereby granted, free of charge, to any person obtaining a copy
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. As clarification, there is no
requirement that the copyright notice and permission be included in binary
distributions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-20
View File
@@ -1,20 +0,0 @@
# SMAA
Enhanced Subpixel Morphological Antialiasing, by Jorge Jimenez, Jose I.
Echevarria, Belen Masia, Fernando Navarro and Diego Gutierrez, MIT licensed
(see `LICENSE`).
Upstream: https://github.com/iryoku/smaa, commit
71c806a838bdd7d517df19192a20f0c61b3ca29d. `SMAA.hlsl` is copied
unchanged. `area_tex.zlib` and `search_tex.zlib` are the bytes of
`Textures/AreaTex.h` (160x560, two channels) and `Textures/SearchTex.h`
(64x16, one channel), compressed with zlib:
python -c "import re, sys, zlib; t = open(sys.argv[1]).read(); \
open(sys.argv[2], 'wb').write(zlib.compress(bytes(int(h, 16) for h in \
re.findall(r'0x([0-9a-fA-F]{2})', t[t.index('{'):])), 9))" AreaTex.h area_tex.zlib
`display.anti_aliasing = "smaa"` antialiases the 3D view with it, compiled
as GLSL (`port/linux/src/xgpu_post.c`); the files are embedded in the Linux
and Windows platform layers by `tools/embed_assets.py`. Android has FXAA in
its place.
-1361
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.
-7
View File
@@ -2082,13 +2082,6 @@ boolean ai_enemies_attacking_player(
return ai_enemies_endanger_player(TRUE, NONE);
}
/* port: whether enemies can see this player's unit */
boolean ai_port_enemies_can_see_unit(
long unit_index)
{
return ai_enemies_endanger_player(FALSE, unit_index);
}
/* port: whether enemies are attacking this player's unit */
boolean ai_port_enemies_attacking_unit(
long unit_index)
-3
View File
@@ -143,9 +143,6 @@ boolean ai_consider_major_upgrade(
real chance);
/* port: network co-op's respawn (players.c) */
/* port: whether enemies can see this player's unit */
boolean ai_port_enemies_can_see_unit(
long unit_index);
boolean ai_port_enemies_attacking_unit(
long unit_index);
boolean ai_enemies_attacking_player(
+1 -2
View File
@@ -3255,8 +3255,7 @@ void ai_scripting_attach_unit(
squad_index,
struct squad_definition);
/* port: an entry the palette has (a map's index), as the others here */
if (VALID_INDEX(squad_definition->actor_palette_index, scenario->ai_actor_palette.count))
if (squad_definition->actor_palette_index != NONE)
{
struct tag_reference *actor_palette_entry = TAG_BLOCK_GET_ELEMENT(
&scenario->ai_actor_palette,
+9 -52
View File
@@ -3006,9 +3006,9 @@ static void encounter_post_combat(
return;
}
/* port: extra_number, counting from 1, places one of network co-op's extra
enemies (coop_enemies.c), spread around the squad's starting locations in
turn; 0 places the squad's own actor, as on the Xbox */
/* port: extra_number, from 1, places one of network co-op's extra enemies
(coop_enemies.c): at the squad's starting locations in turn, each the
place a ring about its own (0: the squad's own actor, as the Xbox's) */
static boolean encounter_place_actor(
long encounter_index,
short squad_index,
@@ -3033,59 +3033,16 @@ static boolean encounter_place_actor(
short actor_palette_index = squad_definition->actor_palette_index;
struct scenario *scenario = global_scenario_get();
/* port: an extra enemy goes on free ground around its starting
location, else around the squad's others in turn */
/* port: an extra enemy a ring about the starting location */
if (extra_number > 0)
{
/* (what was found taken around the starting locations while this
squad's extra enemies are placed, not tried again for the rest of
them: placing actors only takes room, and each enemy would try
every taken place again, and every place of a full location) */
static struct
spread_location = *starting_location;
if (coop_enemies_spread_position(&starting_location->position,
(short)((extra_number - 1) / squad_definition->starting_locations.count + 1),
&spread_location.position))
{
long encounter_index;
long time;
short squad_index;
unsigned long full;
long taken[32][BIT_VECTOR_SIZE_IN_LONGS(COOP_ENEMIES_SPREAD_SPOTS)];
} full_locations = { NONE, NONE, NONE, 0 };
short count = squad_definition->starting_locations.count;
short tried;
if (extra_number == 1 || full_locations.encounter_index != encounter_index ||
full_locations.squad_index != squad_index || full_locations.time != game_time_get())
{
full_locations.encounter_index = encounter_index;
full_locations.squad_index = squad_index;
full_locations.time = game_time_get();
full_locations.full = 0;
csmemset(full_locations.taken, 0, sizeof(full_locations.taken));
starting_location = &spread_location;
}
for (tried = 0; tried < count; tried++)
{
short candidate_index = (short)((starting_location_index + tried) % count);
struct actor_starting_location *candidate = TAG_BLOCK_GET_ELEMENT(&squad_definition->starting_locations,
candidate_index, struct actor_starting_location);
if (candidate_index < 32 && TEST_FLAG(full_locations.full, candidate_index))
continue;
spread_location = *candidate;
if (coop_enemies_spread_position(&candidate->position, extra_number,
candidate_index < 32 ? full_locations.taken[candidate_index] : NULL, &spread_location.position))
break;
if (candidate_index < 32)
SET_FLAG(full_locations.full, candidate_index, TRUE);
}
/* (with no room anywhere, as extra enemies were placed before:
on rings around the squad's starting location in turn, else on
it) */
if (tried == count)
{
spread_location = *starting_location;
coop_enemies_fallback_position(&starting_location->position,
(short)((extra_number - 1) / MAX(count, 1) + 1), &spread_location.position);
}
starting_location = &spread_location;
}
if (starting_location->actor_variant_index != NONE)
+2 -268
View File
@@ -137,16 +137,6 @@ symbols in this file:
/* ---------- constants */
enum
{
/* port: a vertex or index buffer in a cache file (a D3DResource: Common,
Data, Lock), as cache_files_disable_writes counts them */
CACHE_FILE_BUFFER_SIZE = 12,
/* port: what cache_file_read rounds a read's size up to a multiple of
(cache_files_windows.c) */
CACHE_FILE_SECTOR_SIZE = 512,
};
/* ---------- macros */
#define STRUCTURE_BSP_TAG 'sbsp'
@@ -231,18 +221,6 @@ typedef char verify_cache_file_header_size[
static struct cache_file_tag_instance *cache_get_tag_instance(
long tag_index);
static boolean cache_file_region_contains(
void const *region,
unsigned long region_size,
void const *address,
long count,
long element_size);
static boolean cache_file_tag_header_verify(
struct cache_file_tag_header *tag_header,
long tag_data_size,
char const *scenario_name);
static boolean cache_file_structure_bsp_reference_verify(
struct scenario_structure_bsp_reference *reference);
/* ---------- globals */
@@ -292,156 +270,6 @@ static struct cache_file_tag_instance *cache_get_tag_instance(
return tag_instance;
}
/* port: whether count elements of element_size bytes at address all lie in
the region_size bytes at region (no elements always do): a map's pointers
and counts are checked so before anything follows them */
static boolean cache_file_region_contains(
void const *region,
unsigned long region_size,
void const *address,
long count,
long element_size)
{
unsigned long offset = (unsigned long)address - (unsigned long)region;
if (count == 0)
return TRUE;
return count > 0 &&
(unsigned long)address >= (unsigned long)region &&
offset <= region_size &&
(unsigned long)count <= (region_size - offset) / (unsigned long)element_size;
}
/* port: whether the tag header of the tags just read (tag_data_size bytes
at the tag cache's base) can be trusted, as everything after trusts it:
its tag table and vertex and index buffers lie in the tag data, its tags'
count fits a tag index's absolute index, and it names a scenario tag */
static boolean cache_file_tag_header_verify(
struct cache_file_tag_header *tag_header,
long tag_data_size,
char const *scenario_name)
{
char const *problem = NULL;
if (tag_header->signature != CACHE_FILE_TAG_HEADER_SIGNATURE)
{
problem = "signature";
}
else if (tag_header->tag_count <= 0 || tag_header->tag_count > UNSIGNED_SHORT_MAX)
{
problem = "tag count";
}
else if (!cache_file_region_contains(
tag_header,
tag_data_size,
tag_header->tag_instances,
tag_header->tag_count,
sizeof(struct cache_file_tag_instance)))
{
problem = "tag table";
}
else if (!cache_file_region_contains(
tag_header,
tag_data_size,
tag_header->vertex_buffers,
tag_header->vertex_buffer_count,
CACHE_FILE_BUFFER_SIZE))
{
problem = "vertex buffers";
}
else if (!cache_file_region_contains(
tag_header,
tag_data_size,
tag_header->index_buffers,
tag_header->index_buffer_count,
CACHE_FILE_BUFFER_SIZE))
{
problem = "index buffers";
}
else
{
long scenario_absolute_index = DATUM_INDEX_TO_ABSOLUTE_INDEX(tag_header->scenario_tag_index);
if (scenario_absolute_index >= tag_header->tag_count ||
tag_header->tag_instances[scenario_absolute_index].tag_index != tag_header->scenario_tag_index ||
tag_header->tag_instances[scenario_absolute_index].group_tag != SCENARIO_TAG)
{
problem = "scenario tag";
}
}
if (problem)
{
error(_error_silent, "the cache file '%s' is damaged: its tag header's %s is wrong", scenario_name, problem);
return FALSE;
}
return TRUE;
}
/* port: whether a structure bsp reference (the scenario's) may be loaded:
its bytes lie in the map and fit the tag cache after the tag data, where
they are read to (rounded up to whole sectors, as the read is), and it
names a structure bsp tag */
static boolean cache_file_structure_bsp_reference_verify(
struct scenario_structure_bsp_reference *reference)
{
byte *tag_cache_base_address = physical_memory_get_tag_cache_base_address();
long tag_data_size = cache_file_globals.header.tag_data_size;
long absolute_index = DATUM_INDEX_TO_ABSOLUTE_INDEX(reference->structure_bsp.index);
long read_size;
if (reference->file_offset < 0 ||
reference->file_size < (long)sizeof(struct cache_file_structure_bsp_header) ||
reference->file_size > TAG_CACHE_SIZE ||
reference->file_offset > cache_file_globals.header.file_length - reference->file_size)
{
error(
_error_silent,
"a structure bsp is damaged: %08x bytes at %08x, in %08x bytes",
reference->file_size,
reference->file_offset,
cache_file_globals.header.file_length);
return FALSE;
}
read_size = (reference->file_size + CACHE_FILE_SECTOR_SIZE - 1) & ~(CACHE_FILE_SECTOR_SIZE - 1);
if (!cache_file_region_contains(
tag_cache_base_address + tag_data_size,
TAG_CACHE_SIZE - tag_data_size,
reference->base_address,
read_size,
1))
{
error(
_error_silent,
"a structure bsp is damaged: its %08x bytes at %08x would load to %08lx, outside the tag cache",
reference->file_size,
reference->file_offset,
(unsigned long)reference->base_address);
return FALSE;
}
if (reference->structure_bsp.index == NONE ||
absolute_index >= cache_file_globals.tag_header->tag_count ||
global_tag_instances[absolute_index].tag_index != reference->structure_bsp.index ||
global_tag_instances[absolute_index].group_tag != STRUCTURE_BSP_TAG)
{
error(
_error_silent,
"a structure bsp is damaged: %08x is not a structure bsp tag",
reference->structure_bsp.index);
return FALSE;
}
return TRUE;
}
/* ---------- public code */
char const *cache_files_map_directory(
@@ -616,9 +444,7 @@ long tag_loaded(
long group_tag,
char const *name)
{
/* port: a long, as the tags' count is (a short wrapped on a count past
0x7FFF, and the walk never ended) */
long absolute_index;
short absolute_index;
long result = NONE;
if (cache_file_globals.tags_loaded)
@@ -825,27 +651,6 @@ boolean cache_file_header_verify(
return FALSE;
}
/* port: the map holds at least its header, and its tag data lies in it
and fits the tag cache, which it is read into whole (a size rounded up
to whole sectors still fits, the cache being whole sectors). Checked
without overflow: the offset and size are each checked first */
if (header->file_length < (long)sizeof(struct cache_file_header) ||
header->tag_data_offset < 0 ||
header->tag_data_size < 0 ||
header->tag_data_size > TAG_CACHE_SIZE ||
header->tag_data_offset > header->file_length - header->tag_data_size)
{
error(
_error_silent,
"the cache file '%s' is damaged: %08x bytes of tag data at %08x, in %08x bytes",
scenario_name,
header->tag_data_size,
header->tag_data_offset,
header->file_length);
return FALSE;
}
return TRUE;
}
@@ -1041,26 +846,6 @@ long scenario_tags_load(
SwitchToThread();
}
/* port: tags that did not all read, or whose header cannot be
trusted, are not loaded: the map is refused, as one whose header
is wrong is, and closed for the next to open */
if (read_complete != TRUE)
{
error(_error_silent, "the cache file '%s' could not be read", scenario_name);
cache_file_close();
return NONE;
}
if (!cache_file_tag_header_verify(
tag_cache_base_address,
cache_file_globals.header.tag_data_size,
scenario_name))
{
cache_file_close();
return NONE;
}
cache_file_globals.tag_header = tag_cache_base_address;
match_vassert(
"c:\\halo\\SOURCE\\cache\\cache_files.c",
@@ -1100,11 +885,6 @@ long scenario_tags_load(
}
result = cache_file_globals.tag_header->scenario_tag_index;
}
/* port: a map refused is closed for the next to open */
else
{
cache_file_close();
}
}
return result;
@@ -1115,18 +895,6 @@ boolean scenario_structure_bsp_load(
{
struct cache_file_tag_instance *tag_instance;
byte *tag_cache_base_address;
/* port: the bsp's header, once read and checked */
struct cache_file_structure_bsp_header *structure_bsp_header;
/* port: the tag data's size was checked as the map loaded
(cache_file_header_verify); the bsp's reference is the map's, and is
checked before anything is read where it says */
if (cache_file_globals.header.tag_data_size < 0 ||
cache_file_globals.header.tag_data_size > TAG_CACHE_SIZE ||
!cache_file_structure_bsp_reference_verify(reference))
{
return FALSE;
}
tag_cache_base_address = physical_memory_get_tag_cache_base_address();
csmemset(
@@ -1151,43 +919,9 @@ boolean scenario_structure_bsp_load(
sound_idle();
}
}
/* port: a bsp that did not all read, or whose header's pointers
leave what was read, is not loaded */
structure_bsp_header = reference->base_address;
if (read_complete != TRUE ||
structure_bsp_header->signature != CACHE_FILE_STRUCTURE_BSP_HEADER_SIGNATURE ||
!cache_file_region_contains(
reference->base_address,
reference->file_size,
structure_bsp_header->base_address,
1,
1) ||
!cache_file_region_contains(
reference->base_address,
reference->file_size,
structure_bsp_header->vertex_buffers,
structure_bsp_header->vertex_buffer_count,
CACHE_FILE_BUFFER_SIZE) ||
!cache_file_region_contains(
reference->base_address,
reference->file_size,
structure_bsp_header->index_buffers,
structure_bsp_header->index_buffer_count,
CACHE_FILE_BUFFER_SIZE))
{
error(
_error_silent,
"a structure bsp is damaged: its %08x bytes at %08x %s",
reference->file_size,
reference->file_offset,
read_complete != TRUE ? "could not be read" : "have a wrong header");
return FALSE;
}
}
cache_file_globals.structure_bsp_header = structure_bsp_header;
cache_file_globals.structure_bsp_header = reference->base_address;
match_assert(
"c:\\halo\\SOURCE\\cache\\cache_files.c",
0xE0,
+2 -12
View File
@@ -22,14 +22,6 @@ enum cached_map_file_precache_state
NUMBER_OF_CACHED_MAP_FILE_PRECACHE_STATES,
};
/* port: what a read's completion flag is set to when the read failed or came
up short (cache_file_read): not FALSE, so whatever waits on it stops
waiting, but not TRUE either, so a caller that checks can tell */
enum
{
_cache_file_read_failed = 2,
};
/* ---------- macros */
/* ---------- structures */
@@ -45,10 +37,8 @@ struct cache_file_structure_bsp_header;
struct tag_iterator
{
byte reserved0[4];
/* port: a long, as the tags' count is (a short wrapped on a count past
0x7FFF, and the walk never ended); the struct keeps its size */
long absolute_index;
byte reserved8[8];
short absolute_index;
byte reserved6[10];
long group_tag;
};
+2 -37
View File
@@ -755,14 +755,6 @@ static void cache_copy_initialize_file_data(
"c:\\halo\\SOURCE\\cache\\cache_files_decompress_windows.c",
964,
self->read_bytes_left>=sizeof(self->header));
/* port: a map that did not open, or is too short to hold a header, is a
bad file, not copied (the assertion goes on in a release build, and an
unopened file's size is -1, which it compared unsigned) */
if (self->source_file == INVALID_HANDLE_VALUE ||
self->read_bytes_left < (long)sizeof(self->header))
{
cache_copy_set_flag(_copy_bad_file_bit);
}
csmemset(self->overlapped_in_use_flags, 0, sizeof(self->overlapped_in_use_flags));
csmemset(self->overlapped_completed_flags, 0, sizeof(self->overlapped_completed_flags));
@@ -1618,25 +1610,6 @@ static void cache_copy_run_decompression(
if (self->write_requests_pending > 1)
decompressor_timer_stop(_decompressor_timer_zlib_during_write_file);
/* port: a stream that ends before the size the header gives the map
is a bad file: the rest of the cache file would be taken for it */
if (zlib_result == Z_STREAM_END &&
zlib_stream->total_out != (uLong)(self->header.size - sizeof(self->header)))
{
match_vassert(
"c:\\halo\\SOURCE\\cache\\cache_files_decompress_windows.c",
1248,
FALSE,
csprintf(
decompressor_globals.message,
"decompression ended after %lu of %ld bytes",
(unsigned long)zlib_stream->total_out,
self->header.size - (long)sizeof(self->header)));
cache_copy_set_flag(_copy_bad_file_bit);
break;
}
if (zlib_result == Z_OK || zlib_result == Z_STREAM_END)
{
if (!zlib_stream->avail_in)
@@ -1688,22 +1661,14 @@ static unsigned long __stdcall simple_cache_copy_thread(
cache_copy_initialize_read_buffers(self);
cache_copy_initialize_file_data(self);
/* port: nor is a bad file (cache_copy_initialize_file_data) read */
if (!cache_copy_stop_requested() && !(self->flags & ALL_COPY_FAILURE_FLAGS))
if (!cache_copy_stop_requested())
{
decompressor_timer_start(_decompressor_timer_setup);
cache_copy_initialize_read_data(self);
cache_copy_initialize_zlib(self);
decompressor_timer_stop(_decompressor_timer_setup);
/* port: a header that is not a map's is a bad file, so the copy
fails (it ended as if it had worked, and the map was precached
again, or, of no size, seemed never to end) */
if (!cache_file_header_verify(&self->header, "cache decompressed", TRUE))
{
cache_copy_set_flag(_copy_bad_file_bit);
}
else
if (cache_file_header_verify(&self->header, "cache decompressed", TRUE))
{
boolean keep_going = TRUE;
+8 -59
View File
@@ -427,9 +427,7 @@ static struct cache_file_runtime_globals cache_file_globals;
void tags_header_register_vertex_and_index_buffers(
struct cache_file_tag_header *header)
{
/* port: a long, as the buffers' counts are (a short wrapped on a count
past 0x7FFF, and the loop never ended) */
long index;
short index;
for (index = 0; index < header->vertex_buffer_count; index++)
{
@@ -452,9 +450,7 @@ void tags_header_register_vertex_and_index_buffers(
void tags_header_deregister_vertex_and_index_buffers(
struct cache_file_tag_header *header)
{
/* port: a long, as the buffers' counts are (a short wrapped on a count
past 0x7FFF, and the loop never ended) */
long index;
short index;
for (index = 0; index < header->vertex_buffer_count; index++)
{
@@ -484,9 +480,7 @@ void tags_header_deregister_vertex_and_index_buffers(
void structure_bsp_header_register_vertex_buffers(
struct cache_file_structure_bsp_header *header)
{
/* port: a long, as the buffers' counts are (a short wrapped on a count
past 0x7FFF, and the loop never ended) */
long index;
short index;
for (index = 0; index < header->vertex_buffer_count; index++)
{
@@ -510,9 +504,7 @@ void structure_bsp_header_register_vertex_buffers(
void structure_bsp_header_deregister_vertex_buffers(
struct cache_file_structure_bsp_header *header)
{
/* port: a long, as the buffers' counts are (a short wrapped on a count
past 0x7FFF, and the loop never ended) */
long index;
short index;
rasterizer_globals.current_lock_operation = _rasterizer_lock_bsp_switch;
@@ -598,28 +590,11 @@ boolean cache_files_precache_map_begin(
if (cache_file_read_header_from_dvd(cache_map_name, &header))
{
long buffer_size = cache_copy_buffer_size(copy_map);
void *buffer = texture_cache_steal_memory(buffer_size);
short map_file_index = cached_map_files_find_free_map(
header.file_length,
header.scenario_type);
void *buffer;
struct cached_map_file *map_file;
/* port: a map no cache file holds (of no type the cache files are
for, or too big for its type's) is not precached; the texture
cache's memory is taken only once one does */
if (map_file_index == NONE)
{
error(_error_silent, "no cache file can hold map '%s' (%08x bytes, type %d)",
cache_map_name, header.file_length, header.scenario_type);
if (copy_map)
{
display_error_damaged_media();
}
return FALSE;
}
buffer = texture_cache_steal_memory(buffer_size);
map_file = cached_map_file_get(map_file_index);
struct cached_map_file *map_file = cached_map_file_get(map_file_index);
memset(
&map_file->header,
@@ -782,14 +757,6 @@ boolean cache_file_open(
"c:\\halo\\SOURCE\\cache\\cache_files_windows.c",
224,
map_file_index!=NONE);
/* port: a map in no cache file is not opened (its index would be
NONE's, before the cache files) */
if (map_file_index == NONE)
{
error(_error_silent, "the map '%s' is in no cache file", scenario_name);
return FALSE;
}
memset(
cache_file_globals.requests,
0,
@@ -830,16 +797,6 @@ short cache_file_read(
"c:\\halo\\SOURCE\\cache\\cache_files_windows.c",
276,
offset>=0);
/* port: the offset and size are the map's (a texture's or sound's,
which nothing checks): a negative one is no read, failed at once (a
negative size read the file to its end over what follows the buffer) */
if (offset < 0 || size < 0)
{
error(_error_silent, "cache file read of %08x bytes at %08x refused", size, offset);
*completion_flag_reference = _cache_file_read_failed;
return NONE;
}
if (size & (CACHE_FILE_SECTOR_SIZE - 1))
{
size = (size | (CACHE_FILE_SECTOR_SIZE - 1)) + 1;
@@ -1099,13 +1056,7 @@ static void CALLBACK cache_file_read_io_completion_routine(
"c:\\halo\\SOURCE\\cache\\cache_files_windows.c",
1389,
finished_request->overlapped.hEvent);
/* port: a read that failed or came up short completes as failed: what
waits on it stops waiting, and a caller that checks (the map's tags and
bsps) sees it, instead of taking what is in its buffer for the map */
*(volatile boolean *)finished_request->overlapped.hEvent =
error_code == ERROR_SUCCESS && bytes_transferred == (unsigned long)finished_request->size ?
TRUE :
_cache_file_read_failed;
*(volatile boolean *)finished_request->overlapped.hEvent = TRUE;
finished_request->pending = FALSE;
finished_request->running = FALSE;
@@ -1271,11 +1222,9 @@ static short cached_map_files_find_free_map(
* arm's assertion failure calls system_exit, which does not return in January
* (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit).
* Source-policy approval pending (2026-09-27 audit). */
/* port: the type is the map's header's, and a release build's assertion
goes on: a type no cache file is for finds none */
default:
match_vassert("c:\\halo\\SOURCE\\cache\\cache_files_windows.c", 1172, FALSE, NULL);
return NONE;
break;
}
for (map_file_index = first_map_file_index;
+1
View File
@@ -46,6 +46,7 @@ symbols in this file:
#define GAME_STATE_SIZE HALO_PORT_GAME_STATE_SIZE
#define GAME_STATE_VERIFY_SIZE HALO_PORT_GAME_STATE_CPU_SIZE
#define TAG_CACHE_BASE_ADDRESS 0x803A6000
#define TAG_CACHE_SIZE 0x1600000
#define TEXTURE_CACHE_SIZE 0x1600000
#define SOUND_CACHE_SIZE 0x400000
-4
View File
@@ -10,10 +10,6 @@ header included in hcex build.
/* ---------- constants */
/* port: the tag cache's size, which the loader checks a map's tag data and
structure bsps against (cache_files.c) */
#define TAG_CACHE_SIZE 0x1600000
/* ---------- macros */
/* ---------- structures */
+1 -5
View File
@@ -277,11 +277,6 @@ void error(
va_end(argument_list);
csstrcat(string, "\r\n");
/* (port: the file first: printing used to halt on a long line
while this call still held the lock, and the reason never reached
debug.txt) */
write_to_error_file(string, TRUE);
/* (port: on screen as config.toml's game.console_log says: an
assert that stops the game, and what a command someone typed
logs (its answer), always; the rest, the game's chatter.
@@ -292,6 +287,7 @@ void error(
{
terminal_printf(global_real_argb_white, "%s", string);
}
write_to_error_file(string, TRUE);
new_size = csstrlen(string);
if (error_globals.message_buffer_size+new_size >= ERROR_MESSAGE_BUFFER_MAXIMUM_SIZE)
+2 -13
View File
@@ -1189,18 +1189,10 @@ void profile_render_end(
return;
}
/* port: the texture timer is read only by the profile graph and the frame
dumps; a texture set per draw, it is not timed while nothing reads it */
#define PROFILE_TEXTURE_TIMED() \
(profile_global_enable || profile_graph || profile_dump_frames || profile_dump_lost_frames)
void profile_texture_start(
void)
{
if (PROFILE_TEXTURE_TIMED())
{
profile_timesection_begin_now(&profile_globals.current_frame.texture);
}
profile_timesection_begin_now(&profile_globals.current_frame.texture);
return;
}
@@ -1208,10 +1200,7 @@ void profile_texture_start(
void profile_texture_end(
void)
{
if (PROFILE_TEXTURE_TIMED())
{
profile_timesection_end_now(&profile_globals.current_frame.texture);
}
profile_timesection_end_now(&profile_globals.current_frame.texture);
return;
}
+2 -2
View File
@@ -577,10 +577,10 @@ long player_aim_projectile(
if (halo_vr_active() && player->local_player_index==0)
{
struct unit_datum *vr_unit= unit_get(player->unit_index);
float vr_eyes[3], vr_neck[3], vr_heading[3];
float vr_eyes[3], vr_heading[3];
if (vr_unit->object.parent_object_index!=NONE &&
halo_vr_body_frame(player->local_player_index, vr_eyes, vr_neck, vr_heading))
halo_vr_body_frame(player->local_player_index, vr_eyes, vr_heading))
{
/* VR, seated: as the game aims a vehicle's gun, at what lies
along the player's aim, from the eyes (vr.c draws the crosshair
-10
View File
@@ -118,10 +118,6 @@ void cheats_dispose_from_old_map(
/* network_game_globals.c's */
boolean network_game_distributed_client(void);
#ifdef HALO_ANDROID
/* port/linux/game/vr_graphics.c */
boolean vr_graphics_reduces(boolean const *game_switch);
#endif
/* bipeds.c's, player_control.c's */
extern boolean rider_ejection;
extern boolean player_autoaim_flag;
@@ -159,12 +155,6 @@ static boolean cheats_network_client_rasterizer_enforce(
environment_part <= &rasterizer_debug_options.draw_environment_fog_screen;
environment_part++)
{
#ifdef HALO_ANDROID
/* (VR: a part the graphics preset keeps off, as it may in another's
game: shadows, specular light, reflections; vr_graphics.c) */
if (vr_graphics_reduces(environment_part))
continue;
#endif
if (!*environment_part)
changed = TRUE;
*environment_part = TRUE;
+22 -96
View File
@@ -479,10 +479,6 @@ static struct
long bsp_switch_time;
/* the structure BSPs the players have been in this map, by bit */
word visited_structure_bsps;
/* when each local player was last told they wait for the team to go
back, and the count they were told (players_coop_show_backtrack_wait) */
long backtrack_message_times[MAXIMUM_LOCAL_PLAYERS];
short backtrack_message_counts[MAXIMUM_LOCAL_PLAYERS];
/* where each player last stood on the ground, and on which BSP: where
everyone comes back when the last checkpoint was on another one */
short ground_structure_bsp_index;
@@ -1314,16 +1310,6 @@ long find_best_starting_location_index(
}
}
/* port: a multiplayer map played alone (New Game's MULTIPLAYER maps:
no game engine) has no starting location for no game type, every one
being for its game types: the player starts at any of them, not at none
(outside the map, with no pause menu) */
if (best_starting_location_index == NONE && !game_engine_running() && starting_location_count > 0)
{
best_starting_location_index = (short)PIN(
(short)(real_random_range(0.0f, 1.0f) * starting_location_count), 0, starting_location_count - 1);
}
return best_starting_location_index;
}
@@ -2795,13 +2781,9 @@ waits for every machine to have loaded it */
#define COOP_BSP_SWITCH_WAIT_TICKS (10 * TICKS_PER_SECOND)
/* Going back to a BSP the team has already been in needs two thirds of the
living players with the player on the trigger: inside it, or this near
them (world units, about 45 metres). Wide enough for a team walking through
a doorway in a line, not for one player who ran back alone. */
#define COOP_BACKTRACK_GATHER_DISTANCE 15.0f
/* how often a player waiting for the team to go back is reminded */
#define COOP_BACKTRACK_MESSAGE_TICKS (3 * TICKS_PER_SECOND)
living players at the trigger: inside it, or this near the player in it
(world units) */
#define COOP_BACKTRACK_GATHER_DISTANCE 4.0f
/* Co-op host, each tick: a player stranded for COOP_STRANDED_TICKS is moved
beside a grounded teammate inside the BSP, else any teammate inside it, else
@@ -2988,9 +2970,9 @@ void players_respawn_at_checkpoint(
return;
}
/* port: the trigger that switches away from the loaded structure BSP the
unit stands in, or NONE */
static short players_coop_bsp_switch_trigger(
/* port: whether the unit stands in a trigger that switches away from the
loaded structure BSP */
static boolean players_coop_in_bsp_switch_trigger(
long unit_index)
{
struct scenario *scenario = global_scenario_get();
@@ -3004,11 +2986,11 @@ static short players_coop_bsp_switch_trigger(
if (volume->source_structure_bsp_index == global_structure_bsp_index &&
scenario_trigger_volume_test_object(volume->trigger_volume_index, unit_index))
{
return index;
return TRUE;
}
}
return NONE;
return FALSE;
}
/* port: whether a network co-op host lets any trigger switch the BSP yet.
@@ -3037,16 +3019,14 @@ static boolean players_coop_bsp_switch_ready(
return TRUE;
}
/* port: whether this trigger leads back to a BSP the team has been in and
the unit on it must wait for more of the team: two thirds of the living
players must be with it (in the trigger, or within
COOP_BACKTRACK_GATHER_DISTANCE), so one player can't drag everyone back
through the level. Also how many are there and how many it needs. */
static boolean players_coop_backtrack_wait(
/* port: whether a network co-op host lets the unit on this trigger switch
the BSP, which brings the whole team along. Not while the unit's machine
is still loading the BSP the host has. Anyone can lead the team into a BSP
it hasn't been in; going back to one needs two thirds of the team there,
so one player can't drag everyone back through the level. */
static boolean players_coop_bsp_switch_allowed(
short bsp_switch_trigger_volume_index,
long unit_index,
short *gathered_count,
short *needed_count)
long unit_index)
{
struct scenario_bsp_switch_trigger_volume *volume = TAG_BLOCK_GET_ELEMENT(
&global_scenario_get()->bsp_switch_trigger_volumes, bsp_switch_trigger_volume_index,
@@ -3055,13 +3035,14 @@ static boolean players_coop_backtrack_wait(
struct data_iterator iterator;
struct player_datum *player;
short living_count = 0;
short gathered_count = 0;
*gathered_count = 0;
*needed_count = 0;
if (!network_coop_player_has_structure_bsp(unit_get(unit_index)->unit.player_index))
return FALSE;
if (!VALID_INDEX(volume->destination_structure_bsp_index, MAXIMUM_STRUCTURE_BSPS_PER_SCENARIO) ||
!TEST_FLAG(players_coop_state.visited_structure_bsps, volume->destination_structure_bsp_index))
{
return FALSE;
return TRUE;
}
data_iterator_new(&iterator, player_data);
while ((player = data_iterator_next(&iterator)) != NULL)
@@ -3075,64 +3056,11 @@ static boolean players_coop_backtrack_wait(
distance_squared3d(center, &teammate->object.bounding_sphere_center) <=
COOP_BACKTRACK_GATHER_DISTANCE * COOP_BACKTRACK_GATHER_DISTANCE)
{
(*gathered_count)++;
gathered_count++;
}
}
/* (two thirds, rounded up) */
*needed_count = (short)((living_count * 2 + 2) / 3);
return *gathered_count < *needed_count;
}
/* port: whether a network co-op host lets the unit on this trigger switch
the BSP, which brings the whole team along: not while the unit's machine is
still loading the host's BSP, nor back to one the team has been in until
enough of it is there (players_coop_backtrack_wait) */
static boolean players_coop_bsp_switch_allowed(
short bsp_switch_trigger_volume_index,
long unit_index)
{
short gathered_count;
short needed_count;
return network_coop_player_has_structure_bsp(unit_get(unit_index)->unit.player_index) &&
!players_coop_backtrack_wait(bsp_switch_trigger_volume_index, unit_index, &gathered_count, &needed_count);
}
/* port: tells each of this machine's co-op players held back on a trigger
how many of the team are there and how many it needs, at once and every
COOP_BACKTRACK_MESSAGE_TICKS while they wait. Every machine works this out
for its own players, as the host does for its decision. */
static void players_coop_show_backtrack_wait(
void)
{
short local_player_index;
for (local_player_index = 0; local_player_index < MAXIMUM_LOCAL_PLAYERS; local_player_index++)
{
long player_index = local_player_get_player_index(local_player_index);
long unit_index = player_index != NONE ? player_get(player_index)->unit_index : NONE;
short trigger_index = unit_index != NONE ? players_coop_bsp_switch_trigger(unit_index) : NONE;
long *time = &players_coop_state.backtrack_message_times[local_player_index];
short *count = &players_coop_state.backtrack_message_counts[local_player_index];
short gathered_count;
short needed_count;
wchar_t text[64];
if (trigger_index == NONE ||
!players_coop_backtrack_wait(trigger_index, unit_index, &gathered_count, &needed_count))
{
*time = 0;
continue;
}
if (*time != 0 && *count == gathered_count && game_time_get() - *time < COOP_BACKTRACK_MESSAGE_TICKS)
continue;
*time = game_time_get();
*count = gathered_count;
usnprintf(text, NUMBEROF(text), L"Waiting for your team to go back: %d of %d here",
gathered_count, needed_count);
hud_print_message(local_player_index, text);
}
return gathered_count * 3 >= living_count * 2;
}
static void player_teleport_on_bsp_switch(
@@ -3325,7 +3253,7 @@ void players_reconnect_to_structure_bsp(
struct object_datum *unit = object_try_and_get(player->unit_index);
if (unit && scenario_leaf_index_from_point(&unit->object.bounding_sphere_center) != NONE &&
players_coop_bsp_switch_trigger(player->unit_index) == NONE)
!players_coop_in_bsp_switch_trigger(player->unit_index))
{
source_unit_index = player->unit_index;
teleport_position = unit->object.position;
@@ -4705,8 +4633,6 @@ void players_update_after_game(
players_coop_state.structure_bsp_index = global_structure_bsp_index;
players_coop_state.bsp_switch_time = game_time_get();
}
if (network_coop_active())
players_coop_show_backtrack_wait();
data_iterator_new(&iterator, player_data);
while (player = data_iterator_next(&iterator))
+16 -95
View File
@@ -2787,7 +2787,6 @@ symbols in this file:
#include "ai/ai.h"
#include "ai/ai_debug.h"
#include "cache/cache_files.h"
#include "cache/physical_memory_map.h" /* port: hs_scenario_syntax_data_valid */
#include "cache/texture_cache.h"
#include "camera/camera_scripting.h"
#include "cutscene/cinematics.h"
@@ -2838,7 +2837,6 @@ symbols in this file:
#include "main/main.h"
#include "units/units.h"
#include "units/vehicles.h"
#include "coop_scripts.h" /* port: port/linux/game/coop_scripts.c */
/* ---------- constants */
@@ -3608,10 +3606,6 @@ boolean hs_scenario_merge(
struct scenario *source_scenario);
static void hs_allocate(
void);
static boolean hs_scenario_syntax_data_valid(
struct scenario const *scenario);
static void hs_scenario_scripts_disable(
struct scenario *scenario);
static boolean hs_rebuild_source(
void);
static boolean hs_compile_source(
@@ -12099,81 +12093,19 @@ boolean hs_scenario_merge(
return success;
}
/* port: the scenario's script data as the map holds it, the header of its
data array too (which hs_scenario_postprocess and the data array code go by,
the nodes' count above all): inside the tag cache, and the data array of
script nodes it has room for. The shipped maps' all are, of 19001 nodes
(their names differ: a30's is "static script node"). */
static boolean hs_scenario_syntax_data_valid(
struct scenario const *scenario)
{
long const syntax_data_size =
sizeof(struct data_array)+MAXIMUM_HS_SYNTAX_NODES_PER_SCENARIO*sizeof(struct hs_syntax_node);
byte const *tag_cache = (byte const *)physical_memory_get_tag_cache_base_address();
byte const *address = (byte const *)scenario->hs_syntax_data.address;
struct data_array const *data = (struct data_array const *)address;
if (scenario->hs_syntax_data.size != syntax_data_size ||
!tag_cache ||
address < tag_cache ||
address > tag_cache+TAG_CACHE_SIZE-syntax_data_size ||
((unsigned long)address & 3))
{
return FALSE;
}
return data->signature == 'd@t@' &&
data->maximum_count == MAXIMUM_HS_SYNTAX_NODES_PER_SCENARIO &&
data->size == sizeof(struct hs_syntax_node) &&
data->valid &&
data->count >= 0 &&
data->count <= data->maximum_count &&
data->actual_count >= 0 &&
data->actual_count <= data->count &&
data->first_free_absolute_index >= 0 &&
data->first_free_absolute_index <= data->maximum_count;
}
/* port: the scenario runs no scripts, its script data not being sound: a
cache file's blocks can't be resized (tag_block_resize), so the counts are
let go of in place, and no global is initialized or script thread started
(hs_runtime_initialize_for_new_map) against nodes that aren't there */
static void hs_scenario_scripts_disable(
struct scenario *scenario)
{
scenario->hs_scripts.count = 0;
scenario->hs_globals.count = 0;
return;
}
static void hs_allocate(
void)
{
struct scenario *scenario;
scenario = global_scenario_index != NONE ? global_scenario_get() : NULL;
/* port: as the map holds it only when it is sound */
if (scenario &&
scenario->hs_syntax_data.size ==
sizeof(struct data_array)+MAXIMUM_HS_SYNTAX_NODES_PER_SCENARIO*sizeof(struct hs_syntax_node) &&
hs_scenario_syntax_data_valid(scenario))
sizeof(struct data_array)+MAXIMUM_HS_SYNTAX_NODES_PER_SCENARIO*sizeof(struct hs_syntax_node))
{
return;
}
/* port: script data a map holds that isn't sound is the map's, in the
tag cache: it isn't freed or replaced (what the Xbox did here, freeing
tag memory), the map runs no scripts, and an array of the port's own,
made once, stands in for it */
if (scenario)
{
hs_scenario_scripts_disable(scenario);
if (hs_syntax_data && hs_syntax_data_allocated)
return;
error(0, "the scenario's script data is missing or damaged; its scripts won't run");
}
hs_syntax_data = data_new(
"script node",
MAXIMUM_HS_SYNTAX_NODES_PER_SCENARIO,
@@ -12181,7 +12113,19 @@ static void hs_allocate(
if (hs_syntax_data)
{
data_make_valid(hs_syntax_data);
hs_syntax_data_allocated = TRUE;
if (scenario)
{
match_free("c:\\halo\\SOURCE\\hs\\hs.c", 336, scenario->hs_syntax_data.address);
scenario->hs_syntax_data.address = hs_syntax_data;
scenario->hs_syntax_data.size =
sizeof(struct data_array)+MAXIMUM_HS_SYNTAX_NODES_PER_SCENARIO*sizeof(struct hs_syntax_node);
tag_data_resize(&scenario->hs_string_constants, 0x400);
tag_block_resize(&scenario->hs_scripts, 0);
}
else
{
hs_syntax_data_allocated = TRUE;
}
}
else
{
@@ -12598,17 +12542,8 @@ struct hs_external_global_definition *hs_global_external_get(
short hs_global_get_type(
short global_index)
{
/* port: a global that isn't there (a map's syntax node names it) has
no type */
if (global_index & 0x8000)
{
if ((global_index & 0x7FFF) >= hs_external_global_count)
return _hs_unparsed;
return hs_global_external_get(global_index & 0x7FFF)->type;
}
if ((global_index & 0x7FFF) >= global_scenario_get()->hs_globals.count)
return _hs_unparsed;
return TAG_BLOCK_GET_ELEMENT(
&global_scenario_get()->hs_globals,
@@ -13029,8 +12964,7 @@ static long alphabetize(
return _stricmp(*left, *right);
}
/* port: in network co-op, once any player is safe (coop_scripts.c) */
HS_EVALUATE_RETURN_BOOLEAN_NO_ARGUMENTS(game_safe_to_save_evaluate, coop_scripts_safe_to_save)
HS_EVALUATE_RETURN_BOOLEAN_NO_ARGUMENTS(game_safe_to_save_evaluate, game_safe_to_save)
HS_EVALUATE_RETURN_BOOLEAN_NO_ARGUMENTS(game_all_quiet_evaluate, game_all_quiet)
HS_EVALUATE_RETURN_BOOLEAN_NO_ARGUMENTS(game_safe_to_speak_evaluate, game_safe_to_speak)
HS_EVALUATE_RETURN_BOOLEAN_NO_ARGUMENTS(game_is_cooperative_evaluate, game_is_cooperative)
@@ -13848,8 +13782,7 @@ HS_EVALUATE_RETURN_BOOLEAN_NO_ARGUMENTS(player_control_action_test_look_relative
HS_EVALUATE_RETURN_BOOLEAN_NO_ARGUMENTS(player_control_action_test_look_relative_right_evaluate, player_control_action_test_look_relative_right)
HS_EVALUATE_RETURN_BOOLEAN_NO_ARGUMENTS(player_control_action_test_look_relative_all_directions_evaluate, player_control_action_test_look_relative_all_directions)
HS_EVALUATE_RETURN_BOOLEAN_NO_ARGUMENTS(player_control_action_test_move_relative_all_directions_evaluate, player_control_action_test_move_relative_all_directions)
/* port: in network co-op, also for the players the scripts can't name (coop_scripts.c) */
HS_EVALUATE_VOID_FROM_ARGUMENTS(player_add_equipment_evaluate, struct hs_arguments_long_word_boolean, (coop_scripts_player_add_equipment(arguments->value0, arguments->value1, arguments->value2)))
HS_EVALUATE_VOID_FROM_ARGUMENTS(player_add_equipment_evaluate, struct hs_arguments_long_word_boolean, (player_add_equipment(arguments->value0, arguments->value1, arguments->value2)))
HS_EVALUATE_VOID_FROM_ARGUMENTS(debug_player_teleport_evaluate, struct hs_arguments_short_word, (debug_player_teleport(arguments->value0, arguments->value1)))
HS_EVALUATE_VOID_STRING(main_set_map_name_evaluate, main_set_map_name)
HS_EVALUATE_VOID_STRING(main_set_multiplayer_map_name_evaluate, main_set_multiplayer_map_name)
@@ -14028,15 +13961,6 @@ boolean hs_scenario_postprocess(
scenario = global_scenario_get();
saved_syntax_data = hs_syntax_data;
hs_allocate();
/* port: script data that isn't sound isn't gone through (hs_allocate
has an empty array stand in for it, and the map runs no scripts) */
if (!hs_scenario_syntax_data_valid(scenario))
{
if (restore_syntax_data)
hs_syntax_data = saved_syntax_data;
return FALSE;
}
recompile = scenario->hs_scripts.count == 0 && scenario->hs_source_files.count>0;
hs_syntax_data = (struct data_array *)scenario->hs_syntax_data.address;
hs_syntax_data->data = (char *)hs_syntax_data+sizeof(struct data_array);
@@ -14072,9 +13996,6 @@ boolean hs_scenario_postprocess(
!tag_data_resize(&global_scenario_get()->hs_string_constants, 0x400))
{
error(0, "couldn't reset scripts.");
/* port: a cache file's can't be resized: none run against the
nodes just deleted */
hs_scenario_scripts_disable(scenario);
}
success = FALSE;
}
+1 -5
View File
@@ -2486,12 +2486,8 @@ boolean hs_compile_postprocess(
long predicate_index = hs_syntax_get(expression_index)->data;
struct hs_syntax_node *predicate;
/* port: a predicate that isn't a node (a map's) is as corrupt
as one that isn't a function name, where hs_syntax_get's NULL
was gone through */
if (predicate_index == NONE ||
!(predicate = datum_try_and_get(hs_syntax_data, predicate_index)) ||
predicate->type != _hs_function_name)
(predicate = hs_syntax_get(predicate_index))->type != _hs_function_name)
{
hs_compile_globals.error = "corrupt syntax tree (you need to recompile scripts.)";
success = FALSE;
+1 -6
View File
@@ -105,7 +105,6 @@ symbols in this file:
#include "units/units.h"
#include "object_lists.h"
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
#include "coop_scripts.h" /* port: port/linux/game/coop_scripts.c */
/* ---------- constants */
@@ -845,12 +844,10 @@ boolean hs_trigger_volume_test_objects_all(
short trigger_volume_index,
long object_list_index)
{
/* port: in network co-op, waiting for every player means waiting for
any one of them (coop_scripts.c) */
return hs_trigger_volume_test_objects(
trigger_volume_index,
object_list_index,
!coop_scripts_any_player_will_do(object_list_index));
TRUE);
}
boolean hs_trigger_volume_test_objects_any(
@@ -989,8 +986,6 @@ void hs_object_teleport(
short cutscene_flag_index)
{
hs_object_orient(object_index, cutscene_flag_index, TRUE, TRUE);
/* port: co-op players the scripts can't name go with player0 */
coop_scripts_teleport_followers(object_index);
return;
}
+8 -119
View File
@@ -315,9 +315,6 @@ enum
{
_hs_thread_in_function_call_bit = 0,
_hs_thread_sleeping_bit,
/* port: the thread's stack overflowed (hs_stack_overflow); hs_thread_main
ends it */
_hs_thread_stack_overflow_bit,
_hs_global_external_bit = 15,
};
@@ -497,9 +494,7 @@ static void hs_thread_delete(
static long hs_thread_new(
short type,
long script_index);
static boolean hs_stack_push(
long thread_index);
static void hs_stack_overflow(
static void hs_stack_push(
long thread_index);
static void *hs_stack_allocate(
long thread_index,
@@ -1309,7 +1304,7 @@ static void hs_thread_delete(
return;
}
static boolean hs_stack_push(
static void hs_stack_push(
long thread_index)
{
struct hs_thread_datum *thread = hs_thread_get(thread_index);
@@ -1320,39 +1315,10 @@ static boolean hs_stack_push(
(byte *) (new_frame+1)<thread->stack_data+HS_THREAD_STACK_SIZE,
"stack overflow.");
/* port: a frame past the thread's stack isn't pushed (it would be
written over the next thread's datum): the thread is ended instead */
if ((byte *)(new_frame+1)>=thread->stack_data+HS_THREAD_STACK_SIZE ||
TEST_FLAG(thread->flags, _hs_thread_stack_overflow_bit))
{
hs_stack_overflow(thread_index);
return FALSE;
}
new_frame->previous = thread->stack;
thread->stack = new_frame;
new_frame->size = 0;
return TRUE;
}
/* port: a script whose stack overflows is ended rather than let write past
its thread's stack: the push or allocation is refused (its caller returns),
and hs_thread_main lets the thread's frames go once the evaluation in hand
returns */
static void hs_stack_overflow(
long thread_index)
{
struct hs_thread_datum *thread = hs_thread_get(thread_index);
if (!TEST_FLAG(thread->flags, _hs_thread_stack_overflow_bit))
{
error(_error_silent, "a problem occurred while executing the script %s: stack overflow. (it was stopped)",
hs_thread_format(thread_index));
SET_FLAG(thread->flags, _hs_thread_stack_overflow_bit, TRUE);
}
return;
}
@@ -1870,10 +1836,6 @@ void hs_evaluate_begin(
match_assert("c:\\halo\\source\\hs\\hs_library_internal_runtime.h", 0x15,
function_index==_hs_function_begin);
/* port: none on a stack overflow (hs_stack_overflow) */
if (!expression_index || !result)
return;
if (initialize)
{
*expression_index = hs_syntax_get(hs_syntax_get(
@@ -2001,10 +1963,6 @@ void hs_evaluate_logical(
match_assert("c:\\halo\\source\\hs\\hs_library_internal_runtime.h", 0xcf,
function_index==_hs_function_and || function_index==_hs_function_or);
/* port: none on a stack overflow (hs_stack_overflow) */
if (!expression_index || !value || !result)
return;
if (initialize)
{
*expression_index = hs_syntax_get(hs_syntax_get(
@@ -2056,10 +2014,6 @@ void hs_evaluate_if(
match_assert("c:\\halo\\source\\hs\\hs_library_internal_runtime.h", 0x77,
function_index==_hs_function_if);
/* port: none on a stack overflow (hs_stack_overflow) */
if (!condition || !expression_index || !result)
return;
if (initialize)
{
*condition = 0;
@@ -2110,9 +2064,7 @@ void hs_evaluate_set(
short type;
long global_index;
/* port: none on a stack overflow (hs_stack_overflow) */
if (!hs_stack_allocate(thread_index, sizeof(long)))
return;
hs_stack_allocate(thread_index, sizeof(long));
type = hs_global_get_type((short)variable->data);
if (initialize)
@@ -2155,10 +2107,6 @@ void hs_evaluate_inspect(
match_assert("c:\\halo\\source\\hs\\hs_library_internal_runtime.h", 0x2bc,
function_index==_hs_function_inspect);
/* port: none on a stack overflow (hs_stack_overflow) */
if (!value)
return;
if (initialize)
{
hs_evaluate(thread_index, hs_syntax_get(hs_syntax_get(
@@ -2198,10 +2146,6 @@ void hs_evaluate_arithmetic(
real *result = hs_stack_allocate(thread_index, sizeof(real));
long result_long;
/* port: none on a stack overflow (hs_stack_overflow) */
if (!argument_index || !expression_index || !value || !result)
return;
if (initialize)
{
*argument_index = 0;
@@ -2275,10 +2219,6 @@ void hs_evaluate_object_cast_up(
function_index>=_hs_function_object_to_unit &&
function_index<=_hs_function_object_to_unit);
/* port: none on a stack overflow (hs_stack_overflow) */
if (!object_index)
return;
if (initialize)
{
hs_evaluate(thread_index, hs_syntax_get(hs_syntax_get(
@@ -2324,10 +2264,6 @@ void hs_evaluate_begin_random(
match_assert("c:\\halo\\source\\hs\\hs_library_internal_runtime.h", 0x45,
function_index==_hs_function_begin_random);
/* port: none on a stack overflow (hs_stack_overflow) */
if (!argument_count || !evaluated || !result)
return;
if (initialize)
{
long expression_index = hs_syntax_get(hs_syntax_get(
@@ -2382,10 +2318,6 @@ void hs_evaluate_debug_string(
(function_index>=_hs_function_debug_string__first) &&
(function_index<=_hs_function_debug_string__last));
/* port: none on a stack overflow (hs_stack_overflow) */
if (!expression_index || !argument_count || !arguments)
return;
if (initialize)
{
*expression_index = hs_syntax_get(hs_syntax_get(
@@ -2452,10 +2384,6 @@ void hs_evaluate_sleep_until(
match_assert("c:\\halo\\source\\hs\\hs_library_internal_runtime.h", 0x1e5,
function_index==_hs_function_sleep_until);
/* port: none on a stack overflow (hs_stack_overflow) */
if (!condition || !ticks || !timeout || !start_time || !argument_index)
return;
if (initialize)
{
*(boolean *)condition = FALSE;
@@ -2527,10 +2455,6 @@ void hs_evaluate_sleep(
match_assert("c:\\halo\\source\\hs\\hs_library_internal_runtime.h", 0x189,
function_index==_hs_function_sleep);
/* port: none on a stack overflow (hs_stack_overflow) */
if (!ticks || !script_index || !argument_index)
return;
if (initialize)
{
hs_evaluate(thread_index, hs_syntax_get(hs_syntax_get(
@@ -2600,7 +2524,6 @@ static void hs_thread_main(
{
struct hs_thread_datum *thread = hs_thread_get(thread_index);
struct hs_script *script = NULL;
long *root_result;
hs_runtime_globals.executing_thread_index = (short)thread_index;
if (thread->type==_hs_thread_type_script)
@@ -2624,10 +2547,8 @@ static void hs_thread_main(
match_assert("c:\\halo\\SOURCE\\hs\\hs_runtime.c", 0x2c3, script);
thread->stack->size = 0;
/* port: (the base frame, just emptied, has room) */
root_result = hs_stack_allocate(thread_index, sizeof(long));
if (root_result)
hs_evaluate(thread_index, script->root_expression_index, root_result);
hs_evaluate(thread_index, script->root_expression_index,
hs_stack_allocate(thread_index, sizeof(long)));
}
while (thread->stack!=(struct hs_stack_frame *)thread->stack_data &&
@@ -2656,15 +2577,6 @@ static void hs_thread_main(
{
hs_script_evaluate(expression->index, thread_index, initialize);
}
/* port: a stack overflow (hs_stack_overflow) ends the thread: its
frames are let go, and a script's thread doesn't run again */
if (TEST_FLAG(thread->flags, _hs_thread_stack_overflow_bit))
{
thread->stack = (struct hs_stack_frame *)thread->stack_data;
thread->flags = 0;
thread->sleep_until = thread->type==_hs_thread_type_script ? NONE : 0;
}
}
if (thread->stack==(struct hs_stack_frame *)thread->stack_data)
@@ -2700,10 +2612,6 @@ static void hs_script_evaluate(
struct hs_thread_datum *thread = hs_thread_get(thread_index);
long *result = hs_stack_allocate(thread_index, sizeof(long));
/* port: none on a stack overflow (hs_stack_overflow) */
if (!result)
return;
if (initialize)
hs_evaluate(thread_index, script->root_expression_index, result);
else
@@ -2728,17 +2636,6 @@ static void *hs_stack_allocate(
frame->data+frame->size+size<=thread->stack_data+HS_THREAD_STACK_SIZE,
"stack overflow.");
/* port: none past the thread's stack (hs_stack_overflow); the caller
returns on NULL */
if (size<0 ||
size>thread->stack_data+HS_THREAD_STACK_SIZE-(frame->data+frame->size) ||
TEST_FLAG(thread->flags, _hs_thread_stack_overflow_bit))
{
hs_stack_overflow(thread_index);
return NULL;
}
result = frame->data+frame->size;
frame->size += (short)size;
@@ -2779,12 +2676,9 @@ static void hs_evaluate(
else
{
thread->stack->result = destination;
/* port: not pushed on a stack overflow (hs_stack_overflow) */
if (hs_stack_push(thread_index))
{
SET_FLAG(thread->flags, _hs_thread_in_function_call_bit, TRUE);
thread->stack->expression_index = expression_index;
}
hs_stack_push(thread_index);
SET_FLAG(thread->flags, _hs_thread_in_function_call_bit, TRUE);
thread->stack->expression_index = expression_index;
}
return;
@@ -3238,11 +3132,6 @@ static long *hs_arguments_evaluate(
short *argument_index = hs_stack_allocate(thread_index, sizeof(short));
long *expression_index = hs_stack_allocate(thread_index, sizeof(long));
/* port: none on a stack overflow (hs_stack_overflow): as for arguments
still being evaluated, the caller returns on NULL */
if (!values || !argument_index || !expression_index)
return NULL;
if (initialize)
{
*argument_index = 0;
+187 -406
View File
@@ -126,7 +126,6 @@ symbols in this file:
#include "units/units.h"
#ifdef HALO_ANDROID
#include "halo_vr.h"
#include "vr_arms.h"
#endif
/* ---------- constants */
@@ -468,31 +467,10 @@ long first_person_weapon_get_local_index(
}
#ifdef HALO_ANDROID
int config_boolean(char const *name);
void model_vr_log_skin(long model_index);
/* VR: the headset's frame each local player's pose was last built for
from the controller (halo_vr_frame_serial; 0 when not), and its weapon */
static unsigned long first_person_weapon_vr_pose_serial[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS];
static long first_person_weapon_vr_pose_weapon[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS];
/* the pose was built for this frame already, from the controller, which
the eyes do not move (from the eye's own camera, each eye builds its own) */
static boolean first_person_weapon_vr_pose_kept(
short local_player_index)
{
unsigned long serial= halo_vr_frame_serial();
return serial!=0 &&
first_person_weapon_vr_pose_serial[local_player_index]==serial &&
first_person_weapon_vr_pose_weapon[local_player_index]==first_person_weapon_get(local_player_index)->weapon_index;
}
/* VR: the grenade the player holds (the free hand's trigger, units.c)
hangs from the body, which the first-person view leaves out, and with it
the grenade: drawn instead in the free hand, as it is this frame, with the
hands (which are drawn over the world, and would hide it wherever they
overlap; in the room, not mirrored) */
/* VR: the grenade the player holds (the left trigger, units.c) hangs from
the body, which the first-person view leaves out, and with it the grenade:
drawn instead in the left hand, as it is this frame, with the hands (which
are drawn over the world, and would hide it wherever they overlap) */
static void first_person_weapon_draw_held_grenade(
long unit_index,
struct render_lighting *lighting,
@@ -658,22 +636,6 @@ void first_person_weapon_draw(
weapon_definition->weapon.interface_definition.first_person_animations.index,
first_person_weapon->node_matrices,
first_person_weapon->hands_node_remapping_table);
#ifdef HALO_ANDROID
/* (vr.log_performance: once, how its skin hangs from its
bones, for the VR arms) */
{
static int log_skin= -1;
if (log_skin<0)
log_skin= config_boolean("vr.log_performance")!=0;
if (log_skin)
model_vr_log_skin(first_person_interface->hands.index);
}
#endif
#ifdef HALO_ANDROID
/* VR: the weapon alone (vr.body "off") */
if (halo_vr_body_mode()!=HALO_VR_BODY_OFF)
#endif
render_model(
first_person_interface->hands.index,
0.0f,
@@ -763,23 +725,7 @@ void first_person_weapon_render_update(
first_person_weapon_set_visibility(render.local_player_index, visible);
if (first_person_weapon->visible)
{
#ifdef HALO_ANDROID
unsigned long long timing= halo_vr_timing_start();
/* VR: posed from the controller, the pose is the same for
every eye (and the scope's view): built once a frame */
if (first_person_weapon_vr_pose_kept(render.local_player_index))
{
halo_vr_timing_end(HALO_VR_TIMING_FIRST_PERSON_KEPT, timing);
}
else
{
first_person_weapon_build_node_matrices(render.local_player_index);
halo_vr_timing_end(HALO_VR_TIMING_FIRST_PERSON, timing);
}
#else
first_person_weapon_build_node_matrices(render.local_player_index);
#endif
}
}
}
@@ -1218,132 +1164,16 @@ static void first_person_weapon_set_state(
#ifdef HALO_ANDROID
/* ---------- VR arms
The first-person skeleton hangs from the camera at the weapon hand's
controller (halo_vr_weapon_camera), so the weapon and the right hand lie
where they should; its arms, though, reach up to shoulders that float with
the weapon. Here each arm's upper arm and forearm are turned (two-bone
inverse kinematics, port/linux/game/vr_arms.c) to run from the player's
shoulders, below the neck and square to the torso, to its wrist, the
controller's roll shared along the forearm. The left wrist, unless the hand
holds the weapon's foregrip, goes to the free controller, its fingers with
it.
Left-handed (vr.handedness) all this is done in model space (halo_vr.h): a
mirror image of the room, where the left controller holds the weapon as a
right hand would; the body is mirrored into it, and the posed skeleton
mirrored back (vr_mirror_pose), so that the weapon lies in the left hand,
the model's left arm reaches to the right controller. */
The first-person skeleton hangs from the camera at the right controller
(halo_vr_weapon_camera), so the weapon and the right hand lie where they
should; its arms, though, reach up to shoulders that float with the weapon.
Here each arm's upper arm and forearm are turned (two-bone inverse
kinematics) to run from the player's shoulders, below the head, to its
wrist. The left wrist, unless the hand holds the weapon's foregrip, goes to
the left controller, its fingers with it. */
#define VR_METRES_PER_WORLD_UNIT 3.048f
/* a point or a vector across the mirror (a point on it, its normal) */
static void vr_mirror_point(
float const on[3],
float const normal[3],
float point[3])
{
float along = (point[0] - on[0]) * normal[0] + (point[1] - on[1]) * normal[1] + (point[2] - on[2]) * normal[2];
point[0] -= 2.f * along * normal[0];
point[1] -= 2.f * along * normal[1];
point[2] -= 2.f * along * normal[2];
}
static void vr_mirror_vector(
float const normal[3],
float vector[3])
{
float along = vector[0] * normal[0] + vector[1] * normal[1] + vector[2] * normal[2];
vector[0] -= 2.f * along * normal[0];
vector[1] -= 2.f * along * normal[1];
vector[2] -= 2.f * along * normal[2];
}
/* the posed skeleton across a mirror: every node mirrored, its axes too
(a left-handed frame: rasterizer_xbox.c turns its triangles' winding over).
Numeric displays drawn so, the assault rifle's ammo counter and compass,
are read the right way round by rasterizer_xbox_transparent_geometry.c. */
static void vr_mirror_nodes(
float const on[3],
float const normal[3],
real_matrix4x3 *node_matrices,
short node_count)
{
short node_index;
for (node_index = 0; node_index < node_count; node_index++)
{
real_matrix4x3 *node = &node_matrices[node_index];
real_vector3d *axes[3];
float vector[3];
short axis;
vector[0] = node->position.x;
vector[1] = node->position.y;
vector[2] = node->position.z;
vr_mirror_point(on, normal, vector);
node->position.x = vector[0];
node->position.y = vector[1];
node->position.z = vector[2];
axes[0] = &node->forward;
axes[1] = &node->left;
axes[2] = &node->up;
for (axis = 0; axis < 3; axis++)
{
vector[0] = axes[axis]->i;
vector[1] = axes[axis]->j;
vector[2] = axes[axis]->k;
vr_mirror_vector(normal, vector);
axes[axis]->i = vector[0];
axes[axis]->j = vector[1];
axes[axis]->k = vector[2];
}
}
}
/* the posed skeleton out of model space (left-handed) */
static void vr_mirror_pose(
short local_player_index,
real_matrix4x3 *node_matrices,
short node_count)
{
float on[3], normal[3];
if (halo_vr_first_person_mirror(local_player_index, on, normal))
vr_mirror_nodes(on, normal, node_matrices, node_count);
}
/* debug.mirror_first_person (a test aid, flat): the first-person weapon
mirrored across the camera's own forward and up, as the left hand's is in
VR; its numeric displays can so be checked off the headset */
int config_boolean(char const *name);
static void debug_mirror_first_person(
struct render_camera const *camera,
real_matrix4x3 *node_matrices,
short node_count)
{
static int wanted = -1;
real_vector3d left;
float on[3], normal[3];
if (wanted < 0)
wanted = config_boolean("debug.mirror_first_person") ? 1 : 0;
if (!wanted)
return;
cross_product3d(&camera->up, &camera->forward, &left);
if (normalize3d(&left) == 0.0f)
return;
on[0] = camera->position.x;
on[1] = camera->position.y;
on[2] = camera->position.z;
normal[0] = left.i;
normal[1] = left.j;
normal[2] = left.k;
vr_mirror_nodes(on, normal, node_matrices, node_count);
}
struct vr_arm_nodes
{
long graph_index;
@@ -1356,10 +1186,6 @@ struct vr_arm_nodes
short left_middle;
short left_pinky;
long barrel_logged_graph_index;
/* the fingers' joints (vr_finger_nodes) of the skeleton they were
found in: each hand's thumb, index, middle, ring, little finger */
long fingers_graph_index;
short fingers[2][5][3];
/* the whole pose in the weapon camera's frame, outside a melee attack */
boolean idle_pose_valid;
short idle_pose_count;
@@ -1367,11 +1193,7 @@ struct vr_arm_nodes
};
static struct vr_arm_nodes vr_arm_nodes = { NONE, { NONE, NONE }, { NONE, NONE }, { NONE, NONE }, NONE, NONE, NONE, NONE,
NONE, NONE };
/* the skeleton this frame, and each arm's last (its elbow's swing and its
forearm's twist run on from them) */
static struct vr_skeleton vr_arm_skeleton;
static struct vr_limb_history vr_arm_history[2];
NONE };
static void vr_vector_scale_add(
real_vector3d *out,
@@ -1405,6 +1227,69 @@ static void vr_rotate_between(
v->k = v->k * cosine + turned.k * sine + axis.k * along * (1.f - cosine);
}
/* turns v about the unit axis by angle (radians) */
static void vr_rotate_about(
real_vector3d const *axis,
real angle,
real_vector3d *v)
{
real_vector3d turned;
real sin_angle = sine(angle);
real cos_angle = cosine(angle);
real along = dot_product3d(axis, v);
cross_product3d(axis, v, &turned);
v->i = v->i * cos_angle + turned.i * sin_angle + axis->i * along * (1.f - cos_angle);
v->j = v->j * cos_angle + turned.j * sin_angle + axis->j * along * (1.f - cos_angle);
v->k = v->k * cos_angle + turned.k * sin_angle + axis->k * along * (1.f - cos_angle);
}
/* A forearm turned only to point at the wrist keeps the animation's twist,
while the hand may have rolled half a turn from it: the skin between them
collapses. Twists the forearm about its bone by most of the hand's roll
from the animation (the hand's turn from old_wrist to new_wrist, applied to
the animated forearm old_forearm), as a real forearm twists along its
length. */
static void vr_twist_forearm(
real_matrix4x3 *forearm,
real_matrix4x3 const *old_forearm,
real_matrix4x3 const *old_wrist,
real_matrix4x3 const *new_wrist,
real_point3d const *wrist_position)
{
real_vector3d const *old_axes[3];
real_vector3d const *new_axes[3];
real_vector3d bone, turned_up, up, across;
real along, angle;
short axis;
vector_from_points3d(&forearm->position, wrist_position, &bone);
if (normalize3d(&bone) < 1e-6f)
return;
/* the animated forearm's up, turned as the hand turned */
old_axes[0] = &old_wrist->forward;
old_axes[1] = &old_wrist->left;
old_axes[2] = &old_wrist->up;
new_axes[0] = &new_wrist->forward;
new_axes[1] = &new_wrist->left;
new_axes[2] = &new_wrist->up;
turned_up.i = turned_up.j = turned_up.k = 0.f;
for (axis = 0; axis < 3; axis++)
vr_vector_scale_add(&turned_up, new_axes[axis], dot_product3d(&old_forearm->up, old_axes[axis]));
/* both ups across the bone, and the angle between them about it */
up = forearm->up;
vr_vector_scale_add(&up, &bone, -dot_product3d(&up, &bone));
vr_vector_scale_add(&turned_up, &bone, -dot_product3d(&turned_up, &bone));
if (normalize3d(&up) < 1e-4f || normalize3d(&turned_up) < 1e-4f)
return;
cross_product3d(&up, &turned_up, &across);
along = dot_product3d(&up, &turned_up);
angle = arctangent(dot_product3d(&across, &bone), along) * 0.6f;
vr_rotate_about(&bone, angle, &forearm->forward);
vr_rotate_about(&bone, angle, &forearm->left);
vr_rotate_about(&bone, angle, &forearm->up);
}
/* a node turned so that the bone to its child, from old to new direction */
static void vr_turn_bone(
real_matrix4x3 *node,
@@ -1423,6 +1308,39 @@ static void vr_turn_bone(
node->position = *position;
}
/* moves the descendants of a node that moved from old to new with it */
static void vr_carry_descendants(
long graph_index,
real_matrix4x3 *node_matrices,
short node_count,
short root,
real_matrix4x3 const *old_root,
real_matrix4x3 const *new_root)
{
real_matrix4x3 inverse;
real_matrix4x3 delta;
short node_index;
matrix4x3_inverse(old_root, &inverse);
matrix4x3_multiply(new_root, &inverse, &delta);
for (node_index = 0; node_index < node_count; node_index++)
{
short ancestor = animation_graph_node_parent(graph_index, node_index);
short steps;
/* parents come first; bounded in case of a malformed graph */
for (steps = 0; ancestor > 0 && ancestor != root && steps < node_count; steps++)
ancestor = animation_graph_node_parent(graph_index, ancestor);
if (ancestor == root && node_index != root)
{
real_matrix4x3 moved;
matrix4x3_multiply(&delta, &node_matrices[node_index], &moved);
node_matrices[node_index] = moved;
}
}
}
/* the upper arm turned about the elbow to point at the shoulder, keeping
its length (the free left arm: vr_left_hand_free) */
static void vr_point_upper_arm(
@@ -1444,6 +1362,50 @@ static void vr_point_upper_arm(
vr_turn_bone(&node_matrices[upper], &old_direction, &new_direction, &position);
}
/* one arm: upper arm and forearm from the shoulder to the wrist at target */
static void vr_arm(
real_matrix4x3 *node_matrices,
short upper,
short fore,
short wrist,
real_point3d shoulder,
real_point3d const *target,
real_vector3d const *pole)
{
real_vector3d old_upper, old_fore, reach, bend, new_upper, new_fore;
real upper_length, fore_length, distance, along, height;
real_point3d elbow;
vector_from_points3d(&node_matrices[upper].position, &node_matrices[fore].position, &old_upper);
vector_from_points3d(&node_matrices[fore].position, &node_matrices[wrist].position, &old_fore);
upper_length = magnitude3d(&old_upper);
fore_length = magnitude3d(&old_fore);
vector_from_points3d(&shoulder, target, &reach);
distance = normalize3d(&reach);
if (upper_length < 1e-4f || fore_length < 1e-4f || distance < 1e-4f)
return;
/* out of reach: the shoulder follows the hand (the arm stays whole) */
if (distance > (upper_length + fore_length) * 0.999f)
{
distance = (upper_length + fore_length) * 0.999f;
shoulder = *target;
point_from_line3d(target, &reach, -distance, &shoulder);
}
along = (upper_length * upper_length - fore_length * fore_length + distance * distance) / (2.f * distance);
height = upper_length * upper_length - along * along;
height = height > 0.f ? square_root(height) : 0.f;
bend = *pole;
vr_vector_scale_add(&bend, &reach, -dot_product3d(pole, &reach));
normalize3d(&bend);
point_from_line3d(&shoulder, &reach, along, &elbow);
point_from_line3d(&elbow, &bend, height, &elbow);
vector_from_points3d(&shoulder, &elbow, &new_upper);
vector_from_points3d(&elbow, target, &new_fore);
vr_turn_bone(&node_matrices[upper], &old_upper, &new_upper, &shoulder);
vr_turn_bone(&node_matrices[fore], &old_fore, &new_fore, &elbow);
}
/* an orthonormal frame from a direction and a second one across it */
static void vr_frame_from(
real_vector3d const *direction,
@@ -1460,14 +1422,14 @@ static void vr_frame_from(
/* the free left hand: the wrist behind the controller's grip, the fingers
along the controller and the knuckles, little finger to index, up its
handle, as a hand holds it; its fingers with it. The arm is bent to it
from the shoulder afterwards (vr_solve_arm), or, rigid (vr.arm_ik off),
follows the hand as it is shaped on the foregrip. (The arm's skin hangs
from the arm's bones alone, none from the skeleton's root: 3a.0's log.) */
handle, as a hand holds it. The whole arm follows the hand as it is shaped
on the foregrip: bent to the shoulder instead (vr_arm), the arm's skin,
bound partly to the skeleton's root at the other hand, tears into ribbons
wherever the hand is far from where the animation has it. */
static void vr_left_hand_free(
struct vr_skeleton const *skeleton,
long graph_index,
real_matrix4x3 *node_matrices,
boolean rigid,
short node_count,
float const hand_position[3],
float const hand_forward[3],
float const hand_up[3])
@@ -1511,24 +1473,23 @@ static void vr_left_hand_free(
wrist->position.y = hand_position[1];
wrist->position.z = hand_position[2];
point_from_line3d(&wrist->position, &to[0], -0.07f / VR_METRES_PER_WORLD_UNIT, &wrist->position);
/* what the wrist carries (its fingers) with it, and rigid, the forearm
and the upper arm too, as they are on the foregrip */
vr_carry_descendants(graph_index, node_matrices, node_count, wrist_index, &old_wrist, wrist);
/* the forearm and the upper arm with it, as they are on the foregrip */
{
real_matrix4x3 inverse, delta, moved;
short node_index;
short chain[2];
short link;
chain[0] = vr_arm_nodes.fore[0];
chain[1] = vr_arm_nodes.upper[0];
matrix4x3_inverse(&old_wrist, &inverse);
matrix4x3_multiply(wrist, &inverse, &delta);
for (node_index = 0; node_index < skeleton->node_count; node_index++)
for (link = 0; link < 2; link++)
{
if (node_index == wrist_index ||
!(vr_skeleton_under(skeleton, node_index, wrist_index) ||
(rigid && (node_index == vr_arm_nodes.fore[0] || node_index == vr_arm_nodes.upper[0]))))
{
if (chain[link] == NONE)
continue;
}
matrix4x3_multiply(&delta, &node_matrices[node_index], &moved);
node_matrices[node_index] = moved;
matrix4x3_multiply(&delta, &node_matrices[chain[link]], &moved);
node_matrices[chain[link]] = moved;
}
}
}
@@ -1586,16 +1547,6 @@ static void vr_laser_follows_barrel(
{
return;
}
{
/* (the laser at the ready is the room's: into model space) */
float on[3], normal[3];
if (halo_vr_first_person_mirror(local_player_index, on, normal))
{
vr_mirror_point(on, normal, laser_start);
vr_mirror_vector(normal, laser_direction);
}
}
matrix4x3_from_point_and_vectors(&camera_matrix, &weapon_camera->position, &weapon_camera->forward,
&weapon_camera->up);
matrix4x3_multiply(&camera_matrix, &vr_arm_nodes.idle_pose[vr_arm_nodes.gun], &ready_gun);
@@ -1619,58 +1570,6 @@ static void vr_laser_follows_barrel(
halo_vr_set_laser(local_player_index, laser_start, laser_direction);
}
/* The fingers (vr.fingers): the free left hand's from its controller's
trigger, grip and touch (vr_pose_fingers); of the hand on the weapon, only
the index finger, lifted a little off the trigger when the controller
says it is (the rest keeps the weapon's grip). */
static void first_person_weapon_vr_fingers(
short local_player_index,
real_matrix4x3 *node_matrices,
boolean left_free,
float const hand_forward[3],
float const hand_up[3])
{
float curls[4];
int fingers;
if (vr_arm_nodes.fingers_graph_index != vr_arm_skeleton.graph_index)
{
vr_arm_nodes.fingers_graph_index = vr_arm_skeleton.graph_index;
vr_finger_nodes(&vr_arm_skeleton, vr_arm_nodes.wrist[0], "l ", vr_arm_nodes.fingers[0]);
vr_finger_nodes(&vr_arm_skeleton, vr_arm_nodes.wrist[1], "r ", vr_arm_nodes.fingers[1]);
}
if (left_free && vr_arm_nodes.wrist[0] != NONE &&
halo_vr_finger_curls(local_player_index, 0, curls))
{
/* the palm's frame: along the fingers, up the knuckles, and out of
the left palm (inwards, to the right) */
real_vector3d forward, up, palm;
real along;
forward.i = hand_forward[0];
forward.j = hand_forward[1];
forward.k = hand_forward[2];
up.i = hand_up[0];
up.j = hand_up[1];
up.k = hand_up[2];
along = dot_product3d(&forward, &up);
vr_vector_scale_add(&up, &forward, -along);
if (normalize3d(&forward) > 1e-4f && normalize3d(&up) > 1e-4f)
{
cross_product3d(&forward, &up, &palm);
vr_pose_fingers(&vr_arm_skeleton, node_matrices, vr_arm_nodes.wrist[0], vr_arm_nodes.fingers[0], curls,
&palm, &forward, &up);
}
}
fingers = halo_vr_finger_curls(local_player_index, 1, curls);
if (vr_arm_nodes.wrist[1] != NONE && (fingers & HALO_VR_FINGERS_TOUCH))
{
/* (resting on the trigger the index curls 0.22 or more) */
vr_straighten_finger(&vr_arm_skeleton, node_matrices, vr_arm_nodes.fingers[1][1],
0.6f * PIN(1.f - curls[1] / 0.22f, 0.f, 1.f));
}
}
static void first_person_weapon_vr_arms(
short local_player_index,
long graph_index,
@@ -1692,18 +1591,14 @@ static void first_person_weapon_vr_arms(
static char const *const upper_names[2] = { "l upperarm", "r upperarm" };
static char const *const fore_names[2] = { "l forearm", "r forearm" };
static char const *const wrist_names[2] = { "l wriste", "r wriste" };
float head[3], neck[3], body_forward[3];
float head[3], body_forward[3];
real_vector3d forward, left, weapon_left, weapon_right;
real_point3d eye;
real_matrix4x3 animated_upper[2];
real_matrix4x3 animated_forearm[2];
real_matrix4x3 animated_wrist[2];
short side;
boolean gripping;
boolean left_free;
boolean rigid = !halo_vr_free_arm_bends();
int body = halo_vr_body_mode();
float hand_position[3], hand_forward[3], hand_up[3];
if (vr_arm_nodes.graph_index != graph_index)
{
@@ -1777,27 +1672,11 @@ static void first_person_weapon_vr_arms(
}
if (!ready && !melee)
vr_laser_follows_barrel(local_player_index, node_matrices, weapon_camera);
if (!halo_vr_body_frame(local_player_index, head, neck, body_forward) ||
!vr_skeleton_build(&vr_arm_skeleton, graph_index, node_count))
{
if (!halo_vr_body_frame(local_player_index, head, body_forward))
return;
}
{
/* (the body is the room's: into model space, where the shoulders
change sides) */
float on[3], normal[3];
if (halo_vr_first_person_mirror(local_player_index, on, normal))
{
vr_mirror_point(on, normal, head);
vr_mirror_point(on, normal, neck);
vr_mirror_vector(normal, body_forward);
}
}
/* (the shoulders hang from the neck, which the head turns about) */
eye.x = neck[0];
eye.y = neck[1];
eye.z = neck[2];
eye.x = head[0];
eye.y = head[1];
eye.z = head[2];
forward.i = body_forward[0];
forward.j = body_forward[1];
forward.k = 0.f;
@@ -1814,8 +1693,6 @@ static void first_person_weapon_vr_arms(
{
animated_forearm[side] = node_matrices[vr_arm_nodes.fore[side]];
animated_wrist[side] = node_matrices[vr_arm_nodes.wrist[side]];
if (vr_arm_nodes.upper[side] != NONE)
animated_upper[side] = node_matrices[vr_arm_nodes.upper[side]];
}
}
@@ -1838,19 +1715,13 @@ static void first_person_weapon_vr_arms(
reference[2] = dot_product3d(&between, &weapon_camera->up);
gripping = halo_vr_two_hand(local_player_index, foregrip, reference);
}
/* (the weapon alone: no hands to pose) */
if (body == HALO_VR_BODY_OFF)
return;
if (!gripping && vr_arm_nodes.wrist[0] != NONE)
{
if (halo_vr_free_hand(local_player_index, hand_position, hand_forward, hand_up))
float hand_position[3], hand_forward[3], hand_up[3];
if (halo_vr_left_hand(local_player_index, hand_position, hand_forward, hand_up))
{
/* (rigid: the hand and its arm carried to the controller now;
bent, the arm is solved to it first, below, and the hand turned
after, as HaloCE-Quest-VR does: turned first, the solve's swing
turned it on and the arm's skin stretched) */
if (rigid)
vr_left_hand_free(&vr_arm_skeleton, node_matrices, rigid, hand_position, hand_forward, hand_up);
vr_left_hand_free(graph_index, node_matrices, node_count, hand_position, hand_forward, hand_up);
left_free = TRUE;
}
}
@@ -1859,18 +1730,15 @@ static void first_person_weapon_vr_arms(
{
real_point3d shoulder;
real_vector3d pole;
short bones[NUMBER_OF_VR_ARM_BONES];
real outward = side ? -1.f : 1.f; /* left is +left */
if (vr_arm_nodes.upper[side] == NONE || vr_arm_nodes.fore[side] == NONE || vr_arm_nodes.wrist[side] == NONE)
continue;
/* the shoulder: below the eyes, behind them (a little behind the
neck: in front of it, the open end of the sleeve came into view
looking down to the side), to the side, square to the torso */
/* the shoulder: below the eyes, a little behind them, to the side */
shoulder = eye;
point_from_line3d(&shoulder, global_up3d, -0.20f / VR_METRES_PER_WORLD_UNIT, &shoulder);
point_from_line3d(&shoulder, &forward, -0.04f / VR_METRES_PER_WORLD_UNIT, &shoulder);
point_from_line3d(&shoulder, global_up3d, -0.25f / VR_METRES_PER_WORLD_UNIT, &shoulder);
point_from_line3d(&shoulder, &forward, -0.06f / VR_METRES_PER_WORLD_UNIT, &shoulder);
point_from_line3d(&shoulder, &left, outward * 0.19f / VR_METRES_PER_WORLD_UNIT, &shoulder);
/* the elbow bends down, out and back */
pole.i = -global_up3d->i;
@@ -1878,90 +1746,17 @@ static void first_person_weapon_vr_arms(
pole.k = -global_up3d->k;
vr_vector_scale_add(&pole, &left, outward * 0.6f);
vr_vector_scale_add(&pole, &forward, -0.3f);
/* and as the hand rolls: an elbow lifted out to the side rolls the
controller's top in, an elbow tucked in rolls it out. Its underside
(away from the thumb) leads the elbow: the right hand's the weapon's,
the free left hand's its controller's (the hand on the foregrip
keeps the default) */
{
real_vector3d below;
boolean rolls = FALSE;
if (side)
{
below.i = -weapon_camera->up.i;
below.j = -weapon_camera->up.j;
below.k = -weapon_camera->up.k;
rolls = TRUE;
}
else if (left_free)
{
below.i = -hand_up[0];
below.j = -hand_up[1];
below.k = -hand_up[2];
rolls = TRUE;
}
if (rolls && normalize3d(&pole) > 0.f)
{
vr_vector_scale_add(&pole, &below, 1.5f);
if (normalize3d(&pole) <= 0.f)
{
pole.i = -global_up3d->i;
pole.j = -global_up3d->j;
pole.k = -global_up3d->k;
}
}
}
bones[_vr_arm_upper] = vr_arm_nodes.upper[side];
bones[_vr_arm_fore] = vr_arm_nodes.fore[side];
bones[_vr_arm_hand] = vr_arm_nodes.wrist[side];
/* (the hands alone: the arms are shrunk away below) */
if (body == HALO_VR_BODY_HANDS)
continue;
if (!side && left_free && rigid)
if (!side && left_free)
{
/* the free left arm keeps its shape (vr_left_hand_free), but
for the upper arm, which points back at the shoulder */
vr_point_upper_arm(node_matrices, vr_arm_nodes.upper[side], vr_arm_nodes.fore[side], &shoulder);
continue;
}
if (!side && left_free)
{
/* the free left arm bent to the controller's grip (the wrist
7 cm behind it), the hand turned to the controller after */
real_point3d target;
target.x = hand_position[0] - hand_forward[0] * 0.07f / VR_METRES_PER_WORLD_UNIT;
target.y = hand_position[1] - hand_forward[1] * 0.07f / VR_METRES_PER_WORLD_UNIT;
target.z = hand_position[2] - hand_forward[2] * 0.07f / VR_METRES_PER_WORLD_UNIT;
vr_solve_arm(&vr_arm_skeleton, node_matrices, bones, shoulder, &target, &pole, FALSE,
&vr_arm_history[side]);
vr_left_hand_free(&vr_arm_skeleton, node_matrices, FALSE, hand_position, hand_forward, hand_up);
}
else
{
/* the hand stays where it is (on the weapon): the arm bends to
it */
vr_solve_arm(&vr_arm_skeleton, node_matrices, bones, shoulder,
&node_matrices[bones[_vr_arm_hand]].position, &pole, TRUE, &vr_arm_history[side]);
}
vr_align_elbow(node_matrices, bones, &animated_upper[side], &animated_forearm[side], &animated_wrist[side]);
vr_distribute_arm_twist(node_matrices, bones, &animated_forearm[side], &animated_wrist[side],
&vr_arm_history[side]);
}
first_person_weapon_vr_fingers(local_player_index, node_matrices, left_free, hand_forward, hand_up);
if (body == HALO_VR_BODY_HANDS)
{
for (side = 0; side < 2; side++)
{
short bones[NUMBER_OF_VR_ARM_BONES];
bones[_vr_arm_upper] = vr_arm_nodes.upper[side];
bones[_vr_arm_fore] = vr_arm_nodes.fore[side];
bones[_vr_arm_hand] = vr_arm_nodes.wrist[side];
if (bones[_vr_arm_upper] != NONE && bones[_vr_arm_fore] != NONE && bones[_vr_arm_hand] != NONE)
vr_hide_arm(&vr_arm_skeleton, node_matrices, bones, vr_arm_nodes.fingers[side], vr_arm_nodes.gun);
}
vr_arm(node_matrices, vr_arm_nodes.upper[side], vr_arm_nodes.fore[side], vr_arm_nodes.wrist[side],
shoulder, &node_matrices[vr_arm_nodes.wrist[side]].position, &pole);
vr_twist_forearm(&node_matrices[vr_arm_nodes.fore[side]], &animated_forearm[side], &animated_wrist[side],
&node_matrices[vr_arm_nodes.wrist[side]], &node_matrices[vr_arm_nodes.wrist[side]].position);
}
}
@@ -1972,8 +1767,8 @@ static void first_person_weapon_build_node_matrices(
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
struct render_camera const *camera= &render.camera;
#ifdef HALO_ANDROID
/* VR: posed from the weapon hand's controller, so the weapon lies in the
hand (port/linux/src/vr.c) */
/* VR: posed from the right controller, so the weapon lies in the hand
(port/linux/src/vr.c) */
struct render_camera weapon_camera;
float weapon_position[3], weapon_forward[3], weapon_up[3];
@@ -2327,12 +2122,8 @@ static void first_person_weapon_build_node_matrices(
(short)animation_graph->nodes.count,
camera);
#ifdef HALO_ANDROID
first_person_weapon_vr_pose_serial[local_player_index]= camera!=&render.camera ? halo_vr_frame_serial() : 0;
first_person_weapon_vr_pose_weapon[local_player_index]= first_person_weapon->weapon_index;
if (camera!=&render.camera)
{
unsigned long long timing= halo_vr_timing_start();
first_person_weapon_vr_arms(
local_player_index,
weapon_definition->weapon.interface_definition.first_person_animations.index,
@@ -2340,16 +2131,6 @@ static void first_person_weapon_build_node_matrices(
(short)animation_graph->nodes.count,
camera,
first_person_weapon->state);
/* (left-handed: out of model space; what the frame takes from
the pose after, markers, effects, is then the room's) */
vr_mirror_pose(local_player_index, first_person_weapon->node_matrices,
(short)MIN(animation_graph->nodes.count, MAXIMUM_NODES_PER_ANIMATION));
halo_vr_timing_end(HALO_VR_TIMING_ARMS, timing);
}
else
{
debug_mirror_first_person(camera, first_person_weapon->node_matrices,
(short)MIN(animation_graph->nodes.count, MAXIMUM_NODES_PER_ANIMATION));
}
#endif
}
+13 -7
View File
@@ -373,16 +373,22 @@ void terminal_printf(
if (line_index!=NONE)
{
int count;
short 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;
count = _vsnprintf(line->buffer, NUMBEROF(line->buffer)-2, format, arglist) - 1;
match_vassert(
"c:\\halo\\SOURCE\\interface\\terminal.c",
413,
count < (short)(NUMBEROF(line->buffer)-1),
csprintf(
temporary,
"terminal_printf call generated %d characters; the buffer size is %d characters.",
count,
NUMBEROF(line->buffer)-1
)
);
line->tabstop = strstr(line->buffer, "|t") != 0;
telnet_console_print(line->buffer);
}
File diff suppressed because it is too large. Load diff
@@ -2431,14 +2431,6 @@ static boolean network_game_remove_local_player(
match_vassert("c:\\halo\\SOURCE\\interface\\ui_widget_event_handler_functions.c", 4073,
event && event->controller_index >= 0 && event->controller_index < 4,
"valid controller index required to remove player from network game");
/* port: a multiplayer map played alone (its pause screen: ui_widget.c's
ui_check_for_pause_game) has no network game to leave: the main menu,
the map not saved (as the campaign's pause screen quits it) */
if (!global_network_game_client_get())
{
main_goto_main_menu();
return TRUE;
}
network_game_client_local_player_quit(event->controller_index);
/* port: a split screen player who quit, the others staying, is not
joined to the next game */
+17 -55
View File
@@ -1852,40 +1852,11 @@ void main_pregame_render(
return;
}
/* port: a network co-op host reverts to its last saved state with its
clock kept going forward, and the clients follow through the co-op syncs
(network_coop.c). FALSE without a saved state, which would reset the map
on the host alone. */
static boolean main_coop_host_revert(
void)
{
long now = game_time_get();
if (!game_state_port_saved_game_valid())
return FALSE;
game_state_revert();
network_coop_reverted(now);
ui_widgets_disable_pause_game(30);
return TRUE;
}
static boolean main_coop_host(
void)
{
return game_connection() == _game_connection_network_server && network_coop_active();
}
static void main_revert_map_private(
void)
{
/* (a network client never reverts on its own: its game is the host's) */
if (main_coop_host())
main_coop_host_revert();
else if (game_connection() != _game_connection_network_client)
{
game_state_revert();
ui_widgets_disable_pause_game(30);
}
game_state_revert();
ui_widgets_disable_pause_game(30);
main_globals.revert_map = FALSE;
return;
}
@@ -1893,29 +1864,22 @@ static void main_revert_map_private(
static void main_skip_cinematic_private(
void)
{
/* port: only a local game or a network co-op host reverts to skip; a
network game's other machines would be left out of step */
boolean skippable = cinematic_can_be_skipped();
boolean skipped = FALSE;
/* port: a network host never reverts without a saved state, which
would reset the map on the host alone */
if (cinematic_can_be_skipped() &&
(game_connection() != _game_connection_network_server || game_state_port_saved_game_valid()))
{
long now = game_time_get();
if (skippable && main_coop_host())
{
skipped = main_coop_host_revert();
if (skipped)
network_coop_skip_done();
}
else if (skippable && game_connection() == _game_connection_local)
{
game_state_revert();
network_coop_skip_reverted(now);
ui_widgets_disable_pause_game(30);
skipped = TRUE;
}
if (skipped)
main_globals.revert_map = FALSE;
else if (main_coop_host())
}
else if (game_connection() == _game_connection_network_server)
{
error(_error_silent, "co-op: cutscene not skipped (skippable %d, saved state %d)",
skippable, game_state_port_saved_game_valid());
cinematic_can_be_skipped(), game_state_port_saved_game_valid());
}
main_globals.skip_cinematic = FALSE;
return;
@@ -2054,14 +2018,12 @@ static void main_lost_map_private(
{
main_globals.lost_map = FALSE;
main_globals.loss_timer = 0;
/* port: in network co-op, everyone dying respawns the players where
they were at the last checkpoint, without a revert. A mission the
scripts failed (game_lost, with players still alive: d40's timer,
a50's Keyes) does revert, or its failure cutscene would never end. */
if (game_connection() != _game_connection_network_server)
game_state_revert();
else if (players_are_all_dead() || !main_coop_host_revert())
/* port: network co-op can't revert to a checkpoint (every machine would
have to), so the players respawn where they were at the last one */
if (game_connection() == _game_connection_network_server)
players_respawn_at_checkpoint();
else
game_state_revert();
}
return;
}
-6
View File
@@ -322,12 +322,6 @@ long datum_new(
absolute_index++;
header = (struct datum_header *)((byte *)header+size);
}
/* port: none free from first_free_absolute_index on (and none before it
is, as ever): the next search starts at the end, until a datum is deleted
(datum_delete lowers it), rather than walking a full array again. A full
array (the AI's props, with many actors) was walked on every new datum. */
if (result == NONE)
data->first_free_absolute_index = data->maximum_count;
return result;
}
-181
View File
@@ -258,187 +258,6 @@ static struct profile_section render_model_section = { "render_model", NONE, TRU
/* ---------- private code */
#ifdef HALO_ANDROID
/* ---------- VR: the first-person hands' skin
The VR arms bend the first-person hands model's arms far from where its
animations have them; a triangle whose corners hang from bones that move
apart (an arm's, the skeleton's root's) stretches across. Read from the
part's own buffers, as drawn. */
void platform_log(char const *format, ...);
char *tag_get_name(long tag_index);
boolean rasterizer_model_buffer_data(struct vertex_buffer const *vertices, struct triangle_buffer const *triangles,
void const **vertex_data, void const **index_data);
/* the part's vertices and indices, if they are of the kinds read here */
static boolean model_vr_part_buffers(
struct model_geometry_part const *part,
struct model_vertex_compressed const **vertices,
word const **indices)
{
if (!rasterizer_model_buffer_data(&part->vertex_buffer, &part->triangle_buffer,
(void const **)vertices, (void const **)indices))
{
return FALSE;
}
return *vertices && *indices && part->vertex_buffer.count > 0 && part->vertex_buffer.count <= 65535 &&
part->triangle_buffer.count > 0 && part->triangle_buffer.count <= 65535 &&
part->vertex_buffer.type == _rasterizer_vertex_type_model_compressed &&
(part->triangle_buffer.type == _triangle_buffer_type_precompiled_strip ||
part->triangle_buffer.type == _triangle_buffer_type_triangles);
}
/* a triangle's corners (from the strip, or the list) and the nodes they
hang from (bits, a node more than a trace of weight); FALSE if malformed */
static boolean model_vr_triangle(
struct model_geometry_part const *part,
struct model_vertex_compressed const *vertices,
word const *indices,
long triangle_index,
short node_count,
word corners[3],
unsigned long long *nodes)
{
short corner;
*nodes = 0;
for (corner = 0; corner < 3; corner++)
{
long at = part->triangle_buffer.type == _triangle_buffer_type_triangles ?
triangle_index * 3 + corner : triangle_index + corner;
struct model_vertex_compressed const *vertex;
real weight;
short influence;
corners[corner] = indices[at];
if (corners[corner] >= part->vertex_buffer.count)
return FALSE;
vertex = &vertices[corners[corner]];
/* (the weight is a signed short's fraction, as rasterizer_debug_model_vertices reads it) */
weight = (real)vertex->node_weight / 32767.0f;
if (weight < 0.0f || weight > 1.0f)
return FALSE;
for (influence = 0; influence < 2; influence++)
{
short node = (short)(vertex->nodes[influence] / 3);
real amount = influence ? 1.0f - weight : weight;
if (amount <= 0.001f)
continue;
if (vertex->nodes[influence] % 3 || node >= node_count || node >= 64)
return FALSE;
*nodes |= 1ULL << node;
}
}
return TRUE;
}
/* once a model: how many triangles hang from each node, and how many of
those from its root too (debug's logcat; first_person_weapons.c, for the
hands) */
void model_vr_log_skin(
long model_index)
{
static long logged[8] = { NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE };
struct model *model;
long triangles[64];
long with_root[64];
long total = 0;
short slot;
short node_index;
short region_index;
short geometry_seen[MAXIMUM_REGIONS_PER_MODEL];
short geometry_seen_count = 0;
boolean readable = TRUE;
for (slot = 0; slot < NUMBEROF(logged); slot++)
{
if (logged[slot] == model_index)
return;
if (logged[slot] == NONE)
break;
}
if (slot == NUMBEROF(logged) || model_index == NONE)
return;
logged[slot] = model_index;
model = model_definition_get(model_index);
if (model->nodes.count <= 0 || model->nodes.count > 64)
return;
csmemset(triangles, 0, sizeof(triangles));
csmemset(with_root, 0, sizeof(with_root));
/* (each region's first permutation, in the finest detail, as drawn) */
for (region_index = 0; region_index < model->regions.count && region_index < MAXIMUM_REGIONS_PER_MODEL;
region_index++)
{
struct model_region *region = TAG_BLOCK_GET_ELEMENT(&model->regions, region_index, struct model_region);
struct model_region_permutation *permutation;
short geometry_index;
short seen;
struct model_geometry *geometry;
short part_index;
if (region->permutations.count <= 0)
continue;
permutation = TAG_BLOCK_GET_ELEMENT(&region->permutations, 0, struct model_region_permutation);
geometry_index = permutation->geometry_indices[NUMBER_OF_DETAIL_LEVELS_PER_MODEL - 1];
for (seen = 0; seen < geometry_seen_count && geometry_seen[seen] != geometry_index; seen++);
if (geometry_index == NONE || seen < geometry_seen_count)
continue;
geometry_seen[geometry_seen_count++] = geometry_index;
geometry = TAG_BLOCK_GET_ELEMENT(&model->geometries, geometry_index, struct model_geometry);
for (part_index = 0; part_index < geometry->parts.count; part_index++)
{
struct model_geometry_part *part = TAG_BLOCK_GET_ELEMENT(&geometry->parts, part_index,
struct model_geometry_part);
struct model_vertex_compressed const *vertices;
word const *indices;
long triangle_index;
if (!model_vr_part_buffers(part, &vertices, &indices))
{
readable = FALSE;
continue;
}
for (triangle_index = 0; triangle_index < part->triangle_buffer.count; triangle_index++)
{
word corners[3];
unsigned long long nodes;
if (!model_vr_triangle(part, vertices, indices, triangle_index, model->nodes.count, corners, &nodes))
{
readable = FALSE;
break;
}
if (corners[0] == corners[1] || corners[1] == corners[2] || corners[0] == corners[2])
continue;
total++;
for (node_index = 0; node_index < model->nodes.count; node_index++)
{
if (!(nodes & (1ULL << node_index)))
continue;
triangles[node_index]++;
if (nodes & 1)
with_root[node_index]++;
}
}
}
}
platform_log("VR skin of %s: %ld triangles%s; %ld hang from its root '%s'", tag_get_name(model_index), total,
readable ? "" : " (some parts unreadable)", triangles[0],
TAG_BLOCK_GET_ELEMENT(&model->nodes, 0, struct model_node)->name);
for (node_index = 1; node_index < model->nodes.count; node_index++)
{
if (triangles[node_index])
{
platform_log("VR skin: node %d '%s': %ld triangles, %ld with the root", node_index,
TAG_BLOCK_GET_ELEMENT(&model->nodes, node_index, struct model_node)->name, triangles[node_index],
with_root[node_index]);
}
}
}
#endif
static void render_model_parts(
struct model const *model,
char const *region_permutation_indices,
+1 -23
View File
@@ -411,12 +411,6 @@ enum
long as the host waits for a silent machine, network_server_manager.c):
network_game_client_network_lost */
NETWORK_GAME_CLIENT_LINK_DOWN_TIMEOUT = 15000,
/* port: how long a frame the client spends on the messages that came
(a host that floods it with them would hold each frame for as long as
they took): those left wait in the queue, in order, for the next
frame's, and the stream what the queue has no room for. One is always
handled (network_game_client_process_messages) */
MAXIMUM_MESSAGE_MILLISECONDS_PER_IDLE = 50,
};
enum network_game_client_state
@@ -751,9 +745,6 @@ static boolean add_advertised_game(
struct message_server_game_advertise *advertisement);
static boolean network_game_client_process_incoming_messages(
struct network_game_client *client);
static boolean network_game_client_process_messages(
struct network_game_client *client,
boolean budgeted);
static boolean network_game_client_process_last_messages(
struct network_game_client *client);
static void network_game_client_update_precache_status(
@@ -2459,21 +2450,11 @@ static boolean add_advertised_game(
static boolean network_game_client_process_incoming_messages(
struct network_game_client *client)
{
return network_game_client_process_messages(client, TRUE);
}
/* (budgeted: no longer than MAXIMUM_MESSAGE_MILLISECONDS_PER_IDLE, else
every message queued) */
static boolean network_game_client_process_messages(
struct network_game_client *client,
boolean budgeted)
{
boolean success = TRUE;
word message_packet_size;
struct transport_address source_address;
word message_packet[MAXIMUM_NETWORK_MESSAGE_SIZE / sizeof(word)];
unsigned long start_time = system_milliseconds();
message_packet_size = sizeof(message_packet);
@@ -2493,8 +2474,6 @@ static boolean network_game_client_process_messages(
}
message_packet_size = sizeof(message_packet);
if (budgeted && system_milliseconds() - start_time >= MAXIMUM_MESSAGE_MILLISECONDS_PER_IDLE)
break;
}
return success;
@@ -2511,8 +2490,7 @@ static boolean network_game_client_process_last_messages(
{
short state = client->state;
/* (all of them: the connection goes) */
if (!network_game_client_process_messages(client, FALSE))
if (!network_game_client_process_incoming_messages(client))
{
network_event("network_game_client_process_incoming_messages() failed after the connection failed");
}
+1 -11
View File
@@ -662,14 +662,6 @@ boolean network_game_remove_player(
game->players[player_index].machine_index == player->machine_index &&
game->players[player_index].controller_index == player->controller_index)
{
/* port: a client's count is the host's (its settings), and
need not be as many as the players it lists: none taken
below none */
if (game->player_count <= 0)
{
error(2, "tried to remove a player from a game of %d players", game->player_count);
break;
}
network_game_invalidate_player(&game->players[player_index]);
game->player_count--;
result = TRUE;
@@ -715,9 +707,7 @@ boolean network_game_remove_machine(
}
network_game_invalidate_machine(game, machine->machine_index);
/* (port: and none below none, as for its players) */
if (game->machine_count > 0)
game->machine_count--;
game->machine_count--;
result = TRUE;
break;
}
+1 -11
View File
@@ -42,22 +42,12 @@ struct network_game_local_data
byte __padding431[3];
};
/* port: network_game's cooperative_flags */
enum
{
/* the players pass through each other (Server Setup's PLAYER COLLISIONS:
OFF, co-op's): network_coop_player_collisions */
_network_game_cooperative_no_player_collisions_bit = 0,
};
struct network_game
{
wchar_t name[16];
struct network_game_map map;
struct game_variant variant;
/* port: co-op's switches (_network_game_cooperative_*_bit), in a byte the
Xbox's game left as padding, which builds before them send as 0 */
byte cooperative_flags;
byte __padding10C;
char minimum_players;
/* port: 128 does not fit a signed char */
byte maximum_players;
+3 -27
View File
@@ -1116,12 +1116,9 @@ unsigned long network_game_server_machine_address(
/* port: the distributed netcode asks that a client machine be dropped (its
game ran faster than this one's: network_distributed.c); it is, once this
server next looks at its machines, not while its messages are read. Its
address kept out of this server's games (kept_out), else it may join
again, as a kick's */
server next looks at its machines, not while its messages are read */
void network_game_server_kick_machine(
long machine_index,
boolean kept_out)
long machine_index)
{
struct network_game_server *server = global_network_game_server_get();
@@ -1131,7 +1128,7 @@ void network_game_server_kick_machine(
{
return;
}
network_game_server_kick_pending[machine_index] = kept_out ? _kick_kept_out : _kick_rejoinable;
network_game_server_kick_pending[machine_index] = _kick_kept_out;
}
/* (a client machine's player queued to add in game: one refused is as one
@@ -3145,11 +3142,6 @@ struct network_machine *network_game_server_get_client_machine(
if (machine_index)
*machine_index = NONE;
/* port: none for a connection without a slot (NONE: the assert is not
checked in release builds) */
if (client_machine->machine_index < 0 || client_machine->machine_index >= MAXIMUM_NETWORK_MACHINE_COUNT)
return NULL;
machine = &server->game.machines[client_machine->machine_index];
if (machine_index)
*machine_index = machine->machine_index;
@@ -3186,10 +3178,6 @@ struct network_game_server_client_machine *network_game_server_get_client_machin
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x741,
server && (index<MAXIMUM_NETWORK_MACHINE_COUNT));
/* (port: and in release builds) */
if (index < 0 || index >= MAXIMUM_NETWORK_MACHINE_COUNT)
return NULL;
return &server->client_machines[index];
}
@@ -4044,18 +4032,6 @@ void network_game_server_port_set_cooperative_friendly_fire(
network_event("network_game_server_port_set_cooperative_friendly_fire() failed to send updated game settings to clients");
}
void network_game_server_port_set_cooperative_player_collisions(
boolean player_collisions)
{
struct network_game_server *server = global_network_game_server_get();
if (!server)
return;
SET_FLAG(server->game.cooperative_flags, _network_game_cooperative_no_player_collisions_bit, !player_collisions);
if (server->state == _network_game_server_state_pregame && !network_game_server_send_game_data_pregame(server))
network_event("network_game_server_port_set_cooperative_player_collisions() failed to send updated game settings to clients");
}
/* port: a gametype's PC options: the menus' (player_ui_set_game_variant_options)
when it is the menus' gametype, else its defaults */
static void network_game_server_variant_options(
+1 -6
View File
@@ -47,8 +47,7 @@ void network_game_server_pause_countdown(
void network_game_generate_join_game_token(
byte *join_token);
void network_game_server_kick_machine(
long machine_index,
boolean kept_out);
long machine_index);
/* the host's ban command (console.c, hs.c) */
enum
{
@@ -73,10 +72,6 @@ void network_game_server_port_set_cooperative(
Server Setup's FRIENDLY FIRE), kept for the levels after */
void network_game_server_port_set_cooperative_friendly_fire(
short friendly_fire);
/* port: whether co-op's players collide with each other (Server Setup's
PLAYER COLLISIONS), kept for the levels after */
void network_game_server_port_set_cooperative_player_collisions(
boolean player_collisions);
boolean network_game_server_ban_player(
char const *text);
/* port: the host's kick command: as the ban command, but nothing kept (no
@@ -267,7 +267,6 @@ symbols in this file:
/* port/linux/game/network_distributed.c's */
void network_distributed_handle_message(long machine_index, word const *message, word size);
void network_distributed_handle_stream_message(long machine_index, word const *message, word size);
/* ---------- constants */
@@ -290,11 +289,6 @@ enum
/* port: the least time between two advertisements of the game
(milliseconds: network_game_server_handle_message_client_broadcast_game_search) */
GAME_ADVERTISEMENT_INTERVAL = 250,
/* port: the least time between two answers to one address's pings, and
the addresses kept to tell, no more of them answered in that time
(network_game_server_handle_message_client_ping) */
PING_REPLY_INTERVAL = 250,
MAXIMUM_PING_REPLY_ADDRESSES = 32,
};
enum
@@ -453,11 +447,6 @@ struct message_client_switch_to_pregame
long unused;
};
struct message_client_graceful_game_exit_postgame
{
long unused;
};
struct message_server_machine_accepted
{
long random_seed;
@@ -562,11 +551,6 @@ static boolean network_game_server_handle_message_client_switch_to_pregame(
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_graceful_game_exit_postgame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
/* ---------- globals */
@@ -809,12 +793,7 @@ short network_distributed_server_machines(
connection = network_game_server_get_client_connection(machine);
if (!connection || !network_connection_active(connection))
continue;
/* (port: and not a machine without a slot) */
if (!network_game_server_get_client_machine(server, machine, &game_machine_index) ||
game_machine_index == NONE)
{
continue;
}
network_game_server_get_client_machine(server, machine, &game_machine_index);
machine_indices[count++] = game_machine_index;
}
return count;
@@ -1313,19 +1292,15 @@ boolean network_game_server_handle_client_message(
}
break;
/* (port: its own handler, which decodes it as the
postgame message it is: the pregame's, which this
called, took none out of the pregame, nor could decode
one) */
case _message_client_graceful_game_exit_postgame:
result = network_game_server_handle_message_client_graceful_game_exit_postgame(
result = network_game_server_handle_message_client_graceful_game_exit_pregame(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_graceful_game_exit_postgame() failed");
network_event("network_game_server_handle_message_client_graceful_game_exit_pregame() failed");
}
break;
@@ -1353,8 +1328,7 @@ boolean network_game_server_handle_client_message(
long machine_index;
network_game_server_get_client_machine(server, machine, &machine_index);
/* (port: from its stream, which only it can send on) */
network_distributed_handle_stream_message(machine_index, message, message_buffer_size);
network_distributed_handle_message(machine_index, message, message_buffer_size);
}
break;
@@ -1752,44 +1726,12 @@ static boolean network_game_server_handle_message_client_ping(
struct message_server_pong pong;
struct network_message *reply;
boolean result = FALSE;
/* port: an address answered no more often than PING_REPLY_INTERVAL, and
no more than MAXIMUM_PING_REPLY_ADDRESSES in that time (the answer goes
to the port the ping names, at the address it came from, which anyone
can send one as: a flood of pings would be a flood of answers, at
another's machine). A searching client pings once a second */
static struct
{
unsigned long address;
unsigned long time;
} replied[MAXIMUM_PING_REPLY_ADDRESSES];
unsigned long now = system_milliseconds();
short index;
short slot = NONE;
match_assert(
"c:\\halo\\SOURCE\\networking\\network_server_message_handler.c",
0x26A,
server && source_address && client_message);
for (index = 0; index < MAXIMUM_PING_REPLY_ADDRESSES; index++)
{
boolean recent = replied[index].address && now - replied[index].time < PING_REPLY_INTERVAL;
if (replied[index].address == source_address->address.long_words[0])
{
if (recent)
return TRUE;
slot = index;
break;
}
if (!recent && slot == NONE)
slot = index;
}
if (slot == NONE)
return TRUE;
replied[slot].address = source_address->address.long_words[0];
replied[slot].time = now;
pong.timestamp = client_message->timestamp;
reply = create_network_game_message(
_message_server_pong,
@@ -1916,7 +1858,7 @@ static boolean network_game_server_handle_message_client_join_game_request(
/* port: a machine joining the game in progress is named
by its join (its settings request, which names a machine
in the lobby, is refused in game) */
if (late_join && client_machine)
if (late_join)
csmemcpy(client_machine->name, machine_name, sizeof(client_machine->name));
acceptance.machine_index = (short)machine_index;
@@ -2462,10 +2404,9 @@ static boolean network_game_server_handle_message_client_graceful_game_exit_preg
&packet_version,
_network_game_packet_class_client_pregame))
{
struct network_machine *machine = network_game_server_get_client_machine(server, client_machine, NULL);
/* (port: none for a machine without a slot) */
if (machine && network_game_server_remove_machine_from_game(server, machine))
if (network_game_server_remove_machine_from_game(
server,
network_game_server_get_client_machine(server, client_machine, NULL)))
{
/* (not to machines loading the game) */
if (network_game_server_lobby_is_open(server) &&
@@ -2764,48 +2705,3 @@ static boolean network_game_server_handle_message_client_switch_to_pregame(
return result;
}
/* port: a machine leaving the game's scores, as one leaving the lobby does
(network_game_server_handle_message_client_graceful_game_exit_pregame) */
static boolean network_game_server_handle_message_client_graceful_game_exit_postgame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
if (network_game_server_get_state(server, NULL) == _network_game_server_state_postgame)
{
struct message_client_graceful_game_exit_postgame graceful_game_exit;
short packet_type = _message_client_graceful_game_exit_postgame;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&graceful_game_exit,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_postgame))
{
struct network_machine *machine = network_game_server_get_client_machine(server, client_machine, NULL);
if (!machine || !network_game_server_remove_machine_from_game(server, machine))
{
network_event(
"network_game_server_remove_machine_from_game() failed in network_game_server_handle_message_client_graceful_game_exit_postgame()");
}
}
else
{
network_event("server failed to decode a message_client_graceful_game_exit_postgame packet");
}
}
else
{
network_event(
"failed to handle a message_client_graceful_game_exit_postgame message because the server is not in post-game");
}
return TRUE;
}
-14
View File
@@ -2654,20 +2654,6 @@ void damage_replay_kill(
distributed_damage_authorized = FALSE;
}
/* ... damage to scenery or a device, dealt as the host dealt it: the copy
breaks, plays its effects and is destroyed as the host's was */
void damage_replay_static(
long object_index,
struct damage_data *damage,
short node_index,
short region_index,
short material_index)
{
distributed_damage_authorized = TRUE;
object_cause_damage(damage, object_index, node_index, region_index, material_index, NULL);
distributed_damage_authorized = FALSE;
}
/* the host's: what damage_set_network_state takes */
void damage_get_network_state(
long object_index,
+51 -195
View File
@@ -496,31 +496,6 @@ boolean debug_lights;
boolean debug_object_lights;
struct data_array *light_data;
struct cluster_partition light_cluster_partition;
/* port: the clusters' lights kept for lighting objects (lights_port_cluster):
as many as the light partition's lists can hold */
enum
{
LIGHTS_PORT_MAXIMUM_ENTRIES = HALO_PORT_MAXIMUM_CLUSTER_REFERENCES,
};
struct lights_port_entry
{
long light_index;
real_point3d position;
real radius;
};
static struct
{
/* each light's radius or rasterizer light written (lights_preprocess_scene) */
unsigned long radius_writes;
/* what the entries were kept for; each cluster's, and where they are */
unsigned long generation;
short entry_count;
unsigned long cluster_generations[MAXIMUM_CLUSTERS_PER_STRUCTURE];
short cluster_first_entries[MAXIMUM_CLUSTERS_PER_STRUCTURE];
short cluster_entry_counts[MAXIMUM_CLUSTERS_PER_STRUCTURE];
struct lights_port_entry entries[LIGHTS_PORT_MAXIMUM_ENTRIES];
} lights_port;
struct lights_game_globals *lights_game_globals = NULL;
short debug_rasterizer_light_count;
struct lights_globals lights_globals;
@@ -837,7 +812,6 @@ void lights_preprocess_scene(
_point_light_attached_to_first_person_weapon_bit,
FALSE);
light->rasterizer_light_index = NONE;
lights_port.radius_writes++;
if (light->parent_light_index != NONE)
{
struct point_light_definition *definition = light_definition_get(
@@ -993,7 +967,6 @@ void lights_preprocess_scene(
light->radius = (definition->radius_modifier_lower_bound * inverse_intensity
+ definition->radius_modifier_upper_bound * intensity)
* definition->radius;
lights_port.radius_writes++;
if (light->radius != 0.0f)
{
struct rasterizer_light_submit_parameters light_parameters;
@@ -1062,7 +1035,6 @@ void lights_preprocess_scene(
else
{
light->radius = definition->radius;
lights_port.radius_writes++;
}
if (definition->lens_flare.index != NONE)
@@ -1824,136 +1796,6 @@ void light_reconnect_to_map(
return;
}
/* a light of a cluster, for find_point_lights_for_object_in_cluster: as
the Xbox's loop does each, marking it (so its other clusters pass it over) */
static void find_point_light_for_object(
long object_index,
long light_index,
real_point3d const *center,
real radius,
long *light_indices,
real *light_intensities,
real *light_attenuations,
short *light_count,
short maximum_light_count)
{
if (light_unmarked(light_index))
{
struct light_datum *light = light_get(light_index);
if (light->rasterizer_light_index != NONE
&& (light->object_index != object_index
|| !TEST_FLAG(light_definition_get(light->definition_index)->flags,
_light_definition_dont_light_own_object_bit)))
{
real distance = distance3d(&light->position, center);
if (distance < radius + light->radius)
{
real attenuation = light_attenuation(light->radius, distance);
real intensity = real_rgb_color_brightness(&light->color) * attenuation;
short index;
if (*light_count < maximum_light_count)
{
index = (*light_count)++;
}
else
{
real minimum_intensity = REAL_MAX;
short dimmest_index = NONE;
for (index = 0; index < *light_count; index++)
{
if (minimum_intensity > light_intensities[index])
{
minimum_intensity = light_intensities[index];
dimmest_index = index;
}
}
if (minimum_intensity < intensity)
{
index = dimmest_index;
}
}
if (index < maximum_light_count)
{
light_indices[index] = light_index;
light_intensities[index] = intensity;
light_attenuations[index] = attenuation;
}
}
}
light_mark(light_index);
}
}
/* port: a cluster's lights in its list's order, with where each is and how
far it reaches, kept while none of that changes: while the light partition's
lists (cluster_partitions.c's port_modification_count) and the lights' radii
and rasterizer lights (lights_port.radius_writes, written as a frame's lights
are prepared) are as they were; a light only moves as it joins its clusters
again. An object lit (each object drawn, each shadow) then passes over the
lights too far to reach it without their datums or the list's: with hundreds
of lights in a cluster (network co-op's extra enemies), reading them was
most of a frame's lighting. FALSE: not kept (the list walked as before). */
static boolean lights_port_cluster(
short cluster_index,
short *first_entry,
short *entry_count)
{
unsigned long generation = 1 + light_cluster_partition.port_modification_count + lights_port.radius_writes;
long reference_index;
long light_index;
short first;
if (cluster_index < 0 || cluster_index >= MAXIMUM_CLUSTERS_PER_STRUCTURE)
return FALSE;
if (lights_port.generation != generation)
{
lights_port.generation = generation;
lights_port.entry_count = 0;
}
if (lights_port.cluster_generations[cluster_index] != generation)
{
first = lights_port.entry_count;
for (light_index = cluster_partition_get_first_datum(&light_cluster_partition, &reference_index, cluster_index);
light_index != NONE;
light_index = cluster_partition_get_next_datum(&light_cluster_partition, &reference_index))
{
struct light_datum *light = light_get(light_index);
struct lights_port_entry *entry;
/* (one not drawn this frame lights nothing: reaching an object,
it would only be marked, for that object's other clusters to pass
over) */
if (light->rasterizer_light_index == NONE)
continue;
if (lights_port.entry_count >= LIGHTS_PORT_MAXIMUM_ENTRIES)
{
/* (more than are kept: this cluster's walked) */
lights_port.entry_count = first;
first = NONE;
break;
}
entry = &lights_port.entries[lights_port.entry_count++];
entry->light_index = light_index;
entry->position = light->position;
entry->radius = light->radius;
}
lights_port.cluster_generations[cluster_index] = generation;
lights_port.cluster_first_entries[cluster_index] = first;
lights_port.cluster_entry_counts[cluster_index] = first == NONE ? 0 : (short)(lights_port.entry_count - first);
}
if (lights_port.cluster_first_entries[cluster_index] == NONE)
return FALSE;
*first_entry = lights_port.cluster_first_entries[cluster_index];
*entry_count = lights_port.cluster_entry_counts[cluster_index];
return TRUE;
}
static void find_point_lights_for_object_in_cluster(
long object_index,
short cluster_index,
@@ -1967,52 +1809,66 @@ static void find_point_lights_for_object_in_cluster(
{
long light_index;
long reference_index;
short first_entry, entry_count;
match_assert(
"c:\\halo\\SOURCE\\objects\\object_lights.c",
0x544,
lights_globals.marker_initialized);
/* port: the cluster's lights as kept (lights_port_cluster). A light too
far to reach the object is passed over: the Xbox's loop tested it the
same, on the same position and radius, and rejected it. Left unmarked, it
is tested again in the object's other clusters, and rejected again.
Most are far along one axis, which says so without the distance: a
distance is never less than one axis of it but for rounding, a few parts
in 2^24, well inside the margin here. */
if (lights_port_cluster(cluster_index, &first_entry, &entry_count))
{
short entry_index;
for (entry_index = first_entry; entry_index < first_entry + entry_count; entry_index++)
{
struct lights_port_entry const *entry = &lights_port.entries[entry_index];
real reach = radius + entry->radius;
real bound = reach * 1.000001f;
real offset;
offset = entry->position.x - center->x;
if (offset > bound || -offset > bound)
continue;
offset = entry->position.y - center->y;
if (offset > bound || -offset > bound)
continue;
offset = entry->position.z - center->z;
if (offset > bound || -offset > bound)
continue;
if (!(distance3d(&entry->position, center) < reach))
continue;
find_point_light_for_object(object_index, entry->light_index, center, radius, light_indices,
light_intensities, light_attenuations, light_count, maximum_light_count);
}
return;
}
for (light_index = cluster_partition_get_first_datum(&light_cluster_partition, &reference_index, cluster_index);
light_index != NONE;
light_index = cluster_partition_get_next_datum(&light_cluster_partition, &reference_index))
{
find_point_light_for_object(object_index, light_index, center, radius, light_indices, light_intensities,
light_attenuations, light_count, maximum_light_count);
if (light_unmarked(light_index))
{
struct light_datum *light = light_get(light_index);
if (light->rasterizer_light_index != NONE
&& (light->object_index != object_index
|| !TEST_FLAG(light_definition_get(light->definition_index)->flags,
_light_definition_dont_light_own_object_bit)))
{
real distance = distance3d(&light->position, center);
if (distance < radius + light->radius)
{
real attenuation = light_attenuation(light->radius, distance);
real intensity = real_rgb_color_brightness(&light->color) * attenuation;
short index;
if (*light_count < maximum_light_count)
{
index = (*light_count)++;
}
else
{
real minimum_intensity = REAL_MAX;
short dimmest_index = NONE;
for (index = 0; index < *light_count; index++)
{
if (minimum_intensity > light_intensities[index])
{
minimum_intensity = light_intensities[index];
dimmest_index = index;
}
}
if (minimum_intensity < intensity)
{
index = dimmest_index;
}
}
if (index < maximum_light_count)
{
light_indices[index] = light_index;
light_intensities[index] = intensity;
light_attenuations[index] = attenuation;
}
}
}
light_mark(light_index);
}
}
return;
+2 -20
View File
@@ -31,7 +31,6 @@ OBJECTS.C
#include "game/players.h"
#include "items/weapons.h"
#include "main/console.h"
#include "interface/terminal.h"
#include "math/periodic_functions.h"
#include "memory/memory_pool.h"
#include "models/model_animation_definitions.h"
@@ -526,18 +525,6 @@ void object_pvs_set_camera_point(
return;
}
void objects_port_set_activating_cluster(
short cluster_index)
{
if (cluster_index == NONE || cluster_index >= global_structure_bsp_get()->clusters.count)
{
object_globals->pvs_activation_type = _pvs_activation_normal;
return;
}
object_globals->pvs_activation_type = _pvs_activation_cluster;
object_globals->pvs_activation.cluster_index = cluster_index;
}
void object_pvs_clear(void)
{
object_globals->pvs_activation_type = _pvs_activation_normal;
@@ -4103,10 +4090,7 @@ void objects_garbage_collection(
}
sprintf(tempbuffer, "garbage collection %scritical (%s)", status, warningbuf);
/* (port: chatter, shown as config.toml's game.console_log says:
many enemies keep it coming, over the whole screen) */
if (terminal_shows(_terminal_message_chatter))
console_printf(FALSE, "%s", tempbuffer);
console_printf(FALSE, "%s", tempbuffer);
error(_error_log, "%s", tempbuffer);
update_time = TRUE;
}
@@ -4145,9 +4129,7 @@ void objects_garbage_collection(
char tempbuffer[512];
sprintf(tempbuffer, "removing objects: %s", released_resultbuf);
/* (port: chatter, shown as config.toml's game.console_log says) */
if (terminal_shows(_terminal_message_chatter))
console_printf(FALSE, "%s", tempbuffer);
console_printf(FALSE, "%s", tempbuffer);
error(_error_log, "%s", tempbuffer);
}
-4
View File
@@ -386,10 +386,6 @@ void object_pvs_set_object(long object_index);
void object_pvs_set_camera_point(short camera_point_index);
void object_pvs_clear(void);
short objects_get_activating_cluster_index(void);
/* port: makes a cluster active as object_pvs_activate would (NONE: back to
the players' own), for a network co-op client following the host's
cutscene (port/linux/game/network_coop.c) */
void objects_port_set_activating_cluster(short cluster_index);
void object_definition_predict(long definition_index);
void object_predict(long object_index);
void object_beautify(long object_index, boolean beautiful);
+3 -53
View File
@@ -25,7 +25,6 @@ BREAKABLE_SURFACES.C
#include "sound/game_sound.h"
#include "structures/structure_bsp_definitions.h"
#include "tag_files/tag_groups.h"
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
/* ---------- structures */
@@ -38,21 +37,6 @@ struct breakable_surface_globals
/* ---------- prototypes */
/* port: cache_files.c's */
boolean tag_index_is_group(long tag_index, long group_tag);
/* port: a networked break carries no damage effect (the host already dealt
it). Tag 0 is the scenario, and reading it as a damage effect stops the game */
static struct damage_breaking_effect_definition const *breakable_surface_breaking_effect(
struct damage_data const *damage_data)
{
static struct damage_breaking_effect_definition const none;
if (damage_data && tag_index_is_group(damage_data->definition_index, DAMAGE_EFFECT_DEFINITION_TAG))
return &damage_effect_definition_get(damage_data->definition_index)->breaking_effect;
return &none;
}
/* January evaluates the j/k terms as one group in this translation unit.
The shared helper must remain flat for its other exact consumers. */
static __inline real breakable_surface_plane_distance(
@@ -176,7 +160,7 @@ void breakable_surface_damage(
if (globals->enabled)
{
if (breakable_surface_index != NONE &&
tag_index_is_group(damage_data->definition_index, DAMAGE_EFFECT_DEFINITION_TAG) &&
damage_data->definition_index != NONE &&
damage_data->material_type != NONE)
{
struct breakable_surface_datum *surface = breakable_surface_get(breakable_surface_index);
@@ -205,8 +189,6 @@ void breakable_surface_damage(
BIT_VECTOR_SET_FLAG((long *)breakable_surface_flags_get(), breakable_surface_index, FALSE);
breakable_surface_effect(breakable_surface_index, damage_data, seed_surface_index);
/* port: and on network co-op's clients */
network_coop_note_surface_broken(breakable_surface_index, &damage_data->epicenter);
}
}
}
@@ -222,11 +204,7 @@ void breakable_surface_damage_area_of_effect(
const struct damage_data *damage_data)
{
struct structure_bsp *structure_bsp = global_structure_bsp_get();
struct damage_effect_definition *damage_effect_definition;
if (!tag_index_is_group(damage_data->definition_index, DAMAGE_EFFECT_DEFINITION_TAG))
return;
damage_effect_definition = damage_effect_definition_get(damage_data->definition_index);
struct damage_effect_definition *damage_effect_definition = damage_effect_definition_get(damage_data->definition_index);
if (globals->enabled &&
(damage_effect_definition->damage.damage_lower_bound != 0.0f || damage_effect_definition->damage.damage_upper_bound != 0.0f))
@@ -258,8 +236,6 @@ void breakable_surface_damage_area_of_effect(
breakable_surface_get(breakable_surface_index)->vitality = 0.0f;
BIT_VECTOR_SET_FLAG((long *)breakable_surface_flags_get(), breakable_surface_index, FALSE);
breakable_surface_effect(breakable_surface_index, damage_data, breakable_surface->collision_surface_index);
/* port: and on network co-op's clients */
network_coop_note_surface_broken(breakable_surface_index, &damage_data->epicenter);
}
}
}
@@ -268,32 +244,6 @@ void breakable_surface_damage_area_of_effect(
return;
}
void breakable_surface_port_break(
short breakable_surface_index,
real_point3d const *epicenter)
{
struct structure_bsp *structure_bsp = global_structure_bsp_get();
struct structure_breakable_surface *breakable_surface;
struct damage_data damage;
if (breakable_surface_index < 0 || breakable_surface_index >= structure_bsp->breakable_surfaces.count ||
!breakable_surface_extant(breakable_surface_index))
{
return;
}
breakable_surface = TAG_BLOCK_GET_ELEMENT(&structure_bsp->breakable_surfaces, breakable_surface_index,
struct structure_breakable_surface);
/* (the shards fly away from the epicenter, all the effect reads) */
csmemset(&damage, 0, sizeof(damage));
damage.epicenter = *epicenter;
/* (and where it is, which the break's sound plays in: zeros were leaf
and cluster 0) */
scenario_location_from_point(&damage.location, epicenter);
breakable_surface_get(breakable_surface_index)->vitality = 0.0f;
BIT_VECTOR_SET_FLAG((long *)breakable_surface_flags_get(), breakable_surface_index, FALSE);
breakable_surface_effect(breakable_surface_index, &damage, breakable_surface->collision_surface_index);
}
/* ---------- private code */
static void breakable_surface_effect(
@@ -523,7 +473,7 @@ static void breakable_surface_effect(
struct new_particle_data particle;
velocity = *global_zero_vector3d;
breaking_effect = breakable_surface_breaking_effect(damage_data);
breaking_effect = &damage_effect_definition_get(damage_data->definition_index)->breaking_effect;
vector_from_points3d(&damage_data->epicenter, &position, &outward_vector);
distance = normalize3d(&outward_vector);
Loaded 100 of 132 files, more files were not shown because too many files have changed in this diff. Show more