Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9304daf2e8 | ||
|
|
0a62ad4610 | ||
|
|
42dc1f546f | ||
|
|
79a6482c16 | ||
|
|
bfd92ec17d | ||
|
|
852ee322cc | ||
|
|
f0d4fb2608 | ||
|
|
736c217347 | ||
|
|
30edb22f36 | ||
|
|
abc1c12f1e | ||
|
|
3f02527c68 | ||
|
|
ceecb039e3 | ||
|
|
a34685574f | ||
|
|
eb83e9d6d1 | ||
|
|
144063871a | ||
|
|
7165d40f90 | ||
|
|
65d81ad1ba | ||
|
|
d356c0abab | ||
|
|
1536c30870 | ||
|
|
458b4642b6 | ||
|
|
2af2702bfa | ||
|
|
7a1c5e6239 | ||
|
|
a9f3ce2647 | ||
|
|
1cebe431d1 | ||
|
|
ec2ab73acc | ||
|
|
2f5fa771f5 | ||
|
|
3aa88244fc | ||
|
|
a4e1bdb80d | ||
|
|
ceb0260389 | ||
|
|
bdfdef9bd4 | ||
|
|
f02317ca25 | ||
|
|
d7fa443718 | ||
|
|
4414d7606d | ||
|
|
824f79aa70 | ||
|
|
25a947faac | ||
|
|
bf545439b4 | ||
|
|
201f69a35a | ||
|
|
96a75df218 | ||
|
|
1a732f577f |
No files matched your search
@@ -19,9 +19,9 @@ builds of the latest release:
|
||||
|
||||
| Platform | Release | Debug |
|
||||
| --- | --- | --- |
|
||||
| Linux | [halo-linux-release.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-linux-release.zip) | [halo-linux-debug.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-linux-debug.zip) |
|
||||
| Windows | [halo-windows-release.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-windows-release.zip) | [halo-windows-debug.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-windows-debug.zip) |
|
||||
| Android | [halo-android-release.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-android-release.zip) | [halo-android-debug.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-android-debug.zip) |
|
||||
| Linux | [halo-linux-release.zip](https://github.com/cybersecurity/halo-ce-universal/releases/latest/download/halo-linux-release.zip) | [halo-linux-debug.zip](https://github.com/cybersecurity/halo-ce-universal/releases/latest/download/halo-linux-debug.zip) |
|
||||
| Windows | [halo-windows-release.zip](https://github.com/cybersecurity/halo-ce-universal/releases/latest/download/halo-windows-release.zip) | [halo-windows-debug.zip](https://github.com/cybersecurity/halo-ce-universal/releases/latest/download/halo-windows-debug.zip) |
|
||||
| Android | [halo-android-release.zip](https://github.com/cybersecurity/halo-ce-universal/releases/latest/download/halo-android-release.zip) | [halo-android-debug.zip](https://github.com/cybersecurity/halo-ce-universal/releases/latest/download/halo-android-debug.zip) |
|
||||
|
||||
Use the release build to play. The debug build stops at the first failed
|
||||
assertion and writes it to the log. Use the debug build to find and report
|
||||
@@ -32,7 +32,7 @@ if you want to install it. Refer to "Updates" in
|
||||
[port/linux/README.md](port/linux/README.md#updates).
|
||||
|
||||
Each build of the `main` branch that passes on all three platforms is a new
|
||||
release. The [Releases](https://github.com/OpenCommunityEdition/OpenCE/releases)
|
||||
release. The [Releases](https://github.com/cybersecurity/halo-ce-universal/releases)
|
||||
page keeps the last five releases. If the latest build has a problem, get
|
||||
an older build from that page.
|
||||
|
||||
|
||||
@@ -39,37 +39,7 @@ What differs is found at run time, not by a build of its own.
|
||||
| 5 | The Frame's controllers | done: their own profile, the Xbox's layout |
|
||||
| 6 | Frame rate, resolution | done: 72 Hz steady, multiview on |
|
||||
| 7 | Multiplayer, voice, cross-play with the Quest | |
|
||||
| 8 | A button in the Steam library | done: `steam_frame_launch.sh` |
|
||||
|
||||
## Starting it on the Frame
|
||||
|
||||
A button in the Steam library ("Halo VR", a non-Steam game) runs
|
||||
`~/Documents/HaloCE/halo-vr.sh` (`port/android/steam_frame_launch.sh`),
|
||||
which starts the game as Steam starts an Android title of its own:
|
||||
`lepton waitforexitandrun -- ~/Documents/HaloCE/HaloVR.apk`, in a container
|
||||
of its own (`steamlaunch-4290000001`) that Lepton installs the APK into
|
||||
anew whenever the file is newer. Updating the game is replacing that APK.
|
||||
|
||||
What the script does that Steam would for an Android title:
|
||||
- a compat data path (`steamapps/compatdata/4290000001/internal`): after an
|
||||
install Lepton links the app's data to it, a dangling link without it;
|
||||
and the shader path, which Lepton reads Steam's shader dump path against;
|
||||
- a container id of its own: with the shortcut's id, SteamVR's client in the
|
||||
container stalled in xrCreateInstance;
|
||||
- a few starts: a new APK's first start only installs it, and a start that
|
||||
ends within 30 s is tried again (up to three times), unless the game was
|
||||
quit (it writes `quit_requested.txt`).
|
||||
|
||||
Steam's "Exit game" does not reach the game (its process is the container's,
|
||||
not Steam's): quit it with the VR options' "Quit Halo" (Y in the main or
|
||||
pause menu, twice). A container ended within 30 s of its start makes
|
||||
Lepton drop the app's install, so the next start installs it again.
|
||||
|
||||
The shortcut is in `userdata/<id>/config/shortcuts.vdf` (Steam reads it when
|
||||
it starts; appid 3151454348 for this name and path), its pictures in
|
||||
`userdata/<id>/config/grid/` (from `port/android/steam_frame_grid/`). From
|
||||
the Mac, over USB the Frame's SteamOS is an adb device of its own, `frame`,
|
||||
beside Lepton's `<frame-ip>:5555`.
|
||||
| 8 | Installing it, for others | |
|
||||
|
||||
## The game's data
|
||||
|
||||
|
||||
@@ -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 static final String SHARED_DATA_ROOT = "/home/steamos/Documents/HaloCE";
|
||||
|
||||
private File dataRoot;
|
||||
@@ -60,12 +64,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
|
||||
@@ -118,6 +147,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() ||
|
||||
@@ -125,6 +171,7 @@ public class LauncherActivity extends Activity {
|
||||
}
|
||||
|
||||
private void startGame() {
|
||||
passOnMicrophonePermission();
|
||||
startActivity(new Intent(this, HaloActivity.class));
|
||||
finish();
|
||||
}
|
||||
|
||||
@@ -134,6 +134,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 */
|
||||
|
||||
@@ -301,19 +301,6 @@ static void *game_main(void *unused)
|
||||
environment_set(&environment, "HALO_SAVE_ROOT", save_root);
|
||||
time_zone(zone, sizeof(zone));
|
||||
environment_set(&environment, "TZ", zone);
|
||||
/* internet play's MQTT brokers (network.brokers_file): the APK's list,
|
||||
written beside config.toml at each start, as a desktop update replaces
|
||||
the file beside its game */
|
||||
{
|
||||
size_t brokers_size = 0;
|
||||
void *brokers = SDL_LoadFile("brokers.txt", &brokers_size);
|
||||
|
||||
snprintf(path, sizeof(path), "%s/brokers.txt", data_root);
|
||||
if (!brokers || !SDL_SaveFile(path, brokers, brokers_size))
|
||||
host_logf(HOST_LOG_ERROR, "cannot write %s: %s", path, SDL_GetError());
|
||||
SDL_free(brokers);
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/config.toml", data_root);
|
||||
|
||||
image = SDL_LoadFile("halo_guest.elf", &image_size);
|
||||
if (!image)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -60,6 +60,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
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 22 KiB |
@@ -1,85 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Halo VR on the Steam Frame: Lepton runs the APK in a container of its own,
|
||||
# and installs it anew whenever the APK is newer than the installed one
|
||||
# (port/android/STEAM_FRAME.md). The game's data: ~/Documents/HaloCE.
|
||||
# (a container of its own, not the shortcut's id: with that, SteamVR's
|
||||
# client in the container waited on xrCreateInstance)
|
||||
export SteamAppId="${HALO_LEPTON_APP_ID:-4290000001}"
|
||||
# (what Steam gives an Android title of its own, and a non-Steam game not:
|
||||
# the app's data in the container links to the compat data path's
|
||||
# internal/, a dangling link without it; and the shader paths: Lepton reads
|
||||
# the dump path against the other)
|
||||
export STEAM_COMPAT_DATA_PATH="$HOME/.local/share/Steam/steamapps/compatdata/$SteamAppId"
|
||||
export STEAM_COMPAT_SHADER_PATH="$HOME/.local/share/Steam/steamapps/shadercache/$SteamAppId"
|
||||
mkdir -p "$STEAM_COMPAT_DATA_PATH/internal" "$STEAM_COMPAT_SHADER_PATH"
|
||||
unset STEAM_FOSSILIZE_DUMP_PATH LD_PRELOAD
|
||||
LEPTON="$HOME/.local/share/Steam/steamapps/common/Lepton/lepton"
|
||||
APK="$HOME/Documents/HaloCE/HaloVR.apk"
|
||||
LOG="$HOME/Documents/HaloCE/launcher.log"
|
||||
# (written by the game when the player quits it: no start again)
|
||||
QUIT="$HOME/Documents/HaloCE/quit_requested.txt"
|
||||
rm -f "$QUIT"
|
||||
CONTEXT="steamlaunch-$SteamAppId"
|
||||
|
||||
log() { echo "$(date '+%F %T') $*" >> "$LOG"; }
|
||||
log "start (pid $$, parent $PPID)"
|
||||
|
||||
# Steam's "Exit game" ends the game's processes as Steam knows them (this
|
||||
# script, under Steam's reaper), not the container, which podman runs: a
|
||||
# signal here is passed on to Lepton, and a watcher of its own takes the
|
||||
# container down once this script is gone (killed)
|
||||
stopping=0
|
||||
child=0
|
||||
stop()
|
||||
{
|
||||
log "signal $1"
|
||||
stopping=1
|
||||
[ "$child" -gt 0 ] && kill -TERM "$child" 2>/dev/null
|
||||
}
|
||||
trap 'stop TERM' TERM
|
||||
trap 'stop INT' INT
|
||||
trap 'stop HUP' HUP
|
||||
|
||||
script=$$
|
||||
setsid bash -c '
|
||||
while kill -0 '"$script"' 2>/dev/null; do sleep 1; done
|
||||
echo "$(date "+%F %T") watcher: the game was ended, so its container" >> "'"$LOG"'"
|
||||
"'"$LEPTON"'" kill "'"$CONTEXT"'" >/dev/null 2>&1
|
||||
' </dev/null >/dev/null 2>&1 &
|
||||
|
||||
# the game's log while it runs (Lepton's logcat of its container), for
|
||||
# looking into problems: logcat.txt, anew each start
|
||||
LOGCAT="$HOME/Documents/HaloCE/logcat.txt"
|
||||
: > "$LOGCAT"
|
||||
setsid bash -c '
|
||||
while kill -0 '"$script"' 2>/dev/null; do
|
||||
"'"$LEPTON"'" logcat "'"$CONTEXT"'" 2>/dev/null | grep -v "^ERROR: \|^steamlaunch-\|^$" >> "'"$LOGCAT"'"
|
||||
sleep 3
|
||||
done
|
||||
' </dev/null >/dev/null 2>&1 &
|
||||
|
||||
run()
|
||||
{
|
||||
"$LEPTON" waitforexitandrun -- "$APK" &
|
||||
child=$!
|
||||
# (wait returns early when a signal comes: until Lepton is gone)
|
||||
while kill -0 "$child" 2>/dev/null; do
|
||||
wait "$child"
|
||||
done
|
||||
child=0
|
||||
}
|
||||
|
||||
# (a new APK is only installed on its first start, not started; and a start
|
||||
# right after a game that was ended hard can fail in SteamVR, which then
|
||||
# needs a moment: again, up to three times, while it ends within 30 s)
|
||||
for attempt in 1 2 3; do
|
||||
started=$SECONDS
|
||||
run
|
||||
if (( stopping || SECONDS - started >= 30 )) || [ -e "$QUIT" ]; then
|
||||
break
|
||||
fi
|
||||
log "ended within $((SECONDS - started)) s: again"
|
||||
sleep 5
|
||||
done
|
||||
rm -f "$QUIT"
|
||||
log "end"
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
@@ -2,7 +2,7 @@
|
||||
<defs>
|
||||
<radialGradient id="cone" gradientUnits="userSpaceOnUse" cx="167.994" cy="346.437" r="165.922"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#000"/></radialGradient><radialGradient id="coned" gradientUnits="userSpaceOnUse" cx="167.994" cy="346.437" r="165.922"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#878787"/></radialGradient>
|
||||
<g id="art">
|
||||
<path d="M167.994,346.437 L54.604,180.515 L281.384,180.515 Z" fill="url(#cone)"/>
|
||||
<path d="M167.994,346.437 L69.695,202.598 L266.292,202.598 Z" fill="url(#cone)"/>
|
||||
<circle cx="167.994" cy="345.987" r="53.201" fill="#878787"/>
|
||||
<path d="M167.994,346.437 L137.767,302.207 A53.201,53.201 0 0 1 198.220,302.207 Z" fill="url(#coned)"/>
|
||||
<!-- "15m" pixel label -->
|
||||
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -2,7 +2,7 @@
|
||||
<defs>
|
||||
<radialGradient id="cone" gradientUnits="userSpaceOnUse" cx="126.000" cy="130.342" r="128.840"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#000"/></radialGradient><radialGradient id="coned" gradientUnits="userSpaceOnUse" cx="126.000" cy="130.342" r="128.840"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#878787"/></radialGradient>
|
||||
<g id="art">
|
||||
<path d="M126.000,130.342 L37.886,1.502 L214.114,1.502 Z" fill="url(#cone)"/>
|
||||
<path d="M126.000,130.342 L49.632,18.677 L202.368,18.677 Z" fill="url(#cone)"/>
|
||||
<circle cx="126.000" cy="129.985" r="41.776" fill="#878787"/>
|
||||
<path d="M126.000,130.342 L102.251,95.616 A41.776,41.776 0 0 1 149.749,95.616 Z" fill="url(#coned)"/>
|
||||
<!-- "15m" pixel label -->
|
||||
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -51,7 +51,7 @@ until it is redrawn.
|
||||
| `ce/shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__6.png` | 512x256 | `shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__6.png` |
|
||||
| `ce/shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__8.png` | 512x256 | `shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__8.png` |
|
||||
|
||||
13 of 156 frames are placeholders.
|
||||
13 of 155 frames are placeholders.
|
||||
|
||||
The PC version's `ui\gamespy` and `ui\ticker` fonts are drawn with `ui\small_ui`, which
|
||||
the Xbox's map has.
|
||||
@@ -318,9 +318,6 @@
|
||||
<bitmap name="main_menu/multiplayer_type_select/join_game/header_server_browser">
|
||||
<frame png="ce/port/main_menu/multiplayer_type_select/join_game/header_server_browser.png" width="512" height="64"/>
|
||||
</bitmap>
|
||||
<bitmap name="main_menu/multiplayer_type_select/lobby/header_add_player">
|
||||
<frame png="ce/port/main_menu/multiplayer_type_select/lobby/header_add_player.png" width="512" height="64"/>
|
||||
</bitmap>
|
||||
<bitmap name="main_menu/multiplayer_type_select/lobby/header_lobby">
|
||||
<frame png="ce/port/main_menu/multiplayer_type_select/lobby/header_lobby.png" width="512" height="64"/>
|
||||
</bitmap>
|
||||
|
||||
@@ -1,93 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- The port's multiplayer screens, in the PC version's style (tools/port_settings.py) -->
|
||||
<menus>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/lobby_screen" width="640" height="480" flags="pass_unhandled_to_focused_child main_menu_if_no_history" bitmap="bitmaps/gradient">
|
||||
<on event="created" run="port lobby open"/>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/lobby_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/gradient">
|
||||
<on event="created" run="net server accept conx"/>
|
||||
<on event="created" run="net server allow start"/>
|
||||
<on event="b" run="port lobby leave" back="true"/>
|
||||
<on event="back" run="port lobby leave" back="true"/>
|
||||
<on event="start" run="port lobby join" open="main_menu/multiplayer_type_select/lobby/player_profile_screen" branch="true"/>
|
||||
<on event="b" run="net game unjoin player" back="true"/>
|
||||
<on event="back" run="net game unjoin player" back="true"/>
|
||||
<child widget="main_menu/new_select/sel_list_desc_bkd"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_list"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/header_lobby"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_join_help"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/header_lobby" controller="1" left="35" top="11" width="363" height="59" bitmap="main_menu/multiplayer_type_select/lobby/header_lobby">
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/lobby_join_help" type="text" controller="1" left="355" top="446" width="275" height="24" font="ui\small_ui" color="#FF2896FF" align="right" text_y="5" text_flags="no_focus_test">
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/lobby_list" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/multiplayer_type_select/lobby/lobby_desc">
|
||||
<data input="net splitscreen prejoin players"/>
|
||||
<data input="port lobby update"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/list_item_0" x="20" y="73"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/list_item_1" x="20" y="103"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/list_item_2" x="20" y="133"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/list_item_3" x="20" y="163"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/list_item_4" x="20" y="193"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/list_item_5" x="20" y="223"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/list_item_6" x="20" y="253"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/list_item_7" x="20" y="283"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/list_item_8" x="20" y="313"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/list_item_9" x="20" y="343"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/list_item_10" x="20" y="373"/>
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_0" x="20" y="73"/>
|
||||
<child widget="main_menu/new_select/list_item_1" x="20" y="103"/>
|
||||
<child widget="main_menu/new_select/list_item_2" x="20" y="133"/>
|
||||
<child widget="main_menu/new_select/list_item_3" x="20" y="163"/>
|
||||
<child widget="main_menu/new_select/list_item_4" x="20" y="193"/>
|
||||
<child widget="main_menu/new_select/list_item_5" x="20" y="223"/>
|
||||
<child widget="main_menu/new_select/list_item_6" x="20" y="253"/>
|
||||
<child widget="main_menu/new_select/list_item_7" x="20" y="283"/>
|
||||
<child widget="main_menu/new_select/list_item_8" x="20" y="313"/>
|
||||
<child widget="main_menu/new_select/list_item_9" x="20" y="343"/>
|
||||
<child widget="main_menu/new_select/list_item_10" x="20" y="373"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_button_bar" y="414"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/list_item_0" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd_top" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_text" x="25"/>
|
||||
<child widget="main_menu/new_select/list_item_arrows"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/list_item_1" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_text" x="25"/>
|
||||
<child widget="main_menu/new_select/list_item_arrows"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/list_item_2" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_text" x="25"/>
|
||||
<child widget="main_menu/new_select/list_item_arrows"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/list_item_3" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_text" x="25"/>
|
||||
<child widget="main_menu/new_select/list_item_arrows"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/list_item_4" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_text" x="25"/>
|
||||
<child widget="main_menu/new_select/list_item_arrows"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/list_item_5" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_text" x="25"/>
|
||||
<child widget="main_menu/new_select/list_item_arrows"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/list_item_6" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_text" x="25"/>
|
||||
<child widget="main_menu/new_select/list_item_arrows"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/list_item_7" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_text" x="25"/>
|
||||
<child widget="main_menu/new_select/list_item_arrows"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/list_item_8" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_text" x="25"/>
|
||||
<child widget="main_menu/new_select/list_item_arrows"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/list_item_9" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_text" x="25"/>
|
||||
<child widget="main_menu/new_select/list_item_arrows"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/list_item_10" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<on event="left right" run="swap player team"/>
|
||||
<child widget="main_menu/new_select/list_item_text" x="25"/>
|
||||
<child widget="main_menu/new_select/list_item_arrows"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/lobby_desc" width="640" height="480">
|
||||
<child widget="main_menu/current_profile_name"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_right_item" x="22" y="2"/>
|
||||
@@ -95,8 +36,6 @@
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/lobby_right_item" controller="1" left="406" top="75" width="162" height="326" bitmap="bitmaps/spinner_list_3_wide_item_background">
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_map_pic"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_map_name"/>
|
||||
<child widget="main_menu/solo_level_select/replay_level_right_pic"/>
|
||||
<child widget="main_menu/solo_level_select/replay_level_right_name"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_game_data"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/lobby_map_pic" controller="1" left="419" top="87" width="140" height="114" bitmap="ui\shell\bitmaps\mp_map_grafix">
|
||||
@@ -106,16 +45,10 @@
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/lobby_game_data" type="text" controller="1" left="417" top="250" width="146" height="144" font="ui\small_ui" color="#FF2896FF">
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/lobby_button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_button_team" x="120" y="1"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_button_start" x="250" y="1"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_button_add" x="380" y="1"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_button_team" x="250" y="1"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_button_start" x="380" y="1"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_button_leave" x="510" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/lobby_button_add" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" text="ADD PLAYER" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
|
||||
<on event="a" run="port lobby add player"/>
|
||||
<on event="start" run="port lobby add player"/>
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/lobby_button_team" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" text="SWITCH TEAM" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
|
||||
<on event="a" run="swap player team"/>
|
||||
<on event="start" run="swap player team"/>
|
||||
@@ -131,46 +64,14 @@
|
||||
<on event="start" run="mouse emit back event"/>
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/player_profile_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/gradient">
|
||||
<on event="created" run="net server defer start"/>
|
||||
<child widget="main_menu/new_select/sel_list_desc_bkd"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/player_profile_list"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/header_add_player"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/player_profile_help" x="20" y="416"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/header_add_player" controller="1" left="35" top="11" width="363" height="59" bitmap="main_menu/multiplayer_type_select/lobby/header_add_player">
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/player_profile_list" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/profile_manager/player_profile_extended_desc">
|
||||
<data input="3wide player profile list update"/>
|
||||
<on event="created" run="port lobby player list initialize"/>
|
||||
<on event="deleted" run="player profile list dispose"/>
|
||||
<on event="custom_activation" run="port lobby player choose" back="true" branch="true"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/list_item_0" x="20" y="73"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/list_item_1" x="20" y="103"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/list_item_2" x="20" y="133"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/list_item_3" x="20" y="163"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/list_item_4" x="20" y="193"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/list_item_5" x="20" y="223"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/list_item_6" x="20" y="253"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/list_item_7" x="20" y="283"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/list_item_8" x="20" y="313"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/list_item_9" x="20" y="343"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/list_item_10" x="20" y="373"/>
|
||||
<child widget="main_menu/multiplayer_type_select/coop/player_2_button_bar" y="414"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/player_profile_help" type="text" width="350" height="24" text="The new player's profile." font="ui\small_ui" color="#FF2896FF" text_y="5" text_flags="no_focus_test">
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/preview_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/gradient">
|
||||
<on event="created" run="port lobby open"/>
|
||||
<on event="b" run="port lobby preview leave" back="true"/>
|
||||
<on event="back" run="port lobby preview leave" back="true"/>
|
||||
<on event="start" run="port lobby preview add" open="main_menu/multiplayer_type_select/lobby/player_profile_screen" branch="true"/>
|
||||
<on event="b" back="true"/>
|
||||
<on event="back" back="true"/>
|
||||
<child widget="main_menu/new_select/sel_list_desc_bkd"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/preview_list"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/header_lobby"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/lobby_join_help"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/preview_list" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/multiplayer_type_select/lobby/lobby_desc">
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/preview_list" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child left_right_tabs_items" description="main_menu/multiplayer_type_select/lobby/lobby_desc">
|
||||
<data input="port lobby preview update"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/preview_status" x="30" y="75"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/preview_button_bar" y="414"/>
|
||||
@@ -178,8 +79,7 @@
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/preview_status" type="text" controller="1" width="360" height="300" font="ui\large_ui" color="#FF2896FF">
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/preview_button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/preview_button_join" x="250" y="1"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/preview_button_add" x="380" y="1"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/preview_button_join" x="380" y="1"/>
|
||||
<child widget="main_menu/multiplayer_type_select/lobby/preview_button_back" x="510" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/preview_button_join" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" text="JOIN GAME" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
|
||||
@@ -187,11 +87,6 @@
|
||||
<on event="start" run="port lobby preview join"/>
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/preview_button_add" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" text="ADD PLAYER" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
|
||||
<on event="a" run="port lobby add player"/>
|
||||
<on event="start" run="port lobby add player"/>
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/lobby/preview_button_back" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" text="BACK" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
|
||||
<on event="a" run="mouse emit back event"/>
|
||||
<on event="start" run="mouse emit back event"/>
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
<child widget="main_menu/multiplayer_type_select/mp_map_select/mp_map_right_name"/>
|
||||
<child widget="main_menu/multiplayer_type_select/mp_map_select/mp_map_right_pic"/>
|
||||
<child widget="main_menu/multiplayer_type_select/mp_map_select/mp_map_right_data"/>
|
||||
<child widget="main_menu/solo_level_select/replay_level_right_name"/>
|
||||
<child widget="main_menu/solo_level_select/replay_level_right_pic"/>
|
||||
<child widget="main_menu/solo_level_select/replay_level_right_data"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/mp_map_select/mp_map_right_name" type="text" controller="1" left="417" top="204" width="146" height="43" string_list="main_menu/mp_map_list" string_index="2" font="ui\large_ui" color="#FF2896FF">
|
||||
</widget>
|
||||
@@ -36,9 +33,7 @@
|
||||
<on event="created" run="mp level list initialize"/>
|
||||
<on event="deleted" run="mp level list dispose"/>
|
||||
<on event="custom_activation" run="mp level select" open="main_menu/multiplayer_type_select/connected/gametype_select_screen_wrapper"/>
|
||||
<on event="b" run="port map list back"/>
|
||||
<on event="back" run="port map list back"/>
|
||||
<child widget="main_menu/new_select/list_item_0_map_kind" x="82" y="73"/>
|
||||
<child widget="main_menu/new_select/list_item_0" x="20" y="73"/>
|
||||
<child widget="main_menu/new_select/list_item_1" x="20" y="103"/>
|
||||
<child widget="main_menu/new_select/list_item_2" x="20" y="133"/>
|
||||
<child widget="main_menu/new_select/list_item_3" x="20" y="163"/>
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/op_max_players" x="54" y="101"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/op_invite" x="54" y="129"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/op_listing" x="54" y="157"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/op_friendly_fire" x="54" y="185"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/op_extra_enemies" x="54" y="213"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/op_enemies_per_player" x="54" y="241"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/op_enemies_multiplier" x="54" y="241"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/op_game_type" x="54" y="185"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/op_player_options" x="54" y="213"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/op_item_options" x="54" y="241"/>
|
||||
@@ -78,50 +74,6 @@
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/listing_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="PUBLIC|PRIVATE" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="left_mouse" run="mouse spinner 1wide click"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/op_friendly_fire" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/friendly_fire_label"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/friendly_fire_spinner" x="286" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/friendly_fire_label" type="text" controller="1" width="300" height="22" string_list="main_menu/multiplayer_type_select/server_settings/labels" string_index="10" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/friendly_fire_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="OFF|ON|SHIELD ONLY|EXPLOSIVES ONLY" setting="network.coop_friendly_fire" values="off|on|shields_only|explosives_only" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
|
||||
<on event="created" run="port setting load"/>
|
||||
<on event="deleted" run="port setting save"/>
|
||||
<on event="left_mouse" run="mouse spinner 1wide click"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/op_extra_enemies" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/extra_enemies_label"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/extra_enemies_spinner" x="286" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/extra_enemies_label" type="text" controller="1" width="300" height="22" string_list="main_menu/multiplayer_type_select/server_settings/labels" string_index="11" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/extra_enemies_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="NONE|PER PLAYER|STATIC MULTIPLIER" setting="network.coop_enemies_mode" values="none|per_player|multiplier" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
|
||||
<on event="created" run="port setting load"/>
|
||||
<on event="deleted" run="port setting save"/>
|
||||
<on event="left_mouse" run="mouse spinner 1wide click"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/op_enemies_per_player" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/enemies_per_player_label"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/enemies_per_player_spinner" x="286" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/enemies_per_player_label" type="text" controller="1" width="300" height="22" string_list="main_menu/multiplayer_type_select/server_settings/labels" string_index="12" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/enemies_per_player_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="25%|50%|100%|150%|200%" setting="network.coop_enemies" values="25|50|100|150|200" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
|
||||
<on event="created" run="port setting load"/>
|
||||
<on event="deleted" run="port setting save"/>
|
||||
<on event="left_mouse" run="mouse spinner 1wide click"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/op_enemies_multiplier" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/enemies_multiplier_label"/>
|
||||
<child widget="main_menu/multiplayer_type_select/server_settings/enemies_multiplier_spinner" x="286" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/enemies_multiplier_label" type="text" controller="1" width="300" height="22" string_list="main_menu/multiplayer_type_select/server_settings/labels" string_index="13" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/enemies_multiplier_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="2X|4X|8X|16X|32X" setting="network.coop_enemies_multiplier" values="2|4|8|16|32" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
|
||||
<on event="created" run="port setting load"/>
|
||||
<on event="deleted" run="port setting save"/>
|
||||
<on event="left_mouse" run="mouse spinner 1wide click"/>
|
||||
</widget>
|
||||
<widget name="main_menu/multiplayer_type_select/server_settings/op_game_type" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<on event="a" run="port setup edit" open="main_menu/settings_select/multiplayer_setup/playlist_edit/game_type_select/gametype_select_screen"/>
|
||||
<on event="start" run="port setup edit" open="main_menu/settings_select/multiplayer_setup/playlist_edit/game_type_select/gametype_select_screen"/>
|
||||
@@ -207,10 +159,6 @@
|
||||
<string text="INDICATOR OPTIONS:"/>
|
||||
<string text="TEAMPLAY OPTIONS:"/>
|
||||
<string text="LISTING:"/>
|
||||
<string text="FRIENDLY FIRE:"/>
|
||||
<string text="EXTRA ENEMIES:"/>
|
||||
<string text="PER PLAYER:"/>
|
||||
<string text="MULTIPLIER:"/>
|
||||
</strings>
|
||||
<strings name="main_menu/multiplayer_type_select/server_settings/help_strings">
|
||||
<string text=""/>
|
||||
@@ -225,14 +173,5 @@
|
||||
<string text="Friendly fire and team balance, for this game."/>
|
||||
<string text="Anyone can see and join your game: it is listed\nin everyone's Server Browser."/>
|
||||
<string text="Only players with your invite link can join."/>
|
||||
<string text="Players can not be hurt by weapons and explosives\nfired by the other players."/>
|
||||
<string text="Players can be hurt by weapons or explosives\nfired by the other players."/>
|
||||
<string text="Damage from the other players will only reduce\nshields. Health will be unaffected."/>
|
||||
<string text="Players can be hurt by damage from explosives\nfired by the other players."/>
|
||||
<string text="Enemy squads are as the campaign has them."/>
|
||||
<string text="Enemy squads grow with the players: by the amount\nbelow for each player past the first."/>
|
||||
<string text="Enemy squads are the amount below times as large,\nhowever many players there are."/>
|
||||
<string text="For each player past the first, enemy squads get\nthis much more of themselves (100%: as many again)."/>
|
||||
<string text="Each enemy squad is this many times as large."/>
|
||||
</strings>
|
||||
</menus>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- The port's multiplayer screens, in the PC version's style (tools/port_settings.py) -->
|
||||
<menus>
|
||||
<widget name="main_menu/new_select/list_item_0_map_kind" width="256" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds_sm" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
|
||||
<child widget="main_menu/new_select/list_item_0_map_kind_spinner" x="15" y="2"/>
|
||||
</widget>
|
||||
<widget name="main_menu/new_select/list_item_0_map_kind_spinner" type="spinner" width="226" height="22" flags="left_right_tabs_items" string_list="main_menu/new_select/var_map_kinds" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" list_flags="items_from_strings" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 226 19 232">
|
||||
<on event="left_mouse" run="mouse spinner 1wide click"/>
|
||||
</widget>
|
||||
<strings name="main_menu/new_select/var_map_kinds">
|
||||
<string text="SINGLEPLAYER"/>
|
||||
<string text="MULTIPLAYER"/>
|
||||
</strings>
|
||||
</menus>
|
||||
@@ -14,15 +14,13 @@
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/options_menu" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/settings_select/player_setup/player_profile_edit/video_settings/help">
|
||||
<data input="port settings help"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_mode" x="54" y="73" platform="desktop"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_resolution" x="54" y="99" platform="desktop"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_window_size" x="54" y="99" platform="desktop"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_resolution_scaling" x="54" y="125" platform="desktop"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_vsync" x="54" y="151"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_max_fps" x="54" y="177" platform="desktop"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_interpolation" x="54" y="203"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_direct_camera" x="54" y="229" platform="desktop"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_high_res_hud" x="54" y="255"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_high_res_text" x="54" y="281"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_window_scale" x="54" y="99" platform="desktop"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_vsync" x="54" y="125"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_max_fps" x="54" y="151" platform="desktop"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_interpolation" x="54" y="177"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_direct_camera" x="54" y="203" platform="desktop"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_high_res_hud" x="54" y="229"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_high_res_text" x="54" y="255"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/button_bar" y="414"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
|
||||
@@ -39,38 +37,20 @@
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/mode_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="FULLSCREEN|BORDERLESS|WINDOWED" setting="display.mode" values="fullscreen|borderless|windowed" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_resolution" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF" platform="desktop">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/resolution_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/resolution_spinner" x="320" y="1"/>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_window_scale" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF" platform="desktop">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/window_scale_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/window_scale_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/resolution_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="1" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/window_scale_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="1" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/resolution_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="NATIVE" setting="display.resolution" values="native" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_window_size" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF" platform="desktop">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/window_size_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/window_size_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/window_size_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="2" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/window_size_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="1280 x 960" setting="display.window_size" values="1280x960" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_resolution_scaling" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF" platform="desktop">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/resolution_scaling_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/resolution_scaling_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/resolution_scaling_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="3" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/resolution_scaling_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="NATIVE|ORIGINAL" setting="display.resolution_scaling" values="native|original" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/window_scale_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="640 x 480|1280 x 960|1920 x 1440|2560 x 1920" setting="display.window_scale" values="1|2|3|4" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/op_vsync" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/vsync_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/vsync_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/vsync_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="4" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/vsync_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="2" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/vsync_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.vsync" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
@@ -79,7 +59,7 @@
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/max_fps_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/max_fps_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/max_fps_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/max_fps_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="3" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/max_fps_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="AUTO|30|60|120|144|165|240|NONE" setting="display.max_fps" values="0|30|60|120|144|165|240|-1" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
@@ -88,7 +68,7 @@
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/interpolation_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/interpolation_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/interpolation_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="6" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/interpolation_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="4" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/interpolation_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.interpolation" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
@@ -97,7 +77,7 @@
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/direct_camera_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/direct_camera_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/direct_camera_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="7" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/direct_camera_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/direct_camera_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.direct_camera" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
@@ -106,7 +86,7 @@
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/high_res_hud_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/high_res_hud_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/high_res_hud_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="8" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/high_res_hud_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="6" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/high_res_hud_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.high_res_hud" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
@@ -115,7 +95,7 @@
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/high_res_text_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/high_res_text_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/high_res_text_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="9" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/high_res_text_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels" string_index="7" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/high_res_text_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.high_res_text" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
@@ -132,9 +112,7 @@
|
||||
</widget>
|
||||
<strings name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/labels">
|
||||
<string text="DISPLAY MODE:"/>
|
||||
<string text="RESOLUTION:"/>
|
||||
<string text="WINDOW SIZE:"/>
|
||||
<string text="RESOLUTION SCALING:"/>
|
||||
<string text="V-SYNC:"/>
|
||||
<string text="FRAME RATE LIMIT:"/>
|
||||
<string text="SMOOTH MOTION:"/>
|
||||
@@ -144,10 +122,8 @@
|
||||
</strings>
|
||||
<strings name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/help_strings">
|
||||
<string text=""/>
|
||||
<string text="Fullscreen takes the display; borderless covers it\nwith a window. F11 switches to the window."/>
|
||||
<string text="What fullscreen and borderless draw at. Fullscreen\nsets the display to it; borderless scales it."/>
|
||||
<string text="The window's size: 4:3, then 16:10, 16:9 and 21:9\n(its edges can also be dragged)."/>
|
||||
<string text="Native draws at the resolution; Original draws\nthe Xbox's 640x480 and scales it up."/>
|
||||
<string text="Fullscreen and borderless draw at the display's\nresolution; windowed, 640x480 scaled. F11: window."/>
|
||||
<string text="The window's size when windowed (its edges can\nalso be dragged)."/>
|
||||
<string text="Wait for the display between frames, so that the\npicture never tears."/>
|
||||
<string text="With V-Sync off, the most frames a second. Auto:\ntwice the display's refresh rate."/>
|
||||
<string text="Draw a frame for every display refresh, blending\nbetween the game's 30 ticks a second."/>
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
<child widget="main_menu/solo_level_select/difficulty_indicator" x="416" y="343"/>
|
||||
<child widget="main_menu/solo_level_select/difficulty_indicator" x="466" y="343"/>
|
||||
<child widget="main_menu/solo_level_select/difficulty_indicator" x="516" y="343"/>
|
||||
<child widget="main_menu/multiplayer_type_select/mp_map_select/mp_map_right_name"/>
|
||||
<child widget="main_menu/multiplayer_type_select/mp_map_select/mp_map_right_pic"/>
|
||||
<child widget="main_menu/multiplayer_type_select/mp_map_select/mp_map_right_data"/>
|
||||
</widget>
|
||||
<widget name="main_menu/solo_level_select/replay_level_right_name" type="text" controller="1" left="417" top="204" width="146" height="43" string_list="main_menu/map_list" string_index="2" font="ui\large_ui" color="#FF2896FF">
|
||||
</widget>
|
||||
@@ -41,7 +38,7 @@
|
||||
<on event="created" run="initialize sp level list solo"/>
|
||||
<on event="deleted" run="dispose sp level list"/>
|
||||
<on event="custom_activation" run="solo level set map" open="main_menu/difficulty_select/difficulty_select_list_screen"/>
|
||||
<child widget="main_menu/new_select/list_item_0_map_kind" x="82" y="73"/>
|
||||
<child widget="main_menu/new_select/list_item_0" x="20" y="73"/>
|
||||
<child widget="main_menu/new_select/list_item_1" x="20" y="103"/>
|
||||
<child widget="main_menu/new_select/list_item_2" x="20" y="133"/>
|
||||
<child widget="main_menu/new_select/list_item_3" x="20" y="163"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 62 KiB |
@@ -17,7 +17,6 @@
|
||||
"ce/main_menu.multiplayer_type_select.mp_map_select.xml",
|
||||
"ce/main_menu.multiplayer_type_select.server_settings.xml",
|
||||
"ce/main_menu.multiplayer_type_select.xml",
|
||||
"ce/main_menu.new_select.port.xml",
|
||||
"ce/main_menu.new_select.xml",
|
||||
"ce/main_menu.player_profiles_select.xml",
|
||||
"ce/main_menu.profile_manager.xml",
|
||||
@@ -55,7 +54,6 @@
|
||||
"ce/port/main_menu/multiplayer_type_select/coop/header_player_2.png",
|
||||
"ce/port/main_menu/multiplayer_type_select/join_game/header_direct_link.png",
|
||||
"ce/port/main_menu/multiplayer_type_select/join_game/header_server_browser.png",
|
||||
"ce/port/main_menu/multiplayer_type_select/lobby/header_add_player.png",
|
||||
"ce/port/main_menu/multiplayer_type_select/lobby/header_lobby.png",
|
||||
"ce/port/pause/pausebox_center__0.png",
|
||||
"ce/port/pause/pausebox_center__1.png",
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="64" viewBox="0 0 512 64">
|
||||
<defs>
|
||||
<filter id="soft" filterUnits="userSpaceOnUse" x="-60" y="-60" width="632" height="184">
|
||||
<feGaussianBlur stdDeviation="14.23 13.06"/>
|
||||
</filter>
|
||||
<mask id="cut" maskUnits="userSpaceOnUse" x="0" y="0" width="512" height="64">
|
||||
<rect width="512" height="59" fill="#fff"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<g mask="url(#cut)">
|
||||
<rect x="29.08" y="28.05" width="226.48" height="40.95" fill="#021931" fill-opacity="0.893" filter="url(#soft)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 563 B |
@@ -1,10 +0,0 @@
|
||||
# The public MQTT brokers of internet play: the machines of an invite find
|
||||
# each other through them, and the server browser's listings go through
|
||||
# them. The game uses every one at once (up to 4), so any one working is
|
||||
# enough. One host:port on each line; "#" starts a comment.
|
||||
# (The game reads this file beside config.toml: network.brokers_file. An
|
||||
# update replaces it; keep a list of your own under another name.)
|
||||
opence.milenko.org:1883
|
||||
broker.emqx.io:1883
|
||||
broker.hivemq.com:1883
|
||||
test.mosquitto.org:1883
|
||||
@@ -21,64 +21,6 @@ with ideas from VALORANT's netcode articles, keeping the 30 Hz tick:
|
||||
vehicle it drives) from its local input at once. Remote players are
|
||||
driven by the inputs the host relays every tick, the latest one held
|
||||
until a newer arrives.
|
||||
- **Actors driven.** Only the host runs the AI. Each tick it sends its
|
||||
clients the control its actors gave their units (how they move, where
|
||||
they face, aim and look, their trigger and buttons, their animation
|
||||
impulses) with the units' state, those near a client's players every
|
||||
tick and others less often; a client drives each unit with the latest
|
||||
it has, as it drives a remote player's, until it hears nothing of it for
|
||||
two seconds (`port/linux/game/network_actors.c`).
|
||||
- **Co-op.** A network game on a campaign level with no game engine
|
||||
(Create Game's Map screen, its SINGLEPLAYER maps, over LAN and the
|
||||
internet) is co-op. Only the host runs the
|
||||
level's scripts and spawns players. `network_coop.c` sends the clients
|
||||
everything the scripts do that they would otherwise miss:
|
||||
- every tick: the cinematic, camera, screen fade, the HUD settings the
|
||||
scripts control (what is shown, the mission timer), the skip vote, and
|
||||
which teams are allies and friends (the scripts' allegiances, so the
|
||||
marines are the players' allies on every machine);
|
||||
- once each, numbered so nothing is applied twice: script sounds,
|
||||
chapter titles, help and objective text, "Checkpoint" messages, screen
|
||||
shake, nav points, custom animations on units and scenery, and units
|
||||
opening and closing (dropships' doors);
|
||||
- device groups (doors, elevators, switches; a client sets none itself,
|
||||
and its player's use of one is relayed to the host), and each device's
|
||||
position and power as it changes: a client puts its device where the
|
||||
host's is once that stops, or if they drift apart while it moves;
|
||||
- which named objects exist, so scripted creates and deletes match.
|
||||
|
||||
Every machine follows the host's structure BSP: a switch is sent at once
|
||||
and reliably, and each client's input says which BSP it has loaded.
|
||||
Until a client has the host's, the host takes none of its players'
|
||||
movement and none of its loading zones, and after any switch no loading
|
||||
zone switches again until every machine has the new BSP (ten seconds at
|
||||
most). A loading zone into a
|
||||
BSP the team hasn't been in brings every player to whoever crossed it,
|
||||
as split screen does; one back into a BSP it has been in switches only
|
||||
with two thirds of the living players at it (in the trigger, or within
|
||||
4 world units of the player in it), so one player can't drag the team
|
||||
back through the level. A player outside the loaded BSP and falling for
|
||||
two seconds is brought back beside a teammate. A dead player watches a
|
||||
living teammate (`coop_spectate.c`) and comes back beside one once it is
|
||||
safe. With everyone dead they come back where they were at the last
|
||||
checkpoint, since only the host could revert. A level won ends the round
|
||||
as in multiplayer, and the next round is the campaign's next level.
|
||||
|
||||
Cutscenes are skipped by vote: more than half the machines must press
|
||||
skip. The host then reverts as single player does, but keeps its clock
|
||||
moving forward (the netcode depends on that) and moves the script
|
||||
threads' wake times along with it. The object, device and name syncs
|
||||
bring the clients up to date.
|
||||
|
||||
The host's EXTRA ENEMIES (`coop_enemies.c`, `network.coop_enemies_mode`)
|
||||
give each squad of enemies a level places more of itself: PER PLAYER, a
|
||||
percentage of itself for each player past the first; STATIC MULTIPLIER,
|
||||
that many times itself for any number of players. They stand in rings
|
||||
about its starting locations where the ground is open, and never take the
|
||||
actors a level needs for its own (the actor pool, `halo_port_capacity.h`,
|
||||
holds 1024). Riders a dropship has no seats for are kept, and placed
|
||||
beside its riders once they get out. Only the host runs the AI, so the
|
||||
clients see them as the host's other actors.
|
||||
- **Host authoritative.** The host alone decides damage, deaths, spawns,
|
||||
pickups, scores and the game's objects; clients do not decide them but
|
||||
apply what the host sends.
|
||||
@@ -151,15 +93,7 @@ is dead; version 8 is the first whose clients play by the host's rules
|
||||
(below), so a build without them joins no host of it; version 9 tells
|
||||
every machine of a player the host dropped for cheating, each client
|
||||
tells the host its Discord user, and a machine's join request carries its
|
||||
hardware id; version 10 sends every player's ping for the scoreboard;
|
||||
version 11 sends with the game's settings its gametype's PC options;
|
||||
version 12 plays the campaign together (co-op, above), drives the host's
|
||||
actors on its clients and sends the flinches and deaths the host picked;
|
||||
version 13 drives up to 1056 of the host's AI units on its clients (co-op's
|
||||
extra enemies), where 12 drove 288; version 14 sends co-op's device positions
|
||||
and its units opening and closing; version 15 sends co-op's allegiances with
|
||||
its presentation, a message of another size; version 16 has a client's input
|
||||
say which structure BSP it has loaded (co-op).
|
||||
hardware id; version 10 sends every player's ping for the scoreboard.
|
||||
|
||||
A client plays by its host's rules: in another's game (searching for it,
|
||||
in its lobby, or playing it) the developer console, the telnet console
|
||||
@@ -204,9 +138,7 @@ added to `bans.txt` beside `debug.txt` (a line each, as in `cheaters.txt`,
|
||||
with `ip=` and `hwid=`): the host refuses a machine joining whose address or
|
||||
hardware id is in it (a line taken out unbans). Both are as the player's
|
||||
machine tells them: anyone with administrator or root access can change
|
||||
them, and players behind one address share it. The console's `kick <player
|
||||
name>` drops a player as `ban` does (every machine told), but adds no line
|
||||
and keeps no address out: the player may join again at once.
|
||||
them, and players behind one address share it.
|
||||
A speed hack of less than a tenth is let be: the host's bounds on how far
|
||||
and how fast a client's player moves and fires hold it to the host's time
|
||||
anyway.
|
||||
@@ -350,10 +282,8 @@ a pregame keep-alive every five seconds from the host
|
||||
host's latest tick it had heard of when it made the report.
|
||||
- The host deals a report once it has checked it: from that machine's
|
||||
player; damage one of their weapons (a vehicle's a driver's or
|
||||
gunner's; now or in the last ten seconds), a grenade the host's own game
|
||||
saw them throw in the last ten seconds that has not gone off (each
|
||||
throw's explosion is taken once, its other hits that tick with it:
|
||||
holding grenades deals nothing) or the vehicle they drove (in the
|
||||
gunner's; now or in the last ten seconds), their grenades (while they
|
||||
have them, and for a while after) or the vehicle they drove (in the
|
||||
last ten seconds: its collisions) can deal (its projectiles' impacts
|
||||
and detonations, followed through the tags), no harder than it can be
|
||||
(all of it, but an airborne melee blow's half again); of the shape the
|
||||
|
||||
@@ -83,8 +83,7 @@ These files are in the data root:
|
||||
| `init.txt` | Console commands that the game does at start-up. For example, `map_name levels\a10\a10` starts the first campaign level. |
|
||||
|
||||
The settings are in `config.toml` next to the executable. Refer to
|
||||
"Settings". Internet play's MQTT brokers are in `brokers.txt` next to it
|
||||
(`network.brokers_file`).
|
||||
"Settings".
|
||||
|
||||
If the game stops because of a fatal signal, it writes the address and a
|
||||
backtrace to the standard error. To find the function at the address, enter
|
||||
@@ -96,11 +95,9 @@ The keyboard and the mouse are a control scheme of their own for the player
|
||||
of controller 1: each action has up to two keys or mouse buttons, which
|
||||
Settings > Controls Setup (or `[controls]` in `config.toml`) changes. The
|
||||
game adds the input of the first gamepad to controller 1. The other
|
||||
gamepads operate controllers 2 to 4. With two or more players on this
|
||||
computer (co-op, or split screen in a network game) and only one gamepad,
|
||||
that gamepad is controller 2 (player 2) and the keyboard and mouse stay
|
||||
controller 1. The gamepad changes controller only when none of its buttons
|
||||
is held. The profile's button layout (Settings > Gamepads) is the
|
||||
gamepads operate controllers 2 to 4. In co-op with only one gamepad, that
|
||||
gamepad is controller 2 (player 2) and the keyboard and mouse stay
|
||||
controller 1. The profile's button layout (Settings > Gamepads) is the
|
||||
gamepads' only.
|
||||
|
||||
| Action | Keys and buttons (default) |
|
||||
@@ -166,22 +163,9 @@ Multiplayer > CO-OP CAMPAIGN is the Xbox's cooperative play, which the PC
|
||||
version does not have: two players on this computer play the campaign in
|
||||
split screen. Player 1 is the player who chose it, on the current profile.
|
||||
Player 2 then chooses a profile with their own controller (a gamepad), and
|
||||
New Game's levels are those either profile has reached. Either player's
|
||||
controller chooses the level and the difficulty. A co-op game does
|
||||
New Game's levels are those either profile has reached. A co-op game does
|
||||
not continue a saved game of one player.
|
||||
|
||||
Network games have split screen too: up to 4 players on each computer. In
|
||||
the game lobby, another controller presses START to join, and the new
|
||||
player's profile is chosen on the ADD PLAYER screen that opens (with any
|
||||
controller). With one gamepad, choose the lobby's ADD PLAYER button first:
|
||||
until then that gamepad shares controller 1 with the keyboard. Two players on one profile get different names from the host. A
|
||||
player's B in the lobby leaves the game alone, and the last player of the
|
||||
computer leaves it for all of them. In the game, each player's pause menu
|
||||
opens on their part of the screen, and its LEAVE GAME is theirs: their part
|
||||
of the screen stays until the game ends. A game under way shows its own
|
||||
screen before JOIN GAME: players join there the same way, START or ADD
|
||||
PLAYER then START, and JOIN GAME brings them all into the game.
|
||||
|
||||
In a multiplayer game, the pause menu (escape) has SETTINGS, which opens
|
||||
the profile's settings while the game goes on, and for the host END GAME.
|
||||
END GAME ends the game as its time limit does: the players stay, and after
|
||||
@@ -220,19 +204,16 @@ the setting for one start of the game. It has priority over the file.
|
||||
|
||||
| Setting | Default | Environment variable | Function |
|
||||
| --- | --- | --- | --- |
|
||||
| `display.mode` | `""` | `HALO_DISPLAY_MODE` | `"fullscreen"`: the display, taken at the mode of `display.resolution` (the nearest the display has), or at its desktop mode. `"borderless"`: a window over the whole desktop, whose mode does not change. `"windowed"`: a window of `display.window_size`. Empty: `display.fullscreen` decides (`true`: borderless). F11 changes between the window and the fullscreen mode. Video Setup sets it. |
|
||||
| `display.fullscreen` | `true` | `HALO_FULLSCREEN` | `true`: borderless, as `display.mode = "borderless"`. `false`: a window, as `display.mode = "windowed"`. Used when `display.mode` is empty. |
|
||||
| `display.resolution` | `"native"` | `HALO_RESOLUTION` | What fullscreen and borderless draw at: `"native"`, the display's own resolution, or `"<width>x<height>"`, such as `"1920x1080"`, 640x480 or more. Fullscreen sets the display to it. Borderless draws at it and scales the picture to the display, where the display has room for it. The picture has 480 lines of the game and the width of the resolution's shape. A window draws at its own size instead. Video Setup's Resolution sets it, from the display's modes; it shows with Fullscreen and Borderless. |
|
||||
| `display.resolution_scaling` | `"native"` | `HALO_RESOLUTION_SCALING` | `"native"`: the game draws at the resolution of the window, or of the display (or `display.resolution`) fullscreen. `"original"`: the game draws the 640x480 picture of the Xbox and scales it up to the window or the display, whatever `display.mode` is. Video Setup sets it. |
|
||||
| `display.window_size` | `""` | `HALO_WINDOW_SIZE` | The size of the window, as `"<width>x<height>"`, such as `"1920x1080"`, 640x480 or more. You can change the size of the window; the game's picture takes its shape. Empty: `display.window_scale` decides. Video Setup's Window Size sets it, from sizes of each shape (4:3, 16:10, 16:9 and 21:9) that fit the desktop; it shows with Windowed. |
|
||||
| `display.window_scale` | `2` | `HALO_WINDOW_SCALE` | Used when `display.window_size` is empty: the size of the window, as a multiple of 640x480. |
|
||||
| `display.mode` | `""` | `HALO_DISPLAY_MODE` | `"fullscreen"`: the display, taken at its desktop resolution. `"borderless"`: a window over the whole desktop. Both draw at the resolution of the display, the picture 480 lines of the game and the width of the display. `"windowed"`: a window with the 640x480 picture of the Xbox, scaled. Empty: `display.fullscreen` decides (`true`: borderless). F11 changes between the window and the fullscreen mode. Video Setup sets it. |
|
||||
| `display.fullscreen` | `true` | `HALO_FULLSCREEN` | `true`: fullscreen at the resolution of the display. The picture has 480 lines of the game and the width of the display. `false`: a window with the 640x480 picture of the Xbox. Used when `display.mode` is empty. |
|
||||
| `display.window_scale` | `2` | `HALO_WINDOW_SCALE` | The size of the window, as a multiple of 640x480. You can change the size of the window. |
|
||||
| `display.vsync` | `true` | `HALO_NO_VSYNC=1` sets `false` | `true`: each frame waits for the display. |
|
||||
| `display.max_fps` | `0` | `HALO_MAX_FPS` | With vsync off, the most frames each second. `0`: twice the display's refresh rate. `-1`: no limit, which can hang some Intel graphics (Raptor Lake), resetting the desktop's graphics too. |
|
||||
| `debug.gpu_flush_draws` | `-1` | `HALO_GPU_FLUSH_DRAWS` | Flush the GPU's pipeline every this many draws. `-1`: every 3 on Intel graphics with Mesa's driver, which can otherwise hang in the game's long runs of small draws and reset the desktop's graphics too. `0`: never. |
|
||||
| `display.interpolation` | `true` | `HALO_INTERPOLATION` | `true`: one frame for each refresh of the display. `false`: 30 frames each second, as on the Xbox. Refer to "Frame rate". |
|
||||
| `display.direct_camera` | `true` | `HALO_DIRECT_CAMERA` | `true`: in first person, on foot, the view points where the player aims in each frame, not where the last tick left it. Refer to "Frame rate". |
|
||||
| `display.high_res_hud` | `true` | `HALO_HIGH_RES_HUD` | `true`: the HUD (meters, counters, panels and their outlines, the motion sensor, reticles, waypoints, scopes) is drawn from the high-res assets in `port/assets/hud`, 8x the size of the maps' bitmaps. The bitmaps with English text keep the maps' own. `false`: the maps' own bitmaps. |
|
||||
| `display.high_res_text` | `true` | `HALO_HIGH_RES_TEXT` | `true`: the menus' and HUD's text is drawn with the fonts in `port/assets/fonts` (Overpass, in place of the maps' Interstate) at the resolution the game draws at, laid out as before, and the menus' titles are drawn from the high-res pictures in `port/assets/titles`. `false`: the maps' bitmap fonts and titles. |
|
||||
| `display.high_res_text` | `true` | `HALO_HIGH_RES_TEXT` | `true`: the menus' and HUD's text is drawn with the fonts in `port/assets/fonts` (Overpass, in place of the maps' Interstate) at the display's resolution, laid out as before, and the menus' titles are drawn from the high-res pictures in `port/assets/titles`. `false`: the maps' bitmap fonts and titles. |
|
||||
| `display.menus` | `"pc"` | `HALO_MENUS` | `"pc"`: the PC version's menus, from the files in `port/assets/menus` and a `menus` folder next to `config.toml`. Refer to "Menus". `"xbox"`: the Xbox's menus. |
|
||||
| `display.player_names` | `"all"` | `HALO_PLAYER_NAMES` | In multiplayer, whose names are drawn above their heads: `"all"`, `"allies"`, `"enemies"` or `"none"`. An ally's name is drawn above the triangle the game shows over teammates. An enemy's name shows only within the motion sensor's reach, while the enemy is in sight and not camouflaged, so it never shows where an enemy hides. The gametype's motion tracker setting also applies: no names if it shows no players, only allies' if it shows only friends. |
|
||||
| `display.player_name_scale` | `1.0` | `HALO_PLAYER_NAME_SCALE` | How large the players' names are drawn: `1.0` is three quarters of the size of the HUD's text, from `0.25` to `4`. With high-res text, larger names are rasterized at their size, so they stay sharp. |
|
||||
@@ -260,12 +241,7 @@ the setting for one start of the game. It has priority over the file.
|
||||
| `network.allow_upnp` | `true` | `HALO_NET_ALLOW_UPNP` | `true`: internet play can ask the router to forward its port (UPnP). `false`: the game does not ask. Refer to "Internet play". |
|
||||
| `network.public_lobby` | `true` | `HALO_NET_PUBLIC_LOBBY` | `true`: the server browser. Public games are listed, and Join Game > Server Browser shows them. `false`: no games are listed or shown. Refer to "Server browser". |
|
||||
| `network.host_public` | `true` | `HALO_NET_HOST_PUBLIC` | `true`: a new game of Create Game > Internet starts as PUBLIC. `false`: it starts as PRIVATE. LISTING in Server Setup changes it for each game. Refer to "Server browser". |
|
||||
| `network.coop_friendly_fire` | `"on"` | `HALO_NET_COOP_FRIENDLY_FIRE` | Whether the players of an online co-op game hurt each other: `"off"`, `"on"`, `"shields_only"` or `"explosives_only"`. FRIENDLY FIRE in co-op's Server Setup writes its choice here. Their AI allies they always can, as in the campaign. |
|
||||
| `network.coop_enemies_mode` | `"per_player"` | `HALO_NET_COOP_ENEMIES_MODE` | Online co-op's extra enemies: `"none"`; `"per_player"`, each squad of enemies that a level places grows by `network.coop_enemies` for each player past the first; or `"multiplier"`, each squad is `network.coop_enemies_multiplier` times as large, for any number of players. The extra enemies stand around the squad's places, and those that a dropship has no seats for drop out of it after its passengers. EXTRA ENEMIES in co-op's Server Setup writes its choice here. |
|
||||
| `network.coop_enemies` | `50` | `HALO_NET_COOP_ENEMIES` | The extra enemies per player, a percentage from `25` to `200`: for each player past the first, each squad of enemies gets this much of itself more (`100`: as many again, so four players meet four times the squad). PER PLAYER in co-op's Server Setup writes its choice here. |
|
||||
| `network.coop_enemies_multiplier` | `2` | `HALO_NET_COOP_ENEMIES_MULTIPLIER` | The static multiplier of the enemies, `2` to `32`: each squad of enemies is this many times as large. MULTIPLIER in co-op's Server Setup writes its choice here. |
|
||||
| `network.coop_public` | `false` | `HALO_NET_COOP_PUBLIC` | `true`: an online co-op game (Create Game > Internet, a SINGLEPLAYER map) starts as PUBLIC. `false`: it starts as PRIVATE. LISTING in co-op's Server Setup writes its choice here. Refer to "Server browser". |
|
||||
| `network.brokers_file` | `"brokers.txt"` | `HALO_NET_BROKERS_FILE` | The file of the public MQTT brokers that let the machines of an invite find each other, and that carry the listings of the server browser: next to `config.toml`, unless a full path. One `host:port` on each line, up to 4; `#` starts a comment. |
|
||||
| `network.signalling_brokers` | three public brokers | `HALO_NET_BROKERS` | The public MQTT brokers (`host:port`, with commas between them) that let the machines of an invite find each other, and that carry the listings of the server browser. |
|
||||
| `network.stun_servers` | Google and Cloudflare | `HALO_NET_STUN` | The public STUN servers (`host:port`, with commas between them) that give the internet address of a machine. |
|
||||
| `discord.application_id` | the application of the project | `HALO_DISCORD_APPLICATION` | The Discord application for invites. Empty: no Discord. |
|
||||
| `update.auto` | `true` | `HALO_UPDATE_AUTO` | `true`: at start-up, the game looks for a new version. Refer to "Updates". `false`: the game does not look. |
|
||||
@@ -457,8 +433,7 @@ Server Setup in Create Game > Internet has a LISTING row:
|
||||
- PRIVATE: only players with the invite link can join.
|
||||
|
||||
Each new game starts as PUBLIC (`network.host_public = false` makes new
|
||||
games start as PRIVATE). An online co-op game starts as PRIVATE, and keeps
|
||||
the last choice of its LISTING (`network.coop_public`). A LAN game is never listed. `network.public_lobby = false` turns the server browser off.
|
||||
games start as PRIVATE). A LAN game is never listed. `network.public_lobby = false` turns the server browser off.
|
||||
|
||||
In the Server Browser, select a game to join it. The game joins the invite
|
||||
of the game, as for a link. When it reaches the host, it opens the lobby.
|
||||
@@ -469,7 +444,7 @@ How it operates (`src/p2p_lobby.c`):
|
||||
|
||||
- The host of a public game publishes a listing: the invite, and the name,
|
||||
map, gametype and player counts of the game. The listing goes to the
|
||||
same MQTT brokers as the invites (`network.brokers_file`), retained,
|
||||
same MQTT brokers as the invites (`network.signalling_brokers`), retained,
|
||||
to a topic of the host (`hceu/3/lobby/s/<hash of its key>`).
|
||||
- The key of the host signs the listing (Ed25519). The key is the key of the
|
||||
invite, so no other machine can list the invite of the host, change its
|
||||
@@ -491,13 +466,8 @@ with the invite can ask the host to connect, and the host then sends its
|
||||
addresses. Thus anyone can learn the address of the host of a public game,
|
||||
as for any public server.
|
||||
|
||||
The brokers are in `brokers.txt` next to the executable (from
|
||||
`port/assets/network/brokers.txt`; on Android, the app writes it next to
|
||||
`config.toml` at each start), one `host:port` on each line. The game uses
|
||||
all of them at once (up to 4), so one that works is enough. An update
|
||||
replaces `brokers.txt`: to use brokers of your own, put them in another
|
||||
file and name it in `network.brokers_file`. All the players must use the
|
||||
same broker to see each other's games. The game uses
|
||||
To use a broker of your own, add it to `network.signalling_brokers`. All the
|
||||
players must use the same broker to see each other's games. The game uses
|
||||
MQTT 5 if the broker has it, else MQTT 3.1.1. A broker that does not keep
|
||||
retained messages, or does not let clients subscribe with wildcards, carries
|
||||
only invites, not listings.
|
||||
@@ -514,7 +484,7 @@ Only machines with the invite can find the game:
|
||||
random 16-byte token. The identifier of the host is from the first 6
|
||||
bytes of the hash.
|
||||
- The machines exchange their public keys and addresses through public MQTT
|
||||
brokers (`network.brokers_file`). The topics are HMACs of the token.
|
||||
brokers (`network.signalling_brokers`). The topics are HMACs of the token.
|
||||
A key from the token encrypts and authenticates the messages
|
||||
(`src/p2p_signal.c`, `src/p2p_crypto.c`). The host authenticates its answer
|
||||
with a key that only it and the player can calculate. Its public key must
|
||||
@@ -554,9 +524,7 @@ Only machines with the invite can find the game:
|
||||
`bans.txt`, and refuses a machine whose address or hardware id is in it.
|
||||
- The host can ban a player with `ban <player name>` in the developer
|
||||
console (Tab completes the name). Remove a line from `bans.txt` to unban.
|
||||
Refer to `NETCODE.md`. `kick <player name>` drops the player the same
|
||||
way, but keeps nothing: no line in `bans.txt`, and the player can join
|
||||
again at once. So that every player can be named, the host trims
|
||||
Refer to `NETCODE.md`. So that every player can be named, the host trims
|
||||
the spaces around a name and removes characters that draw as nothing. A
|
||||
letter with a mark is typed as the plain letter (`ban jose` for "José").
|
||||
A name with nothing left to type becomes "Player", and a name that another
|
||||
@@ -626,7 +594,7 @@ The game sends the activity only to a Discord client of the same user.
|
||||
| Game code | All 466 C files of the game. The changes are in "Game source changes". |
|
||||
| Graphics | Direct3D 8 on OpenGL 4.5 core through SDL3 (`src/d3d8_gl.c`). The port translates the NV2A vertex shaders and register combiners to GLSL. It decodes all the Xbox texture formats. The vertex and index buffers come from a GL copy of the Xbox memory. |
|
||||
| High-res HUD | The HUD is drawn from high-res assets: redraws at 8x the size of the maps' bitmaps (4x for the largest), in `port/assets/hud`. They cover the meters, counters, panels and their outlines, the motion sensor, reticles, waypoints and scopes, but no bitmap with English text. `tools/hud_assets.py` makes them from the SVG redraws, and the build embeds them in the executable. When the game uploads one of those bitmaps, `src/hud_hires.c` gives the high-res texture in its place, if the bitmap's pixels are those of the English maps: another language's maps keep their own. The game sizes and places the HUD from its tags as before. `display.high_res_hud = false` turns this off. |
|
||||
| High-res text | The menus' and HUD's text is drawn with Overpass (`port/assets/fonts`, SIL Open Font License) in place of the maps' bitmap fonts, which are Interstate. `src/text_hires.c` rasterizes each glyph with stb_truetype (`port/third_party/stb`) at the resolution the game draws at, into an atlas that a placeholder bitmap of the game stands for. The game lays the text out from its font tags as before. The menus' titles (the screens' headers and the main menu's items) are pictures of text in the maps, so they are drawn as the high-res HUD is: `tools/title_assets.py` sets each one again in OpenCE, Roger White's public-domain Newtown respaced to match the maps' commercial title typeface (`tools/title_font.py`), at 4x the bitmap's size over its own plate or glow, each letter placed where the map's letter is, in `port/assets/titles`. The postgame carnage report's title is set over a hand-made SVG redraw of its panel (`port/assets/titles/svg`) instead. `display.high_res_text = false` turns it off. |
|
||||
| High-res text | The menus' and HUD's text is drawn with Overpass (`port/assets/fonts`, SIL Open Font License) in place of the maps' bitmap fonts, which are Interstate. `src/text_hires.c` rasterizes each glyph with stb_truetype (`port/third_party/stb`) at the display's resolution, into an atlas that a placeholder bitmap of the game stands for. The game lays the text out from its font tags as before. The menus' titles (the screens' headers and the main menu's items) are pictures of text in the maps, so they are drawn as the high-res HUD is: `tools/title_assets.py` sets each one again in OpenCE, Roger White's public-domain Newtown respaced to match the maps' commercial title typeface (`tools/title_font.py`), at 4x the bitmap's size over its own plate or glow, each letter placed where the map's letter is, in `port/assets/titles`. The postgame carnage report's title is set over a hand-made SVG redraw of its panel (`port/assets/titles/svg`) instead. `display.high_res_text = false` turns it off. |
|
||||
| Sound | Xbox DirectSound on SDL3 audio (`src/dsound_sdl.c`): PCM and Xbox ADPCM, mixed at 48 kHz, with volume, pitch, mix bins, distance, stereo pan, occlusion and obstruction. There is no Doppler effect, no cones and no reverb. |
|
||||
| Input | XInput on SDL3 (`src/xinput_sdl.c`): keyboard, mouse, gamepads with rumble, and the debug keyboard for the console. |
|
||||
| Files | The Win32 file functions and the MSVC file functions on POSIX, with the translation of Xbox paths. |
|
||||
@@ -712,11 +680,11 @@ Other changes:
|
||||
| `networking/`, `game/`, `interface/`, `bungie_net/network/` and the pools of objects, effects and sounds | The system link limits and the memory for them. |
|
||||
| `game/`, `objects/`, `units/`, `networking/` | The distributed netcode. Refer to `NETCODE.md`. |
|
||||
| `cache/cache_files.c` | When a map's tags load and unload, the port finds the bitmaps that the high-res HUD replaces (`game/hud_hires_tags.c`), and adds the tags of the menus to the menus' map (`game/menu_tags.c`). |
|
||||
| `interface/ui_widget.c`, `interface/ui_widget_event_handler_functions.c`, `interface/ui_widget_game_data_input_functions.c` | The main menu is the PC version's from `port/assets/menus` (`display.menus`); the menus' widgets can call the port's functions (`game/menu_functions.c`) and send the PC version's custom activation event; the widgets' memory is 256 KB, not 16 KB; the main menu and Multiplayer clear co-op's controllers, so that a gamepad going back to controller 1 is not a controller unplugged; the lobby's split screen players leave alone, and one who quits in game is not joined to the next game (`interface/player_ui.c`). |
|
||||
| `interface/ui_widget.c`, `interface/ui_widget_event_handler_functions.c`, `interface/ui_widget_game_data_input_functions.c` | The main menu is the PC version's from `port/assets/menus` (`display.menus`); the menus' widgets can call the port's functions (`game/menu_functions.c`) and send the PC version's custom activation event; the widgets' memory is 256 KB, not 16 KB; the main menu and Multiplayer clear co-op's controllers, so that a gamepad going back to controller 1 is not a controller unplugged. |
|
||||
| `input/input_abstraction.c`, `game/player_control.c`, `game/players.c`, `game/player_queues_new.c`, `units/units.h` | The keyboard and mouse's actions (`src/xinput_sdl.c`, `include/halo_keyboard.h`) join controller 1's game controls; their reload key reloads on its own, and their action key only acts (a control flag of the port's, sent with the player's action, stops the reload the controller's X falls back to). |
|
||||
| `sound/sound_manager.c`, `interface/hud.c`, `game/game_engine.c` | The music's and the other sounds' volumes; the HUD's and the scoreboard's settings are read again when Settings changes them. |
|
||||
| `interface/hud.c` | In multiplayer, players' names are drawn above their heads (`display.player_names`, `display.player_name_scale`). |
|
||||
| `rasterizer/rasterizer_text.c`, `text/draw_string.c` | Text is drawn from an atlas of the fonts' glyphs, rasterized at the resolution the game draws at (`src/text_hires.c`), when the font has every character of the string. Text can be drawn scaled about a point (`rasterizer_text_set_scale`), as the players' names are. Each glyph's advance is centred on the font tag character's, so the layout is the same, and a glyph is cut at a text box only where the font tag's character visibly was. |
|
||||
| `rasterizer/rasterizer_text.c`, `text/draw_string.c` | Text is drawn from an atlas of the fonts' glyphs, rasterized at the display's resolution (`src/text_hires.c`), when the font has every character of the string. Text can be drawn scaled about a point (`rasterizer_text_set_scale`), as the players' names are. Each glyph's advance is centred on the font tag character's, so the layout is the same, and a glyph is cut at a text box only where the font tag's character visibly was. |
|
||||
|
||||
The x86 inline assembly of the game is replaced by C. Thus the compiler
|
||||
can optimize that code for each processor:
|
||||
|
||||
@@ -1,523 +0,0 @@
|
||||
/*
|
||||
COOP_ENEMIES.C
|
||||
|
||||
Network co-op's extra enemies: Server Setup's EXTRA ENEMIES
|
||||
(network.coop_enemies_mode). PER PLAYER (network.coop_enemies, a
|
||||
percentage): for each player past the first, each squad of the players'
|
||||
enemies that a level places (encounters.c's encounter_create) gets that
|
||||
much of its count more; at 100%, two players meet twice the squad, four
|
||||
players four times it. STATIC MULTIPLIER (network.coop_enemies_multiplier):
|
||||
each squad is that many times as large, for any number of players. The
|
||||
extra enemies stand in rings about the squad's starting locations, on
|
||||
open ground, so they do not start inside each other.
|
||||
|
||||
A squad a script loads into a dropship (vehicle_load_magic) can be larger
|
||||
than the dropship's seats. The riders left without a seat are erased, and
|
||||
kept: once the dropship's own riders get out (vehicle_unload, or any other
|
||||
way), they are placed beside those riders, a few ticks apart, and drop and
|
||||
fight as they do. A dropship gone first takes them with it.
|
||||
|
||||
Only the host runs the AI, so all of this is the host's.
|
||||
*/
|
||||
|
||||
#include "cseries.h"
|
||||
#include "ai/actor_placement.h"
|
||||
#include "ai/actors.h"
|
||||
#include "ai/ai_scenario_definitions.h"
|
||||
#include "ai/encounters.h"
|
||||
#include "game/game.h"
|
||||
#include "game/game_allegiance.h"
|
||||
#include "game/players.h"
|
||||
#include "networking/network_game_globals.h"
|
||||
#include "networking/network_game_manager.h"
|
||||
#include "objects/objects.h"
|
||||
#include "physics/collisions.h"
|
||||
#include "scenario/scenario.h"
|
||||
#include "scenario/scenario_definitions.h"
|
||||
#include "units/units.h"
|
||||
#include "units/unit_definitions.h"
|
||||
|
||||
#include "coop_enemies.h"
|
||||
#include "network_coop.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
/* port_config.c's */
|
||||
long config_integer(char const *name);
|
||||
char const *config_string(char const *name);
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
enum
|
||||
{
|
||||
/* the actors a level keeps for its own placements, which the extra
|
||||
enemies never take: as many as the Xbox's whole pool */
|
||||
COOP_ENEMIES_LEVEL_ACTORS = 256,
|
||||
/* EXTRA ENEMIES' amounts, as Server Setup has them: 25 to 200% per
|
||||
player, or 2 to 32 times (none is its NONE) */
|
||||
COOP_ENEMIES_MINIMUM_PERCENT = 25,
|
||||
COOP_ENEMIES_MAXIMUM_PERCENT = 200,
|
||||
COOP_ENEMIES_MINIMUM_MULTIPLIER = 2,
|
||||
COOP_ENEMIES_MAXIMUM_MULTIPLIER = 32,
|
||||
/* the places tried for an extra enemy, a ring further out each */
|
||||
SPREAD_ATTEMPTS = 8,
|
||||
SPREAD_PLACES_PER_RING = 6,
|
||||
MAXIMUM_RIDING_VEHICLES = 32,
|
||||
MAXIMUM_SEATED_RIDERS = 16,
|
||||
MAXIMUM_KEPT_RIDERS = 512,
|
||||
/* a kept rider placed every this many ticks, as the seated ones
|
||||
jumped out */
|
||||
RIDER_RELEASE_TICKS = 4,
|
||||
};
|
||||
|
||||
/* the rings' spacing, and the height over the ground the way to a place is
|
||||
looked along, and the ground looked for below it */
|
||||
#define SPREAD_SPACING 0.8f
|
||||
#define SPREAD_STEP_HEIGHT 0.6f
|
||||
#define SPREAD_GROUND_DEPTH 2.0f
|
||||
/* a kept rider's place beside a rider who got out, each a little apart */
|
||||
#define RIDER_SPACING 0.5f
|
||||
|
||||
/* EXTRA ENEMIES' choices (network.coop_enemies_mode's values) */
|
||||
enum
|
||||
{
|
||||
_coop_enemies_none,
|
||||
_coop_enemies_per_player,
|
||||
_coop_enemies_multiplier,
|
||||
};
|
||||
|
||||
/* ---------- structures */
|
||||
|
||||
/* a vehicle a script loaded riders into: its seated ones, and whether they
|
||||
have begun to get out */
|
||||
struct coop_riding_vehicle
|
||||
{
|
||||
long vehicle_index;
|
||||
long seated_unit_indices[MAXIMUM_SEATED_RIDERS];
|
||||
short seat_indices[MAXIMUM_SEATED_RIDERS];
|
||||
short seated_count;
|
||||
boolean releasing;
|
||||
short released_count;
|
||||
long next_release_time;
|
||||
};
|
||||
|
||||
/* a rider left without a seat, to place once its vehicle's get out */
|
||||
struct coop_kept_rider
|
||||
{
|
||||
long vehicle_index;
|
||||
long variant_definition_index;
|
||||
long encounter_index;
|
||||
short squad_index;
|
||||
};
|
||||
|
||||
/* ---------- globals */
|
||||
|
||||
static struct
|
||||
{
|
||||
/* EXTRA ENEMIES, as the game began with it: its choice, and the amount
|
||||
of PER PLAYER (a percentage) and of STATIC MULTIPLIER */
|
||||
short mode;
|
||||
short percent;
|
||||
short multiplier;
|
||||
struct coop_riding_vehicle vehicles[MAXIMUM_RIDING_VEHICLES];
|
||||
short vehicle_count;
|
||||
struct coop_kept_rider riders[MAXIMUM_KEPT_RIDERS];
|
||||
short rider_count;
|
||||
} coop_enemies;
|
||||
|
||||
/* ---------- private code */
|
||||
|
||||
static boolean coop_enemies_host(
|
||||
void)
|
||||
{
|
||||
return game_connection() == _game_connection_network_server && network_coop_active();
|
||||
}
|
||||
|
||||
/* whether this game has extra enemies (it is a co-op host's, with some
|
||||
chosen) */
|
||||
static boolean coop_enemies_on(
|
||||
void)
|
||||
{
|
||||
return coop_enemies.mode != _coop_enemies_none && coop_enemies_host();
|
||||
}
|
||||
|
||||
/* the players in the game, every machine's: the network game's (a level
|
||||
places its first enemies before the players' units are made, as it loads:
|
||||
game_initialize_for_new_map) */
|
||||
static short coop_enemies_player_count(
|
||||
void)
|
||||
{
|
||||
struct network_game *game = network_game_get_game();
|
||||
short count = 0;
|
||||
short index;
|
||||
|
||||
for (index = 0; game && index < HALO_PORT_MAXIMUM_NETWORK_PLAYERS; index++)
|
||||
{
|
||||
if (network_player_is_valid(&game->players[index]))
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* whether the way from a to b is open (no structure between them) */
|
||||
static boolean coop_enemies_open(
|
||||
real_point3d const *a,
|
||||
real_point3d const *b)
|
||||
{
|
||||
struct collision_result collision;
|
||||
real_vector3d vector;
|
||||
|
||||
vector.i = b->x - a->x;
|
||||
vector.j = b->y - a->y;
|
||||
vector.k = b->z - a->z;
|
||||
return !collision_test_vector(FLAG(_collision_test_structure_bit) | FLAG(_collision_test_front_facing_surfaces_bit) |
|
||||
FLAG(_collision_test_back_facing_surfaces_bit), a, &vector, NONE, &collision);
|
||||
}
|
||||
|
||||
/* the ground below a point, within SPREAD_GROUND_DEPTH of it */
|
||||
static boolean coop_enemies_ground(
|
||||
real_point3d const *point,
|
||||
real_point3d *ground)
|
||||
{
|
||||
struct collision_result collision;
|
||||
real_vector3d vector = { 0.0f, 0.0f, -(SPREAD_STEP_HEIGHT + SPREAD_GROUND_DEPTH) };
|
||||
|
||||
if (!collision_test_vector(FLAG(_collision_test_structure_bit) | FLAG(_collision_test_front_facing_surfaces_bit),
|
||||
point, &vector, NONE, &collision))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
*ground = collision.point;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static struct coop_riding_vehicle *coop_enemies_riding_vehicle(
|
||||
long vehicle_index,
|
||||
boolean create)
|
||||
{
|
||||
short index;
|
||||
|
||||
for (index = 0; index < coop_enemies.vehicle_count; index++)
|
||||
{
|
||||
if (coop_enemies.vehicles[index].vehicle_index == vehicle_index)
|
||||
return &coop_enemies.vehicles[index];
|
||||
}
|
||||
if (!create || coop_enemies.vehicle_count >= MAXIMUM_RIDING_VEHICLES)
|
||||
return NULL;
|
||||
csmemset(&coop_enemies.vehicles[coop_enemies.vehicle_count], 0, sizeof(coop_enemies.vehicles[0]));
|
||||
coop_enemies.vehicles[coop_enemies.vehicle_count].vehicle_index = vehicle_index;
|
||||
return &coop_enemies.vehicles[coop_enemies.vehicle_count++];
|
||||
}
|
||||
|
||||
/* the riders kept for a vehicle forgotten, and the vehicle with them */
|
||||
static void coop_enemies_forget_vehicle(
|
||||
short vehicle_number)
|
||||
{
|
||||
long vehicle_index = coop_enemies.vehicles[vehicle_number].vehicle_index;
|
||||
short index, kept = 0;
|
||||
|
||||
for (index = 0; index < coop_enemies.rider_count; index++)
|
||||
{
|
||||
if (coop_enemies.riders[index].vehicle_index != vehicle_index)
|
||||
coop_enemies.riders[kept++] = coop_enemies.riders[index];
|
||||
}
|
||||
coop_enemies.rider_count = kept;
|
||||
coop_enemies.vehicles[vehicle_number] = coop_enemies.vehicles[--coop_enemies.vehicle_count];
|
||||
}
|
||||
|
||||
/* whether a vehicle's seated riders have begun to get out: one alive and
|
||||
out of it (one killed in its seat is not getting out) */
|
||||
static boolean coop_enemies_riders_getting_out(
|
||||
struct coop_riding_vehicle const *vehicle)
|
||||
{
|
||||
short index;
|
||||
|
||||
for (index = 0; index < vehicle->seated_count; index++)
|
||||
{
|
||||
struct object_datum *unit = object_try_and_get_and_verify_type(vehicle->seated_unit_indices[index],
|
||||
_object_mask_unit);
|
||||
|
||||
if (unit && !TEST_FLAG(unit->object.damage_flags, _object_dead_bit) &&
|
||||
unit->object.parent_object_index != vehicle->vehicle_index)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* where a vehicle's `number`th kept rider is placed: beside one of the
|
||||
riders who got out of it (where it is now: out of the vehicle, in the
|
||||
open), a ring further about them each round; else at a seat's marker, else
|
||||
the vehicle's place */
|
||||
static void coop_enemies_rider_place(
|
||||
struct coop_riding_vehicle const *vehicle,
|
||||
short number,
|
||||
real_point3d *position,
|
||||
real *facing)
|
||||
{
|
||||
struct object_datum *object = object_get(vehicle->vehicle_index);
|
||||
struct unit_definition *definition = unit_definition_get(object->definition_index);
|
||||
long out_unit_indices[MAXIMUM_SEATED_RIDERS];
|
||||
short out_count = 0;
|
||||
short index, round;
|
||||
real angle;
|
||||
|
||||
*position = object->object.position;
|
||||
*facing = (real)atan2(object->object.forward.j, object->object.forward.i);
|
||||
for (index = 0; index < vehicle->seated_count; index++)
|
||||
{
|
||||
struct object_datum *unit = object_try_and_get_and_verify_type(vehicle->seated_unit_indices[index],
|
||||
_object_mask_unit);
|
||||
|
||||
if (unit && !TEST_FLAG(unit->object.damage_flags, _object_dead_bit) &&
|
||||
unit->object.parent_object_index != vehicle->vehicle_index)
|
||||
{
|
||||
out_unit_indices[out_count++] = vehicle->seated_unit_indices[index];
|
||||
}
|
||||
}
|
||||
if (out_count > 0)
|
||||
{
|
||||
*position = object_get(out_unit_indices[number % out_count])->object.position;
|
||||
round = (short)(number / out_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct object_marker marker;
|
||||
|
||||
round = number;
|
||||
if (vehicle->seated_count > 0 && VALID_INDEX(vehicle->seat_indices[0], definition->unit.seats.count))
|
||||
{
|
||||
struct unit_seat *seat = TAG_BLOCK_GET_ELEMENT(&definition->unit.seats, vehicle->seat_indices[0],
|
||||
struct unit_seat);
|
||||
|
||||
if (seat->marker_name[0] &&
|
||||
object_get_marker_by_name(vehicle->vehicle_index, seat->marker_name, &marker, 1) > 0)
|
||||
{
|
||||
*position = marker.matrix.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* (never on the rider's own place: a ring about it, further each round) */
|
||||
angle = (real)number * (_pi * 2.0f / SPREAD_PLACES_PER_RING);
|
||||
position->x += (real)cos(angle) * RIDER_SPACING * (real)(round / SPREAD_PLACES_PER_RING + 1);
|
||||
position->y += (real)sin(angle) * RIDER_SPACING * (real)(round / SPREAD_PLACES_PER_RING + 1);
|
||||
}
|
||||
|
||||
/* ---------- public code */
|
||||
|
||||
void coop_enemies_new_game(
|
||||
void)
|
||||
{
|
||||
char const *mode = config_string("network.coop_enemies_mode");
|
||||
|
||||
csmemset(&coop_enemies, 0, sizeof(coop_enemies));
|
||||
/* (an amount past Server Setup's, as it shows it: its nearest choice's
|
||||
end) */
|
||||
coop_enemies.percent = (short)PIN(config_integer("network.coop_enemies"), COOP_ENEMIES_MINIMUM_PERCENT,
|
||||
COOP_ENEMIES_MAXIMUM_PERCENT);
|
||||
coop_enemies.multiplier = (short)PIN(config_integer("network.coop_enemies_multiplier"),
|
||||
COOP_ENEMIES_MINIMUM_MULTIPLIER, COOP_ENEMIES_MAXIMUM_MULTIPLIER);
|
||||
coop_enemies.mode = !strcmp(mode, "per_player") ? _coop_enemies_per_player :
|
||||
!strcmp(mode, "multiplier") ? _coop_enemies_multiplier : _coop_enemies_none;
|
||||
}
|
||||
|
||||
void coop_enemies_reset(
|
||||
void)
|
||||
{
|
||||
coop_enemies.vehicle_count = 0;
|
||||
coop_enemies.rider_count = 0;
|
||||
}
|
||||
|
||||
short coop_enemies_extra_count(
|
||||
long encounter_index,
|
||||
short count)
|
||||
{
|
||||
struct encounter_datum *encounter;
|
||||
long extra, room;
|
||||
short players;
|
||||
|
||||
if (count <= 0 || !coop_enemies_on())
|
||||
return 0;
|
||||
encounter = encounter_get(encounter_index);
|
||||
if (!game_team_is_enemy(_game_team_player, encounter->team_index))
|
||||
return 0;
|
||||
if (coop_enemies.mode == _coop_enemies_multiplier)
|
||||
extra = (long)count * (coop_enemies.multiplier - 1);
|
||||
else
|
||||
{
|
||||
players = coop_enemies_player_count();
|
||||
if (players <= 1)
|
||||
return 0;
|
||||
extra = ((long)count * coop_enemies.percent * (players - 1) + 50) / 100;
|
||||
}
|
||||
room = MAXIMUM_ACTORS - COOP_ENEMIES_LEVEL_ACTORS - actor_data->actual_count;
|
||||
return (short)PIN(extra, 0, MAX(room, 0));
|
||||
}
|
||||
|
||||
boolean coop_enemies_spread_position(
|
||||
real_point3d const *origin,
|
||||
short number,
|
||||
real_point3d *position)
|
||||
{
|
||||
real_point3d from = *origin;
|
||||
short place = (short)((number - 1) % SPREAD_PLACES_PER_RING);
|
||||
short ring = (short)((number - 1) / SPREAD_PLACES_PER_RING);
|
||||
short attempt;
|
||||
|
||||
from.z += SPREAD_STEP_HEIGHT;
|
||||
for (attempt = 0; attempt < SPREAD_ATTEMPTS; attempt++, ring++)
|
||||
{
|
||||
/* (each ring turned half a place from the last, so its places fall
|
||||
between the last's) */
|
||||
real angle = ((real)place + 0.5f * (real)(ring % 2)) * (_pi * 2.0f / SPREAD_PLACES_PER_RING);
|
||||
real radius = SPREAD_SPACING * (real)(ring + 1);
|
||||
real_point3d to = from;
|
||||
|
||||
to.x += (real)cos(angle) * radius;
|
||||
to.y += (real)sin(angle) * radius;
|
||||
if (coop_enemies_open(&from, &to) && coop_enemies_ground(&to, position))
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void coop_enemies_rider_seated(
|
||||
long vehicle_index,
|
||||
long unit_index)
|
||||
{
|
||||
struct coop_riding_vehicle *vehicle;
|
||||
struct unit_datum *unit;
|
||||
|
||||
if (!coop_enemies_on())
|
||||
return;
|
||||
vehicle = coop_enemies_riding_vehicle(vehicle_index, TRUE);
|
||||
unit = unit_get(unit_index);
|
||||
if (!vehicle)
|
||||
return;
|
||||
/* (a vehicle loaded again: the riders that got out of it before are not
|
||||
this load's) */
|
||||
{
|
||||
short index, kept = 0;
|
||||
|
||||
for (index = 0; index < vehicle->seated_count; index++)
|
||||
{
|
||||
struct object_datum *seated = object_try_and_get_and_verify_type(vehicle->seated_unit_indices[index],
|
||||
_object_mask_unit);
|
||||
|
||||
if (seated && seated->object.parent_object_index == vehicle_index)
|
||||
{
|
||||
vehicle->seated_unit_indices[kept] = vehicle->seated_unit_indices[index];
|
||||
vehicle->seat_indices[kept++] = vehicle->seat_indices[index];
|
||||
}
|
||||
}
|
||||
vehicle->seated_count = kept;
|
||||
}
|
||||
if (vehicle->seated_count >= MAXIMUM_SEATED_RIDERS)
|
||||
return;
|
||||
vehicle->seated_unit_indices[vehicle->seated_count] = unit_index;
|
||||
vehicle->seat_indices[vehicle->seated_count] = unit->unit.parent_seat_index;
|
||||
vehicle->seated_count++;
|
||||
/* (a vehicle loaded again, after its riders got out: they get out again) */
|
||||
vehicle->releasing = FALSE;
|
||||
}
|
||||
|
||||
boolean coop_enemies_rider_unseated(
|
||||
long vehicle_index,
|
||||
long unit_index)
|
||||
{
|
||||
struct unit_datum *unit;
|
||||
struct actor_datum *actor;
|
||||
struct coop_kept_rider *rider;
|
||||
struct coop_riding_vehicle *vehicle;
|
||||
|
||||
if (!coop_enemies_on() || coop_enemies.rider_count >= MAXIMUM_KEPT_RIDERS)
|
||||
return FALSE;
|
||||
unit = unit_get(unit_index);
|
||||
vehicle = coop_enemies_riding_vehicle(vehicle_index, FALSE);
|
||||
/* (kept only for a vehicle with riders seated, whose getting out places
|
||||
it: one of a load that seated nobody is left where it is) */
|
||||
if (unit->object.type != _object_type_biped || unit->unit.actor_index == NONE || !vehicle ||
|
||||
vehicle->seated_count == 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
actor = actor_get(unit->unit.actor_index);
|
||||
if (actor->meta.swarm || actor->meta.encounter_index == NONE ||
|
||||
!game_team_is_enemy(_game_team_player, encounter_get(actor->meta.encounter_index)->team_index))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
rider = &coop_enemies.riders[coop_enemies.rider_count++];
|
||||
rider->vehicle_index = vehicle_index;
|
||||
rider->variant_definition_index = actor->meta.variant_definition_index;
|
||||
rider->encounter_index = actor->meta.encounter_index;
|
||||
rider->squad_index = actor->meta.squad_index;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void coop_enemies_update(
|
||||
void)
|
||||
{
|
||||
long now = game_time_get();
|
||||
short vehicle_number;
|
||||
|
||||
if (!coop_enemies_host())
|
||||
return;
|
||||
for (vehicle_number = coop_enemies.vehicle_count - 1; vehicle_number >= 0; vehicle_number--)
|
||||
{
|
||||
struct coop_riding_vehicle *vehicle = &coop_enemies.vehicles[vehicle_number];
|
||||
struct object_datum *object = object_try_and_get_and_verify_type(vehicle->vehicle_index, _object_mask_unit);
|
||||
short index;
|
||||
|
||||
/* (the vehicle's next kept rider) */
|
||||
for (index = 0; index < coop_enemies.rider_count; index++)
|
||||
{
|
||||
if (coop_enemies.riders[index].vehicle_index == vehicle->vehicle_index)
|
||||
break;
|
||||
}
|
||||
/* (a vehicle gone, or destroyed, takes its kept riders with it; one
|
||||
with none kept, every one loaded seated or all placed, is done with) */
|
||||
if (!object || TEST_FLAG(object->object.damage_flags, _object_dead_bit) || index >= coop_enemies.rider_count)
|
||||
{
|
||||
coop_enemies_forget_vehicle(vehicle_number);
|
||||
continue;
|
||||
}
|
||||
if (!vehicle->releasing)
|
||||
{
|
||||
if (!coop_enemies_riders_getting_out(vehicle))
|
||||
continue;
|
||||
vehicle->releasing = TRUE;
|
||||
vehicle->released_count = 0;
|
||||
vehicle->next_release_time = now;
|
||||
}
|
||||
if (now < vehicle->next_release_time)
|
||||
continue;
|
||||
{
|
||||
struct coop_kept_rider rider = coop_enemies.riders[index];
|
||||
struct encounter_definition *encounter_definition;
|
||||
struct squad_definition *squad_definition;
|
||||
struct actor_starting_location location;
|
||||
|
||||
coop_enemies.riders[index] = coop_enemies.riders[--coop_enemies.rider_count];
|
||||
encounter_definition = TAG_BLOCK_GET_ELEMENT(&global_scenario_get()->ai_encounters,
|
||||
DATUM_INDEX_TO_ABSOLUTE_INDEX(rider.encounter_index), struct encounter_definition);
|
||||
squad_definition = TAG_BLOCK_GET_ELEMENT(&encounter_definition->squads, rider.squad_index,
|
||||
struct squad_definition);
|
||||
/* (as the squad's: its states, no command list of a starting
|
||||
location's) */
|
||||
csmemset(&location, 0, sizeof(location));
|
||||
coop_enemies_rider_place(vehicle, vehicle->released_count++, &location.position, &location.facing);
|
||||
location.cluster_index = NONE;
|
||||
location.noncombat_sequence_id = NONE;
|
||||
location.default_state = squad_definition->default_state;
|
||||
location.initial_state = squad_definition->initial_state;
|
||||
location.actor_variant_index = NONE;
|
||||
location.command_list_index = NONE;
|
||||
actor_place(rider.variant_definition_index, rider.encounter_index, rider.squad_index, &location, FALSE, 0);
|
||||
encounter_update_status(rider.encounter_index);
|
||||
}
|
||||
vehicle->next_release_time = now + RIDER_RELEASE_TICKS;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
COOP_ENEMIES.H
|
||||
|
||||
Network co-op's extra enemies, for its players (coop_enemies.c).
|
||||
*/
|
||||
|
||||
#ifndef __COOP_ENEMIES_H
|
||||
#define __COOP_ENEMIES_H
|
||||
|
||||
/* network_coop.c: a game begins (its EXTRA ENEMIES read), or the host's
|
||||
game state went back (a cutscene skipped): the dropships' riders kept are
|
||||
forgotten */
|
||||
void coop_enemies_new_game(
|
||||
void);
|
||||
void coop_enemies_reset(
|
||||
void);
|
||||
|
||||
/* encounters.c: how many enemies more a squad placed with `count` gets
|
||||
(none but on a co-op host, for an encounter of the players' enemies) */
|
||||
short coop_enemies_extra_count(
|
||||
long encounter_index,
|
||||
short count);
|
||||
|
||||
/* encounters.c: the place of a squad's `number`th extra enemy (from 1) at
|
||||
its starting location `origin`: rings about it, on open ground; FALSE if
|
||||
none is (the starting location itself then) */
|
||||
boolean coop_enemies_spread_position(
|
||||
real_point3d const *origin,
|
||||
short number,
|
||||
real_point3d *position);
|
||||
|
||||
/* units.c's vehicle_scripting_load_magic: a rider seated, and one left
|
||||
without a seat (the vehicle's seats taken: an extra enemy), which is kept,
|
||||
off the map, until the vehicle's riders get out (TRUE: kept, the caller
|
||||
erases it) */
|
||||
void coop_enemies_rider_seated(
|
||||
long vehicle_index,
|
||||
long unit_index);
|
||||
boolean coop_enemies_rider_unseated(
|
||||
long vehicle_index,
|
||||
long unit_index);
|
||||
|
||||
/* network_coop.c, after each of the host's ticks: the riders kept placed
|
||||
at their vehicle's seats once its own riders get out */
|
||||
void coop_enemies_update(
|
||||
void);
|
||||
|
||||
#endif
|
||||
@@ -1,285 +0,0 @@
|
||||
/*
|
||||
COOP_SPECTATE.C
|
||||
|
||||
Spectating in network co-op. A dead player watches a living teammate
|
||||
until they respawn (players.c respawns them beside a teammate once it is
|
||||
safe). The campaign's dead camera would watch the body; instead this
|
||||
follows a teammate from behind, and A (jump on the keyboard) switches to
|
||||
the next one. director.c and hud.c call in here.
|
||||
|
||||
It also draws the line telling players about the vote to skip a cutscene
|
||||
(network_coop.c counts the votes).
|
||||
*/
|
||||
|
||||
#include "cseries.h"
|
||||
#include "camera/dead_camera.h"
|
||||
#include "game/game.h"
|
||||
#include "game/game_engine.h"
|
||||
#include "game/players.h"
|
||||
#include "input/input.h"
|
||||
#include "interface/hud_messaging.h"
|
||||
#include "objects/objects.h"
|
||||
#include "rasterizer/rasterizer.h"
|
||||
#include "render/render.h"
|
||||
#include "text/draw_string.h"
|
||||
#include "text/font_group.h"
|
||||
#include "text/unicode.h"
|
||||
#include "units/units.h"
|
||||
|
||||
#include "coop_spectate.h"
|
||||
#include "network_coop.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
/* camera distance behind the unit, pitch (radians), and how much of the
|
||||
turn toward the unit's facing it makes each frame */
|
||||
#define SPECTATE_DISTANCE 3.5f
|
||||
#define SPECTATE_PITCH -0.3f
|
||||
#define SPECTATE_TURN 0.15f
|
||||
|
||||
/* a passenger (riding a Pelican) is watched from in front of them, above
|
||||
and looking down, so the camera stays inside the vehicle; a wide view (the
|
||||
observer allows up to 90 degrees) takes in all of them from that close */
|
||||
#define SPECTATE_PASSENGER_DISTANCE 1.0f
|
||||
#define SPECTATE_PASSENGER_PITCH -0.5f
|
||||
#define SPECTATE_PASSENGER_FIELD_OF_VIEW DEGREES_TO_RADIANS(85.0f)
|
||||
/* field of view for every other spectate view (dead_camera.c's default) */
|
||||
#define SPECTATE_FIELD_OF_VIEW DEGREES_TO_RADIANS(70.0f)
|
||||
|
||||
/* a teammate the scripts hold (Pillar of Autumn's cryo tube) is watched from
|
||||
in front, far enough back to see all of what holds them */
|
||||
#define SPECTATE_HELD_DISTANCE 2.0f
|
||||
#define SPECTATE_HELD_PITCH -0.1f
|
||||
|
||||
/* ---------- globals */
|
||||
|
||||
/* the player each local player is watching, or NONE */
|
||||
static long coop_spectate_watched[MAXIMUM_LOCAL_PLAYERS] = { NONE, NONE, NONE, NONE };
|
||||
|
||||
/* input_abstraction.c: how many ticks the keyboard's jump key has been held */
|
||||
byte input_abstraction_port_accept(short controller_index);
|
||||
|
||||
/* ---------- private code */
|
||||
|
||||
/* The next living player after `after` (or the first, if `after` is NONE),
|
||||
skipping `self` and wrapping around. NONE if nobody else is alive. */
|
||||
static long next_living_player(long self, long after)
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
long first = NONE;
|
||||
boolean passed = after == NONE;
|
||||
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
if (iterator.datum_index == after)
|
||||
{
|
||||
passed = TRUE;
|
||||
continue;
|
||||
}
|
||||
if (iterator.datum_index == self || player->unit_index == NONE)
|
||||
continue;
|
||||
if (passed)
|
||||
return iterator.datum_index;
|
||||
if (first == NONE)
|
||||
first = iterator.datum_index;
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
/* Returns TRUE once per press of A (or jump on the keyboard), however long
|
||||
it is held. The buttons count ticks, and one tick can span several frames,
|
||||
so testing "held" alone would switch more than once. */
|
||||
static boolean next_pressed(short controller_index)
|
||||
{
|
||||
static boolean held[MAXIMUM_GAMEPADS];
|
||||
struct gamepad_state const *gamepad;
|
||||
boolean down;
|
||||
boolean pressed;
|
||||
|
||||
if (controller_index < 0 || controller_index >= MAXIMUM_GAMEPADS)
|
||||
return FALSE;
|
||||
gamepad = input_get_gamepad_state(controller_index);
|
||||
down = (gamepad && gamepad->buttons[FIRST_GAMEPAD_ANALOG_BUTTON + _gamepad_analog_button_a] > 0) ||
|
||||
input_abstraction_port_accept(controller_index) > 0;
|
||||
pressed = down && !held[controller_index];
|
||||
held[controller_index] = down;
|
||||
return pressed;
|
||||
}
|
||||
|
||||
/* whether the unit rides in a vehicle it isn't driving */
|
||||
static boolean unit_is_passenger(
|
||||
long unit_index)
|
||||
{
|
||||
long vehicle_index = object_get(unit_index)->object.parent_object_index;
|
||||
|
||||
return vehicle_index != NONE && object_try_and_get_and_verify_type(vehicle_index, _object_mask_unit) &&
|
||||
unit_get(vehicle_index)->unit.driver_object_index != unit_index;
|
||||
}
|
||||
|
||||
/* ---------- public code */
|
||||
|
||||
boolean coop_spectating(
|
||||
void)
|
||||
{
|
||||
return network_coop_active();
|
||||
}
|
||||
|
||||
long coop_spectate_unit(
|
||||
short local_player_index)
|
||||
{
|
||||
long self = local_player_get_player_index(local_player_index);
|
||||
long *watched = &coop_spectate_watched[local_player_index];
|
||||
struct player_datum *player;
|
||||
|
||||
boolean next;
|
||||
|
||||
if (self == NONE)
|
||||
return NONE;
|
||||
player = player_get(self);
|
||||
/* checked every frame so a press is never missed or counted twice */
|
||||
next = next_pressed(player->network_player_data.controller_index);
|
||||
/* alive: keep `watched`, so the next death starts on the same teammate */
|
||||
if (player->unit_index != NONE)
|
||||
return NONE;
|
||||
if (*watched == NONE || !player_try_and_get(*watched) || player_get(*watched)->unit_index == NONE || next)
|
||||
{
|
||||
*watched = next_living_player(self, *watched);
|
||||
}
|
||||
|
||||
return *watched != NONE ? player_get(*watched)->unit_index : NONE;
|
||||
}
|
||||
|
||||
boolean coop_spectate_watching_rider(
|
||||
short local_player_index)
|
||||
{
|
||||
long self = local_player_get_player_index(local_player_index);
|
||||
long watched = coop_spectate_watched[local_player_index];
|
||||
long unit_index;
|
||||
long driver_index;
|
||||
|
||||
if (self == NONE || player_get(self)->unit_index != NONE)
|
||||
return FALSE;
|
||||
/* the scripted camera may have been running since before this player
|
||||
died, so nobody has been picked to watch yet */
|
||||
if (watched == NONE || !player_try_and_get(watched) || player_get(watched)->unit_index == NONE)
|
||||
watched = next_living_player(self, NONE);
|
||||
unit_index = watched != NONE ? player_get(watched)->unit_index : NONE;
|
||||
if (unit_index == NONE || !unit_is_passenger(unit_index))
|
||||
return FALSE;
|
||||
driver_index = unit_get(object_get(unit_index)->object.parent_object_index)->unit.driver_object_index;
|
||||
return driver_index == NONE || unit_get(driver_index)->unit.player_index == NONE;
|
||||
}
|
||||
|
||||
boolean coop_spectate_nothing_to_watch(
|
||||
short local_player_index)
|
||||
{
|
||||
long self = local_player_get_player_index(local_player_index);
|
||||
|
||||
return self != NONE && player_get(self)->unit_index == NONE && next_living_player(self, NONE) == NONE;
|
||||
}
|
||||
|
||||
void coop_spectate_camera(
|
||||
struct dead_camera *camera)
|
||||
{
|
||||
struct object_datum *unit = camera->unit_index != NONE ? object_try_and_get(camera->unit_index) : NULL;
|
||||
real yaw, turn;
|
||||
|
||||
if (!unit)
|
||||
return;
|
||||
yaw = (real)atan2(unit->object.forward.j, unit->object.forward.i);
|
||||
if (unit_is_passenger(camera->unit_index))
|
||||
{
|
||||
/* fixed in front of the seat, turning with the vehicle */
|
||||
camera->facing.yaw = yaw + _pi;
|
||||
camera->facing.pitch = SPECTATE_PASSENGER_PITCH;
|
||||
camera->distance = SPECTATE_PASSENGER_DISTANCE;
|
||||
camera->field_of_view = SPECTATE_PASSENGER_FIELD_OF_VIEW;
|
||||
return;
|
||||
}
|
||||
camera->field_of_view = SPECTATE_FIELD_OF_VIEW;
|
||||
if (!player_input_enabled())
|
||||
{
|
||||
camera->facing.yaw = yaw + _pi;
|
||||
camera->facing.pitch = SPECTATE_HELD_PITCH;
|
||||
camera->distance = SPECTATE_HELD_DISTANCE;
|
||||
return;
|
||||
}
|
||||
/* ease the camera round behind the unit */
|
||||
turn = yaw - camera->facing.yaw;
|
||||
while (turn > _pi)
|
||||
turn -= 2.0f * _pi;
|
||||
while (turn < -_pi)
|
||||
turn += 2.0f * _pi;
|
||||
camera->facing.yaw += turn * SPECTATE_TURN;
|
||||
camera->facing.pitch = SPECTATE_PITCH;
|
||||
camera->distance = SPECTATE_DISTANCE;
|
||||
}
|
||||
|
||||
/* centred text near the bottom of the screen, in the HUD's font */
|
||||
static void draw_bottom_text(
|
||||
wchar_t const *text)
|
||||
{
|
||||
long font_index = hud_get_font_index();
|
||||
struct font_header *font;
|
||||
real_argb_color color = *global_real_argb_white;
|
||||
rectangle2d bounds;
|
||||
short height;
|
||||
|
||||
if (font_index == NONE)
|
||||
return;
|
||||
font = font_definition_get(font_index);
|
||||
height = (short)(font->ascending_height + font->descending_height);
|
||||
bounds = render.camera.window_bounds;
|
||||
bounds.y0 = (short)(bounds.y1 - 3 * height - 24);
|
||||
bounds.y1 = (short)(bounds.y1 - 24);
|
||||
/* draw mode 2: centered */
|
||||
draw_string_set_draw_mode(font_index, NONE, 2, 0, &color);
|
||||
rasterizer_draw_unicode_string(&bounds, NULL, NULL, 0, text);
|
||||
}
|
||||
|
||||
void coop_spectate_draw(
|
||||
short local_player_index)
|
||||
{
|
||||
long watched = coop_spectate_watched[local_player_index];
|
||||
wchar_t text[96];
|
||||
wchar_t const *hint;
|
||||
|
||||
/* nobody to watch yet (still joining): network_coop.c shows the host's
|
||||
view from behind instead */
|
||||
if (watched == NONE || !player_try_and_get(watched))
|
||||
{
|
||||
if (coop_spectate_nothing_to_watch(local_player_index))
|
||||
draw_bottom_text(L"JOINING THE GAME\r\nYou spawn beside a teammate once it is safe");
|
||||
return;
|
||||
}
|
||||
if (!players_coop_waiting_to_start(local_player_get_player_index(local_player_index)))
|
||||
hint = L"You come back beside them once it is safe";
|
||||
else
|
||||
hint = L"You join them when they're on foot";
|
||||
usnprintf(text, NUMBEROF(text) - 1, L"SPECTATING %.12s (A: NEXT)\r\n%s", player_get(watched)->name, hint);
|
||||
text[NUMBEROF(text) - 1] = 0;
|
||||
draw_bottom_text(text);
|
||||
}
|
||||
|
||||
void coop_skip_vote_draw(
|
||||
short local_player_index)
|
||||
{
|
||||
short votes, voters;
|
||||
boolean voted;
|
||||
wchar_t text[96];
|
||||
|
||||
/* once, for the first local player */
|
||||
if (local_player_index != local_player_get_next(NONE) || !network_coop_skip_vote_status(&votes, &voters, &voted))
|
||||
return;
|
||||
if (voted)
|
||||
usnprintf(text, NUMBEROF(text) - 1, L"VOTED TO SKIP %d OF %d", votes, voters);
|
||||
else
|
||||
usnprintf(text, NUMBEROF(text) - 1, L"PRESS SPACE OR A TO VOTE TO SKIP %d OF %d", votes, voters);
|
||||
text[NUMBEROF(text) - 1] = 0;
|
||||
draw_bottom_text(text);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
COOP_SPECTATE.H
|
||||
|
||||
Spectating in network co-op, and the cutscene skip vote's line
|
||||
(coop_spectate.c).
|
||||
*/
|
||||
|
||||
#ifndef __COOP_SPECTATE_H
|
||||
#define __COOP_SPECTATE_H
|
||||
|
||||
struct dead_camera;
|
||||
|
||||
/* whether this is network co-op: a network game with no game engine (a campaign map) */
|
||||
boolean coop_spectating(
|
||||
void);
|
||||
|
||||
/* the unit a dead local player watches (A switches teammate), or NONE if
|
||||
the player is alive or nobody else is */
|
||||
long coop_spectate_unit(
|
||||
short local_player_index);
|
||||
|
||||
/* TRUE if the teammate this local player is spectating (while dead or not
|
||||
yet spawned) rides in an AI-driven vehicle, such as an insertion Pelican.
|
||||
network_coop.c then shows that seat instead of the scripted camera. */
|
||||
boolean coop_spectate_watching_rider(
|
||||
short local_player_index);
|
||||
|
||||
/* whether a local player has nothing to look at: no unit of its own and
|
||||
no living teammate to watch (network_coop.c shows it the host's view) */
|
||||
boolean coop_spectate_nothing_to_watch(
|
||||
short local_player_index);
|
||||
|
||||
/* keeps the dead camera behind the watched unit as it turns */
|
||||
void coop_spectate_camera(
|
||||
struct dead_camera *camera);
|
||||
|
||||
/* the HUD line saying who a dead local player is watching */
|
||||
void coop_spectate_draw(
|
||||
short local_player_index);
|
||||
|
||||
/* during a skippable cutscene in network co-op, the vote count and how to vote */
|
||||
void coop_skip_vote_draw(
|
||||
short local_player_index);
|
||||
|
||||
#endif
|
||||
@@ -45,8 +45,6 @@ no screen pauses the game (a network game's clock runs on).
|
||||
/* the platform layer's (port/linux/src) */
|
||||
void platform_log(char const *format, ...);
|
||||
char const *config_string(char const *name);
|
||||
int platform_display_resolutions(long *widths, long *heights, int maximum);
|
||||
int platform_window_sizes(long *widths, long *heights, int maximum);
|
||||
|
||||
/* the game's network server (this machine hosts: network_game_globals.c) */
|
||||
void *global_network_game_server_get(void);
|
||||
@@ -343,12 +341,8 @@ static char const *const port_function_names[] =
|
||||
"mp level select", "mp profiles list initialize", "mp profiles list dispose", "mp profile set for game",
|
||||
"port lobby preview join",
|
||||
"port setup edit",
|
||||
"port map list back",
|
||||
"port pause end game",
|
||||
"port coop begin", "port coop player 2 list initialize", "port coop player 2",
|
||||
"port lobby open", "port lobby add player", "port lobby join", "port lobby leave",
|
||||
"port lobby player list initialize", "port lobby player choose",
|
||||
"port lobby preview add", "port lobby preview leave",
|
||||
/* (the gametype editor's: the Xbox's walk their rows by place, which the
|
||||
PC version's screens changed) */
|
||||
"mp profile begin editing", "mp profile save changes", "request del playlist profile", "final del playlist profile",
|
||||
@@ -704,41 +698,6 @@ static long split(char const *text, char const **pieces)
|
||||
return count;
|
||||
}
|
||||
|
||||
/* a spinner's strings, or its values, split: its own, but Video Setup's
|
||||
sizes are the display's (sdl_platform.c), shown "1920 x 1080" and set
|
||||
"1920x1080": its resolutions after Resolution's own (NATIVE), and the
|
||||
window sizes that fit it in place of Window Size's own */
|
||||
static long spinner_split(struct halo_menu_widget const *widget, boolean values, char const **pieces)
|
||||
{
|
||||
long count = split(values ? widget->values : widget->strings, pieces);
|
||||
long widths[MAXIMUM_STRINGS], heights[MAXIMUM_STRINGS];
|
||||
long added, index;
|
||||
|
||||
if (widget->setting && !strcmp(widget->setting, "display.resolution"))
|
||||
added = platform_display_resolutions(widths, heights, (int)(MAXIMUM_STRINGS - count));
|
||||
else if (widget->setting && !strcmp(widget->setting, "display.window_size"))
|
||||
{
|
||||
added = platform_window_sizes(widths, heights, MAXIMUM_STRINGS);
|
||||
if (added > 0)
|
||||
count = 0;
|
||||
}
|
||||
else
|
||||
return count;
|
||||
for (index = 0; index < added; index++)
|
||||
{
|
||||
char text[32];
|
||||
char *piece;
|
||||
|
||||
snprintf(text, sizeof(text), values ? "%ldx%ld" : "%ld x %ld", widths[index], heights[index]);
|
||||
piece = allocate((long)strlen(text) + 1);
|
||||
if (!piece)
|
||||
break;
|
||||
strcpy(piece, text);
|
||||
pieces[count++] = piece;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void *bitmap_build(struct halo_menu_bitmap const *source, long tag_index)
|
||||
{
|
||||
struct bitmap_group *group = allocate(sizeof(struct bitmap_group));
|
||||
@@ -953,7 +912,7 @@ static void setting_add(struct halo_menu_widget const *source, long definition_i
|
||||
setting->definition_index = definition_index;
|
||||
setting->setting = source->setting;
|
||||
setting->loaded_index = NONE;
|
||||
setting->value_count = spinner_split(source, TRUE, setting->values);
|
||||
setting->value_count = split(source->values, setting->values);
|
||||
}
|
||||
|
||||
/* a font: large, small, terminal, or the map's by its path */
|
||||
@@ -1616,8 +1575,9 @@ void menu_tags_loaded(
|
||||
{
|
||||
return;
|
||||
}
|
||||
/* (in VR, the Xbox's unless vr.menus asks for the PC version's) */
|
||||
if (halo_vr_active() && strcmp(config_string("vr.menus"), "pc"))
|
||||
/* (in VR, the Xbox's: the VR port's join codes, game list, host options
|
||||
and keyboard pointer are built into its screens) */
|
||||
if (halo_vr_active())
|
||||
return;
|
||||
menus = halo_menus_load();
|
||||
if (!menus)
|
||||
@@ -1681,7 +1641,7 @@ void menu_tags_loaded(
|
||||
if (widget->strings)
|
||||
{
|
||||
char const *pieces[MAXIMUM_STRINGS];
|
||||
long count = spinner_split(widget, FALSE, pieces);
|
||||
long count = split(widget->strings, pieces);
|
||||
|
||||
instance_set(instances, UNICODE_STRING_LIST_TAG, build.spinner_tags[index], widget->name, " strings",
|
||||
string_list_build(pieces, count));
|
||||
|
||||
@@ -1,901 +0,0 @@
|
||||
/*
|
||||
NETWORK_ACTORS.C
|
||||
|
||||
AI sync (port/linux/NETCODE.md).
|
||||
|
||||
Only the host runs the AI (game.c skips ai_update on clients). An actor
|
||||
drives its unit the way a player does, through unit_control. The clients
|
||||
have the units (network_objects.c); this file makes them move like the
|
||||
host's.
|
||||
|
||||
Each tick the host records the control every actor gave its unit (a hook
|
||||
in unit_control, and the bursts it holds the trigger for) and sends it to
|
||||
each client with the unit's state: position, seat, shields and health,
|
||||
camouflage, and whether it flees in a panic. Units near a client's players
|
||||
are sent every tick, others less often, but a unit starting a one-tick
|
||||
action (a jump, a grenade) goes to everyone that tick. Each tick a client
|
||||
feeds the latest control into unit_control, the same path that drives a
|
||||
remote player's unit, so the unit walks, aims, fires and throws as it does
|
||||
on the host. The state corrects any drift.
|
||||
|
||||
What the AI starts once is numbered, so a client plays each once however
|
||||
many times it receives it: animation impulses (dives, vaults), melee
|
||||
attacks and leaps (which the AI starts directly, not through the control),
|
||||
speech, and custom animations (outside co-op, whose events carry them).
|
||||
Pain and death sounds aren't sent: a client plays those itself as it
|
||||
replays the host's damage.
|
||||
|
||||
A client creates no actors of its own (actors.c): the host's are the only
|
||||
ones. Deaths need nothing here: network_damage.c kills a client's copy of
|
||||
an AI unit the same way it kills a player's.
|
||||
*/
|
||||
|
||||
/* ---------- headers */
|
||||
|
||||
#include "cseries.h"
|
||||
#include "game/game.h"
|
||||
#include "networking/network_game_globals.h"
|
||||
#include "objects/objects.h"
|
||||
#include "objects/damage.h"
|
||||
#include "sound/game_sound.h"
|
||||
#include "sound/sound_definitions.h"
|
||||
#include "units/units.h"
|
||||
#include "units/unit_control_data.h"
|
||||
#include "network_coop.h"
|
||||
#include "network_distributed.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
enum
|
||||
{
|
||||
/* AI units tracked at once: every actor (halo_port_capacity.h's
|
||||
HALO_PORT_MAXIMUM_ACTORS) and the units the cutscenes' recorded
|
||||
animations drive */
|
||||
MAXIMUM_NETWORK_ACTORS = HALO_PORT_MAXIMUM_ACTORS + 32,
|
||||
MAXIMUM_ENTRIES_PER_MESSAGE = 64,
|
||||
/* A client keeps applying a control for this long after the host last
|
||||
sent one. After that the unit is left alone: the actor let go of it, or
|
||||
the host went quiet. */
|
||||
CONTROL_HELD_TICKS = 2 * TICKS_PER_SECOND,
|
||||
/* an impulse is repeated in this many entries, in case one is lost */
|
||||
IMPULSE_REPEAT_TICKS = 3,
|
||||
/* the number of unit_start_animation_impulse impulses (private to units.c) */
|
||||
NUMBER_OF_UNIT_ANIMATION_IMPULSES = 14,
|
||||
NO_IMPULSE = 0xFF,
|
||||
NO_TEAM = 0xFF,
|
||||
|
||||
/* An entry's impulse is one of the animation impulses or, past them, an
|
||||
action the AI starts directly: a melee attack (unit_melee_attack_begin)
|
||||
or a leap (unit_leap_begin). */
|
||||
_actor_action_melee = NUMBER_OF_UNIT_ANIMATION_IMPULSES,
|
||||
_actor_action_leap,
|
||||
NUMBER_OF_ACTOR_ACTIONS,
|
||||
|
||||
/* control flags a unit acts on in one tick: a unit that sets one goes to
|
||||
every client that tick, however far away, so the action isn't missed */
|
||||
ONE_SHOT_CONTROL_FLAGS = FLAG(_unit_control_jump_bit) | FLAG(_unit_control_action_bit) |
|
||||
FLAG(_unit_control_weapon_reload_bit) | FLAG(_unit_control_throw_grenade_bit) |
|
||||
FLAG(_unit_control_swap_weapons_bit),
|
||||
};
|
||||
|
||||
/* struct distributed_actor_state flags */
|
||||
enum
|
||||
{
|
||||
_distributed_actor_rides_bit = 0,
|
||||
_distributed_actor_shield_depleted_bit,
|
||||
_distributed_actor_shield_charging_bit,
|
||||
_distributed_actor_shield_over_charging_bit,
|
||||
_distributed_actor_camouflaged_bit,
|
||||
_distributed_actor_super_camouflaged_bit,
|
||||
/* the impulse turns the unit to impulse_alignment */
|
||||
_distributed_actor_impulse_aligned_bit,
|
||||
/* fleeing in a panic, toward run_blindly_angle (unit_start_running_blindly) */
|
||||
_distributed_actor_running_blindly_bit,
|
||||
};
|
||||
|
||||
/* facing, aiming and looking vectors are sent as yaw and pitch */
|
||||
enum
|
||||
{
|
||||
_angle_yaw,
|
||||
_angle_pitch,
|
||||
NUMBER_OF_ANGLES
|
||||
};
|
||||
|
||||
/* ---------- structures */
|
||||
|
||||
/* an AI unit's control and state at the end of the host's tick (like
|
||||
struct distributed_unit_state for players) */
|
||||
struct distributed_actor_state
|
||||
{
|
||||
long unit_index;
|
||||
long vehicle_index;
|
||||
short seat_index;
|
||||
byte flags;
|
||||
/* the impulse's sequence number; impulse is NO_IMPULSE when there is none */
|
||||
byte impulse_number;
|
||||
byte impulse;
|
||||
byte animation_state;
|
||||
byte aiming_speed;
|
||||
byte primary_trigger;
|
||||
word control_flags;
|
||||
signed char throttle[3];
|
||||
byte active_camouflage;
|
||||
/* the speech's sequence number; 0 when there is none */
|
||||
byte speech_number;
|
||||
/* the unit's team (NO_TEAM for none): the AI sets it after the unit is
|
||||
made, so a client's copy would keep the team it was made with */
|
||||
byte team;
|
||||
short facing[NUMBER_OF_ANGLES];
|
||||
short aiming[NUMBER_OF_ANGLES];
|
||||
short looking[NUMBER_OF_ANGLES];
|
||||
short impulse_alignment;
|
||||
short run_blindly_angle;
|
||||
word body_vitality;
|
||||
word shield_vitality;
|
||||
/* the sound the unit started saying (a sound tag) */
|
||||
long speech_sound;
|
||||
/* the custom animation it started (unit_start_user_animation), and its
|
||||
sequence number; the graph is NONE when there is none */
|
||||
long user_animation_graph;
|
||||
short user_animation;
|
||||
byte user_animation_number;
|
||||
byte user_animation_interpolate;
|
||||
real_point3d position;
|
||||
struct distributed_vector velocity;
|
||||
struct distributed_vector forward;
|
||||
struct distributed_vector up;
|
||||
};
|
||||
|
||||
/* the damage an AI unit is taking: its shield's flare and its body's wounds
|
||||
are drawn by it */
|
||||
struct distributed_actor_damage
|
||||
{
|
||||
long unit_index;
|
||||
word current_shield_damage;
|
||||
word recent_shield_damage;
|
||||
word current_body_damage;
|
||||
word recent_body_damage;
|
||||
};
|
||||
|
||||
struct distributed_actor_damage_message
|
||||
{
|
||||
struct distributed_message_header header;
|
||||
struct distributed_actor_damage units[MAXIMUM_ENTRIES_PER_MESSAGE];
|
||||
};
|
||||
|
||||
struct distributed_actor_state_message
|
||||
{
|
||||
struct distributed_message_header header;
|
||||
struct distributed_actor_state states[MAXIMUM_ENTRIES_PER_MESSAGE];
|
||||
};
|
||||
|
||||
/* host: what an actor gave its unit this tick */
|
||||
struct host_actor
|
||||
{
|
||||
long unit_index;
|
||||
/* got a control since the last send (noted during game_tick, sent after it) */
|
||||
boolean noted;
|
||||
/* its damage was sent last tick (and once more when it ends) */
|
||||
boolean damaged;
|
||||
struct unit_control_data control;
|
||||
/* the control flags of the last tick's entry */
|
||||
word sent_control_flags;
|
||||
/* the latest impulse, how many more entries carry it, and its number */
|
||||
short impulse;
|
||||
short impulse_sends;
|
||||
byte impulse_number;
|
||||
boolean impulse_aligned;
|
||||
real_vector2d impulse_alignment;
|
||||
/* the latest speech, how many more entries carry it, and its number */
|
||||
long speech_sound;
|
||||
short speech_sends;
|
||||
byte speech_number;
|
||||
/* the latest custom animation, the same way */
|
||||
long user_animation_graph;
|
||||
short user_animation;
|
||||
boolean user_animation_interpolate;
|
||||
short user_animation_sends;
|
||||
byte user_animation_number;
|
||||
};
|
||||
|
||||
/* client: the latest the host sent about an AI unit */
|
||||
struct client_actor
|
||||
{
|
||||
long unit_index;
|
||||
long received_time;
|
||||
struct unit_control_data control;
|
||||
/* the impulse to play, and the number of the last one played */
|
||||
short impulse;
|
||||
byte impulse_number;
|
||||
byte played_impulse_number;
|
||||
boolean impulse_aligned;
|
||||
real_vector2d impulse_alignment;
|
||||
/* the speech to play (NONE when there is none), and the number of the last played */
|
||||
long speech_sound;
|
||||
byte speech_number;
|
||||
byte played_speech_number;
|
||||
/* the custom animation to play (graph NONE when there is none), and the
|
||||
number of the last played */
|
||||
long user_animation_graph;
|
||||
short user_animation;
|
||||
boolean user_animation_interpolate;
|
||||
byte user_animation_number;
|
||||
byte played_user_animation_number;
|
||||
boolean driven;
|
||||
};
|
||||
|
||||
/* ---------- globals */
|
||||
|
||||
static struct host_actor host_actors[MAXIMUM_NETWORK_ACTORS];
|
||||
static short host_actor_count;
|
||||
/* host: the numbers of every unit's impulses, speech and animations, from
|
||||
one counter, so a unit that drops out of the table and comes back can't
|
||||
repeat the number a client last played */
|
||||
static byte host_event_number;
|
||||
|
||||
static struct client_actor client_actors[MAXIMUM_NETWORK_ACTORS];
|
||||
static short client_actor_count;
|
||||
|
||||
/* ---------- private code */
|
||||
|
||||
static void angles_pack(
|
||||
real_vector3d const *vector,
|
||||
short *angles)
|
||||
{
|
||||
angles[_angle_yaw] = distributed_angle_pack((real)atan2(vector->j, vector->i));
|
||||
angles[_angle_pitch] = distributed_angle_pack((real)asin(PIN(vector->k, -1.0f, 1.0f)));
|
||||
}
|
||||
|
||||
static void angles_unpack(
|
||||
short const *angles,
|
||||
real_vector3d *vector)
|
||||
{
|
||||
real yaw = distributed_angle_unpack(angles[_angle_yaw], FALSE);
|
||||
real pitch = distributed_angle_unpack(angles[_angle_pitch], TRUE);
|
||||
|
||||
vector->i = (real)(cos(pitch) * cos(yaw));
|
||||
vector->j = (real)(cos(pitch) * sin(yaw));
|
||||
vector->k = (real)sin(pitch);
|
||||
}
|
||||
|
||||
/* host: the next event's number (never 0, which a client's new entry has
|
||||
as played) */
|
||||
static byte next_event_number(
|
||||
void)
|
||||
{
|
||||
if (++host_event_number == 0)
|
||||
host_event_number = 1;
|
||||
return host_event_number;
|
||||
}
|
||||
|
||||
/* the unit's entry, adding one if needed; NULL if the table is full */
|
||||
static struct host_actor *host_actor_for(
|
||||
long unit_index)
|
||||
{
|
||||
short index;
|
||||
|
||||
for (index = 0; index < host_actor_count; index++)
|
||||
{
|
||||
if (host_actors[index].unit_index == unit_index)
|
||||
return &host_actors[index];
|
||||
}
|
||||
if (host_actor_count >= MAXIMUM_NETWORK_ACTORS)
|
||||
return NULL;
|
||||
csmemset(&host_actors[host_actor_count], 0, sizeof(host_actors[0]));
|
||||
host_actors[host_actor_count].unit_index = unit_index;
|
||||
host_actors[host_actor_count].impulse = NONE;
|
||||
host_actors[host_actor_count].speech_sound = NONE;
|
||||
host_actors[host_actor_count].user_animation_graph = NONE;
|
||||
return &host_actors[host_actor_count++];
|
||||
}
|
||||
|
||||
static struct client_actor *client_actor_for(
|
||||
long unit_index)
|
||||
{
|
||||
short index;
|
||||
|
||||
for (index = 0; index < client_actor_count; index++)
|
||||
{
|
||||
if (client_actors[index].unit_index == unit_index)
|
||||
return &client_actors[index];
|
||||
}
|
||||
if (client_actor_count >= MAXIMUM_NETWORK_ACTORS)
|
||||
return NULL;
|
||||
csmemset(&client_actors[client_actor_count], 0, sizeof(client_actors[0]));
|
||||
client_actors[client_actor_count].unit_index = unit_index;
|
||||
client_actors[client_actor_count].impulse = NONE;
|
||||
client_actors[client_actor_count].speech_sound = NONE;
|
||||
client_actors[client_actor_count].user_animation_graph = NONE;
|
||||
return &client_actors[client_actor_count++];
|
||||
}
|
||||
|
||||
/* whether the unit exists, is alive, and isn't a player's */
|
||||
static boolean actor_unit_valid(
|
||||
long unit_index)
|
||||
{
|
||||
struct unit_datum *unit;
|
||||
|
||||
if (!object_try_and_get_and_verify_type(unit_index, _object_mask_unit))
|
||||
return FALSE;
|
||||
unit = unit_get(unit_index);
|
||||
return !TEST_FLAG(unit->object.damage_flags, _object_dead_bit) && unit->unit.player_index == NONE;
|
||||
}
|
||||
|
||||
/* host: builds the entry to send for an AI unit */
|
||||
static void actor_state_from_unit(
|
||||
struct host_actor const *actor,
|
||||
struct distributed_actor_state *state)
|
||||
{
|
||||
struct unit_datum *unit = unit_get(actor->unit_index);
|
||||
struct damage_network_state damage;
|
||||
real camouflage = PIN(unit->unit.active_camouflage, 0.0f, 1.0f);
|
||||
|
||||
csmemset(state, 0, sizeof(*state));
|
||||
state->unit_index = actor->unit_index;
|
||||
state->vehicle_index = NONE;
|
||||
state->seat_index = NONE;
|
||||
if (unit->object.parent_object_index != NONE && unit->unit.parent_seat_index != NONE)
|
||||
{
|
||||
SET_FLAG(state->flags, _distributed_actor_rides_bit, TRUE);
|
||||
state->vehicle_index = unit->object.parent_object_index;
|
||||
state->seat_index = unit->unit.parent_seat_index;
|
||||
}
|
||||
state->animation_state = (byte)actor->control.animation_state;
|
||||
state->aiming_speed = (byte)actor->control.aiming_speed;
|
||||
state->primary_trigger = (byte)(long)floor(PIN(actor->control.primary_trigger, 0.0f, 1.0f) * 255.0f + 0.5f);
|
||||
state->control_flags = actor->control.control_flags;
|
||||
/* for a burst the AI holds the trigger for (unit_persistent_control),
|
||||
send the flags and trigger the unit's update produced this tick */
|
||||
if (unit->unit.persistent_control_timer > 0)
|
||||
{
|
||||
state->control_flags = (word)(unit->unit.control_flags & (FLAG(NUMBER_OF_UNIT_CONTROL_FLAGS) - 1));
|
||||
state->primary_trigger = (byte)(long)floor(PIN(unit->unit.primary_trigger, 0.0f, 1.0f) * 255.0f + 0.5f);
|
||||
}
|
||||
state->throttle[0] = (signed char)(long)floor(PIN(actor->control.throttle.i, -1.0f, 1.0f) * 127.0f + 0.5f);
|
||||
state->throttle[1] = (signed char)(long)floor(PIN(actor->control.throttle.j, -1.0f, 1.0f) * 127.0f + 0.5f);
|
||||
state->throttle[2] = (signed char)(long)floor(PIN(actor->control.throttle.k, -1.0f, 1.0f) * 127.0f + 0.5f);
|
||||
angles_pack(&actor->control.facing_vector, state->facing);
|
||||
angles_pack(&actor->control.aiming_vector, state->aiming);
|
||||
angles_pack(&actor->control.looking_vector, state->looking);
|
||||
state->impulse = NO_IMPULSE;
|
||||
if (actor->impulse != NONE && actor->impulse_sends > 0)
|
||||
{
|
||||
state->impulse = (byte)actor->impulse;
|
||||
state->impulse_number = actor->impulse_number;
|
||||
SET_FLAG(state->flags, _distributed_actor_impulse_aligned_bit, actor->impulse_aligned);
|
||||
if (actor->impulse_aligned)
|
||||
{
|
||||
state->impulse_alignment = distributed_angle_pack(
|
||||
(real)atan2(actor->impulse_alignment.j, actor->impulse_alignment.i));
|
||||
}
|
||||
}
|
||||
state->speech_sound = NONE;
|
||||
if (actor->speech_sound != NONE && actor->speech_sends > 0)
|
||||
{
|
||||
state->speech_sound = actor->speech_sound;
|
||||
state->speech_number = actor->speech_number;
|
||||
}
|
||||
state->user_animation_graph = NONE;
|
||||
if (actor->user_animation_graph != NONE && actor->user_animation_sends > 0)
|
||||
{
|
||||
state->user_animation_graph = actor->user_animation_graph;
|
||||
state->user_animation = actor->user_animation;
|
||||
state->user_animation_interpolate = (byte)actor->user_animation_interpolate;
|
||||
state->user_animation_number = actor->user_animation_number;
|
||||
}
|
||||
if (TEST_FLAG(unit->unit.flags, _unit_running_blindly_bit))
|
||||
{
|
||||
SET_FLAG(state->flags, _distributed_actor_running_blindly_bit, TRUE);
|
||||
state->run_blindly_angle = distributed_angle_pack(unit->unit.run_blindly_angle);
|
||||
}
|
||||
damage_get_network_state(actor->unit_index, &damage);
|
||||
SET_FLAG(state->flags, _distributed_actor_shield_depleted_bit, damage.shield_depleted);
|
||||
SET_FLAG(state->flags, _distributed_actor_shield_charging_bit, damage.shield_charging);
|
||||
SET_FLAG(state->flags, _distributed_actor_shield_over_charging_bit, damage.shield_over_charging);
|
||||
state->body_vitality = distributed_vitality_pack(damage.body_vitality);
|
||||
state->shield_vitality = distributed_vitality_pack(damage.shield_vitality);
|
||||
SET_FLAG(state->flags, _distributed_actor_camouflaged_bit,
|
||||
TEST_FLAG(unit->unit.flags, _unit_active_camouflaged_bit));
|
||||
SET_FLAG(state->flags, _distributed_actor_super_camouflaged_bit,
|
||||
TEST_FLAG(unit->unit.flags, _unit_super_camouflaged_bit));
|
||||
state->active_camouflage = (byte)(long)floor(camouflage * 255.0f + 0.5f);
|
||||
state->team = unit->object.owner_team_index >= 0 && unit->object.owner_team_index < NO_TEAM ?
|
||||
(byte)unit->object.owner_team_index : NO_TEAM;
|
||||
state->position = unit->object.position;
|
||||
distributed_vector_pack(&unit->object.translational_velocity, DISTRIBUTED_VELOCITY_SCALE, &state->velocity);
|
||||
distributed_vector_pack(&unit->object.forward, DISTRIBUTED_UNIT_SCALE, &state->forward);
|
||||
distributed_vector_pack(&unit->object.up, DISTRIBUTED_UNIT_SCALE, &state->up);
|
||||
}
|
||||
|
||||
/* Client: stores the entry's control and applies its state to the unit.
|
||||
Returns FALSE for an entry with invalid values. */
|
||||
static boolean actor_state_apply(
|
||||
struct distributed_actor_state const *state,
|
||||
long now)
|
||||
{
|
||||
struct client_actor *actor;
|
||||
struct unit_datum *unit;
|
||||
struct damage_network_state damage;
|
||||
real_vector3d velocity;
|
||||
real_vector3d forward;
|
||||
real_vector3d up;
|
||||
|
||||
if (!distributed_object_index_valid(state->unit_index) || !network_objects_client_has(state->unit_index))
|
||||
return FALSE;
|
||||
/* (a body the host's unit got back up from: a Flood combat form that
|
||||
feigned death, which it does only once its body is at rest. Left dead,
|
||||
the ragdoll was dragged about by the host's positions. The host sends
|
||||
only living units.) */
|
||||
if (object_try_and_get_and_verify_type(state->unit_index, _object_mask_unit) &&
|
||||
TEST_FLAG(unit_get(state->unit_index)->object.damage_flags, _object_dead_bit) &&
|
||||
TEST_FLAG(unit_get(state->unit_index)->object.flags, _object_at_rest_bit) &&
|
||||
unit_get(state->unit_index)->unit.player_index == NONE && state->body_vitality > 0)
|
||||
{
|
||||
unit_port_resurrect(state->unit_index);
|
||||
}
|
||||
if (!actor_unit_valid(state->unit_index) ||
|
||||
state->animation_state >= NUMBER_OF_UNIT_ANIMATION_STATES ||
|
||||
state->aiming_speed >= NUMBER_OF_UNIT_AIMING_SPEEDS ||
|
||||
!VALID_FLAGS(state->control_flags, NUMBER_OF_UNIT_CONTROL_FLAGS) ||
|
||||
(state->impulse != NO_IMPULSE && state->impulse >= NUMBER_OF_ACTOR_ACTIONS) ||
|
||||
(state->speech_sound != NONE && !distributed_tag_of_group(state->speech_sound, SOUND_DEFINITION_TAG)) ||
|
||||
(state->user_animation_graph != NONE &&
|
||||
!distributed_graph_animation(state->user_animation_graph, state->user_animation)) ||
|
||||
!distributed_point_valid(&state->position, UNIT_WORLD_BOUND))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
distributed_vector_unpack(&state->velocity, DISTRIBUTED_VELOCITY_SCALE, &velocity);
|
||||
distributed_vector_unpack(&state->forward, DISTRIBUTED_UNIT_SCALE, &forward);
|
||||
distributed_vector_unpack(&state->up, DISTRIBUTED_UNIT_SCALE, &up);
|
||||
if (!distributed_axes_make_valid(&forward, &up))
|
||||
return FALSE;
|
||||
actor = client_actor_for(state->unit_index);
|
||||
if (!actor)
|
||||
return TRUE;
|
||||
|
||||
/* the control, applied every tick until the next entry */
|
||||
actor->received_time = now;
|
||||
actor->control.animation_state = (char)state->animation_state;
|
||||
actor->control.aiming_speed = (char)state->aiming_speed;
|
||||
actor->control.control_flags = state->control_flags;
|
||||
actor->control.weapon_index = NONE;
|
||||
actor->control.grenade_index = NONE;
|
||||
actor->control.zoom_level = NONE;
|
||||
actor->control.throttle.i = (real)state->throttle[0] / 127.0f;
|
||||
actor->control.throttle.j = (real)state->throttle[1] / 127.0f;
|
||||
actor->control.throttle.k = (real)state->throttle[2] / 127.0f;
|
||||
actor->control.primary_trigger = (real)state->primary_trigger / 255.0f;
|
||||
angles_unpack(state->facing, &actor->control.facing_vector);
|
||||
angles_unpack(state->aiming, &actor->control.aiming_vector);
|
||||
angles_unpack(state->looking, &actor->control.looking_vector);
|
||||
if (state->impulse != NO_IMPULSE && state->impulse_number != actor->played_impulse_number)
|
||||
{
|
||||
real yaw = distributed_angle_unpack(state->impulse_alignment, FALSE);
|
||||
|
||||
actor->impulse = state->impulse;
|
||||
actor->impulse_number = state->impulse_number;
|
||||
actor->impulse_aligned = TEST_FLAG(state->flags, _distributed_actor_impulse_aligned_bit);
|
||||
actor->impulse_alignment.i = (real)cos(yaw);
|
||||
actor->impulse_alignment.j = (real)sin(yaw);
|
||||
}
|
||||
|
||||
if (state->speech_sound != NONE && state->speech_number != actor->played_speech_number)
|
||||
{
|
||||
actor->speech_sound = state->speech_sound;
|
||||
actor->speech_number = state->speech_number;
|
||||
}
|
||||
if (state->user_animation_graph != NONE && state->user_animation_number != actor->played_user_animation_number)
|
||||
{
|
||||
actor->user_animation_graph = state->user_animation_graph;
|
||||
actor->user_animation = state->user_animation;
|
||||
actor->user_animation_interpolate = state->user_animation_interpolate != 0;
|
||||
actor->user_animation_number = state->user_animation_number;
|
||||
}
|
||||
|
||||
/* the state: seat, shields and health, camouflage, running blindly, and
|
||||
position (a unit in a vehicle goes where the vehicle goes) */
|
||||
unit = unit_get(state->unit_index);
|
||||
if (TEST_FLAG(state->flags, _distributed_actor_rides_bit))
|
||||
{
|
||||
if (unit->object.parent_object_index != state->vehicle_index ||
|
||||
unit->unit.parent_seat_index != state->seat_index)
|
||||
{
|
||||
network_objects_set_seat(state->unit_index, state->vehicle_index, state->seat_index);
|
||||
}
|
||||
}
|
||||
else if (unit->object.parent_object_index != NONE)
|
||||
{
|
||||
network_objects_set_seat(state->unit_index, NONE, NONE);
|
||||
}
|
||||
damage_get_network_state(state->unit_index, &damage);
|
||||
damage.shield_depleted = TEST_FLAG(state->flags, _distributed_actor_shield_depleted_bit);
|
||||
damage.shield_charging = TEST_FLAG(state->flags, _distributed_actor_shield_charging_bit);
|
||||
damage.shield_over_charging = TEST_FLAG(state->flags, _distributed_actor_shield_over_charging_bit);
|
||||
damage.body_vitality = distributed_vitality_unpack(state->body_vitality);
|
||||
damage.shield_vitality = distributed_vitality_unpack(state->shield_vitality);
|
||||
damage_set_network_state(state->unit_index, &damage);
|
||||
SET_FLAG(unit->unit.flags, _unit_active_camouflaged_bit,
|
||||
TEST_FLAG(state->flags, _distributed_actor_camouflaged_bit));
|
||||
SET_FLAG(unit->unit.flags, _unit_super_camouflaged_bit,
|
||||
TEST_FLAG(state->flags, _distributed_actor_super_camouflaged_bit));
|
||||
unit->unit.active_camouflage = (real)state->active_camouflage / 255.0f;
|
||||
if (state->team != NO_TEAM)
|
||||
unit->object.owner_team_index = state->team;
|
||||
SET_FLAG(unit->unit.flags, _unit_running_blindly_bit,
|
||||
TEST_FLAG(state->flags, _distributed_actor_running_blindly_bit));
|
||||
if (TEST_FLAG(state->flags, _distributed_actor_running_blindly_bit))
|
||||
unit->unit.run_blindly_angle = distributed_angle_unpack(state->run_blindly_angle, FALSE);
|
||||
/* vehicles are placed only by the host's object states (network_objects.c) */
|
||||
if (unit->object.parent_object_index == NONE && unit->object.type != _object_type_vehicle)
|
||||
{
|
||||
real dx = state->position.x - unit->object.position.x;
|
||||
real dy = state->position.y - unit->object.position.y;
|
||||
real dz = state->position.z - unit->object.position.z;
|
||||
|
||||
if (dx * dx + dy * dy + dz * dz > REMOTE_CORRECTION_TOLERANCE * REMOTE_CORRECTION_TOLERANCE &&
|
||||
network_objects_reconcile(state->unit_index, &state->position, &forward, &up, &velocity, NULL,
|
||||
REMOTE_BLEND_DISTANCE))
|
||||
{
|
||||
distributed_count_correction();
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Client: plays a sound at the unit's head, as unit_dialogue_update starts
|
||||
a unit's speech. */
|
||||
static void speech_play(
|
||||
long unit_index,
|
||||
long sound_definition_index)
|
||||
{
|
||||
struct object_marker marker;
|
||||
real_point3d position = *global_origin3d;
|
||||
real_vector3d forward = *global_forward3d;
|
||||
short node_index = 0;
|
||||
|
||||
if (object_get_marker_by_name(unit_index, "head", &marker, 1))
|
||||
{
|
||||
node_index = marker.node_index;
|
||||
position = marker.node_matrix.position;
|
||||
forward = marker.node_matrix.forward;
|
||||
}
|
||||
object_impulse_sound_new(unit_index, sound_definition_index, node_index, &position, &forward, 1.0f);
|
||||
}
|
||||
|
||||
/* ---------- public code */
|
||||
|
||||
void network_actors_new_game(
|
||||
void)
|
||||
{
|
||||
host_actor_count = 0;
|
||||
client_actor_count = 0;
|
||||
}
|
||||
|
||||
/* host: unit_control calls this when an actor drives its unit */
|
||||
void network_actors_note_control(
|
||||
long unit_index,
|
||||
struct unit_control_data const *control_data)
|
||||
{
|
||||
struct host_actor *actor;
|
||||
|
||||
if (game_connection() != _game_connection_network_server)
|
||||
return;
|
||||
actor = host_actor_for(unit_index);
|
||||
if (!actor)
|
||||
return;
|
||||
actor->noted = TRUE;
|
||||
actor->control = *control_data;
|
||||
}
|
||||
|
||||
/* host: an impulse or action for an AI unit, sent in the next few entries */
|
||||
static void note_action(
|
||||
long unit_index,
|
||||
short action,
|
||||
real_vector2d const *alignment_vector)
|
||||
{
|
||||
struct host_actor *actor;
|
||||
|
||||
if (game_connection() != _game_connection_network_server)
|
||||
return;
|
||||
actor = host_actor_for(unit_index);
|
||||
if (!actor)
|
||||
return;
|
||||
actor->impulse = action;
|
||||
actor->impulse_sends = IMPULSE_REPEAT_TICKS;
|
||||
actor->impulse_number = next_event_number();
|
||||
actor->impulse_aligned = alignment_vector != NULL;
|
||||
if (alignment_vector)
|
||||
actor->impulse_alignment = *alignment_vector;
|
||||
}
|
||||
|
||||
/* host: unit_start_animation_impulse calls this for an AI unit */
|
||||
void network_actors_note_impulse(
|
||||
long unit_index,
|
||||
short animation_impulse,
|
||||
real_vector2d const *alignment_vector)
|
||||
{
|
||||
note_action(unit_index, animation_impulse, alignment_vector);
|
||||
}
|
||||
|
||||
/* host: unit_melee_attack_begin calls this when an AI unit starts a melee attack */
|
||||
void network_actors_note_melee(
|
||||
long unit_index,
|
||||
real_vector2d const *alignment_vector)
|
||||
{
|
||||
note_action(unit_index, _actor_action_melee, alignment_vector);
|
||||
}
|
||||
|
||||
/* host: unit_leap_begin calls this when an AI unit leaps */
|
||||
void network_actors_note_leap(
|
||||
long unit_index,
|
||||
real_vector2d const *alignment_vector)
|
||||
{
|
||||
note_action(unit_index, _actor_action_leap, alignment_vector);
|
||||
}
|
||||
|
||||
/* host: unit_dialogue_update calls this when an AI unit starts saying
|
||||
something (not a pain or death sound, which clients play themselves) */
|
||||
void network_actors_note_speech(
|
||||
long unit_index,
|
||||
long sound_definition_index)
|
||||
{
|
||||
struct host_actor *actor;
|
||||
|
||||
if (game_connection() != _game_connection_network_server)
|
||||
return;
|
||||
actor = host_actor_for(unit_index);
|
||||
if (!actor)
|
||||
return;
|
||||
actor->speech_sound = sound_definition_index;
|
||||
actor->speech_sends = IMPULSE_REPEAT_TICKS;
|
||||
actor->speech_number = next_event_number();
|
||||
}
|
||||
|
||||
/* host: unit_start_user_animation calls this when an AI unit starts a
|
||||
custom animation (co-op's events carry them in co-op) */
|
||||
void network_actors_note_user_animation(
|
||||
long unit_index,
|
||||
long animation_graph_index,
|
||||
short animation_index,
|
||||
boolean interpolate)
|
||||
{
|
||||
struct host_actor *actor;
|
||||
|
||||
if (game_connection() != _game_connection_network_server || network_coop_active())
|
||||
return;
|
||||
actor = host_actor_for(unit_index);
|
||||
if (!actor)
|
||||
return;
|
||||
actor->user_animation_graph = animation_graph_index;
|
||||
actor->user_animation = animation_index;
|
||||
actor->user_animation_interpolate = interpolate;
|
||||
actor->user_animation_sends = IMPULSE_REPEAT_TICKS;
|
||||
actor->user_animation_number = next_event_number();
|
||||
}
|
||||
|
||||
/* host: the damage of the AI units taking some, to every client (and once
|
||||
more when it ends, so a flare goes) */
|
||||
static void host_send_damage(
|
||||
void)
|
||||
{
|
||||
struct distributed_actor_damage_message message;
|
||||
short count = 0;
|
||||
short index;
|
||||
|
||||
for (index = 0; index < host_actor_count; index++)
|
||||
{
|
||||
struct host_actor *actor = &host_actors[index];
|
||||
struct damage_network_state damage;
|
||||
boolean damaged;
|
||||
|
||||
if (!actor_unit_valid(actor->unit_index))
|
||||
continue;
|
||||
damage_get_network_state(actor->unit_index, &damage);
|
||||
damaged = damage.current_shield_damage > 0.0f || damage.recent_shield_damage > 0.0f ||
|
||||
damage.current_body_damage > 0.0f || damage.recent_body_damage > 0.0f;
|
||||
if (!damaged && !actor->damaged)
|
||||
continue;
|
||||
actor->damaged = damaged;
|
||||
message.units[count].unit_index = actor->unit_index;
|
||||
message.units[count].current_shield_damage = distributed_vitality_pack(damage.current_shield_damage);
|
||||
message.units[count].recent_shield_damage = distributed_vitality_pack(damage.recent_shield_damage);
|
||||
message.units[count].current_body_damage = distributed_vitality_pack(damage.current_body_damage);
|
||||
message.units[count].recent_body_damage = distributed_vitality_pack(damage.recent_body_damage);
|
||||
if (++count == MAXIMUM_ENTRIES_PER_MESSAGE)
|
||||
{
|
||||
distributed_send(&message, _distributed_message_actor_damage, count,
|
||||
(word)(sizeof(message.header) + count * sizeof(message.units[0])), _distributed_to_clients);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
if (count)
|
||||
{
|
||||
distributed_send(&message, _distributed_message_actor_damage, count,
|
||||
(word)(sizeof(message.header) + count * sizeof(message.units[0])), _distributed_to_clients);
|
||||
}
|
||||
}
|
||||
|
||||
/* Host, after each tick: sends each client the AI units driven this tick.
|
||||
Units near the client's players go every tick, others less often. A unit
|
||||
with a fresh impulse, action, speech or animation goes to everyone in each
|
||||
of the next IMPULSE_REPEAT_TICKS ticks, and one starting a one-tick action
|
||||
goes that tick. A unit no actor drove this tick is dropped from the table,
|
||||
unless it is fleeing: a unit running blindly moves by itself, without its
|
||||
actor's control, and dropped it stood running in place on the clients. */
|
||||
void network_actors_host_tick(
|
||||
void)
|
||||
{
|
||||
static struct distributed_actor_state states[MAXIMUM_NETWORK_ACTORS];
|
||||
/* sent to every client this tick, whatever its period */
|
||||
static boolean urgent[MAXIMUM_NETWORK_ACTORS];
|
||||
struct distributed_actor_state_message message;
|
||||
long machine_indices[HALO_PORT_MAXIMUM_NETWORK_MACHINES];
|
||||
short machine_count = distributed_client_machines(machine_indices, HALO_PORT_MAXIMUM_NETWORK_MACHINES);
|
||||
long now = game_time_get();
|
||||
short state_count = 0;
|
||||
short index;
|
||||
short machine_number;
|
||||
|
||||
for (index = 0; index < host_actor_count; index++)
|
||||
{
|
||||
struct host_actor *actor = &host_actors[index];
|
||||
|
||||
if (!actor_unit_valid(actor->unit_index) ||
|
||||
(!actor->noted && !TEST_FLAG(unit_get(actor->unit_index)->unit.flags, _unit_running_blindly_bit)))
|
||||
{
|
||||
host_actors[index--] = host_actors[--host_actor_count];
|
||||
continue;
|
||||
}
|
||||
actor_state_from_unit(actor, &states[state_count]);
|
||||
urgent[state_count] = states[state_count].impulse != NO_IMPULSE || states[state_count].speech_sound != NONE ||
|
||||
states[state_count].user_animation_graph != NONE ||
|
||||
(states[state_count].control_flags & ~actor->sent_control_flags & ONE_SHOT_CONTROL_FLAGS) != 0;
|
||||
actor->sent_control_flags = states[state_count].control_flags;
|
||||
state_count++;
|
||||
actor->noted = FALSE;
|
||||
if (actor->impulse_sends > 0)
|
||||
actor->impulse_sends--;
|
||||
if (actor->speech_sends > 0)
|
||||
actor->speech_sends--;
|
||||
if (actor->user_animation_sends > 0)
|
||||
actor->user_animation_sends--;
|
||||
}
|
||||
host_send_damage();
|
||||
for (machine_number = 0; machine_number < machine_count; machine_number++)
|
||||
{
|
||||
long machine_index = machine_indices[machine_number];
|
||||
short count = 0;
|
||||
|
||||
for (index = 0; index < state_count; index++)
|
||||
{
|
||||
struct distributed_actor_state const *state = &states[index];
|
||||
short period = network_objects_send_period(machine_index, &state->position);
|
||||
|
||||
if (!urgent[index] && (now + DATUM_INDEX_TO_ABSOLUTE_INDEX(state->unit_index)) % period != 0)
|
||||
continue;
|
||||
message.states[count++] = *state;
|
||||
if (count == MAXIMUM_ENTRIES_PER_MESSAGE || count == DATAGRAM_ENTRIES(struct distributed_actor_state))
|
||||
{
|
||||
distributed_send_to_machine(machine_index, &message, _distributed_message_actor_states, count,
|
||||
(word)(sizeof(message.header) + count * sizeof(struct distributed_actor_state)));
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
if (count)
|
||||
{
|
||||
distributed_send_to_machine(machine_index, &message, _distributed_message_actor_states, count,
|
||||
(word)(sizeof(message.header) + count * sizeof(struct distributed_actor_state)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
word network_actors_damage_entry_size(
|
||||
void)
|
||||
{
|
||||
return sizeof(struct distributed_actor_damage);
|
||||
}
|
||||
|
||||
/* client: the damage the host's AI units are taking */
|
||||
void network_actors_handle_damage(
|
||||
void const *entries,
|
||||
short count)
|
||||
{
|
||||
struct distributed_actor_damage const *units = entries;
|
||||
short index;
|
||||
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
struct damage_network_state damage;
|
||||
|
||||
if (!distributed_object_index_valid(units[index].unit_index) ||
|
||||
!network_objects_client_has(units[index].unit_index) || !actor_unit_valid(units[index].unit_index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
damage_get_network_state(units[index].unit_index, &damage);
|
||||
damage.current_shield_damage = distributed_vitality_unpack(units[index].current_shield_damage);
|
||||
damage.recent_shield_damage = distributed_vitality_unpack(units[index].recent_shield_damage);
|
||||
damage.current_body_damage = distributed_vitality_unpack(units[index].current_body_damage);
|
||||
damage.recent_body_damage = distributed_vitality_unpack(units[index].recent_body_damage);
|
||||
damage_set_network_state(units[index].unit_index, &damage);
|
||||
}
|
||||
}
|
||||
|
||||
word network_actors_entry_size(
|
||||
void)
|
||||
{
|
||||
return sizeof(struct distributed_actor_state);
|
||||
}
|
||||
|
||||
/* client: entries from the host */
|
||||
void network_actors_handle_states(
|
||||
void const *entries,
|
||||
short count)
|
||||
{
|
||||
struct distributed_actor_state const *states = entries;
|
||||
long now = game_time_get();
|
||||
short index;
|
||||
|
||||
for (index = 0; index < count; index++)
|
||||
actor_state_apply(&states[index], now);
|
||||
}
|
||||
|
||||
/* Client: called at the point in the tick where the host runs the AI.
|
||||
Drives each AI unit with the last control the host sent. */
|
||||
void network_actors_drive(
|
||||
void)
|
||||
{
|
||||
long now = game_time_get();
|
||||
short index;
|
||||
|
||||
for (index = 0; index < client_actor_count; index++)
|
||||
{
|
||||
struct client_actor *actor = &client_actors[index];
|
||||
|
||||
if (!actor_unit_valid(actor->unit_index) || now - actor->received_time > CONTROL_HELD_TICKS)
|
||||
{
|
||||
if (actor->driven && object_try_and_get_and_verify_type(actor->unit_index, _object_mask_unit))
|
||||
unit_set_actively_controlled(actor->unit_index, FALSE);
|
||||
client_actors[index--] = client_actors[--client_actor_count];
|
||||
continue;
|
||||
}
|
||||
/* vehicles follow the host's transform instead (network_objects.c) */
|
||||
if (object_get(actor->unit_index)->object.type == _object_type_vehicle)
|
||||
continue;
|
||||
if (!actor->driven)
|
||||
{
|
||||
unit_set_actively_controlled(actor->unit_index, TRUE);
|
||||
actor->driven = TRUE;
|
||||
}
|
||||
unit_control(actor->unit_index, &actor->control);
|
||||
if (actor->impulse != NONE && actor->impulse_number != actor->played_impulse_number)
|
||||
{
|
||||
real_vector2d *alignment = actor->impulse_aligned ? &actor->impulse_alignment : NULL;
|
||||
|
||||
if (actor->impulse == _actor_action_melee)
|
||||
unit_melee_attack_begin(actor->unit_index, FALSE, alignment);
|
||||
else if (actor->impulse == _actor_action_leap)
|
||||
unit_leap_begin(actor->unit_index, alignment);
|
||||
else
|
||||
unit_start_animation_impulse(actor->unit_index, actor->impulse, alignment);
|
||||
actor->played_impulse_number = actor->impulse_number;
|
||||
actor->impulse = NONE;
|
||||
}
|
||||
if (actor->speech_sound != NONE)
|
||||
{
|
||||
speech_play(actor->unit_index, actor->speech_sound);
|
||||
actor->played_speech_number = actor->speech_number;
|
||||
actor->speech_sound = NONE;
|
||||
}
|
||||
if (actor->user_animation_graph != NONE)
|
||||
{
|
||||
unit_port_play_user_animation(actor->unit_index, actor->user_animation_graph, actor->user_animation,
|
||||
actor->user_animation_interpolate, 0);
|
||||
actor->played_user_animation_number = actor->user_animation_number;
|
||||
actor->user_animation_graph = NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
NETWORK_COOP.H
|
||||
|
||||
The calls Halo's own code (source/) makes into network_coop.c. On a co-op
|
||||
host the note_ functions send what the scripts just did to the clients; on
|
||||
any other machine they do nothing, so the clients can call the same engine
|
||||
functions to apply what they receive.
|
||||
*/
|
||||
|
||||
#ifndef __NETWORK_COOP_H
|
||||
#define __NETWORK_COOP_H
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
/* network_coop_note_hud */
|
||||
enum
|
||||
{
|
||||
_coop_hud_help_text,
|
||||
_coop_hud_objective,
|
||||
_coop_hud_help_flash,
|
||||
_coop_hud_messages_clear,
|
||||
_coop_hud_checkpoint,
|
||||
};
|
||||
|
||||
/* network_coop_note_player_effect */
|
||||
enum
|
||||
{
|
||||
_coop_player_effect_translation,
|
||||
_coop_player_effect_rotation,
|
||||
_coop_player_effect_start,
|
||||
_coop_player_effect_stop,
|
||||
};
|
||||
|
||||
/* network_coop_note_nav_point */
|
||||
enum
|
||||
{
|
||||
_coop_nav_point_team_flag,
|
||||
_coop_nav_point_team_object,
|
||||
_coop_nav_point_unit_flag,
|
||||
_coop_nav_point_unit_object,
|
||||
};
|
||||
|
||||
/* ---------- prototypes/NETWORK_COOP.C */
|
||||
|
||||
/* whether this is a network co-op game (not its lobby, whose menu scene also
|
||||
has no game engine) */
|
||||
boolean network_coop_active(void);
|
||||
/* whether this machine is a co-op client, whose devices only the host moves */
|
||||
boolean network_coop_devices_remote(void);
|
||||
/* devices.c: a group's devices were set straight to its value */
|
||||
void network_coop_note_device_snap(short group_index);
|
||||
/* unit_scripting_commands.c: a script set a unit's maximum or current
|
||||
vitality; TRUE if it was a co-op player's, now set on every player's */
|
||||
boolean network_coop_set_players_vitality(long unit_index, boolean maximum, real body, real shield);
|
||||
/* host: whether the player's machine has the host's structure BSP loaded
|
||||
(always outside co-op, and for the host's own players). Until a client has,
|
||||
the host takes none of its players' movement, which falls there with no
|
||||
floor, and lets them trigger no BSP switch (players.c). */
|
||||
boolean network_coop_player_has_structure_bsp(long player_index);
|
||||
/* network_distributed.c: the structure BSP a client's input says it has */
|
||||
void network_coop_note_player_structure_bsp(short player_index, short structure_bsp_index);
|
||||
/* units.c: a vehicle left the vehicle carrying it (a Pelican's Warthog drop) */
|
||||
void network_coop_vehicle_dropped(long vehicle_index, long carrier_index);
|
||||
|
||||
/* cinematics.c: a chapter title */
|
||||
void network_coop_note_title(short title_index, real delay);
|
||||
/* hud_messaging.c, hud.c: value is a message index, or TRUE/FALSE */
|
||||
void network_coop_note_hud(short kind, short value);
|
||||
/* player_effects.c: the script screen shake (a, b, c as the function takes them) */
|
||||
void network_coop_note_player_effect(short kind, real a, real b, real c);
|
||||
/* hud_nav_points.c: activate (or, with nav_index NONE, deactivate) a nav point.
|
||||
target is the team or the unit; marker is the cutscene flag or the object. */
|
||||
void network_coop_note_nav_point(short kind, short nav_index, long target, long marker, real vertical_offset);
|
||||
|
||||
/* units.c: a unit started a custom animation (animation_index NONE: stopped) */
|
||||
void network_coop_note_unit_animation(long unit_index, long animation_graph_index, short animation_index,
|
||||
boolean interpolate);
|
||||
/* units.c: a unit (a dropship) opened or closed (unit_open, unit_close) */
|
||||
void network_coop_note_unit_open(long unit_index, boolean open);
|
||||
/* units.c: unit_custom_animation_at_frame moved it to a frame */
|
||||
void network_coop_note_unit_animation_frame(long unit_index, short frame_index);
|
||||
/* scenery.c: a scenery animation started */
|
||||
/* the scripts' effects (hs_library_external.c): at a cutscene flag, or on an
|
||||
object's marker */
|
||||
void network_coop_note_effect(long effect_definition_index, short cutscene_flag_index);
|
||||
/* the scripts' objects_attach and objects_detach (objects.c) */
|
||||
void network_coop_note_attach(long parent_index, char const *parent_marker_name, long child_index,
|
||||
char const *child_marker_name);
|
||||
void network_coop_note_detach(long parent_index, long child_index);
|
||||
void network_coop_note_object_effect(long effect_definition_index, long object_index, char const *marker_name);
|
||||
void network_coop_note_scenery_animation(long object_index, long animation_graph_index, short animation_index,
|
||||
short frame_index);
|
||||
|
||||
/* player_control.c: whether the skip key should do anything, and the press.
|
||||
network_coop_vote_skip returns FALSE outside network co-op, where the
|
||||
cinematic is skipped as usual. */
|
||||
boolean network_coop_skip_offered(void);
|
||||
boolean network_coop_vote_skip(void);
|
||||
/* main.c: after a co-op host reverts to skip a cinematic; now is the game
|
||||
time before the revert */
|
||||
void network_coop_skip_reverted(long now);
|
||||
/* the vote count to show, if a skippable cinematic is playing */
|
||||
boolean network_coop_skip_vote_status(short *votes, short *voters, boolean *voted);
|
||||
|
||||
#endif
|
||||
@@ -16,9 +16,8 @@ being hit looks and feels like on the clients).
|
||||
- A client deals no damage itself. What its own players' shots, grenades,
|
||||
melee and vehicles hit it reports to the host, which deals it once it has
|
||||
checked it: the report is of that machine's player, the damage one that
|
||||
player's weapons (now or lately), a grenade the host saw them throw
|
||||
(each throw's explosion once) or the vehicle they drove deal, of the
|
||||
shape the game gives it (at a point, a melee blow from the
|
||||
player's weapons (now or lately), grenades or the vehicle they drove
|
||||
deal, of the shape the game gives it (at a point, a melee blow from the
|
||||
striker, a collision from the vehicle), the target about where the host
|
||||
had it when the shooter saw it (the host keeps a second of where
|
||||
players' units and vehicles were, and looks back as far as the report
|
||||
@@ -37,7 +36,6 @@ being hit looks and feels like on the clients).
|
||||
*/
|
||||
|
||||
#include "cseries.h"
|
||||
#include "cseries/errors.h"
|
||||
#include "game/game.h"
|
||||
#include "game/game_globals.h"
|
||||
#include "game/players.h"
|
||||
@@ -83,13 +81,6 @@ enum
|
||||
died or dropped the launcher) */
|
||||
MAXIMUM_RECENT_WEAPONS = 8,
|
||||
RECENT_WEAPON_TICKS = 10 * TICKS_PER_SECOND,
|
||||
/* the grenades a player has thrown lately, as the host's own game threw
|
||||
them, whose damage the host takes from them: each goes off once, within
|
||||
RECENT_WEAPON_TICKS of its throw. Each player has their own (all of
|
||||
MAXIMUM_TRACKED_PLAYERS); one that hits nothing is kept the whole time,
|
||||
so this is more than a player throws in it, infinite grenades and all
|
||||
(a throw takes most of a second); past it the oldest gives way */
|
||||
MAXIMUM_GRENADE_THROWS = 32,
|
||||
/* a player's hits: a bucket of this many seconds of fire, filling at a
|
||||
second a second, a hit taking what one of its weapon's projectiles
|
||||
takes to fire (at twice the weapon's fastest rate: a shotgun's
|
||||
@@ -350,9 +341,7 @@ static struct
|
||||
{
|
||||
long definition_indices[MAXIMUM_RECENT_WEAPONS];
|
||||
long times[MAXIMUM_RECENT_WEAPONS];
|
||||
/* (the grenades thrown, not yet gone off: NONE for none) */
|
||||
long throw_times[MAXIMUM_GRENADE_THROWS];
|
||||
short throw_types[MAXIMUM_GRENADE_THROWS];
|
||||
long grenade_times[NUMBER_OF_UNIT_GRENADE_TYPES];
|
||||
long driven_time;
|
||||
/* (the blow of the player's unit with no weapon: units.c's
|
||||
unit_unarmed_melee_damage) */
|
||||
@@ -1089,74 +1078,21 @@ static real distributed_source_deals(
|
||||
return distributed_source_deals_uncached(source_index, damage_index, grenade, kinds, reach);
|
||||
}
|
||||
|
||||
/* the oldest grenade of the type the host saw the player throw within
|
||||
RECENT_WEAPON_TICKS that has not gone off yet (network_damage_note_grenade),
|
||||
NONE for none */
|
||||
static short distributed_grenade_throw(
|
||||
short player_index,
|
||||
short grenade_type)
|
||||
{
|
||||
short index;
|
||||
short oldest = NONE;
|
||||
|
||||
for (index = 0; index < MAXIMUM_GRENADE_THROWS; index++)
|
||||
{
|
||||
long time = damage_players[player_index].throw_times[index];
|
||||
|
||||
if (time != NONE && damage_players[player_index].throw_types[index] == grenade_type &&
|
||||
game_time_get() - time <= RECENT_WEAPON_TICKS &&
|
||||
(oldest == NONE || time < damage_players[player_index].throw_times[oldest]))
|
||||
{
|
||||
oldest = index;
|
||||
}
|
||||
}
|
||||
return oldest;
|
||||
}
|
||||
|
||||
/* whether an explosion of the damage is among the player's this tick
|
||||
(distributed_report_paid): at the epicenter, or (NULL) anywhere */
|
||||
static boolean distributed_explosion_this_tick(
|
||||
short player_index,
|
||||
long damage_index,
|
||||
real_point3d const *epicenter)
|
||||
{
|
||||
short index;
|
||||
|
||||
if (damage_explosions[player_index].time != game_time_get())
|
||||
return FALSE;
|
||||
for (index = 0; index < damage_explosions[player_index].count; index++)
|
||||
{
|
||||
if (damage_explosions[player_index].explosions[index].definition_index == damage_index &&
|
||||
(!epicenter || (damage_explosions[player_index].explosions[index].epicenter.x == epicenter->x &&
|
||||
damage_explosions[player_index].explosions[index].epicenter.y == epicenter->y &&
|
||||
damage_explosions[player_index].explosions[index].epicenter.z == epicenter->z)))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* how many hits a second the player could deal of the damage: their
|
||||
weapons, lately, a grenade the host saw them throw that has not gone off
|
||||
(or one going off this tick, hitting more than one object), the vehicle
|
||||
they drove lately; 0 when none of them deals it; how they deal it (the
|
||||
_damage_source flags); how far from them what they fire deals it; and the
|
||||
type of grenade when nothing but a grenade of theirs deals it, else NONE */
|
||||
weapons, lately, their grenades, the vehicle they drove lately; 0 when none
|
||||
of them deals it; how they deal it (the _damage_source flags); and how far
|
||||
from them what they fire deals it */
|
||||
static real distributed_player_deals(
|
||||
short player_index,
|
||||
long damage_index,
|
||||
byte *kinds,
|
||||
struct damage_reach *reach,
|
||||
short *grenade_type)
|
||||
struct damage_reach *reach)
|
||||
{
|
||||
struct game_globals *globals = scenario_get_game_globals();
|
||||
short index;
|
||||
real rate = 0.0f;
|
||||
real grenade_rate = 0.0f;
|
||||
|
||||
*kinds = 0;
|
||||
*grenade_type = NONE;
|
||||
reach->distance = 0.0f;
|
||||
reach->ticks = 0.0f;
|
||||
reach->carried_ticks = 0.0f;
|
||||
@@ -1176,6 +1112,25 @@ static real distributed_player_deals(
|
||||
*kinds |= weapon_kinds;
|
||||
}
|
||||
}
|
||||
for (index = 0; index < globals->grenades.count && index < NUMBER_OF_UNIT_GRENADE_TYPES; index++)
|
||||
{
|
||||
struct game_globals_grenade *grenade = TAG_BLOCK_GET_ELEMENT(&globals->grenades, index,
|
||||
struct game_globals_grenade);
|
||||
|
||||
if (game_time_get() - damage_players[player_index].grenade_times[index] <= RECENT_WEAPON_TICKS &&
|
||||
grenade->projectile.index != NONE)
|
||||
{
|
||||
byte grenade_kinds;
|
||||
struct damage_reach grenade_reach;
|
||||
real grenade_rate = distributed_source_deals(grenade->projectile.index, damage_index, TRUE, &grenade_kinds,
|
||||
&grenade_reach);
|
||||
|
||||
if (grenade_rate > 0.0f)
|
||||
distributed_reach_combine(reach, &grenade_reach);
|
||||
rate = MAX(rate, grenade_rate);
|
||||
*kinds |= grenade_kinds;
|
||||
}
|
||||
}
|
||||
/* (a blow with no weapon: the unit's own, units.c) */
|
||||
if (damage_players[player_index].unarmed_melee_damage_index == damage_index && damage_index != NONE &&
|
||||
game_time_get() - damage_players[player_index].unarmed_time <= RECENT_WEAPON_TICKS)
|
||||
@@ -1197,36 +1152,6 @@ static real distributed_player_deals(
|
||||
SET_FLAG(*kinds, _damage_source_collision_bit, TRUE);
|
||||
}
|
||||
}
|
||||
/* (a grenade's damage only from one thrown: holding grenades deals
|
||||
nothing) */
|
||||
for (index = 0; index < globals->grenades.count && index < NUMBER_OF_UNIT_GRENADE_TYPES; index++)
|
||||
{
|
||||
struct game_globals_grenade *grenade = TAG_BLOCK_GET_ELEMENT(&globals->grenades, index,
|
||||
struct game_globals_grenade);
|
||||
boolean thrown = distributed_grenade_throw(player_index, index) != NONE;
|
||||
|
||||
if ((thrown || distributed_explosion_this_tick(player_index, damage_index, NULL)) &&
|
||||
grenade->projectile.index != NONE)
|
||||
{
|
||||
byte this_kinds;
|
||||
struct damage_reach this_reach;
|
||||
real this_rate = distributed_source_deals(grenade->projectile.index, damage_index, TRUE, &this_kinds,
|
||||
&this_reach);
|
||||
|
||||
if (this_rate > 0.0f)
|
||||
{
|
||||
distributed_reach_combine(reach, &this_reach);
|
||||
/* (one with a throw left before one only going off now) */
|
||||
if (*grenade_type == NONE || thrown)
|
||||
*grenade_type = index;
|
||||
}
|
||||
grenade_rate = MAX(grenade_rate, this_rate);
|
||||
*kinds |= this_kinds;
|
||||
}
|
||||
}
|
||||
if (rate > 0.0f)
|
||||
*grenade_type = NONE;
|
||||
rate = MAX(rate, grenade_rate);
|
||||
return rate * HIT_REPORT_RATE_MARGIN;
|
||||
}
|
||||
|
||||
@@ -1507,6 +1432,16 @@ static void distributed_note_weapons(
|
||||
if (vehicle && vehicle->unit.driver_object_index == unit_index)
|
||||
damage_players[player_index].driven_time = game_time_get();
|
||||
}
|
||||
{
|
||||
struct unit_datum *unit = unit_get(unit_index);
|
||||
short grenade_index;
|
||||
|
||||
for (grenade_index = 0; grenade_index < NUMBER_OF_UNIT_GRENADE_TYPES; grenade_index++)
|
||||
{
|
||||
if (unit->unit.grenade_counts[grenade_index] > 0)
|
||||
damage_players[player_index].grenade_times[grenade_index] = game_time_get();
|
||||
}
|
||||
}
|
||||
for (unit_number = 0; unit_number < 2; unit_number++)
|
||||
{
|
||||
struct unit_datum *unit = units[unit_number] != NONE ?
|
||||
@@ -1704,8 +1639,6 @@ static boolean distributed_report_valid(
|
||||
real reach;
|
||||
struct damage_reach fired_reach;
|
||||
short seen;
|
||||
short grenade_type;
|
||||
short throw_index = NONE;
|
||||
|
||||
/* that machine's player */
|
||||
if (player_index == NO_PLAYER || player_index >= MAXIMUM_TRACKED_PLAYERS ||
|
||||
@@ -1738,8 +1671,7 @@ static boolean distributed_report_valid(
|
||||
return FALSE;
|
||||
ticks = MIN(now - report->host_time + TARGET_HISTORY_SLACK_TICKS, TARGET_HISTORY_TICKS - 1);
|
||||
/* damage that player could deal, and how they deal it */
|
||||
rate = distributed_player_deals(player_index, report->damage.definition_index, &kinds, &fired_reach,
|
||||
&grenade_type);
|
||||
rate = distributed_player_deals(player_index, report->damage.definition_index, &kinds, &fired_reach);
|
||||
if (rate <= 0.0f)
|
||||
return FALSE;
|
||||
/* ... of its shape: at a point, else a melee blow, else a collision; and
|
||||
@@ -1781,26 +1713,12 @@ static boolean distributed_report_valid(
|
||||
reach += definition->cutoff_radius;
|
||||
if (!(distributed_distance_squared(&report->damage.origin, &report->target_position) <= reach * reach))
|
||||
return FALSE;
|
||||
/* what only a grenade of theirs deals, from one the host saw them throw
|
||||
that has not gone off: its explosion once (the explosion's other objects
|
||||
that tick come free, distributed_report_paid), what it hits at a point
|
||||
while it is about */
|
||||
if (grenade_type != NONE &&
|
||||
!distributed_explosion_this_tick(player_index, report->damage.definition_index, &report->damage.epicenter))
|
||||
{
|
||||
throw_index = distributed_grenade_throw(player_index, grenade_type);
|
||||
if (throw_index == NONE)
|
||||
return FALSE;
|
||||
}
|
||||
/* no more than the weapon fires */
|
||||
if (!distributed_report_paid(player_index, report, rate, area && !melee && !collision,
|
||||
definition->cutoff_radius > 0.0f))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
/* (the grenade gone off: its throw spent) */
|
||||
if (throw_index != NONE && area && !melee && !collision)
|
||||
damage_players[player_index].throw_times[throw_index] = NONE;
|
||||
/* a melee blow from where the host had the player (its origin their
|
||||
head, its epicenter their body); a collision from the vehicle they
|
||||
drove */
|
||||
@@ -2021,26 +1939,6 @@ static boolean distributed_event_goes_to(
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* How often the host logs how many client hits it accepted and refused
|
||||
(only when it refused some). Tells "my shots don't register" apart from
|
||||
"I missed". */
|
||||
#define REPORT_LOG_TICKS (30 * TICKS_PER_SECOND)
|
||||
|
||||
static void distributed_log_reports(
|
||||
void)
|
||||
{
|
||||
static long logged_dealt;
|
||||
static long logged_rejected;
|
||||
|
||||
if (game_time_get() % REPORT_LOG_TICKS != 0 || damage_rejected_reports == logged_rejected)
|
||||
return;
|
||||
error(_error_silent, "damage: clients' hits in the last %d seconds: %ld dealt, %ld refused",
|
||||
REPORT_LOG_TICKS / TICKS_PER_SECOND, damage_dealt_reports - logged_dealt,
|
||||
damage_rejected_reports - logged_rejected);
|
||||
logged_dealt = damage_dealt_reports;
|
||||
logged_rejected = damage_rejected_reports;
|
||||
}
|
||||
|
||||
void network_damage_host_tick(
|
||||
void)
|
||||
{
|
||||
@@ -2051,7 +1949,6 @@ void network_damage_host_tick(
|
||||
distributed_client_machines(machine_indices, HALO_PORT_MAXIMUM_NETWORK_MACHINES) : 0;
|
||||
short machine_number;
|
||||
|
||||
distributed_log_reports();
|
||||
distributed_note_weapons();
|
||||
distributed_note_targets();
|
||||
distributed_note_event_destinations();
|
||||
@@ -2205,41 +2102,6 @@ void network_damage_statistics(
|
||||
*replayed_events = damage_replayed_events;
|
||||
}
|
||||
|
||||
/* the host: a grenade a player's unit threw (units.c), as the host's own
|
||||
game threw it, which that player's machine reports the damage of
|
||||
(distributed_report_valid) */
|
||||
void network_damage_note_grenade(
|
||||
long unit_index,
|
||||
short grenade_type)
|
||||
{
|
||||
struct unit_datum *unit = unit_get(unit_index);
|
||||
long player_index;
|
||||
short index;
|
||||
short slot = 0;
|
||||
|
||||
if (game_connection() != _game_connection_network_server || unit->unit.player_index == NONE ||
|
||||
grenade_type < 0 || grenade_type >= NUMBER_OF_UNIT_GRENADE_TYPES)
|
||||
{
|
||||
return;
|
||||
}
|
||||
player_index = DATUM_INDEX_TO_ABSOLUTE_INDEX(unit->unit.player_index);
|
||||
if (player_index >= MAXIMUM_TRACKED_PLAYERS)
|
||||
return;
|
||||
/* (in a free place, else the oldest's) */
|
||||
for (index = 0; index < MAXIMUM_GRENADE_THROWS; index++)
|
||||
{
|
||||
if (damage_players[player_index].throw_times[index] == NONE)
|
||||
{
|
||||
slot = index;
|
||||
break;
|
||||
}
|
||||
if (damage_players[player_index].throw_times[index] < damage_players[player_index].throw_times[slot])
|
||||
slot = index;
|
||||
}
|
||||
damage_players[player_index].throw_times[slot] = game_time_get();
|
||||
damage_players[player_index].throw_types[slot] = grenade_type;
|
||||
}
|
||||
|
||||
void network_damage_new_game(
|
||||
void)
|
||||
{
|
||||
@@ -2259,8 +2121,8 @@ void network_damage_new_game(
|
||||
for (index = 0; index < MAXIMUM_RECENT_WEAPONS; index++)
|
||||
damage_players[player_index].definition_indices[index] = NONE;
|
||||
damage_players[player_index].unarmed_melee_damage_index = NONE;
|
||||
for (index = 0; index < MAXIMUM_GRENADE_THROWS; index++)
|
||||
damage_players[player_index].throw_times[index] = NONE;
|
||||
for (index = 0; index < NUMBER_OF_UNIT_GRENADE_TYPES; index++)
|
||||
damage_players[player_index].grenade_times[index] = -RECENT_WEAPON_TICKS - 1;
|
||||
damage_players[player_index].driven_time = -RECENT_WEAPON_TICKS - 1;
|
||||
damage_players[player_index].hit_seconds = HIT_REPORT_BURST_SECONDS;
|
||||
damage_explosions[player_index].time = NONE;
|
||||
|
||||
@@ -53,24 +53,18 @@ machine (their datum identifiers need not be).
|
||||
|
||||
#include "cseries.h"
|
||||
#include "cseries/errors.h"
|
||||
#include "cache/cache_files.h"
|
||||
#include "models/model_animation_definitions.h"
|
||||
#include "game/game.h"
|
||||
#include "game/game_globals.h"
|
||||
#include "game/players.h"
|
||||
#include "game/game_engine.h"
|
||||
#include "main/main.h"
|
||||
#include "game/player_queues_new.h"
|
||||
#include "networking/network_game_globals.h"
|
||||
#include "objects/objects.h"
|
||||
#include "objects/damage.h"
|
||||
#include "scenario/scenario.h"
|
||||
#include "scenario/scenario_definitions.h"
|
||||
#include "structures/structure_bsp_definitions.h"
|
||||
#include "units/units.h"
|
||||
#include "units/biped_definitions.h"
|
||||
#include "units/bipeds.h"
|
||||
#include "network_coop.h"
|
||||
#include "network_distributed.h"
|
||||
|
||||
#include <limits.h>
|
||||
@@ -244,8 +238,11 @@ before the host takes it no longer, and before the client is put where the
|
||||
host has it (no further than that: between the two they would disagree for
|
||||
good, the host's player somewhere its own is not) */
|
||||
#define HOST_ACCEPT_TOLERANCE 3.5f
|
||||
#define REMOTE_CORRECTION_TOLERANCE 0.05f
|
||||
#define LOCAL_CORRECTION_TOLERANCE 3.0f
|
||||
/* how fast a client's own player's unit moves at most (world units a tick) */
|
||||
/* how far from the origin a unit is (world units), and how fast a client's
|
||||
own player's unit moves at most (world units a tick) */
|
||||
#define UNIT_WORLD_BOUND 32768.0f
|
||||
#define MAXIMUM_PREDICTED_SPEED 2.0f
|
||||
/* ... on foot: this many times as fast as a player runs and jumps, or as
|
||||
fast as the host's ticks threw its copy lately, whichever is more; and how
|
||||
@@ -320,9 +317,7 @@ ticks before it */
|
||||
struct distributed_player_input
|
||||
{
|
||||
byte player_index;
|
||||
/* co-op: the structure BSP the client has loaded */
|
||||
byte structure_bsp_index;
|
||||
byte pad[2];
|
||||
byte pad[3];
|
||||
/* the client's tick */
|
||||
long tick;
|
||||
/* the latest host tick the client has had a message of (the host
|
||||
@@ -671,40 +666,6 @@ boolean distributed_point_valid(
|
||||
return fabsf(point->x) <= bound && fabsf(point->y) <= bound && fabsf(point->z) <= bound;
|
||||
}
|
||||
|
||||
/* whether a tag index from the host really is a tag of that group */
|
||||
boolean distributed_tag_of_group(
|
||||
long tag_index,
|
||||
unsigned long group_tag)
|
||||
{
|
||||
struct tag_iterator iterator;
|
||||
long index;
|
||||
|
||||
tag_iterator_new(&iterator, group_tag);
|
||||
while ((index = tag_iterator_next(&iterator)) != NONE)
|
||||
{
|
||||
if (index == tag_index)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* the graph's animation, if the graph tag and index are valid */
|
||||
struct animation *distributed_graph_animation(
|
||||
long animation_graph_index,
|
||||
short animation_index)
|
||||
{
|
||||
struct animation_graph *graph;
|
||||
|
||||
if (!distributed_tag_of_group(animation_graph_index, ANIMATION_GRAPH_TAG))
|
||||
return NULL;
|
||||
graph = animation_graph_definition_get(animation_graph_index);
|
||||
if (animation_index < 0 || animation_index >= graph->animations.count)
|
||||
return NULL;
|
||||
|
||||
return TAG_BLOCK_GET_ELEMENT(&graph->animations, animation_index, struct animation);
|
||||
}
|
||||
|
||||
boolean distributed_object_index_valid(
|
||||
long object_index)
|
||||
{
|
||||
@@ -788,7 +749,7 @@ void distributed_unit_vector_unpack(
|
||||
}
|
||||
}
|
||||
|
||||
word distributed_vitality_pack(
|
||||
static word distributed_vitality_pack(
|
||||
real value)
|
||||
{
|
||||
value *= VITALITY_SCALE;
|
||||
@@ -796,13 +757,15 @@ word distributed_vitality_pack(
|
||||
return (word)(long)floor(value + 0.5f);
|
||||
}
|
||||
|
||||
real distributed_vitality_unpack(
|
||||
static real distributed_vitality_unpack(
|
||||
word value)
|
||||
{
|
||||
return (real)value / VITALITY_SCALE;
|
||||
}
|
||||
|
||||
short distributed_angle_pack(
|
||||
/* an angle as a 16-bit fraction of a turn, and back (yaw from 0 to 2 pi,
|
||||
pitch from -pi to pi) */
|
||||
static short distributed_angle_pack(
|
||||
real angle)
|
||||
{
|
||||
real turns = angle / (2.0f * _pi);
|
||||
@@ -811,7 +774,7 @@ short distributed_angle_pack(
|
||||
return (short)(word)((long)floor(turns * 65536.0f + 0.5f) & 0xFFFF);
|
||||
}
|
||||
|
||||
real distributed_angle_unpack(
|
||||
static real distributed_angle_unpack(
|
||||
short value,
|
||||
boolean signed_angle)
|
||||
{
|
||||
@@ -2085,14 +2048,9 @@ static void distributed_apply_predictions(
|
||||
{
|
||||
distributed_predictions[player_index].valid = FALSE;
|
||||
distributed_predictions[player_index].taken_host_time = NONE;
|
||||
/* (of the unit it has now: not one of a life before; and not
|
||||
from a co-op client still loading the host's BSP, whose player
|
||||
falls there with no floor under it) */
|
||||
if (unit_index != NONE && unit_index == state->unit_index &&
|
||||
network_coop_player_has_structure_bsp(unit_get(unit_index)->unit.player_index))
|
||||
{
|
||||
/* (of the unit it has now: not one of a life before) */
|
||||
if (unit_index != NONE && unit_index == state->unit_index)
|
||||
distributed_take_prediction(player_index, unit_index, &bound);
|
||||
}
|
||||
}
|
||||
/* (what the host's next tick starts from) */
|
||||
if (unit_index != NONE)
|
||||
@@ -2264,7 +2222,6 @@ static void distributed_client_send_inputs(
|
||||
if (!update_client_distributed_input(local_player_index, &input->tick, &input->action, input->control_flags))
|
||||
continue;
|
||||
input->player_index = distributed_player_to_byte(player_index);
|
||||
input->structure_bsp_index = (byte)global_structure_bsp_index_get();
|
||||
input->host_time = distributed_host_time;
|
||||
count++;
|
||||
}
|
||||
@@ -2275,37 +2232,6 @@ static void distributed_client_send_inputs(
|
||||
}
|
||||
}
|
||||
|
||||
/* (the host, in co-op) where each client player's input last faced, by
|
||||
absolute index: how far it turns between inputs is its looking around */
|
||||
static struct
|
||||
{
|
||||
boolean valid;
|
||||
real_euler_angles2d facing;
|
||||
} distributed_input_facings[MAXIMUM_TRACKED_PLAYERS];
|
||||
|
||||
/* (the host, in co-op) a client player's input counted in the scripts'
|
||||
action tests, as a local player's is (player_control_action_test_note) */
|
||||
static void distributed_note_input_actions(
|
||||
short player_index,
|
||||
struct player_action const *action)
|
||||
{
|
||||
real_euler_angles2d turn = { 0.0f, 0.0f };
|
||||
|
||||
if (distributed_input_facings[player_index].valid)
|
||||
{
|
||||
turn.yaw = action->desired_facing.yaw - distributed_input_facings[player_index].facing.yaw;
|
||||
turn.pitch = action->desired_facing.pitch - distributed_input_facings[player_index].facing.pitch;
|
||||
/* (the short way round) */
|
||||
if (turn.yaw > _pi)
|
||||
turn.yaw -= 2.0f * _pi;
|
||||
else if (turn.yaw < -_pi)
|
||||
turn.yaw += 2.0f * _pi;
|
||||
}
|
||||
distributed_input_facings[player_index].valid = TRUE;
|
||||
distributed_input_facings[player_index].facing = action->desired_facing;
|
||||
player_control_action_test_note(action->control_flags, &turn, &action->throttle, action->primary_trigger);
|
||||
}
|
||||
|
||||
/* (the host) a client's players' input, and from it (once a message) how
|
||||
long a message takes that client and back */
|
||||
static void distributed_handle_inputs(
|
||||
@@ -2335,10 +2261,6 @@ static void distributed_handle_inputs(
|
||||
action.throttle.i = PIN(action.throttle.i, -1.0f, 1.0f);
|
||||
action.throttle.j = PIN(action.throttle.j, -1.0f, 1.0f);
|
||||
action.primary_trigger = PIN(action.primary_trigger, 0.0f, 1.0f);
|
||||
/* (a spectator's buttons pick whom it watches: not counted) */
|
||||
if (network_coop_active() && player->unit_index != NONE)
|
||||
distributed_note_input_actions(input->player_index, &action);
|
||||
network_coop_note_player_structure_bsp(input->player_index, input->structure_bsp_index);
|
||||
update_server_handle_distributed_input(DATUM_INDEX_NEW(input->player_index, player->identifier), input->tick,
|
||||
&action, input->control_flags, DISTRIBUTED_INPUT_HISTORY);
|
||||
if (input->host_time != NONE && (host_time == NONE || input->host_time > host_time))
|
||||
@@ -3009,52 +2931,6 @@ static void distributed_send_pings(
|
||||
}
|
||||
}
|
||||
|
||||
/* Co-op: the host's structure BSP. Clients switch BSP only when told
|
||||
(their own trigger volumes don't, players.c). A switch is sent at once and
|
||||
reliably; it is also resent regularly so a client that joined late catches
|
||||
up. */
|
||||
#define STRUCTURE_BSP_INTERVAL_TICKS (TICKS_PER_SECOND / 2)
|
||||
|
||||
static short distributed_sent_structure_bsp_index = NONE;
|
||||
|
||||
struct distributed_structure_bsp
|
||||
{
|
||||
short structure_bsp_index;
|
||||
short pad;
|
||||
};
|
||||
|
||||
struct distributed_structure_bsp_message
|
||||
{
|
||||
struct distributed_message_header header;
|
||||
struct distributed_structure_bsp structure_bsp;
|
||||
};
|
||||
|
||||
static void distributed_send_structure_bsp(
|
||||
void)
|
||||
{
|
||||
struct distributed_structure_bsp_message message;
|
||||
short index = global_structure_bsp_index_get();
|
||||
boolean switched = index != distributed_sent_structure_bsp_index;
|
||||
|
||||
message.structure_bsp.structure_bsp_index = index;
|
||||
message.structure_bsp.pad = 0;
|
||||
distributed_send(&message, _distributed_message_structure_bsp, 1, (word)sizeof(message),
|
||||
switched ? _distributed_to_clients_reliably : _distributed_to_clients);
|
||||
distributed_sent_structure_bsp_index = index;
|
||||
}
|
||||
|
||||
static void distributed_handle_structure_bsp(
|
||||
struct distributed_structure_bsp const *structure_bsp)
|
||||
{
|
||||
short index = structure_bsp->structure_bsp_index;
|
||||
|
||||
if (network_coop_active() && index >= 0 && index < global_scenario_get()->structure_bsp_references.count &&
|
||||
index != global_structure_bsp_index_get())
|
||||
{
|
||||
main_switch_structure_bsp(index);
|
||||
}
|
||||
}
|
||||
|
||||
/* the players' statistics that changed since they were last sent, and when
|
||||
refreshing, STATISTICS_REFRESH_PLAYERS more whatever they are, round them
|
||||
all (a client that lost a change has it again within eight seconds) */
|
||||
@@ -3200,7 +3076,6 @@ void network_distributed_new_game(
|
||||
csmemset(distributed_accepted, 0, sizeof(distributed_accepted));
|
||||
csmemset(distributed_host_speeds, 0, sizeof(distributed_host_speeds));
|
||||
csmemset(distributed_round_trips, 0, sizeof(distributed_round_trips));
|
||||
csmemset(distributed_input_facings, 0, sizeof(distributed_input_facings));
|
||||
csmemset(distributed_client_clocks, 0, sizeof(distributed_client_clocks));
|
||||
csmemset(distributed_client_identities, 0, sizeof(distributed_client_identities));
|
||||
distributed_identity_sent = FALSE;
|
||||
@@ -3261,8 +3136,7 @@ void network_distributed_new_game(
|
||||
network_damage_new_game();
|
||||
network_vr_new_game();
|
||||
bots_new_game();
|
||||
network_actors_new_game();
|
||||
network_coop_new_game();
|
||||
network_voice_new_game();
|
||||
}
|
||||
|
||||
/* after each tick (game_time.c) */
|
||||
@@ -3305,18 +3179,10 @@ void network_distributed_tick(
|
||||
distributed_statistics_due = FALSE;
|
||||
if (game_time_get() % PING_INTERVAL_TICKS == 0)
|
||||
distributed_send_pings();
|
||||
/* (before the players, so clients load a new BSP before they hear
|
||||
where the host moved everyone into it) */
|
||||
if (network_coop_active() && (global_structure_bsp_index_get() != distributed_sent_structure_bsp_index ||
|
||||
game_time_get() % STRUCTURE_BSP_INTERVAL_TICKS == 0))
|
||||
{
|
||||
distributed_send_structure_bsp();
|
||||
}
|
||||
distributed_host_send_players();
|
||||
network_vr_tick();
|
||||
bots_tick();
|
||||
network_actors_host_tick();
|
||||
network_coop_host_tick();
|
||||
network_voice_tick();
|
||||
distributed_send_pickups();
|
||||
if (game_time_get() % GAME_STATE_INTERVAL_TICKS == 0)
|
||||
distributed_send_game_state(NONE);
|
||||
@@ -3326,10 +3192,10 @@ void network_distributed_tick(
|
||||
distributed_note_own_positions();
|
||||
distributed_client_send_inputs();
|
||||
network_vr_tick();
|
||||
network_voice_tick();
|
||||
distributed_client_send_predictions();
|
||||
network_objects_client_tick();
|
||||
network_damage_client_tick();
|
||||
network_coop_client_tick();
|
||||
}
|
||||
distributed_batches_flush();
|
||||
distributed_machines.in_tick = FALSE;
|
||||
@@ -3358,19 +3224,6 @@ static boolean distributed_message_stale(
|
||||
case _distributed_message_damage_events:
|
||||
case _distributed_message_vr_poses:
|
||||
case _distributed_message_pings:
|
||||
case _distributed_message_actor_states:
|
||||
case _distributed_message_structure_bsp:
|
||||
case _distributed_message_coop_presentation:
|
||||
case _distributed_message_coop_device_groups:
|
||||
case _distributed_message_coop_object_names:
|
||||
case _distributed_message_coop_skip_vote:
|
||||
case _distributed_message_coop_events:
|
||||
case _distributed_message_coop_object_transforms:
|
||||
case _distributed_message_actor_damage:
|
||||
case _distributed_message_coop_object_looks:
|
||||
case _distributed_message_damage_animations:
|
||||
case _distributed_message_coop_screen_effect:
|
||||
case _distributed_message_coop_device_states:
|
||||
break;
|
||||
default:
|
||||
return FALSE;
|
||||
@@ -3673,27 +3526,6 @@ void network_distributed_ban(
|
||||
}
|
||||
}
|
||||
|
||||
/* (the host: network_server_manager.c, its kick command) players kicked by
|
||||
the host, which may join again: every machine told, nothing kept (no line
|
||||
in BANS_FILE) */
|
||||
void network_distributed_kick(
|
||||
char const *names)
|
||||
{
|
||||
char kept_names[64];
|
||||
char notice[MAXIMUM_NOTICE_LENGTH];
|
||||
|
||||
distributed_printable(kept_names, sizeof(kept_names), names);
|
||||
snprintf(notice, sizeof(notice), "%s kicked by the host", kept_names);
|
||||
/* (to every client in the game: in the lobby, the host's own) */
|
||||
if (game_in_progress())
|
||||
distributed_send_notice(notice);
|
||||
else
|
||||
{
|
||||
console_warning("%s", notice);
|
||||
error(_error_log, "%s", notice);
|
||||
}
|
||||
}
|
||||
|
||||
/* (the host) a client machine's tick, which one of its messages is
|
||||
stamped with: its clock measured, each window, against the host's; one
|
||||
whose game runs fast (distributed_client_clock) has its players'
|
||||
@@ -3829,19 +3661,6 @@ void network_distributed_handle_message(
|
||||
case _distributed_message_unit_states: entry_size = DISTRIBUTED_UNIT_STATE_MINIMUM_SIZE; break;
|
||||
case _distributed_message_player_statistics: entry_size = sizeof(struct distributed_player_statistics); break;
|
||||
case _distributed_message_pings: entry_size = sizeof(struct distributed_player_ping); break;
|
||||
case _distributed_message_actor_states: entry_size = network_actors_entry_size(); break;
|
||||
case _distributed_message_structure_bsp: entry_size = sizeof(struct distributed_structure_bsp); break;
|
||||
case _distributed_message_coop_presentation: entry_size = network_coop_presentation_entry_size(); break;
|
||||
case _distributed_message_coop_events: entry_size = network_coop_event_entry_size(); break;
|
||||
case _distributed_message_coop_skip_vote: entry_size = network_coop_skip_vote_entry_size(); break;
|
||||
case _distributed_message_coop_device_groups: entry_size = network_coop_device_group_entry_size(); break;
|
||||
case _distributed_message_coop_object_names: entry_size = network_coop_object_names_entry_size(); break;
|
||||
case _distributed_message_coop_object_transforms: entry_size = network_coop_object_transform_entry_size(); break;
|
||||
case _distributed_message_actor_damage: entry_size = network_actors_damage_entry_size(); break;
|
||||
case _distributed_message_coop_object_looks: entry_size = network_coop_object_look_entry_size(); break;
|
||||
case _distributed_message_damage_animations: entry_size = network_objects_damage_animation_entry_size(); break;
|
||||
case _distributed_message_coop_screen_effect: entry_size = network_coop_screen_effect_entry_size(); break;
|
||||
case _distributed_message_coop_device_states: entry_size = network_coop_device_state_entry_size(); break;
|
||||
case _distributed_message_pickups: entry_size = sizeof(struct distributed_pickup); break;
|
||||
case _distributed_message_player_inputs: entry_size = sizeof(struct distributed_player_input); break;
|
||||
case _distributed_message_relayed_actions: entry_size = DISTRIBUTED_RELAYED_ACTION_MINIMUM_SIZE; break;
|
||||
@@ -3851,6 +3670,7 @@ void network_distributed_handle_message(
|
||||
case _distributed_message_client_ready: entry_size = 0; break;
|
||||
case _distributed_message_client_identity: entry_size = sizeof(struct distributed_client_identity); break;
|
||||
case _distributed_message_vr_poses: entry_size = network_vr_entry_size(); break;
|
||||
case _distributed_message_voice: entry_size = network_voice_entry_size(); break;
|
||||
case _distributed_message_damage_events:
|
||||
case _distributed_message_hit_reports: entry_size = network_damage_entry_size(header.type); break;
|
||||
default: entry_size = network_objects_entry_size(header.type); break;
|
||||
@@ -3860,23 +3680,20 @@ void network_distributed_handle_message(
|
||||
{
|
||||
return;
|
||||
}
|
||||
/* (the kinds whose handlers read one entry: not without it) */
|
||||
switch (header.type)
|
||||
{
|
||||
case _distributed_message_structure_bsp:
|
||||
case _distributed_message_coop_presentation:
|
||||
case _distributed_message_coop_object_names:
|
||||
case _distributed_message_coop_skip_vote:
|
||||
if (header.count < 1)
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
distributed_statistics.received++;
|
||||
|
||||
/* (each kind from the host, or from a client; a VR player's hands
|
||||
both ways) */
|
||||
if (header.type == _distributed_message_voice)
|
||||
{
|
||||
if (machine_index == NONE ? game_connection() != _game_connection_network_client :
|
||||
game_connection() != _game_connection_network_server)
|
||||
{
|
||||
return;
|
||||
}
|
||||
network_voice_handle(machine_index, message, size);
|
||||
return;
|
||||
}
|
||||
if (header.type == _distributed_message_vr_poses)
|
||||
{
|
||||
if (machine_index == NONE ? game_connection() != _game_connection_network_client :
|
||||
@@ -3896,7 +3713,6 @@ void network_distributed_handle_message(
|
||||
case _distributed_message_hit_reports:
|
||||
case _distributed_message_vehicle_prediction:
|
||||
case _distributed_message_player_inputs:
|
||||
case _distributed_message_coop_skip_vote:
|
||||
if (machine_index == NONE || game_connection() != _game_connection_network_server)
|
||||
return;
|
||||
break;
|
||||
@@ -3933,45 +3749,6 @@ void network_distributed_handle_message(
|
||||
case _distributed_message_unit_states:
|
||||
distributed_handle_unit_states((byte const *)entries, (byte const *)message + size, header.count);
|
||||
break;
|
||||
case _distributed_message_actor_states:
|
||||
network_actors_handle_states(entries, header.count);
|
||||
break;
|
||||
case _distributed_message_structure_bsp:
|
||||
distributed_handle_structure_bsp((struct distributed_structure_bsp const *)entries);
|
||||
break;
|
||||
case _distributed_message_coop_presentation:
|
||||
network_coop_handle_presentation(entries, header.game_time);
|
||||
break;
|
||||
case _distributed_message_coop_events:
|
||||
network_coop_handle_events(entries, header.count);
|
||||
break;
|
||||
case _distributed_message_coop_skip_vote:
|
||||
network_coop_handle_skip_vote(machine_index, entries);
|
||||
break;
|
||||
case _distributed_message_coop_device_groups:
|
||||
network_coop_handle_device_groups(entries, header.count);
|
||||
break;
|
||||
case _distributed_message_coop_object_names:
|
||||
network_coop_handle_object_names(entries);
|
||||
break;
|
||||
case _distributed_message_coop_object_transforms:
|
||||
network_coop_handle_object_transforms(entries, header.count);
|
||||
break;
|
||||
case _distributed_message_actor_damage:
|
||||
network_actors_handle_damage(entries, header.count);
|
||||
break;
|
||||
case _distributed_message_coop_object_looks:
|
||||
network_coop_handle_object_looks(entries, header.count);
|
||||
break;
|
||||
case _distributed_message_damage_animations:
|
||||
network_objects_handle_damage_animations(entries, header.count);
|
||||
break;
|
||||
case _distributed_message_coop_screen_effect:
|
||||
network_coop_handle_screen_effect(entries, header.count);
|
||||
break;
|
||||
case _distributed_message_coop_device_states:
|
||||
network_coop_handle_device_states(entries, header.count);
|
||||
break;
|
||||
case _distributed_message_player_statistics:
|
||||
{
|
||||
/* the host's count of kills, deaths, ... */
|
||||
|
||||
@@ -65,47 +65,6 @@ enum
|
||||
/* every player's ping as the host measures it, every two seconds, for
|
||||
the scoreboard (unreliable) */
|
||||
_distributed_message_pings,
|
||||
/* the host's actors' units: their control and state (network_actors.c).
|
||||
A number of its own, clear of the kinds upstream adds: a build without
|
||||
it drops the message as a kind it does not know, so the network version
|
||||
stays upstream's and its players join as before */
|
||||
_distributed_message_actor_states = 64,
|
||||
/* co-op (a campaign map, no game engine): the host's structure BSP,
|
||||
twice a second, for a client to switch to; numbered as actor_states */
|
||||
_distributed_message_structure_bsp,
|
||||
/* co-op: the host's cinematic, camera and screen fade, every tick
|
||||
(network_coop.c) */
|
||||
_distributed_message_coop_presentation,
|
||||
/* (0.2.0's co-op script sounds; now part of coop_events. The number
|
||||
stays taken so a 0.2.0 build never misreads a newer message.) */
|
||||
_distributed_message_coop_sounds_retired,
|
||||
/* co-op: device group state (network_coop.c) */
|
||||
_distributed_message_coop_device_groups,
|
||||
/* co-op: which named objects exist on the host (network_coop.c) */
|
||||
_distributed_message_coop_object_names,
|
||||
/* co-op: a client's vote to skip the cutscene, to the host (network_coop.c) */
|
||||
_distributed_message_coop_skip_vote,
|
||||
/* co-op: what the host's scripts did once: sounds, titles, HUD text,
|
||||
animations (network_coop.c) */
|
||||
_distributed_message_coop_events,
|
||||
/* co-op: where the host's scenery and machines are, when they move
|
||||
(network_coop.c) */
|
||||
_distributed_message_coop_object_transforms,
|
||||
/* the damage the host's AI units are taking, which their shields'
|
||||
flares are drawn by (network_actors.c); 73 in upstream's AI sync too */
|
||||
_distributed_message_actor_damage = 73,
|
||||
/* co-op: how the host's objects look (their permutations, scale), when
|
||||
that changes (network_coop.c) */
|
||||
_distributed_message_coop_object_looks,
|
||||
/* the flinch and death animations the host picked for its units, which a
|
||||
client plays rather than its own picks (network_objects.c) */
|
||||
_distributed_message_damage_animations = 75,
|
||||
/* co-op: the host's cinematic screen effect (blur, filters, video), when
|
||||
it changes and every two seconds (network_coop.c) */
|
||||
_distributed_message_coop_screen_effect = 76,
|
||||
/* co-op: where the host's devices are (doors, elevators), when they move
|
||||
(network_coop.c) */
|
||||
_distributed_message_coop_device_states = 77,
|
||||
|
||||
/* The VR port's own messages, numbered far past the game's (240 on),
|
||||
so that messages upstream adds never take their numbers: a build
|
||||
@@ -113,6 +72,10 @@ enum
|
||||
/* a VR player's hands, every tick: a client's own to the host, the
|
||||
host's of the others to each client (unreliable; network_vr.c) */
|
||||
_distributed_message_vr_poses = 240,
|
||||
/* a player's speech, every tick while they speak: a client's own to
|
||||
the host, the host's and the other clients' to each client (unreliable;
|
||||
network_voice.c) */
|
||||
_distributed_message_voice,
|
||||
|
||||
NUMBER_OF_DISTRIBUTED_MESSAGES
|
||||
};
|
||||
@@ -134,10 +97,6 @@ tick, drawn as it goes; further, it is put there, drawn gliding
|
||||
#define HOST_BLEND_DISTANCE 0.25f
|
||||
#define HOST_VEHICLE_BLEND_DISTANCE 0.5f
|
||||
#define REMOTE_BLEND_DISTANCE 1.0f
|
||||
/* world units: how far a remote unit may be from where the host says
|
||||
before it is put there, and how far from the origin a unit can be */
|
||||
#define REMOTE_CORRECTION_TOLERANCE 0.05f
|
||||
#define UNIT_WORLD_BOUND 32768.0f
|
||||
#define REMOTE_VEHICLE_BLEND_DISTANCE 2.0f
|
||||
|
||||
enum
|
||||
@@ -235,10 +194,6 @@ never 0, which any object at the index matches) */
|
||||
boolean distributed_real_valid(real value);
|
||||
boolean distributed_point_valid(real_point3d const *point, real bound);
|
||||
boolean distributed_object_index_valid(long object_index);
|
||||
/* whether a tag index from the host really is a tag of that group */
|
||||
boolean distributed_tag_of_group(long tag_index, unsigned long group_tag);
|
||||
/* the graph's animation, if a graph tag and animation index from the host are valid */
|
||||
struct animation *distributed_graph_animation(long animation_graph_index, short animation_index);
|
||||
/* ... an orientation's two axes (unpacked): TRUE when they are one long
|
||||
and about square, then made exactly so */
|
||||
boolean distributed_axes_make_valid(real_vector3d *forward, real_vector3d *up);
|
||||
@@ -249,70 +204,12 @@ void distributed_unit_vector_unpack(struct distributed_vector const *vector, rea
|
||||
#define DISTRIBUTED_UNIT_SCALE 32767.0f
|
||||
#define DISTRIBUTED_VELOCITY_SCALE 1024.0f
|
||||
#define DISTRIBUTED_ANGULAR_VELOCITY_SCALE 4096.0f
|
||||
/* an angle as a 16-bit fraction of a turn, and back (yaw from 0 to 2 pi,
|
||||
pitch from -pi to pi) */
|
||||
short distributed_angle_pack(real angle);
|
||||
real distributed_angle_unpack(short value, boolean signed_angle);
|
||||
/* shields and health in 16 bits */
|
||||
word distributed_vitality_pack(real value);
|
||||
real distributed_vitality_unpack(word value);
|
||||
|
||||
/* ---------- prototypes/NETWORK_ACTORS.C */
|
||||
|
||||
void network_actors_new_game(void);
|
||||
/* (the host, after each tick) the units its actors drove this tick, to
|
||||
each client */
|
||||
void network_actors_host_tick(void);
|
||||
/* (a client) the host's word on its actors' units */
|
||||
void network_actors_handle_states(void const *entries, short count);
|
||||
word network_actors_entry_size(void);
|
||||
/* (a client, in its tick where the host runs its actors) each actor's unit
|
||||
given the control the host last sent for it */
|
||||
void network_actors_drive(void);
|
||||
|
||||
/* ---------- prototypes/NETWORK_COOP.C */
|
||||
|
||||
void network_coop_new_game(void);
|
||||
/* after each tick, on the host and on a client */
|
||||
void network_coop_host_tick(void);
|
||||
void network_coop_client_tick(void);
|
||||
/* the message handlers, and their entry sizes */
|
||||
word network_coop_presentation_entry_size(void);
|
||||
void network_coop_handle_presentation(void const *entries, long host_time);
|
||||
word network_coop_event_entry_size(void);
|
||||
void network_coop_handle_events(void const *entries, short count);
|
||||
word network_coop_device_group_entry_size(void);
|
||||
void network_coop_handle_device_groups(void const *entries, short count);
|
||||
word network_coop_object_names_entry_size(void);
|
||||
void network_coop_handle_object_names(void const *entries);
|
||||
word network_coop_object_transform_entry_size(void);
|
||||
word network_coop_object_look_entry_size(void);
|
||||
void network_coop_handle_object_looks(void const *entries, short count);
|
||||
word network_coop_screen_effect_entry_size(void);
|
||||
word network_coop_device_state_entry_size(void);
|
||||
void network_coop_handle_device_states(void const *entries, short count);
|
||||
void network_coop_handle_screen_effect(void const *entries, short count);
|
||||
word network_actors_damage_entry_size(void);
|
||||
void network_actors_handle_damage(void const *entries, short count);
|
||||
void network_coop_handle_object_transforms(void const *entries, short count);
|
||||
word network_coop_skip_vote_entry_size(void);
|
||||
void network_coop_handle_skip_vote(long machine_index, void const *entries);
|
||||
/* game_sound.c: a sound the host's scripts played */
|
||||
enum
|
||||
{
|
||||
_coop_sound_impulse,
|
||||
_coop_sound_looping_start,
|
||||
_coop_sound_looping_stop,
|
||||
};
|
||||
void network_coop_note_sound(short kind, long definition_index, long object_index, real scale);
|
||||
|
||||
/* ---------- prototypes/NETWORK_OBJECTS.C */
|
||||
|
||||
void network_objects_new_game(void);
|
||||
/* after each tick */
|
||||
void network_objects_host_tick(void);
|
||||
word network_objects_damage_animation_entry_size(void);
|
||||
void network_objects_handle_damage_animations(void const *entries, short count);
|
||||
void network_objects_client_tick(void);
|
||||
/* (the host) a client has loaded the game and asks for the host's objects:
|
||||
again, having failed to make one of them */
|
||||
@@ -339,16 +236,10 @@ boolean network_objects_reconcile(long object_index, real_point3d const *positio
|
||||
real blend_distance);
|
||||
/* a unit in the vehicle's seat as the host has it (NONE: in none) */
|
||||
void network_objects_set_seat(long unit_index, long vehicle_index, short seat_index);
|
||||
/* (the host) the ticks between sends to a machine of a unit at position, by
|
||||
how near that machine's players are (network_actors.c) */
|
||||
short network_objects_send_period(long machine_index, real_point3d const *position);
|
||||
|
||||
/* ---------- prototypes/NETWORK_DAMAGE.C */
|
||||
|
||||
void network_damage_new_game(void);
|
||||
/* the host: a grenade a player's unit threw (units.c), whose damage that
|
||||
player's machine reports */
|
||||
void network_damage_note_grenade(long unit_index, short grenade_type);
|
||||
void network_damage_host_tick(void);
|
||||
void network_damage_client_tick(void);
|
||||
void network_damage_handle_events(void const *entries, short count);
|
||||
@@ -365,4 +256,10 @@ void network_vr_handle_poses(long machine_index, void const *entries, short coun
|
||||
void bots_new_game(void);
|
||||
void bots_tick(void);
|
||||
|
||||
/* the voice chat (network_voice.c) */
|
||||
void network_voice_new_game(void);
|
||||
void network_voice_tick(void);
|
||||
word network_voice_entry_size(void);
|
||||
void network_voice_handle(long machine_index, void const *message, word size);
|
||||
|
||||
#endif // __NETWORK_DISTRIBUTED_H
|
||||
@@ -50,7 +50,6 @@ same datum index (identifier and all), so that any message can name one:
|
||||
#include "objects/objects.h"
|
||||
#include "objects/damage.h"
|
||||
#include "objects/object_definitions.h"
|
||||
#include "models/model_animation_definitions.h"
|
||||
#include "models/model_definitions.h"
|
||||
#include "units/units.h"
|
||||
#include "units/biped_definitions.h"
|
||||
@@ -60,8 +59,6 @@ same datum index (identifier and all), so that any message can name one:
|
||||
#include "items/weapons.h"
|
||||
#include "items/weapon_definitions.h"
|
||||
#include "items/equipment_definitions.h"
|
||||
#include "cutscene/cinematics.h"
|
||||
#include "network_coop.h"
|
||||
#include "network_distributed.h"
|
||||
|
||||
#include <math.h>
|
||||
@@ -332,14 +329,11 @@ static struct
|
||||
} objects_host_inventories[MAXIMUM_TRACKED_OBJECTS];
|
||||
/* ... each client machine, found once a tick: where its players' living
|
||||
units are, those units, and the vehicles they drive (with the player's
|
||||
absolute index); and where every player's living unit is, for a machine
|
||||
with none (dead or joining, it watches one of them) */
|
||||
absolute index) */
|
||||
static struct
|
||||
{
|
||||
short count;
|
||||
long indices[HALO_PORT_MAXIMUM_NETWORK_MACHINES];
|
||||
short player_unit_count;
|
||||
real_point3d player_unit_origins[MAXIMUM_TRACKED_PLAYERS];
|
||||
struct
|
||||
{
|
||||
short unit_count;
|
||||
@@ -643,32 +637,6 @@ static void distributed_vector_clamp(
|
||||
}
|
||||
}
|
||||
|
||||
/* Whether the object is a vehicle no player drives in co-op (a Pelican on
|
||||
its flight path): nothing on a client steers it, so its copy is kept at rest
|
||||
there (no physics of its own) and moved only to where the host has it. In
|
||||
multiplayer an empty vehicle (one bailed out of, or tumbling) keeps its own
|
||||
physics between updates. */
|
||||
static boolean distributed_vehicle_unsteered(
|
||||
long object_index)
|
||||
{
|
||||
struct unit_datum *vehicle;
|
||||
|
||||
if (!network_coop_active() || object_get(object_index)->object.type != _object_type_vehicle)
|
||||
return FALSE;
|
||||
vehicle = unit_get(object_index);
|
||||
return vehicle->unit.driver_object_index == NONE ||
|
||||
unit_get(vehicle->unit.driver_object_index)->unit.player_index == NONE;
|
||||
}
|
||||
|
||||
/* Whether a client puts its copy exactly where the host has it, rather than
|
||||
closing on it: an unsteered vehicle, and anything in a co-op cutscene
|
||||
(where it keeps up with the host's camera). */
|
||||
static boolean distributed_object_follows_host(
|
||||
long object_index)
|
||||
{
|
||||
return (network_coop_active() && cinematic_in_progress()) || distributed_vehicle_unsteered(object_index);
|
||||
}
|
||||
|
||||
/* (the transform checked) */
|
||||
static void distributed_object_move(
|
||||
long object_index,
|
||||
@@ -690,10 +658,7 @@ static void distributed_object_move(
|
||||
object->object.translational_velocity = *velocity;
|
||||
if (angular_velocity)
|
||||
object->object.angular_velocity = *angular_velocity;
|
||||
if (!distributed_object_follows_host(object_index))
|
||||
render_interpolation_correct_object(object_index, &offset);
|
||||
if (distributed_vehicle_unsteered(object_index))
|
||||
SET_FLAG(object->object.flags, _object_at_rest_bit, TRUE);
|
||||
render_interpolation_correct_object(object_index, &offset);
|
||||
}
|
||||
|
||||
void network_objects_correct(
|
||||
@@ -738,33 +703,15 @@ boolean network_objects_reconcile(
|
||||
distributed_object_move(object_index, position, &valid_forward, &valid_up, velocity, angular_velocity);
|
||||
return TRUE;
|
||||
}
|
||||
/* Move halfway, and draw the move as a glide over the next few frames,
|
||||
as larger corrections are. Drawn as one step, a unit corrected every tick
|
||||
or two (a distant enemy) visibly hitched each time. */
|
||||
if (distributed_object_follows_host(object_index))
|
||||
blended = *position;
|
||||
else
|
||||
{
|
||||
blended.x = object->object.position.x + dx * 0.5f;
|
||||
blended.y = object->object.position.y + dy * 0.5f;
|
||||
blended.z = object->object.position.z + dz * 0.5f;
|
||||
}
|
||||
{
|
||||
real_vector3d offset;
|
||||
|
||||
offset.i = object->object.position.x - blended.x;
|
||||
offset.j = object->object.position.y - blended.y;
|
||||
offset.k = object->object.position.z - blended.z;
|
||||
object_set_position(object_index, &blended, &valid_forward, &valid_up);
|
||||
if (!distributed_object_follows_host(object_index))
|
||||
render_interpolation_correct_object(object_index, &offset);
|
||||
}
|
||||
/* (half of the way: the tick's snapshots draw it moving, no jump) */
|
||||
blended.x = object->object.position.x + dx * 0.5f;
|
||||
blended.y = object->object.position.y + dy * 0.5f;
|
||||
blended.z = object->object.position.z + dz * 0.5f;
|
||||
object_set_position(object_index, &blended, &valid_forward, &valid_up);
|
||||
if (velocity)
|
||||
object->object.translational_velocity = *velocity;
|
||||
if (angular_velocity)
|
||||
object->object.angular_velocity = *angular_velocity;
|
||||
if (distributed_vehicle_unsteered(object_index))
|
||||
SET_FLAG(object->object.flags, _object_at_rest_bit, TRUE);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -819,175 +766,6 @@ static long distributed_driven_vehicle(
|
||||
object_get(unit_index)->object.parent_object_index : NONE;
|
||||
}
|
||||
|
||||
/* ---------- damage animations
|
||||
|
||||
A unit's flinches and deaths are animations picked by where the damage came
|
||||
from and at random among their permutations, so a client replaying the
|
||||
host's damage could pick others. The host sends its picks (each in three
|
||||
ticks' messages, in case one is lost). A client plays the host's pick when
|
||||
it gets there first, or switches to it if its own has only just begun
|
||||
(unit_port_correct_damage_animation). */
|
||||
|
||||
enum
|
||||
{
|
||||
DAMAGE_ANIMATION_SENDS = 3,
|
||||
MAXIMUM_QUEUED_DAMAGE_ANIMATIONS = 32,
|
||||
/* how long a client keeps the host's pick for a hit it hasn't replayed yet */
|
||||
DAMAGE_ANIMATION_KEPT_TICKS = 2 * TICKS_PER_SECOND,
|
||||
};
|
||||
|
||||
/* type: units.h's _unit_damage_animation_soft_ping and the rest; number:
|
||||
the pick's, so a client tells a pick sent again from a new one */
|
||||
struct distributed_damage_animation
|
||||
{
|
||||
long unit_index;
|
||||
short animation_index;
|
||||
byte type;
|
||||
byte number;
|
||||
};
|
||||
|
||||
struct distributed_damage_animations_message
|
||||
{
|
||||
struct distributed_message_header header;
|
||||
struct distributed_damage_animation animations[MAXIMUM_QUEUED_DAMAGE_ANIMATIONS];
|
||||
};
|
||||
|
||||
/* host: the picks still to be sent */
|
||||
static struct
|
||||
{
|
||||
struct distributed_damage_animation animation;
|
||||
short sends;
|
||||
} host_damage_animations[MAXIMUM_QUEUED_DAMAGE_ANIMATIONS];
|
||||
static short host_damage_animation_count;
|
||||
static byte host_damage_animation_number;
|
||||
|
||||
/* client: the host's latest pick of each type for each unit, by absolute
|
||||
index: its number, and when it came (NONE once played) */
|
||||
static struct
|
||||
{
|
||||
long unit_index;
|
||||
short animation_index[NUMBER_OF_UNIT_DAMAGE_ANIMATIONS];
|
||||
byte number[NUMBER_OF_UNIT_DAMAGE_ANIMATIONS];
|
||||
long time[NUMBER_OF_UNIT_DAMAGE_ANIMATIONS];
|
||||
} client_damage_animations[MAXIMUM_TRACKED_OBJECTS];
|
||||
|
||||
/* whether the animation is one of the unit's graph */
|
||||
static boolean distributed_unit_has_animation(
|
||||
long unit_index,
|
||||
short animation_index)
|
||||
{
|
||||
long graph_index = unit_definition_get(unit_get(unit_index)->definition_index)->object.animation_graph.index;
|
||||
|
||||
return graph_index != NONE && animation_index >= 0 &&
|
||||
animation_index < animation_graph_definition_get(graph_index)->animations.count;
|
||||
}
|
||||
|
||||
/* units.c, as a unit flinches or dies: the host notes the animation it
|
||||
picked; a client takes the host's pick when it has one */
|
||||
short network_objects_damage_animation(
|
||||
long unit_index,
|
||||
short type,
|
||||
short animation_index)
|
||||
{
|
||||
short connection = game_connection();
|
||||
long absolute_index = DATUM_INDEX_TO_ABSOLUTE_INDEX(unit_index);
|
||||
|
||||
if (type < 0 || type >= NUMBER_OF_UNIT_DAMAGE_ANIMATIONS)
|
||||
return animation_index;
|
||||
if (connection == _game_connection_network_server && animation_index != NONE &&
|
||||
host_damage_animation_count < MAXIMUM_QUEUED_DAMAGE_ANIMATIONS)
|
||||
{
|
||||
struct distributed_damage_animation *animation =
|
||||
&host_damage_animations[host_damage_animation_count].animation;
|
||||
|
||||
animation->unit_index = unit_index;
|
||||
animation->animation_index = animation_index;
|
||||
animation->type = (byte)type;
|
||||
animation->number = ++host_damage_animation_number;
|
||||
host_damage_animations[host_damage_animation_count++].sends = DAMAGE_ANIMATION_SENDS;
|
||||
}
|
||||
else if (connection == _game_connection_network_client && absolute_index >= 0 &&
|
||||
absolute_index < MAXIMUM_TRACKED_OBJECTS && client_damage_animations[absolute_index].unit_index == unit_index)
|
||||
{
|
||||
long *time = &client_damage_animations[absolute_index].time[type];
|
||||
|
||||
if (*time != NONE && game_time_get() - *time < DAMAGE_ANIMATION_KEPT_TICKS)
|
||||
{
|
||||
*time = NONE;
|
||||
return client_damage_animations[absolute_index].animation_index[type];
|
||||
}
|
||||
}
|
||||
return animation_index;
|
||||
}
|
||||
|
||||
/* host, each tick: the picks still to be sent, to every client */
|
||||
static void distributed_host_send_damage_animations(
|
||||
void)
|
||||
{
|
||||
struct distributed_damage_animations_message message;
|
||||
short count = 0;
|
||||
short index;
|
||||
|
||||
for (index = 0; index < host_damage_animation_count; index++)
|
||||
{
|
||||
message.animations[count++] = host_damage_animations[index].animation;
|
||||
if (--host_damage_animations[index].sends <= 0)
|
||||
host_damage_animations[index--] = host_damage_animations[--host_damage_animation_count];
|
||||
}
|
||||
if (count > 0)
|
||||
{
|
||||
distributed_send(&message, _distributed_message_damage_animations, count,
|
||||
(word)(sizeof(message.header) + count * sizeof(message.animations[0])), _distributed_to_clients);
|
||||
}
|
||||
}
|
||||
|
||||
word network_objects_damage_animation_entry_size(
|
||||
void)
|
||||
{
|
||||
return sizeof(struct distributed_damage_animation);
|
||||
}
|
||||
|
||||
void network_objects_handle_damage_animations(
|
||||
void const *entries,
|
||||
short count)
|
||||
{
|
||||
struct distributed_damage_animation const *animations = entries;
|
||||
short index;
|
||||
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
struct distributed_damage_animation const *animation = &animations[index];
|
||||
long absolute_index = DATUM_INDEX_TO_ABSOLUTE_INDEX(animation->unit_index);
|
||||
short type;
|
||||
|
||||
if (!distributed_object_index_valid(animation->unit_index) || absolute_index >= MAXIMUM_TRACKED_OBJECTS ||
|
||||
animation->type >= NUMBER_OF_UNIT_DAMAGE_ANIMATIONS || !network_objects_client_has(animation->unit_index) ||
|
||||
!object_try_and_get_and_verify_type(animation->unit_index, _object_mask_unit) ||
|
||||
!distributed_unit_has_animation(animation->unit_index, animation->animation_index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (client_damage_animations[absolute_index].unit_index != animation->unit_index)
|
||||
{
|
||||
csmemset(&client_damage_animations[absolute_index], 0, sizeof(client_damage_animations[absolute_index]));
|
||||
client_damage_animations[absolute_index].unit_index = animation->unit_index;
|
||||
for (type = 0; type < NUMBER_OF_UNIT_DAMAGE_ANIMATIONS; type++)
|
||||
client_damage_animations[absolute_index].time[type] = NONE;
|
||||
}
|
||||
/* (a pick sent again) */
|
||||
else if (client_damage_animations[absolute_index].number[animation->type] == animation->number)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
client_damage_animations[absolute_index].number[animation->type] = animation->number;
|
||||
client_damage_animations[absolute_index].animation_index[animation->type] = animation->animation_index;
|
||||
/* (replayed here already: switched to the host's if it has only just begun) */
|
||||
client_damage_animations[absolute_index].time[animation->type] =
|
||||
unit_port_correct_damage_animation(animation->unit_index, animation->type, animation->animation_index) ?
|
||||
NONE : game_time_get();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- the host */
|
||||
|
||||
static void distributed_change_from_object(
|
||||
@@ -1099,30 +877,6 @@ static void distributed_host_update_objects(
|
||||
}
|
||||
}
|
||||
|
||||
/* The order a newly loaded client receives the host's objects in, most
|
||||
needed first: players' units (it may spectate them), the vehicles players
|
||||
and AI ride, other units, then everything else. */
|
||||
enum
|
||||
{
|
||||
_send_rank_player_unit,
|
||||
_send_rank_vehicle,
|
||||
_send_rank_unit,
|
||||
_send_rank_other,
|
||||
NUMBER_OF_SEND_RANKS
|
||||
};
|
||||
|
||||
static short distributed_object_send_rank(
|
||||
long object_index)
|
||||
{
|
||||
struct object_datum *object = object_get(object_index);
|
||||
|
||||
if (object->object.type == _object_type_vehicle)
|
||||
return _send_rank_vehicle;
|
||||
if (object_try_and_get_and_verify_type(object_index, _object_mask_unit))
|
||||
return unit_get(object_index)->unit.player_index != NONE ? _send_rank_player_unit : _send_rank_unit;
|
||||
return _send_rank_other;
|
||||
}
|
||||
|
||||
/* a client has loaded the game: every object the host has, to it alone,
|
||||
and word that that is all of them; asked again, only the word (they are on
|
||||
their way ahead of it), unless it failed to make one since it last asked
|
||||
@@ -1135,7 +889,6 @@ void network_objects_client_asked(
|
||||
short count = 0;
|
||||
short limit = MIN(MAXIMUM_ENTRIES_PER_MESSAGE, RELIABLE_ENTRIES(struct distributed_object_change));
|
||||
long absolute_index;
|
||||
short rank;
|
||||
long *player_list;
|
||||
|
||||
if (machine_index < 0 || machine_index >= HALO_PORT_MAXIMUM_NETWORK_MACHINES)
|
||||
@@ -1163,22 +916,14 @@ void network_objects_client_asked(
|
||||
/* (what every unit carries, with the next of them) */
|
||||
objects_host_inventories[absolute_index].checksum = 0;
|
||||
objects_host_inventories[absolute_index].weapons_checksum = 0;
|
||||
}
|
||||
for (rank = 0; rank < NUMBER_OF_SEND_RANKS; rank++)
|
||||
{
|
||||
for (absolute_index = 0; absolute_index < objects_host_told_count; absolute_index++)
|
||||
if (objects_host_told[absolute_index] == NONE)
|
||||
continue;
|
||||
distributed_change_from_object(objects_host_told[absolute_index], &message.changes[count]);
|
||||
if (++count == limit)
|
||||
{
|
||||
long object_index = objects_host_told[absolute_index];
|
||||
|
||||
if (object_index == NONE || distributed_object_send_rank(object_index) != rank)
|
||||
continue;
|
||||
distributed_change_from_object(object_index, &message.changes[count]);
|
||||
if (++count == limit)
|
||||
{
|
||||
distributed_send_to_machine_reliably(machine_index, &message, _distributed_message_object_changes, count,
|
||||
(word)(sizeof(message.header) + count * sizeof(struct distributed_object_change)));
|
||||
count = 0;
|
||||
}
|
||||
distributed_send_to_machine_reliably(machine_index, &message, _distributed_message_object_changes, count,
|
||||
(word)(sizeof(message.header) + count * sizeof(struct distributed_object_change)));
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
if (count)
|
||||
@@ -1223,24 +968,8 @@ static void distributed_host_find_viewers(
|
||||
{
|
||||
short machine_number;
|
||||
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
|
||||
objects_host_viewers.count = distributed_client_machines(objects_host_viewers.indices,
|
||||
HALO_PORT_MAXIMUM_NETWORK_MACHINES);
|
||||
objects_host_viewers.player_unit_count = 0;
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL &&
|
||||
objects_host_viewers.player_unit_count < MAXIMUM_TRACKED_PLAYERS)
|
||||
{
|
||||
long unit_index = distributed_living_unit(player);
|
||||
|
||||
if (unit_index != NONE)
|
||||
{
|
||||
object_get_origin(unit_index,
|
||||
&objects_host_viewers.player_unit_origins[objects_host_viewers.player_unit_count++]);
|
||||
}
|
||||
}
|
||||
for (machine_number = 0; machine_number < objects_host_viewers.count; machine_number++)
|
||||
{
|
||||
long *player_list = machine_get_player_list(objects_host_viewers.indices[machine_number]);
|
||||
@@ -1272,32 +1001,19 @@ static void distributed_host_find_viewers(
|
||||
}
|
||||
}
|
||||
|
||||
/* How often, in ticks, the host sends a moving object's position to a
|
||||
client machine, based on its distance from that machine's nearest player.
|
||||
A machine with no player in the world (dead or joining) spectates someone,
|
||||
so the distance is measured from every player instead. Sending such a
|
||||
machine every moving object every tick flooded it on campaign levels and
|
||||
threw everything it saw out of sync. */
|
||||
/* how often (ticks) the host sends that client machine an object where it
|
||||
is, moving: by its nearest player; every tick to one with none in the
|
||||
world (dead, it watches anyone) */
|
||||
static short distributed_host_object_period(
|
||||
short machine_number,
|
||||
real_point3d const *position)
|
||||
{
|
||||
short count = objects_host_viewers.machines[machine_number].unit_count;
|
||||
real_point3d const *origins = objects_host_viewers.machines[machine_number].origins;
|
||||
real nearest = -1.0f;
|
||||
short index;
|
||||
|
||||
if (!count)
|
||||
for (index = 0; index < objects_host_viewers.machines[machine_number].unit_count; index++)
|
||||
{
|
||||
count = objects_host_viewers.player_unit_count;
|
||||
origins = objects_host_viewers.player_unit_origins;
|
||||
}
|
||||
/* nobody is alive anywhere, so nothing needs to be sent often */
|
||||
if (!count)
|
||||
return MAXIMUM_OBJECT_PERIOD_TICKS;
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
real_point3d const *origin = &origins[index];
|
||||
real_point3d const *origin = &objects_host_viewers.machines[machine_number].origins[index];
|
||||
real dx = position->x - origin->x;
|
||||
real dy = position->y - origin->y;
|
||||
real dz = position->z - origin->z;
|
||||
@@ -1311,21 +1027,6 @@ static short distributed_host_object_period(
|
||||
nearest < FAR_OBJECT_DISTANCE * FAR_OBJECT_DISTANCE ? 3 : MAXIMUM_OBJECT_PERIOD_TICKS;
|
||||
}
|
||||
|
||||
/* ... for other units sent by a machine's index (network_actors.c) */
|
||||
short network_objects_send_period(
|
||||
long machine_index,
|
||||
real_point3d const *position)
|
||||
{
|
||||
short machine_number;
|
||||
|
||||
for (machine_number = 0; machine_number < objects_host_viewers.count; machine_number++)
|
||||
{
|
||||
if (objects_host_viewers.indices[machine_number] == machine_index)
|
||||
return distributed_host_object_period(machine_number, position);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* the kinds of states sent this tick */
|
||||
enum
|
||||
{
|
||||
@@ -1795,10 +1496,6 @@ void network_objects_apply_vehicle_predictions(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
/* (not from a co-op client still loading the host's BSP: the
|
||||
vehicle falls there with no ground under it) */
|
||||
if (!network_coop_player_has_structure_bsp(driver->unit.player_index))
|
||||
continue;
|
||||
dx = state->position.x - vehicle->object.position.x;
|
||||
dy = state->position.y - vehicle->object.position.y;
|
||||
dz = state->position.z - vehicle->object.position.z;
|
||||
@@ -1947,7 +1644,6 @@ void network_objects_host_tick(
|
||||
distributed_host_send_states();
|
||||
if (game_time_get() % INVENTORY_INTERVAL_TICKS == 0)
|
||||
distributed_host_send_inventories();
|
||||
distributed_host_send_damage_animations();
|
||||
}
|
||||
|
||||
/* ---------- a client */
|
||||
@@ -2142,19 +1838,6 @@ static void distributed_client_apply_change(
|
||||
struct object_datum *object = object_get(object_index);
|
||||
|
||||
csmemcpy(object->object.base_change_colors, change->change_colors, sizeof(object->object.base_change_colors));
|
||||
/* (and the colors drawn, which object_new chose from the tag: an AI unit's
|
||||
are its variant's, set after it was made, actors.c) */
|
||||
{
|
||||
short color_index;
|
||||
|
||||
for (color_index = 0; color_index < NUMBER_OF_OBJECT_CHANGE_COLORS; color_index++)
|
||||
{
|
||||
object->object.outgoing_change_colors[color_index].red = PIN(change->change_colors[color_index].red, 0.0f, 1.0f);
|
||||
object->object.outgoing_change_colors[color_index].green =
|
||||
PIN(change->change_colors[color_index].green, 0.0f, 1.0f);
|
||||
object->object.outgoing_change_colors[color_index].blue = PIN(change->change_colors[color_index].blue, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
/* (a permutation the model has, or none: the model's renderer takes it
|
||||
as it is) */
|
||||
{
|
||||
@@ -2409,20 +2092,6 @@ static void distributed_client_dead_biped(
|
||||
}
|
||||
}
|
||||
|
||||
/* client: a vehicle the host says is dead is destroyed here too, with its
|
||||
explosion. No killing blow comes for vehicles, only for bipeds. */
|
||||
static void distributed_client_dead_vehicle(
|
||||
long object_index)
|
||||
{
|
||||
struct unit_datum *vehicle = (struct unit_datum *)object_try_and_get_and_verify_type(object_index,
|
||||
_object_mask_vehicle);
|
||||
|
||||
if (!vehicle || TEST_FLAG(vehicle->object.damage_flags, _object_dead_bit))
|
||||
return;
|
||||
unit_kill_no_statistics(object_index);
|
||||
object_damage_update(object_index);
|
||||
}
|
||||
|
||||
void network_objects_handle_states(
|
||||
void const *entries,
|
||||
short count)
|
||||
@@ -2446,7 +2115,6 @@ void network_objects_handle_states(
|
||||
if (TEST_FLAG(state->flags, _distributed_object_dead_bit))
|
||||
{
|
||||
distributed_client_dead_biped(state->object_index);
|
||||
distributed_client_dead_vehicle(state->object_index);
|
||||
/* (a body its tag destroys at once is gone) */
|
||||
if (!object_try_and_get_and_verify_type(state->object_index, _object_mask_all))
|
||||
continue;
|
||||
@@ -2479,12 +2147,6 @@ void network_objects_handle_states(
|
||||
|
||||
if (blend_distance > 0.0f)
|
||||
blend_distance = REMOTE_VEHICLE_BLEND_DISTANCE;
|
||||
/* (one no player drives follows every move the host sends, however small) */
|
||||
if (distributed_vehicle_unsteered(state->object_index))
|
||||
{
|
||||
tolerance = 0.0f;
|
||||
angle_tolerance = 2.0f;
|
||||
}
|
||||
if (vehicle->unit.driver_object_index != NONE &&
|
||||
distributed_player_is_local(unit_get(vehicle->unit.driver_object_index)->unit.player_index))
|
||||
{
|
||||
@@ -2515,8 +2177,7 @@ void network_objects_handle_states(
|
||||
{
|
||||
distributed_count_correction();
|
||||
}
|
||||
SET_FLAG(object->object.flags, _object_at_rest_bit, TEST_FLAG(state->flags, _distributed_object_at_rest_bit) ||
|
||||
distributed_vehicle_unsteered(state->object_index));
|
||||
SET_FLAG(object->object.flags, _object_at_rest_bit, TEST_FLAG(state->flags, _distributed_object_at_rest_bit));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2937,51 +2598,6 @@ static void distributed_client_remove_own_objects(
|
||||
objects_client_new_object_count = 0;
|
||||
}
|
||||
|
||||
/* Client: keeps each AI-driven vehicle (such as a Pelican on its flight
|
||||
path) moving at the velocity the host last sent. These vehicles are held at
|
||||
rest here, and a distant one is only updated every few ticks, so otherwise
|
||||
it would stop between updates and jump on each one. */
|
||||
static void distributed_client_carry_unsteered_vehicles(
|
||||
void)
|
||||
{
|
||||
struct object_iterator iterator;
|
||||
struct object_datum *object;
|
||||
|
||||
object_iterator_new(&iterator, _object_mask_vehicle, 0);
|
||||
while ((object = object_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
real_vector3d const *velocity = &object->object.translational_velocity;
|
||||
real_vector3d axis = object->object.angular_velocity;
|
||||
real_vector3d forward = object->object.forward;
|
||||
real_vector3d up = object->object.up;
|
||||
real_point3d position;
|
||||
real angle;
|
||||
|
||||
if (object->object.parent_object_index != NONE || unit_get(iterator.index)->unit.driver_object_index == NONE ||
|
||||
!distributed_vehicle_unsteered(iterator.index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
angle = normalize3d(&axis);
|
||||
if (velocity->i == 0.0f && velocity->j == 0.0f && velocity->k == 0.0f && angle == 0.0f)
|
||||
continue;
|
||||
position.x = object->object.position.x + velocity->i;
|
||||
position.y = object->object.position.y + velocity->j;
|
||||
position.z = object->object.position.z + velocity->k;
|
||||
/* rotate the same way physics.c applies angular velocity */
|
||||
if (angle != 0.0f)
|
||||
{
|
||||
real_matrix4x3 rotation;
|
||||
|
||||
matrix4x3_rotation_from_axis_and_angle(&rotation, &axis, (real)sin(angle), (real)cos(angle));
|
||||
matrix4x3_transform_vector(&rotation, &object->object.forward, &forward);
|
||||
matrix4x3_transform_vector(&rotation, &object->object.up, &up);
|
||||
}
|
||||
if (distributed_transform_valid(&position, &forward, &up, NULL, NULL, &forward, &up))
|
||||
object_set_position(iterator.index, &position, &forward, &up);
|
||||
}
|
||||
}
|
||||
|
||||
void network_objects_client_tick(
|
||||
void)
|
||||
{
|
||||
@@ -3009,7 +2625,6 @@ void network_objects_client_tick(
|
||||
distributed_client_ready_picked_up_weapons();
|
||||
distributed_client_note_own_inventories();
|
||||
distributed_client_send_vehicles();
|
||||
distributed_client_carry_unsteered_vehicles();
|
||||
/* (who it is, as its Discord told it: once its ready went, which makes
|
||||
it a machine the host takes messages of) */
|
||||
if (objects_client_ready_time != NONE)
|
||||
@@ -3027,8 +2642,6 @@ void network_objects_new_game(
|
||||
short index;
|
||||
|
||||
csmemset(objects_host_inventories, 0, sizeof(objects_host_inventories));
|
||||
host_damage_animation_count = 0;
|
||||
csmemset(client_damage_animations, 0, sizeof(client_damage_animations));
|
||||
for (absolute_index = 0; absolute_index < MAXIMUM_TRACKED_OBJECTS; absolute_index++)
|
||||
{
|
||||
objects_host_told[absolute_index] = NONE;
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
NETWORK_VOICE.C
|
||||
|
||||
The voice chat (the VR port's): an extension of the distributed netcode
|
||||
(port/linux/NETCODE.md, network_distributed.c), as the VR players' hands
|
||||
are (network_vr.c).
|
||||
|
||||
- Every tick, a machine sends what its microphone heard of its player's
|
||||
speech (halo_voice.h: Opus packets of 20 ms, only while they speak) in
|
||||
_distributed_message_voice (unreliable): a client to the host, which
|
||||
plays it and passes it on to its other clients; the host its own to
|
||||
every client.
|
||||
- A machine plays each speaker's voice from where they are: a teammate
|
||||
(in a game of teams) over the radio, as loud anywhere and only a little
|
||||
to the side they are on; anyone else as near as they are (no further off
|
||||
than VOICE_RANGE), from their side. A speaker with no unit (dead) is
|
||||
heard over the radio by their team, and not by the rest.
|
||||
|
||||
A machine that does not know the message (a build without this, of the same
|
||||
network version) drops it, as it drops any type past its own.
|
||||
*/
|
||||
|
||||
#include "cseries.h"
|
||||
#include "game/game.h"
|
||||
#include "game/game_engine.h"
|
||||
#include "game/players.h"
|
||||
#include "units/units.h"
|
||||
#include "network_distributed.h"
|
||||
#include "halo_voice.h"
|
||||
#include "halo_vr.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
void platform_log(char const *format, ...);
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
enum
|
||||
{
|
||||
/* an entry: the speaker, its sequence number and the packet's length,
|
||||
and the packet */
|
||||
VOICE_ENTRY_HEADER = 4,
|
||||
VOICE_MAXIMUM_PACKET = 200,
|
||||
VOICE_MAXIMUM_TAKEN = 1024,
|
||||
};
|
||||
|
||||
/* how far off someone not on the radio is heard (world units: 60 metres) */
|
||||
#define VOICE_RANGE 20.0f
|
||||
/* how far to the side a voice is put: on the radio, and near */
|
||||
#define VOICE_RADIO_PAN 0.3f
|
||||
#define VOICE_NEAR_PAN 0.8f
|
||||
|
||||
/* ---------- globals */
|
||||
|
||||
static struct
|
||||
{
|
||||
boolean active;
|
||||
word sequence;
|
||||
long sent;
|
||||
long played;
|
||||
long log_time;
|
||||
} network_voice;
|
||||
|
||||
/* ---------- private code */
|
||||
|
||||
static boolean network_voice_in_network_game(
|
||||
void)
|
||||
{
|
||||
short connection = game_connection();
|
||||
|
||||
return (connection == _game_connection_network_server || connection == _game_connection_network_client) &&
|
||||
game_in_progress();
|
||||
}
|
||||
|
||||
/* where a player's head is, and whether they have a unit */
|
||||
static boolean network_voice_player_head(
|
||||
struct player_datum const *player,
|
||||
real_point3d *head)
|
||||
{
|
||||
long unit_index = distributed_living_unit(player);
|
||||
|
||||
if (unit_index == NONE)
|
||||
return FALSE;
|
||||
unit_get_camera_position(unit_index, head);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* the gains a speaker is heard at here (FALSE: not heard) */
|
||||
static boolean network_voice_gains(
|
||||
struct player_datum const *speaker,
|
||||
real *left,
|
||||
real *right)
|
||||
{
|
||||
long listener_index = local_player_get_player_index(0);
|
||||
struct player_datum *listener = listener_index != NONE ? player_try_and_get(listener_index) : NULL;
|
||||
boolean teammate = listener && game_engine_has_teams() && listener->team_index == speaker->team_index;
|
||||
real_point3d speaker_head;
|
||||
real_point3d listener_head;
|
||||
real gain = 1.0f;
|
||||
real pan = 0.0f;
|
||||
|
||||
if (!listener)
|
||||
return FALSE;
|
||||
if (!network_voice_player_head(speaker, &speaker_head))
|
||||
{
|
||||
/* (dead: on the radio, to the team) */
|
||||
if (!teammate)
|
||||
return FALSE;
|
||||
*left = *right = 1.0f;
|
||||
return TRUE;
|
||||
}
|
||||
if (network_voice_player_head(listener, &listener_head))
|
||||
{
|
||||
real dx = speaker_head.x - listener_head.x;
|
||||
real dy = speaker_head.y - listener_head.y;
|
||||
real dz = speaker_head.z - listener_head.z;
|
||||
real distance = (real)sqrt(dx * dx + dy * dy + dz * dz);
|
||||
real flat = (real)sqrt(dx * dx + dy * dy);
|
||||
float look[3];
|
||||
real forward_x;
|
||||
real forward_y;
|
||||
real length;
|
||||
struct unit_datum *unit = unit_try_and_get(distributed_living_unit(listener));
|
||||
|
||||
/* (where the listener looks: the head, in VR) */
|
||||
if (halo_vr_look_vector(0, look))
|
||||
{
|
||||
forward_x = look[0];
|
||||
forward_y = look[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
forward_x = unit ? unit->unit.looking_vector.i : 1.0f;
|
||||
forward_y = unit ? unit->unit.looking_vector.j : 0.0f;
|
||||
}
|
||||
length = (real)sqrt(forward_x * forward_x + forward_y * forward_y);
|
||||
if (length > 0.0001f && flat > 0.0001f)
|
||||
{
|
||||
/* (the right: forward turned clockwise) */
|
||||
pan = (dx * forward_y - dy * forward_x) / (flat * length);
|
||||
}
|
||||
if (!teammate)
|
||||
{
|
||||
gain = 1.0f - distance / VOICE_RANGE;
|
||||
if (gain <= 0.0f)
|
||||
return FALSE;
|
||||
gain *= gain;
|
||||
}
|
||||
pan *= teammate ? VOICE_RADIO_PAN : VOICE_NEAR_PAN;
|
||||
}
|
||||
else if (!teammate)
|
||||
{
|
||||
/* (the listener dead: the others as from where the body is not:
|
||||
not at all) */
|
||||
return FALSE;
|
||||
}
|
||||
*left = gain * (pan > 0.0f ? 1.0f - pan : 1.0f);
|
||||
*right = gain * (pan < 0.0f ? 1.0f + pan : 1.0f);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void network_voice_play_entries(
|
||||
byte const *entries,
|
||||
byte const *end,
|
||||
short count,
|
||||
long machine_index)
|
||||
{
|
||||
short index;
|
||||
|
||||
for (index = 0; index < count && entries + VOICE_ENTRY_HEADER <= end; index++)
|
||||
{
|
||||
byte player_byte = entries[0];
|
||||
word sequence = (word)(entries[1] | entries[2] << 8);
|
||||
short length = entries[3];
|
||||
long player_index;
|
||||
struct player_datum *player;
|
||||
real left;
|
||||
real right;
|
||||
|
||||
if (length <= 0 || entries + VOICE_ENTRY_HEADER + length > end)
|
||||
break;
|
||||
player_index = distributed_player_from_byte(player_byte);
|
||||
player = player_index != NONE ? player_try_and_get(player_index) : NULL;
|
||||
/* (the host: only a client's own players') */
|
||||
if (player && (machine_index == NONE || distributed_machine_has_player(machine_index, player_byte)) &&
|
||||
player->local_player_index == NONE && network_voice_gains(player, &left, &right))
|
||||
{
|
||||
halo_voice_play(player_byte, sequence, entries + VOICE_ENTRY_HEADER, length, left, right);
|
||||
network_voice.played++;
|
||||
}
|
||||
entries += VOICE_ENTRY_HEADER + length;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- public code */
|
||||
|
||||
void network_voice_new_game(
|
||||
void)
|
||||
{
|
||||
network_voice.sequence = 0;
|
||||
}
|
||||
|
||||
word network_voice_entry_size(
|
||||
void)
|
||||
{
|
||||
return VOICE_ENTRY_HEADER;
|
||||
}
|
||||
|
||||
/* after each tick (network_distributed_tick): this machine's speech sent */
|
||||
void network_voice_tick(
|
||||
void)
|
||||
{
|
||||
struct
|
||||
{
|
||||
struct distributed_message_header header;
|
||||
byte entries[VOICE_MAXIMUM_TAKEN + 64];
|
||||
} message;
|
||||
byte taken[VOICE_MAXIMUM_TAKEN];
|
||||
long player_index = local_player_get_player_index(0);
|
||||
long size;
|
||||
long offset = 0;
|
||||
long used = 0;
|
||||
short count = 0;
|
||||
|
||||
if (!network_voice_in_network_game() || !halo_voice_enabled())
|
||||
{
|
||||
if (network_voice.active)
|
||||
{
|
||||
network_voice.active = FALSE;
|
||||
halo_voice_end();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!network_voice.active)
|
||||
{
|
||||
network_voice.active = TRUE;
|
||||
halo_voice_begin();
|
||||
}
|
||||
if (game_time_get() - network_voice.log_time >= 150 && (network_voice.sent || network_voice.played))
|
||||
{
|
||||
platform_log("voice: sent %ld, played %ld packets (in 150 ticks)", network_voice.sent, network_voice.played);
|
||||
network_voice.sent = network_voice.played = 0;
|
||||
network_voice.log_time = game_time_get();
|
||||
}
|
||||
size = halo_voice_take(taken, sizeof(taken));
|
||||
if (player_index == NONE)
|
||||
return;
|
||||
while (offset < size)
|
||||
{
|
||||
short length = taken[offset];
|
||||
|
||||
if (length <= 0 || offset + 1 + length > size || length > VOICE_MAXIMUM_PACKET)
|
||||
break;
|
||||
if (used + VOICE_ENTRY_HEADER + length > (long)sizeof(message.entries))
|
||||
break;
|
||||
message.entries[used] = distributed_player_to_byte(player_index);
|
||||
message.entries[used + 1] = (byte)(network_voice.sequence & 0xFF);
|
||||
message.entries[used + 2] = (byte)(network_voice.sequence >> 8);
|
||||
message.entries[used + 3] = (byte)length;
|
||||
csmemcpy(message.entries + used + VOICE_ENTRY_HEADER, taken + offset + 1, length);
|
||||
used += VOICE_ENTRY_HEADER + length;
|
||||
offset += 1 + length;
|
||||
network_voice.sequence++;
|
||||
count++;
|
||||
}
|
||||
if (!count)
|
||||
return;
|
||||
network_voice.sent += count;
|
||||
distributed_send(&message, _distributed_message_voice, count, (word)(sizeof(message.header) + used),
|
||||
game_connection() == _game_connection_network_server ? _distributed_to_clients : _distributed_to_host);
|
||||
}
|
||||
|
||||
/* a machine's speech: on the host a client's, played and passed on to the
|
||||
other clients; on a client the host's of any other */
|
||||
void network_voice_handle(
|
||||
long machine_index,
|
||||
void const *message,
|
||||
word size)
|
||||
{
|
||||
struct distributed_message_header header;
|
||||
byte const *entries = (byte const *)message + sizeof(header);
|
||||
byte const *end = (byte const *)message + size;
|
||||
|
||||
if (!halo_voice_enabled() || !network_voice.active)
|
||||
return;
|
||||
csmemcpy(&header, message, sizeof(header));
|
||||
network_voice_play_entries(entries, end, header.count, machine_index);
|
||||
if (machine_index != NONE)
|
||||
{
|
||||
long machine_indices[HALO_PORT_MAXIMUM_NETWORK_MACHINES];
|
||||
short machine_count = distributed_client_machines(machine_indices, HALO_PORT_MAXIMUM_NETWORK_MACHINES);
|
||||
short number;
|
||||
struct
|
||||
{
|
||||
struct distributed_message_header header;
|
||||
byte entries[VOICE_MAXIMUM_TAKEN + 64];
|
||||
} relayed;
|
||||
word entries_size = (word)(end - entries);
|
||||
|
||||
byte const *entry = entries;
|
||||
short index;
|
||||
|
||||
/* (only the sender's own players' speech is passed on, whole) */
|
||||
if (entries_size < VOICE_ENTRY_HEADER || entries_size > sizeof(relayed.entries))
|
||||
return;
|
||||
for (index = 0; index < header.count; index++)
|
||||
{
|
||||
if (entry + VOICE_ENTRY_HEADER > end || entry[3] == 0 || entry + VOICE_ENTRY_HEADER + entry[3] > end ||
|
||||
!distributed_machine_has_player(machine_index, entry[0]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
entry += VOICE_ENTRY_HEADER + entry[3];
|
||||
}
|
||||
if (entry != end)
|
||||
return;
|
||||
for (number = 0; number < machine_count; number++)
|
||||
{
|
||||
if (machine_indices[number] == machine_index)
|
||||
continue;
|
||||
csmemcpy(relayed.entries, entries, entries_size);
|
||||
distributed_send_to_machine(machine_indices[number], &relayed, _distributed_message_voice, header.count,
|
||||
(word)(sizeof(relayed.header) + entries_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* the game includes <StdDef.h>; Linux file names are case sensitive. On a
|
||||
case-insensitive file system (macOS) a plain <stddef.h> would find this file
|
||||
again; include_next goes to the real one, as the other shims do. */
|
||||
/* the game includes <StdDef.h>; Linux file names are case sensitive.
|
||||
#include_next, since on a case-insensitive file system (macOS) this file
|
||||
is also what <stddef.h> finds */
|
||||
#include_next <stddef.h>
|
||||
@@ -24,27 +24,15 @@ state above the tag cache (which ends at 0x819A6000), inside the Xbox memory
|
||||
window (0x80000000-0x88000000, port/linux/src/platform.h) and below everything
|
||||
the window hands out top-down (texture and sound caches, Direct3D resources).
|
||||
|
||||
The CPU part holds about 17.2 MB of pools at the sizes below (the Xbox pools
|
||||
The CPU part holds about 13.6 MB of pools at the sizes below (the Xbox pools
|
||||
fill 3,165,260 of its 0x305000 bytes); the GPU part holds only the decal
|
||||
vertices, as on the Xbox. A change to a pool's size changes the game state's
|
||||
layout: saved games of builds before it no longer load. */
|
||||
vertices, as on the Xbox. */
|
||||
|
||||
#define HALO_PORT_GAME_STATE_BASE_ADDRESS 0x81A00000 /* (0x80061000) */
|
||||
#define HALO_PORT_GAME_STATE_CPU_SIZE 0x13C0000 /* (0x305000) */
|
||||
#define HALO_PORT_GAME_STATE_CPU_SIZE 0xFC0000 /* (0x305000) */
|
||||
#define HALO_PORT_GAME_STATE_GPU_SIZE 0x40000 /* (0x40000) */
|
||||
#define HALO_PORT_GAME_STATE_SIZE (HALO_PORT_GAME_STATE_CPU_SIZE+HALO_PORT_GAME_STATE_GPU_SIZE)
|
||||
|
||||
/* ---------- AI
|
||||
|
||||
Network co-op adds enemies for its players (port/linux/game/coop_enemies.c):
|
||||
the actors, and their knowledge of the units about them (props: with many
|
||||
players, more each), have room for several times a level's own. */
|
||||
|
||||
#define HALO_PORT_MAXIMUM_ACTORS 1024 /* (256) */
|
||||
#define HALO_PORT_MAXIMUM_PROPS 8192 /* (768) */
|
||||
#define HALO_PORT_MAXIMUM_SWARMS 128 /* (32) */
|
||||
#define HALO_PORT_MAXIMUM_SWARM_COMPONENTS 1024 /* (256) */
|
||||
|
||||
/* ---------- objects */
|
||||
|
||||
#define HALO_PORT_MAXIMUM_OBJECTS_PER_MAP 8192 /* (2048) */
|
||||
|
||||
@@ -66,7 +66,7 @@ is newer (network_client_manager.c). A host advertises it, with its netcode,
|
||||
in its game's advertisement's reserved bytes (network_server_message_handler.c),
|
||||
which hosts built before there was a version send as zeros: version 0.
|
||||
Raise it with any change to what the machines send each other. */
|
||||
#define HALO_PORT_NETWORK_VERSION 16
|
||||
#define HALO_PORT_NETWORK_VERSION 11
|
||||
/* ... the advertisement's reserved bytes: the version (a little-endian word),
|
||||
then flags */
|
||||
#define HALO_PORT_ADVERTISED_VERSION_OFFSET 0
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
HALO_VOICE.H
|
||||
|
||||
The voice chat's sound for the game (port/linux/src/voice.c: on Android the
|
||||
host's microphone, Opus and mixer, port/android/host/host_voice.c; elsewhere
|
||||
nothing). port/linux/game/network_voice.c sends and receives it.
|
||||
*/
|
||||
|
||||
#ifndef __HALO_VOICE_H
|
||||
#define __HALO_VOICE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* whether the voice chat is on (voice.enabled) */
|
||||
int halo_voice_enabled(void);
|
||||
/* starts playing voices, and the microphone if voice.microphone; stops */
|
||||
int halo_voice_begin(void);
|
||||
void halo_voice_end(void);
|
||||
/* the microphone's speech since asked: Opus packets, each after a byte of
|
||||
its length; returns the bytes */
|
||||
int halo_voice_take(void *buffer, int size);
|
||||
/* a speaker's packet, played at these gains (left, right) */
|
||||
void halo_voice_play(int speaker, int sequence, const void *data, int length, float left, float right);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -61,19 +61,17 @@ struct xgpu_capabilities xgpu_capabilities;
|
||||
The Xbox screen is 640x480. The native ports can draw a wider one: 480
|
||||
lines, and as many columns as the display's shape gives. On Android that is
|
||||
display.screen_width (port_config.c; 640 keeps 4:3); on the desktop, the
|
||||
shape of the window (of the display while the game is fullscreen, or of
|
||||
display.resolution), and 640 where display.resolution_scaling is "original".
|
||||
The game's camera derives its horizontal field of view from the viewport, so
|
||||
the 3D view simply widens. The menus and full-screen overlays are laid out
|
||||
for 640 columns; while they draw (halo_screen_ui_offset), everything shifts
|
||||
right to center them.
|
||||
display's shape while the game is fullscreen, and 640 in a window. The
|
||||
game's camera derives its horizontal field of view from the viewport, so the
|
||||
3D view simply widens. The menus and full-screen overlays are laid out for
|
||||
640 columns; while they draw (halo_screen_ui_offset), everything shifts right
|
||||
to center them.
|
||||
|
||||
The desktop also draws at that resolution (platform_screen_mode): render
|
||||
Fullscreen on the desktop also draws at the display's resolution: render
|
||||
targets the size of the screen get that many pixels (screen_scale), and
|
||||
viewports, clears and visibility counts are scaled to match, so the game
|
||||
still works in its 480 lines; "original" draws 640x480, scaled up at
|
||||
presentation. The width and the scale change only between frames, after one
|
||||
is presented (halo_screen_commit). */
|
||||
still works in its 480 lines. The width and the scale change only between
|
||||
frames, after one is presented (halo_screen_commit). */
|
||||
|
||||
#define SCREEN_HEIGHT 480
|
||||
#define SCREEN_MAXIMUM_WIDTH 1920
|
||||
@@ -1366,6 +1364,25 @@ HRESULT WINAPI Direct3D_CreateDevice(UINT adapter, D3DDEVTYPE device_type, void
|
||||
|
||||
/* ---------- the menus' pointer */
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
/* the controller pointing at the menus' screen in VR (halo_vr_menu_pointer) */
|
||||
int halo_ui_pointer_update(int menus_active, struct halo_ui_pointer *pointer)
|
||||
{
|
||||
int x, y, click_x, click_y, moved, clicks;
|
||||
|
||||
platform_menus_set_active(menus_active != 0);
|
||||
if (!menus_active || !halo_vr_menu_pointer(&x, &y, &click_x, &click_y, &moved, &clicks))
|
||||
return 0;
|
||||
memset(pointer, 0, sizeof(*pointer));
|
||||
pointer->x = (short)(x - (halo_screen_width() - 640) / 2);
|
||||
pointer->y = (short)y;
|
||||
pointer->click_x = (short)(click_x - (halo_screen_width() - 640) / 2);
|
||||
pointer->click_y = (short)click_y;
|
||||
pointer->moved = (unsigned char)(moved != 0);
|
||||
pointer->left_clicks = (unsigned char)(clicks < 255 ? clicks : 255);
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
/* a point in the window, as SDL reports it, in the menus' coordinates: the
|
||||
inverse of the letterboxed display blit at presentation, the screen's
|
||||
width and the menus' centering (halo_screen_ui_offset) */
|
||||
@@ -1397,47 +1414,6 @@ static void ui_point_from_window(float window_x, float window_y, short *x, short
|
||||
*y = (short)floorf(screen_y);
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
BOOL platform_flat_mouse(float *x, float *y);
|
||||
|
||||
/* the controller pointing at the menus' screen in VR (halo_vr_menu_pointer);
|
||||
flat (the emulator), the mouse */
|
||||
int halo_ui_pointer_update(int menus_active, struct halo_ui_pointer *pointer)
|
||||
{
|
||||
int x, y, click_x, click_y, moved, clicks;
|
||||
|
||||
platform_menus_set_active(menus_active != 0);
|
||||
if (menus_active && !halo_vr_active() && device.gl_ready)
|
||||
{
|
||||
static BOOL was_down;
|
||||
static short last_x, last_y;
|
||||
float window_x, window_y;
|
||||
BOOL down = platform_flat_mouse(&window_x, &window_y);
|
||||
|
||||
memset(pointer, 0, sizeof(*pointer));
|
||||
ui_point_from_window(window_x, window_y, &pointer->x, &pointer->y);
|
||||
pointer->click_x = pointer->x;
|
||||
pointer->click_y = pointer->y;
|
||||
pointer->moved = pointer->x != last_x || pointer->y != last_y;
|
||||
pointer->left_clicks = (unsigned char)(down && !was_down);
|
||||
last_x = pointer->x;
|
||||
last_y = pointer->y;
|
||||
was_down = down;
|
||||
return 1;
|
||||
}
|
||||
if (!menus_active || !halo_vr_menu_pointer(&x, &y, &click_x, &click_y, &moved, &clicks))
|
||||
return 0;
|
||||
memset(pointer, 0, sizeof(*pointer));
|
||||
pointer->x = (short)(x - (halo_screen_width() - 640) / 2);
|
||||
pointer->y = (short)y;
|
||||
pointer->click_x = (short)(click_x - (halo_screen_width() - 640) / 2);
|
||||
pointer->click_y = (short)click_y;
|
||||
pointer->moved = (unsigned char)(moved != 0);
|
||||
pointer->left_clicks = (unsigned char)(clicks < 255 ? clicks : 255);
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
|
||||
int halo_ui_pointer_update(int menus_active, struct halo_ui_pointer *pointer)
|
||||
{
|
||||
struct platform_ui_pointer state;
|
||||
@@ -2487,10 +2463,6 @@ static GLuint mip_composite_get(const struct xgpu_texture_description *descripti
|
||||
|
||||
static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale[4][4])
|
||||
{
|
||||
/* Bind only after resolving every stage, since texture uploads can
|
||||
overwrite the active unit's binding. */
|
||||
GLenum gl_targets[D3DTSS_MAXSTAGES];
|
||||
GLuint gl_textures[D3DTSS_MAXSTAGES];
|
||||
int stage;
|
||||
|
||||
for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++)
|
||||
@@ -2502,8 +2474,7 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale
|
||||
texture_scale[stage][2] = texture_scale[stage][3] = 1.0f;
|
||||
if (!texture || !texture->Data || mode == 0 || mode == 0x04 || mode == 0x05 || mode == 0x11)
|
||||
{
|
||||
gl_targets[stage] = GL_TEXTURE_2D;
|
||||
gl_textures[stage] = 0;
|
||||
state_texture(stage, GL_TEXTURE_2D, 0);
|
||||
key->sampler_type[stage] = mode == 0x11 ? _xgpu_sampler_2d : _xgpu_sampler_none;
|
||||
continue;
|
||||
}
|
||||
@@ -2559,8 +2530,7 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale
|
||||
texture_scale[stage][1] = 1.0f / (float)description.height;
|
||||
}
|
||||
}
|
||||
gl_targets[stage] = gl_target;
|
||||
gl_textures[stage] = gl_texture;
|
||||
state_texture(stage, gl_target, gl_texture);
|
||||
state_sampler(stage, device.samplers[stage]);
|
||||
configure_sampler(stage, description.levels > 1, description.hires);
|
||||
if (stage == 0)
|
||||
@@ -2569,8 +2539,6 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale
|
||||
gl_target == GL_TEXTURE_3D ? _xgpu_sampler_3d : _xgpu_sampler_2d;
|
||||
}
|
||||
}
|
||||
for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++)
|
||||
state_texture(stage, gl_targets[stage], gl_textures[stage]);
|
||||
}
|
||||
|
||||
static GLenum stencil_operation(DWORD operation)
|
||||
|
||||
@@ -249,7 +249,9 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru
|
||||
"\tvec4 oFog = vec4(1.0), oPts = vec4(point_size), oUnused = vec4(0.0);\n"
|
||||
"\tint a0 = 0;\n"
|
||||
"\tvec4 A, B, C, mac, ilu;\n");
|
||||
#ifdef HALO_ANDROID
|
||||
xgpu_text_append(&text, "\tvec4 clip_position = vec4(0.0);\n\tbool clip_captured = false;\n");
|
||||
#endif
|
||||
|
||||
for (index = 0; index < instruction_count; index++)
|
||||
{
|
||||
@@ -301,6 +303,7 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru
|
||||
case _ilu_lit: xgpu_text_append(&text, "\tilu = nv2a_lit(C);\n"); break;
|
||||
default: xgpu_text_append(&text, "\tilu = vec4(0.0);\n"); break;
|
||||
}
|
||||
#ifdef HALO_ANDROID
|
||||
/* the screen-space conversion takes the reciprocal of the clip-space
|
||||
position's w (rcc of r12.w); keep the position it converts */
|
||||
if (ilu == _ilu_rcc && field(instruction, 3, 28, 2) == _mux_temporary &&
|
||||
@@ -308,6 +311,7 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru
|
||||
{
|
||||
xgpu_text_append(&text, "\tclip_position = oPos;\n\tclip_captured = true;\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* results are written only after both units have read their inputs */
|
||||
if (mac == _mac_arl)
|
||||
@@ -353,33 +357,21 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru
|
||||
/* Direct3D 8 puts pixel centres on integer screen coordinates (the
|
||||
game offsets its screen-space quads by -0.5 to match), OpenGL on
|
||||
half-integers */
|
||||
#ifdef HALO_ANDROID
|
||||
/* The conversion is screen = clip * c[-38] * rcc(w) + c[-37]; undoing
|
||||
it by multiplying by w again is lossy near the camera plane, where
|
||||
rcc clamps and 1/w rounds differently on each GPU (Mali put vertices
|
||||
of the first-person weapon at the vanishing point). Where the clip
|
||||
position was kept, the same result is computed without dividing. */
|
||||
"\tvec4 position;\n"
|
||||
"\tif (clip_captured)\n"
|
||||
"\t\tposition = vec4((clip_position.xyz * c[%d].xyz + (c[%d].xyz + vec3(0.5 + screen_offset, 0.5, 0.0)\n"
|
||||
"\t\tgl_Position = vec4((clip_position.xyz * c[%d].xyz + (c[%d].xyz + vec3(0.5 + screen_offset, 0.5, 0.0)\n"
|
||||
"\t\t\t- viewport_offset.xyz) * clip_position.w) / scale, clip_position.w);\n"
|
||||
"\telse\n"
|
||||
"\t{\n"
|
||||
"\t\tvec3 ndc = (vec3(oPos.xy + vec2(0.5 + screen_offset, 0.5), oPos.z) - viewport_offset.xyz) / scale;\n"
|
||||
"\t\tposition = vec4(ndc * oPos.w, oPos.w);\n"
|
||||
"\t}\n"
|
||||
/* A position whose w is zero, or is not a number, is the clip-space
|
||||
origin: the screen conversion's reciprocal is clamped rather than
|
||||
infinite, so a large position times a w of zero is exactly zero, and
|
||||
the origin is inside the frustum. Nothing then clips the triangle
|
||||
away and OpenGL divides zero by zero there: the vertex lands on the
|
||||
middle of the screen and the triangle is drawn out to it from the
|
||||
first-person weapon, whose pose follows the camera and so reaches the
|
||||
camera plane. The divide on the Xbox sends such a vertex to infinity
|
||||
and the clipper takes the triangle; put it behind the camera instead,
|
||||
which the clipper also takes. */
|
||||
"\tif (!(abs(position.w) > 0.0))\n"
|
||||
"\t\tposition = vec4(0.0, 0.0, 0.0, -1.0);\n"
|
||||
"\tgl_Position = position;\n"
|
||||
"\t\tgl_Position = vec4((vec3(oPos.xy + vec2(0.5 + screen_offset, 0.5), oPos.z) - viewport_offset.xyz) / scale * oPos.w, oPos.w);\n"
|
||||
#else
|
||||
"\tvec3 ndc = (vec3(oPos.xy + vec2(0.5 + screen_offset, 0.5), oPos.z) - viewport_offset.xyz) / scale;\n"
|
||||
"\tgl_Position = vec4(ndc * oPos.w, oPos.w);\n"
|
||||
#endif
|
||||
#ifdef HALO_ANDROID
|
||||
/* what glClipControl(GL_UPPER_LEFT, GL_ZERO_TO_ONE) does on desktop
|
||||
GL: rows from the top, depth 0..1 */
|
||||
@@ -397,16 +389,16 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru
|
||||
"\txT2 = oT2;\n"
|
||||
"\txT3 = oT3;\n"
|
||||
"\txFog = oFog.x;\n"
|
||||
"}\n",
|
||||
XGPU_VERTEX_CONSTANT_BIAS - 38, XGPU_VERTEX_CONSTANT_BIAS - 37
|
||||
"}\n"
|
||||
#ifdef HALO_ANDROID
|
||||
, XGPU_VERTEX_CONSTANT_BIAS - 38, XGPU_VERTEX_CONSTANT_BIAS - 37,
|
||||
/* (what is drawn in screen space, which kept no clip position, is
|
||||
taken to each view as though infinitely far: a lens flare's sun
|
||||
lands where the sky's does) */
|
||||
/* (what the game projects itself, a sprite or a lens flare's
|
||||
test, comes as a screen position with w 1 and its depth in z: its
|
||||
eye's offset is by that depth, where it is depth tested) */
|
||||
, multiview ?
|
||||
multiview ?
|
||||
"\t{\n"
|
||||
"\t\tint row = int(gl_ViewID_OVR) * 2;\n"
|
||||
"\t\tfloat near = 0.0;\n"
|
||||
|
||||
@@ -3,10 +3,9 @@ P2P_SIGNAL.C
|
||||
|
||||
Internet play's signalling (p2p.c): how a joiner and the host of an invite
|
||||
tell each other where they can be reached, through public MQTT brokers
|
||||
(those in brokers.txt, network.brokers_file; MQTT 5 over TCP, or 3.1.1 with
|
||||
a broker that refuses 5). Every broker is used at once, so any one of them
|
||||
working is enough (an answer goes back through each broker a request came
|
||||
through).
|
||||
(network.signalling_brokers; MQTT 5 over TCP, or 3.1.1 with a broker that
|
||||
refuses 5). Every broker is used at once, so any one of them working is
|
||||
enough (an answer goes back through each broker a request came through).
|
||||
They also carry the server browser's listings of public games (p2p_lobby.c):
|
||||
retained in each host's slot, published at least once (QoS 1, sent again
|
||||
until the broker acknowledges them), expiring at the broker after
|
||||
@@ -1522,50 +1521,8 @@ static void broker_readable(struct broker *broker)
|
||||
|
||||
/* ---------- p2p.c's side */
|
||||
|
||||
/* the brokers in network.brokers_file (beside config.toml, unless a full
|
||||
path: port/assets/network/brokers.txt, which the builds put there), one on
|
||||
each line, "#" starting a comment, into text: host:port entries separated by
|
||||
commas; empty if the file cannot be read */
|
||||
static void brokers_list(char *text, size_t size)
|
||||
{
|
||||
const char *name = config_string("network.brokers_file");
|
||||
char path[1024];
|
||||
char *file;
|
||||
size_t file_size = 0, index, length = 0;
|
||||
int comment = 0;
|
||||
|
||||
text[0] = 0;
|
||||
if (name[0] == '/' || name[0] == '\\' || (name[0] && name[1] == ':'))
|
||||
snprintf(path, sizeof(path), "%s", name);
|
||||
else
|
||||
{
|
||||
config_folder(path, sizeof(path));
|
||||
snprintf(path + strlen(path), sizeof(path) - strlen(path), "%s", name);
|
||||
}
|
||||
file = config_file_read(path, &file_size);
|
||||
if (!file)
|
||||
{
|
||||
platform_log("Internet play: the brokers' file %s cannot be read (network.brokers_file)", path);
|
||||
return;
|
||||
}
|
||||
for (index = 0; index < file_size && length + 1 < size; index++)
|
||||
{
|
||||
char character = file[index];
|
||||
|
||||
if (character == '\n' || character == '\r')
|
||||
comment = 0;
|
||||
else if (character == '#')
|
||||
comment = 1;
|
||||
if (!comment)
|
||||
text[length++] = character == '\n' || character == '\r' || character == '\t' ? ',' : character;
|
||||
}
|
||||
text[length] = 0;
|
||||
free(file);
|
||||
}
|
||||
|
||||
void p2p_signal_start(void)
|
||||
{
|
||||
char list[1024];
|
||||
const char *text;
|
||||
unsigned char random[8];
|
||||
char hex[17];
|
||||
@@ -1582,8 +1539,7 @@ void p2p_signal_start(void)
|
||||
p2p_key_hash(p2p_public_key(), hash);
|
||||
p2p_lobby_slot_topic(hash, signalling.own_slot, sizeof(signalling.own_slot));
|
||||
}
|
||||
brokers_list(list, sizeof(list));
|
||||
text = list;
|
||||
text = config_string("network.signalling_brokers");
|
||||
while (*text && signalling.broker_count < MAXIMUM_BROKERS)
|
||||
{
|
||||
const char *end = text + strcspn(text, ",");
|
||||
@@ -1615,9 +1571,7 @@ void p2p_signal_start(void)
|
||||
text = *end ? end + 1 : end;
|
||||
}
|
||||
if (!signalling.broker_count)
|
||||
platform_log("Internet play: no signalling brokers (network.brokers_file), so invites cannot work");
|
||||
else if (text[strspn(text, ", ")])
|
||||
platform_log("Internet play: only the first %d signalling brokers are used", MAXIMUM_BROKERS);
|
||||
platform_log("Internet play: no signalling brokers (network.signalling_brokers), so invites cannot work");
|
||||
}
|
||||
|
||||
void p2p_signal_select_sets(int *read, int *read_count, int *write, int *write_count, int maximum_count)
|
||||
|
||||
@@ -66,28 +66,15 @@ struct config_setting
|
||||
static const struct config_setting config_settings[] =
|
||||
{
|
||||
{ "display.fullscreen", _config_boolean, "true", "HALO_FULLSCREEN", _environment_value, _platform_desktop,
|
||||
"Where display.mode is empty: start borderless over the whole display;\n"
|
||||
"false starts in a window. F11 switches." },
|
||||
"Start fullscreen, drawing at the display's resolution and shape; false\n"
|
||||
"starts in a window, which draws the Xbox's 640x480. F11 switches." },
|
||||
{ "display.mode", _config_string, "\"\"", "HALO_DISPLAY_MODE", _environment_value, _platform_desktop,
|
||||
"\"fullscreen\" takes the display (at display.resolution's mode),\n"
|
||||
"\"borderless\" is a window over the whole desktop, \"windowed\" a window\n"
|
||||
"(display.window_size). Empty: display.fullscreen's (true: borderless).\n"
|
||||
"F11 switches to the window and back." },
|
||||
{ "display.resolution", _config_string, "\"native\"", "HALO_RESOLUTION", _environment_value, _platform_desktop,
|
||||
"What fullscreen and borderless draw at: \"native\", the display's own, or\n"
|
||||
"\"<width>x<height>\" (\"1920x1080\"), 640x480 or more. Fullscreen sets the\n"
|
||||
"display to it; borderless draws it scaled to the display." },
|
||||
{ "display.resolution_scaling", _config_string, "\"native\"", "HALO_RESOLUTION_SCALING", _environment_value,
|
||||
_platform_desktop,
|
||||
"\"native\" draws at the window's resolution (fullscreen, the display's or\n"
|
||||
"display.resolution); \"original\" draws the Xbox's 640x480 and scales it\n"
|
||||
"up." },
|
||||
{ "display.window_size", _config_string, "\"\"", "HALO_WINDOW_SIZE", _environment_value, _platform_desktop,
|
||||
"The window's size, \"<width>x<height>\" (\"1920x1080\"), 640x480 or more (it\n"
|
||||
"can be resized). Empty: display.window_scale's." },
|
||||
"\"fullscreen\" takes the display at its desktop resolution, \"borderless\"\n"
|
||||
"is a window over the whole desktop (both draw at the display's\n"
|
||||
"resolution), \"windowed\" a window of the Xbox's 640x480, scaled. Empty:\n"
|
||||
"display.fullscreen's (true: borderless). F11 switches to the window and back." },
|
||||
{ "display.window_scale", _config_integer, "2", "HALO_WINDOW_SCALE", _environment_value, _platform_desktop,
|
||||
"Where display.window_size is empty: the window's size as a multiple of\n"
|
||||
"640x480." },
|
||||
"The window's size as a multiple of 640x480 (it can be resized)." },
|
||||
{ "display.screen_width", _config_integer, "0", "HALO_SCREEN_WIDTH", _environment_value, _platform_android,
|
||||
"Columns of the 480-line picture: 0 for the display's shape, 640 for the\n"
|
||||
"Xbox's 4:3." },
|
||||
@@ -109,8 +96,8 @@ static const struct config_setting config_settings[] =
|
||||
"false draws the maps' own bitmaps." },
|
||||
{ "display.high_res_text", _config_boolean, "true", "HALO_HIGH_RES_TEXT", _environment_value, _platform_all,
|
||||
"Draw the menus' and HUD's text with the fonts in port/assets/fonts\n"
|
||||
"(Overpass) at the resolution the game draws at, and the menus' titles\n"
|
||||
"from port/assets/titles; false draws the maps' bitmap fonts and titles." },
|
||||
"(Overpass) at the display's resolution, and the menus' titles from\n"
|
||||
"port/assets/titles; false draws the maps' bitmap fonts and titles." },
|
||||
{ "display.menus", _config_string, "\"pc\"", "HALO_MENUS", _environment_value, _platform_all,
|
||||
"The menus: \"pc\" for the PC version's main menu (port/assets/menus,\n"
|
||||
"and a menus folder here for your own), \"xbox\" for the Xbox's." },
|
||||
@@ -139,9 +126,6 @@ static const struct config_setting config_settings[] =
|
||||
{ "vr.enabled", _config_boolean, "true", "HALO_VR_ENABLED", _environment_value, _platform_android,
|
||||
"On a VR headset (Quest, Steam Frame), play in VR; false shows the game\n"
|
||||
"on a flat panel instead. Read when the app starts." },
|
||||
{ "vr.menus", _config_string, "\"pc\"", "HALO_VR_MENUS", _environment_value, _platform_android,
|
||||
"The menus in VR: \"pc\" for the PC version's (as display.menus), \"xbox\"\n"
|
||||
"for the Xbox's. Read when the menus' map loads." },
|
||||
{ "vr.resolution_scale", _config_real, "1.0", "HALO_VR_RESOLUTION_SCALE", _environment_value, _platform_android,
|
||||
"The eye images' size as a multiple of what the headset recommends;\n"
|
||||
"lower is faster. Read when the app starts." },
|
||||
@@ -415,39 +399,11 @@ static const struct config_setting config_settings[] =
|
||||
"in everyone's server browser: anyone can see and join it) or, false,\n"
|
||||
"PRIVATE (only players with its invite link can join). Server Setup's\n"
|
||||
"LISTING changes it for each game." },
|
||||
{ "network.coop_public", _config_boolean, "false", "HALO_NET_COOP_PUBLIC", _environment_value, _platform_all,
|
||||
"Whether an online co-op game (Create Game > Internet, a SINGLEPLAYER\n"
|
||||
"map) starts as PUBLIC or, false, PRIVATE: Server Setup's LISTING in\n"
|
||||
"co-op, which writes its choice here." },
|
||||
{ "network.coop_friendly_fire", _config_string, "\"on\"", "HALO_NET_COOP_FRIENDLY_FIRE", _environment_value,
|
||||
_platform_all,
|
||||
"Whether the players of an online co-op game hurt each other: \"off\",\n"
|
||||
"\"on\", \"shields_only\" or \"explosives_only\" (Server Setup's FRIENDLY\n"
|
||||
"FIRE in co-op, which writes its choice here). Their AI allies they\n"
|
||||
"always can, as in the campaign." },
|
||||
{ "network.coop_enemies_mode", _config_string, "\"per_player\"", "HALO_NET_COOP_ENEMIES_MODE", _environment_value,
|
||||
_platform_all,
|
||||
"Online co-op's extra enemies: \"none\", \"per_player\" (each squad of\n"
|
||||
"enemies grows by coop_enemies for each player past the first) or\n"
|
||||
"\"multiplier\" (each is coop_enemies_multiplier times as large, for any\n"
|
||||
"number of players). Server Setup's EXTRA ENEMIES in co-op writes its\n"
|
||||
"choice here." },
|
||||
{ "network.coop_enemies", _config_integer, "50", "HALO_NET_COOP_ENEMIES", _environment_value, _platform_all,
|
||||
"Online co-op's extra enemies per player, a percentage: for each player\n"
|
||||
"past the first, each squad of enemies a level places gets this much of\n"
|
||||
"itself more (100: as many again; 25 to 200). Server Setup's PER PLAYER\n"
|
||||
"in co-op writes its choice here." },
|
||||
{ "network.coop_enemies_multiplier", _config_integer, "2", "HALO_NET_COOP_ENEMIES_MULTIPLIER", _environment_value,
|
||||
_platform_all,
|
||||
"Online co-op's static multiplier of its enemies: each squad of enemies\n"
|
||||
"a level places is this many times as large (2 to 32). Server Setup's\n"
|
||||
"MULTIPLIER in co-op writes its choice here." },
|
||||
{ "network.brokers_file", _config_string, "\"brokers.txt\"",
|
||||
"HALO_NET_BROKERS_FILE", _environment_value, _platform_all,
|
||||
"The file of the public MQTT brokers through which the machines of an\n"
|
||||
"invite find each other (its messages are encrypted), beside this file\n"
|
||||
"unless a full path: one host:port on each line, up to 4. Updates\n"
|
||||
"replace brokers.txt: keep a list of your own under another name." },
|
||||
{ "network.signalling_brokers", _config_string,
|
||||
"\"broker.emqx.io:1883,broker.hivemq.com:1883,test.mosquitto.org:1883\"",
|
||||
"HALO_NET_BROKERS", _environment_value, _platform_all,
|
||||
"Public MQTT brokers through which the machines of an invite find each\n"
|
||||
"other (its messages are encrypted); comma-separated host:port." },
|
||||
{ "network.stun_servers", _config_string, "\"stun.l.google.com:19302,stun.cloudflare.com:3478\"",
|
||||
"HALO_NET_STUN", _environment_value, _platform_all,
|
||||
"Public STUN servers that tell this machine its internet address;\n"
|
||||
@@ -457,6 +413,17 @@ static const struct config_setting config_settings[] =
|
||||
"The Discord application internet play invites go through while the\n"
|
||||
"Discord desktop client runs; empty for none." },
|
||||
|
||||
{ "voice.enabled", _config_boolean, "true", "HALO_VOICE_ENABLED", _environment_value, _platform_android,
|
||||
"Voice chat in multiplayer: hear the other players (teammates over the\n"
|
||||
"radio, the rest when near, from where they are)." },
|
||||
{ "voice.microphone", _config_boolean, "true", "HALO_VOICE_MICROPHONE", _environment_value, _platform_android,
|
||||
"Send your voice in multiplayer, whenever you speak (the headset's\n"
|
||||
"microphone; Android asks for it once). False only listens." },
|
||||
{ "voice.volume", _config_real, "1.0", "HALO_VOICE_VOLUME", _environment_value, _platform_android,
|
||||
"The other players' voices' volume (1 as they come)." },
|
||||
{ "voice.threshold", _config_real, "0.02", "HALO_VOICE_THRESHOLD", _environment_value, _platform_android,
|
||||
"How loud the microphone must be to send (0..1 of full scale): higher\n"
|
||||
"if it sends noise, lower if it cuts words off." },
|
||||
{ "bots.count", _config_integer, "0", "HALO_BOTS_COUNT", _environment_value, _platform_android,
|
||||
"Bots in the multiplayer games this machine hosts (0 to 15; 0: none):\n"
|
||||
"players it drives itself, which every machine sees as players. Set in\n"
|
||||
@@ -1294,11 +1261,6 @@ int config_write_integer(const char *name, long value)
|
||||
|
||||
/* ---------- public code */
|
||||
|
||||
char *config_file_read(const char *path, size_t *size)
|
||||
{
|
||||
return config_read_file(path, size);
|
||||
}
|
||||
|
||||
unsigned long config_changes(void)
|
||||
{
|
||||
return config_change_count;
|
||||
|
||||
@@ -30,9 +30,6 @@ such setting */
|
||||
int config_text(const char *name, char *text, size_t size);
|
||||
/* the folder config.toml is in, with its separator */
|
||||
void config_folder(char *path, size_t size);
|
||||
/* a whole file (a UTF-8 path), NUL terminated, or NULL; free() it (the Android
|
||||
guest has no SDL_LoadFile) */
|
||||
char *config_file_read(const char *path, size_t *size);
|
||||
/* a setting's default, as text; 0 if there is no such setting */
|
||||
int config_default(const char *name, char *text, size_t size);
|
||||
/* how many times config_write has changed a setting: what keeps one reads
|
||||
|
||||
@@ -62,10 +62,6 @@ gone, ends: else the keyboard stays held from the game) */
|
||||
static Uint64 binding_polled_ms;
|
||||
#define BINDING_ABANDONED_MS 500
|
||||
static unsigned mouse_buttons_down;
|
||||
#ifdef HALO_ANDROID
|
||||
/* the mouse's place, flat (platform_flat_mouse) */
|
||||
static float flat_mouse_x, flat_mouse_y;
|
||||
#endif
|
||||
/* (an input taken that nothing asks for is let go after this) */
|
||||
#define BINDING_UNCLAIMED_MS 2000
|
||||
/* the multiplayer scoreboard is open (platform_scoreboard_scroll): the wheel
|
||||
@@ -383,43 +379,38 @@ static BOOL platform_fullscreen_setting(void)
|
||||
return !config_boolean("debug.hidden_window") && platform_display_mode() != _display_mode_windowed;
|
||||
}
|
||||
|
||||
/* a size as a setting has it, "<width>x<height>": whether it is one, and
|
||||
the Xbox's 640x480 or more */
|
||||
static BOOL platform_size_parse(const char *text, long *width, long *height)
|
||||
/* the window's fullscreen kind (display.mode): borderless, a window over
|
||||
the whole desktop (SDL's fullscreen without a mode), or fullscreen, the
|
||||
display taken at its desktop resolution. Either draws at the display's
|
||||
resolution (platform_screen_mode); F11 switches to the kind set. */
|
||||
static void platform_fullscreen_kind_apply(void)
|
||||
{
|
||||
char *end;
|
||||
static int applied = -1;
|
||||
int exclusive = platform_display_mode() == _display_mode_fullscreen ? 1 : 0;
|
||||
SDL_DisplayID display;
|
||||
|
||||
*width = strtol(text, &end, 10);
|
||||
*height = *end == 'x' || *end == 'X' ? strtol(end + 1, &end, 10) : 0;
|
||||
return !*end && *width >= 640 && *height >= 480;
|
||||
if (!platform_window || exclusive == applied)
|
||||
return;
|
||||
applied = exclusive;
|
||||
display = SDL_GetDisplayForWindow(platform_window);
|
||||
SDL_SetWindowFullscreenMode(platform_window,
|
||||
exclusive && display ? SDL_GetDesktopDisplayMode(display) : NULL);
|
||||
}
|
||||
|
||||
/* display.resolution in pixels, or 0x0 for the display's own ("native"),
|
||||
as for one the game cannot draw at */
|
||||
static void platform_resolution_setting(long *width, long *height)
|
||||
/* whether the game is, or is to be, fullscreen, and if so the size in
|
||||
pixels of the display it fills (d3d8_gl.c draws at that resolution) */
|
||||
BOOL platform_screen_mode(long *width, long *height)
|
||||
{
|
||||
if (!platform_size_parse(config_string("display.resolution"), width, height))
|
||||
*width = *height = 0;
|
||||
}
|
||||
SDL_DisplayID display;
|
||||
const SDL_DisplayMode *mode;
|
||||
|
||||
/* the window's size (display.window_size), else the Xbox's 640x480 times
|
||||
display.window_scale, as older versions set it */
|
||||
static void platform_window_size_setting(long *width, long *height)
|
||||
{
|
||||
long scale = config_integer("display.window_scale");
|
||||
|
||||
if (!platform_size_parse(config_string("display.window_size"), width, height))
|
||||
if (platform_window ? !(SDL_GetWindowFlags(platform_window) & SDL_WINDOW_FULLSCREEN) :
|
||||
!platform_fullscreen_setting() || !platform_sdl_initialize())
|
||||
{
|
||||
*width = 640 * (scale < 1 ? 1 : scale);
|
||||
*height = 480 * (scale < 1 ? 1 : scale);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* a display's own size in pixels (its desktop mode) */
|
||||
static BOOL platform_display_size(SDL_DisplayID display, long *width, long *height)
|
||||
{
|
||||
const SDL_DisplayMode *mode = display ? SDL_GetDesktopDisplayMode(display) : NULL;
|
||||
|
||||
display = platform_window ? SDL_GetDisplayForWindow(platform_window) : SDL_GetPrimaryDisplay();
|
||||
mode = display ? SDL_GetDesktopDisplayMode(display) : NULL;
|
||||
if (!mode)
|
||||
return FALSE;
|
||||
*width = (long)(mode->w * mode->pixel_density + 0.5f);
|
||||
@@ -427,263 +418,23 @@ static BOOL platform_display_size(SDL_DisplayID display, long *width, long *heig
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* the window's fullscreen kind (display.mode): borderless, a window over
|
||||
the whole desktop (SDL's fullscreen without a mode), or fullscreen, the
|
||||
display taken at display.resolution's mode (the one nearest it), else at its
|
||||
desktop one. F11 switches to the kind set. */
|
||||
static void platform_fullscreen_kind_apply(void)
|
||||
{
|
||||
static int applied = -1;
|
||||
static long applied_width, applied_height;
|
||||
int exclusive = platform_display_mode() == _display_mode_fullscreen ? 1 : 0;
|
||||
long width = 0, height = 0;
|
||||
SDL_DisplayID display;
|
||||
SDL_DisplayMode closest;
|
||||
const SDL_DisplayMode *mode = NULL;
|
||||
|
||||
if (!platform_window)
|
||||
return;
|
||||
if (exclusive)
|
||||
platform_resolution_setting(&width, &height);
|
||||
if (exclusive == applied && width == applied_width && height == applied_height)
|
||||
return;
|
||||
applied = exclusive;
|
||||
applied_width = width;
|
||||
applied_height = height;
|
||||
display = SDL_GetDisplayForWindow(platform_window);
|
||||
if (exclusive && display)
|
||||
{
|
||||
if (width && SDL_GetClosestFullscreenDisplayMode(display, (int)width, (int)height, 0.0f, false, &closest))
|
||||
mode = &closest;
|
||||
else
|
||||
mode = SDL_GetDesktopDisplayMode(display);
|
||||
}
|
||||
SDL_SetWindowFullscreenMode(platform_window, mode);
|
||||
}
|
||||
|
||||
/* whether the game last asked for the window to be fullscreen */
|
||||
static BOOL platform_fullscreen_requested = FALSE;
|
||||
|
||||
static void platform_window_set_fullscreen(BOOL fullscreen)
|
||||
{
|
||||
platform_fullscreen_requested = fullscreen;
|
||||
SDL_SetWindowFullscreen(platform_window, fullscreen ? true : false);
|
||||
}
|
||||
|
||||
/* whether the window is fullscreen. SDL sets its flag when the window
|
||||
manager confirms the request, and some never do: gamescope (the Steam
|
||||
Deck's Game Mode) makes the window the size of the display but leaves the
|
||||
flag unset, so the game drew 640x480 and gamescope stretched it. A window
|
||||
that was asked to be fullscreen and covers its display counts too. */
|
||||
static BOOL platform_window_fullscreen(void)
|
||||
{
|
||||
SDL_DisplayID display;
|
||||
const SDL_DisplayMode *mode;
|
||||
int width, height;
|
||||
|
||||
if (SDL_GetWindowFlags(platform_window) & SDL_WINDOW_FULLSCREEN)
|
||||
return TRUE;
|
||||
if (!platform_fullscreen_requested)
|
||||
return FALSE;
|
||||
display = SDL_GetDisplayForWindow(platform_window);
|
||||
mode = display ? SDL_GetDesktopDisplayMode(display) : NULL;
|
||||
if (!mode || !SDL_GetWindowSizeInPixels(platform_window, &width, &height))
|
||||
return FALSE;
|
||||
return width >= (int)(mode->w * mode->pixel_density + 0.5f) &&
|
||||
height >= (int)(mode->h * mode->pixel_density + 0.5f);
|
||||
}
|
||||
|
||||
/* the size in pixels the game draws its picture at (d3d8_gl.c): the
|
||||
window's, the display's while fullscreen, or display.resolution's while
|
||||
fullscreen (either kind) where the display has room for it; before the
|
||||
window opens, what it will be. FALSE where display.resolution_scaling is
|
||||
"original": the Xbox's 640x480, scaled to the window. */
|
||||
BOOL platform_screen_mode(long *width, long *height)
|
||||
{
|
||||
/* (the size last given, for a window that has none: minimized) */
|
||||
static long last_width, last_height;
|
||||
long resolution_width, resolution_height;
|
||||
BOOL fullscreen;
|
||||
|
||||
if (!strcmp(config_string("display.resolution_scaling"), "original"))
|
||||
return FALSE;
|
||||
if (platform_window)
|
||||
{
|
||||
int pixel_width = 0, pixel_height = 0;
|
||||
|
||||
SDL_GetWindowSizeInPixels(platform_window, &pixel_width, &pixel_height);
|
||||
if (pixel_width <= 0 || pixel_height <= 0)
|
||||
{
|
||||
*width = last_width;
|
||||
*height = last_height;
|
||||
return last_width > 0;
|
||||
}
|
||||
*width = pixel_width;
|
||||
*height = pixel_height;
|
||||
fullscreen = platform_window_fullscreen();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!platform_sdl_initialize())
|
||||
return FALSE;
|
||||
fullscreen = platform_fullscreen_setting();
|
||||
platform_window_size_setting(width, height);
|
||||
if (fullscreen && !platform_display_size(SDL_GetPrimaryDisplay(), width, height))
|
||||
return FALSE;
|
||||
}
|
||||
/* (fullscreen's display is at the resolution already, where it has that
|
||||
mode: platform_fullscreen_kind_apply; borderless's is scaled to) */
|
||||
platform_resolution_setting(&resolution_width, &resolution_height);
|
||||
if (fullscreen && resolution_width && resolution_width <= *width && resolution_height <= *height)
|
||||
{
|
||||
*width = resolution_width;
|
||||
*height = resolution_height;
|
||||
}
|
||||
last_width = *width;
|
||||
last_height = *height;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* the size added to the list unless it has it already; the count */
|
||||
static int platform_resolution_add(long *widths, long *heights, int count, int maximum, long width, long height)
|
||||
{
|
||||
int index;
|
||||
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
if (widths[index] == width && heights[index] == height)
|
||||
return count;
|
||||
}
|
||||
if (count < maximum)
|
||||
{
|
||||
widths[count] = width;
|
||||
heights[count] = height;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int platform_display_resolutions(long *widths, long *heights, int maximum)
|
||||
{
|
||||
SDL_DisplayID display;
|
||||
SDL_DisplayMode **modes;
|
||||
long display_width, display_height, width, height;
|
||||
int mode_count = 0, count = 0, index;
|
||||
|
||||
if (maximum < 1 || !platform_sdl_initialize())
|
||||
return 0;
|
||||
display = platform_window ? SDL_GetDisplayForWindow(platform_window) : 0;
|
||||
if (!display)
|
||||
display = SDL_GetPrimaryDisplay();
|
||||
if (!platform_display_size(display, &display_width, &display_height))
|
||||
return 0;
|
||||
modes = SDL_GetFullscreenDisplayModes(display, &mode_count);
|
||||
for (index = 0; modes && index < mode_count; index++)
|
||||
{
|
||||
width = (long)(modes[index]->w * modes[index]->pixel_density + 0.5f);
|
||||
height = (long)(modes[index]->h * modes[index]->pixel_density + 0.5f);
|
||||
if (width >= 640 && height >= 480 && width <= display_width && height <= display_height &&
|
||||
(width != display_width || height != display_height))
|
||||
{
|
||||
count = platform_resolution_add(widths, heights, count, maximum, width, height);
|
||||
}
|
||||
}
|
||||
SDL_free(modes);
|
||||
/* (the one set, though this display has no such mode, so that Video
|
||||
Setup shows it) */
|
||||
platform_resolution_setting(&width, &height);
|
||||
if (width && (width != display_width || height != display_height))
|
||||
count = platform_resolution_add(widths, heights, count, maximum, width, height);
|
||||
/* largest first */
|
||||
for (index = 1; index < count; index++)
|
||||
{
|
||||
int place;
|
||||
|
||||
width = widths[index];
|
||||
height = heights[index];
|
||||
for (place = index; place > 0 && (widths[place - 1] < width ||
|
||||
(widths[place - 1] == width && heights[place - 1] < height)); place--)
|
||||
{
|
||||
widths[place] = widths[place - 1];
|
||||
heights[place] = heights[place - 1];
|
||||
}
|
||||
widths[place] = width;
|
||||
heights[place] = height;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Video Setup's window sizes, by shape (4:3, 16:10, 16:9, 21:9), each from
|
||||
the smallest */
|
||||
static const short platform_window_sizes_offered[][2] =
|
||||
{
|
||||
{ 640, 480 }, { 800, 600 }, { 1024, 768 }, { 1280, 960 }, { 1600, 1200 }, { 1920, 1440 }, { 2560, 1920 },
|
||||
{ 1280, 800 }, { 1440, 900 }, { 1680, 1050 }, { 1920, 1200 }, { 2560, 1600 },
|
||||
{ 1280, 720 }, { 1600, 900 }, { 1920, 1080 }, { 2560, 1440 }, { 3840, 2160 },
|
||||
{ 2560, 1080 }, { 3440, 1440 }, { 3840, 1600 }, { 5120, 2160 },
|
||||
};
|
||||
|
||||
int platform_window_sizes(long *widths, long *heights, int maximum)
|
||||
{
|
||||
SDL_DisplayID display;
|
||||
SDL_Rect usable;
|
||||
long width, height;
|
||||
int count = 0, index;
|
||||
|
||||
if (maximum < 1 || !platform_sdl_initialize())
|
||||
return 0;
|
||||
display = platform_window ? SDL_GetDisplayForWindow(platform_window) : 0;
|
||||
if (!display)
|
||||
display = SDL_GetPrimaryDisplay();
|
||||
if (!display || !SDL_GetDisplayUsableBounds(display, &usable))
|
||||
usable.w = usable.h = 0;
|
||||
for (index = 0; index < (int)(sizeof(platform_window_sizes_offered) / sizeof(*platform_window_sizes_offered));
|
||||
index++)
|
||||
{
|
||||
width = platform_window_sizes_offered[index][0];
|
||||
height = platform_window_sizes_offered[index][1];
|
||||
/* (the Xbox's own whatever the desktop's size) */
|
||||
if (!index || (width <= usable.w && height <= usable.h))
|
||||
count = platform_resolution_add(widths, heights, count, maximum, width, height);
|
||||
}
|
||||
/* (the one set, though it is none of them or too big for this desktop,
|
||||
so that Video Setup shows it) */
|
||||
platform_window_size_setting(&width, &height);
|
||||
return platform_resolution_add(widths, heights, count, maximum, width, height);
|
||||
}
|
||||
|
||||
#else
|
||||
int platform_display_resolutions(long *widths, long *heights, int maximum)
|
||||
{
|
||||
(void)widths;
|
||||
(void)heights;
|
||||
(void)maximum;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int platform_window_sizes(long *widths, long *heights, int maximum)
|
||||
{
|
||||
(void)widths;
|
||||
(void)heights;
|
||||
(void)maximum;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifndef HALO_ANDROID
|
||||
/* the window's size (platform_window_size_setting), as the window was made
|
||||
or last resized: platform_display_apply */
|
||||
static long platform_window_width = -1, platform_window_height = -1;
|
||||
#endif
|
||||
/* the window's scale (display.window_scale, as the window was made or last
|
||||
resized: platform_display_apply) */
|
||||
static long platform_window_scale = -1;
|
||||
|
||||
BOOL platform_video_initialize(unsigned long width, unsigned long height)
|
||||
{
|
||||
int scale = (int)config_integer("display.window_scale");
|
||||
int version;
|
||||
|
||||
if (platform_window)
|
||||
return TRUE;
|
||||
if (!platform_sdl_initialize())
|
||||
return FALSE;
|
||||
if (scale < 1)
|
||||
scale = 1;
|
||||
platform_window_scale = scale;
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
@@ -708,23 +459,14 @@ BOOL platform_video_initialize(unsigned long width, unsigned long height)
|
||||
#endif
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
{
|
||||
int scale = (int)config_integer("display.window_scale");
|
||||
|
||||
if (scale < 1)
|
||||
scale = 1;
|
||||
platform_window = SDL_CreateWindow("Halo", (int)(width * scale), (int)(height * scale),
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN);
|
||||
}
|
||||
platform_window = SDL_CreateWindow("Halo", (int)(width * scale), (int)(height * scale),
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN);
|
||||
#else
|
||||
/* fullscreen (either kind) unless display.mode is the window, which F11
|
||||
switches to and from: display.window_size, whatever shape the fullscreen
|
||||
picture has. The game draws at the size platform_screen_mode gives
|
||||
(d3d8_gl.c). */
|
||||
(void)width;
|
||||
(void)height;
|
||||
platform_window_size_setting(&platform_window_width, &platform_window_height);
|
||||
platform_window = SDL_CreateWindow("Halo", (int)platform_window_width, (int)platform_window_height,
|
||||
/* fullscreen at the desktop's resolution unless display.fullscreen is
|
||||
false, where the game draws the display's shape at its resolution
|
||||
(d3d8_gl.c); the window size is the windowed mode F11 switches to and
|
||||
from, where it draws 640x480 */
|
||||
platform_window = SDL_CreateWindow("Halo", (int)(width * scale), (int)(height * scale),
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY |
|
||||
(config_boolean("debug.hidden_window") ? SDL_WINDOW_HIDDEN : 0) |
|
||||
(platform_fullscreen_setting() ? SDL_WINDOW_FULLSCREEN : 0));
|
||||
@@ -735,7 +477,6 @@ BOOL platform_video_initialize(unsigned long width, unsigned long height)
|
||||
return FALSE;
|
||||
}
|
||||
#ifndef HALO_ANDROID
|
||||
platform_fullscreen_requested = platform_fullscreen_setting();
|
||||
platform_fullscreen_kind_apply();
|
||||
#endif
|
||||
platform_gl_context = SDL_GL_CreateContext(platform_window);
|
||||
@@ -771,28 +512,23 @@ BOOL platform_video_initialize(unsigned long width, unsigned long height)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* display.mode, display.resolution (fullscreen's display mode),
|
||||
display.window_size (when it changes: the window can be resized) and
|
||||
display.vsync, as Settings has written them; display.resolution_scaling
|
||||
and borderless's resolution are taken up between frames
|
||||
(halo_screen_commit) */
|
||||
/* display.fullscreen, display.window_scale (when it changes: the window can
|
||||
be resized) and display.vsync, as Settings has written them */
|
||||
void platform_display_apply(void)
|
||||
{
|
||||
#ifndef HALO_ANDROID
|
||||
BOOL fullscreen = platform_fullscreen_setting();
|
||||
long width, height;
|
||||
long scale = config_integer("display.window_scale");
|
||||
|
||||
if (!platform_window)
|
||||
return;
|
||||
platform_fullscreen_kind_apply();
|
||||
if (platform_window_fullscreen() != (fullscreen != FALSE))
|
||||
platform_window_set_fullscreen(fullscreen);
|
||||
platform_window_size_setting(&width, &height);
|
||||
if (width != platform_window_width || height != platform_window_height)
|
||||
if (((SDL_GetWindowFlags(platform_window) & SDL_WINDOW_FULLSCREEN) != 0) != (fullscreen != FALSE))
|
||||
SDL_SetWindowFullscreen(platform_window, fullscreen ? true : false);
|
||||
if (scale != platform_window_scale && scale >= 1)
|
||||
{
|
||||
platform_window_width = width;
|
||||
platform_window_height = height;
|
||||
SDL_SetWindowSize(platform_window, (int)width, (int)height);
|
||||
platform_window_scale = scale;
|
||||
SDL_SetWindowSize(platform_window, (int)(640 * scale), (int)(480 * scale));
|
||||
}
|
||||
#else
|
||||
if (!platform_window)
|
||||
@@ -1175,13 +911,13 @@ static void platform_show_pending_message(void)
|
||||
#else
|
||||
{
|
||||
/* (a box cannot show above a fullscreen game) */
|
||||
BOOL fullscreen = platform_window_fullscreen();
|
||||
int fullscreen = (SDL_GetWindowFlags(platform_window) & SDL_WINDOW_FULLSCREEN) != 0;
|
||||
|
||||
if (fullscreen)
|
||||
platform_window_set_fullscreen(FALSE);
|
||||
SDL_SetWindowFullscreen(platform_window, false);
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, title, text, platform_window);
|
||||
if (fullscreen)
|
||||
platform_window_set_fullscreen(TRUE);
|
||||
SDL_SetWindowFullscreen(platform_window, true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1189,10 +925,8 @@ static void platform_show_pending_message(void)
|
||||
/* ---------- events */
|
||||
|
||||
/* quits as closing the window does, when the events are next read (the
|
||||
menus' Quit: port/linux/game/menu_functions.c); on Android, where the
|
||||
system closes its apps, the VR options' Quit (ui_widget.c) ends the process
|
||||
at once: on the Steam Frame, Steam cannot end the game, which runs in
|
||||
Lepton's container (port/android/STEAM_FRAME.md) */
|
||||
menus' Quit: port/linux/game/menu_functions.c); Android's menus have none,
|
||||
as the system closes its apps */
|
||||
void platform_request_quit(void)
|
||||
{
|
||||
#ifndef HALO_ANDROID
|
||||
@@ -1201,20 +935,6 @@ void platform_request_quit(void)
|
||||
memset(&event, 0, sizeof(event));
|
||||
event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
#else
|
||||
{
|
||||
/* (and says so beside the game's data: the Steam Frame's launcher
|
||||
does not then take it for a start that failed and start it again) */
|
||||
char path[512];
|
||||
FILE *file;
|
||||
|
||||
snprintf(path, sizeof(path), "%s/quit_requested.txt", platform_data_root());
|
||||
file = fopen(path, "wb");
|
||||
if (file)
|
||||
fclose(file);
|
||||
}
|
||||
platform_log("quit: the player asked");
|
||||
exit(EXIT_SUCCESS);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1312,15 +1032,13 @@ void platform_pump_events(void)
|
||||
window's size and place while fullscreen) */
|
||||
if (event.key.down && !event.key.repeat && event.key.scancode == SDL_SCANCODE_F11)
|
||||
{
|
||||
platform_window_set_fullscreen(!platform_window_fullscreen());
|
||||
SDL_SetWindowFullscreen(platform_window,
|
||||
(SDL_GetWindowFlags(platform_window) & SDL_WINDOW_FULLSCREEN) ? false : true);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
#ifdef HALO_ANDROID
|
||||
flat_mouse_x = event.motion.x;
|
||||
flat_mouse_y = event.motion.y;
|
||||
#else
|
||||
#ifndef HALO_ANDROID
|
||||
/* in the menus the mouse moves the pointer, not the view */
|
||||
if (input_state.ui_pointer)
|
||||
{
|
||||
@@ -1480,23 +1198,6 @@ int platform_binding_capture_poll(int *input)
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
/* flat, not in VR (the emulator, for testing the menus): the mouse's place
|
||||
in the window and whether its left button is down, polled */
|
||||
BOOL platform_flat_mouse(float *x, float *y)
|
||||
{
|
||||
BOOL down;
|
||||
|
||||
pthread_mutex_lock(&input_lock);
|
||||
*x = flat_mouse_x;
|
||||
*y = flat_mouse_y;
|
||||
/* (an injected mouse's, adb shell input mouse: button 0) */
|
||||
down = (mouse_buttons_down & ((1u << SDL_BUTTON_LEFT) | 1u)) != 0;
|
||||
pthread_mutex_unlock(&input_lock);
|
||||
return down;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef HALO_ANDROID
|
||||
/* ---------- the menus' pointer */
|
||||
|
||||
@@ -1547,17 +1248,12 @@ BOOL platform_ui_pointer_read(struct platform_ui_pointer *pointer)
|
||||
return active;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void platform_video_window_size(int *width, int *height)
|
||||
{
|
||||
#ifdef HALO_ANDROID
|
||||
SDL_GetWindowSizeInPixels(platform_window, width, height);
|
||||
#else
|
||||
SDL_GetWindowSize(platform_window, width, height);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
void platform_input_read(struct platform_input_state *state, BOOL consume_motion)
|
||||
{
|
||||
pthread_mutex_lock(&input_lock);
|
||||
|
||||
@@ -62,15 +62,6 @@ BOOL platform_video_initialize(unsigned long width, unsigned long height);
|
||||
#ifndef HALO_ANDROID
|
||||
BOOL platform_screen_mode(long *width, long *height);
|
||||
#endif
|
||||
/* Video Setup's resolutions (port/linux/game/menu_tags.c), in pixels: the
|
||||
display's modes the game can draw at, largest first, each once, but the
|
||||
display's own (Native), and display.resolution's where it is none of them;
|
||||
how many. None on Android. */
|
||||
int platform_display_resolutions(long *widths, long *heights, int maximum);
|
||||
/* Video Setup's window sizes: those of each shape (4:3, 16:10, 16:9, 21:9)
|
||||
that fit the desktop, and display.window_size's; how many. None on
|
||||
Android. */
|
||||
int platform_window_sizes(long *widths, long *heights, int maximum);
|
||||
void platform_video_drawable_size(int *width, int *height);
|
||||
/* the window's mode and size and V-Sync, from config.toml as Settings has
|
||||
just written it (the main thread's) */
|
||||
@@ -98,8 +89,8 @@ struct platform_ui_pointer
|
||||
};
|
||||
void platform_ui_pointer_set_active(BOOL active);
|
||||
BOOL platform_ui_pointer_read(struct platform_ui_pointer *pointer);
|
||||
#endif
|
||||
void platform_video_window_size(int *width, int *height);
|
||||
#endif
|
||||
BOOL platform_next_keystroke(struct platform_keystroke *keystroke);
|
||||
/* the multiplayer scoreboard (game_engine.c) open or not: while it is, the
|
||||
mouse wheel and Page Up/Down scroll it instead of switching weapons; how
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
VOICE.C
|
||||
|
||||
The voice chat's sound (halo_voice.h): on Android, the host's
|
||||
(port/android/host/host_voice.c); elsewhere none.
|
||||
*/
|
||||
|
||||
#include "halo_voice.h"
|
||||
#include "platform.h"
|
||||
#include "port_config.h"
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
#include "guest_host.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static int voice_started;
|
||||
|
||||
/* whether the app may use the microphone, as the launcher found
|
||||
(LauncherActivity.java): opened without it, Android would ask in the middle
|
||||
of the game */
|
||||
static int voice_microphone_allowed(void)
|
||||
{
|
||||
char path[512];
|
||||
FILE *file;
|
||||
int allowed = 0;
|
||||
|
||||
snprintf(path, sizeof(path), "%s/microphone_permission.txt", platform_data_root());
|
||||
file = fopen(path, "rb");
|
||||
if (file)
|
||||
{
|
||||
allowed = fgetc(file) == '1';
|
||||
fclose(file);
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
|
||||
int halo_voice_enabled(void)
|
||||
{
|
||||
return config_boolean("voice.enabled");
|
||||
}
|
||||
|
||||
int halo_voice_begin(void)
|
||||
{
|
||||
int microphone;
|
||||
|
||||
if (voice_started)
|
||||
return 1;
|
||||
voice_started = 1;
|
||||
microphone = config_boolean("voice.microphone") && voice_microphone_allowed();
|
||||
platform_log("voice: on, %s", microphone ? "with the microphone" :
|
||||
config_boolean("voice.microphone") ? "without the microphone (not allowed)" : "listening only");
|
||||
return host_voice_start(microphone, (float)config_real("voice.volume"), (float)config_real("voice.threshold"));
|
||||
}
|
||||
|
||||
void halo_voice_end(void)
|
||||
{
|
||||
if (!voice_started)
|
||||
return;
|
||||
voice_started = 0;
|
||||
host_voice_stop();
|
||||
platform_log("voice: off");
|
||||
}
|
||||
|
||||
int halo_voice_take(void *buffer, int size)
|
||||
{
|
||||
return voice_started ? host_voice_take_packets(buffer, size) : 0;
|
||||
}
|
||||
|
||||
void halo_voice_play(int speaker, int sequence, const void *data, int length, float left, float right)
|
||||
{
|
||||
if (voice_started)
|
||||
host_voice_play(speaker, sequence, data, length, left, right);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int halo_voice_enabled(void) { return 0; }
|
||||
int halo_voice_begin(void) { return 0; }
|
||||
void halo_voice_end(void) {}
|
||||
int halo_voice_take(void *buffer, int size) { (void)buffer; (void)size; return 0; }
|
||||
void halo_voice_play(int speaker, int sequence, const void *data, int length, float left, float right)
|
||||
{
|
||||
(void)speaker; (void)sequence; (void)data; (void)length; (void)left; (void)right;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -55,9 +55,9 @@ drive the controller.
|
||||
|
||||
/* main/console.c */
|
||||
extern unsigned char console_is_active(void);
|
||||
/* port/linux/game/menu_functions.c: two or more players on this machine
|
||||
(co-op, or split screen in a network game) */
|
||||
extern unsigned char pc_menu_split_players(void);
|
||||
/* port/linux/game/menu_functions.c: two players on this machine (the
|
||||
campaign's co-op) */
|
||||
extern unsigned char pc_menu_coop_players(void);
|
||||
|
||||
/* ---------- device tables */
|
||||
|
||||
@@ -638,43 +638,13 @@ static int sdl_gamepads(SDL_Gamepad *gamepads[PORT_COUNT])
|
||||
return found;
|
||||
}
|
||||
|
||||
/* whether one gamepad is port 1's (port_gamepad) */
|
||||
static BOOL lone_gamepad_split;
|
||||
|
||||
/* no button of the gamepad held, its sticks and triggers at rest */
|
||||
static BOOL gamepad_idle(SDL_Gamepad *gamepad)
|
||||
{
|
||||
int index;
|
||||
|
||||
for (index = 0; index < SDL_GAMEPAD_BUTTON_COUNT; index++)
|
||||
{
|
||||
if (SDL_GetGamepadButton(gamepad, (SDL_GamepadButton)index))
|
||||
return FALSE;
|
||||
}
|
||||
for (index = 0; index < SDL_GAMEPAD_AXIS_COUNT; index++)
|
||||
{
|
||||
if (abs(SDL_GetGamepadAxis(gamepad, (SDL_GamepadAxis)index)) > 8000)
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* the gamepad of a port: the first shares port 0 with the keyboard, but for
|
||||
two or more players with one gamepad (co-op, split screen), port 1 has it
|
||||
(the keyboard's player is 1, the gamepad's 2). It changes port only at rest:
|
||||
a button held across the change would be pressed again on the other port
|
||||
(the B that leaves a profile screen leaving the game as player 1's) */
|
||||
two players with one gamepad (co-op), port 1 has it (the keyboard's player
|
||||
is 1, the gamepad's 2) */
|
||||
static SDL_Gamepad *port_gamepad(SDL_Gamepad *gamepads[PORT_COUNT], int count, int port)
|
||||
{
|
||||
if (count == 1)
|
||||
{
|
||||
BOOL split = pc_menu_split_players() != 0;
|
||||
|
||||
if (split != lone_gamepad_split && gamepad_idle(gamepads[0]))
|
||||
lone_gamepad_split = split;
|
||||
if (lone_gamepad_split)
|
||||
return port == 1 ? gamepads[0] : NULL;
|
||||
}
|
||||
if (count == 1 && pc_menu_coop_players())
|
||||
return port == 1 ? gamepads[0] : NULL;
|
||||
return port < count ? gamepads[port] : NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ The game finds the game data as on Linux. Refer to "Start the game" in
|
||||
| Settings | `config.toml` next to `halo.exe` |
|
||||
| Saved games | `%APPDATA%\halo`, or `paths.saves` in `config.toml` |
|
||||
| Log | `debug.txt` in the data root (the folder that contains `maps\`) |
|
||||
| Log of the port | The console. The release build has no console: `halo.log` next to `halo.exe` |
|
||||
|
||||
## How the port operates
|
||||
|
||||
|
||||
@@ -37,24 +37,6 @@ static void windows_startup(void)
|
||||
/* Sleep() waits in 1 ms steps rather than the default 15.6 ms: the
|
||||
game's frame pacing sleeps for short intervals */
|
||||
timeBeginPeriod(1);
|
||||
/* a release build has no console (/SUBSYSTEM:WINDOWS), so the platform
|
||||
layer's log (platform_log's stderr: invite links, the data root, crash
|
||||
reports) goes to halo.log next to halo.exe, unless stderr already goes
|
||||
somewhere (a pipe or a file the game was started with) */
|
||||
if (_fileno(stderr) < 0)
|
||||
{
|
||||
char path[MAX_PATH];
|
||||
DWORD length = GetModuleFileNameA(NULL, path, sizeof(path));
|
||||
char *slash;
|
||||
|
||||
if (length && length < sizeof(path) && (slash = strrchr(path, '\\')) &&
|
||||
(size_t)(slash + 1 - path) + sizeof("halo.log") <= sizeof(path))
|
||||
{
|
||||
strcpy(slash + 1, "halo.log");
|
||||
if (freopen(path, "w", stderr))
|
||||
setvbuf(stderr, NULL, _IONBF, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int errno_from_windows_error(DWORD error)
|
||||
|
||||
@@ -317,7 +317,6 @@ symbols in this file:
|
||||
#include "units/units.h"
|
||||
#include "units/vehicle_definitions.h"
|
||||
#include "units/vehicles.h"
|
||||
#include "networking/network_game_globals.h"
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -2225,12 +2224,6 @@ long actor_new(
|
||||
long actor_definition_index;
|
||||
long actor_index = NONE;
|
||||
|
||||
/* port: a client of the distributed netcode runs no AI: it has the host's
|
||||
actors' units, driven as the host sends them (port/linux/game/network_actors.c),
|
||||
and its own (placed at the map's start or by its scripts) would be extras
|
||||
the host doesn't have */
|
||||
if (network_game_distributed_client())
|
||||
return NONE;
|
||||
if (actor_variant_definition_index != NONE)
|
||||
{
|
||||
actor_variant_definition = actor_variant_definition_get(actor_variant_definition_index);
|
||||
@@ -2669,9 +2662,6 @@ long actor_place(
|
||||
long unit_index;
|
||||
|
||||
match_assert("c:\\halo\\SOURCE\\ai\\actors.c", 603, starting_location);
|
||||
/* port: none on a client of the distributed netcode (actor_new) */
|
||||
if (network_game_distributed_client())
|
||||
return NONE;
|
||||
objects_garbage_collection();
|
||||
|
||||
actor_variant_definition =
|
||||
@@ -2841,8 +2831,7 @@ short actors_spawn_from_unit(
|
||||
{
|
||||
short spawned_actor_count = 0;
|
||||
|
||||
/* port: none on a client of the distributed netcode (actor_new) */
|
||||
if (actor_variant_definition_index != NONE && actor_count > 0 && !network_game_distributed_client())
|
||||
if (actor_variant_definition_index != NONE && actor_count > 0)
|
||||
{
|
||||
struct unit_datum *source_unit = unit_get(unit_index);
|
||||
long source_actor_index = source_unit->unit.swarm_actor_index != NONE ?
|
||||
|
||||
@@ -25,10 +25,9 @@ enum
|
||||
|
||||
enum
|
||||
{
|
||||
/* port: the native builds' larger pools (halo_port_capacity.h) */
|
||||
MAXIMUM_ACTORS = HALO_PORT_MAXIMUM_ACTORS,
|
||||
MAXIMUM_SWARMS = HALO_PORT_MAXIMUM_SWARMS,
|
||||
MAXIMUM_SWARM_COMPONENTS = HALO_PORT_MAXIMUM_SWARM_COMPONENTS,
|
||||
MAXIMUM_ACTORS = 256,
|
||||
MAXIMUM_SWARMS = 32,
|
||||
MAXIMUM_SWARM_COMPONENTS = 256,
|
||||
SWARM_COMPONENT_DATUM_SIZE = 0x40,
|
||||
};
|
||||
|
||||
@@ -256,11 +255,7 @@ enum
|
||||
NUMBER_OF_ACTOR_PERCEPTION_TYPES,
|
||||
};
|
||||
|
||||
/* port: the debug information's actors (ai_debug.c's actor_debug_array,
|
||||
by actor index): every actor of the native builds' larger pool
|
||||
(halo_port_capacity.h), else one past the Xbox's 256 is written past its
|
||||
end */
|
||||
#define MAXIMUM_NUMBER_OF_ACTORS HALO_PORT_MAXIMUM_ACTORS
|
||||
#define MAXIMUM_NUMBER_OF_ACTORS 256
|
||||
#define MAXIMUM_NUMBER_OF_ACTOR_PATHS 32
|
||||
|
||||
#define MAXIMUM_UNIT_INDICES_PER_SWARM 16
|
||||
|
||||
@@ -465,8 +465,7 @@ typedef char ai_prop_unopposable_enemy_offset_assert[
|
||||
static void ai_place_pending_mounted_weapons(
|
||||
void);
|
||||
static boolean ai_enemies_endanger_player(
|
||||
boolean must_be_attacking,
|
||||
long unit_index);
|
||||
boolean must_be_attacking);
|
||||
static void ai_flush_spatial_effects(
|
||||
void);
|
||||
|
||||
@@ -1972,11 +1971,8 @@ void ai_update(
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: unit_index NONE is any player's unit (the game's own check); else
|
||||
only enemies of that unit count (network co-op's respawn, players.c) */
|
||||
static boolean ai_enemies_endanger_player(
|
||||
boolean must_be_attacking,
|
||||
long unit_index)
|
||||
boolean must_be_attacking)
|
||||
{
|
||||
long current_time = game_time_get();
|
||||
struct data_iterator iterator;
|
||||
@@ -1989,8 +1985,7 @@ static boolean ai_enemies_endanger_player(
|
||||
{
|
||||
if (prop->player &&
|
||||
prop->enemy &&
|
||||
unit_get(prop->unit_index)->unit.player_index != NONE &&
|
||||
(unit_index == NONE || prop->unit_index == unit_index))
|
||||
unit_get(prop->unit_index)->unit.player_index != NONE)
|
||||
{
|
||||
struct actor_datum *actor = actor_get(prop->owner_actor_index);
|
||||
long object_index = actor->meta.swarm
|
||||
@@ -2073,20 +2068,13 @@ static boolean ai_enemies_endanger_player(
|
||||
boolean ai_enemies_can_see_player(
|
||||
void)
|
||||
{
|
||||
return ai_enemies_endanger_player(FALSE, NONE);
|
||||
return ai_enemies_endanger_player(FALSE);
|
||||
}
|
||||
|
||||
boolean ai_enemies_attacking_player(
|
||||
void)
|
||||
{
|
||||
return ai_enemies_endanger_player(TRUE, NONE);
|
||||
}
|
||||
|
||||
/* port: whether enemies are attacking this player's unit */
|
||||
boolean ai_port_enemies_attacking_unit(
|
||||
long unit_index)
|
||||
{
|
||||
return ai_enemies_endanger_player(TRUE, unit_index);
|
||||
return ai_enemies_endanger_player(TRUE);
|
||||
}
|
||||
|
||||
long ai_get_race_from_team_index(
|
||||
|
||||
@@ -142,9 +142,6 @@ boolean ai_consider_major_upgrade(
|
||||
short squad_index,
|
||||
real chance);
|
||||
|
||||
/* port: network co-op's respawn (players.c) */
|
||||
boolean ai_port_enemies_attacking_unit(
|
||||
long unit_index);
|
||||
boolean ai_enemies_attacking_player(
|
||||
void);
|
||||
boolean ai_enemies_can_see_player(
|
||||
|
||||
@@ -298,7 +298,7 @@ void ai_profile_display(
|
||||
ai_profile.meters[_ai_meter_units_updated].current_value,
|
||||
ai_profile.meters[_ai_meter_units].current_value,
|
||||
ai_profile.meters[_ai_meter_props].current_value,
|
||||
HALO_PORT_MAXIMUM_PROPS);
|
||||
768);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ static void ai_profile_render_encounters(
|
||||
ai_profile.meters[_ai_meter_encounters_updated].current_value,
|
||||
ai_profile.meters[_ai_meter_encounters].current_value,
|
||||
ai_profile.meters[_ai_meter_props].current_value,
|
||||
HALO_PORT_MAXIMUM_PROPS);
|
||||
768);
|
||||
ai_profile_draw_string(profilestring, NUMBEROF(tab_stops), tab_stops, global_real_argb_white);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -311,7 +311,6 @@ symbols in this file:
|
||||
#include "scenario/scenario_definitions.h"
|
||||
#include "structures/structure_bsp_definitions.h"
|
||||
#include "units/units.h"
|
||||
#include "coop_enemies.h" /* port: port/linux/game/coop_enemies.c */
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -541,8 +540,7 @@ static boolean encounter_place_actor(
|
||||
long encounter_index,
|
||||
short squad_index,
|
||||
short initial_variant,
|
||||
boolean spawning,
|
||||
short extra_number);
|
||||
boolean spawning);
|
||||
static void encounterless_deactivate(
|
||||
long actor_index);
|
||||
static void encounters_test_activation(
|
||||
@@ -1838,7 +1836,7 @@ boolean encounter_spawn_actor(
|
||||
{
|
||||
if (ai_globals->ai_initialized_for_map)
|
||||
{
|
||||
if (encounter_place_actor(encounter_index, squad_index, 0, TRUE, 0))
|
||||
if (encounter_place_actor(encounter_index, squad_index, 0, TRUE))
|
||||
{
|
||||
struct encounter_datum *encounter = encounter_get(encounter_index);
|
||||
struct encounter_definition *encounter_definition = TAG_BLOCK_GET_ELEMENT(
|
||||
@@ -2247,17 +2245,9 @@ void encounter_create(
|
||||
|
||||
for (i = 0; i < count; ++i)
|
||||
{
|
||||
encounter_place_actor(encounter_index, squad_index, initial_variant, FALSE, 0);
|
||||
encounter_place_actor(encounter_index, squad_index, initial_variant, FALSE);
|
||||
initial_variant = 0;
|
||||
}
|
||||
/* port: network co-op's extra enemies, for its players
|
||||
(coop_enemies.c) */
|
||||
{
|
||||
short extra_count = coop_enemies_extra_count(encounter_index, count);
|
||||
|
||||
for (i = 0; i < extra_count; ++i)
|
||||
encounter_place_actor(encounter_index, squad_index, 0, FALSE, (short)(i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
encounter_update_status(encounter_index);
|
||||
@@ -3006,25 +2996,18 @@ static void encounter_post_combat(
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: extra_number, from 1, places one of network co-op's extra enemies
|
||||
(coop_enemies.c): at the squad's starting locations in turn, each the
|
||||
place a ring about its own (0: the squad's own actor, as the Xbox's) */
|
||||
static boolean encounter_place_actor(
|
||||
long encounter_index,
|
||||
short squad_index,
|
||||
short initial_variant,
|
||||
boolean spawning,
|
||||
short extra_number)
|
||||
boolean spawning)
|
||||
{
|
||||
boolean placed = FALSE;
|
||||
struct encounter_definition *encounter_definition = TAG_BLOCK_GET_ELEMENT(
|
||||
&global_scenario_get()->ai_encounters, DATUM_INDEX_TO_ABSOLUTE_INDEX(encounter_index), struct encounter_definition);
|
||||
struct squad_definition *squad_definition = TAG_BLOCK_GET_ELEMENT(
|
||||
&encounter_definition->squads, squad_index, struct squad_definition);
|
||||
short starting_location_index = extra_number > 0 && squad_definition->starting_locations.count > 0 ?
|
||||
(short)((extra_number - 1) % squad_definition->starting_locations.count) :
|
||||
encounter_get_actor_starting_location(encounter_index, squad_index, spawning);
|
||||
struct actor_starting_location spread_location;
|
||||
short starting_location_index = encounter_get_actor_starting_location(encounter_index, squad_index, spawning);
|
||||
|
||||
if (starting_location_index != NONE)
|
||||
{
|
||||
@@ -3033,18 +3016,6 @@ static boolean encounter_place_actor(
|
||||
short actor_palette_index = squad_definition->actor_palette_index;
|
||||
struct scenario *scenario = global_scenario_get();
|
||||
|
||||
/* port: an extra enemy a ring about the starting location */
|
||||
if (extra_number > 0)
|
||||
{
|
||||
spread_location = *starting_location;
|
||||
if (coop_enemies_spread_position(&starting_location->position,
|
||||
(short)((extra_number - 1) / squad_definition->starting_locations.count + 1),
|
||||
&spread_location.position))
|
||||
{
|
||||
starting_location = &spread_location;
|
||||
}
|
||||
}
|
||||
|
||||
if (starting_location->actor_variant_index != NONE)
|
||||
actor_palette_index = starting_location->actor_variant_index;
|
||||
|
||||
|
||||
@@ -95,8 +95,7 @@ static long last_prop_data_full_warn_time = NONE;
|
||||
void props_initialize(
|
||||
void)
|
||||
{
|
||||
/* port: the native builds' larger pool (halo_port_capacity.h) */
|
||||
prop_data = game_state_data_new("prop", HALO_PORT_MAXIMUM_PROPS, sizeof(struct prop_datum));
|
||||
prop_data = game_state_data_new("prop", 768, sizeof(struct prop_datum));
|
||||
|
||||
match_assert("c:\\halo\\SOURCE\\ai\\props.c", 0x24, prop_data);
|
||||
|
||||
@@ -142,7 +141,7 @@ static void prop_add(
|
||||
error(
|
||||
_error_silent,
|
||||
"AI knowledge database (%d entries) is full (warns once every 30 sec)",
|
||||
HALO_PORT_MAXIMUM_PROPS);
|
||||
768);
|
||||
last_prop_data_full_warn_time = game_time;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,6 @@ void scripted_camera_set_camera_point_relative(
|
||||
long relative_object_index);
|
||||
short scripted_camera_time(
|
||||
void);
|
||||
long scripted_camera_object_relative_to(
|
||||
void);
|
||||
void scripted_camera_update(
|
||||
struct dead_camera *camera,
|
||||
struct camera_control const *controls,
|
||||
|
||||
@@ -190,9 +190,11 @@ void dead_camera_update(
|
||||
}
|
||||
else
|
||||
{
|
||||
next_unit_index = NONE;
|
||||
next_unit_index = (long)result;
|
||||
}
|
||||
/* With no living player, keep observing the dead unit/current position. */
|
||||
/* January preserves the command pointer as the fallback object index
|
||||
* when no player is found. This is a bug; a corrected build should
|
||||
* initialize next_unit_index to NONE instead. */
|
||||
|
||||
if (next_unit_index != camera->unit_index && next_unit_index != NONE)
|
||||
{
|
||||
|
||||
@@ -121,7 +121,6 @@ symbols in this file:
|
||||
|
||||
#include "camera_scripting.h"
|
||||
#include "dead_camera.h"
|
||||
#include "coop_spectate.h" /* port: port/linux/game/coop_spectate.c */
|
||||
#include "editor_flying_camera.h"
|
||||
#include "first_person_camera.h"
|
||||
#include "flying_camera.h"
|
||||
@@ -709,12 +708,8 @@ static void director_choose_camera_game(
|
||||
}
|
||||
else
|
||||
{
|
||||
long player_index = local_player_get_player_index(local_player_index);
|
||||
struct player_datum *player = player_get(player_index);
|
||||
/* port: and a network co-op player who hasn't spawned yet, waiting for
|
||||
the level's first checkpoint, watches too */
|
||||
boolean use_dead_camera = player->unit_index == NONE &&
|
||||
(player->statistics.deaths > 0 || players_coop_waiting_to_start(player_index));
|
||||
struct player_datum *player = player_get(local_player_get_player_index(local_player_index));
|
||||
boolean use_dead_camera = player->unit_index == NONE && player->statistics.deaths > 0;
|
||||
|
||||
/* (port: not a client in another's game, the host's rules: a flying
|
||||
camera would see all of it, and one behind the player round its
|
||||
@@ -726,25 +721,17 @@ static void director_choose_camera_game(
|
||||
director_choose_game_perspective(local_player_index, initialize);
|
||||
if (use_dead_camera)
|
||||
{
|
||||
/* port: a dead network co-op player watches a living teammate
|
||||
(port/linux/game/coop_spectate.c), or its own body if nobody is alive */
|
||||
long watched_unit_index = coop_spectating() ? coop_spectate_unit(local_player_index) : NONE;
|
||||
|
||||
if (director->camera_proc != (director_camera_update_proc)dead_camera_update ||
|
||||
(watched_unit_index != NONE &&
|
||||
((struct dead_camera *)director->camera_data)->unit_index != watched_unit_index))
|
||||
if (director->camera_proc != (director_camera_update_proc)dead_camera_update)
|
||||
{
|
||||
dead_camera_new(
|
||||
(struct dead_camera *)director->camera_data,
|
||||
local_player_index,
|
||||
watched_unit_index);
|
||||
NONE);
|
||||
director_set_camera(
|
||||
local_player_index,
|
||||
(director_camera_update_proc)dead_camera_update,
|
||||
TRUE);
|
||||
}
|
||||
if (watched_unit_index != NONE)
|
||||
coop_spectate_camera((struct dead_camera *)director->camera_data);
|
||||
}
|
||||
else if (director->camera_proc == (director_camera_update_proc)dead_camera_update)
|
||||
{
|
||||
|
||||
@@ -74,7 +74,6 @@ symbols in this file:
|
||||
#include "scenario/scenario_definitions.h"
|
||||
#include "text/draw_string.h"
|
||||
#include "text/text_group.h"
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -266,7 +265,6 @@ void cinematic_set_title_delayed(
|
||||
{
|
||||
short title_slot_index;
|
||||
|
||||
network_coop_note_title(title_index, delay);
|
||||
for (title_slot_index = 0;
|
||||
title_slot_index < MAXIMUM_QUEUED_CINEMATIC_TITLES &&
|
||||
cinematic_globals->queued_titles[title_slot_index].title_index != NONE;
|
||||
|
||||
@@ -98,7 +98,6 @@ symbols in this file:
|
||||
#include "scenario/scenario_definitions.h"
|
||||
#include "sound/game_sound.h"
|
||||
#include "sound/sound_definitions.h"
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -113,10 +112,6 @@ enum
|
||||
_scenario_device_group_can_change_only_once_bit = 0,
|
||||
};
|
||||
|
||||
/* port: the group flags network_coop.c syncs */
|
||||
#define DEVICE_GROUP_NETWORK_FLAGS \
|
||||
(FLAG(_device_group_can_change_only_once_bit) | FLAG(_device_group_changed_once_bit))
|
||||
|
||||
enum
|
||||
{
|
||||
_device_function_none = 0,
|
||||
@@ -191,9 +186,6 @@ typedef char device_animation_frame_count_offset_assert[
|
||||
void device_group_set_actual_value(
|
||||
short group_index,
|
||||
real actual_value);
|
||||
static boolean device_group_change(
|
||||
short group_index,
|
||||
real desired_value);
|
||||
static short device_group_new(
|
||||
real initial_value,
|
||||
unsigned long flags);
|
||||
@@ -602,7 +594,6 @@ void device_group_set_actual_value(
|
||||
|
||||
group = datum_get(device_groups_data, group_index);
|
||||
group->actual_value = actual_value;
|
||||
network_coop_note_device_snap(group_index);
|
||||
|
||||
object_iterator_new(&iterator, _object_mask_device, 0);
|
||||
while ((device = object_iterator_next(&iterator)) != NULL)
|
||||
@@ -686,74 +677,6 @@ void device_render_debug(
|
||||
boolean device_group_set_desired_value(
|
||||
short group_index,
|
||||
real desired_value)
|
||||
{
|
||||
/* port: on a co-op client only the host moves devices (network_coop.c) */
|
||||
if (network_coop_devices_remote())
|
||||
return FALSE;
|
||||
|
||||
return device_group_change(group_index, desired_value);
|
||||
}
|
||||
|
||||
/* port: reads a group for network_coop.c; FALSE if it doesn't exist */
|
||||
boolean device_group_network_get(
|
||||
short group_index,
|
||||
real *value,
|
||||
word *flags,
|
||||
boolean *runtime)
|
||||
{
|
||||
struct device_group_datum *group = group_index != NONE ? datum_try_and_get(device_groups_data, group_index) : NULL;
|
||||
|
||||
if (!group)
|
||||
return FALSE;
|
||||
*value = group->actual_value;
|
||||
*flags = group->flags & DEVICE_GROUP_NETWORK_FLAGS;
|
||||
*runtime = TEST_FLAG(group->flags, _device_group_runtime_bit);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* port: a co-op client applies the host's group state. snap jumps the
|
||||
devices to the value; otherwise they move there as usual. */
|
||||
void device_group_network_set(
|
||||
short group_index,
|
||||
real value,
|
||||
word flags,
|
||||
boolean snap)
|
||||
{
|
||||
struct device_group_datum *group = group_index != NONE ? datum_try_and_get(device_groups_data, group_index) : NULL;
|
||||
|
||||
if (!group)
|
||||
return;
|
||||
if (snap)
|
||||
device_group_set_actual_value(group_index, value);
|
||||
else
|
||||
{
|
||||
group->flags &= ~FLAG(_device_group_changed_once_bit);
|
||||
device_group_change(group_index, value);
|
||||
}
|
||||
group->flags = (word)((group->flags & ~DEVICE_GROUP_NETWORK_FLAGS) | (flags & DEVICE_GROUP_NETWORK_FLAGS));
|
||||
}
|
||||
|
||||
void device_port_set_state(
|
||||
long device_index,
|
||||
real power,
|
||||
boolean move_to_position,
|
||||
real position)
|
||||
{
|
||||
struct device_datum *device = device_get(device_index);
|
||||
|
||||
device->device.power = PIN(power, 0.0f, 1.0f);
|
||||
if (move_to_position)
|
||||
{
|
||||
device->device.position = PIN(position, 0.0f, 1.0f);
|
||||
device->device.position_velocity = 0.0f;
|
||||
device->device.flags |= FLAG(_device_position_changed_bit);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean device_group_change(
|
||||
short group_index,
|
||||
real desired_value)
|
||||
{
|
||||
boolean changed = FALSE;
|
||||
|
||||
|
||||
@@ -129,24 +129,6 @@ void device_set_power(
|
||||
boolean device_group_set_desired_value(
|
||||
short group_index,
|
||||
real desired_value);
|
||||
/* port: co-op device sync (port/linux/game/network_coop.c) */
|
||||
boolean device_group_network_get(
|
||||
short group_index,
|
||||
real *value,
|
||||
word *flags,
|
||||
boolean *runtime);
|
||||
void device_group_network_set(
|
||||
short group_index,
|
||||
real value,
|
||||
word flags,
|
||||
boolean snap);
|
||||
/* port: a co-op client puts a device at the host's power, and at its
|
||||
position when move_to_position */
|
||||
void device_port_set_state(
|
||||
long device_index,
|
||||
real power,
|
||||
boolean move_to_position,
|
||||
real position);
|
||||
boolean device_can_change_position(
|
||||
long device_index);
|
||||
boolean device_frontfacing(
|
||||
|
||||
@@ -107,7 +107,6 @@ symbols in this file:
|
||||
#include "units/units.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -398,7 +397,6 @@ void scripted_player_effect_set_rotation(
|
||||
real pitch,
|
||||
real roll)
|
||||
{
|
||||
network_coop_note_player_effect(_coop_player_effect_rotation, yaw, pitch, roll);
|
||||
player_effect_globals->scripted_effect.max_rotation.yaw = DEGREES_TO_RADIANS(yaw);
|
||||
player_effect_globals->scripted_effect.max_rotation.pitch = DEGREES_TO_RADIANS(pitch);
|
||||
player_effect_globals->scripted_effect.max_rotation.roll = DEGREES_TO_RADIANS(roll);
|
||||
@@ -460,50 +458,6 @@ void player_effect_screen_fade_out(
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: read and set the screen fade, so a co-op host can send it to its
|
||||
clients (port/linux/game/network_coop.c): color, length, direction, and
|
||||
the game time it started */
|
||||
void player_effect_port_screen_fade_get(
|
||||
real_rgb_color *color,
|
||||
short *ticks,
|
||||
boolean *fading_out,
|
||||
long *start_time)
|
||||
{
|
||||
*color = player_effect_globals->screen_fade.color;
|
||||
*ticks = player_effect_globals->screen_fade.ticks;
|
||||
*fading_out = player_effect_globals->screen_fade.fading_out;
|
||||
*start_time = player_effect_globals->screen_fade.start_time;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void player_effect_port_screen_fade_set(
|
||||
real_rgb_color const *color,
|
||||
short ticks,
|
||||
boolean fading_out,
|
||||
long start_time)
|
||||
{
|
||||
player_effect_globals->screen_fade.color = *color;
|
||||
player_effect_globals->screen_fade.ticks = ticks;
|
||||
player_effect_globals->screen_fade.fading_out = fading_out;
|
||||
player_effect_globals->screen_fade.start_time = start_time;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
boolean player_effect_port_scripted_active(
|
||||
void)
|
||||
{
|
||||
return TEST_FLAG(player_effect_globals->global_flags, _scripted_player_effect_active_bit);
|
||||
}
|
||||
|
||||
void player_effect_port_scripted_end(
|
||||
void)
|
||||
{
|
||||
SET_FLAG(player_effect_globals->global_flags, _scripted_player_effect_active_bit, FALSE);
|
||||
SET_FLAG(player_effect_globals->global_flags, _scripted_player_effect_stopping_bit, FALSE);
|
||||
}
|
||||
|
||||
void player_effect_get_damage_indicators(
|
||||
short local_player_index,
|
||||
byte *damage_indicators)
|
||||
@@ -655,7 +609,6 @@ void scripted_player_effect_set_translation(
|
||||
{
|
||||
real_vector3d *translation = &player_effect_globals->scripted_effect.max_translation;
|
||||
|
||||
network_coop_note_player_effect(_coop_player_effect_translation, horizontal, vertical, depth);
|
||||
translation->i = horizontal;
|
||||
translation->j = vertical;
|
||||
translation->k = depth;
|
||||
@@ -669,7 +622,6 @@ void scripted_player_effect_start(
|
||||
{
|
||||
short ticks;
|
||||
|
||||
network_coop_note_player_effect(_coop_player_effect_start, maximum_intensity, attack_time, 0.0f);
|
||||
player_effect_globals->scripted_effect.max_intensity = maximum_intensity;
|
||||
|
||||
ticks = (short)fast_ftol(attack_time * TICKS_PER_SECOND);
|
||||
@@ -692,7 +644,6 @@ void scripted_player_effect_stop(
|
||||
{
|
||||
short ticks = (short)fast_ftol(duration * TICKS_PER_SECOND);
|
||||
|
||||
network_coop_note_player_effect(_coop_player_effect_stop, duration, 0.0f, 0.0f);
|
||||
player_effect_globals->scripted_effect.timer = ticks;
|
||||
player_effect_globals->scripted_effect.total_time = ticks;
|
||||
SET_FLAG(
|
||||
|
||||
@@ -56,21 +56,6 @@ void player_effect_screen_fade_out(
|
||||
real green,
|
||||
real blue,
|
||||
short ticks);
|
||||
/* port: screen fade sync for network co-op */
|
||||
void player_effect_port_screen_fade_get(
|
||||
real_rgb_color *color,
|
||||
short *ticks,
|
||||
boolean *fading_out,
|
||||
long *start_time);
|
||||
void player_effect_port_screen_fade_set(
|
||||
real_rgb_color const *color,
|
||||
short ticks,
|
||||
boolean fading_out,
|
||||
long start_time);
|
||||
/* port: whether the scripts' screen shake is running, and ending it at once
|
||||
(network co-op keeps a client's in step with the host's) */
|
||||
boolean player_effect_port_scripted_active(void);
|
||||
void player_effect_port_scripted_end(void);
|
||||
void player_effect_get_damage_indicators(
|
||||
short local_player_index,
|
||||
byte *damage_indicators);
|
||||
|
||||
@@ -162,7 +162,6 @@ struct game_options;
|
||||
#include "networking/network_messages.h"
|
||||
#include "networking/telnet_console.h"
|
||||
#include "objects/objects.h"
|
||||
#include "objects/widgets/antenna.h"
|
||||
#include "objects/widgets/widgets.h"
|
||||
#include "physics/breakable_surfaces.h"
|
||||
#include "physics/collision_usage.h"
|
||||
@@ -180,12 +179,9 @@ struct game_options;
|
||||
#include "structures/structures.h"
|
||||
#include "units/units.h"
|
||||
#include "units/vehicles.h"
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
|
||||
/* network_game_globals.c's */
|
||||
boolean network_game_distributed_client(void);
|
||||
/* port: a client drives the host's actors' units as the host sent them (port/linux/game/network_actors.c) */
|
||||
void network_actors_drive(void);
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -329,31 +325,19 @@ void game_tick(
|
||||
positions, and could place objects of their own) */
|
||||
if (!network_game_distributed_client())
|
||||
ai_update();
|
||||
else
|
||||
network_actors_drive();
|
||||
players_update_before_game();
|
||||
|
||||
seconds_per_tick = game_globals->players_are_double_speed
|
||||
? 1.0f / (2 * TICKS_PER_SECOND)
|
||||
: 1.0f / TICKS_PER_SECOND;
|
||||
effects_update(seconds_per_tick);
|
||||
/* An antenna's chain is a simulation of the Xbox's own step, an update a
|
||||
tick: stepped a frame at a time, its springs, its carry and its points'
|
||||
physics act against the frames' own movement of it, and a vehicle at
|
||||
speed swings it far wider than a tick's step does. The frames between the
|
||||
ticks are drawn from what each tick leaves (antenna.c). */
|
||||
antennas_update(seconds_per_tick);
|
||||
lock_global_random_seed();
|
||||
rumble_update();
|
||||
first_person_weapons_update();
|
||||
unlock_global_random_seed();
|
||||
game_engine_update();
|
||||
editor_update();
|
||||
/* port: in network co-op only the host runs the scripts. A client running
|
||||
them would place the map's actors and objects a second time and make
|
||||
decisions that belong to the host. */
|
||||
if (!(network_game_distributed_client() && network_coop_active()))
|
||||
hs_update();
|
||||
hs_update();
|
||||
recorded_animations_update();
|
||||
objects_update();
|
||||
players_update_after_game();
|
||||
@@ -941,9 +925,7 @@ void remove_quitting_players_from_game(
|
||||
struct player_datum *player;
|
||||
long current_time;
|
||||
|
||||
/* port: also in network co-op, which has no game engine. Without this a
|
||||
player who left kept their unit, and it respawned. */
|
||||
if (!game_engine_running() && !network_coop_active())
|
||||
if (!game_engine_running())
|
||||
return;
|
||||
|
||||
current_time = game_time_get();
|
||||
|
||||
@@ -88,8 +88,8 @@ struct game_allegiance_globals
|
||||
short allegiance_count;
|
||||
struct game_allegiance_record allegiances[8];
|
||||
byte reserved92[2];
|
||||
unsigned long ally_bitvector[GAME_ALLEGIANCE_BITVECTOR_LONGS];
|
||||
unsigned long friendly_bitvector[GAME_ALLEGIANCE_BITVECTOR_LONGS];
|
||||
unsigned long ally_bitvector[4];
|
||||
unsigned long friendly_bitvector[4];
|
||||
};
|
||||
|
||||
typedef char game_allegiance_record_size_assert[
|
||||
@@ -452,28 +452,6 @@ void game_allegiance_update(
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: which teams are allies and which friends. A co-op client takes the
|
||||
host's, as only the host runs the scripts that make allegiances. */
|
||||
void game_allegiance_get_teams(
|
||||
unsigned long ally_bitvector[GAME_ALLEGIANCE_BITVECTOR_LONGS],
|
||||
unsigned long friendly_bitvector[GAME_ALLEGIANCE_BITVECTOR_LONGS])
|
||||
{
|
||||
csmemcpy(ally_bitvector, game_allegiance_globals->ally_bitvector,
|
||||
sizeof(game_allegiance_globals->ally_bitvector));
|
||||
csmemcpy(friendly_bitvector, game_allegiance_globals->friendly_bitvector,
|
||||
sizeof(game_allegiance_globals->friendly_bitvector));
|
||||
}
|
||||
|
||||
void game_allegiance_set_teams(
|
||||
unsigned long const ally_bitvector[GAME_ALLEGIANCE_BITVECTOR_LONGS],
|
||||
unsigned long const friendly_bitvector[GAME_ALLEGIANCE_BITVECTOR_LONGS])
|
||||
{
|
||||
csmemcpy(game_allegiance_globals->ally_bitvector, ally_bitvector,
|
||||
sizeof(game_allegiance_globals->ally_bitvector));
|
||||
csmemcpy(game_allegiance_globals->friendly_bitvector, friendly_bitvector,
|
||||
sizeof(game_allegiance_globals->friendly_bitvector));
|
||||
}
|
||||
|
||||
void game_allegiance_create(
|
||||
short team1_index,
|
||||
boolean team1_suspicious,
|
||||
|
||||
@@ -11,9 +11,6 @@ enum
|
||||
_allegiance_incident_forgive
|
||||
};
|
||||
|
||||
/* port: the longs of game_allegiance_get_teams' bit vectors */
|
||||
#define GAME_ALLEGIANCE_BITVECTOR_LONGS 4
|
||||
|
||||
/* ---------- prototypes/GAME_ALLEGIANCE.C */
|
||||
|
||||
void game_allegiance_initialize(
|
||||
@@ -26,12 +23,6 @@ void game_allegiance_dispose_from_old_map(
|
||||
void);
|
||||
void game_allegiance_update(
|
||||
void);
|
||||
void game_allegiance_get_teams(
|
||||
unsigned long ally_bitvector[GAME_ALLEGIANCE_BITVECTOR_LONGS],
|
||||
unsigned long friendly_bitvector[GAME_ALLEGIANCE_BITVECTOR_LONGS]);
|
||||
void game_allegiance_set_teams(
|
||||
unsigned long const ally_bitvector[GAME_ALLEGIANCE_BITVECTOR_LONGS],
|
||||
unsigned long const friendly_bitvector[GAME_ALLEGIANCE_BITVECTOR_LONGS]);
|
||||
void game_allegiance_create(
|
||||
short team1_index,
|
||||
boolean team1_suspicious,
|
||||
|
||||
@@ -568,7 +568,6 @@ symbols in this file:
|
||||
#include "networking/network_game_globals.h"
|
||||
#include "networking/network_server_manager.h"
|
||||
#include "networking/network_game_manager.h"
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
/* (network_server_manager_internal.h's: the host's game record) */
|
||||
struct network_game *network_game_server_get_game(struct network_game_server *server);
|
||||
#include "objects.h"
|
||||
@@ -7069,31 +7068,16 @@ short game_engine_friendly_damage(
|
||||
{
|
||||
struct game_variant_options const *options = game_variant_options_get();
|
||||
struct player_datum *attacker;
|
||||
short friendly_fire;
|
||||
|
||||
/* port: network co-op's friendly fire is Server Setup's FRIENDLY FIRE,
|
||||
in the host's game settings (only the host deals damage), between its
|
||||
players: their AI allies they always hurt, as in the campaign */
|
||||
if (network_coop_active())
|
||||
if (!game_engine || !global_variant.universal_variant.teams || attacker_player_index == NONE ||
|
||||
options->friendly_fire == _friendly_fire_on)
|
||||
{
|
||||
struct network_game *game = network_game_get_game();
|
||||
struct unit_datum *unit = (struct unit_datum *)object_try_and_get_and_verify_type(object_index,
|
||||
_object_mask_unit);
|
||||
|
||||
if (!game || !unit || unit->unit.player_index == NONE)
|
||||
return _friendly_damage_all;
|
||||
friendly_fire = game->variant_options.friendly_fire;
|
||||
return _friendly_damage_all;
|
||||
}
|
||||
else if (!game_engine || !global_variant.universal_variant.teams)
|
||||
return _friendly_damage_all;
|
||||
else
|
||||
friendly_fire = options->friendly_fire;
|
||||
if (attacker_player_index == NONE || friendly_fire == _friendly_fire_on)
|
||||
return _friendly_damage_all;
|
||||
attacker = (struct player_datum *)datum_try_and_get(player_data, attacker_player_index);
|
||||
if (!attacker || attacker->unit_index == object_index)
|
||||
return _friendly_damage_all;
|
||||
switch (friendly_fire)
|
||||
switch (options->friendly_fire)
|
||||
{
|
||||
case _friendly_fire_off: return _friendly_damage_none;
|
||||
case _friendly_fire_shields_only: return _friendly_damage_shields;
|
||||
|
||||
@@ -482,14 +482,7 @@ void game_time_update(
|
||||
}
|
||||
}
|
||||
|
||||
/* The effects and the widgets that move every frame (game_frame) hang
|
||||
off objects: they read the pose between the last two ticks, as the
|
||||
renderer does, and the ticks above have taken their snapshots already
|
||||
(port/linux/game/render_interpolation.c). The frames of one tick share
|
||||
the fraction, so what they hang off is drawn where they put it. */
|
||||
render_interpolation_frame_begin();
|
||||
game_frame(game_time_get_speed()*time_delta_sec);
|
||||
render_interpolation_frame_end();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -209,7 +209,6 @@ symbols in this file:
|
||||
#include "units/vehicles.h"
|
||||
|
||||
#include "real_math.h"
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -1788,49 +1787,59 @@ boolean player_control_action_test_look_relative_all_directions(
|
||||
_player_control_look_relative_all_directions_flags);
|
||||
}
|
||||
|
||||
/* The actions the scripts test for (player_control_action_test_*) that a
|
||||
player's unit control, turning, sticks and trigger show. A local player's
|
||||
come from its input blob; in network co-op the host's scripts count a
|
||||
client's too (port/linux/game/network_distributed.c), so Pillar of
|
||||
Autumn's tutorial goes on for whichever player does what it asks. */
|
||||
void player_control_action_test_note(
|
||||
unsigned long unit_control_flags,
|
||||
real_euler_angles2d const *facing_delta,
|
||||
real_vector2d const *throttle,
|
||||
real primary_trigger)
|
||||
static void player_control_action_test_check_reset_input_blob(
|
||||
struct input_blob *input)
|
||||
{
|
||||
struct player_control_globals_data *globals = player_control_globals;
|
||||
struct player_control_globals_data *globals;
|
||||
|
||||
if (TEST_FLAG(unit_control_flags, _unit_control_action_bit))
|
||||
if (input->accept && cinematic_can_be_skipped())
|
||||
{
|
||||
main_skip_cinematic();
|
||||
}
|
||||
|
||||
globals = player_control_globals;
|
||||
if (TEST_FLAG(input->unit_control_flags, _unit_control_action_bit))
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_action_bit);
|
||||
}
|
||||
if (TEST_FLAG(unit_control_flags, _unit_control_jump_bit))
|
||||
if (TEST_FLAG(input->unit_control_flags, _unit_control_jump_bit))
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_jump_bit);
|
||||
}
|
||||
if (TEST_FLAG(unit_control_flags, _unit_control_throw_grenade_bit))
|
||||
if (input->accept)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_grenade_trigger_bit);
|
||||
globals->action_flags |= FLAG(_player_control_accept_bit);
|
||||
}
|
||||
if (primary_trigger > 0.f)
|
||||
if (input->back)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_back_bit);
|
||||
}
|
||||
if (input->primary_trigger > 0.f)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_primary_trigger_bit);
|
||||
}
|
||||
if (TEST_FLAG(input->unit_control_flags, _unit_control_throw_grenade_bit))
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_grenade_trigger_bit);
|
||||
}
|
||||
if (TEST_FLAG(input->player_control_flags, _player_control_input_zoom_bit))
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_zoom_bit);
|
||||
}
|
||||
|
||||
if (facing_delta->pitch > 0.f)
|
||||
if (input->facing_delta.pitch > 0.f)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_look_relative_up_bit);
|
||||
}
|
||||
else if (facing_delta->pitch < 0.f)
|
||||
else if (input->facing_delta.pitch < 0.f)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_look_relative_down_bit);
|
||||
}
|
||||
if (facing_delta->yaw > 0.f)
|
||||
if (input->facing_delta.yaw > 0.f)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_look_relative_left_bit);
|
||||
}
|
||||
else if (facing_delta->yaw < 0.f)
|
||||
else if (input->facing_delta.yaw < 0.f)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_look_relative_right_bit);
|
||||
}
|
||||
@@ -1854,51 +1863,22 @@ void player_control_action_test_note(
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (throttle->i > 0.f)
|
||||
if (input->throttle.i > 0.f)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_move_relative_forward_bit);
|
||||
}
|
||||
else if (throttle->i < 0.f)
|
||||
else if (input->throttle.i < 0.f)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_move_relative_backward_bit);
|
||||
}
|
||||
if (throttle->j > 0.f)
|
||||
if (input->throttle.j > 0.f)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_move_relative_right_bit);
|
||||
}
|
||||
else if (throttle->j < 0.f)
|
||||
else if (input->throttle.j < 0.f)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_move_relative_left_bit);
|
||||
}
|
||||
}
|
||||
|
||||
static void player_control_action_test_check_reset_input_blob(
|
||||
struct input_blob *input)
|
||||
{
|
||||
struct player_control_globals_data *globals;
|
||||
|
||||
if (input->accept && (cinematic_can_be_skipped() || network_coop_skip_offered()))
|
||||
{
|
||||
/* port: in network co-op the players vote to skip (network_coop.c) */
|
||||
if (!network_coop_vote_skip())
|
||||
main_skip_cinematic();
|
||||
}
|
||||
|
||||
player_control_action_test_note(input->unit_control_flags, &input->facing_delta, &input->throttle,
|
||||
input->primary_trigger);
|
||||
globals = player_control_globals;
|
||||
if (input->accept)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_accept_bit);
|
||||
}
|
||||
if (input->back)
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_back_bit);
|
||||
}
|
||||
if (TEST_FLAG(input->player_control_flags, _player_control_input_zoom_bit))
|
||||
{
|
||||
globals->action_flags |= FLAG(_player_control_zoom_bit);
|
||||
}
|
||||
|
||||
if (!TEST_FLAG(globals->action_test_flags, _player_control_action_bit))
|
||||
{
|
||||
|
||||
@@ -236,8 +236,6 @@ symbols in this file:
|
||||
#include "camera/observer.h"
|
||||
#include "data.h"
|
||||
#include "devices/devices.h"
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
#include "cutscene/cinematics.h" /* port: network co-op's first spawns */
|
||||
#include "editor/editor_stubs.h"
|
||||
#include "effects/effects.h"
|
||||
#include "effects/player_effects.h"
|
||||
@@ -272,7 +270,6 @@ symbols in this file:
|
||||
#include "tag_files/tag_groups.h"
|
||||
#include "text/text_group.h"
|
||||
#include "text/unicode.h"
|
||||
#include "objects/object_definitions.h"
|
||||
#include "units/biped_definitions.h"
|
||||
#include "units/bipeds.h"
|
||||
#include "units/units.h"
|
||||
@@ -411,8 +408,6 @@ typedef char players_static_data_screen_flash_offset_assert[
|
||||
|
||||
/* ---------- prototypes */
|
||||
|
||||
static boolean players_respawn_network_coop(
|
||||
void);
|
||||
static long is_player_in_trigger(
|
||||
short bsp_switch_trigger_volume_index,
|
||||
long object_index);
|
||||
@@ -462,40 +457,6 @@ static void player_handle_powerup_equipment(
|
||||
|
||||
/* ---------- globals */
|
||||
|
||||
/* port: network co-op timers, by player absolute index. Each holds the
|
||||
game time plus one at which something started, or 0 when it isn't happening.
|
||||
spawnable_since: when the player became somewhere others can spawn beside
|
||||
(player_spawnable_beside); they wait a while after (players_coop_room_to_spawn). */
|
||||
static struct
|
||||
{
|
||||
long spawnable_since[NETWORK_GAME_MAXIMUM_PLAYER_COUNT];
|
||||
/* when the dead started waiting for a safe teammate to respawn beside */
|
||||
long respawn_wait_since;
|
||||
/* when each player's unit left the loaded structure BSP */
|
||||
long stranded_since[NETWORK_GAME_MAXIMUM_PLAYER_COUNT];
|
||||
/* the structure BSP loaded, and when it last switched, by a trigger,
|
||||
a script or a skipped cutscene (players_coop_bsp_switch_ready) */
|
||||
short structure_bsp_index;
|
||||
long bsp_switch_time;
|
||||
/* the structure BSPs the players have been in this map, by bit */
|
||||
word visited_structure_bsps;
|
||||
/* where each player last stood on the ground, and on which BSP: where
|
||||
everyone comes back when the last checkpoint was on another one */
|
||||
short ground_structure_bsp_index;
|
||||
boolean has_ground_position[NETWORK_GAME_MAXIMUM_PLAYER_COUNT];
|
||||
real_point3d ground_positions[NETWORK_GAME_MAXIMUM_PLAYER_COUNT];
|
||||
} players_coop_state;
|
||||
|
||||
/* port: where each player was at the last checkpoint, in network co-op
|
||||
(players_note_checkpoint) */
|
||||
static struct
|
||||
{
|
||||
boolean valid;
|
||||
short structure_bsp_index;
|
||||
boolean has_position[NETWORK_GAME_MAXIMUM_PLAYER_COUNT];
|
||||
real_point3d positions[NETWORK_GAME_MAXIMUM_PLAYER_COUNT];
|
||||
} players_checkpoint;
|
||||
|
||||
struct players_globals *players_globals;
|
||||
struct data_array *team_data;
|
||||
struct data_array *player_data;
|
||||
@@ -592,9 +553,6 @@ void players_initialize_for_new_map(
|
||||
players_globals->all_dead = FALSE;
|
||||
players_globals->pending_teleport_starting_location_index = NONE;
|
||||
players_globals->respawn_failure = 0;
|
||||
/* port: a new map has no network co-op checkpoint yet */
|
||||
csmemset(&players_checkpoint, 0, sizeof(players_checkpoint));
|
||||
csmemset(&players_coop_state, 0, sizeof(players_coop_state));
|
||||
data_make_valid(player_data);
|
||||
data_make_valid(team_data);
|
||||
csmemset(
|
||||
@@ -749,15 +707,6 @@ short local_player_count(
|
||||
return players_globals->local_player_count;
|
||||
}
|
||||
|
||||
/* port: local_player_count, 0 before the players' globals are made (the
|
||||
input's first polls ask it: port/linux/game/menu_functions.c's
|
||||
pc_menu_split_players) */
|
||||
short players_port_local_player_count(
|
||||
void)
|
||||
{
|
||||
return players_globals ? players_globals->local_player_count : 0;
|
||||
}
|
||||
|
||||
short local_player_get_next(
|
||||
short local_player_index)
|
||||
{
|
||||
@@ -967,9 +916,7 @@ boolean any_player_is_dead(
|
||||
player_data);
|
||||
while ((player = (struct player_datum *)data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
/* port: ignore co-op players still waiting for the first checkpoint,
|
||||
or the checkpoint that lets them in could never be saved */
|
||||
if (player->unit_index == NONE && !players_coop_waiting_to_start(iterator.datum_index))
|
||||
if (player->unit_index == NONE)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -1313,31 +1260,6 @@ long find_best_starting_location_index(
|
||||
return best_starting_location_index;
|
||||
}
|
||||
|
||||
/* port: paints a network co-op player's unit in their profile colour. The
|
||||
campaign Spartan is always green because its tag fixes that colour, which
|
||||
overrides the colour the unit is created with; this replaces fixed colours. */
|
||||
static void players_coop_color_unit(
|
||||
long unit_index,
|
||||
real_rgb_color const *color)
|
||||
{
|
||||
struct object_datum *object = object_get(unit_index);
|
||||
struct object_definition *definition = object_definition_get(object->definition_index);
|
||||
short index;
|
||||
|
||||
for (index = 0; index < definition->object.change_colors.count && index < NUMBER_OF_OBJECT_CHANGE_COLORS; index++)
|
||||
{
|
||||
struct object_change_color_definition *change_color = TAG_BLOCK_GET_ELEMENT(
|
||||
&definition->object.change_colors, index, struct object_change_color_definition);
|
||||
|
||||
/* skip colours driven by a function, like the shield glow */
|
||||
if (change_color->permutations.count > 0 && !change_color->scaled_by && !change_color->darken_by)
|
||||
{
|
||||
object->object.base_change_colors[index] = *color;
|
||||
object->object.outgoing_change_colors[index] = *color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void player_spawn(
|
||||
long player_index)
|
||||
{
|
||||
@@ -1448,8 +1370,6 @@ static void player_spawn(
|
||||
&placement_data,
|
||||
&change_color);
|
||||
unit_index = object_new(&placement_data);
|
||||
if (unit_index != NONE && network_coop_active())
|
||||
players_coop_color_unit(unit_index, &change_color);
|
||||
if (unit_index != NONE)
|
||||
{
|
||||
unit = unit_try_and_get(unit_index);
|
||||
@@ -1878,17 +1798,14 @@ static boolean player_handle_action(
|
||||
switch (player->action_result)
|
||||
{
|
||||
case _player_action_result_touch_device:
|
||||
/* port: on a co-op client the host decides device use; the action is
|
||||
relayed to it (network_coop.c) */
|
||||
if (!network_coop_devices_remote())
|
||||
device_touched(player->action_object_index, player->unit_index);
|
||||
device_touched(player->action_object_index, player->unit_index);
|
||||
result = TRUE;
|
||||
break;
|
||||
|
||||
case _player_action_result_swap_for_powerup:
|
||||
/* port: a distributed client swaps nothing: the host owns inventories
|
||||
and decides pickups, and a remote player's relayed action also reaches
|
||||
here */
|
||||
/* port: a distributed client's inventories are the host's (the
|
||||
powerup is swapped where the host decides pickups, and the relayed
|
||||
action of a remote player reaches here too): it swaps nothing */
|
||||
if (!players_decide_pickups())
|
||||
{
|
||||
result = TRUE;
|
||||
@@ -1990,7 +1907,7 @@ static boolean player_teleport_internal(
|
||||
match_assert(
|
||||
"c:\\halo\\SOURCE\\game\\players.c",
|
||||
0x4FB,
|
||||
source_unit_index==NONE || local_player_count()>1 || game_connection()!=_game_connection_local);
|
||||
source_unit_index==NONE || local_player_count()>1);
|
||||
if (source_unit_index != NONE &&
|
||||
object_get_ultimate_parent(source_unit_index) != source_unit_index)
|
||||
{
|
||||
@@ -2186,12 +2103,11 @@ static boolean player_teleport_internal(
|
||||
else
|
||||
{
|
||||
error(2, "couldn't teleport player into a valid location");
|
||||
/* port: only a local player is pseudo-killed, because the respawn
|
||||
hands the unit back to a local player; another machine's stays put
|
||||
(the Xbox halted for it). Network co-op respawns its own way, so its
|
||||
players stay put too. */
|
||||
if (player->local_player_index != NONE && !network_coop_active())
|
||||
player_pseudo_kill(player_index, source_unit_index);
|
||||
match_assert(
|
||||
"c:\\halo\\SOURCE\\game\\players.c",
|
||||
0x5AB,
|
||||
player->local_player_index!=NONE);
|
||||
player_pseudo_kill(player_index, source_unit_index);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -2390,11 +2306,6 @@ boolean players_respawn_coop(
|
||||
boolean result;
|
||||
boolean dangerous;
|
||||
|
||||
/* port: network co-op checks safety around each teammate instead
|
||||
(players_respawn_network_coop) */
|
||||
if (game_connection() == _game_connection_network_server && network_coop_active())
|
||||
return players_respawn_network_coop();
|
||||
|
||||
players_globals->respawn_failure = 0;
|
||||
result = FALSE;
|
||||
if (!players_globals->respawn_failed)
|
||||
@@ -2490,579 +2401,6 @@ result_complete:
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ---------- port: network co-op
|
||||
|
||||
A network game on a campaign map with no game engine is co-op. Players
|
||||
spawn on the host and respawn beside each other, as in local co-op
|
||||
(players_respawn_coop). When everyone is dead, local co-op reverts to the
|
||||
last checkpoint (game_state_revert), but every machine would have to do
|
||||
that in step. Instead the players respawn where they were at the last
|
||||
checkpoint (main_save_map_private records it), or at the map's start if
|
||||
that was on another BSP. */
|
||||
|
||||
static boolean players_coop_room_to_spawn(
|
||||
void);
|
||||
static boolean player_place_beside_teammate(
|
||||
long player_index);
|
||||
|
||||
/* How long into a level the extra players wait before spawning. The opening
|
||||
cutscene starts a tick or so in, and the first player may still be somewhere
|
||||
with no room beside them (the drop pod on Halo). */
|
||||
#define COOP_LEVEL_START_TICKS (2 * TICKS_PER_SECOND)
|
||||
|
||||
/* Co-op: TRUE while players after the first are held back from spawning:
|
||||
during the level's first seconds, during a cutscene, or until a teammate has
|
||||
been free on foot for a while (players_coop_room_to_spawn). */
|
||||
static boolean players_coop_extras_held(
|
||||
void)
|
||||
{
|
||||
return game_time_get() < COOP_LEVEL_START_TICKS || cinematic_in_progress() || !players_coop_room_to_spawn();
|
||||
}
|
||||
|
||||
/* Co-op host: TRUE if a player who hasn't spawned on this level yet may
|
||||
spawn now. The first player spawns at once (the level's script places them).
|
||||
The others spawn beside a teammate (player_place_beside_teammate) once nothing
|
||||
holds them back (players_coop_extras_held): when the opening cutscene ends or
|
||||
is skipped, or straight away if they join later. */
|
||||
static boolean players_coop_may_spawn(
|
||||
void)
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
if (player->unit_index != NONE)
|
||||
return !players_coop_extras_held();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* TRUE if this player hasn't spawned on the level yet and is being held
|
||||
back (players_coop_extras_held); they spectate a teammate meanwhile */
|
||||
boolean players_coop_waiting_to_start(
|
||||
long player_index)
|
||||
{
|
||||
struct player_datum *player = player_try_and_get(player_index);
|
||||
|
||||
return player && player->unit_index == NONE && player->statistics.deaths == 0 && network_coop_active() &&
|
||||
players_coop_extras_held();
|
||||
}
|
||||
|
||||
/* an enemy projectile this close (world units) makes a teammate unsafe
|
||||
to respawn beside */
|
||||
#define COOP_RESPAWN_DANGER_RADIUS 15.0f
|
||||
/* how often the respawn's safety test runs while someone is dead */
|
||||
#define COOP_RESPAWN_CHECK_TICKS (TICKS_PER_SECOND / 2)
|
||||
/* after waiting this long for a safe teammate, the dead respawn beside any
|
||||
teammate standing on the ground */
|
||||
#define COOP_RESPAWN_FALLBACK_TICKS (10 * TICKS_PER_SECOND)
|
||||
|
||||
/* TRUE if an enemy projectile (a grenade, a plasma bolt) is flying near the
|
||||
unit. Players' own shots and needles stuck in a body don't count. */
|
||||
static boolean players_coop_danger_near(
|
||||
long unit_index)
|
||||
{
|
||||
real_point3d const *center = &object_get(unit_index)->object.bounding_sphere_center;
|
||||
real radius_squared = COOP_RESPAWN_DANGER_RADIUS * COOP_RESPAWN_DANGER_RADIUS;
|
||||
struct object_iterator iterator;
|
||||
struct object_datum *object;
|
||||
|
||||
object_iterator_new(&iterator, _object_mask_projectile, 0);
|
||||
while ((object = object_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
if (object->object.owner_player_index == NONE && object->object.parent_object_index == NONE &&
|
||||
distance_squared3d(center, &object->object.bounding_sphere_center) <= radius_squared)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* TRUE if the unit (or the vehicle it rides) is inside the loaded structure
|
||||
BSP. In network co-op players can be left outside it after a BSP switch. */
|
||||
static boolean players_coop_unit_in_structure(
|
||||
long unit_index)
|
||||
{
|
||||
return object_get(object_get_ultimate_parent(unit_index))->object.location.cluster_index != NONE;
|
||||
}
|
||||
|
||||
/* TRUE if a living player's unit is on solid ground: inside the structure
|
||||
BSP, not airborne and not in a moving vehicle */
|
||||
static boolean players_coop_unit_grounded(
|
||||
long unit_index)
|
||||
{
|
||||
long root_index = object_get_ultimate_parent(unit_index);
|
||||
|
||||
if (!players_coop_unit_in_structure(unit_index))
|
||||
return FALSE;
|
||||
if (root_index == unit_index)
|
||||
{
|
||||
struct biped_datum *biped = biped_try_and_get(unit_index);
|
||||
|
||||
return !biped || !TEST_FLAG(biped->biped.flags, _biped_airborne_bit);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct players_vehicle_datum *vehicle = (struct players_vehicle_datum *)
|
||||
object_try_and_get_and_verify_type(root_index, _object_mask_vehicle);
|
||||
|
||||
return !vehicle || vehicle->vehicle.unknown_state <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* TRUE if it is safe to respawn beside a living player's unit: grounded,
|
||||
not under attack, and no enemy projectile nearby */
|
||||
static boolean players_coop_unit_safe(
|
||||
long unit_index)
|
||||
{
|
||||
return players_coop_unit_grounded(unit_index) && !ai_port_enemies_attacking_unit(unit_index) &&
|
||||
!players_coop_danger_near(unit_index);
|
||||
}
|
||||
|
||||
/* the unit of the first living player that passes `test`, or NONE */
|
||||
static long players_coop_unit_where(
|
||||
boolean (*test)(long unit_index))
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
if (player->unit_index != NONE && test(player->unit_index))
|
||||
return player->unit_index;
|
||||
}
|
||||
|
||||
return NONE;
|
||||
}
|
||||
|
||||
/* Network co-op respawn: the dead come back beside the first teammate who
|
||||
is safe, or after COOP_RESPAWN_FALLBACK_TICKS beside one at least on the
|
||||
ground, so a long fight can't keep them out. The campaign's own test
|
||||
(players_respawn_coop) is map-wide (any projectile or enemy attack
|
||||
anywhere), which with many players spread out would almost never pass.
|
||||
Returns whether everyone waiting came back. */
|
||||
static boolean players_respawn_network_coop(
|
||||
void)
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
long safe_unit_index;
|
||||
boolean result = TRUE;
|
||||
|
||||
if (!players_coop_state.respawn_wait_since)
|
||||
players_coop_state.respawn_wait_since = game_time_get() + 1;
|
||||
/* main.c calls this every tick while someone is dead; the safety test
|
||||
walks all the map's projectiles, so twice a second is enough */
|
||||
if (game_time_get() % COOP_RESPAWN_CHECK_TICKS != 0)
|
||||
return FALSE;
|
||||
safe_unit_index = players_coop_unit_where(players_coop_unit_safe);
|
||||
if (safe_unit_index == NONE &&
|
||||
game_time_get() + 1 - players_coop_state.respawn_wait_since >= COOP_RESPAWN_FALLBACK_TICKS)
|
||||
{
|
||||
safe_unit_index = players_coop_unit_where(players_coop_unit_grounded);
|
||||
}
|
||||
if (safe_unit_index == NONE)
|
||||
return FALSE;
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
/* players still waiting for the level's first checkpoint spawn then */
|
||||
if (player->unit_index != NONE || player->quit_out_of_game ||
|
||||
players_coop_waiting_to_start(iterator.datum_index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
player_spawn(iterator.datum_index);
|
||||
if (player->unit_index == NONE)
|
||||
result = FALSE;
|
||||
/* (beside the safe teammate, else any with room: never left at the
|
||||
level's start, where player_spawn put them) */
|
||||
else if (!player_teleport(iterator.datum_index, safe_unit_index,
|
||||
&object_get(safe_unit_index)->object.bounding_sphere_center) &&
|
||||
!player_place_beside_teammate(iterator.datum_index))
|
||||
{
|
||||
result = FALSE;
|
||||
}
|
||||
}
|
||||
if (result)
|
||||
players_coop_state.respawn_wait_since = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* What a new player can spawn beside: the player's unit on foot, or the
|
||||
vehicle it rides if a player drives it (a Warthog). NONE while scripts hold
|
||||
the controls (Pillar of Autumn's cryo tube) or while it rides an AI vehicle
|
||||
(Silent Cartographer's Pelican), which would put the new player inside. */
|
||||
static long player_spawnable_beside(
|
||||
struct player_datum const *player)
|
||||
{
|
||||
long vehicle_index;
|
||||
long driver_index;
|
||||
|
||||
if (player->unit_index == NONE || !player_input_enabled() || !players_coop_unit_in_structure(player->unit_index))
|
||||
return NONE;
|
||||
vehicle_index = object_get_ultimate_parent(player->unit_index);
|
||||
if (vehicle_index == player->unit_index)
|
||||
return vehicle_index;
|
||||
driver_index = object_try_and_get_and_verify_type(vehicle_index, _object_mask_unit) ?
|
||||
unit_get(vehicle_index)->unit.driver_object_index : NONE;
|
||||
return driver_index != NONE && unit_get(driver_index)->unit.player_index != NONE ? vehicle_index : NONE;
|
||||
}
|
||||
|
||||
/* How long a teammate must have been somewhere spawnable before others
|
||||
spawn beside them, so nobody lands inside a vehicle they just left. */
|
||||
#define COOP_DISEMBARK_TICKS (4 * TICKS_PER_SECOND)
|
||||
|
||||
/* Where the player comes back: their position at the last checkpoint, or,
|
||||
when that was on another structure BSP (everyone died before the next one),
|
||||
where they last stood on the ground on this one. NULL if the player has
|
||||
neither (they joined since); they come back beside a teammate. */
|
||||
static real_point3d const *players_checkpoint_position(
|
||||
long player_index)
|
||||
{
|
||||
short index = DATUM_INDEX_TO_ABSOLUTE_INDEX(player_index);
|
||||
|
||||
if (players_checkpoint.valid && players_checkpoint.structure_bsp_index == global_structure_bsp_index_get())
|
||||
return players_checkpoint.has_position[index] ? &players_checkpoint.positions[index] : NULL;
|
||||
if (players_coop_state.ground_structure_bsp_index == global_structure_bsp_index_get() &&
|
||||
players_coop_state.has_ground_position[index])
|
||||
{
|
||||
return &players_coop_state.ground_positions[index];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* TRUE if a biped stands on something (not airborne), in the BSP or not */
|
||||
static boolean players_coop_unit_grounded_anywhere(
|
||||
long unit_index)
|
||||
{
|
||||
struct biped_datum *biped = biped_try_and_get(unit_index);
|
||||
|
||||
return biped && !TEST_FLAG(biped->biped.flags, _biped_airborne_bit);
|
||||
}
|
||||
|
||||
/* TRUE if the unit is outside the loaded structure BSP, where it would fall
|
||||
forever: on foot, or in a vehicle that is empty or player-driven. AI-driven
|
||||
vehicles are ignored because the intro Pelicans fly outside the BSP. */
|
||||
static boolean players_coop_unit_stranded(
|
||||
long unit_index)
|
||||
{
|
||||
long vehicle_index = object_get_ultimate_parent(unit_index);
|
||||
long driver_index;
|
||||
|
||||
if (players_coop_unit_in_structure(unit_index))
|
||||
return FALSE;
|
||||
/* (on foot: only while falling, not standing on something the BSP
|
||||
doesn't hold, such as an elevator in its shaft) */
|
||||
if (vehicle_index == unit_index)
|
||||
return !players_coop_unit_grounded_anywhere(unit_index);
|
||||
driver_index = object_try_and_get_and_verify_type(vehicle_index, _object_mask_unit) ?
|
||||
unit_get(vehicle_index)->unit.driver_object_index : NONE;
|
||||
return driver_index == NONE || unit_get(driver_index)->unit.player_index != NONE;
|
||||
}
|
||||
|
||||
/* How long a player can be falling outside the BSP before being brought
|
||||
back. A BSP switch settles in a tick or two; waiting longer avoids moving
|
||||
someone who is only crossing a seam or riding over a gap. */
|
||||
#define COOP_STRANDED_TICKS (2 * TICKS_PER_SECOND)
|
||||
|
||||
/* After a BSP switch, how long the co-op host lets no other trigger switch
|
||||
it while the players are brought into the new one, and the longest it then
|
||||
waits for every machine to have loaded it */
|
||||
#define COOP_BSP_SWITCH_SETTLE_TICKS (2 * TICKS_PER_SECOND)
|
||||
#define COOP_BSP_SWITCH_WAIT_TICKS (10 * TICKS_PER_SECOND)
|
||||
|
||||
/* Going back to a BSP the team has already been in needs two thirds of the
|
||||
living players at the trigger: inside it, or this near the player in it
|
||||
(world units) */
|
||||
#define COOP_BACKTRACK_GATHER_DISTANCE 4.0f
|
||||
|
||||
/* Co-op host, each tick: a player stranded for COOP_STRANDED_TICKS is moved
|
||||
beside a grounded teammate inside the BSP, else any teammate inside it, else
|
||||
to the last checkpoint. Skipped while scripts hold the controls, since
|
||||
cutscenes place the players themselves. A BSP switch already moves everyone
|
||||
(players_reconnect_to_structure_bsp); this catches anyone it missed. */
|
||||
static void players_coop_rescue_stranded(
|
||||
void)
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
|
||||
if (!network_coop_active() || game_connection() != _game_connection_network_server)
|
||||
return;
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
long *since = &players_coop_state.stranded_since[DATUM_INDEX_TO_ABSOLUTE_INDEX(iterator.datum_index)];
|
||||
long teammate_index;
|
||||
boolean rescued;
|
||||
|
||||
if (player->unit_index == NONE || !player_input_enabled() || !players_coop_unit_stranded(player->unit_index))
|
||||
{
|
||||
*since = 0;
|
||||
continue;
|
||||
}
|
||||
if (*since == 0)
|
||||
*since = game_time_get() + 1;
|
||||
if (game_time_get() + 1 - *since < COOP_STRANDED_TICKS)
|
||||
continue;
|
||||
teammate_index = players_coop_unit_where(players_coop_unit_grounded);
|
||||
if (teammate_index == NONE)
|
||||
teammate_index = players_coop_unit_where(players_coop_unit_in_structure);
|
||||
if (teammate_index != NONE)
|
||||
{
|
||||
rescued = player_teleport(iterator.datum_index, teammate_index,
|
||||
&object_get(teammate_index)->object.bounding_sphere_center);
|
||||
}
|
||||
else
|
||||
{
|
||||
real_point3d const *position = players_checkpoint_position(iterator.datum_index);
|
||||
|
||||
rescued = position && player_teleport(iterator.datum_index, NONE, position);
|
||||
}
|
||||
if (rescued)
|
||||
*since = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* co-op, each tick: records when each player became somewhere spawnable,
|
||||
and where each last stood on the ground */
|
||||
static void players_coop_note_on_foot(
|
||||
void)
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
|
||||
if (!network_coop_active())
|
||||
return;
|
||||
if (players_coop_state.ground_structure_bsp_index != global_structure_bsp_index_get())
|
||||
{
|
||||
players_coop_state.ground_structure_bsp_index = global_structure_bsp_index_get();
|
||||
csmemset(players_coop_state.has_ground_position, 0, sizeof(players_coop_state.has_ground_position));
|
||||
}
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
short index = DATUM_INDEX_TO_ABSOLUTE_INDEX(iterator.datum_index);
|
||||
long *since = &players_coop_state.spawnable_since[index];
|
||||
|
||||
if (player_spawnable_beside(player) == NONE)
|
||||
*since = 0;
|
||||
else if (*since == 0)
|
||||
*since = game_time_get() + 1;
|
||||
if (player->unit_index != NONE && object_get_ultimate_parent(player->unit_index) == player->unit_index &&
|
||||
players_coop_unit_grounded(player->unit_index))
|
||||
{
|
||||
players_coop_state.ground_positions[index] = object_get(player->unit_index)->object.bounding_sphere_center;
|
||||
players_coop_state.has_ground_position[index] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Co-op host: TRUE if a new player has somewhere to spawn, i.e. a teammate
|
||||
who has been spawnable for COOP_DISEMBARK_TICKS. While every teammate rides
|
||||
an AI vehicle (Silent Cartographer's Pelican) or is held by the scripts
|
||||
(Pillar of Autumn's cryo tube), the others spectate. */
|
||||
static boolean players_coop_room_to_spawn(
|
||||
void)
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
long since = players_coop_state.spawnable_since[DATUM_INDEX_TO_ABSOLUTE_INDEX(iterator.datum_index)];
|
||||
|
||||
if (player_spawnable_beside(player) != NONE && since != 0 && game_time_get() + 1 - since >= COOP_DISEMBARK_TICKS)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Co-op host: moves a newly spawned player beside a teammate who is on foot
|
||||
or in a player-driven vehicle (player_spawnable_beside). player_teleport tries
|
||||
a few spots around each. Teammates are tried in turn so a big lobby spreads
|
||||
out. FALSE if nobody has room; the player stays where they spawned. */
|
||||
static boolean player_place_beside_teammate(
|
||||
long player_index)
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *other;
|
||||
|
||||
if (player_get(player_index)->unit_index == NONE)
|
||||
return FALSE;
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((other = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
if (iterator.datum_index != player_index && player_spawnable_beside(other) != NONE &&
|
||||
player_teleport(player_index, other->unit_index, &object_get(other->unit_index)->object.bounding_sphere_center))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void players_note_checkpoint(
|
||||
void)
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
|
||||
csmemset(&players_checkpoint, 0, sizeof(players_checkpoint));
|
||||
players_checkpoint.structure_bsp_index = global_structure_bsp_index_get();
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
short index = DATUM_INDEX_TO_ABSOLUTE_INDEX(iterator.datum_index);
|
||||
|
||||
if (player->unit_index != NONE)
|
||||
{
|
||||
players_checkpoint.positions[index] = object_get(player->unit_index)->object.bounding_sphere_center;
|
||||
players_checkpoint.has_position[index] = TRUE;
|
||||
players_checkpoint.valid = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Everyone back after all died: each where they were at the last checkpoint
|
||||
(players_checkpoint_position), then those with no such spot beside the
|
||||
others. Players who come back are spawned first, at the level's start. */
|
||||
void players_respawn_at_checkpoint(
|
||||
void)
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
boolean unplaced[NETWORK_GAME_MAXIMUM_PLAYER_COUNT] = { FALSE };
|
||||
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
real_point3d const *position;
|
||||
|
||||
if (player->unit_index != NONE || player->quit_out_of_game)
|
||||
continue;
|
||||
player_spawn(iterator.datum_index);
|
||||
position = players_checkpoint_position(iterator.datum_index);
|
||||
unplaced[DATUM_INDEX_TO_ABSOLUTE_INDEX(iterator.datum_index)] = player->unit_index != NONE &&
|
||||
!(position && player_teleport(iterator.datum_index, NONE, position));
|
||||
}
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
if (unplaced[DATUM_INDEX_TO_ABSOLUTE_INDEX(iterator.datum_index)])
|
||||
player_place_beside_teammate(iterator.datum_index);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: whether the unit stands in a trigger that switches away from the
|
||||
loaded structure BSP */
|
||||
static boolean players_coop_in_bsp_switch_trigger(
|
||||
long unit_index)
|
||||
{
|
||||
struct scenario *scenario = global_scenario_get();
|
||||
short index;
|
||||
|
||||
for (index = 0; index < scenario->bsp_switch_trigger_volumes.count; index++)
|
||||
{
|
||||
struct scenario_bsp_switch_trigger_volume *volume = TAG_BLOCK_GET_ELEMENT(
|
||||
&scenario->bsp_switch_trigger_volumes, index, struct scenario_bsp_switch_trigger_volume);
|
||||
|
||||
if (volume->source_structure_bsp_index == global_structure_bsp_index &&
|
||||
scenario_trigger_volume_test_object(volume->trigger_volume_index, unit_index))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* port: whether a network co-op host lets any trigger switch the BSP yet.
|
||||
After a switch it waits COOP_BSP_SWITCH_SETTLE_TICKS while the players are
|
||||
brought in, then until every player's machine has loaded the new BSP, so no
|
||||
client falls two switches behind. A client stuck longer than
|
||||
COOP_BSP_SWITCH_WAIT_TICKS no longer holds the team. */
|
||||
static boolean players_coop_bsp_switch_ready(
|
||||
void)
|
||||
{
|
||||
long since = game_time_get() - players_coop_state.bsp_switch_time;
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
|
||||
if (since < COOP_BSP_SWITCH_SETTLE_TICKS)
|
||||
return FALSE;
|
||||
if (since >= COOP_BSP_SWITCH_WAIT_TICKS)
|
||||
return TRUE;
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
if (player->unit_index != NONE && !network_coop_player_has_structure_bsp(iterator.datum_index))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* port: whether a network co-op host lets the unit on this trigger switch
|
||||
the BSP, which brings the whole team along. Not while the unit's machine
|
||||
is still loading the BSP the host has. Anyone can lead the team into a BSP
|
||||
it hasn't been in; going back to one needs two thirds of the team there,
|
||||
so one player can't drag everyone back through the level. */
|
||||
static boolean players_coop_bsp_switch_allowed(
|
||||
short bsp_switch_trigger_volume_index,
|
||||
long unit_index)
|
||||
{
|
||||
struct scenario_bsp_switch_trigger_volume *volume = TAG_BLOCK_GET_ELEMENT(
|
||||
&global_scenario_get()->bsp_switch_trigger_volumes, bsp_switch_trigger_volume_index,
|
||||
struct scenario_bsp_switch_trigger_volume);
|
||||
real_point3d const *center = &object_get(unit_index)->object.bounding_sphere_center;
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
short living_count = 0;
|
||||
short gathered_count = 0;
|
||||
|
||||
if (!network_coop_player_has_structure_bsp(unit_get(unit_index)->unit.player_index))
|
||||
return FALSE;
|
||||
if (!VALID_INDEX(volume->destination_structure_bsp_index, MAXIMUM_STRUCTURE_BSPS_PER_SCENARIO) ||
|
||||
!TEST_FLAG(players_coop_state.visited_structure_bsps, volume->destination_structure_bsp_index))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
struct object_datum *teammate = object_try_and_get(player->unit_index);
|
||||
|
||||
if (!teammate || TEST_FLAG(teammate->object.damage_flags, _object_dead_bit))
|
||||
continue;
|
||||
living_count++;
|
||||
if (scenario_trigger_volume_test_object(volume->trigger_volume_index, player->unit_index) ||
|
||||
distance_squared3d(center, &teammate->object.bounding_sphere_center) <=
|
||||
COOP_BACKTRACK_GATHER_DISTANCE * COOP_BACKTRACK_GATHER_DISTANCE)
|
||||
{
|
||||
gathered_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return gathered_count * 3 >= living_count * 2;
|
||||
}
|
||||
|
||||
static void player_teleport_on_bsp_switch(
|
||||
long player_index,
|
||||
long source_unit_index,
|
||||
@@ -3084,16 +2422,7 @@ static void player_teleport_on_bsp_switch(
|
||||
position);
|
||||
if (biped)
|
||||
{
|
||||
/* port: in network co-op a loading zone brings the whole team to
|
||||
whoever crossed it, as split screen does. Only one BSP is ever
|
||||
loaded, so anyone left elsewhere would stand outside it, or on a
|
||||
trigger that switches it straight back (players_update_after_game
|
||||
waits for players_coop_bsp_switch_ready before another switch). */
|
||||
if (network_coop_active())
|
||||
{
|
||||
outside_switch_trigger = TRUE;
|
||||
}
|
||||
else if (players_globals->pending_teleport_starting_location_index != NONE &&
|
||||
if (players_globals->pending_teleport_starting_location_index != NONE &&
|
||||
!scenario_trigger_volume_test_object(
|
||||
TAG_BLOCK_GET_ELEMENT(
|
||||
&global_scenario_get()->bsp_switch_trigger_volumes,
|
||||
@@ -3128,10 +2457,6 @@ static void player_teleport_on_bsp_switch(
|
||||
player_index,
|
||||
source_unit_index,
|
||||
position);
|
||||
/* port: a big co-op team runs out of room around the
|
||||
crosser, so the rest go beside whoever is already in */
|
||||
if (!teleport_succeeded && network_coop_active())
|
||||
teleport_succeeded = player_place_beside_teammate(player_index);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3156,13 +2481,13 @@ void players_reconnect_to_structure_bsp(
|
||||
long source_unit_index;
|
||||
boolean teleport_position_valid;
|
||||
boolean found_player;
|
||||
short local_player_index;
|
||||
short cutscene_flag_index;
|
||||
long player_index;
|
||||
long player_unit_index;
|
||||
|
||||
/* port: also on a network co-op host, whose other players are remote */
|
||||
if (players_globals->pending_teleport_starting_location_index != NONE &&
|
||||
(players_globals->local_player_count > 1 ||
|
||||
(network_coop_active() && game_connection() == _game_connection_network_server)))
|
||||
players_globals->local_player_count > 1)
|
||||
{
|
||||
scenario = global_scenario_get();
|
||||
bsp_switch = TAG_BLOCK_GET_ELEMENT(
|
||||
@@ -3242,46 +2567,28 @@ void players_reconnect_to_structure_bsp(
|
||||
}
|
||||
}
|
||||
|
||||
/* port: in network co-op, if whoever crossed isn't in the new BSP yet,
|
||||
the others gather to a player who is and stands on no switch trigger,
|
||||
so nobody is left to switch it straight back */
|
||||
if (!found_player && network_coop_active())
|
||||
{
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL && !found_player)
|
||||
{
|
||||
struct object_datum *unit = object_try_and_get(player->unit_index);
|
||||
|
||||
if (unit && scenario_leaf_index_from_point(&unit->object.bounding_sphere_center) != NONE &&
|
||||
!players_coop_in_bsp_switch_trigger(player->unit_index))
|
||||
{
|
||||
source_unit_index = player->unit_index;
|
||||
teleport_position = unit->object.position;
|
||||
found_player = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match_vassert(
|
||||
"c:\\halo\\SOURCE\\game\\players.c",
|
||||
0x63E,
|
||||
found_player,
|
||||
"no players in the bsp");
|
||||
/* port: all players, not just local ones (the same set in split screen) */
|
||||
if (found_player)
|
||||
{
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
local_player_index = local_player_get_next(NONE);
|
||||
while (local_player_index != NONE)
|
||||
{
|
||||
player_index = local_player_get_player_index(local_player_index);
|
||||
player = player_get(player_index);
|
||||
if (player->unit_index != NONE &&
|
||||
player->unit_index != source_unit_index)
|
||||
{
|
||||
player_teleport_on_bsp_switch(
|
||||
iterator.datum_index,
|
||||
player_index,
|
||||
source_unit_index,
|
||||
&teleport_position);
|
||||
player->cluster_index = NONE;
|
||||
player_get(player_index)->cluster_index = NONE;
|
||||
}
|
||||
local_player_index = local_player_get_next(local_player_index);
|
||||
}
|
||||
}
|
||||
players_globals->pending_teleport_starting_location_index = NONE;
|
||||
@@ -4325,8 +3632,6 @@ void players_update_before_game(
|
||||
short action_index;
|
||||
|
||||
profile_enter(PLAYERS_UPDATE_BEFORE_GAME_PROFILE);
|
||||
players_coop_note_on_foot();
|
||||
players_coop_rescue_stranded();
|
||||
if (update_client_dequeue(actions))
|
||||
{
|
||||
data_iterator_new(&iterator, player_data);
|
||||
@@ -4397,24 +3702,10 @@ void players_update_before_game(
|
||||
else if (network_game_distributed_client())
|
||||
game_engine_client_respawn_countdown(iterator.datum_index);
|
||||
}
|
||||
/* port: in co-op only the host spawns players; clients get their units
|
||||
from the network. One who quit doesn't come back. */
|
||||
else if (!main_menu_is_active() && !network_game_distributed_client() && !player->quit_out_of_game)
|
||||
else if (!main_menu_is_active())
|
||||
{
|
||||
if (player->statistics.deaths == 0)
|
||||
{
|
||||
/* port: in network co-op the first player spawns at the map's
|
||||
start; everyone else, late joiners included, spawns beside a
|
||||
player already in the game once nothing holds them back
|
||||
(players_coop_may_spawn) */
|
||||
if (game_connection() != _game_connection_network_server)
|
||||
player_spawn(iterator.datum_index);
|
||||
else if (players_coop_may_spawn())
|
||||
{
|
||||
player_spawn(iterator.datum_index);
|
||||
player_place_beside_teammate(iterator.datum_index);
|
||||
}
|
||||
}
|
||||
player_spawn(iterator.datum_index);
|
||||
else if (!players_globals->all_dead)
|
||||
main_respawn(players_globals->respawn_failed);
|
||||
}
|
||||
@@ -4626,13 +3917,6 @@ void players_update_after_game(
|
||||
if (players_globals->double_speed_ticks == 0)
|
||||
game_set_players_are_double_speed(FALSE);
|
||||
}
|
||||
if (VALID_INDEX(global_structure_bsp_index, MAXIMUM_STRUCTURE_BSPS_PER_SCENARIO))
|
||||
SET_FLAG(players_coop_state.visited_structure_bsps, global_structure_bsp_index, TRUE);
|
||||
if (players_coop_state.structure_bsp_index != global_structure_bsp_index)
|
||||
{
|
||||
players_coop_state.structure_bsp_index = global_structure_bsp_index;
|
||||
players_coop_state.bsp_switch_time = game_time_get();
|
||||
}
|
||||
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while (player = data_iterator_next(&iterator))
|
||||
@@ -4679,11 +3963,7 @@ void players_update_after_game(
|
||||
{
|
||||
root_object_index = object_get_ultimate_parent(player->unit_index);
|
||||
root_object = object_get(root_object_index);
|
||||
/* port: a co-op client only switches BSP when the host does
|
||||
(network_distributed.c), and the host not again until everyone
|
||||
has the new one */
|
||||
if (!TEST_FLAG(root_object->object.flags, _object_outside_of_map_bit) &&
|
||||
!(network_coop_active() && (network_game_distributed_client() || !players_coop_bsp_switch_ready())))
|
||||
if (!TEST_FLAG(root_object->object.flags, _object_outside_of_map_bit))
|
||||
{
|
||||
scenario = global_scenario_get();
|
||||
for (bsp_switch_trigger_volume_index = 0;
|
||||
@@ -4698,9 +3978,7 @@ void players_update_after_game(
|
||||
global_structure_bsp_index &&
|
||||
scenario_trigger_volume_test_object(
|
||||
bsp_switch_trigger_volume->trigger_volume_index,
|
||||
player->unit_index) &&
|
||||
(!network_coop_active() ||
|
||||
players_coop_bsp_switch_allowed(bsp_switch_trigger_volume_index, player->unit_index)))
|
||||
player->unit_index))
|
||||
{
|
||||
if (players_globals->local_player_triggered_switch !=
|
||||
_local_player_triggered_switch_none &&
|
||||
@@ -4719,7 +3997,6 @@ void players_update_after_game(
|
||||
bsp_switch_trigger_volume_index;
|
||||
main_switch_structure_bsp(
|
||||
bsp_switch_trigger_volume->destination_structure_bsp_index);
|
||||
players_coop_state.bsp_switch_time = game_time_get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4743,7 +4020,7 @@ void players_update_after_game(
|
||||
|
||||
if (players_globals->all_dead)
|
||||
{
|
||||
if (!game_engine_running() && !players_lost_map_started && !network_game_distributed_client())
|
||||
if (!game_engine_running() && !players_lost_map_started)
|
||||
{
|
||||
main_lost_map();
|
||||
players_lost_map_started = TRUE;
|
||||
@@ -4844,17 +4121,6 @@ boolean player_name_valid(
|
||||
return FALSE;
|
||||
for (index = 0; index < count && (cleaned[index] || name[index]); index++)
|
||||
{
|
||||
/* (but the spaces it trims from the end: the Xbox's menus pad a new
|
||||
profile's name, "New001 ") */
|
||||
if (!cleaned[index])
|
||||
{
|
||||
for (; index < count && name[index]; index++)
|
||||
{
|
||||
if (name[index] != ' ')
|
||||
return FALSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (cleaned[index] != name[index])
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -295,11 +295,6 @@ void player_control_new_unit(
|
||||
long unit_index);
|
||||
void player_control_action_test_reset(
|
||||
void);
|
||||
void player_control_action_test_note(
|
||||
unsigned long unit_control_flags,
|
||||
real_euler_angles2d const *facing_delta,
|
||||
real_vector2d const *throttle,
|
||||
real primary_trigger);
|
||||
boolean player_control_action_test_accept(
|
||||
void);
|
||||
boolean player_control_action_test_back(
|
||||
@@ -378,17 +373,6 @@ short players_get_respawn_failure(
|
||||
|
||||
boolean players_respawn_coop(
|
||||
void);
|
||||
/* port: network co-op checkpoints (players.c): remember where the players
|
||||
were at the last checkpoint, and respawn everyone there. Players other than
|
||||
the first are held back from spawning on a new level: until its opening
|
||||
cutscene ends and a teammate has been free on foot a few seconds (not
|
||||
riding a vehicle, nor held by the scripts). */
|
||||
boolean players_coop_waiting_to_start(
|
||||
long player_index);
|
||||
void players_note_checkpoint(
|
||||
void);
|
||||
void players_respawn_at_checkpoint(
|
||||
void);
|
||||
void players_reconnect_to_structure_bsp(
|
||||
void);
|
||||
|
||||
|
||||
@@ -14088,29 +14088,6 @@ static boolean hs_expression_changes_no_game(
|
||||
static boolean hs_compile_and_evaluate_command(
|
||||
char const *expression);
|
||||
|
||||
/* port: the text after the host's command `word` ("ban", "kick") that an
|
||||
expression starts with (spaces and an opening parenthesis before it, in
|
||||
either case, then a space or the end), else NULL */
|
||||
static char const *hs_host_player_command(
|
||||
char const *expression,
|
||||
char const *word)
|
||||
{
|
||||
char const *text = expression;
|
||||
long index;
|
||||
|
||||
while (*text == ' ' || *text == '\t' || *text == '(')
|
||||
text++;
|
||||
for (index = 0; word[index]; index++)
|
||||
{
|
||||
char character = text[index] >= 'A' && text[index] <= 'Z' ? text[index] - 'A' + 'a' : text[index];
|
||||
|
||||
if (character != word[index])
|
||||
return NULL;
|
||||
}
|
||||
text += index;
|
||||
return *text == ' ' || *text == '\t' || *text == 0 ? text : NULL;
|
||||
}
|
||||
|
||||
/* port: a command someone typed (the console, the telnet console, a cheat
|
||||
button, init.txt): what it logs is its answer, shown whatever
|
||||
config.toml's game.console_log is (terminal_command_running) */
|
||||
@@ -14145,20 +14122,20 @@ static boolean hs_compile_and_evaluate_command(
|
||||
console_warning("not while playing in another's game: the host decides the game");
|
||||
return FALSE;
|
||||
}
|
||||
/* port: the host's ban and kick commands ("ban <player name>", "kick
|
||||
<player name>", or the name's start: Tab completes it), which are no
|
||||
script's */
|
||||
/* port: the host's ban command ("ban <player name>", or its start: Tab
|
||||
completes it), which is no script's */
|
||||
{
|
||||
char const *text = hs_host_player_command(expression, "ban");
|
||||
boolean kick = FALSE;
|
||||
char const *text = expression;
|
||||
|
||||
if (!text && (text = hs_host_player_command(expression, "kick")) != NULL)
|
||||
kick = TRUE;
|
||||
if (text)
|
||||
while (*text == ' ' || *text == '\t' || *text == '(')
|
||||
text++;
|
||||
if ((text[0] == 'b' || text[0] == 'B') && (text[1] == 'a' || text[1] == 'A') &&
|
||||
(text[2] == 'n' || text[2] == 'N') && (text[3] == ' ' || text[3] == '\t' || text[3] == 0))
|
||||
{
|
||||
char name[64];
|
||||
long length = 0;
|
||||
|
||||
text += 3;
|
||||
while (*text == ' ' || *text == '\t' || *text == '"')
|
||||
text++;
|
||||
while (*text && *text != '"' && *text != ')' && length < (long)sizeof(name) - 1)
|
||||
@@ -14166,7 +14143,7 @@ static boolean hs_compile_and_evaluate_command(
|
||||
while (length > 0 && (name[length - 1] == ' ' || name[length - 1] == '\t'))
|
||||
length--;
|
||||
name[length] = 0;
|
||||
return kick ? network_game_server_kick_player(name) : network_game_server_ban_player(name);
|
||||
return network_game_server_ban_player(name);
|
||||
}
|
||||
}
|
||||
csstrncpy(buffer, expression, sizeof(buffer));
|
||||
|
||||
@@ -179,9 +179,6 @@ void hs_runtime_initialize(
|
||||
void);
|
||||
void hs_runtime_initialize_for_new_map(
|
||||
void);
|
||||
/* port: network co-op (port/linux/game/network_coop.c) */
|
||||
void hs_runtime_port_shift_sleep_times(
|
||||
long ticks);
|
||||
void hs_runtime_update(
|
||||
void);
|
||||
long hs_runtime_evaluate(
|
||||
|
||||
@@ -104,7 +104,6 @@ symbols in this file:
|
||||
#include "sound/sound_definitions.h"
|
||||
#include "units/units.h"
|
||||
#include "object_lists.h"
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -675,8 +674,6 @@ void hs_effect_new(
|
||||
struct scenario_cutscene_flag *cutscene_flag;
|
||||
real_vector3d forward;
|
||||
|
||||
/* port: and on network co-op's clients (port/linux/game/network_coop.c) */
|
||||
network_coop_note_effect(effect_definition_index, cutscene_flag_index);
|
||||
cutscene_flag = TAG_BLOCK_GET_ELEMENT(
|
||||
&global_scenario_get()->cutscene_flags,
|
||||
cutscene_flag_index,
|
||||
@@ -710,9 +707,6 @@ void hs_effect_new_from_object_marker(
|
||||
{
|
||||
struct object_marker marker;
|
||||
|
||||
/* port: and on network co-op's clients (port/linux/game/network_coop.c) */
|
||||
network_coop_note_object_effect(effect_definition_index, object_index, marker_name);
|
||||
|
||||
if (object_get_marker_by_name(
|
||||
object_index,
|
||||
marker_name,
|
||||
@@ -1004,9 +998,6 @@ void hs_teleport_players_not_in_trigger_volume(
|
||||
short cutscene_flag_index)
|
||||
{
|
||||
long player_index;
|
||||
/* port: in network co-op the first player moved goes to the flag and
|
||||
the rest around them, instead of all into the same spot */
|
||||
long first_unit_index = NONE;
|
||||
|
||||
for (player_index = data_next_index(player_data, NONE);
|
||||
player_index != NONE;
|
||||
@@ -1020,17 +1011,11 @@ void hs_teleport_players_not_in_trigger_volume(
|
||||
trigger_volume_index,
|
||||
player->unit_index))
|
||||
{
|
||||
if (first_unit_index != NONE && network_coop_active())
|
||||
{
|
||||
player_teleport(player_index, first_unit_index, &object_get(first_unit_index)->object.position);
|
||||
continue;
|
||||
}
|
||||
hs_object_orient(
|
||||
player->unit_index,
|
||||
cutscene_flag_index,
|
||||
TRUE,
|
||||
TRUE);
|
||||
first_unit_index = player->unit_index;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1120,24 +1120,6 @@ void render_debug_trigger_volumes(
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: moves every sleeping script thread's wake time on by `ticks`, after
|
||||
a network co-op host reverts but keeps its clock (network_coop.c) */
|
||||
void hs_runtime_port_shift_sleep_times(
|
||||
long ticks)
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct hs_thread_datum *thread;
|
||||
|
||||
data_iterator_new(&iterator, hs_thread_data);
|
||||
while ((thread = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
if (thread->sleep_until > 0)
|
||||
thread->sleep_until += ticks;
|
||||
if (thread->previous_sleep_until > 0)
|
||||
thread->previous_sleep_until += ticks;
|
||||
}
|
||||
}
|
||||
|
||||
void hs_runtime_update(
|
||||
void)
|
||||
{
|
||||
|
||||
@@ -107,8 +107,6 @@ symbols in this file:
|
||||
#include "sound/game_sound.h"
|
||||
#include "tag_files/tag_files.h"
|
||||
#include "text/draw_string.h"
|
||||
#include "coop_spectate.h" /* port: port/linux/game/coop_spectate.c */
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
#include "text/font_group.h"
|
||||
#include "text/text_group.h"
|
||||
#include "units/unit_definitions.h"
|
||||
@@ -873,7 +871,6 @@ void hud_autosave(
|
||||
: hud_globals->checkpoint_end_index;
|
||||
short local_player_index;
|
||||
|
||||
network_coop_note_hud(_coop_hud_checkpoint, active);
|
||||
scripted_hud_messages_clear();
|
||||
if (active && hud_globals->checkpoint_sound.index != NONE)
|
||||
{
|
||||
@@ -1002,7 +999,7 @@ static void hud_draw_players(
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: in multiplayer and network co-op, players' names above their heads
|
||||
/* port: in multiplayer, players' names above their heads
|
||||
(display.player_names: "all", "allies", "enemies" or "none"). An ally's goes
|
||||
above the triangle the game draws over teammates; an enemy's only within the
|
||||
motion sensor's reach, while the view sees them and they are not
|
||||
@@ -1275,8 +1272,7 @@ static void hud_draw_player_names_now(
|
||||
if ((setting == _player_names_none && !vr_apart) || player_index == NONE)
|
||||
return;
|
||||
team_index = player_get(player_index)->team_index;
|
||||
/* the campaign always draws teammate triangles (hud_draw_players) */
|
||||
indicators = game_engine_display_team_indicators() || !game_engine_running();
|
||||
indicators = game_engine_display_team_indicators();
|
||||
enemy_range = hud_player_name_enemy_range();
|
||||
/* (the players the motion tracker would show this local player) */
|
||||
game_engine_motion_sensor_viewer(render.local_player_index);
|
||||
@@ -1447,15 +1443,9 @@ void hud_draw_screen(
|
||||
hud_draw_players();
|
||||
}
|
||||
|
||||
/* port: players' names above their heads, in multiplayer and network co-op */
|
||||
if ((game_engine_running() || network_coop_active()) && !cinematic_in_progress())
|
||||
/* port: players' names above their heads, in multiplayer */
|
||||
if (game_engine_running() && !cinematic_in_progress())
|
||||
hud_draw_player_names();
|
||||
/* port: who a dead network co-op player is watching */
|
||||
if (player->unit_index == NONE && coop_spectating() && !cinematic_in_progress())
|
||||
coop_spectate_draw(render.local_player_index);
|
||||
/* port: the network co-op vote to skip a cinematic */
|
||||
if (cinematic_in_progress())
|
||||
coop_skip_vote_draw(render.local_player_index);
|
||||
|
||||
if (!game_time_get_paused() &&
|
||||
render.local_player_index == local_player_get_next(NONE))
|
||||
|
||||
@@ -145,7 +145,6 @@ symbols in this file:
|
||||
#include "text/font_group.h"
|
||||
#include "text/text_group.h"
|
||||
#include "text/unicode.h"
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -607,7 +606,6 @@ void scripted_hud_set_state_message(
|
||||
{
|
||||
struct scenario *scenario = global_scenario_get();
|
||||
|
||||
network_coop_note_hud(_coop_hud_help_text, message_index);
|
||||
if (hud_scripted_globals->show_hud_help_text &&
|
||||
scenario->hud_messages.index != NONE)
|
||||
{
|
||||
@@ -628,7 +626,6 @@ void scripted_hud_set_flashing_state(
|
||||
{
|
||||
long time;
|
||||
|
||||
network_coop_note_hud(_coop_hud_help_flash, flash);
|
||||
if (flash && !hud_messaging_globals->use_flash)
|
||||
{
|
||||
time = game_time_get();
|
||||
@@ -659,7 +656,6 @@ void scripted_hud_set_objective(
|
||||
{
|
||||
struct scenario *scenario = global_scenario_get();
|
||||
|
||||
network_coop_note_hud(_coop_hud_objective, message_index);
|
||||
if (scenario->hud_messages.index != NONE)
|
||||
{
|
||||
struct hud_message_text_definition *hud_messages =
|
||||
@@ -741,47 +737,6 @@ void scripted_hud_show_timer(
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: the script timer and the scenario's message count, for network
|
||||
co-op (port/linux/game/network_coop.c) */
|
||||
void hud_messaging_port_timer_get(
|
||||
struct hud_timer_state *state)
|
||||
{
|
||||
struct hud_timer_data_definition const *timer = &hud_messaging_globals->timer;
|
||||
|
||||
state->reference_time = timer->reference_time;
|
||||
state->ticks = timer->ticks;
|
||||
state->flash_cutoff = timer->flash_cutoff;
|
||||
state->x = timer->position.n[0];
|
||||
state->y = timer->position.n[1];
|
||||
state->corner = timer->corner;
|
||||
state->paused = timer->paused;
|
||||
state->enabled = timer->enabled;
|
||||
}
|
||||
|
||||
void hud_messaging_port_timer_set(
|
||||
struct hud_timer_state const *state)
|
||||
{
|
||||
struct hud_timer_data_definition *timer = &hud_messaging_globals->timer;
|
||||
|
||||
timer->reference_time = state->reference_time;
|
||||
timer->ticks = state->ticks;
|
||||
timer->flash_cutoff = state->flash_cutoff;
|
||||
timer->position.n[0] = state->x;
|
||||
timer->position.n[1] = state->y;
|
||||
timer->corner = PIN(state->corner, 0, 4);
|
||||
timer->paused = state->paused;
|
||||
timer->enabled = state->enabled;
|
||||
}
|
||||
|
||||
short hud_messaging_port_message_count(
|
||||
void)
|
||||
{
|
||||
struct scenario *scenario = global_scenario_get();
|
||||
|
||||
return scenario->hud_messages.index != NONE ?
|
||||
(short)HUD_MESSAGE_TEXT_DEFINITION_GET(scenario->hud_messages.index)->messages.count : 0;
|
||||
}
|
||||
|
||||
void scripted_hud_pause_timer(
|
||||
boolean paused)
|
||||
{
|
||||
@@ -1174,7 +1129,6 @@ void scripted_hud_messages_clear(
|
||||
struct hud_messaging_datum_definition *datum = hud_messaging_globals->message_data;
|
||||
long datum_count = NUMBER_OF_HUD_MESSAGING_DATUMS;
|
||||
|
||||
network_coop_note_hud(_coop_hud_messages_clear, 0);
|
||||
do
|
||||
{
|
||||
struct hud_message_definition *message = datum->messages;
|
||||
|
||||
@@ -27,24 +27,6 @@ void hud_messaging_dispose_from_old_map(
|
||||
void);
|
||||
void hud_messaging_dispose(
|
||||
void);
|
||||
/* port: network co-op (port/linux/game/network_coop.c) */
|
||||
struct hud_timer_state
|
||||
{
|
||||
long reference_time;
|
||||
short ticks;
|
||||
short flash_cutoff;
|
||||
short x, y;
|
||||
short corner;
|
||||
boolean paused;
|
||||
boolean enabled;
|
||||
};
|
||||
void hud_messaging_port_timer_get(
|
||||
struct hud_timer_state *state);
|
||||
void hud_messaging_port_timer_set(
|
||||
struct hud_timer_state const *state);
|
||||
short hud_messaging_port_message_count(
|
||||
void);
|
||||
|
||||
void scripted_hud_set_state_message(
|
||||
short message_index);
|
||||
void hud_messaging_globals_update(
|
||||
|
||||
@@ -123,7 +123,6 @@ symbols in this file:
|
||||
#include "scenario/scenario.h"
|
||||
#include "scenario/scenario_definitions.h"
|
||||
#include "units/units.h"
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -497,7 +496,6 @@ void hud_activate_team_nav_point_with_flag(
|
||||
short flag_index,
|
||||
float vertical_offset)
|
||||
{
|
||||
network_coop_note_nav_point(_coop_nav_point_team_flag, nav_index, team_index, flag_index, vertical_offset);
|
||||
hud_activate_team_nav_point(nav_index, team_index, _hud_nav_point_type_flag, flag_index, vertical_offset);
|
||||
|
||||
return;
|
||||
@@ -509,7 +507,6 @@ void hud_activate_team_nav_point_with_object(
|
||||
long object_index,
|
||||
float vertical_offset)
|
||||
{
|
||||
network_coop_note_nav_point(_coop_nav_point_team_object, nav_index, team_index, object_index, vertical_offset);
|
||||
hud_activate_team_nav_point(nav_index, team_index, _hud_nav_point_type_object, object_index, vertical_offset);
|
||||
|
||||
return;
|
||||
@@ -556,7 +553,6 @@ void hud_deactivate_team_nav_point_with_flag(
|
||||
short team_index,
|
||||
short flag_index)
|
||||
{
|
||||
network_coop_note_nav_point(_coop_nav_point_team_flag, NONE, team_index, flag_index, 0.0f);
|
||||
hud_deactivate_team_nav_point(team_index, _hud_nav_point_type_flag, flag_index);
|
||||
|
||||
return;
|
||||
@@ -566,7 +562,6 @@ void hud_deactivate_team_nav_point_with_object(
|
||||
short team_index,
|
||||
long object_index)
|
||||
{
|
||||
network_coop_note_nav_point(_coop_nav_point_team_object, NONE, team_index, object_index, 0.0f);
|
||||
hud_deactivate_team_nav_point(team_index, _hud_nav_point_type_object, object_index);
|
||||
|
||||
return;
|
||||
@@ -580,7 +575,6 @@ void hud_unit_activate_nav_point_with_flag(
|
||||
{
|
||||
long player_index = player_index_from_unit_index(unit_index);
|
||||
|
||||
network_coop_note_nav_point(_coop_nav_point_unit_flag, nav_index, unit_index, flag_index, vertical_offset);
|
||||
if (player_index!=NONE)
|
||||
{
|
||||
hud_activate_nav_point(nav_index, player_index, _hud_nav_point_type_flag, flag_index, vertical_offset);
|
||||
@@ -597,7 +591,6 @@ void hud_unit_activate_nav_point_with_object(
|
||||
{
|
||||
long player_index = player_index_from_unit_index(unit_index);
|
||||
|
||||
network_coop_note_nav_point(_coop_nav_point_unit_object, nav_index, unit_index, object_index, vertical_offset);
|
||||
if (player_index!=NONE)
|
||||
{
|
||||
hud_activate_nav_point(nav_index, player_index, _hud_nav_point_type_object, object_index, vertical_offset);
|
||||
@@ -612,7 +605,6 @@ void hud_unit_deactivate_nav_point_with_flag(
|
||||
{
|
||||
long player_index = player_index_from_unit_index(unit_index);
|
||||
|
||||
network_coop_note_nav_point(_coop_nav_point_unit_flag, NONE, unit_index, flag_index, 0.0f);
|
||||
if (player_index!=NONE)
|
||||
{
|
||||
hud_deactivate_nav_point(player_index, _hud_nav_point_type_flag, flag_index);
|
||||
@@ -627,7 +619,6 @@ void hud_unit_deactivate_nav_point_with_object(
|
||||
{
|
||||
long player_index = player_index_from_unit_index(unit_index);
|
||||
|
||||
network_coop_note_nav_point(_coop_nav_point_unit_object, NONE, unit_index, object_index, 0.0f);
|
||||
if (player_index!=NONE)
|
||||
{
|
||||
hud_deactivate_nav_point(player_index, _hud_nav_point_type_object, object_index);
|
||||
|
||||
@@ -381,20 +381,6 @@ void scripted_hud_blink_motion_sensor(
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: the scripts' show and blink flags, for network co-op
|
||||
(port/linux/game/network_coop.c) */
|
||||
long hud_unit_port_script_flags(
|
||||
void)
|
||||
{
|
||||
return unit_hud_globals->script_flags;
|
||||
}
|
||||
|
||||
void hud_unit_port_set_script_flags(
|
||||
long flags)
|
||||
{
|
||||
unit_hud_globals->script_flags = flags;
|
||||
}
|
||||
|
||||
void hud_play_unit_sounds(
|
||||
struct player_datum const *player,
|
||||
boolean show_hud)
|
||||
|
||||
@@ -31,11 +31,6 @@ void hud_tick_shield(
|
||||
long player_index,
|
||||
real amount);
|
||||
|
||||
/* port: network co-op (port/linux/game/network_coop.c) */
|
||||
long hud_unit_port_script_flags(
|
||||
void);
|
||||
void hud_unit_port_set_script_flags(
|
||||
long flags);
|
||||
void scripted_hud_show_health(
|
||||
boolean show);
|
||||
void scripted_hud_blink_health(
|
||||
|
||||
@@ -483,20 +483,6 @@ void scripted_hud_show_crosshair(
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: the scripts' crosshair flag, for network co-op
|
||||
(port/linux/game/network_coop.c) */
|
||||
long hud_weapon_port_script_flags(
|
||||
void)
|
||||
{
|
||||
return weapon_hud_globals->script_flags;
|
||||
}
|
||||
|
||||
void hud_weapon_port_set_script_flags(
|
||||
long flags)
|
||||
{
|
||||
weapon_hud_globals->script_flags = flags;
|
||||
}
|
||||
|
||||
static struct weapon_hud_state *get_hud_state(
|
||||
short local_player_index)
|
||||
{
|
||||
|
||||
@@ -14,11 +14,6 @@ void hud_dispose_weapon_interface_from_old_map(
|
||||
void hud_dispose_weapon_interface(
|
||||
void);
|
||||
|
||||
/* port: network co-op (port/linux/game/network_coop.c) */
|
||||
long hud_weapon_port_script_flags(
|
||||
void);
|
||||
void hud_weapon_port_set_script_flags(
|
||||
long flags);
|
||||
void scripted_hud_show_crosshair(
|
||||
boolean show);
|
||||
|
||||
|
||||
@@ -1227,9 +1227,6 @@ static void interface_splitscreen_render(
|
||||
{
|
||||
rectangle2d bounds;
|
||||
short window_count;
|
||||
/* port: the screen's width, wider than the Xbox's 640 on a wide screen,
|
||||
whose middle the windows split at (compute_window_bounds) */
|
||||
short width = (short)halo_screen_width();
|
||||
|
||||
if (game_engine_force_single_screen() || cinematic_in_progress())
|
||||
return;
|
||||
@@ -1242,7 +1239,7 @@ static void interface_splitscreen_render(
|
||||
bounds.y0 = 239;
|
||||
bounds.x0 = 0;
|
||||
bounds.y1 = 241;
|
||||
bounds.x1 = width;
|
||||
bounds.x1 = 640;
|
||||
draw_quad(&bounds, 0xFF000000);
|
||||
|
||||
if (window_count <= 2)
|
||||
@@ -1251,18 +1248,18 @@ static void interface_splitscreen_render(
|
||||
if (window_count == 3)
|
||||
{
|
||||
bounds.y0 = 240;
|
||||
bounds.x0 = width / 2 - 1;
|
||||
bounds.x0 = 319;
|
||||
bounds.y1 = 480;
|
||||
bounds.x1 = width / 2 + 1;
|
||||
bounds.x1 = 321;
|
||||
draw_quad(&bounds, 0xFF000000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bounds.y0 = 0;
|
||||
bounds.x0 = width / 2 - 1;
|
||||
bounds.x0 = 319;
|
||||
bounds.y1 = 480;
|
||||
bounds.x1 = width / 2 + 1;
|
||||
bounds.x1 = 321;
|
||||
|
||||
match_assert(
|
||||
"c:\\halo\\SOURCE\\interface\\interface.c",
|
||||
|
||||
@@ -514,19 +514,6 @@ void player_ui_local_player_joined_multiplayer_game(
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: the local player out of this game and the next (a split screen
|
||||
player who quit: player_ui_autojoin_players_to_next_multiplayer_game would
|
||||
join them again) */
|
||||
void player_ui_local_player_left_multiplayer_game(
|
||||
short local_player_index)
|
||||
{
|
||||
if (local_player_index < 0 || local_player_index >= MAXIMUM_NUMBER_OF_LOCAL_PLAYERS)
|
||||
return;
|
||||
player_ui_globals.local_players[local_player_index].prejoined_multiplayer = FALSE;
|
||||
player_ui_globals.multiplayer_autojoin[local_player_index] = FALSE;
|
||||
return;
|
||||
}
|
||||
|
||||
boolean player_ui_rumble_disabled(
|
||||
short local_player_index)
|
||||
{
|
||||
|
||||
@@ -72,9 +72,6 @@ short player_ui_get_last_single_player_level_played(
|
||||
short local_player_index);
|
||||
void player_ui_local_player_joined_multiplayer_game(
|
||||
short local_player_index);
|
||||
/* port: the local player out of this game and the next */
|
||||
void player_ui_local_player_left_multiplayer_game(
|
||||
short local_player_index);
|
||||
boolean player_ui_rumble_disabled(
|
||||
short local_player_index);
|
||||
boolean player_ui_get_path_to_local_player_profile_directory(
|
||||
|
||||
@@ -662,7 +662,6 @@ struct widget_instance;
|
||||
#include "networking/network_connection.h"
|
||||
#include "networking/network_game_globals.h"
|
||||
#include "networking/network_server_manager.h"
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
#include "rasterizer/rasterizer.h"
|
||||
#include "saved games/player_profile.h"
|
||||
#include "saved games/playlist_profile.h"
|
||||
@@ -3701,10 +3700,7 @@ static void widget_instance_initialize(
|
||||
widget->visible = TRUE;
|
||||
widget->render_regardless_of_controller_index =
|
||||
TEST_FLAG(definition->flags, _widget_render_regardless_of_controller_index_bit);
|
||||
/* port: a network co-op game never pauses (it opens the campaign's pause
|
||||
screen, which would) */
|
||||
widget->pause_game_time = TEST_FLAG(definition->flags, _widget_pause_game_time_bit) &&
|
||||
!network_coop_active();
|
||||
widget->pause_game_time = TEST_FLAG(definition->flags, _widget_pause_game_time_bit);
|
||||
widget->creation_time = widget_globals.current_system_milliseconds;
|
||||
widget->milliseconds_to_auto_close = MAX(definition->milliseconds_to_auto_close, 0);
|
||||
widget->auto_close_fade_time = MAX(definition->auto_close_fade_time, 0);
|
||||
@@ -5702,10 +5698,6 @@ static void ui_mouse_note_target(
|
||||
if (!parent || ui_mouse_list_shows_several(widget) || !widget_instance_can_receive_events(widget))
|
||||
return;
|
||||
kind = _ui_mouse_target_value;
|
||||
/* port: its arrows, drawn just outside it (the PC version's
|
||||
spinners' header and footer bitmaps), its too */
|
||||
bounds.x0 -= 12;
|
||||
bounds.x1 += 12;
|
||||
}
|
||||
else if (ui_mouse_widget_is_item(widget))
|
||||
{
|
||||
@@ -5998,16 +5990,18 @@ void p2p_set_public_game(int public_game);
|
||||
int p2p_join_invite(char const *text);
|
||||
int p2p_public_game(void);
|
||||
int p2p_public_games(struct ui_public_game *games, int maximum);
|
||||
int config_boolean(char const *name);
|
||||
int config_write_boolean(char const *name, int value);
|
||||
long config_integer(char const *name);
|
||||
int config_write_integer(char const *name, long value);
|
||||
|
||||
enum
|
||||
{
|
||||
MAXIMUM_SHOWN_PUBLIC_GAMES = 32,
|
||||
/* the online games panel's rows: "enter a code" and the games */
|
||||
/* the online games panel's rows: "enter a code", the voice chat's
|
||||
switches, and the games */
|
||||
ONLINE_PANEL_ROWS = 11,
|
||||
/* (the rows before the games: "enter a code") */
|
||||
ONLINE_PANEL_FIXED_ROWS = 1,
|
||||
ONLINE_PANEL_FIXED_ROWS = 3,
|
||||
};
|
||||
|
||||
static struct
|
||||
@@ -6062,26 +6056,6 @@ static boolean ui_join_code_screen_active(
|
||||
strcmp(widget_globals.active_widgets[0]->name, name) == 0;
|
||||
}
|
||||
|
||||
/* the screens the panels go over: the Xbox's, or the PC version's
|
||||
(vr.menus, port/linux/game/menu_tags.c) */
|
||||
static boolean ui_join_code_pc_screens(
|
||||
void)
|
||||
{
|
||||
return ui_join_code_screen_active("join_game_screen") || ui_join_code_screen_active("lobby_screen");
|
||||
}
|
||||
|
||||
static boolean ui_join_code_join_screen(
|
||||
void)
|
||||
{
|
||||
return ui_join_code_screen_active("server_list_screen") || ui_join_code_screen_active("join_game_screen");
|
||||
}
|
||||
|
||||
static boolean ui_join_code_lobby_screen(
|
||||
void)
|
||||
{
|
||||
return ui_join_code_screen_active("connected_pregame_screen") || ui_join_code_screen_active("lobby_screen");
|
||||
}
|
||||
|
||||
static void ui_join_code_launch_keyboard(
|
||||
void)
|
||||
{
|
||||
@@ -6101,7 +6075,7 @@ 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 rows = (short)(ui_join_code_globals.game_count + ONLINE_PANEL_FIXED_ROWS);
|
||||
short move = 0;
|
||||
|
||||
if (event->type == _event_type_left_stick)
|
||||
@@ -6127,9 +6101,20 @@ static void ui_join_code_panel_event(
|
||||
break;
|
||||
case _gamepad_analog_button_a:
|
||||
case _gamepad_binary_button_start:
|
||||
/* (the voice chat's switches, kept in config.toml) */
|
||||
if (ui_join_code_globals.selection == 1)
|
||||
{
|
||||
config_write_boolean("voice.enabled", !config_boolean("voice.enabled"));
|
||||
break;
|
||||
}
|
||||
if (ui_join_code_globals.selection == 2)
|
||||
{
|
||||
config_write_boolean("voice.microphone", !config_boolean("voice.microphone"));
|
||||
break;
|
||||
}
|
||||
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_globals.selection >= ui_join_code_globals.game_count + ONLINE_PANEL_FIXED_ROWS)
|
||||
{
|
||||
ui_join_code_launch_keyboard();
|
||||
}
|
||||
@@ -6158,8 +6143,6 @@ static void ui_join_code_panel_event(
|
||||
}
|
||||
}
|
||||
|
||||
long network_game_server_port_bot_count(void);
|
||||
|
||||
/* the host options panel's input: the stick or the pad's arrows up and
|
||||
down choose a row, left and right (or A) change it, B or Y closes it */
|
||||
static void ui_join_code_host_panel_event(
|
||||
@@ -6208,18 +6191,13 @@ static void ui_join_code_host_panel_event(
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
/* (from the bots there are, which the game's room may hold below
|
||||
bots.count; A goes round: past the most there is room for, none) */
|
||||
long before = network_game_server_port_bot_count();
|
||||
long count = PIN(before + change, 0, MAXIMUM_BOTS_SHOWN);
|
||||
/* (A goes round: past the most, none) */
|
||||
long count = config_integer("bots.count") + change;
|
||||
|
||||
config_write_integer("bots.count", count);
|
||||
if (change > 0 && network_game_server_port_bot_count() <= before)
|
||||
{
|
||||
if (count > MAXIMUM_BOTS_SHOWN)
|
||||
count = event->type == _event_type_button && event->data.button.index == _gamepad_analog_button_a ? 0 :
|
||||
before;
|
||||
config_write_integer("bots.count", count);
|
||||
}
|
||||
MAXIMUM_BOTS_SHOWN;
|
||||
config_write_integer("bots.count", PIN(count, 0, MAXIMUM_BOTS_SHOWN));
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
@@ -6245,7 +6223,7 @@ static boolean ui_join_code_take_event(
|
||||
return FALSE;
|
||||
if (ui_join_code_globals.host_panel_open)
|
||||
{
|
||||
if (!ui_join_code_lobby_screen() || !global_network_game_server_get())
|
||||
if (!ui_join_code_screen_active("connected_pregame_screen") || !global_network_game_server_get())
|
||||
{
|
||||
ui_join_code_globals.host_panel_open = FALSE;
|
||||
return FALSE;
|
||||
@@ -6255,7 +6233,7 @@ static boolean ui_join_code_take_event(
|
||||
}
|
||||
if (ui_join_code_globals.panel_open)
|
||||
{
|
||||
if (!ui_join_code_join_screen())
|
||||
if (!ui_join_code_screen_active("server_list_screen"))
|
||||
{
|
||||
ui_join_code_globals.panel_open = FALSE;
|
||||
return FALSE;
|
||||
@@ -6265,7 +6243,7 @@ static boolean ui_join_code_take_event(
|
||||
}
|
||||
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_join_screen())
|
||||
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;
|
||||
@@ -6273,7 +6251,7 @@ static boolean ui_join_code_take_event(
|
||||
return TRUE;
|
||||
}
|
||||
if (event->data.button.index == _gamepad_analog_button_y &&
|
||||
ui_join_code_lobby_screen() && global_network_game_server_get())
|
||||
ui_join_code_screen_active("connected_pregame_screen") && global_network_game_server_get())
|
||||
{
|
||||
ui_join_code_globals.host_panel_open = TRUE;
|
||||
ui_join_code_globals.host_selection = 0;
|
||||
@@ -6306,20 +6284,6 @@ static boolean ui_join_code_take_pointer(
|
||||
}
|
||||
if (ui_join_code_globals.panel_open)
|
||||
{
|
||||
/* (a click beside the panel, or on its key's B, closes it; on its
|
||||
key's X, a code to enter, on its A, as A) */
|
||||
if (pointer->left_clicks && (x < 40 || x >= 600 || y < 72 || y >= 410 ||
|
||||
(y >= 384 && x >= 210 && x < 280)))
|
||||
{
|
||||
ui_join_code_globals.panel_open = FALSE;
|
||||
ui_play_audio_feedback_sound(_ui_audio_feedback_back);
|
||||
return TRUE;
|
||||
}
|
||||
if (pointer->left_clicks && y >= 384 && x >= 104 && x < 210)
|
||||
{
|
||||
ui_join_code_launch_keyboard();
|
||||
return TRUE;
|
||||
}
|
||||
if (x >= 48 && x < 592 && y >= 128 && y < 128 + ONLINE_PANEL_ROWS * 22)
|
||||
{
|
||||
row = (short)(ui_join_code_globals.scroll + (y - 128) / 22);
|
||||
@@ -6334,13 +6298,6 @@ static boolean ui_join_code_take_pointer(
|
||||
}
|
||||
if (ui_join_code_globals.host_panel_open)
|
||||
{
|
||||
/* (a click beside the panel, or on its key's B, closes it) */
|
||||
if (pointer->left_clicks && (x < 140 || x >= 500 || y < 150 || y >= 330 || (y >= 296 && x >= 250)))
|
||||
{
|
||||
ui_join_code_globals.host_panel_open = FALSE;
|
||||
ui_play_audio_feedback_sound(_ui_audio_feedback_back);
|
||||
return TRUE;
|
||||
}
|
||||
if (x >= 148 && x < 492 && y >= 187 && y < 187 + HOST_PANEL_ROWS * 30)
|
||||
{
|
||||
ui_join_code_globals.host_selection = (short)((y - 187) / 30);
|
||||
@@ -6349,13 +6306,6 @@ static boolean ui_join_code_take_pointer(
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
/* (on the PC version's screens, their hints top right clicked: X's and
|
||||
Y's) */
|
||||
if (pointer->left_clicks && x >= 420 && y < 52 && ui_join_code_pc_screens())
|
||||
{
|
||||
press.data.button.index = ui_join_code_join_screen() ? _gamepad_analog_button_x : _gamepad_analog_button_y;
|
||||
return ui_join_code_take_event(widget_globals.active_widgets[0], &press);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -6466,7 +6416,7 @@ static void ui_join_code_render_panel(
|
||||
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);
|
||||
(short)PIN(ui_join_code_globals.selection, 0, ui_join_code_globals.game_count + ONLINE_PANEL_FIXED_ROWS - 1);
|
||||
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);
|
||||
@@ -6481,7 +6431,7 @@ static void ui_join_code_render_panel(
|
||||
short index = (short)(ui_join_code_globals.scroll + row);
|
||||
short y = (short)(128 + row * 22);
|
||||
|
||||
if (index > ui_join_code_globals.game_count)
|
||||
if (index >= ui_join_code_globals.game_count + ONLINE_PANEL_FIXED_ROWS)
|
||||
break;
|
||||
if (index == ui_join_code_globals.selection)
|
||||
ui_join_code_draw_quad(48, y, 592, (short)(y + 21), 0x803A6EA5);
|
||||
@@ -6489,9 +6439,20 @@ static void ui_join_code_render_panel(
|
||||
{
|
||||
ui_join_code_draw_text(56, 584, (short)(y + 1), "Enter a join code...", FALSE, 0, &text_color);
|
||||
}
|
||||
else if (index == 1)
|
||||
{
|
||||
ui_join_code_draw_text(56, 584, (short)(y + 1),
|
||||
config_boolean("voice.enabled") ? "Voice chat: On" : "Voice chat: Off", FALSE, 0, &text_color);
|
||||
}
|
||||
else if (index == 2)
|
||||
{
|
||||
ui_join_code_draw_text(56, 584, (short)(y + 1),
|
||||
config_boolean("voice.microphone") ? "Microphone: On (sent while you speak)" :
|
||||
"Microphone: Off (listening only)", FALSE, 0, &text_color);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct ui_public_game const *game = &ui_join_code_globals.games[index - 1];
|
||||
struct ui_public_game const *game = &ui_join_code_globals.games[index - ONLINE_PANEL_FIXED_ROWS];
|
||||
|
||||
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,
|
||||
@@ -6502,9 +6463,9 @@ static void ui_join_code_render_panel(
|
||||
}
|
||||
}
|
||||
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);
|
||||
ui_join_code_draw_text(56, 584, 196, "No public games right now", FALSE, 0, &dim_color);
|
||||
ui_join_code_draw_text(56, 584, 384, "%a-button Join or switch %x-button Enter a code %b-button Back", TRUE,
|
||||
0, &text_color);
|
||||
}
|
||||
|
||||
/* over the screens, as they are drawn */
|
||||
@@ -6513,7 +6474,7 @@ static void ui_join_code_render(
|
||||
{
|
||||
char code[16];
|
||||
|
||||
if (ui_join_code_join_screen())
|
||||
if (ui_join_code_screen_active("server_list_screen"))
|
||||
{
|
||||
char const *status = NULL;
|
||||
real_argb_color hint_color = { 1.0f, 0.85f, 0.85f, 0.85f };
|
||||
@@ -6524,11 +6485,8 @@ static void ui_join_code_render(
|
||||
return;
|
||||
}
|
||||
/* (in the key bar's free left, and under it: clear of the map's
|
||||
picture; on the PC version's, top right, beside its title) */
|
||||
if (ui_join_code_pc_screens())
|
||||
ui_join_code_draw_text(500, 630, 12, "%x-button Join code", TRUE, 0, &hint_color);
|
||||
else
|
||||
ui_join_code_draw_text(8, 200, 420, "%x-button Online / code", TRUE, 0, &hint_color);
|
||||
picture) */
|
||||
ui_join_code_draw_text(8, 200, 420, "%x-button Online / code", TRUE, 0, &hint_color);
|
||||
switch (p2p_join_code_status())
|
||||
{
|
||||
case _join_code_looking: status = "Looking for the game of that code..."; break;
|
||||
@@ -6550,20 +6508,13 @@ static void ui_join_code_render(
|
||||
case _join_state_lost: status = "Lost the connection to the online game's host"; break;
|
||||
}
|
||||
}
|
||||
/* (on the PC version's, under its list, which says itself when
|
||||
online play is off) */
|
||||
if (status && ui_join_code_pc_screens())
|
||||
{
|
||||
if (p2p_join_code_status() != _join_code_offline)
|
||||
ui_join_code_draw_text(20, 620, 358, status, FALSE, 2, &hint_color);
|
||||
}
|
||||
else if (status)
|
||||
if (status)
|
||||
ui_join_code_draw_line(452, status, FALSE, FALSE);
|
||||
}
|
||||
else if (ui_join_code_lobby_screen() && global_network_game_server_get())
|
||||
else if (ui_join_code_screen_active("connected_pregame_screen") && global_network_game_server_get())
|
||||
{
|
||||
char line[128];
|
||||
long bots = network_game_server_port_bot_count();
|
||||
long bots = config_integer("bots.count");
|
||||
char bots_text[24];
|
||||
|
||||
if (bots > 0)
|
||||
@@ -6579,24 +6530,8 @@ static void ui_join_code_render(
|
||||
{
|
||||
snprintf(line, sizeof(line), "%%y-button Host options (%s)", bots_text);
|
||||
}
|
||||
/* (under the buttons' key; on the PC version's, top right, beside
|
||||
its title, a line each) */
|
||||
if (ui_join_code_pc_screens())
|
||||
{
|
||||
real_argb_color hint_color = { 1.0f, 0.85f, 0.85f, 0.85f };
|
||||
|
||||
if (p2p_join_code(code, sizeof(code)))
|
||||
{
|
||||
snprintf(line, sizeof(line), "Join code: %s", code);
|
||||
ui_join_code_draw_text(420, 630, 12, line, FALSE, 0, &hint_color);
|
||||
}
|
||||
snprintf(line, sizeof(line), "%%y-button Host options (%s)", bots_text);
|
||||
ui_join_code_draw_text(420, 630, 32, line, TRUE, 0, &hint_color);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui_join_code_draw_line(448, line, TRUE, TRUE);
|
||||
}
|
||||
/* (under the buttons' key) */
|
||||
ui_join_code_draw_line(448, line, TRUE, TRUE);
|
||||
if (ui_join_code_globals.host_panel_open)
|
||||
{
|
||||
static char const *const skills[] = { "Easy", "Normal", "Hard" };
|
||||
@@ -6659,11 +6594,10 @@ enum
|
||||
_vr_option_crosshair,
|
||||
_vr_option_motion_tracker,
|
||||
_vr_option_controls,
|
||||
_vr_option_quit,
|
||||
NUMBER_OF_VR_OPTIONS,
|
||||
|
||||
VR_OPTIONS_TOP = 98,
|
||||
VR_OPTIONS_ROW_HEIGHT = 26,
|
||||
VR_OPTIONS_TOP = 102,
|
||||
VR_OPTIONS_ROW_HEIGHT = 28,
|
||||
VR_OPTIONS_FOOTER = 362,
|
||||
|
||||
_vr_controls_on_foot = 0,
|
||||
@@ -6678,12 +6612,8 @@ static struct
|
||||
boolean controls;
|
||||
short selection;
|
||||
short page;
|
||||
/* "Quit Halo" chosen once: a second time quits */
|
||||
boolean quit_armed;
|
||||
} ui_vr_options_globals;
|
||||
|
||||
void platform_request_quit(void);
|
||||
|
||||
static real const vr_snap_angles[] = { 30.0f, 45.0f, 60.0f, 90.0f };
|
||||
static real const vr_smooth_speeds[] = { 60.0f, 90.0f, 120.0f, 150.0f, 180.0f };
|
||||
static real const vr_vignettes[] = { 0.0f, 0.35f, 0.7f, 1.0f };
|
||||
@@ -6694,10 +6624,8 @@ static boolean ui_vr_options_screen(
|
||||
{
|
||||
struct widget_instance *screen = widget_globals.active_widgets[0];
|
||||
|
||||
/* (the Xbox's main menu, or the PC version's: vr.menus) */
|
||||
return halo_vr_active() && screen && screen->name &&
|
||||
(main_menu_screen_is_active() || strstr(screen->name, "pause_game") != NULL ||
|
||||
strcmp(screen->name, "main_menu") == 0);
|
||||
(main_menu_screen_is_active() || strstr(screen->name, "pause_game") != NULL);
|
||||
}
|
||||
|
||||
/* which of the choices a setting is nearest */
|
||||
@@ -6757,13 +6685,6 @@ static void ui_vr_options_change(
|
||||
ui_vr_options_globals.controls = TRUE;
|
||||
ui_vr_options_globals.page = _vr_controls_on_foot;
|
||||
break;
|
||||
case _vr_option_quit:
|
||||
/* (the Steam Frame's Steam cannot end the game, which runs in
|
||||
Lepton's container: it ends itself; asked twice) */
|
||||
if (ui_vr_options_globals.quit_armed)
|
||||
platform_request_quit();
|
||||
ui_vr_options_globals.quit_armed = TRUE;
|
||||
break;
|
||||
}
|
||||
ui_play_audio_feedback_sound(_ui_audio_feedback_forward);
|
||||
}
|
||||
@@ -6771,7 +6692,6 @@ static void ui_vr_options_change(
|
||||
static void ui_vr_options_close(
|
||||
void)
|
||||
{
|
||||
ui_vr_options_globals.quit_armed = FALSE;
|
||||
ui_vr_options_globals.open = FALSE;
|
||||
ui_vr_options_globals.controls = FALSE;
|
||||
}
|
||||
@@ -6858,7 +6778,6 @@ static boolean ui_vr_options_take_event(
|
||||
else if (move)
|
||||
{
|
||||
ui_vr_options_globals.selection = (short)PIN(ui_vr_options_globals.selection + move, 0, NUMBER_OF_VR_OPTIONS - 1);
|
||||
ui_vr_options_globals.quit_armed = FALSE;
|
||||
ui_play_audio_feedback_sound(_ui_audio_feedback_cursor);
|
||||
}
|
||||
else if (accept)
|
||||
@@ -6884,11 +6803,7 @@ static boolean ui_vr_options_take_pointer(
|
||||
if (!ui_vr_options_globals.open)
|
||||
{
|
||||
/* (its hint clicked: open) */
|
||||
boolean pc_main_menu = widget_globals.active_widgets[0] && widget_globals.active_widgets[0]->name &&
|
||||
strcmp(widget_globals.active_widgets[0]->name, "main_menu") == 0;
|
||||
|
||||
if (pointer->left_clicks && ui_vr_options_screen() &&
|
||||
(pc_main_menu ? x >= 400 && y >= 446 : x >= 256 && x < 400 && y >= 434 && y < 464))
|
||||
if (pointer->left_clicks && x >= 256 && x < 400 && y >= 434 && y < 464 && ui_vr_options_screen())
|
||||
{
|
||||
ui_vr_options_globals.open = TRUE;
|
||||
ui_vr_options_globals.controls = FALSE;
|
||||
@@ -6922,11 +6837,7 @@ static boolean ui_vr_options_take_pointer(
|
||||
if (x >= 118 && x < 522 && y >= VR_OPTIONS_TOP - 4 &&
|
||||
y < VR_OPTIONS_TOP - 4 + NUMBER_OF_VR_OPTIONS * VR_OPTIONS_ROW_HEIGHT)
|
||||
{
|
||||
short row = (short)((y - (VR_OPTIONS_TOP - 4)) / VR_OPTIONS_ROW_HEIGHT);
|
||||
|
||||
if (row != ui_vr_options_globals.selection)
|
||||
ui_vr_options_globals.quit_armed = FALSE;
|
||||
ui_vr_options_globals.selection = row;
|
||||
ui_vr_options_globals.selection = (short)((y - (VR_OPTIONS_TOP - 4)) / VR_OPTIONS_ROW_HEIGHT);
|
||||
if (pointer->left_clicks)
|
||||
ui_vr_options_change(1);
|
||||
}
|
||||
@@ -7118,10 +7029,7 @@ static void ui_vr_options_render(
|
||||
{
|
||||
/* (under the menu; centred by hand, as icons are drawn from the
|
||||
left) */
|
||||
if (strcmp(widget_globals.active_widgets[0]->name, "main_menu") == 0)
|
||||
ui_join_code_draw_text(490, 630, 452, "%y-button VR options", TRUE, 0, &hint_color);
|
||||
else
|
||||
ui_join_code_draw_text(266, 400, 440, "%y-button VR options", TRUE, 0, &hint_color);
|
||||
ui_join_code_draw_text(266, 400, 440, "%y-button VR options", TRUE, 0, &hint_color);
|
||||
return;
|
||||
}
|
||||
if (ui_vr_options_globals.controls)
|
||||
@@ -7182,10 +7090,6 @@ static void ui_vr_options_render(
|
||||
case _vr_option_controls:
|
||||
label = "Controller buttons...";
|
||||
break;
|
||||
case _vr_option_quit:
|
||||
label = ui_vr_options_globals.quit_armed && ui_vr_options_globals.selection == _vr_option_quit ?
|
||||
"Quit Halo? Once more to quit" : "Quit Halo";
|
||||
break;
|
||||
}
|
||||
if (row == ui_vr_options_globals.selection)
|
||||
ui_join_code_draw_quad(118, (short)(y - 4), 522, (short)(y + VR_OPTIONS_ROW_HEIGHT - 6), 0x803A6EA5);
|
||||
@@ -7987,33 +7891,6 @@ static void widget_instance_tab_to_previous_valid_widget(
|
||||
return;
|
||||
}
|
||||
|
||||
/* port: whether a widget of the local player (NONE: any) takes the
|
||||
controller's events. In co-op's menus (Multiplayer's CO-OP CAMPAIGN,
|
||||
port/linux/game/menu_functions.c) the screens it shares with one player's
|
||||
campaign, New Game's levels and the difficulty, are player 1's (their rows
|
||||
the first controller's), and either player's controller uses them: player 1's
|
||||
is the one that chose co-op, player 2's the one that chose their profile */
|
||||
static boolean widget_takes_events_of_controller(
|
||||
struct widget_instance const *widget,
|
||||
short controller_index)
|
||||
{
|
||||
short player;
|
||||
|
||||
if (widget->local_player_index == NONE || widget->local_player_index == controller_index)
|
||||
return TRUE;
|
||||
if (widget->local_player_index != 0 || !we_are_at_the_main_menu || player_spawn_count < 2 ||
|
||||
controller_index < 0 || controller_index >= MAXIMUM_NUMBER_OF_LOCAL_PLAYERS)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
for (player = 0; player < 2; player++)
|
||||
{
|
||||
if (player_ui_get_single_player_local_player_controller(player) == controller_index)
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void widget_instance_process_one_event_recursive(
|
||||
struct widget_instance *widget,
|
||||
struct ui_widget_definition *definition,
|
||||
@@ -8022,7 +7899,8 @@ static void widget_instance_process_one_event_recursive(
|
||||
{
|
||||
boolean event_handled = FALSE;
|
||||
boolean widget_deleted = FALSE;
|
||||
boolean event_for_this_widget = widget_takes_events_of_controller(widget, event->controller_index);
|
||||
boolean event_for_this_widget = widget->local_player_index == NONE ||
|
||||
widget->local_player_index == event->controller_index;
|
||||
long audio_feedback = _ui_audio_feedback_none;
|
||||
|
||||
match_assert(
|
||||
@@ -8449,7 +8327,8 @@ static void widget_instance_process_one_event_recursive(
|
||||
|
||||
for (child = widget->child; child; child = child->next)
|
||||
{
|
||||
if (widget_takes_events_of_controller(child, event->controller_index))
|
||||
if (child->local_player_index == NONE ||
|
||||
child->local_player_index == event->controller_index)
|
||||
{
|
||||
widget_instance_process_one_event_recursive(
|
||||
child,
|
||||
@@ -8463,7 +8342,8 @@ static void widget_instance_process_one_event_recursive(
|
||||
}
|
||||
else if (widget->focused_child)
|
||||
{
|
||||
if (widget_takes_events_of_controller(widget->focused_child, event->controller_index))
|
||||
if (widget->focused_child->local_player_index == NONE ||
|
||||
widget->focused_child->local_player_index == event->controller_index)
|
||||
{
|
||||
widget_instance_process_one_event_recursive(
|
||||
widget->focused_child,
|
||||
@@ -8569,10 +8449,7 @@ static boolean ui_check_for_pause_game(
|
||||
network_game_client_get_machine_index(client);
|
||||
char const *widget_name;
|
||||
|
||||
/* port: a campaign map has only the campaign's pause screen */
|
||||
if (network_coop_active())
|
||||
widget_name = "ui\\shell\\solo_game\\pause_game\\pause_game";
|
||||
else switch (local_player_count)
|
||||
switch (local_player_count)
|
||||
{
|
||||
case 1:
|
||||
widget_name =
|
||||
|
||||
@@ -923,7 +923,6 @@ symbols in this file:
|
||||
#include "networking/network_game_manager.h"
|
||||
#include "networking/network_messages.h"
|
||||
#include "networking/network_server_manager.h"
|
||||
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
|
||||
#include "saved games/game_state.h"
|
||||
#include "saved games/player_profile.h"
|
||||
#include "interface/ui_widget_definitions.h"
|
||||
@@ -1992,9 +1991,6 @@ static boolean pause_game_restart_at_checkpoint(
|
||||
struct event_record *event,
|
||||
boolean *widget_deleted)
|
||||
{
|
||||
/* port: in co-op this would revert only this machine */
|
||||
if (network_coop_active())
|
||||
return FALSE;
|
||||
main_revert_map();
|
||||
return TRUE;
|
||||
}
|
||||
@@ -2004,9 +2000,6 @@ static boolean pause_game_restart_level(
|
||||
struct event_record *event,
|
||||
boolean *widget_deleted)
|
||||
{
|
||||
/* port: in co-op this would restart only this machine */
|
||||
if (network_coop_active())
|
||||
return FALSE;
|
||||
main_reset_map();
|
||||
return TRUE;
|
||||
}
|
||||
@@ -2016,21 +2009,7 @@ static boolean pause_game_quit_to_main_menu(
|
||||
struct event_record *event,
|
||||
boolean *widget_deleted)
|
||||
{
|
||||
/* port: in co-op, take every player on this machine out of the network
|
||||
game with one press (not one per split screen player). The solo save is
|
||||
left alone. */
|
||||
if (network_coop_active())
|
||||
{
|
||||
short controller_index;
|
||||
|
||||
for (controller_index = 0; controller_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS; controller_index++)
|
||||
network_game_client_local_player_quit(controller_index);
|
||||
return TRUE;
|
||||
}
|
||||
/* port: a multiplayer map played alone (New Game's MULTIPLAYER maps) is
|
||||
not saved, so it never takes the place of the campaign's saved game */
|
||||
if (main_get_current_solo_level() != NONE)
|
||||
game_state_save_to_persistent_storage();
|
||||
game_state_save_to_persistent_storage();
|
||||
main_goto_main_menu();
|
||||
return TRUE;
|
||||
}
|
||||
@@ -2432,10 +2411,6 @@ static boolean network_game_remove_local_player(
|
||||
event && event->controller_index >= 0 && event->controller_index < 4,
|
||||
"valid controller index required to remove player from network game");
|
||||
network_game_client_local_player_quit(event->controller_index);
|
||||
/* port: a split screen player who quit, the others staying, is not
|
||||
joined to the next game */
|
||||
if (local_player_count() > 1)
|
||||
player_ui_local_player_left_multiplayer_game(event->controller_index);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -5949,30 +5924,6 @@ short ui_widget_port_gametypes(
|
||||
return (short)count;
|
||||
}
|
||||
|
||||
/* port: sets up the server for co-op (port/linux/game/menu_functions.c):
|
||||
the campaign level, the difficulty, and a gametype with no game engine,
|
||||
which is what makes a network game co-op (game.c, players.c). Returns FALSE
|
||||
without a server or a campaign level. */
|
||||
boolean ui_widget_port_cooperative_level_choose(
|
||||
char const *map_name,
|
||||
short difficulty)
|
||||
{
|
||||
struct network_game_server *server = global_network_game_server_get();
|
||||
struct game_variant variant;
|
||||
|
||||
if (!server || !map_name || main_get_solo_level_from_name(map_name) == NONE)
|
||||
return FALSE;
|
||||
csmemset(&variant, 0, sizeof(variant));
|
||||
ustrncpy(variant.human_readable_game_description, L"Co-op",
|
||||
NUMBEROF(variant.human_readable_game_description) - 1);
|
||||
main_set_difficulty(difficulty);
|
||||
main_set_multiplayer_map_name(map_name);
|
||||
network_game_server_port_set_cooperative(server, difficulty);
|
||||
network_game_server_change_map_name(server, map_name);
|
||||
network_game_server_change_game_variant(server, &variant);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* the gametype chosen (as multiplayer_profile_set_for_game), the server's
|
||||
if there is one */
|
||||
boolean ui_widget_port_gametype_choose(
|
||||
@@ -6005,18 +5956,12 @@ boolean ui_widget_port_gametype_choose(
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* hosting (as the Xbox's server list's Y): always a new game. A game made
|
||||
before and backed out of keeps its server (the lobby's last player leaving
|
||||
pauses it: netgame_unjoin_player), and network_game_start_new_server joins
|
||||
only a server it makes, so the client it made for that one never joined it
|
||||
and the lobby had nobody in it */
|
||||
/* hosting (as the Xbox's server list's Y) */
|
||||
boolean ui_widget_port_host(
|
||||
struct widget_instance *widget,
|
||||
struct event_record *event,
|
||||
boolean *widget_deleted)
|
||||
{
|
||||
dispose_global_network_game_client();
|
||||
dispose_global_network_game_server();
|
||||
return network_game_start_new_server(widget, event, widget_deleted);
|
||||
}
|
||||
|
||||
@@ -6104,22 +6049,6 @@ boolean ui_widget_port_multiplayer_player(
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* the lobby's B of a player (port/linux/game/menu_functions.c): that
|
||||
controller's player leaves the game (netgame_unjoin_player); TRUE if they were
|
||||
the machine's last, which leaves it (and are joined again if its host's
|
||||
lobby comes back), else they leave the next game too */
|
||||
boolean ui_widget_port_unjoin_player(
|
||||
struct widget_instance *widget,
|
||||
struct event_record *event,
|
||||
boolean *widget_deleted)
|
||||
{
|
||||
boolean left = netgame_unjoin_player(widget, event, widget_deleted);
|
||||
|
||||
if (!left && event && event->controller_index >= 0 && event->controller_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS)
|
||||
player_ui_local_player_left_multiplayer_game(event->controller_index);
|
||||
return left;
|
||||
}
|
||||
|
||||
/* a screen by name in place of the widget's (back returns to it: as
|
||||
ui_widget_port_join opens the lobby) */
|
||||
boolean ui_widget_port_open(
|
||||
|
||||