Compare commits

..
Author SHA1 Message Date
Stefan MaierandClaude Opus 5.5 d8034d37fd Raise the rocket launcher's scope disc 3.5 cm more (18 cm)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:03:19 +02:00
Stefan MaierandClaude Opus 5.5 23d14319a7 Raise the rocket launcher's scope disc 3.5 cm more
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:14:34 +02:00
Stefan MaierandClaude Opus 5.5 ff5b91f120 Raise the VR scope's disc over the rocket launcher's tube
The disc sits 7 cm over the hand, where a rifle's or pistol's scope is;
over the rocket launcher, carried higher on the shoulder, it was inside
the tube. A weapon may raise it (halo_vr_set_scope_raise): the rocket
launcher by 11 cm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:07:26 +02:00
Stefan MaierandClaude Opus 5.5 66c0d33e34 List public online games and join them from a panel
A host can make its game public (Y in the lobby, or network.public_game):
it then announces name, map, game type, players and join code every 10 s,
retained, on hcvr/1/g<network version>/<id> through the signalling
brokers, and clears it when it stops. On the system link list, X opens an
online games panel listing the public games heard lately (stale ones
dropped after 45 s) and a row to type a code; A joins by the game's code.
Announcements carry no addresses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:00:57 +02:00
Stefan MaierandClaude Opus 5.5 6b77fe428a Show how joining an online game goes on the system link list
p2p_join_state tells the menus whether the host is being reached, was
reached (its game is in the list), did not answer, could not be reached
(both routers too strict: allow UPnP) or was lost; the join screen shows
it under the code's status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:37:25 +02:00
11 changed files with 823 additions and 32 deletions

No files matched your search

