Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0d454ae9b | ||
|
|
96a75df218 | ||
|
|
1a732f577f |
No files matched your search
@@ -24,8 +24,6 @@ xbox/
|
||||
|
||||
# Locally extracted proprietary game data (never commit)
|
||||
assets/
|
||||
# (but the high-res assets, drawn for the port: tools/hud_assets.py)
|
||||
!port/assets/
|
||||
original/
|
||||
|
||||
# CI build outputs (tools/ci_build.py)
|
||||
|
||||
@@ -168,8 +168,7 @@ the Xbox:
|
||||
- The HUD stays at the edges of the screen.
|
||||
- The menus, the loading bar and the screens after a game have 640
|
||||
columns, at the center of the screen.
|
||||
- Black bars and fades cover all of the screen, and so do the menus' dims
|
||||
and backgrounds (the pause menu's dim, dialogs, the menus' gradient).
|
||||
- Black bars and fades cover all of the screen.
|
||||
|
||||
## VR
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<!-- the voice chat in multiplayer (host_voice.c) -->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<!-- installing a new version of the game (Updater) -->
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.halo.decomp;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@@ -35,6 +38,7 @@ import java.nio.channels.FileChannel;
|
||||
*/
|
||||
public class LauncherActivity extends Activity {
|
||||
private static final int PICK_IMAGE = 1;
|
||||
private static final int ASK_MICROPHONE = 2;
|
||||
|
||||
private File dataRoot;
|
||||
private TextView status;
|
||||
@@ -53,12 +57,37 @@ public class LauncherActivity extends Activity {
|
||||
passOnHardwareId();
|
||||
passOnInvite(getIntent());
|
||||
if (haveData()) {
|
||||
startGame();
|
||||
if (!askForMicrophone())
|
||||
startGame();
|
||||
return;
|
||||
}
|
||||
buildInterface();
|
||||
}
|
||||
|
||||
/**
|
||||
* The voice chat's microphone (port/android/host/host_voice.c), asked
|
||||
* for here, once, on this flat panel, before the game starts: in the
|
||||
* game, the question would stop it. Returns true if it asked (the game
|
||||
* starts on the answer).
|
||||
*/
|
||||
private boolean askForMicrophone() {
|
||||
SharedPreferences preferences = getSharedPreferences("launcher", MODE_PRIVATE);
|
||||
|
||||
if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
|
||||
|| preferences.getBoolean("asked_microphone", false))
|
||||
return false;
|
||||
preferences.edit().putBoolean("asked_microphone", true).apply();
|
||||
requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, ASK_MICROPHONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, results);
|
||||
if (requestCode == ASK_MICROPHONE)
|
||||
startGame();
|
||||
}
|
||||
|
||||
/**
|
||||
* An internet play invite link the app was opened with: the game
|
||||
* (port/linux/src/p2p.c) picks it up from join_link.txt, whether it is
|
||||
@@ -111,6 +140,23 @@ public class LauncherActivity extends Activity {
|
||||
partial.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the microphone may be used, for the game (port/linux/src/
|
||||
* voice.c), which opens it only then: opening it otherwise would have
|
||||
* Android ask in the middle of the game.
|
||||
*/
|
||||
private void passOnMicrophonePermission() {
|
||||
if (dataRoot == null)
|
||||
return;
|
||||
boolean granted =
|
||||
checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
|
||||
try (OutputStream out = new FileOutputStream(new File(dataRoot, "microphone_permission.txt"))) {
|
||||
out.write((granted ? "1" : "0").getBytes("UTF-8"));
|
||||
} catch (java.io.IOException e) {
|
||||
// the game then does without the microphone
|
||||
}
|
||||
}
|
||||
|
||||
/** The game data, or the marker file of the VR self-test (host_xr.c). */
|
||||
private boolean haveData() {
|
||||
return dataRoot != null && (new File(dataRoot, "maps/ui.map").isFile() ||
|
||||
@@ -118,6 +164,7 @@ public class LauncherActivity extends Activity {
|
||||
}
|
||||
|
||||
private void startGame() {
|
||||
passOnMicrophonePermission();
|
||||
startActivity(new Intent(this, HaloActivity.class));
|
||||
finish();
|
||||
}
|
||||
|
||||
@@ -131,6 +131,21 @@ 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 voice chat (host_voice.c) */
|
||||
|
||||
/* opens the voices' playback, and the microphone if asked (else closes
|
||||
it): their volume, and the level (0..1 of full scale) the microphone's
|
||||
sound must reach to be sent */
|
||||
int host_voice_start(int microphone, float volume, float threshold);
|
||||
void host_voice_stop(void);
|
||||
/* the microphone's speech since asked: Opus packets, each after a byte of
|
||||
its length; returns the bytes */
|
||||
int host_voice_take_packets(void *buffer, int size);
|
||||
/* the microphone's level (0..1000) */
|
||||
int host_voice_level(void);
|
||||
/* a speaker's packet (sequence numbered), played at these gains */
|
||||
void host_voice_play(int id, int sequence, const void *data, int length, float gain_left, float gain_right);
|
||||
|
||||
/* ---------- Android */
|
||||
|
||||
/* the storage directories the port uses, copied into buffer */
|
||||
|
||||
@@ -0,0 +1,494 @@
|
||||
/*
|
||||
HOST_VOICE.C
|
||||
|
||||
The voice chat's sound (the VR port's; port/linux/game/network_voice.c
|
||||
sends and receives it): the microphone, Opus, and the voices played. It
|
||||
runs here, natively, where SDL's audio threads may call it.
|
||||
|
||||
- The microphone: SDL's default recording device, mono at 16 kHz, with
|
||||
Android's voice communication preset (its echo cancelling and noise
|
||||
suppression: SDL_HINT_ANDROID_AAUDIO_INPUT_PRESET). Every 20 ms of it
|
||||
that is loud enough to be speech (or follows speech closely, VAD_HANGOVER)
|
||||
is encoded (Opus, VOIP, 16 kbit/s) for the guest to send.
|
||||
- The voices: each speaker's packets, decoded as they come (a gap in their
|
||||
numbers concealed by Opus's packet loss concealment), wait in the
|
||||
speaker's buffer until JITTER_START of them is there, and are mixed, each
|
||||
at the gains its latest packet was given (left and right: where the
|
||||
speaker is), into a stream of their own.
|
||||
|
||||
The devices are opened and closed on a thread of their own (voice_worker):
|
||||
opening a microphone can take a long while (an emulator's took a minute),
|
||||
which the game, whose clock the other machines follow, must not wait for.
|
||||
*/
|
||||
|
||||
#include "host.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <opus.h>
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
|
||||
enum
|
||||
{
|
||||
VOICE_RATE = 16000,
|
||||
FRAME_SAMPLES = VOICE_RATE / 50,
|
||||
MAXIMUM_PACKET = 200,
|
||||
MAXIMUM_SPEAKERS = 32,
|
||||
/* a speaker's buffer: two seconds */
|
||||
SPEAKER_BUFFER = VOICE_RATE * 2,
|
||||
/* played from once this much is there, after silence */
|
||||
JITTER_START = FRAME_SAMPLES * 3,
|
||||
/* more than this behind: the oldest dropped */
|
||||
JITTER_MAXIMUM = FRAME_SAMPLES * 15,
|
||||
/* frames sent on after the voice drops below the threshold */
|
||||
VAD_HANGOVER = 15,
|
||||
BITRATE = 16000,
|
||||
};
|
||||
|
||||
struct speaker
|
||||
{
|
||||
int used;
|
||||
int id;
|
||||
OpusDecoder *decoder;
|
||||
int has_sequence;
|
||||
unsigned short sequence;
|
||||
short buffer[SPEAKER_BUFFER];
|
||||
int read;
|
||||
int count;
|
||||
int playing;
|
||||
float gain_left;
|
||||
float gain_right;
|
||||
unsigned long long heard_ticks;
|
||||
};
|
||||
|
||||
static struct
|
||||
{
|
||||
SDL_Mutex *lock;
|
||||
/* the devices wanted, which the worker opens and closes to match
|
||||
(under worker_lock); the recording stream is read under it too */
|
||||
pthread_mutex_t worker_lock;
|
||||
pthread_cond_t worker_wake;
|
||||
int worker_started;
|
||||
int want_playback;
|
||||
int want_recording;
|
||||
/* the microphone */
|
||||
SDL_AudioStream *recording;
|
||||
OpusEncoder *encoder;
|
||||
short frame[FRAME_SAMPLES];
|
||||
int frame_count;
|
||||
int hangover;
|
||||
float threshold;
|
||||
int level;
|
||||
/* the voices */
|
||||
SDL_AudioStream *playback;
|
||||
struct speaker speakers[MAXIMUM_SPEAKERS];
|
||||
float volume;
|
||||
} voice;
|
||||
|
||||
static void voice_lock(void)
|
||||
{
|
||||
if (!voice.lock)
|
||||
voice.lock = SDL_CreateMutex();
|
||||
SDL_LockMutex(voice.lock);
|
||||
}
|
||||
|
||||
static void voice_unlock(void)
|
||||
{
|
||||
SDL_UnlockMutex(voice.lock);
|
||||
}
|
||||
|
||||
/* the voices mixed, as SDL asks for them (on its audio thread) */
|
||||
static void SDLCALL voice_playback_callback(void *userdata, SDL_AudioStream *stream, int additional, int total)
|
||||
{
|
||||
short mixed[2 * 512];
|
||||
(void)userdata;
|
||||
(void)total;
|
||||
|
||||
while (additional > 0)
|
||||
{
|
||||
int frames = additional / 4;
|
||||
int frame;
|
||||
int index;
|
||||
|
||||
if (frames > 512)
|
||||
frames = 512;
|
||||
if (frames <= 0)
|
||||
frames = 1;
|
||||
memset(mixed, 0, sizeof(mixed));
|
||||
voice_lock();
|
||||
for (index = 0; index < MAXIMUM_SPEAKERS; index++)
|
||||
{
|
||||
struct speaker *speaker = &voice.speakers[index];
|
||||
|
||||
if (!speaker->used)
|
||||
continue;
|
||||
if (!speaker->playing && speaker->count >= JITTER_START)
|
||||
speaker->playing = 1;
|
||||
if (!speaker->playing)
|
||||
continue;
|
||||
for (frame = 0; frame < frames && speaker->count > 0; frame++)
|
||||
{
|
||||
float sample = (float)speaker->buffer[speaker->read] * voice.volume;
|
||||
int left = mixed[2 * frame] + (int)(sample * speaker->gain_left);
|
||||
int right = mixed[2 * frame + 1] + (int)(sample * speaker->gain_right);
|
||||
|
||||
mixed[2 * frame] = (short)(left > 32767 ? 32767 : left < -32768 ? -32768 : left);
|
||||
mixed[2 * frame + 1] = (short)(right > 32767 ? 32767 : right < -32768 ? -32768 : right);
|
||||
speaker->read = (speaker->read + 1) % SPEAKER_BUFFER;
|
||||
speaker->count--;
|
||||
}
|
||||
/* (run dry: wait for enough again) */
|
||||
if (!speaker->count)
|
||||
speaker->playing = 0;
|
||||
}
|
||||
voice_unlock();
|
||||
SDL_PutAudioStreamData(stream, mixed, frames * 4);
|
||||
additional -= frames * 4;
|
||||
}
|
||||
}
|
||||
|
||||
static int voice_open_playback(void)
|
||||
{
|
||||
SDL_AudioSpec spec;
|
||||
|
||||
if (voice.playback)
|
||||
return 1;
|
||||
spec.format = SDL_AUDIO_S16;
|
||||
spec.channels = 2;
|
||||
spec.freq = VOICE_RATE;
|
||||
voice.playback = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, voice_playback_callback,
|
||||
NULL);
|
||||
if (!voice.playback)
|
||||
{
|
||||
host_logf(HOST_LOG_WARN, "voice: cannot open the playback stream: %s", SDL_GetError());
|
||||
return 0;
|
||||
}
|
||||
SDL_ResumeAudioStreamDevice(voice.playback);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static SDL_AudioStream *voice_open_recording(void)
|
||||
{
|
||||
SDL_AudioSpec spec;
|
||||
SDL_AudioStream *recording;
|
||||
int error;
|
||||
|
||||
if (!voice.encoder)
|
||||
{
|
||||
voice.encoder = opus_encoder_create(VOICE_RATE, 1, OPUS_APPLICATION_VOIP, &error);
|
||||
if (!voice.encoder)
|
||||
{
|
||||
host_logf(HOST_LOG_WARN, "voice: cannot make the encoder (%d)", error);
|
||||
return NULL;
|
||||
}
|
||||
opus_encoder_ctl(voice.encoder, OPUS_SET_BITRATE(BITRATE));
|
||||
opus_encoder_ctl(voice.encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
|
||||
opus_encoder_ctl(voice.encoder, OPUS_SET_INBAND_FEC(1));
|
||||
opus_encoder_ctl(voice.encoder, OPUS_SET_PACKET_LOSS_PERC(10));
|
||||
}
|
||||
/* Android's voice communication preset: echo cancelling, noise
|
||||
suppression (AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION) */
|
||||
SDL_SetHint(SDL_HINT_ANDROID_AAUDIO_INPUT_PRESET, "7");
|
||||
spec.format = SDL_AUDIO_S16;
|
||||
spec.channels = 1;
|
||||
spec.freq = VOICE_RATE;
|
||||
recording = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, &spec, NULL, NULL);
|
||||
if (!recording)
|
||||
{
|
||||
host_logf(HOST_LOG_WARN, "voice: cannot open the microphone: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
SDL_ResumeAudioStreamDevice(recording);
|
||||
host_logf(HOST_LOG_INFO, "voice: the microphone is open");
|
||||
return recording;
|
||||
}
|
||||
|
||||
static void voice_close_recording(void)
|
||||
{
|
||||
SDL_AudioStream *recording;
|
||||
|
||||
pthread_mutex_lock(&voice.worker_lock);
|
||||
recording = voice.recording;
|
||||
voice.recording = NULL;
|
||||
voice.level = 0;
|
||||
pthread_mutex_unlock(&voice.worker_lock);
|
||||
if (!recording)
|
||||
return;
|
||||
SDL_DestroyAudioStream(recording);
|
||||
host_logf(HOST_LOG_INFO, "voice: the microphone is closed");
|
||||
}
|
||||
|
||||
static void voice_close_playback(void)
|
||||
{
|
||||
SDL_AudioStream *playback = voice.playback;
|
||||
|
||||
voice.playback = NULL;
|
||||
if (playback)
|
||||
SDL_DestroyAudioStream(playback);
|
||||
}
|
||||
|
||||
/* opens and closes the devices as wanted, for as long as the app runs */
|
||||
static void *voice_worker(void *unused)
|
||||
{
|
||||
(void)unused;
|
||||
pthread_mutex_lock(&voice.worker_lock);
|
||||
for (;;)
|
||||
{
|
||||
int want_playback = voice.want_playback;
|
||||
int want_recording = voice.want_recording;
|
||||
int have_playback = voice.playback != NULL;
|
||||
int have_recording = voice.recording != NULL;
|
||||
|
||||
if (want_playback == have_playback && want_recording == have_recording)
|
||||
{
|
||||
pthread_cond_wait(&voice.worker_wake, &voice.worker_lock);
|
||||
continue;
|
||||
}
|
||||
pthread_mutex_unlock(&voice.worker_lock);
|
||||
if (want_recording && !have_recording)
|
||||
{
|
||||
/* (opened outside the lock, which the game reads it under) */
|
||||
SDL_AudioStream *recording = voice_open_recording();
|
||||
|
||||
pthread_mutex_lock(&voice.worker_lock);
|
||||
voice.recording = recording;
|
||||
pthread_mutex_unlock(&voice.worker_lock);
|
||||
/* (a microphone that will not open is not tried again until
|
||||
wanted anew) */
|
||||
if (!recording)
|
||||
{
|
||||
pthread_mutex_lock(&voice.worker_lock);
|
||||
voice.want_recording = 0;
|
||||
pthread_mutex_unlock(&voice.worker_lock);
|
||||
}
|
||||
}
|
||||
else if (!want_recording && have_recording)
|
||||
{
|
||||
voice_close_recording();
|
||||
}
|
||||
if (want_playback && !have_playback)
|
||||
{
|
||||
if (!voice_open_playback())
|
||||
{
|
||||
pthread_mutex_lock(&voice.worker_lock);
|
||||
voice.want_playback = 0;
|
||||
pthread_mutex_unlock(&voice.worker_lock);
|
||||
}
|
||||
}
|
||||
else if (!want_playback && have_playback)
|
||||
{
|
||||
voice_close_playback();
|
||||
}
|
||||
pthread_mutex_lock(&voice.worker_lock);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void voice_want(int playback, int recording)
|
||||
{
|
||||
if (!voice.worker_started)
|
||||
{
|
||||
voice.worker_started = 1;
|
||||
pthread_mutex_init(&voice.worker_lock, NULL);
|
||||
pthread_cond_init(&voice.worker_wake, NULL);
|
||||
if (host_native_thread_create(voice_worker, NULL, 256 * 1024) != 0)
|
||||
{
|
||||
host_logf(HOST_LOG_WARN, "voice: cannot start the device thread");
|
||||
return;
|
||||
}
|
||||
}
|
||||
pthread_mutex_lock(&voice.worker_lock);
|
||||
voice.want_playback = playback;
|
||||
voice.want_recording = recording;
|
||||
pthread_cond_signal(&voice.worker_wake);
|
||||
pthread_mutex_unlock(&voice.worker_lock);
|
||||
}
|
||||
|
||||
/* ---------- the guest's side */
|
||||
|
||||
int host_voice_start(int microphone, float volume, float threshold)
|
||||
{
|
||||
voice_lock();
|
||||
voice.volume = volume < 0.0f ? 0.0f : volume > 4.0f ? 4.0f : volume;
|
||||
voice.threshold = threshold;
|
||||
voice_unlock();
|
||||
voice_want(1, microphone);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void host_voice_stop(void)
|
||||
{
|
||||
int index;
|
||||
|
||||
voice_want(0, 0);
|
||||
voice_lock();
|
||||
for (index = 0; index < MAXIMUM_SPEAKERS; index++)
|
||||
{
|
||||
if (voice.speakers[index].decoder)
|
||||
opus_decoder_destroy(voice.speakers[index].decoder);
|
||||
}
|
||||
memset(voice.speakers, 0, sizeof(voice.speakers));
|
||||
voice_unlock();
|
||||
}
|
||||
|
||||
int host_voice_take_packets(void *buffer, int size)
|
||||
{
|
||||
unsigned char *out = buffer;
|
||||
int used = 0;
|
||||
|
||||
if (!voice.worker_started)
|
||||
return 0;
|
||||
pthread_mutex_lock(&voice.worker_lock);
|
||||
if (!voice.recording)
|
||||
{
|
||||
voice.frame_count = 0;
|
||||
voice.hangover = 0;
|
||||
pthread_mutex_unlock(&voice.worker_lock);
|
||||
return 0;
|
||||
}
|
||||
for (;;)
|
||||
{
|
||||
int wanted = (FRAME_SAMPLES - voice.frame_count) * (int)sizeof(short);
|
||||
int got = SDL_GetAudioStreamData(voice.recording, voice.frame + voice.frame_count, wanted);
|
||||
double energy = 0.0;
|
||||
int index;
|
||||
|
||||
if (got <= 0)
|
||||
break;
|
||||
voice.frame_count += got / (int)sizeof(short);
|
||||
if (voice.frame_count < FRAME_SAMPLES)
|
||||
break;
|
||||
voice.frame_count = 0;
|
||||
for (index = 0; index < FRAME_SAMPLES; index++)
|
||||
energy += (double)voice.frame[index] * voice.frame[index];
|
||||
energy = SDL_sqrt(energy / FRAME_SAMPLES) / 32768.0;
|
||||
voice.level = (int)(energy * 1000.0);
|
||||
if (energy >= voice.threshold)
|
||||
voice.hangover = VAD_HANGOVER;
|
||||
else if (voice.hangover > 0)
|
||||
voice.hangover--;
|
||||
else
|
||||
continue;
|
||||
if (used + 1 + MAXIMUM_PACKET > size)
|
||||
continue;
|
||||
{
|
||||
int length = opus_encode(voice.encoder, voice.frame, FRAME_SAMPLES, out + used + 1, MAXIMUM_PACKET);
|
||||
|
||||
if (length > 0 && length < 256)
|
||||
{
|
||||
out[used] = (unsigned char)length;
|
||||
used += 1 + length;
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&voice.worker_lock);
|
||||
return used;
|
||||
}
|
||||
|
||||
int host_voice_level(void)
|
||||
{
|
||||
return voice.level;
|
||||
}
|
||||
|
||||
void host_voice_play(int id, int sequence, const void *data, int length, float gain_left, float gain_right)
|
||||
{
|
||||
struct speaker *speaker = NULL;
|
||||
struct speaker *oldest = NULL;
|
||||
short decoded[FRAME_SAMPLES * 6];
|
||||
int frames[4];
|
||||
int frame_count = 0;
|
||||
int index;
|
||||
|
||||
if (length <= 0 || length > MAXIMUM_PACKET)
|
||||
return;
|
||||
voice_lock();
|
||||
for (index = 0; index < MAXIMUM_SPEAKERS && !speaker; index++)
|
||||
{
|
||||
if (voice.speakers[index].used && voice.speakers[index].id == id)
|
||||
speaker = &voice.speakers[index];
|
||||
}
|
||||
if (!speaker)
|
||||
{
|
||||
for (index = 0; index < MAXIMUM_SPEAKERS && !speaker; index++)
|
||||
{
|
||||
if (!voice.speakers[index].used)
|
||||
speaker = &voice.speakers[index];
|
||||
else if (!oldest || voice.speakers[index].heard_ticks < oldest->heard_ticks)
|
||||
oldest = &voice.speakers[index];
|
||||
}
|
||||
if (!speaker)
|
||||
speaker = oldest;
|
||||
if (!speaker->decoder)
|
||||
{
|
||||
int error;
|
||||
|
||||
speaker->decoder = opus_decoder_create(VOICE_RATE, 1, &error);
|
||||
if (!speaker->decoder)
|
||||
{
|
||||
voice_unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
opus_decoder_ctl(speaker->decoder, OPUS_RESET_STATE);
|
||||
}
|
||||
speaker->used = 1;
|
||||
speaker->id = id;
|
||||
speaker->has_sequence = 0;
|
||||
speaker->read = speaker->count = 0;
|
||||
speaker->playing = 0;
|
||||
}
|
||||
speaker->heard_ticks = SDL_GetTicks();
|
||||
speaker->gain_left = gain_left;
|
||||
speaker->gain_right = gain_right;
|
||||
{
|
||||
unsigned short number = (unsigned short)sequence;
|
||||
|
||||
if (speaker->has_sequence)
|
||||
{
|
||||
short gap = (short)(number - speaker->sequence);
|
||||
|
||||
/* (late, or again: dropped) */
|
||||
if (gap <= 0)
|
||||
{
|
||||
voice_unlock();
|
||||
return;
|
||||
}
|
||||
/* (lost ones: concealed, a few) */
|
||||
if (gap > 1 && gap <= 4 && speaker->count > 0)
|
||||
{
|
||||
int missing;
|
||||
|
||||
for (missing = 1; missing < gap && frame_count < 3; missing++)
|
||||
{
|
||||
frames[frame_count] = opus_decode(speaker->decoder, NULL, 0, decoded + frame_count * FRAME_SAMPLES,
|
||||
FRAME_SAMPLES, 0);
|
||||
frame_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
speaker->has_sequence = 1;
|
||||
speaker->sequence = number;
|
||||
}
|
||||
frames[frame_count] = opus_decode(speaker->decoder, data, length, decoded + frame_count * FRAME_SAMPLES,
|
||||
FRAME_SAMPLES * 2, 0);
|
||||
frame_count++;
|
||||
for (index = 0; index < frame_count; index++)
|
||||
{
|
||||
int sample;
|
||||
|
||||
for (sample = 0; sample < frames[index] && sample < FRAME_SAMPLES * 2; sample++)
|
||||
{
|
||||
if (speaker->count >= JITTER_MAXIMUM)
|
||||
{
|
||||
speaker->read = (speaker->read + 1) % SPEAKER_BUFFER;
|
||||
speaker->count--;
|
||||
}
|
||||
speaker->buffer[(speaker->read + speaker->count) % SPEAKER_BUFFER] =
|
||||
decoded[index * FRAME_SAMPLES + sample];
|
||||
speaker->count++;
|
||||
}
|
||||
}
|
||||
voice_unlock();
|
||||
}
|
||||
@@ -794,12 +794,6 @@ the eyes reads it. Shown only while it is. */
|
||||
|
||||
#define WRIST_IMAGE_SIZE 512
|
||||
#define CROSSHAIR_IMAGE_SIZE 256
|
||||
/* the names layer: HALO_XR_MAXIMUM_NAMES slots of 4:1, one over another */
|
||||
#define NAMES_IMAGE_WIDTH 512
|
||||
#define NAMES_IMAGE_HEIGHT (NAMES_IMAGE_WIDTH / 4 * HALO_XR_MAXIMUM_NAMES)
|
||||
/* a name's slot, as the game draws it: wider than its image's 4:1 (the
|
||||
game's 640x480 drawn square): the height a slot shows at, to its width */
|
||||
#define NAME_SLOT_ASPECT (30.0f / 160.0f)
|
||||
#define WRIST_BEHIND_PALM 0.05f
|
||||
#define WRIST_DOWN_HANDLE 0.02f
|
||||
#define WRIST_ABOVE_HAND 0.04f
|
||||
@@ -1038,15 +1032,8 @@ int host_xr_initialize(struct halo_xr_info *info, float wanted_refresh_rate)
|
||||
{
|
||||
int size = layer == HALO_XR_LAYER_WRIST || layer == HALO_XR_LAYER_SCOPE ? WRIST_IMAGE_SIZE :
|
||||
layer == HALO_XR_LAYER_CROSSHAIR ? CROSSHAIR_IMAGE_SIZE : 0;
|
||||
int width = size ? size : xr.eye_width;
|
||||
int height = size ? size : xr.eye_height;
|
||||
|
||||
if (layer == HALO_XR_LAYER_NAMES)
|
||||
{
|
||||
width = NAMES_IMAGE_WIDTH;
|
||||
height = NAMES_IMAGE_HEIGHT;
|
||||
}
|
||||
if (!swapchain_create(&xr.swapchains[layer], width, height))
|
||||
if (!swapchain_create(&xr.swapchains[layer], size ? size : xr.eye_width, size ? size : xr.eye_height))
|
||||
return 0;
|
||||
}
|
||||
if (!copy_initialize() || !actions_initialize())
|
||||
@@ -1367,8 +1354,7 @@ int host_xr_end_frame(const struct halo_xr_submit *submit)
|
||||
XrCompositionLayerQuad wrist = { XR_TYPE_COMPOSITION_LAYER_QUAD };
|
||||
XrCompositionLayerQuad scope = { XR_TYPE_COMPOSITION_LAYER_QUAD };
|
||||
XrCompositionLayerQuad crosshair = { XR_TYPE_COMPOSITION_LAYER_QUAD };
|
||||
XrCompositionLayerQuad names[HALO_XR_MAXIMUM_NAMES];
|
||||
const XrCompositionLayerBaseHeader *layers[5 + HALO_XR_MAXIMUM_NAMES];
|
||||
const XrCompositionLayerBaseHeader *layers[5];
|
||||
uint32_t layer_count = 0;
|
||||
int eye;
|
||||
|
||||
@@ -1472,34 +1458,6 @@ int host_xr_end_frame(const struct halo_xr_submit *submit)
|
||||
layers[layer_count++] = (const XrCompositionLayerBaseHeader *)&crosshair;
|
||||
}
|
||||
|
||||
if (xr.frame_state.shouldRender && submit->name_count > 0 && xr.swapchains[HALO_XR_LAYER_NAMES].filled)
|
||||
{
|
||||
struct xr_swapchain *swapchain = &xr.swapchains[HALO_XR_LAYER_NAMES];
|
||||
int slot_height = swapchain->height / HALO_XR_MAXIMUM_NAMES;
|
||||
int name;
|
||||
|
||||
for (name = 0; name < submit->name_count && name < HALO_XR_MAXIMUM_NAMES; name++)
|
||||
{
|
||||
XrCompositionLayerQuad *quad_name = &names[name];
|
||||
|
||||
memset(quad_name, 0, sizeof(*quad_name));
|
||||
quad_name->type = XR_TYPE_COMPOSITION_LAYER_QUAD;
|
||||
quad_name->layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
||||
quad_name->space = xr.local_space;
|
||||
quad_name->eyeVisibility = XR_EYE_VISIBILITY_BOTH;
|
||||
quad_name->subImage.swapchain = swapchain->handle;
|
||||
/* (slot 0 at the image's top: GL's rows run bottom up) */
|
||||
quad_name->subImage.imageRect.offset.x = 0;
|
||||
quad_name->subImage.imageRect.offset.y = swapchain->height - (name + 1) * slot_height;
|
||||
quad_name->subImage.imageRect.extent.width = swapchain->width;
|
||||
quad_name->subImage.imageRect.extent.height = slot_height;
|
||||
quad_name->size.width = submit->name_widths[name];
|
||||
quad_name->size.height = submit->name_widths[name] * NAME_SLOT_ASPECT;
|
||||
pose_to_xr(&quad_name->pose, &submit->name_poses[name]);
|
||||
layers[layer_count++] = (const XrCompositionLayerBaseHeader *)quad_name;
|
||||
}
|
||||
}
|
||||
|
||||
end.displayTime = xr.frame_state.predictedDisplayTime;
|
||||
end.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
|
||||
end.layerCount = layer_count;
|
||||
|
||||
@@ -58,6 +58,11 @@ host_xr_copy_layer_region
|
||||
host_xr_end_frame
|
||||
host_xr_haptic
|
||||
host_xr_set_vignette
|
||||
host_voice_start
|
||||
host_voice_stop
|
||||
host_voice_take_packets
|
||||
host_voice_level
|
||||
host_voice_play
|
||||
host_xr_copy_layer_array
|
||||
host_android_path
|
||||
|
||||
|
||||
@@ -27,10 +27,7 @@ enum
|
||||
HALO_XR_LAYER_WRIST = 3, /* the motion tracker, on the left wrist */
|
||||
HALO_XR_LAYER_SCOPE = 4, /* the zoomed view, a disc over the weapon */
|
||||
HALO_XR_LAYER_CROSSHAIR = 5, /* the weapon's crosshair, where it aims */
|
||||
HALO_XR_LAYER_NAMES = 6, /* players' names, each a quad over a head */
|
||||
HALO_XR_LAYER_COUNT = 7,
|
||||
/* the names layer's slots, one a name, stacked from its top */
|
||||
HALO_XR_MAXIMUM_NAMES = 8,
|
||||
HALO_XR_LAYER_COUNT = 6,
|
||||
|
||||
/* host_xr_copy_layer's clear_alpha: the source's alpha kept, made
|
||||
opaque, or cut to a disc (premultiplied) */
|
||||
@@ -150,12 +147,6 @@ struct halo_xr_submit
|
||||
int crosshair;
|
||||
float crosshair_size;
|
||||
struct halo_xr_pose crosshair_pose;
|
||||
/* the names layer was filled: its first name_count slots, each shown
|
||||
this wide (metres, a slot's height following from its shape), here in
|
||||
LOCAL space, facing the eyes */
|
||||
int name_count;
|
||||
float name_widths[HALO_XR_MAXIMUM_NAMES];
|
||||
struct halo_xr_pose name_poses[HALO_XR_MAXIMUM_NAMES];
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,32 +0,0 @@
|
||||
Newtown
|
||||
Copyright (c) Roger White, 1994. Released by its author into the public domain.
|
||||
|
||||
Newtown-Regular.ttf is the TrueType file of Newtown, as Roger White
|
||||
distributed it (NEWTOW__.TTF, version 1.00, SHA-256
|
||||
376d82c5b660c059c182d848d0a96da9dbe7277d7c8820f6e9a2c99b1a256c65), here
|
||||
renamed. OpenCE (OpenCE-Regular.ttf) is made from it by tools/title_font.py.
|
||||
|
||||
The file's copyright notice ("All Rights Reserved - see .txt file for terms
|
||||
for distribution and use") and the readme it was first distributed with
|
||||
(personal use free, commercial use for a fee) date from 1994. Its author
|
||||
later released all of his fonts into the public domain. His website,
|
||||
rogersfonts.org.uk, said so from 2008 until it closed:
|
||||
|
||||
"Despite what is said in the readme files associated with the fonts all
|
||||
fonts on this site are now public domain, whether used for personal use
|
||||
or for commercial purposes. All that I ask is that the source is
|
||||
acknowledged if you are using any for commercial purposes."
|
||||
|
||||
The statement is in the Internet Archive's copies of the site's home page,
|
||||
the first of them from 24 July 2008:
|
||||
|
||||
https://web.archive.org/web/20080724224405/http://www.rogersfonts.org.uk/body_index.html
|
||||
https://web.archive.org/web/20110724011202/http://www.rogersfonts.org.uk/body_index.html
|
||||
|
||||
and Newtown was one of the site's fonts (its samples are archived from 2004,
|
||||
https://web.archive.org/web/20040909032418/http://www.rogersfonts.org.uk/assets/images/Newtown.gif).
|
||||
The site says its fonts were made from the author's own hand-drawn artwork,
|
||||
scanned and cleaned up.
|
||||
|
||||
As he asked, the source is acknowledged: OpenCE is based on Newtown by
|
||||
Roger White.
|
||||
@@ -1,95 +0,0 @@
|
||||
Copyright 2026 The OpenCE Project Authors, for its changes to Newtown's spacing.
|
||||
OpenCE is based on Newtown, by Roger White (1994), which its author released
|
||||
into the public domain (Newtown-LICENSE.txt).
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,93 +0,0 @@
|
||||
Copyright 2021 The Overpass Project Authors (https://github.com/RedHatOfficial/Overpass)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,45 +0,0 @@
|
||||
# Fonts
|
||||
|
||||
The fonts the game's text is drawn with, at the display's resolution, in
|
||||
place of the bitmap fonts of the maps (`port/linux/src/text_hires.c`).
|
||||
`fonts.json` says which font draws each of the maps' font tags.
|
||||
|
||||
| File | Draws | From |
|
||||
| --- | --- | --- |
|
||||
| `Overpass-900.ttf` | `ui\large_ui`, `ui\interstate` | Overpass, weight 900 (Black) |
|
||||
| `Overpass-750.ttf` | `ui\small_ui` | Overpass, weight 750 |
|
||||
| `OpenCE-Regular.ttf` | the menus' titles, as pictures (`port/assets/titles`) | OpenCE: Newtown, respaced for this project (`tools/title_font.py`) |
|
||||
| `Newtown-Regular.ttf` | (OpenCE's source) | Newtown by Roger White, unmodified |
|
||||
|
||||
The maps' fonts are Interstate (Tobias Frere-Jones, The Font Bureau), a
|
||||
commercial typeface that cannot be shipped. Overpass (Red Hat, Delve
|
||||
Withrington) follows the same Highway Gothic road-sign alphabet and is
|
||||
under the SIL Open Font License 1.1 (`Overpass-OFL.txt`), without a
|
||||
Reserved Font Name. Matched against the maps' glyphs, its weights 900 and 750 overlap
|
||||
them as much as Interstate Bold does, at the same letter widths.
|
||||
|
||||
The menus' titles are pictures of text in the maps, set in another
|
||||
commercial typeface. OpenCE (Open Community Edition) stands in for it:
|
||||
Newtown, a typeface in the same style by Roger White (1994), which its author
|
||||
released into the public domain (`Newtown-LICENSE.txt` quotes his statement
|
||||
and where it is archived), respaced for this project. Only its spacing is
|
||||
changed, its outlines are Newtown's: each letter's side spaces and the
|
||||
kerning pairs are measured from where the letters sit in the maps' titles.
|
||||
OpenCE is under the SIL Open Font License 1.1 (`OpenCE-OFL.txt`), without a
|
||||
Reserved Font Name. It is not embedded: `tools/title_assets.py` draws the
|
||||
titles with it, and the builds embed those pictures. To measure its spacing
|
||||
again and rebuild it:
|
||||
|
||||
python3 tools/title_font.py --measure # paste the values into tools/title_font.py
|
||||
python3 tools/title_font.py
|
||||
python3 tools/title_assets.py --map assets/maps/ui.map
|
||||
|
||||
The Overpass files are static instances of Google Fonts' variable Overpass
|
||||
(`ofl/overpass/Overpass[wght].ttf`, google/fonts commit
|
||||
9710da1eacb3be272583c3224dcb70f9da6eadbb), made with fontTools:
|
||||
|
||||
python3 -m fontTools.varLib.instancer 'Overpass[wght].ttf' wght=900 -o Overpass-900.ttf
|
||||
python3 -m fontTools.varLib.instancer 'Overpass[wght].ttf' wght=750 -o Overpass-750.ttf
|
||||
|
||||
The builds embed the Overpass files (`tools/embed_assets.py`), and the
|
||||
release packages carry `Overpass-OFL.txt`, as the license asks.
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"comment": "Which font draws each of the maps' font tags (port/linux/src/text_hires.c).",
|
||||
"fonts": [
|
||||
{"tag": "ui\\large_ui", "file": "Overpass-900.ttf"},
|
||||
{"tag": "ui\\interstate", "file": "Overpass-900.ttf"},
|
||||
{"tag": "ui\\small_ui", "file": "Overpass-750.ttf"}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 369 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 701 B |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 502 B |
|
Before Width: | Height: | Size: 504 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 275 B |
|
Before Width: | Height: | Size: 268 B |
|
Before Width: | Height: | Size: 361 B |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- ammo meter fill alpha: slanted bar whose top sweeps up into a tab on the right -->
|
||||
<path d="M7.56,32 H209.2 C289.7,32 340.8,7.2 353.7,0 H406.9 A8.3,8.3 0 0 1 414.1,11.4 L393.2,56 H5 A5,5 0 0 1 0,51 V48.1 Z" fill="#fff"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 323 B |
@@ -1,66 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="128" viewBox="0 0 512 128">
|
||||
<!-- 3 rows x 20 slanted bullet rounds (alpha mask) -->
|
||||
<defs>
|
||||
<path id="b" d="M51.34,0 H51.79 L54.02,6 L42.83,30 H32.47 L43.71,5.89 Z" fill="#fff"/>
|
||||
</defs>
|
||||
<use href="#b"/>
|
||||
<use href="#b" x="20.63" y="0"/>
|
||||
<use href="#b" x="41.26" y="0"/>
|
||||
<use href="#b" x="61.89" y="0"/>
|
||||
<use href="#b" x="82.52" y="0"/>
|
||||
<use href="#b" x="103.16" y="0"/>
|
||||
<use href="#b" x="123.79" y="0"/>
|
||||
<use href="#b" x="144.42" y="0"/>
|
||||
<use href="#b" x="165.05" y="0"/>
|
||||
<use href="#b" x="185.68" y="0"/>
|
||||
<use href="#b" x="206.31" y="0"/>
|
||||
<use href="#b" x="226.94" y="0"/>
|
||||
<use href="#b" x="247.57" y="0"/>
|
||||
<use href="#b" x="268.20" y="0"/>
|
||||
<use href="#b" x="288.83" y="0"/>
|
||||
<use href="#b" x="309.47" y="0"/>
|
||||
<use href="#b" x="330.10" y="0"/>
|
||||
<use href="#b" x="350.73" y="0"/>
|
||||
<use href="#b" x="371.36" y="0"/>
|
||||
<use href="#b" x="391.99" y="0"/>
|
||||
<use href="#b" x="-15.85" y="34"/>
|
||||
<use href="#b" x="4.78" y="34"/>
|
||||
<use href="#b" x="25.41" y="34"/>
|
||||
<use href="#b" x="46.04" y="34"/>
|
||||
<use href="#b" x="66.67" y="34"/>
|
||||
<use href="#b" x="87.30" y="34"/>
|
||||
<use href="#b" x="107.93" y="34"/>
|
||||
<use href="#b" x="128.56" y="34"/>
|
||||
<use href="#b" x="149.19" y="34"/>
|
||||
<use href="#b" x="169.82" y="34"/>
|
||||
<use href="#b" x="190.46" y="34"/>
|
||||
<use href="#b" x="211.09" y="34"/>
|
||||
<use href="#b" x="231.72" y="34"/>
|
||||
<use href="#b" x="252.35" y="34"/>
|
||||
<use href="#b" x="272.98" y="34"/>
|
||||
<use href="#b" x="293.61" y="34"/>
|
||||
<use href="#b" x="314.24" y="34"/>
|
||||
<use href="#b" x="334.87" y="34"/>
|
||||
<use href="#b" x="355.50" y="34"/>
|
||||
<use href="#b" x="376.14" y="34"/>
|
||||
<use href="#b" x="-31.71" y="68"/>
|
||||
<use href="#b" x="-11.08" y="68"/>
|
||||
<use href="#b" x="9.55" y="68"/>
|
||||
<use href="#b" x="30.19" y="68"/>
|
||||
<use href="#b" x="50.82" y="68"/>
|
||||
<use href="#b" x="71.45" y="68"/>
|
||||
<use href="#b" x="92.08" y="68"/>
|
||||
<use href="#b" x="112.71" y="68"/>
|
||||
<use href="#b" x="133.34" y="68"/>
|
||||
<use href="#b" x="153.97" y="68"/>
|
||||
<use href="#b" x="174.60" y="68"/>
|
||||
<use href="#b" x="195.23" y="68"/>
|
||||
<use href="#b" x="215.86" y="68"/>
|
||||
<use href="#b" x="236.49" y="68"/>
|
||||
<use href="#b" x="257.13" y="68"/>
|
||||
<use href="#b" x="277.76" y="68"/>
|
||||
<use href="#b" x="298.39" y="68"/>
|
||||
<use href="#b" x="319.02" y="68"/>
|
||||
<use href="#b" x="339.65" y="68"/>
|
||||
<use href="#b" x="360.28" y="68"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,11 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- rocket ammo: tail fin + body, grey translucent fill with white outline; pair repeated -->
|
||||
<defs>
|
||||
<g id="rocket" fill="#a6a6a6" fill-opacity=".651" stroke="#fff" stroke-width="4.1" stroke-miterlimit="10">
|
||||
<path d="M30.7,1.35 H51.4 L58.4,22 H90.6 L79.5,46 H48.1 L24.5,62.2 H3.5 L18.4,46.2 H11.6 L22.65,22 H29.35 Z"/>
|
||||
<path d="M88.5,48.4 L124.3,52 L191.9,46.1 L210.8,32.8 L202.7,21.9 L142.6,16 L101.8,19.8 Z"/>
|
||||
</g>
|
||||
</defs>
|
||||
<use href="#rocket"/>
|
||||
<use href="#rocket" x="214"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 644 B |
@@ -1,10 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- slanted meter bar, grey translucent fill fading slightly darker downwards (rgb = alpha) -->
|
||||
<defs>
|
||||
<linearGradient id="g" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="0" y2="44">
|
||||
<stop offset="0" stop-color="#c7c7c7" stop-opacity=".781"/>
|
||||
<stop offset="1" stop-color="#b4b4b4" stop-opacity=".705"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M20,0 H412 L392,44 H0 Z" fill="url(#g)"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 506 B |
@@ -1,26 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- 20 slanted blade ticks (alpha mask) -->
|
||||
<defs>
|
||||
<path id="t" d="M28.03,0 H40.39 L17.73,48.6 L0.58,59.39 L1.03,57.87 Z" fill="#fff"/>
|
||||
</defs>
|
||||
<use href="#t" x="0.000"/>
|
||||
<use href="#t" x="20.704"/>
|
||||
<use href="#t" x="41.408"/>
|
||||
<use href="#t" x="62.112"/>
|
||||
<use href="#t" x="82.816"/>
|
||||
<use href="#t" x="103.520"/>
|
||||
<use href="#t" x="124.224"/>
|
||||
<use href="#t" x="144.928"/>
|
||||
<use href="#t" x="165.632"/>
|
||||
<use href="#t" x="186.336"/>
|
||||
<use href="#t" x="207.040"/>
|
||||
<use href="#t" x="227.744"/>
|
||||
<use href="#t" x="248.448"/>
|
||||
<use href="#t" x="269.152"/>
|
||||
<use href="#t" x="289.856"/>
|
||||
<use href="#t" x="310.560"/>
|
||||
<use href="#t" x="331.264"/>
|
||||
<use href="#t" x="351.968"/>
|
||||
<use href="#t" x="372.672"/>
|
||||
<use href="#t" x="393.376"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 905 B |
@@ -1,20 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- 12 tilted shotgun shells (alpha mask): tapered body with rim + separate tip -->
|
||||
<defs>
|
||||
<path id="s" transform="translate(15.87,38.77) rotate(25)" fill="#fff"
|
||||
d="M-8.04,-18.2 H8.04 V4.82 L6.23,12.94 H9.78 V17.06 H-9.78 V12.94 H-6.23 L-8.04,4.82 Z
|
||||
M-8.08,-21.95 H8.08 L4.93,-40.72 H-4.93 Z"/>
|
||||
</defs>
|
||||
<use href="#s" x="0.000"/>
|
||||
<use href="#s" x="35.273"/>
|
||||
<use href="#s" x="70.546"/>
|
||||
<use href="#s" x="105.819"/>
|
||||
<use href="#s" x="141.092"/>
|
||||
<use href="#s" x="176.365"/>
|
||||
<use href="#s" x="211.638"/>
|
||||
<use href="#s" x="246.911"/>
|
||||
<use href="#s" x="282.184"/>
|
||||
<use href="#s" x="317.457"/>
|
||||
<use href="#s" x="352.730"/>
|
||||
<use href="#s" x="388.003"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 841 B |
@@ -1,11 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="128" viewBox="0 0 512 128">
|
||||
<!-- 2x2 rifle rounds (alpha mask): rimmed case with rounded shoulder + ogive bullet -->
|
||||
<defs>
|
||||
<path id="r" fill="#fff" d="M20,0 H28 V5.5 H101.8 V6.4 H108.5 A7.5,7.5 0 0 1 116,13.9 V22.1 A7.5,7.5 0 0 1 108.5,29.6 H101.8 V30.5 H28 V36 H20 Z
|
||||
M120,8 H135 C163.6,9 194.5,13.65 212.3,18 C194.5,22.35 163.6,27 135,28 H120 Z"/>
|
||||
</defs>
|
||||
<use href="#r"/>
|
||||
<use href="#r" x="208"/>
|
||||
<use href="#r" x="-20" y="44"/>
|
||||
<use href="#r" x="188" y="44"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 598 B |
@@ -1,19 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="64" viewBox="0 0 512 64">
|
||||
<!-- 12 two-part slanted ammo ticks (alpha mask); lower part has a flared foot -->
|
||||
<defs>
|
||||
<path id="t" fill="#fff" d="M33.4,0 h19 L33.76,40 h-19 Z
|
||||
M12.89,44 h19 L24.43,60 h2.7 L25.03,64.5 H0.66 L2.75,60 h2.68 Z"/>
|
||||
</defs>
|
||||
<use href="#t" x="0.000"/>
|
||||
<use href="#t" x="34.973"/>
|
||||
<use href="#t" x="69.946"/>
|
||||
<use href="#t" x="104.919"/>
|
||||
<use href="#t" x="139.892"/>
|
||||
<use href="#t" x="174.865"/>
|
||||
<use href="#t" x="209.838"/>
|
||||
<use href="#t" x="244.811"/>
|
||||
<use href="#t" x="279.784"/>
|
||||
<use href="#t" x="314.757"/>
|
||||
<use href="#t" x="349.730"/>
|
||||
<use href="#t" x="384.703"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 742 B |
@@ -1,11 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="128" viewBox="0 0 512 128">
|
||||
<!-- ammo meter outline: rounded slanted frame (white) with a faint inner grid -->
|
||||
<defs>
|
||||
<path id="frame" d="M25.24,2 H411.34 A9.84,9.84 0 0 1 420.27,15.97 L398.97,62 H9.4 A7.61,7.61 0 0 1 2.48,51.19 Z"/>
|
||||
<mask id="inside" maskUnits="userSpaceOnUse" x="0" y="0" width="512" height="128">
|
||||
<use href="#frame" fill="#fff"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<path mask="url(#inside)" fill="#404040" fill-opacity=".251" d="M28,0 h4 v64 h-4 Z M56,0 h4 v64 h-4 Z M84,0 h4 v64 h-4 Z M112,0 h4 v64 h-4 Z M140,0 h4 v64 h-4 Z M168,0 h4 v64 h-4 Z M196,0 h4 v64 h-4 Z M224,0 h4 v64 h-4 Z M252,0 h4 v64 h-4 Z M280,0 h4 v64 h-4 Z M308,0 h4 v64 h-4 Z M336,0 h4 v64 h-4 Z M364,0 h4 v64 h-4 Z M392,0 h4 v64 h-4 Z M420,0 h4 v64 h-4 Z M0,16 h430 v4 h-430 Z M0,32 h430 v4 h-430 Z M0,48 h430 v4 h-430 Z"/>
|
||||
<use href="#frame" fill="none" stroke="#fff" stroke-width="4.04" stroke-miterlimit="10"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 981 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="32" viewBox="0 0 512 32">
|
||||
<!-- thin slanted meter frame, light grey (rgb = alpha) -->
|
||||
<path d="M9.24,2 H406.76 L400.76,14 H3.24 Z" fill="none" stroke="#ccc" stroke-opacity=".8" stroke-width="4" stroke-miterlimit="10"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 290 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="128" viewBox="0 0 512 128">
|
||||
<!-- slanted meter frame with rounded top corners (white) -->
|
||||
<path d="M28.69,8.58 A11.67,11.67 0 0 1 39.19,2 H280.64 A7.71,7.71 0 0 1 287.58,13.08 L263.85,62 H2.76 Z" fill="none" stroke="#fff" stroke-width="4.14" stroke-miterlimit="10"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 338 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="128" viewBox="0 0 512 128">
|
||||
<!-- short slanted meter frame, top-right and bottom-left corners rounded (white) -->
|
||||
<path d="M25.2,2 H138.86 A7.38,7.38 0 0 1 145.58,12.44 L122.95,62 H8.52 A6.89,6.89 0 0 1 2.26,52.26 Z" fill="none" stroke="#fff" stroke-width="4.17" stroke-miterlimit="10"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 359 B |