The in-game list of who talks colours each name as the names above players' heads are (hud_player_name_color, now shared): allies in the HUD's blue, enemies red, this machine's own players white; in co-op every other player is an ally.
A player talking in voice chat has the speaker just left of their name above their head, centred on its capitals, at the name's scale and fade; only where the name itself is shown (display.player_names, and the gametype's motion tracker rules).
Voice chat's speaker and muted speaker are Lucide's volume-2 and volume-x (1.53.0, ISC: port/assets/icons/lucide, credited in AUTHORS.md, its licence shipped beside the game), drawn white into the menus' bitmap voice/speaker by tools/ce_menus.py and tinted, in place of the speaker made of rectangles. In the lobby, on the scoreboard and in the list of who talks, the icon is centred on the middle of the text's capitals (draw_unicode_string_capital_middle).
Opus voice chat (port/third_party/opus 1.5.2, port/linux/src/voice_audio.c, port/linux/game/network_voice.c), relayed by the host, in the lobby too. The host decides in Server Setup > Teamplay Options: lobby voice (on by default), the game's mode (off, team near, anyone near, team, team and enemies near: the default), the near distance, and the quality, 8 to 64 kbps (24). Each player picks push to talk (V), open mic or off, the voices' volume, and the output and input devices in Settings > Audio, and mutes others from the scoreboard's menu. A speaker icon marks who talks in the lobby, on the scoreboard and in a list in game. The host takes a machine's frames only with the key it gave it over its stream, within the quality's size, 50 a second and 8 talkers at once, and sends voices only to machines that asked for them, so the network version stays.
Any player of a network game can start a vote to kick another, on any team, by picking their name on the scoreboard (right-click while it is open frees the mouse) or with the console's votekick; the host's own scoreboard also has Kick and Ban (Ban picked twice). The host alone counts (port/linux/game/network_votekick.c): votes only from each voter's stream, one per real address and again per hardware id (which only removes votes), time played on the host before starting (network.votekick_minutes) and voting, more than half of everyone with the target counted and at least two, one vote at a time with cooldowns for a failed vote's starter and target, and a passed vote keeps the player out for network.votekick_ban_minutes. Builds without it drop the new messages, so the network version stays.
unicode_string_list_get_string terminates a string at its last
character, size / 2 - 1, so a string whose data is one byte long had its
terminator written two bytes before its data. Strings shorter than one
character are now not used.
The scenario's tag name was copied with strcpy into the game state
header's 256-byte map_name, a tag's name was formatted with sprintf into
the out of objects message's 512 bytes, and csprintf bounded its output
at MAXIMUM_STRING_SIZE even when it formats into temporary, which is 256
bytes and where most callers put tag names. All three are bounded by
their buffers' sizes now.
A widget's event handlers name their function by its index in the table,
which nothing checked, so a game map's widget could run the main menu's
profile functions (deleting and saving player and playlist profiles, the
demos) and the port's own menu functions (writing config.toml, quitting,
connecting), on its created event too. The shipped maps' widgets run none
of these. Now only the port's own menu tags run the port's functions, and
only the main menu runs its profile functions; a refusal is logged once.
saved_game_file_remember_last_used_multiplayer_map wrote 256 bytes from
the caller's name, which the menus' map list keeps in 64, so whatever
followed it in memory went into lastmpmp.txt. The name is now copied into
a zeroed buffer of the written size first.
#191's inverse-distance gain replaces the linear fade for a positioned stereo sound rather than adding to it, so it is as loud as a mono sound at the same distance (both curves together left it some 7 dB quieter at 40 units and 15 at 60), and it is the fade the reverb send takes back out, so the reverb follows the 3D room rolloff as a mono sound's does. A 3D sound is not cut off at the tag's maximum distance either: none starts past it.
A stereo channel's positioned sound is now given what a 3D channel's is: the same occlusion, obstruction and underwater listener (as the 3D call passes them) and its class's wet gain make its I3DL2 source, so the mixer muffles its direct path and sends it to the reverb as it does a 3D voice. The room send rolls off with distance as a 3D voice's does, the game's own fade of the stereo voice's volume taken back out of it. Unpositioned stereo stays unpanned and dry.
A sound with a position plays on a 3D channel, but those are mono: a stereo one (a Custom Edition map's gunfire, a bullet's impact) fell back to a stereo channel, which the Xbox never panned, so it was only faded with distance and came from nowhere. update_channels now gives a stereo channel's sound in the world a pan towards it, as the mixer pans a 3D voice; the mixer pans the channels' middle and keeps their difference as wide as the far ear's gain. Unpositioned stereo plays as before, and no channel pools change.
Our version of #147. With audio.loose_sounds (off by default), each of a map's sounds that has a Halo PC sound tag file of its name in the data root's tags folder plays from it (port/linux/game/loose_sounds.c); loose_sounds_reload and loose_sounds at the console read them again or give the map's back. The files' samples are made the Xbox ADPCM this build plays through the Custom Edition converter, so no channel pools change and stock maps play as before. The override is written into the map's definition in place, keeping the counters the game updates; a change stops every sound and flushes the sound cache first, and old samples are freed once no cache block holds them. A split sound's length is its chain's.
The sound cache is HALO_PORT_SOUND_CACHE_SIZE: 16 MB on desktop, the Xbox's 4 MB on Android, with the debug view's buffer sized to match.
#180 gave a weapon without a second magazine the secondary's overlays at
rest (default and always) and states at rest (not disabled). The Xbox
never set those overlays, and an empty magazine's total ammunition state
is disabled, so the stock HUDs' secondary numbers and icons, which never
showed, drew over the campaign HUD's ammunition and grenade panels.
Such a weapon now leaves the secondary overlays unset, as the Xbox did,
and its states read the cleared, empty magazine. #180's fix stays: the
secondary's overlays no longer write over the primary's.
The HUD's text takes '|' and the character after it as one escape (draw_string.c, parse_unicode_string), so a map's message ending in '|' had its drawing read past the terminator. sv_say leaves '|' out of what it shows.
#152's fallback gets a port: comment and includes cseries/errors.h, as the rest of source/ does, and clamps t with PIN; a pitch not a number still samples the first point.
Custom Edition maps' scripts call these Halo PC functions (lookout_classic
and the Halo Kart maps show players messages with sv_say), which did
nothing here. sv_say now shows the message in each player's HUD, and
sv_end_game ends the game on the host, as its time running out does.
They go at the end of the function table, so Xbox maps, which call
functions by their place in it, are unaffected; Custom Edition maps call
them by name (custom_edition_scripts.c).
sv_end_game adapted from ChupathingyCE (4e89e01f), by MrMilenko.
A particle state names its bitmap's sequence, which a Custom Edition map's
bitmap may not have, or may have with no sprites (Hornets Nest's). The
draw read past the bitmap's sequences, then took the sprite index modulo
a sprite count of zero. Such a particle is now skipped.
Found by ChupathingyCE (MrMilenko).
render_weapon_hud set the secondary magazine's overlay flags into slots 0
and 1, over the primary's, where its states and numbers use 4 and 5: a
weapon of one magazine showed its ammunition overlays as the empty second
magazine's (coldsnap's flamethrower read NO FUEL with a full tank).
A weapon of one magazine also read a second it doesn't have:
weapon_build_weapon_interface_state fills only the weapon's own, into
unset stack memory, so the HUD's secondary flash, reload and fired-empty
warnings followed whatever was there. The state is now cleared first, and
a weapon of one magazine keeps its secondary states and overlays at rest.
coldsnap_20th_xl's scripts start an object_name global as none, which
this compiler refused ("none: this is not a valid object name."), so on
a Custom Edition map the scripts using it were dropped. none is now no
object: the object_name type's own default, which every lookup takes.
Adapted from ChupathingyCE (92342517), by MrMilenko.
The powerup bounds of #117 move from source/models/models.c to port/linux/game/powerup_render_bounds.c, called from scenario_tags_load beside the other tags-loaded hooks, so the game's models.c no longer takes <xtl.h> and D3D buffer locks. It uses square_root and a relative margin in place of sqrtf and nextafterf, which strict gnu89 does not declare, and the objects.h scale change is marked as the port's. tools/test_powerup_bounds.py is dropped: it redeclares the engine's types by hand and CI does not run it.
On bloodgulch the active camouflage's render radius grows from 0.1 to 0.23 and the overshield's from 0.1 to 0.21; no other equipment changes.
The cache is forgotten before every game state load (beside cluster_partitions_port_forget) and at each new map, after the declarations, rather than at the top of four functions.
Fixed: Weapons whose trigger had flag "analog rate of fire" now read how far the trigger is pulled: fully for mouse/keyboard, the actual pressure for a controller trigger. Before they never fired.
source/input/input_abstraction.c
source/input/input_abstraction.h
source/game/player_control.c
Fixed: Weapon spawn orientation on Custom Edition maps. Multiplayer weapons and items now spawn facing the way the map places them, as in Halo PC.
source/game/game_engine.c
Added: PC vehicle set. There's a new PC option in the vehicle set list, between Gun Turrets and Custom. A team set to PC gets every vehicle the map places, as in Halo PC. On Custom Edition maps that means every placement whose spawn flags name the gametype.
port/assets/menus/ce/strings.xml
port/linux/game/menu_functions.c
source/game/game_engine.h
source/game/game_engine.c
Added: Start LAN, internet and split screen games alone. A game with one machine and one player can now start. Pressing start alone begins the countdown, others can join while the game is in progress, and the lobby no longer says it's waiting for another machine or player.
source/networking/network_server_manager.c
source/interface/ui_widget_game_data_input_functions.c
A collision query (collision_get_features_in_sphere) reads the bounding
sphere of every collideable object in every cluster it touches, to pass
over the far ones. Every biped queries each tick, so a crowded cluster
(co-op's extra enemies, a carrier burst) costs the square of the crowd in
scattered object reads.
object_bounds_cache.c keeps the spheres packed by absolute index, written
by their only writer (object_compute_node_matrices). The loop runs the same
point_in_sphere on the copy, so the same objects are passed over and the
same features gathered, in order. game_state.c invalidates the copy when
the game state is replaced (revert, loaded core or game, new map).
Deterministic offline crowd (c20, legendary, extra enemies x8, 24 carriers
burst, seed pinned): game state identical tick for tick; collision queries
4.2 -> 3.0 us (-28%), object updates -15%.