+3
View File
@@ -140,6 +140,9 @@ out (halo_vr_scope_pass). */
int halo_vr_scope(short local_player_index, float magnification, float position[3], float forward[3], float up[3],
float *vertical_field_of_view);
int halo_vr_scope_pass(void);
/* how much higher than a rifle's the weapon in hand has its scope's disc
(metres; render.c, before halo_vr_scope) */
void halo_vr_set_scope_raise(float metres);
void halo_vr_capture_scope(float left, float top, float right, float bottom);
/* where the weapon's gun node is as drawn, at the ready (world units;
first_person_weapons.c, each frame): shots, the crosshair and the laser
+58
View File
@@ -331,6 +331,8 @@ static struct
unsigned char join_host_hash[P2P_KEY_HASH_SIZE];
unsigned char join_token[P2P_TOKEN_SIZE];
unsigned long join_time;
/* how the latest join goes, for the menus (p2p_join_state) */
int join_state;
char clipboard[P2P_LINK_SIZE];
int has_clipboard;
@@ -840,6 +842,12 @@ static void drop_peer(struct peer *peer, const char *reason)
struct retired_session *retired = &p2p.retired[p2p.retired_next++ % MAXIMUM_RETIRED_SESSIONS];
int ask_again = p2p.joining && peer->is_host && !memcmp(peer->identifier, p2p.join_host, P2P_IDENTIFIER_SIZE);
if (peer->is_host && !memcmp(peer->identifier, p2p.join_host, P2P_IDENTIFIER_SIZE) &&
p2p.join_state != P2P_JOIN_NONE)
{
p2p.join_state = peer->connected ? P2P_JOIN_LOST : P2P_JOIN_BLOCKED;
}
platform_log("Internet play: %s %s: %s", peer->is_host ? "host" : "player", peer->name, reason);
if (peer->connected)
{
@@ -1029,6 +1037,8 @@ static void peer_heard(struct peer *peer, unsigned long address, unsigned short
address_text(address, port, text));
if (peer->is_host)
{
if (!memcmp(p2p.join_host, peer->identifier, P2P_IDENTIFIER_SIZE) && p2p.join_state != P2P_JOIN_NONE)
p2p.join_state = P2P_JOIN_CONNECTED;
if (p2p.joining && !memcmp(p2p.join_host, peer->identifier, P2P_IDENTIFIER_SIZE))
{
p2p.joining = 0;
@@ -2298,6 +2308,7 @@ static int join_invite(const char *text)
memcpy(p2p.join_host_hash, hash, sizeof(hash));
memcpy(p2p.join_token, token, sizeof(token));
p2p.join_requested = 1;
p2p.join_state = P2P_JOIN_REACHING;
return 1;
}
@@ -2344,6 +2355,51 @@ int p2p_join_code_status(void)
return status;
}
void p2p_set_public_game(int public_game)
{
pthread_mutex_lock(&p2p_lock);
p2p_list_set_public(public_game);
pthread_mutex_unlock(&p2p_lock);
}
int p2p_public_game(void)
{
int result;
pthread_mutex_lock(&p2p_lock);
result = p2p_list_public();
pthread_mutex_unlock(&p2p_lock);
return result;
}
void p2p_set_game_description(const char *name, const char *map, const char *mode, int players,
int maximum_players)
{
pthread_mutex_lock(&p2p_lock);
p2p_list_set_description(name, map, mode, players, maximum_players);
pthread_mutex_unlock(&p2p_lock);
}
int p2p_public_games(struct p2p_public_game *games, int maximum)
{
int count;
pthread_mutex_lock(&p2p_lock);
count = p2p.running ? p2p_list_games(games, maximum) : 0;
pthread_mutex_unlock(&p2p_lock);
return count;
}
int p2p_join_state(void)
{
int state;
pthread_mutex_lock(&p2p_lock);
state = p2p.running ? p2p.join_state : P2P_JOIN_NONE;
pthread_mutex_unlock(&p2p_lock);
return state;
}
void p2p_join_code_clear_status(void)
{
pthread_mutex_lock(&p2p_lock);
@@ -2381,6 +2437,8 @@ static void update_joining(void)
{
p2p.joining = 0;
p2p_signal_stop_joining();
if (p2p.join_state == P2P_JOIN_REACHING)
p2p.join_state = P2P_JOIN_NO_ANSWER;
platform_log("Internet play: no answer from the invite's host; it may have stopped hosting or quit");
}
}
+37
View File
@@ -43,6 +43,43 @@ 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);
/* how the latest join of an invite or code goes (the VR port's, for the
menus): reaching the host, connected (its game is in the system link list),
or why not */
enum
{
P2P_JOIN_NONE,
P2P_JOIN_REACHING,
P2P_JOIN_CONNECTED,
P2P_JOIN_NO_ANSWER,
P2P_JOIN_BLOCKED,
P2P_JOIN_LOST,
};
int p2p_join_state(void);
/* Public games (p2p_list.c): the hosted game listed for anyone or not, and
what it is (the game's server, as it changes); and the games others list,
joined by their codes (p2p_join_with_code) */
enum
{
P2P_PUBLIC_NAME_SIZE = 16,
P2P_PUBLIC_MAP_SIZE = 32,
P2P_PUBLIC_MODE_SIZE = 24,
};
struct p2p_public_game
{
char name[P2P_PUBLIC_NAME_SIZE];
char map[P2P_PUBLIC_MAP_SIZE];
char mode[P2P_PUBLIC_MODE_SIZE];
char code[12];
int players;
int maximum_players;
};
void p2p_set_public_game(int public_game);
int p2p_public_game(void);
void p2p_set_game_description(const char *name, const char *map, const char *mode, int players,
int maximum_players);
int p2p_public_games(struct p2p_public_game *games, int maximum);
/* this machine's identifier, which its XNADDR carries (6 bytes) */
const unsigned char *p2p_identifier(void);
+18
View File
@@ -111,6 +111,9 @@ int p2p_signal_connected(void);
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);
/* the same, kept by the brokers for whoever listens later (empty: no
longer) */
void p2p_signal_publish_retained(const char *topic, const unsigned char *payload, int size);
/* ---------- p2p_code.c: join codes (under p2p_lock) */
@@ -136,6 +139,21 @@ 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_list.c: public games (under p2p_lock) */
/* hosting: whether the game is listed, and what it is */
void p2p_list_set_public(int public_game);
int p2p_list_public(void);
void p2p_list_set_description(const char *name, const char *map, const char *mode, int players,
int maximum_players);
/* the games listed lately; asking keeps this machine listening a while */
int p2p_list_games(struct p2p_public_game *games, int maximum);
/* (p2p_signal.c) the topic to listen on (empty: none); a message on one
(nonzero if it was a listing's); and each pass, whether hosting */
const char *p2p_list_topic(void);
int p2p_list_received(const char *topic, const unsigned char *payload, int size);
void p2p_list_update(int hosting);
/* ---------- p2p_crypto.c */
enum
+335
View File
@@ -0,0 +1,335 @@
/*
P2P_LIST.C
Public games (the VR port's): a host may list its game for anyone, who
finds it in a list of online games and joins it by its join code
(p2p_code.c), without a server of this project's.
- A public game's host announces it through the signalling brokers
(p2p_signal.c) every ANNOUNCE_INTERVAL, retained, on a topic of its own
under hcvr/1/g<network version>/: its name, map, game type, players, its
join code, and the time. Retained, a machine that starts listening gets
every game's latest at once. A host that stops (it closes its game, or
makes it private) clears its topic; one that quits without that leaves
its last announcement, which listeners drop once it is older than
STALE_SECONDS (by the machines' clocks).
- A machine that shows the list listens on the topics of its network
version (a wildcard), and keeps the games heard of lately.
- An announcement carries no address: a machine reaches a public game's
host only by its code, as anyone given the code does. Anyone can announce
anything, so what is shown is checked to be printable and in bounds, and
a game that is not what it says is only a code that does not answer.
*/
#include "platform.h"
#include "posix.h"
#include "port_config.h"
#include "p2p_internal.h"
#include "halo_port_limits.h"
#include <stdio.h>
#include <string.h>
#include <time.h>
enum
{
ANNOUNCEMENT_VERSION = 1,
/* milliseconds */
ANNOUNCE_INTERVAL = 10000,
/* a listener's interest lapses this long after the menu last asked */
WATCH_TIME = 3000,
STALE_SECONDS = 45,
MAXIMUM_LISTED_GAMES = 32,
};
/* what goes to the brokers (byte for byte) */
struct announcement
{
char magic[4];
unsigned char version;
unsigned char players;
unsigned char maximum_players;
unsigned char flags;
unsigned char time[8];
char code[8];
char name[P2P_PUBLIC_NAME_SIZE];
char map[P2P_PUBLIC_MAP_SIZE];
char mode[P2P_PUBLIC_MODE_SIZE];
};
struct listed_game
{
int used;
char topic[P2P_CODE_TOPIC_SIZE];
unsigned long heard_time;
long long announced_time;
struct p2p_public_game game;
};
static struct
{
/* hosting (whether public: network.public_game, until Y changes it) */
int initialized;
int public_game;
char topic[P2P_CODE_TOPIC_SIZE];
int announced;
unsigned long announced_time;
char name[P2P_PUBLIC_NAME_SIZE];
char map[P2P_PUBLIC_MAP_SIZE];
char mode[P2P_PUBLIC_MODE_SIZE];
int players;
int maximum_players;
/* listening */
unsigned long watched_time;
int watching;
char watch_topic[P2P_CODE_TOPIC_SIZE];
struct listed_game games[MAXIMUM_LISTED_GAMES];
} list;
static int list_elapsed(unsigned long since, unsigned long time)
{
return (unsigned int)(p2p_now() - since) >= (unsigned int)time;
}
/* text as shown: printable ASCII only, ended */
static void list_text(char *destination, int size, const char *source, int source_size)
{
int index;
for (index = 0; index < size - 1 && index < source_size && source[index]; index++)
destination[index] = source[index] >= 32 && source[index] < 127 ? source[index] : '?';
destination[index] = 0;
}
static long long list_time_now(void)
{
return (long long)time(NULL);
}
static void list_topics_make(void)
{
if (!list.topic[0])
{
unsigned char random[8];
char hex[17];
posix_random_bytes(random, sizeof(random));
p2p_hex(random, sizeof(random), hex);
snprintf(list.topic, sizeof(list.topic), "hcvr/1/g%d/%s", HALO_PORT_NETWORK_VERSION, hex);
}
if (!list.watch_topic[0])
snprintf(list.watch_topic, sizeof(list.watch_topic), "hcvr/1/g%d/+", HALO_PORT_NETWORK_VERSION);
}
static void list_announce(void)
{
struct announcement announcement;
char code[16];
long long now = list_time_now();
int index;
if (!p2p_code_hosted(code, sizeof(code)))
return;
memset(&announcement, 0, sizeof(announcement));
memcpy(announcement.magic, "HCVR", 4);
announcement.version = ANNOUNCEMENT_VERSION;
announcement.players = (unsigned char)(list.players < 0 ? 0 : list.players > 255 ? 255 : list.players);
announcement.maximum_players =
(unsigned char)(list.maximum_players < 0 ? 0 : list.maximum_players > 255 ? 255 : list.maximum_players);
for (index = 0; index < 8; index++)
announcement.time[index] = (unsigned char)(now >> (8 * index));
/* (the code without its dash) */
memcpy(announcement.code, code, 4);
memcpy(announcement.code + 4, code + 5, 4);
memcpy(announcement.name, list.name, sizeof(announcement.name));
memcpy(announcement.map, list.map, sizeof(announcement.map));
memcpy(announcement.mode, list.mode, sizeof(announcement.mode));
p2p_signal_publish_retained(list.topic, (const unsigned char *)&announcement, sizeof(announcement));
if (!list.announced)
platform_log("Internet play: the game is listed as public (%s, %s, %s)", list.name, list.map, list.mode);
list.announced = 1;
list.announced_time = p2p_now();
}
static void list_withdraw(void)
{
if (!list.announced)
return;
p2p_signal_publish_retained(list.topic, NULL, 0);
platform_log("Internet play: the game is no longer listed as public");
list.announced = 0;
}
/* ---------- p2p.c's side (under p2p_lock) */
void p2p_list_set_public(int public_game)
{
if (public_game == list.public_game)
return;
list.public_game = public_game;
list_topics_make();
if (!public_game)
list_withdraw();
else
list.announced_time = p2p_now() - ANNOUNCE_INTERVAL;
}
int p2p_list_public(void)
{
return list.public_game;
}
void p2p_list_set_description(const char *name, const char *map, const char *mode, int players,
int maximum_players)
{
int changed = strncmp(list.name, name, sizeof(list.name) - 1) || strncmp(list.map, map, sizeof(list.map) - 1) ||
strncmp(list.mode, mode, sizeof(list.mode) - 1) || players != list.players ||
maximum_players != list.maximum_players;
list_text(list.name, sizeof(list.name), name, P2P_PUBLIC_NAME_SIZE);
list_text(list.map, sizeof(list.map), map, P2P_PUBLIC_MAP_SIZE);
list_text(list.mode, sizeof(list.mode), mode, P2P_PUBLIC_MODE_SIZE);
list.players = players;
list.maximum_players = maximum_players;
/* (a change goes out soon, not at the next interval) */
if (changed && list.announced && list_elapsed(list.announced_time, 1000))
list.announced_time = p2p_now() - ANNOUNCE_INTERVAL;
}
int p2p_list_games(struct p2p_public_game *games, int maximum)
{
int count = 0;
int index;
list.watched_time = p2p_now();
list.watching = 1;
for (index = 0; index < MAXIMUM_LISTED_GAMES && count < maximum; index++)
{
if (list.games[index].used)
games[count++] = list.games[index].game;
}
return count;
}
/* ---------- p2p_signal.c's side (under p2p_lock) */
const char *p2p_list_topic(void)
{
return list.watching ? list.watch_topic : "";
}
int p2p_list_received(const char *topic, const unsigned char *payload, int size)
{
struct announcement announcement;
struct listed_game *slot = NULL;
long long announced = 0;
char code[16];
int index;
if (!list.watch_topic[0] || strncmp(topic, list.watch_topic, strlen(list.watch_topic) - 1))
return 0;
/* (this machine's own, as the broker echoes it) */
if (!strcmp(topic, list.topic))
return 1;
for (index = 0; index < MAXIMUM_LISTED_GAMES; index++)
{
if (list.games[index].used && !strcmp(list.games[index].topic, topic))
slot = &list.games[index];
}
if (size != (int)sizeof(announcement))
{
/* (empty: withdrawn) */
if (slot && size == 0)
slot->used = 0;
return 1;
}
memcpy(&announcement, payload, sizeof(announcement));
if (memcmp(announcement.magic, "HCVR", 4) || announcement.version != ANNOUNCEMENT_VERSION ||
!announcement.maximum_players || announcement.players > announcement.maximum_players)
{
return 1;
}
for (index = 0; index < 8; index++)
announced |= (long long)announcement.time[index] << (8 * index);
if (announced < list_time_now() - STALE_SECONDS || announced > list_time_now() + STALE_SECONDS)
{
if (slot)
slot->used = 0;
return 1;
}
snprintf(code, sizeof(code), "%.4s-%.4s", announcement.code, announcement.code + 4);
for (index = 0; code[index]; index++)
{
if (index != 4 && !((code[index] >= '0' && code[index] <= '9') || (code[index] >= 'A' && code[index] <= 'Z')))
return 1;
}
if (!slot)
{
int oldest = -1;
for (index = 0; index < MAXIMUM_LISTED_GAMES && !slot; index++)
{
if (!list.games[index].used)
slot = &list.games[index];
else if (oldest < 0 || list.games[index].announced_time < list.games[oldest].announced_time)
oldest = index;
}
if (!slot)
slot = &list.games[oldest];
memset(slot, 0, sizeof(*slot));
snprintf(slot->topic, sizeof(slot->topic), "%s", topic);
}
slot->used = 1;
slot->heard_time = p2p_now();
slot->announced_time = announced;
list_text(slot->game.name, sizeof(slot->game.name), announcement.name, sizeof(announcement.name));
list_text(slot->game.map, sizeof(slot->game.map), announcement.map, sizeof(announcement.map));
list_text(slot->game.mode, sizeof(slot->game.mode), announcement.mode, sizeof(announcement.mode));
snprintf(slot->game.code, sizeof(slot->game.code), "%s", code);
slot->game.players = announcement.players;
slot->game.maximum_players = announcement.maximum_players;
return 1;
}
void p2p_list_update(int hosting)
{
int index;
if (!list.initialized)
{
list.initialized = 1;
if (!list.public_game)
list.public_game = config_boolean("network.public_game");
}
list_topics_make();
/* hosting a public game: announced every so often; withdrawn when it
is not */
if (hosting && list.public_game)
{
p2p_signal_start();
if (p2p_signal_connected() && list_elapsed(list.announced_time, ANNOUNCE_INTERVAL))
list_announce();
}
else if (list.announced)
{
list_withdraw();
}
/* listening while the menu asks */
if (list.watching && list_elapsed(list.watched_time, WATCH_TIME))
{
list.watching = 0;
memset(list.games, 0, sizeof(list.games));
p2p_signal_sync_topics();
}
else if (list.watching)
{
p2p_signal_start();
p2p_signal_sync_topics();
}
for (index = 0; index < MAXIMUM_LISTED_GAMES; index++)
{
if (list.games[index].used && list.games[index].announced_time < list_time_now() - STALE_SECONDS)
list.games[index].used = 0;
}
}
+32 -4
View File
@@ -157,6 +157,8 @@ struct broker
/* (join codes' topics: p2p_code.c) */
char code_host_topic[TOPIC_SIZE];
char code_look_topic[TOPIC_SIZE];
/* (public games': p2p_list.c) */
char list_topic[TOPIC_SIZE];
unsigned char input[BUFFER_SIZE];
int input_size;
unsigned char output[BUFFER_SIZE];
@@ -328,6 +330,7 @@ static void broker_close(struct broker *broker, int failed)
broker->join_topic[0] = 0;
broker->code_host_topic[0] = 0;
broker->code_look_topic[0] = 0;
broker->list_topic[0] = 0;
if (failed)
broker->failures++;
}
@@ -417,8 +420,8 @@ static void broker_publish(struct broker *broker, const char *topic, const unsig
/* the topics a ready broker should be subscribed to */
static void broker_sync_topics(struct broker *broker)
{
const char *wanted[4];
char *had[4];
const char *wanted[5];
char *had[5];
int index;
if (broker->state != _broker_ready)
@@ -426,11 +429,13 @@ static void broker_sync_topics(struct broker *broker)
wanted[0] = signalling.hosting ? signalling.host_topic : "";
wanted[1] = signalling.joining ? signalling.join_topic : "";
p2p_code_topics(&wanted[2], &wanted[3]);
wanted[4] = p2p_list_topic();
had[0] = broker->host_topic;
had[1] = broker->join_topic;
had[2] = broker->code_host_topic;
had[3] = broker->code_look_topic;
for (index = 0; index < 4; index++)
had[4] = broker->list_topic;
for (index = 0; index < 5; index++)
{
if (!strcmp(wanted[index], had[index]))
continue;
@@ -949,7 +954,7 @@ static void publish_received(struct broker *broker, const char *topic, const uns
if (size > MAXIMUM_MESSAGE_SIZE + P2P_SEAL_OVERHEAD)
return;
if (p2p_code_received(topic, payload, size))
if (p2p_code_received(topic, payload, size) || p2p_list_received(topic, payload, size))
return;
if (signalling.hosting && !strcmp(topic, signalling.host_topic))
{
@@ -1203,6 +1208,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))
send_join();
p2p_code_update();
p2p_list_update(signalling.hosting);
}
int p2p_signal_connected(void)
@@ -1236,6 +1242,28 @@ void p2p_signal_publish(const char *topic, const unsigned char *payload, int siz
publish_everywhere(topic, payload, size);
}
void p2p_signal_publish_retained(const char *topic, const unsigned char *payload, int size)
{
unsigned char body[2 + TOPIC_SIZE + MAXIMUM_MESSAGE_SIZE];
int index;
if (size > MAXIMUM_MESSAGE_SIZE)
return;
for (index = 0; index < signalling.broker_count; index++)
{
struct broker *broker = &signalling.brokers[index];
int body_size;
if (broker->state != _broker_ready)
continue;
body_size = put_string(body, topic);
if (size)
memcpy(body + body_size, payload, (size_t)size);
/* (PUBLISH, at most once, retained: an empty one clears it) */
broker_send(broker, 0x31, body, body_size + size);
}
}
void p2p_signal_host(const unsigned char *token)
{
if (!signalling.has_nonce_key)
+3
View File
@@ -275,6 +275,9 @@ static const struct config_setting config_settings[] =
"clipboard) that lets whoever has it join over the internet; opening a\n"
"link (or copying one before switching to the game) joins. Only people\n"
"with the invite can join. Off keeps system link to the local network." },
{ "network.public_game", _config_boolean, "false", "HALO_NET_PUBLIC_GAME", _environment_value, _platform_all,
"Host games as public: listed for anyone among the online games (the\n"
"system link screen's X), as Y in a hosted game's lobby makes them." },
{ "network.join_from_clipboard", _config_boolean, "true", "HALO_NET_JOIN_FROM_CLIPBOARD", _environment_value,
_platform_all,
"Join the game of an invite link found on the clipboard when the game\n"
+10 -1
View File
@@ -1957,6 +1957,14 @@ The world around it stays as it is (zooming the whole view sickens). */
#define SCOPE_ABOVE_HAND 0.07f
#define SCOPE_AHEAD_OF_HAND 0.22f
/* (the weapon in hand's: halo_vr_set_scope_raise) */
static float scope_raise;
void halo_vr_set_scope_raise(float metres)
{
scope_raise = metres;
}
int halo_vr_scope(short local_player_index, float magnification, float position[3], float forward[3], float up[3],
float *vertical_field_of_view)
{
@@ -1975,7 +1983,7 @@ int halo_vr_scope(short local_player_index, float magnification, float position[
game_to_local(right, local_right);
for (axis = 0; axis < 3; axis++)
{
vr.scope_pose.position[axis] = hand->aim.position[axis] + local_up[axis] * SCOPE_ABOVE_HAND +
vr.scope_pose.position[axis] = hand->aim.position[axis] + local_up[axis] * (SCOPE_ABOVE_HAND + scope_raise) +
local_forward[axis] * SCOPE_AHEAD_OF_HAND;
}
if (barrel_known())
@@ -2167,6 +2175,7 @@ int halo_vr_scope(short local_player_index, float magnification, float position[
return 0;
}
int halo_vr_scope_pass(void) { return 0; }
void halo_vr_set_scope_raise(float metres) { (void)metres; }
void halo_vr_capture_scope(float left, float top, float right, float bottom)
{
(void)left; (void)top; (void)right; (void)bottom;
+277 -27
View File
@@ -5593,15 +5593,18 @@ static boolean ui_mouse_menus_active(
#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). */
Internet play's join codes (port/linux/src/p2p_code.c) and public games
(p2p_list.c): the host's lobby shows its game's code, and Y there lists the
game for anyone or not; on the screen of games to join, X opens a panel of
the public games and a way to type a code in, either of 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);
int p2p_join_state(void);
enum
{
@@ -5615,12 +5618,74 @@ enum
_join_code_offline,
};
enum
{
/* p2p.h's */
_join_state_none,
_join_state_reaching,
_join_state_connected,
_join_state_no_answer,
_join_state_blocked,
_join_state_lost,
};
struct ui_public_game
{
/* p2p.h's struct p2p_public_game */
char name[16];
char map[32];
char mode[24];
char code[12];
int players;
int maximum_players;
};
void p2p_set_public_game(int public_game);
int p2p_public_game(void);
int p2p_public_games(struct ui_public_game *games, int maximum);
enum
{
MAXIMUM_SHOWN_PUBLIC_GAMES = 32,
/* the online games panel's rows: "enter a code" and the games */
ONLINE_PANEL_ROWS = 11,
};
static struct
{
boolean keyboard_launched;
wchar_t typed[12];
/* the panel of online games, over the screen of games to join */
boolean panel_open;
short selection;
short scroll;
short game_count;
struct ui_public_game games[MAXIMUM_SHOWN_PUBLIC_GAMES];
} ui_join_code_globals;
/* the multiplayer maps' names, as the menus show them */
static char const *ui_join_code_map_name(
char const *map)
{
static char const *const names[][2] = {
{ "beavercreek", "Battle Creek" }, { "bloodgulch", "Blood Gulch" }, { "boardingaction", "Boarding Action" },
{ "carousel", "Derelict" }, { "chillout", "Chill Out" }, { "damnation", "Damnation" },
{ "dangercanyon", "Danger Canyon" }, { "deathisland", "Death Island" }, { "gephyrophobia", "Gephyrophobia" },
{ "hangemhigh", "Hang 'Em High" }, { "icefields", "Ice Fields" }, { "infinity", "Infinity" },
{ "longest", "Longest" }, { "prisoner", "Prisoner" }, { "putput", "Chiron TL-34" },
{ "ratrace", "Rat Race" }, { "sidewinder", "Sidewinder" }, { "timberland", "Timberland" },
{ "wizard", "Wizard" },
};
short index;
for (index = 0; index < (short)NUMBEROF(names); index++)
{
if (!strcmp(map, names[index][0]))
return names[index][1];
}
return map;
}
static boolean ui_join_code_screen_active(
char const *name)
{
@@ -5628,28 +5693,110 @@ static boolean ui_join_code_screen_active(
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)
static void ui_join_code_launch_keyboard(
void)
{
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;
return;
}
p2p_join_code_clear_status();
ui_join_code_globals.keyboard_launched = TRUE;
return TRUE;
}
/* the panel's input: the stick or the pad's arrows choose a row, A takes
it, B closes it */
static void ui_join_code_panel_event(
struct event_record const *event)
{
short rows = (short)(ui_join_code_globals.game_count + 1);
short move = 0;
if (event->type == _event_type_left_stick)
{
if (event->data.stick.y == SHORT_MAX)
move = -1;
else if (event->data.stick.y == SHORT_MIN)
move = 1;
}
else if (event->type == _event_type_button && event->data.button.value == 1)
{
switch (event->data.button.index)
{
case _gamepad_binary_button_dpad_up: move = -1; break;
case _gamepad_binary_button_dpad_down: move = 1; break;
case _gamepad_analog_button_b:
case _gamepad_binary_button_back:
ui_join_code_globals.panel_open = FALSE;
break;
case _gamepad_analog_button_x:
ui_join_code_globals.panel_open = FALSE;
ui_join_code_launch_keyboard();
break;
case _gamepad_analog_button_a:
case _gamepad_binary_button_start:
ui_join_code_globals.panel_open = FALSE;
if (ui_join_code_globals.selection == 0 ||
ui_join_code_globals.selection > ui_join_code_globals.game_count)
{
ui_join_code_launch_keyboard();
}
else
{
p2p_join_code_clear_status();
p2p_join_with_code(ui_join_code_globals.games[ui_join_code_globals.selection - 1].code);
}
break;
}
}
if (move)
{
ui_join_code_globals.selection = (short)PIN(ui_join_code_globals.selection + move, 0, rows - 1);
if (ui_join_code_globals.selection < ui_join_code_globals.scroll)
ui_join_code_globals.scroll = ui_join_code_globals.selection;
else if (ui_join_code_globals.selection >= ui_join_code_globals.scroll + ONLINE_PANEL_ROWS)
ui_join_code_globals.scroll = (short)(ui_join_code_globals.selection - ONLINE_PANEL_ROWS + 1);
}
}
/* X on the screen of games to join: the panel of online games (and in it,
everything); Y in a hosted game's lobby: whether the game is public. TRUE:
the event is taken. */
static boolean ui_join_code_take_event(
struct widget_instance *widget,
struct event_record const *event)
{
if (widget != widget_globals.active_widgets[0])
return FALSE;
if (ui_join_code_globals.panel_open)
{
if (!ui_join_code_screen_active("server_list_screen"))
{
ui_join_code_globals.panel_open = FALSE;
return FALSE;
}
ui_join_code_panel_event(event);
return TRUE;
}
if (event->type != _event_type_button || event->data.button.value != 1)
return FALSE;
if (event->data.button.index == _gamepad_analog_button_x && ui_join_code_screen_active("server_list_screen"))
{
ui_join_code_globals.panel_open = TRUE;
ui_join_code_globals.selection = 0;
ui_join_code_globals.scroll = 0;
return TRUE;
}
if (event->data.button.index == _gamepad_analog_button_y &&
ui_join_code_screen_active("connected_pregame_screen") && global_network_game_server_get())
{
p2p_set_public_game(!p2p_public_game());
return TRUE;
}
return FALSE;
}
/* the keyboard closed: the code typed, looked up */
@@ -5675,17 +5822,20 @@ static void ui_join_code_update(
p2p_join_with_code(text);
}
static void ui_join_code_draw_line(
/* text in bounds, left, right or centred (0, 1, 2) */
static void ui_join_code_draw_text(
short x0,
short x1,
short y0,
char const *text,
boolean icons,
boolean centered)
short justification,
real_argb_color const *color)
{
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;
@@ -5694,15 +5844,97 @@ static void ui_join_code_draw_line(
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);
bounds.x0 = x0;
bounds.x1 = x1;
draw_string_set_draw_mode(font_index, NONE, justification, 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);
}
static void ui_join_code_draw_line(
short y0,
char const *text,
boolean icons,
boolean centered)
{
real_argb_color color = { 1.0f, 0.85f, 0.85f, 0.85f };
ui_join_code_draw_text((short)(centered ? 0 : 48), (short)(centered ? 640 : 600), y0, text, icons,
(short)(centered ? 2 : 0), &color);
}
static void ui_join_code_draw_quad(
short x0,
short y0,
short x1,
short y1,
unsigned long color)
{
rectangle2d bounds;
bounds.x0 = x0;
bounds.y0 = y0;
bounds.x1 = x1;
bounds.y1 = y1;
draw_quad(&bounds, color);
}
/* the panel of online games: "enter a code", then the public games */
static void ui_join_code_render_panel(
void)
{
real_argb_color title_color = { 1.0f, 0.55f, 0.75f, 1.0f };
real_argb_color text_color = { 1.0f, 0.85f, 0.85f, 0.85f };
real_argb_color dim_color = { 1.0f, 0.6f, 0.6f, 0.6f };
char line[64];
short row;
ui_join_code_globals.game_count =
(short)p2p_public_games(ui_join_code_globals.games, MAXIMUM_SHOWN_PUBLIC_GAMES);
ui_join_code_globals.selection =
(short)PIN(ui_join_code_globals.selection, 0, ui_join_code_globals.game_count);
ui_join_code_draw_quad(40, 72, 600, 410, 0xF0050A18);
ui_join_code_draw_quad(40, 72, 600, 74, 0xFF3A6EA5);
ui_join_code_draw_quad(40, 408, 600, 410, 0xFF3A6EA5);
snprintf(line, sizeof(line), "ONLINE GAMES (%d public)", (int)ui_join_code_globals.game_count);
ui_join_code_draw_text(56, 584, 80, line, FALSE, 0, &title_color);
ui_join_code_draw_text(56, 216, 104, "Name", FALSE, 0, &dim_color);
ui_join_code_draw_text(216, 376, 104, "Map", FALSE, 0, &dim_color);
ui_join_code_draw_text(376, 516, 104, "Game", FALSE, 0, &dim_color);
ui_join_code_draw_text(516, 584, 104, "Players", FALSE, 1, &dim_color);
for (row = 0; row < ONLINE_PANEL_ROWS; row++)
{
short index = (short)(ui_join_code_globals.scroll + row);
short y = (short)(128 + row * 22);
if (index > ui_join_code_globals.game_count)
break;
if (index == ui_join_code_globals.selection)
ui_join_code_draw_quad(48, y, 592, (short)(y + 21), 0x803A6EA5);
if (index == 0)
{
ui_join_code_draw_text(56, 584, (short)(y + 1), "Enter a join code...", FALSE, 0, &text_color);
}
else
{
struct ui_public_game const *game = &ui_join_code_globals.games[index - 1];
ui_join_code_draw_text(56, 212, (short)(y + 1), game->name, FALSE, 0, &text_color);
ui_join_code_draw_text(216, 372, (short)(y + 1), ui_join_code_map_name(game->map), FALSE, 0,
&text_color);
ui_join_code_draw_text(376, 512, (short)(y + 1), game->mode, FALSE, 0, &text_color);
snprintf(line, sizeof(line), "%d/%d", game->players, game->maximum_players);
ui_join_code_draw_text(516, 584, (short)(y + 1), line, FALSE, 1, &text_color);
}
}
if (!ui_join_code_globals.game_count)
ui_join_code_draw_text(56, 584, 152, "No public games right now", FALSE, 0, &dim_color);
ui_join_code_draw_text(56, 584, 384, "%a-button Join %x-button Enter a code %b-button Back", TRUE, 0,
&text_color);
}
/* over the screens, as they are drawn */
static void ui_join_code_render(
void)
@@ -5713,7 +5945,12 @@ static void ui_join_code_render(
{
char const *status = NULL;
ui_join_code_draw_line(372, "%x-button Join an online game with its code", TRUE, FALSE);
if (ui_join_code_globals.panel_open)
{
ui_join_code_render_panel();
return;
}
ui_join_code_draw_line(372, "%x-button Online games: public ones, or join by a code", TRUE, FALSE);
switch (p2p_join_code_status())
{
case _join_code_looking: status = "Looking for the game of that code..."; break;
@@ -5723,17 +5960,30 @@ static void ui_join_code_render(
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;
}
/* (the code found, or a link: how reaching its host goes) */
if (!status || p2p_join_code_status() == _join_code_found)
{
switch (p2p_join_state())
{
case _join_state_reaching: status = "Reaching the online game's host..."; break;
case _join_state_connected: status = "Connected: the online game is in the list above"; break;
case _join_state_no_answer: status = "The host did not answer: it may have stopped hosting"; break;
case _join_state_blocked: status = "Blocked by the routers: allow UPnP on yours or the host's"; break;
case _join_state_lost: status = "Lost the connection to the online game's host"; 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];
char line[96];
snprintf(line, sizeof(line), "Online join code: %s", code);
snprintf(line, sizeof(line), "Online join code: %s %%y-button Public game: %s", code,
p2p_public_game() ? "Yes" : "No");
/* (under the buttons' key) */
ui_join_code_draw_line(448, line, FALSE, TRUE);
ui_join_code_draw_line(448, line, TRUE, TRUE);
}
}
@@ -475,6 +475,10 @@ symbols in this file:
/* port: internet play's Discord presence (port/linux/src/p2p.c) */
void p2p_set_game_player_counts(int count, int maximum);
#ifdef HALO_ANDROID
void p2p_set_game_description(char const *name, char const *map, char const *mode, int players,
int maximum_players);
#endif
/* ---------- constants */
@@ -1308,6 +1312,42 @@ boolean network_game_server_idle(
/* (what Discord shows of a game hosted for internet play) */
p2p_set_game_player_counts(server->game.player_count, server->game.maximum_players);
#ifdef HALO_ANDROID
/* (and what a public game's listing says: port/linux/src/p2p_list.c) */
{
char name[16], map[32], mode[24];
char const *map_name = server->game.map.name;
char const *separator;
short index;
for (index = 0; index < (short)(NUMBEROF(name) - 1) && server->game.name[index]; index++)
name[index] = server->game.name[index] < 128 ? (char)server->game.name[index] : '?';
name[index] = 0;
/* (the scenario's name, the last of its path) */
while ((separator = strpbrk(map_name, "\\/")) != NULL)
map_name = separator + 1;
csstrncpy(map, map_name, sizeof(map) - 1);
map[sizeof(map) - 1] = 0;
for (index = 0; index < (short)(NUMBEROF(mode) - 1) && index < 12 &&
server->game.variant.human_readable_game_description[index]; index++)
{
wchar_t character = server->game.variant.human_readable_game_description[index];
mode[index] = character < 128 ? (char)character : '?';
}
mode[index] = 0;
/* (a game type without a name of its own: its kind's) */
if (!mode[0])
{
static char const *const kinds[] = { "", "CTF", "Slayer", "Oddball", "King of the Hill", "Race" };
long kind = server->game.variant.game_engine_index;
csstrncpy(mode, kind > 0 && kind < (long)NUMBEROF(kinds) ? kinds[kind] : "Custom", sizeof(mode) - 1);
mode[sizeof(mode) - 1] = 0;
}
p2p_set_game_description(name, map, mode, server->game.player_count, server->game.maximum_players);
}
#endif
if (network_game_server_game_is_valid(server))
{
+10
View File
@@ -746,6 +746,16 @@ static void render_vr_scope(
if (zoom_level == NONE || player_index == NONE || player_get(player_index)->unit_index == NONE)
return;
magnification = unit_get_zoom_magnification(player_get(player_index)->unit_index, zoom_level);
{
/* (a weapon whose body stands higher over the hand than a rifle's
has its scope's disc higher: the rocket launcher's tube) */
struct unit_datum *unit = unit_get(player_get(player_index)->unit_index);
long weapon_index = unit->unit.current_weapon_index >= 0 ?
unit->unit.weapon_object_indices[unit->unit.current_weapon_index] : NONE;
char const *name = weapon_index != NONE ? tag_get_name(object_get(weapon_index)->definition_index) : NULL;
halo_vr_set_scope_raise(name && strstr(name, "rocket launcher") ? 0.18f : 0.0f);
}
if (!halo_vr_scope(window->local_player_index, magnification, position, forward, up, &vertical_field_of_view))
return;
scope_window = *window;