mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-28 07:00:32 +01:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0badc451cb | ||
|
|
39a6cc9794 |
No files matched your search
@@ -49,8 +49,13 @@ enum
|
|||||||
|
|
||||||
/* how long a player's hands drive them after the last came (ticks): longer
|
/* how long a player's hands drive them after the last came (ticks): longer
|
||||||
and they are driven as before (the hands stopped coming: the player left
|
and they are driven as before (the hands stopped coming: the player left
|
||||||
VR, or the network) */
|
VR, or the network). Where the weapon aims and fires from is held a
|
||||||
|
second, over the gaps of a network that delivers in bursts (as the input
|
||||||
|
the netcode relays is); a grenade held, only a third of one, so that it
|
||||||
|
does not hang in the hand after the hand let go. The host relays only
|
||||||
|
hands as fresh as that. */
|
||||||
#define VR_POSE_FRESH_TICKS 10
|
#define VR_POSE_FRESH_TICKS 10
|
||||||
|
#define VR_AIM_HOLD_TICKS 30
|
||||||
/* how long a throw is sent after it (ticks), and taken after it came */
|
/* how long a throw is sent after it (ticks), and taken after it came */
|
||||||
#define VR_THROW_TICKS 30
|
#define VR_THROW_TICKS 30
|
||||||
/* offsets from the unit's camera: in 1/4096 of a world unit, no further
|
/* offsets from the unit's camera: in 1/4096 of a world unit, no further
|
||||||
@@ -152,7 +157,8 @@ static boolean network_vr_direction_unpack(
|
|||||||
|
|
||||||
/* a player of another machine whose hands came lately, or NULL */
|
/* a player of another machine whose hands came lately, or NULL */
|
||||||
static struct network_vr_player *network_vr_remote_player(
|
static struct network_vr_player *network_vr_remote_player(
|
||||||
long player_index)
|
long player_index,
|
||||||
|
long fresh_ticks)
|
||||||
{
|
{
|
||||||
struct network_vr_player *vr_player;
|
struct network_vr_player *vr_player;
|
||||||
long absolute_index;
|
long absolute_index;
|
||||||
@@ -163,7 +169,7 @@ static struct network_vr_player *network_vr_remote_player(
|
|||||||
if (absolute_index < 0 || absolute_index >= MAXIMUM_TRACKED_PLAYERS)
|
if (absolute_index < 0 || absolute_index >= MAXIMUM_TRACKED_PLAYERS)
|
||||||
return NULL;
|
return NULL;
|
||||||
vr_player = &network_vr_players[absolute_index];
|
vr_player = &network_vr_players[absolute_index];
|
||||||
if (!vr_player->valid || game_time_get() - vr_player->received_time > VR_POSE_FRESH_TICKS ||
|
if (!vr_player->valid || game_time_get() - vr_player->received_time > fresh_ticks ||
|
||||||
game_time_get() < vr_player->received_time)
|
game_time_get() < vr_player->received_time)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -395,7 +401,7 @@ void network_vr_handle_poses(
|
|||||||
|
|
||||||
int halo_vr_network_aim(long player_index, float vector[3])
|
int halo_vr_network_aim(long player_index, float vector[3])
|
||||||
{
|
{
|
||||||
struct network_vr_player *vr_player = network_vr_remote_player(player_index);
|
struct network_vr_player *vr_player = network_vr_remote_player(player_index, VR_AIM_HOLD_TICKS);
|
||||||
real_vector3d aim;
|
real_vector3d aim;
|
||||||
|
|
||||||
if (!vr_player || !TEST_FLAG(vr_player->pose.flags, _vr_pose_aim_bit) ||
|
if (!vr_player || !TEST_FLAG(vr_player->pose.flags, _vr_pose_aim_bit) ||
|
||||||
@@ -412,7 +418,7 @@ int halo_vr_network_aim(long player_index, float vector[3])
|
|||||||
|
|
||||||
int halo_vr_network_weapon_origin(long player_index, long unit_index, float origin[3])
|
int halo_vr_network_weapon_origin(long player_index, long unit_index, float origin[3])
|
||||||
{
|
{
|
||||||
struct network_vr_player *vr_player = network_vr_remote_player(player_index);
|
struct network_vr_player *vr_player = network_vr_remote_player(player_index, VR_AIM_HOLD_TICKS);
|
||||||
real_vector3d offset;
|
real_vector3d offset;
|
||||||
real_point3d camera;
|
real_point3d camera;
|
||||||
|
|
||||||
@@ -430,7 +436,7 @@ int halo_vr_network_weapon_origin(long player_index, long unit_index, float orig
|
|||||||
|
|
||||||
int halo_vr_network_grenade_held(long player_index)
|
int halo_vr_network_grenade_held(long player_index)
|
||||||
{
|
{
|
||||||
struct network_vr_player *vr_player = network_vr_remote_player(player_index);
|
struct network_vr_player *vr_player = network_vr_remote_player(player_index, VR_POSE_FRESH_TICKS);
|
||||||
|
|
||||||
return vr_player && TEST_FLAG(vr_player->pose.flags, _vr_pose_grenade_held_bit);
|
return vr_player && TEST_FLAG(vr_player->pose.flags, _vr_pose_grenade_held_bit);
|
||||||
}
|
}
|
||||||
@@ -438,7 +444,7 @@ int halo_vr_network_grenade_held(long player_index)
|
|||||||
int halo_vr_network_grenade_throw(long player_index, long unit_index, float origin[3], float direction[3],
|
int halo_vr_network_grenade_throw(long player_index, long unit_index, float origin[3], float direction[3],
|
||||||
float *strength)
|
float *strength)
|
||||||
{
|
{
|
||||||
struct network_vr_player *vr_player = network_vr_remote_player(player_index);
|
struct network_vr_player *vr_player = network_vr_remote_player(player_index, VR_THROW_TICKS);
|
||||||
real_vector3d offset;
|
real_vector3d offset;
|
||||||
real_vector3d way;
|
real_vector3d way;
|
||||||
real_point3d camera;
|
real_point3d camera;
|
||||||
|
|||||||
+49
-2
@@ -2314,10 +2314,48 @@ int p2p_join_invite(const char *text)
|
|||||||
return result > 0;
|
return result > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int p2p_join_code(char *text, int size)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&p2p_lock);
|
||||||
|
result = p2p.running && p2p_code_hosted(text, size);
|
||||||
|
pthread_mutex_unlock(&p2p_lock);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int p2p_join_with_code(const char *typed)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&p2p_lock);
|
||||||
|
result = p2p.running && p2p_code_look_up(typed);
|
||||||
|
pthread_mutex_unlock(&p2p_lock);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int p2p_join_code_status(void)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&p2p_lock);
|
||||||
|
status = p2p.running ? p2p_code_status() : P2P_CODE_OFFLINE;
|
||||||
|
pthread_mutex_unlock(&p2p_lock);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void p2p_join_code_clear_status(void)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&p2p_lock);
|
||||||
|
p2p_code_clear_status();
|
||||||
|
pthread_mutex_unlock(&p2p_lock);
|
||||||
|
}
|
||||||
|
|
||||||
void p2p_invite_received(const char *text)
|
void p2p_invite_received(const char *text)
|
||||||
{
|
{
|
||||||
/* (an older version's is logged as such) */
|
/* (an older version's is logged as such; a join code is looked up,
|
||||||
if (!join_invite(text))
|
p2p_code.c) */
|
||||||
|
if (!join_invite(text) && !p2p_code_look_up(text))
|
||||||
platform_log("Internet play: that is not an invite");
|
platform_log("Internet play: that is not an invite");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2382,6 +2420,14 @@ static void update_hosting(void)
|
|||||||
p2p.stun_started = 1;
|
p2p.stun_started = 1;
|
||||||
p2p_signal_start();
|
p2p_signal_start();
|
||||||
p2p_signal_host(p2p.token);
|
p2p_signal_host(p2p.token);
|
||||||
|
{
|
||||||
|
/* (and a join code for it, p2p_code.c) */
|
||||||
|
unsigned char bytes[P2P_KEY_HASH_SIZE + P2P_TOKEN_SIZE];
|
||||||
|
|
||||||
|
p2p_key_hash(p2p_public_key(), bytes);
|
||||||
|
memcpy(bytes + P2P_KEY_HASH_SIZE, p2p.token, P2P_TOKEN_SIZE);
|
||||||
|
p2p_code_host(bytes);
|
||||||
|
}
|
||||||
platform_log("Internet play: hosting. Invite players with this link (it only works while this "
|
platform_log("Internet play: hosting. Invite players with this link (it only works while this "
|
||||||
"copy of the game runs): %s", p2p.invite);
|
"copy of the game runs): %s", p2p.invite);
|
||||||
if (!p2p.invite_copied)
|
if (!p2p.invite_copied)
|
||||||
@@ -2396,6 +2442,7 @@ static void update_hosting(void)
|
|||||||
{
|
{
|
||||||
p2p.hosting = 0;
|
p2p.hosting = 0;
|
||||||
p2p_signal_stop_hosting();
|
p2p_signal_stop_hosting();
|
||||||
|
p2p_code_stop_hosting();
|
||||||
p2p_discord_set_hosting(NULL, 0, 0);
|
p2p_discord_set_hosting(NULL, 0, 0);
|
||||||
}
|
}
|
||||||
if (p2p.hosting)
|
if (p2p.hosting)
|
||||||
|
|||||||
@@ -26,6 +26,24 @@ int p2p_hand_off_invite(void);
|
|||||||
words around it. Returns nonzero if it held an invite */
|
words around it. Returns nonzero if it held an invite */
|
||||||
int p2p_join_invite(const char *text);
|
int p2p_join_invite(const char *text);
|
||||||
|
|
||||||
|
/* Join codes (p2p_code.c): the hosted game's code as XXXX-XXXX (0 if
|
||||||
|
none); looking a typed one up, which then joins as its invite does (0 if
|
||||||
|
it is not a code, or internet play is off); and how the look up goes */
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
P2P_CODE_IDLE,
|
||||||
|
P2P_CODE_LOOKING,
|
||||||
|
P2P_CODE_FOUND,
|
||||||
|
P2P_CODE_NOT_FOUND,
|
||||||
|
P2P_CODE_INVALID,
|
||||||
|
P2P_CODE_OWN,
|
||||||
|
P2P_CODE_OFFLINE,
|
||||||
|
};
|
||||||
|
int p2p_join_code(char *text, int size);
|
||||||
|
int p2p_join_with_code(const char *typed);
|
||||||
|
int p2p_join_code_status(void);
|
||||||
|
void p2p_join_code_clear_status(void);
|
||||||
|
|
||||||
/* this machine's identifier, which its XNADDR carries (6 bytes) */
|
/* this machine's identifier, which its XNADDR carries (6 bytes) */
|
||||||
const unsigned char *p2p_identifier(void);
|
const unsigned char *p2p_identifier(void);
|
||||||
/* the address the game reaches the machine with this identifier at, if it
|
/* the address the game reaches the machine with this identifier at, if it
|
||||||
|
|||||||
@@ -0,0 +1,317 @@
|
|||||||
|
/*
|
||||||
|
P2P_CODE.C
|
||||||
|
|
||||||
|
Join codes (the VR port's): a short code that stands for a hosted game's
|
||||||
|
invite (p2p.c), for a player who cannot pass a link on, as in a headset,
|
||||||
|
where a link is not copied out and 64 hex digits are not typed in.
|
||||||
|
|
||||||
|
- A host makes a code when it starts hosting: 8 characters of Crockford's
|
||||||
|
base 32 (40 bits), shown as XXXX-XXXX (ui_join_code.c draws it in the
|
||||||
|
lobby). It lasts while the host hosts.
|
||||||
|
- From the code come a topic and a key (PBKDF2-HMAC-SHA256, slow on
|
||||||
|
purpose: CODE_KDF_ITERATIONS). The host listens on the topic through the
|
||||||
|
signalling brokers (p2p_signal.c). A player who types the code asks
|
||||||
|
there (ASK: a nonce), and the host answers with its invite, sealed with
|
||||||
|
the key, with the nonce (INVITE); the asker opens it and joins as an
|
||||||
|
invite link would.
|
||||||
|
- The brokers, and anyone who watches them, see the topic, but cannot tell
|
||||||
|
the code from it, or open the answer, short of working the slow
|
||||||
|
derivation out for codes until one fits; a code is guessed at one in 2^40
|
||||||
|
of them. An asker who has the code has the invite, as one given the link
|
||||||
|
has: it joins as anyone invited does.
|
||||||
|
|
||||||
|
A build without this (another of the same network version) has no codes,
|
||||||
|
and its players join with the link (which the host still logs and copies).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include "posix.h"
|
||||||
|
#include "p2p_internal.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
CODE_SYMBOLS = 8,
|
||||||
|
CODE_KDF_ITERATIONS = 20000,
|
||||||
|
CODE_NONCE_SIZE = 8,
|
||||||
|
CODE_INVITE_SIZE = P2P_KEY_HASH_SIZE + P2P_TOKEN_SIZE,
|
||||||
|
CODE_MESSAGE_VERSION = 1,
|
||||||
|
/* milliseconds */
|
||||||
|
ASK_INTERVAL = 1000,
|
||||||
|
LOOK_TIMEOUT = 20000,
|
||||||
|
/* the host's answers: this many at once, and one more each this often */
|
||||||
|
MAXIMUM_ANSWERS = 8,
|
||||||
|
ANSWER_INTERVAL = 250,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
_code_message_ask = 'Q',
|
||||||
|
_code_message_invite = 'I',
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Crockford's base 32: no I, L, O or U */
|
||||||
|
static const char code_alphabet[] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
||||||
|
|
||||||
|
static struct
|
||||||
|
{
|
||||||
|
/* hosting: the code, its topic and key, and the invite it stands for */
|
||||||
|
int hosting;
|
||||||
|
char host_code[CODE_SYMBOLS + 1];
|
||||||
|
char host_topic[P2P_CODE_TOPIC_SIZE];
|
||||||
|
unsigned char host_key[P2P_SHA256_SIZE];
|
||||||
|
unsigned char invite[CODE_INVITE_SIZE];
|
||||||
|
int answers_left;
|
||||||
|
unsigned long answers_time;
|
||||||
|
|
||||||
|
/* looking a code up */
|
||||||
|
int status;
|
||||||
|
char look_topic[P2P_CODE_TOPIC_SIZE];
|
||||||
|
unsigned char look_key[P2P_SHA256_SIZE];
|
||||||
|
unsigned char look_nonce[CODE_NONCE_SIZE];
|
||||||
|
unsigned long look_started_time;
|
||||||
|
unsigned long asked_time;
|
||||||
|
int asked;
|
||||||
|
} code;
|
||||||
|
|
||||||
|
static int code_elapsed(unsigned long since, unsigned long time)
|
||||||
|
{
|
||||||
|
return (unsigned int)(p2p_now() - since) >= (unsigned int)time;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the code's symbols from what was typed: letters and digits, read as
|
||||||
|
Crockford reads them (O as 0, I and L as 1), the rest (dashes, spaces)
|
||||||
|
left out; 0 unless they are exactly a code's */
|
||||||
|
static int code_normalize(const char *text, char *symbols)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (; *text; text++)
|
||||||
|
{
|
||||||
|
char character = *text;
|
||||||
|
|
||||||
|
if (character >= 'a' && character <= 'z')
|
||||||
|
character = (char)(character - 'a' + 'A');
|
||||||
|
if (character == 'O')
|
||||||
|
character = '0';
|
||||||
|
else if (character == 'I' || character == 'L')
|
||||||
|
character = '1';
|
||||||
|
if (!strchr(code_alphabet, character) || !character)
|
||||||
|
{
|
||||||
|
if (character == '-' || character == ' ' || character == '\t' || character == '\r' ||
|
||||||
|
character == '\n')
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (count == CODE_SYMBOLS)
|
||||||
|
return 0;
|
||||||
|
symbols[count++] = character;
|
||||||
|
}
|
||||||
|
symbols[count] = 0;
|
||||||
|
return count == CODE_SYMBOLS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the topic and key of a code (PBKDF2-HMAC-SHA256, one block) */
|
||||||
|
static void code_derive(const char *symbols, char *topic, unsigned char *key)
|
||||||
|
{
|
||||||
|
static const char salt[] = "halo-ce-vr join code 1";
|
||||||
|
unsigned char block[sizeof(salt) - 1 + 4];
|
||||||
|
unsigned char u[P2P_SHA256_SIZE];
|
||||||
|
unsigned char derived[P2P_SHA256_SIZE];
|
||||||
|
unsigned char digest[P2P_SHA256_SIZE];
|
||||||
|
char hex[2 * P2P_SHA256_SIZE + 1];
|
||||||
|
int iteration;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
memcpy(block, salt, sizeof(salt) - 1);
|
||||||
|
block[sizeof(salt) - 1] = 0;
|
||||||
|
block[sizeof(salt)] = 0;
|
||||||
|
block[sizeof(salt) + 1] = 0;
|
||||||
|
block[sizeof(salt) + 2] = 1;
|
||||||
|
p2p_hmac_sha256((const unsigned char *)symbols, CODE_SYMBOLS, block, sizeof(block), u);
|
||||||
|
memcpy(derived, u, sizeof(derived));
|
||||||
|
for (iteration = 1; iteration < CODE_KDF_ITERATIONS; iteration++)
|
||||||
|
{
|
||||||
|
p2p_hmac_sha256((const unsigned char *)symbols, CODE_SYMBOLS, u, sizeof(u), u);
|
||||||
|
for (index = 0; index < P2P_SHA256_SIZE; index++)
|
||||||
|
derived[index] ^= u[index];
|
||||||
|
}
|
||||||
|
p2p_hmac_sha256(derived, sizeof(derived), "topic", 5, digest);
|
||||||
|
p2p_hex(digest, 16, hex);
|
||||||
|
snprintf(topic, P2P_CODE_TOPIC_SIZE, "hcvr/1/%s", hex);
|
||||||
|
p2p_hmac_sha256(derived, sizeof(derived), "seal", 4, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- p2p.c's side (under p2p_lock) */
|
||||||
|
|
||||||
|
void p2p_code_host(const unsigned char *invite)
|
||||||
|
{
|
||||||
|
unsigned char random[CODE_SYMBOLS];
|
||||||
|
int index;
|
||||||
|
|
||||||
|
posix_random_bytes(random, sizeof(random));
|
||||||
|
for (index = 0; index < CODE_SYMBOLS; index++)
|
||||||
|
code.host_code[index] = code_alphabet[random[index] & 31];
|
||||||
|
code.host_code[CODE_SYMBOLS] = 0;
|
||||||
|
memcpy(code.invite, invite, CODE_INVITE_SIZE);
|
||||||
|
code_derive(code.host_code, code.host_topic, code.host_key);
|
||||||
|
code.answers_left = MAXIMUM_ANSWERS;
|
||||||
|
code.answers_time = p2p_now();
|
||||||
|
code.hosting = 1;
|
||||||
|
platform_log("Internet play: join code %.4s-%.4s", code.host_code, code.host_code + 4);
|
||||||
|
p2p_signal_sync_topics();
|
||||||
|
}
|
||||||
|
|
||||||
|
void p2p_code_stop_hosting(void)
|
||||||
|
{
|
||||||
|
code.hosting = 0;
|
||||||
|
code.host_code[0] = 0;
|
||||||
|
p2p_signal_sync_topics();
|
||||||
|
}
|
||||||
|
|
||||||
|
int p2p_code_hosted(char *text, int size)
|
||||||
|
{
|
||||||
|
if (!code.hosting || size < CODE_SYMBOLS + 2)
|
||||||
|
return 0;
|
||||||
|
snprintf(text, (size_t)size, "%.4s-%.4s", code.host_code, code.host_code + 4);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int p2p_code_look_up(const char *typed)
|
||||||
|
{
|
||||||
|
char symbols[CODE_SYMBOLS + 1];
|
||||||
|
|
||||||
|
if (!code_normalize(typed, symbols))
|
||||||
|
{
|
||||||
|
code.status = P2P_CODE_INVALID;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (code.hosting && !strcmp(symbols, code.host_code))
|
||||||
|
{
|
||||||
|
code.status = P2P_CODE_OWN;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
code_derive(symbols, code.look_topic, code.look_key);
|
||||||
|
posix_random_bytes(code.look_nonce, sizeof(code.look_nonce));
|
||||||
|
code.look_started_time = p2p_now();
|
||||||
|
code.asked = 0;
|
||||||
|
code.status = P2P_CODE_LOOKING;
|
||||||
|
p2p_signal_sync_topics();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int p2p_code_status(void)
|
||||||
|
{
|
||||||
|
return code.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void p2p_code_clear_status(void)
|
||||||
|
{
|
||||||
|
if (code.status != P2P_CODE_LOOKING)
|
||||||
|
code.status = P2P_CODE_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- p2p_signal.c's side (under p2p_lock) */
|
||||||
|
|
||||||
|
void p2p_code_topics(const char **host_topic, const char **look_topic)
|
||||||
|
{
|
||||||
|
*host_topic = code.hosting ? code.host_topic : "";
|
||||||
|
*look_topic = code.status == P2P_CODE_LOOKING ? code.look_topic : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void code_answer(const unsigned char *nonce)
|
||||||
|
{
|
||||||
|
unsigned char message[2 + CODE_NONCE_SIZE + CODE_INVITE_SIZE];
|
||||||
|
unsigned char sealed[2 + sizeof(message) + P2P_SEAL_OVERHEAD];
|
||||||
|
int sealed_size;
|
||||||
|
|
||||||
|
/* (anyone watching the brokers can ask, as often as they like) */
|
||||||
|
while (code.answers_left < MAXIMUM_ANSWERS && code_elapsed(code.answers_time, ANSWER_INTERVAL))
|
||||||
|
{
|
||||||
|
code.answers_left++;
|
||||||
|
code.answers_time += ANSWER_INTERVAL;
|
||||||
|
}
|
||||||
|
if (code.answers_left == MAXIMUM_ANSWERS)
|
||||||
|
code.answers_time = p2p_now();
|
||||||
|
if (code.answers_left <= 0)
|
||||||
|
return;
|
||||||
|
code.answers_left--;
|
||||||
|
message[0] = _code_message_invite;
|
||||||
|
message[1] = CODE_MESSAGE_VERSION;
|
||||||
|
memcpy(message + 2, nonce, CODE_NONCE_SIZE);
|
||||||
|
memcpy(message + 2 + CODE_NONCE_SIZE, code.invite, CODE_INVITE_SIZE);
|
||||||
|
sealed[0] = _code_message_invite;
|
||||||
|
sealed[1] = CODE_MESSAGE_VERSION;
|
||||||
|
sealed_size = p2p_seal(code.host_key, message, sizeof(message), sealed + 2);
|
||||||
|
p2p_signal_publish(code.host_topic, sealed, 2 + sealed_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void code_invite_received(const unsigned char *payload, int size)
|
||||||
|
{
|
||||||
|
unsigned char message[2 + CODE_NONCE_SIZE + CODE_INVITE_SIZE + 16];
|
||||||
|
int message_size;
|
||||||
|
char hex[2 * CODE_INVITE_SIZE + 1];
|
||||||
|
char link[P2P_LINK_SIZE];
|
||||||
|
|
||||||
|
if (size > (int)sizeof(message) + P2P_SEAL_OVERHEAD)
|
||||||
|
return;
|
||||||
|
message_size = p2p_open(code.look_key, payload, size, message);
|
||||||
|
if (message_size != 2 + CODE_NONCE_SIZE + CODE_INVITE_SIZE || message[0] != _code_message_invite ||
|
||||||
|
message[1] != CODE_MESSAGE_VERSION || !p2p_equal(message + 2, code.look_nonce, CODE_NONCE_SIZE))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
code.status = P2P_CODE_FOUND;
|
||||||
|
p2p_signal_sync_topics();
|
||||||
|
p2p_hex(message + 2 + CODE_NONCE_SIZE, CODE_INVITE_SIZE, hex);
|
||||||
|
snprintf(link, sizeof(link), "halo://join/%s", hex);
|
||||||
|
platform_log("Internet play: the join code's host answered");
|
||||||
|
p2p_invite_received(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
int p2p_code_received(const char *topic, const unsigned char *payload, int size)
|
||||||
|
{
|
||||||
|
if (size < 2 || payload[1] != CODE_MESSAGE_VERSION)
|
||||||
|
return 0;
|
||||||
|
if (code.hosting && !strcmp(topic, code.host_topic))
|
||||||
|
{
|
||||||
|
if (payload[0] == _code_message_ask && size == 2 + CODE_NONCE_SIZE)
|
||||||
|
code_answer(payload + 2);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (code.status == P2P_CODE_LOOKING && !strcmp(topic, code.look_topic))
|
||||||
|
{
|
||||||
|
if (payload[0] == _code_message_invite)
|
||||||
|
code_invite_received(payload + 2, size - 2);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void p2p_code_update(void)
|
||||||
|
{
|
||||||
|
unsigned char message[2 + CODE_NONCE_SIZE];
|
||||||
|
|
||||||
|
if (code.status != P2P_CODE_LOOKING)
|
||||||
|
return;
|
||||||
|
p2p_signal_start();
|
||||||
|
if (code_elapsed(code.look_started_time, LOOK_TIMEOUT))
|
||||||
|
{
|
||||||
|
code.status = P2P_CODE_NOT_FOUND;
|
||||||
|
p2p_signal_sync_topics();
|
||||||
|
platform_log("Internet play: no game answered the join code");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!p2p_signal_connected() || (code.asked && !code_elapsed(code.asked_time, ASK_INTERVAL)))
|
||||||
|
return;
|
||||||
|
message[0] = _code_message_ask;
|
||||||
|
message[1] = CODE_MESSAGE_VERSION;
|
||||||
|
memcpy(message + 2, code.look_nonce, CODE_NONCE_SIZE);
|
||||||
|
p2p_signal_publish(code.look_topic, message, sizeof(message));
|
||||||
|
code.asked = 1;
|
||||||
|
code.asked_time = p2p_now();
|
||||||
|
}
|
||||||
@@ -107,6 +107,34 @@ void p2p_signal_join(const unsigned char *host_hash, const unsigned char *token)
|
|||||||
void p2p_signal_stop_joining(void);
|
void p2p_signal_stop_joining(void);
|
||||||
/* whether any broker is connected */
|
/* whether any broker is connected */
|
||||||
int p2p_signal_connected(void);
|
int p2p_signal_connected(void);
|
||||||
|
/* (for p2p_code.c) the topics every broker listens on, as they are now; and
|
||||||
|
a message to a topic through every broker */
|
||||||
|
void p2p_signal_sync_topics(void);
|
||||||
|
void p2p_signal_publish(const char *topic, const unsigned char *payload, int size);
|
||||||
|
|
||||||
|
/* ---------- p2p_code.c: join codes (under p2p_lock) */
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
P2P_CODE_TOPIC_SIZE = 7 + 32 + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* hosting: a new code for the invite (its key hash and token), and none */
|
||||||
|
void p2p_code_host(const unsigned char *invite);
|
||||||
|
void p2p_code_stop_hosting(void);
|
||||||
|
/* the code hosted, as XXXX-XXXX; 0 if none */
|
||||||
|
int p2p_code_hosted(char *text, int size);
|
||||||
|
/* starts looking up a typed code (0: not a code, or this machine's own;
|
||||||
|
p2p_code_status says which) */
|
||||||
|
int p2p_code_look_up(const char *typed);
|
||||||
|
int p2p_code_status(void);
|
||||||
|
/* forgets a finished look up's result */
|
||||||
|
void p2p_code_clear_status(void);
|
||||||
|
/* (p2p_signal.c) the topics to listen on (empty: none); a message on a
|
||||||
|
topic, nonzero if it was a code's; and each pass of the p2p thread */
|
||||||
|
void p2p_code_topics(const char **host_topic, const char **look_topic);
|
||||||
|
int p2p_code_received(const char *topic, const unsigned char *payload, int size);
|
||||||
|
void p2p_code_update(void);
|
||||||
|
|
||||||
/* ---------- p2p_crypto.c */
|
/* ---------- p2p_crypto.c */
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ enum
|
|||||||
one is not forgotten while its host nonce lasts: USED_REQUEST_TIME) */
|
one is not forgotten while its host nonce lasts: USED_REQUEST_TIME) */
|
||||||
MAXIMUM_USED_REQUESTS = 1024,
|
MAXIMUM_USED_REQUESTS = 1024,
|
||||||
TOPIC_SIZE = 7 + 32 + 1,
|
TOPIC_SIZE = 7 + 32 + 1,
|
||||||
|
/* (p2p_code.c's are as long) */
|
||||||
|
TOPIC_SIZE_CHECK = 1 / (TOPIC_SIZE == P2P_CODE_TOPIC_SIZE),
|
||||||
NONCE_SIZE = 8,
|
NONCE_SIZE = 8,
|
||||||
/* an ACCEPT's or a proven JOIN's tag: the first half of an HMAC-SHA256 */
|
/* an ACCEPT's or a proven JOIN's tag: the first half of an HMAC-SHA256 */
|
||||||
TAG_SIZE = 16,
|
TAG_SIZE = 16,
|
||||||
@@ -152,6 +154,9 @@ struct broker
|
|||||||
/* the topics it has been asked for */
|
/* the topics it has been asked for */
|
||||||
char host_topic[TOPIC_SIZE];
|
char host_topic[TOPIC_SIZE];
|
||||||
char join_topic[TOPIC_SIZE];
|
char join_topic[TOPIC_SIZE];
|
||||||
|
/* (join codes' topics: p2p_code.c) */
|
||||||
|
char code_host_topic[TOPIC_SIZE];
|
||||||
|
char code_look_topic[TOPIC_SIZE];
|
||||||
unsigned char input[BUFFER_SIZE];
|
unsigned char input[BUFFER_SIZE];
|
||||||
int input_size;
|
int input_size;
|
||||||
unsigned char output[BUFFER_SIZE];
|
unsigned char output[BUFFER_SIZE];
|
||||||
@@ -321,6 +326,8 @@ static void broker_close(struct broker *broker, int failed)
|
|||||||
broker->output_size = 0;
|
broker->output_size = 0;
|
||||||
broker->host_topic[0] = 0;
|
broker->host_topic[0] = 0;
|
||||||
broker->join_topic[0] = 0;
|
broker->join_topic[0] = 0;
|
||||||
|
broker->code_host_topic[0] = 0;
|
||||||
|
broker->code_look_topic[0] = 0;
|
||||||
if (failed)
|
if (failed)
|
||||||
broker->failures++;
|
broker->failures++;
|
||||||
}
|
}
|
||||||
@@ -410,17 +417,20 @@ static void broker_publish(struct broker *broker, const char *topic, const unsig
|
|||||||
/* the topics a ready broker should be subscribed to */
|
/* the topics a ready broker should be subscribed to */
|
||||||
static void broker_sync_topics(struct broker *broker)
|
static void broker_sync_topics(struct broker *broker)
|
||||||
{
|
{
|
||||||
const char *wanted[2];
|
const char *wanted[4];
|
||||||
char *had[2];
|
char *had[4];
|
||||||
int index;
|
int index;
|
||||||
|
|
||||||
if (broker->state != _broker_ready)
|
if (broker->state != _broker_ready)
|
||||||
return;
|
return;
|
||||||
wanted[0] = signalling.hosting ? signalling.host_topic : "";
|
wanted[0] = signalling.hosting ? signalling.host_topic : "";
|
||||||
wanted[1] = signalling.joining ? signalling.join_topic : "";
|
wanted[1] = signalling.joining ? signalling.join_topic : "";
|
||||||
|
p2p_code_topics(&wanted[2], &wanted[3]);
|
||||||
had[0] = broker->host_topic;
|
had[0] = broker->host_topic;
|
||||||
had[1] = broker->join_topic;
|
had[1] = broker->join_topic;
|
||||||
for (index = 0; index < 2; index++)
|
had[2] = broker->code_host_topic;
|
||||||
|
had[3] = broker->code_look_topic;
|
||||||
|
for (index = 0; index < 4; index++)
|
||||||
{
|
{
|
||||||
if (!strcmp(wanted[index], had[index]))
|
if (!strcmp(wanted[index], had[index]))
|
||||||
continue;
|
continue;
|
||||||
@@ -939,6 +949,8 @@ static void publish_received(struct broker *broker, const char *topic, const uns
|
|||||||
|
|
||||||
if (size > MAXIMUM_MESSAGE_SIZE + P2P_SEAL_OVERHEAD)
|
if (size > MAXIMUM_MESSAGE_SIZE + P2P_SEAL_OVERHEAD)
|
||||||
return;
|
return;
|
||||||
|
if (p2p_code_received(topic, payload, size))
|
||||||
|
return;
|
||||||
if (signalling.hosting && !strcmp(topic, signalling.host_topic))
|
if (signalling.hosting && !strcmp(topic, signalling.host_topic))
|
||||||
{
|
{
|
||||||
message_size = p2p_open(signalling.host_key, payload, size, message);
|
message_size = p2p_open(signalling.host_key, payload, size, message);
|
||||||
@@ -1190,6 +1202,7 @@ void p2p_signal_update(const int *read, int read_count, const int *write, int wr
|
|||||||
}
|
}
|
||||||
if (signalling.joining && elapsed(signalling.join_sent_time, JOIN_INTERVAL))
|
if (signalling.joining && elapsed(signalling.join_sent_time, JOIN_INTERVAL))
|
||||||
send_join();
|
send_join();
|
||||||
|
p2p_code_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
int p2p_signal_connected(void)
|
int p2p_signal_connected(void)
|
||||||
@@ -1212,6 +1225,17 @@ static void sync_all_topics(void)
|
|||||||
broker_sync_topics(&signalling.brokers[index]);
|
broker_sync_topics(&signalling.brokers[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void p2p_signal_sync_topics(void)
|
||||||
|
{
|
||||||
|
sync_all_topics();
|
||||||
|
}
|
||||||
|
|
||||||
|
void p2p_signal_publish(const char *topic, const unsigned char *payload, int size)
|
||||||
|
{
|
||||||
|
if (size <= MAXIMUM_MESSAGE_SIZE + P2P_SEAL_OVERHEAD)
|
||||||
|
publish_everywhere(topic, payload, size);
|
||||||
|
}
|
||||||
|
|
||||||
void p2p_signal_host(const unsigned char *token)
|
void p2p_signal_host(const unsigned char *token)
|
||||||
{
|
{
|
||||||
if (!signalling.has_nonce_key)
|
if (!signalling.has_nonce_key)
|
||||||
|
|||||||
@@ -614,18 +614,56 @@ static BOOL platform_text_has_invite_link(const char *text)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* the last clipboard text looked at, kept beside the settings from run to
|
||||||
|
run (the VR port's): a run's own invite, still on the clipboard when the
|
||||||
|
game starts again, is not joined then (its host, the run before, is gone,
|
||||||
|
and the attempt held up a join for a minute and a half) */
|
||||||
|
static void platform_clipboard_seen(char *seen, int size, BOOL save)
|
||||||
|
{
|
||||||
|
char path[512];
|
||||||
|
FILE *file;
|
||||||
|
|
||||||
|
snprintf(path, sizeof(path), "%s/clipboard_seen.txt", platform_data_root());
|
||||||
|
if (save)
|
||||||
|
{
|
||||||
|
file = fopen(path, "wb");
|
||||||
|
if (file)
|
||||||
|
{
|
||||||
|
fputs(seen, file);
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file = fopen(path, "rb");
|
||||||
|
if (!file)
|
||||||
|
return;
|
||||||
|
{
|
||||||
|
size_t length = fread(seen, 1, (size_t)size - 1, file);
|
||||||
|
|
||||||
|
seen[length] = 0;
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
/* puts a new invite on the clipboard, and joins one found there when the
|
/* puts a new invite on the clipboard, and joins one found there when the
|
||||||
game comes to the front */
|
game comes to the front */
|
||||||
static void platform_invite_clipboard(BOOL look)
|
static void platform_invite_clipboard(BOOL look)
|
||||||
{
|
{
|
||||||
/* the last clipboard text looked at, so each invite is joined once */
|
/* the last clipboard text looked at, so each invite is joined once */
|
||||||
static char seen[256];
|
static char seen[256];
|
||||||
|
static BOOL seen_loaded;
|
||||||
const char *invite = p2p_take_clipboard_text();
|
const char *invite = p2p_take_clipboard_text();
|
||||||
|
|
||||||
|
if (!seen_loaded)
|
||||||
|
{
|
||||||
|
seen_loaded = TRUE;
|
||||||
|
platform_clipboard_seen(seen, sizeof(seen), FALSE);
|
||||||
|
}
|
||||||
if (invite)
|
if (invite)
|
||||||
{
|
{
|
||||||
SDL_SetClipboardText(invite);
|
SDL_SetClipboardText(invite);
|
||||||
snprintf(seen, sizeof(seen), "%s", invite);
|
snprintf(seen, sizeof(seen), "%s", invite);
|
||||||
|
platform_clipboard_seen(seen, sizeof(seen), TRUE);
|
||||||
platform_log("Internet play: the invite link is on the clipboard");
|
platform_log("Internet play: the invite link is on the clipboard");
|
||||||
#ifdef HALO_ANDROID
|
#ifdef HALO_ANDROID
|
||||||
SDL_ShowAndroidToast("Hosting: the invite link is on the clipboard", 1, -1, 0, 0);
|
SDL_ShowAndroidToast("Hosting: the invite link is on the clipboard", 1, -1, 0, 0);
|
||||||
@@ -638,6 +676,7 @@ static void platform_invite_clipboard(BOOL look)
|
|||||||
if (text && strcmp(text, seen) && strlen(text) < sizeof(seen))
|
if (text && strcmp(text, seen) && strlen(text) < sizeof(seen))
|
||||||
{
|
{
|
||||||
snprintf(seen, sizeof(seen), "%s", text);
|
snprintf(seen, sizeof(seen), "%s", text);
|
||||||
|
platform_clipboard_seen(seen, sizeof(seen), TRUE);
|
||||||
/* (a link, not a bare code: 64 hex digits alone are as often a
|
/* (a link, not a bare code: 64 hex digits alone are as often a
|
||||||
checksum copied for something else) */
|
checksum copied for something else) */
|
||||||
if (platform_text_has_invite_link(text) && p2p_join_invite(text))
|
if (platform_text_has_invite_link(text) && p2p_join_invite(text))
|
||||||
|
|||||||
@@ -42,7 +42,13 @@ enum
|
|||||||
_event_type_right_stick,
|
_event_type_right_stick,
|
||||||
_event_type_button,
|
_event_type_button,
|
||||||
|
|
||||||
|
#ifdef HALO_ANDROID
|
||||||
|
/* (the VR port's: a headset's controllers' thumbsticks are pushed as
|
||||||
|
far less readily than a gamepad's) */
|
||||||
|
STICK_EVENT_THRESHOLD = 22937,
|
||||||
|
#else
|
||||||
STICK_EVENT_THRESHOLD = 29490,
|
STICK_EVENT_THRESHOLD = 29490,
|
||||||
|
#endif
|
||||||
STICK_EVENT_REPEAT_MILLISECONDS = 250
|
STICK_EVENT_REPEAT_MILLISECONDS = 250
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,6 +77,24 @@ typedef char verify_event_manager_state_size[
|
|||||||
typedef char verify_event_manager_globals_size[
|
typedef char verify_event_manager_globals_size[
|
||||||
sizeof(struct event_manager_globals) == 0x168 ? 1 : -1];
|
sizeof(struct event_manager_globals) == 0x168 ? 1 : -1];
|
||||||
|
|
||||||
|
#ifdef HALO_ANDROID
|
||||||
|
/* The VR port's: a stick held moves on once after a pause, then quickly,
|
||||||
|
as menus do now (the Xbox's moved on every quarter second, which in a
|
||||||
|
headset, with the keyboard's letters to cross, is slow going). */
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
STICK_REPEAT_FIRST_MILLISECONDS = 350,
|
||||||
|
STICK_REPEAT_MILLISECONDS = 90,
|
||||||
|
};
|
||||||
|
|
||||||
|
static short stick_repeats[NUMBER_OF_GAMEPAD_STICKS][MAXIMUM_GAMEPADS];
|
||||||
|
|
||||||
|
#define STICK_REPEAT_INTERVAL(stick, controller_index) \
|
||||||
|
(stick_repeats[stick][controller_index] ? STICK_REPEAT_MILLISECONDS : STICK_REPEAT_FIRST_MILLISECONDS)
|
||||||
|
#else
|
||||||
|
#define STICK_REPEAT_INTERVAL(stick, controller_index) STICK_EVENT_REPEAT_MILLISECONDS
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ---------- prototypes */
|
/* ---------- prototypes */
|
||||||
|
|
||||||
static void queue_event(
|
static void queue_event(
|
||||||
@@ -83,6 +107,30 @@ struct event_manager_globals event_manager_globals = { 0 };
|
|||||||
|
|
||||||
/* ---------- public code */
|
/* ---------- public code */
|
||||||
|
|
||||||
|
#ifdef HALO_ANDROID
|
||||||
|
/* an event the stick posts: a fresh push (it was within the threshold on
|
||||||
|
that axis) starts the count of repeats again */
|
||||||
|
static void stick_note_event(
|
||||||
|
short stick,
|
||||||
|
short controller_index,
|
||||||
|
long x,
|
||||||
|
long y,
|
||||||
|
unsigned long time)
|
||||||
|
{
|
||||||
|
boolean fresh =
|
||||||
|
(ABS(x) >= STICK_EVENT_THRESHOLD &&
|
||||||
|
ABS(event_manager_globals.previous_stick_axes[stick][0][controller_index]) < STICK_EVENT_THRESHOLD) ||
|
||||||
|
(ABS(y) >= STICK_EVENT_THRESHOLD &&
|
||||||
|
ABS(event_manager_globals.previous_stick_axes[stick][1][controller_index]) < STICK_EVENT_THRESHOLD);
|
||||||
|
|
||||||
|
(void)time;
|
||||||
|
if (fresh)
|
||||||
|
stick_repeats[stick][controller_index] = 0;
|
||||||
|
else if (stick_repeats[stick][controller_index] < SHORT_MAX)
|
||||||
|
stick_repeats[stick][controller_index]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
void event_manager_initialize(
|
void event_manager_initialize(
|
||||||
void)
|
void)
|
||||||
{
|
{
|
||||||
@@ -209,12 +257,16 @@ static void queue_event(
|
|||||||
ABS(event_manager_globals.previous_stick_axes[_gamepad_stick_left][0][controller_index]) < STICK_EVENT_THRESHOLD) ||
|
ABS(event_manager_globals.previous_stick_axes[_gamepad_stick_left][0][controller_index]) < STICK_EVENT_THRESHOLD) ||
|
||||||
(ABS(y) >= STICK_EVENT_THRESHOLD &&
|
(ABS(y) >= STICK_EVENT_THRESHOLD &&
|
||||||
ABS(event_manager_globals.previous_stick_axes[_gamepad_stick_left][1][controller_index]) < STICK_EVENT_THRESHOLD) ||
|
ABS(event_manager_globals.previous_stick_axes[_gamepad_stick_left][1][controller_index]) < STICK_EVENT_THRESHOLD) ||
|
||||||
time - event_manager_globals.stick_event_times[_gamepad_stick_left][controller_index] >= STICK_EVENT_REPEAT_MILLISECONDS))
|
time - event_manager_globals.stick_event_times[_gamepad_stick_left][controller_index] >=
|
||||||
|
STICK_REPEAT_INTERVAL(_gamepad_stick_left, controller_index)))
|
||||||
{
|
{
|
||||||
post = FALSE;
|
post = FALSE;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
#ifdef HALO_ANDROID
|
||||||
|
stick_note_event(_gamepad_stick_left, controller_index, x, y, time);
|
||||||
|
#endif
|
||||||
event_manager_globals.stick_event_times[_gamepad_stick_left][controller_index] = time;
|
event_manager_globals.stick_event_times[_gamepad_stick_left][controller_index] = time;
|
||||||
post = TRUE;
|
post = TRUE;
|
||||||
|
|
||||||
@@ -260,12 +312,16 @@ static void queue_event(
|
|||||||
ABS(event_manager_globals.previous_stick_axes[_gamepad_stick_right][0][controller_index]) < STICK_EVENT_THRESHOLD) ||
|
ABS(event_manager_globals.previous_stick_axes[_gamepad_stick_right][0][controller_index]) < STICK_EVENT_THRESHOLD) ||
|
||||||
(ABS(y) >= STICK_EVENT_THRESHOLD &&
|
(ABS(y) >= STICK_EVENT_THRESHOLD &&
|
||||||
ABS(event_manager_globals.previous_stick_axes[_gamepad_stick_right][1][controller_index]) < STICK_EVENT_THRESHOLD) ||
|
ABS(event_manager_globals.previous_stick_axes[_gamepad_stick_right][1][controller_index]) < STICK_EVENT_THRESHOLD) ||
|
||||||
time - event_manager_globals.stick_event_times[_gamepad_stick_right][controller_index] >= STICK_EVENT_REPEAT_MILLISECONDS))
|
time - event_manager_globals.stick_event_times[_gamepad_stick_right][controller_index] >=
|
||||||
|
STICK_REPEAT_INTERVAL(_gamepad_stick_right, controller_index)))
|
||||||
{
|
{
|
||||||
post = FALSE;
|
post = FALSE;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
#ifdef HALO_ANDROID
|
||||||
|
stick_note_event(_gamepad_stick_right, controller_index, x, y, time);
|
||||||
|
#endif
|
||||||
event_manager_globals.stick_event_times[_gamepad_stick_right][controller_index] = time;
|
event_manager_globals.stick_event_times[_gamepad_stick_right][controller_index] = time;
|
||||||
post = TRUE;
|
post = TRUE;
|
||||||
|
|
||||||
|
|||||||
@@ -5591,6 +5591,152 @@ static boolean ui_mouse_menus_active(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HALO_ANDROID
|
#ifdef HALO_ANDROID
|
||||||
|
/* ---------- join codes (the VR port's)
|
||||||
|
|
||||||
|
Internet play's join codes (port/linux/src/p2p_code.c): the host's lobby
|
||||||
|
shows its game's code, and on the screen of games to join, X opens the
|
||||||
|
keyboard to type one in, which then joins as an invite link does (the game
|
||||||
|
shows up in the list once its host is reached). */
|
||||||
|
|
||||||
|
int p2p_join_code(char *text, int size);
|
||||||
|
int p2p_join_with_code(char const *typed);
|
||||||
|
int p2p_join_code_status(void);
|
||||||
|
void p2p_join_code_clear_status(void);
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
/* p2p.h's */
|
||||||
|
_join_code_idle,
|
||||||
|
_join_code_looking,
|
||||||
|
_join_code_found,
|
||||||
|
_join_code_not_found,
|
||||||
|
_join_code_invalid,
|
||||||
|
_join_code_own,
|
||||||
|
_join_code_offline,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct
|
||||||
|
{
|
||||||
|
boolean keyboard_launched;
|
||||||
|
wchar_t typed[12];
|
||||||
|
} ui_join_code_globals;
|
||||||
|
|
||||||
|
static boolean ui_join_code_screen_active(
|
||||||
|
char const *name)
|
||||||
|
{
|
||||||
|
return widget_globals.active_widgets[0] && widget_globals.active_widgets[0]->name &&
|
||||||
|
strcmp(widget_globals.active_widgets[0]->name, name) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* X on the screen of games to join: the keyboard, for a code (TRUE: the
|
||||||
|
event is taken) */
|
||||||
|
static boolean ui_join_code_take_event(
|
||||||
|
struct widget_instance *widget,
|
||||||
|
struct event_record const *event)
|
||||||
|
{
|
||||||
|
if (event->type != _event_type_button || event->data.button.value != 1 ||
|
||||||
|
event->data.button.index != _gamepad_analog_button_x || widget != widget_globals.active_widgets[0] ||
|
||||||
|
!ui_join_code_screen_active("server_list_screen"))
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
ui_join_code_globals.typed[0] = 0;
|
||||||
|
virtual_keyboard_set_caption(L"Enter the join code of an online game");
|
||||||
|
if (!virtual_keyboard_launch(ui_join_code_globals.typed, sizeof(ui_join_code_globals.typed), 8))
|
||||||
|
{
|
||||||
|
virtual_keyboard_set_caption(NULL);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
p2p_join_code_clear_status();
|
||||||
|
ui_join_code_globals.keyboard_launched = TRUE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the keyboard closed: the code typed, looked up */
|
||||||
|
static void ui_join_code_update(
|
||||||
|
void)
|
||||||
|
{
|
||||||
|
char text[sizeof(ui_join_code_globals.typed) / sizeof(wchar_t)];
|
||||||
|
short index;
|
||||||
|
|
||||||
|
if (!ui_join_code_globals.keyboard_launched || virtual_keyboard_active())
|
||||||
|
return;
|
||||||
|
ui_join_code_globals.keyboard_launched = FALSE;
|
||||||
|
virtual_keyboard_set_caption(NULL);
|
||||||
|
if (!virtual_keyboard_last_exit_saved_text())
|
||||||
|
return;
|
||||||
|
for (index = 0; index < (short)(NUMBEROF(text) - 1) && ui_join_code_globals.typed[index]; index++)
|
||||||
|
{
|
||||||
|
wchar_t character = ui_join_code_globals.typed[index];
|
||||||
|
|
||||||
|
text[index] = character < 128 ? (char)character : '?';
|
||||||
|
}
|
||||||
|
text[index] = 0;
|
||||||
|
p2p_join_with_code(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_join_code_draw_line(
|
||||||
|
short y0,
|
||||||
|
char const *text,
|
||||||
|
boolean icons,
|
||||||
|
boolean centered)
|
||||||
|
{
|
||||||
|
rectangle2d bounds;
|
||||||
|
wchar_t wide[96];
|
||||||
|
short index;
|
||||||
|
long font_index = tag_loaded(FONT_GROUP_TAG, "ui\\small_ui");
|
||||||
|
real_argb_color color = { 1.0f, 0.85f, 0.85f, 0.85f };
|
||||||
|
|
||||||
|
if (font_index == NONE)
|
||||||
|
return;
|
||||||
|
for (index = 0; index < (short)(NUMBEROF(wide) - 1) && text[index]; index++)
|
||||||
|
wide[index] = (wchar_t)(unsigned char)text[index];
|
||||||
|
wide[index] = 0;
|
||||||
|
bounds.y0 = y0;
|
||||||
|
bounds.y1 = (short)(y0 + 20);
|
||||||
|
bounds.x0 = centered ? 0 : 48;
|
||||||
|
bounds.x1 = centered ? 640 : 600;
|
||||||
|
draw_string_set_draw_mode(font_index, NONE, centered ? 2 : 0, 0, &color);
|
||||||
|
if (icons)
|
||||||
|
draw_string_and_hack_in_icons(&bounds, &bounds, NULL, 0, wide, FALSE);
|
||||||
|
else
|
||||||
|
rasterizer_draw_unicode_string(&bounds, &bounds, NULL, 0, wide);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* over the screens, as they are drawn */
|
||||||
|
static void ui_join_code_render(
|
||||||
|
void)
|
||||||
|
{
|
||||||
|
char code[16];
|
||||||
|
|
||||||
|
if (ui_join_code_screen_active("server_list_screen"))
|
||||||
|
{
|
||||||
|
char const *status = NULL;
|
||||||
|
|
||||||
|
ui_join_code_draw_line(372, "%x-button Join an online game with its code", TRUE, FALSE);
|
||||||
|
switch (p2p_join_code_status())
|
||||||
|
{
|
||||||
|
case _join_code_looking: status = "Looking for the game of that code..."; break;
|
||||||
|
case _join_code_found: status = "Found it: the game shows up in the list once its host is reached"; break;
|
||||||
|
case _join_code_not_found: status = "No game answered that code"; break;
|
||||||
|
case _join_code_invalid: status = "That is not a join code (8 letters and digits)"; break;
|
||||||
|
case _join_code_own: status = "That is this game's own code"; break;
|
||||||
|
case _join_code_offline: status = "Online play is off (network.online in config.toml)"; break;
|
||||||
|
}
|
||||||
|
if (status)
|
||||||
|
ui_join_code_draw_line(392, status, FALSE, FALSE);
|
||||||
|
}
|
||||||
|
else if (ui_join_code_screen_active("connected_pregame_screen") && global_network_game_server_get() &&
|
||||||
|
p2p_join_code(code, sizeof(code)))
|
||||||
|
{
|
||||||
|
char line[48];
|
||||||
|
|
||||||
|
snprintf(line, sizeof(line), "Online join code: %s", code);
|
||||||
|
/* (under the buttons' key) */
|
||||||
|
ui_join_code_draw_line(448, line, FALSE, TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* whether a menu is up (VR shows it on a screen in the room: render.c) */
|
/* whether a menu is up (VR shows it on a screen in the room: render.c) */
|
||||||
boolean ui_widgets_menus_active(
|
boolean ui_widgets_menus_active(
|
||||||
void)
|
void)
|
||||||
@@ -6045,6 +6191,9 @@ void render_ui_widgets(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef HALO_ANDROID
|
||||||
|
ui_join_code_render();
|
||||||
|
#endif
|
||||||
if (widget_globals.fade_to_black >= 0.0f &&
|
if (widget_globals.fade_to_black >= 0.0f &&
|
||||||
widget_globals.fade_to_black <= 1.0f)
|
widget_globals.fade_to_black <= 1.0f)
|
||||||
{
|
{
|
||||||
@@ -6987,6 +7136,9 @@ void process_ui_widgets(
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#ifdef HALO_ANDROID
|
||||||
|
ui_join_code_update();
|
||||||
|
#endif
|
||||||
if (attract_mode_should_start())
|
if (attract_mode_should_start())
|
||||||
{
|
{
|
||||||
attract_mode_start();
|
attract_mode_start();
|
||||||
@@ -7088,6 +7240,10 @@ void process_ui_widgets(
|
|||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
#ifdef HALO_ANDROID
|
||||||
|
if (ui_join_code_take_event(widget, &event))
|
||||||
|
continue;
|
||||||
|
#endif
|
||||||
if (!pause_pressed)
|
if (!pause_pressed)
|
||||||
{
|
{
|
||||||
widget_instance_process_one_event_recursive(
|
widget_instance_process_one_event_recursive(
|
||||||
|
|||||||
@@ -96,6 +96,18 @@ symbols in this file:
|
|||||||
#include "text/text_group.h"
|
#include "text/text_group.h"
|
||||||
#include "text/unicode.h"
|
#include "text/unicode.h"
|
||||||
|
|
||||||
|
#ifdef HALO_ANDROID
|
||||||
|
/* the VR port's: a caption of its own instead of the string list's, for a
|
||||||
|
keyboard it launches (ui_widget.c's join codes); NULL for the list's */
|
||||||
|
static wchar_t const *virtual_keyboard_caption_override;
|
||||||
|
|
||||||
|
void virtual_keyboard_set_caption(
|
||||||
|
wchar_t const *caption)
|
||||||
|
{
|
||||||
|
virtual_keyboard_caption_override = caption;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ---------- constants */
|
/* ---------- constants */
|
||||||
|
|
||||||
enum
|
enum
|
||||||
@@ -671,6 +683,10 @@ static void virtual_keyboard_render_internal(
|
|||||||
wchar_t *caption = unicode_string_list_get_string(
|
wchar_t *caption = unicode_string_list_get_string(
|
||||||
virtual_keyboard_globals.keyboard->special_key_labels_string_list_tag.index,
|
virtual_keyboard_globals.keyboard->special_key_labels_string_list_tag.index,
|
||||||
virtual_keyboard_globals.caption_index);
|
virtual_keyboard_globals.caption_index);
|
||||||
|
#ifdef HALO_ANDROID
|
||||||
|
if (virtual_keyboard_caption_override)
|
||||||
|
caption = (wchar_t *)virtual_keyboard_caption_override;
|
||||||
|
#endif
|
||||||
|
|
||||||
bounds.y0 = 78;
|
bounds.y0 = 78;
|
||||||
bounds.x0 = 114;
|
bounds.x0 = 114;
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ void virtual_keyboard_process(
|
|||||||
void);
|
void);
|
||||||
void virtual_keyboard_render(
|
void virtual_keyboard_render(
|
||||||
void);
|
void);
|
||||||
|
#ifdef HALO_ANDROID
|
||||||
|
void virtual_keyboard_set_caption(
|
||||||
|
wchar_t const *caption);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ---------- globals */
|
/* ---------- globals */
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user