namespacify everything
This commit is contained in:
@@ -6,251 +6,250 @@ using FlaxEngine;
|
|||||||
using Console = Game.Console;
|
using Console = Game.Console;
|
||||||
using Object = FlaxEngine.Object;
|
using Object = FlaxEngine.Object;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
{
|
|
||||||
[Flags]
|
|
||||||
public enum AudioFlags
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
|
|
||||||
/// Avoid replacing the existing playing audio source in this channel.
|
[Flags]
|
||||||
ContinuePlayingExistingSource = 1
|
public enum AudioFlags
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// Avoid replacing the existing playing audio source in this channel.
|
||||||
|
ContinuePlayingExistingSource = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AudioManager
|
||||||
|
{
|
||||||
|
private static readonly Random random = new Random();
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, AudioInfo> cachedAudioInfos = new Dictionary<string, AudioInfo>();
|
||||||
|
|
||||||
|
private static readonly Dictionary<Actor, ActorAudioChannels> actorAudioChannels =
|
||||||
|
new Dictionary<Actor, ActorAudioChannels>();
|
||||||
|
|
||||||
|
public static void PlaySound(string soundName, Actor actor, Float3 position, float volume = 1f)
|
||||||
|
{
|
||||||
|
PlaySoundInternal(soundName, actor, 0, AudioFlags.None, position, volume, Float2.One, Float2.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AudioManager
|
public static void PlaySound(string soundName, Actor actor, Float3 position, float volume, Float2 pitchRange)
|
||||||
{
|
{
|
||||||
private static readonly Random random = new Random();
|
PlaySoundInternal(soundName, actor, 0, AudioFlags.None, position, volume, pitchRange, Float2.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly Dictionary<string, AudioInfo> cachedAudioInfos = new Dictionary<string, AudioInfo>();
|
public static void PlaySound(string soundName, Actor actor, int channel, Float3 position, float volume = 1f)
|
||||||
|
{
|
||||||
|
PlaySoundInternal(soundName, actor, channel, AudioFlags.None, position, volume, Float2.One, Float2.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly Dictionary<Actor, ActorAudioChannels> actorAudioChannels =
|
public static void PlaySound(string soundName, Actor actor, int channel, AudioFlags flags, Float3 position,
|
||||||
new Dictionary<Actor, ActorAudioChannels>();
|
float volume = 1f)
|
||||||
|
{
|
||||||
|
PlaySoundInternal(soundName, actor, channel, flags, position, volume, Float2.One, Float2.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
public static void PlaySound(string soundName, Actor actor, Float3 position, float volume = 1f)
|
public static void PlaySound(string soundName, Actor actor, int channel, AudioFlags flags, Float3 position,
|
||||||
|
float volume, Float2 pitchRange)
|
||||||
|
{
|
||||||
|
PlaySoundInternal(soundName, actor, channel, flags, position, volume, pitchRange, Float2.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PlaySoundDelayed(Float2 delayRange, string soundName, Actor actor, int channel,
|
||||||
|
Float3 position, float volume = 1f)
|
||||||
|
{
|
||||||
|
PlaySoundInternal(soundName, actor, channel, AudioFlags.None, position, volume, Float2.One, delayRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PlaySoundDelayed(Float2 delayRange, string soundName, Actor actor, int channel,
|
||||||
|
Float3 position, float volume, Float2 pitchRange)
|
||||||
|
{
|
||||||
|
PlaySoundInternal(soundName, actor, channel, AudioFlags.None, position, volume, pitchRange, delayRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PlaySoundDelayed(Float2 delayRange, string soundName, Actor actor, int channel,
|
||||||
|
AudioFlags flags, Float3 position, float volume, Float2 pitchRange)
|
||||||
|
{
|
||||||
|
PlaySoundInternal(soundName, actor, channel, flags, position, volume, pitchRange, delayRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PlaySoundInternal(string soundName, Actor actor, int channel, AudioFlags flags, Float3 position,
|
||||||
|
float volume, Float2 pitchRange, Float2 delayRange)
|
||||||
|
{
|
||||||
|
AudioInfo audio = GetSound(soundName);
|
||||||
|
if (audio.AudioClips.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
channel = Math.Max(channel, 0);
|
||||||
|
ActorAudioChannels actorChannels = null;
|
||||||
|
if (channel > 0)
|
||||||
{
|
{
|
||||||
PlaySoundInternal(soundName, actor, 0, AudioFlags.None, position, volume, Float2.One, Float2.Zero);
|
if (!actorAudioChannels.TryGetValue(actor, out actorChannels))
|
||||||
}
|
|
||||||
|
|
||||||
public static void PlaySound(string soundName, Actor actor, Float3 position, float volume, Float2 pitchRange)
|
|
||||||
{
|
|
||||||
PlaySoundInternal(soundName, actor, 0, AudioFlags.None, position, volume, pitchRange, Float2.Zero);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void PlaySound(string soundName, Actor actor, int channel, Float3 position, float volume = 1f)
|
|
||||||
{
|
|
||||||
PlaySoundInternal(soundName, actor, channel, AudioFlags.None, position, volume, Float2.One, Float2.Zero);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void PlaySound(string soundName, Actor actor, int channel, AudioFlags flags, Float3 position,
|
|
||||||
float volume = 1f)
|
|
||||||
{
|
|
||||||
PlaySoundInternal(soundName, actor, channel, flags, position, volume, Float2.One, Float2.Zero);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void PlaySound(string soundName, Actor actor, int channel, AudioFlags flags, Float3 position,
|
|
||||||
float volume, Float2 pitchRange)
|
|
||||||
{
|
|
||||||
PlaySoundInternal(soundName, actor, channel, flags, position, volume, pitchRange, Float2.Zero);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void PlaySoundDelayed(Float2 delayRange, string soundName, Actor actor, int channel,
|
|
||||||
Float3 position, float volume = 1f)
|
|
||||||
{
|
|
||||||
PlaySoundInternal(soundName, actor, channel, AudioFlags.None, position, volume, Float2.One, delayRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void PlaySoundDelayed(Float2 delayRange, string soundName, Actor actor, int channel,
|
|
||||||
Float3 position, float volume, Float2 pitchRange)
|
|
||||||
{
|
|
||||||
PlaySoundInternal(soundName, actor, channel, AudioFlags.None, position, volume, pitchRange, delayRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void PlaySoundDelayed(Float2 delayRange, string soundName, Actor actor, int channel,
|
|
||||||
AudioFlags flags, Float3 position, float volume, Float2 pitchRange)
|
|
||||||
{
|
|
||||||
PlaySoundInternal(soundName, actor, channel, flags, position, volume, pitchRange, delayRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void PlaySoundInternal(string soundName, Actor actor, int channel, AudioFlags flags, Float3 position,
|
|
||||||
float volume, Float2 pitchRange, Float2 delayRange)
|
|
||||||
{
|
|
||||||
AudioInfo audio = GetSound(soundName);
|
|
||||||
if (audio.AudioClips.Length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
channel = Math.Max(channel, 0);
|
|
||||||
ActorAudioChannels actorChannels = null;
|
|
||||||
if (channel > 0)
|
|
||||||
{
|
{
|
||||||
if (!actorAudioChannels.TryGetValue(actor, out actorChannels))
|
actorChannels = new ActorAudioChannels();
|
||||||
{
|
actorAudioChannels.Add(actor, actorChannels);
|
||||||
actorChannels = new ActorAudioChannels();
|
|
||||||
actorAudioChannels.Add(actor, actorChannels);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!actorChannels.channelSources.TryGetValue(channel, out AudioSource existingAudioSource))
|
|
||||||
actorChannels.channelSources.Add(channel, null);
|
|
||||||
|
|
||||||
if (existingAudioSource && existingAudioSource != null &&
|
|
||||||
existingAudioSource.State == AudioSource.States.Playing)
|
|
||||||
{
|
|
||||||
if (flags.HasFlag(AudioFlags.ContinuePlayingExistingSource))
|
|
||||||
return;
|
|
||||||
|
|
||||||
existingAudioSource.Stop();
|
|
||||||
Object.Destroy(existingAudioSource);
|
|
||||||
actorChannels.channelSources[channel] = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioClip audioClip;
|
|
||||||
if (audio.AudioClips.Length > 1)
|
|
||||||
{
|
|
||||||
// Randomize selected clip while avoiding the last used clip
|
|
||||||
int randomIndex = 0;
|
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
randomIndex = random.Next(audio.AudioClips.Length);
|
|
||||||
if (randomIndex != audio.lastAudioPlayed)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
audioClip = audio.AudioClips[randomIndex];
|
|
||||||
audio.lastAudioPlayed = randomIndex;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
audioClip = audio.AudioClips[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Randomized pitch
|
|
||||||
float pitch;
|
|
||||||
if (pitchRange[0] < pitchRange[1])
|
|
||||||
pitch = (float)(pitchRange[0] + random.NextDouble() * (pitchRange[1] - pitchRange[0]));
|
|
||||||
else
|
|
||||||
pitch = pitchRange[0];
|
|
||||||
|
|
||||||
// Randomized start delay
|
|
||||||
float randomDelay;
|
|
||||||
if (delayRange[0] < delayRange[1])
|
|
||||||
randomDelay = (float)(delayRange[0] + random.NextDouble() * (delayRange[1] - delayRange[0]));
|
|
||||||
else
|
|
||||||
randomDelay = delayRange[0];
|
|
||||||
|
|
||||||
AudioSourceDelayed audioSource = new AudioSourceDelayed();
|
|
||||||
audioSource.Delay = randomDelay;
|
|
||||||
audioSource.Clip = audioClip;
|
|
||||||
audioSource.Position = position;
|
|
||||||
audioSource.Parent = actor.Parent;
|
|
||||||
audioSource.Pitch = pitch;
|
|
||||||
audioSource.Name = Path.GetFileNameWithoutExtension(audioClip.Path);
|
|
||||||
audioSource.Volume = volume;
|
|
||||||
audioSource.PlayOnStart = randomDelay == 0;
|
|
||||||
|
|
||||||
if (volume != 1f)
|
|
||||||
audioSource.Name += ", vol: " + volume;
|
|
||||||
if (pitch != 1f)
|
|
||||||
audioSource.Name += ", pitch: " + pitch;
|
|
||||||
if (pitch != 0f)
|
|
||||||
audioSource.Name += ", delay: " + randomDelay;
|
|
||||||
Console.Print("playing sound " + audioSource.Name);
|
|
||||||
|
|
||||||
if (channel > 0)
|
|
||||||
actorChannels.channelSources[channel] = audioSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void StopSound(Actor actor, int channel)
|
|
||||||
{
|
|
||||||
if (channel <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!actorAudioChannels.TryGetValue(actor, out ActorAudioChannels actorChannels))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!actorChannels.channelSources.TryGetValue(channel, out AudioSource existingAudioSource))
|
if (!actorChannels.channelSources.TryGetValue(channel, out AudioSource existingAudioSource))
|
||||||
return;
|
actorChannels.channelSources.Add(channel, null);
|
||||||
|
|
||||||
if (existingAudioSource && existingAudioSource != null &&
|
if (existingAudioSource && existingAudioSource != null &&
|
||||||
existingAudioSource.State == AudioSource.States.Playing)
|
existingAudioSource.State == AudioSource.States.Playing)
|
||||||
{
|
{
|
||||||
|
if (flags.HasFlag(AudioFlags.ContinuePlayingExistingSource))
|
||||||
|
return;
|
||||||
|
|
||||||
existingAudioSource.Stop();
|
existingAudioSource.Stop();
|
||||||
Object.Destroy(existingAudioSource);
|
Object.Destroy(existingAudioSource);
|
||||||
actorChannels.channelSources[channel] = null;
|
actorChannels.channelSources[channel] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsSoundPlaying(Actor actor, int channel)
|
AudioClip audioClip;
|
||||||
|
if (audio.AudioClips.Length > 1)
|
||||||
{
|
{
|
||||||
if (channel <= 0)
|
// Randomize selected clip while avoiding the last used clip
|
||||||
return false;
|
int randomIndex = 0;
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
randomIndex = random.Next(audio.AudioClips.Length);
|
||||||
|
if (randomIndex != audio.lastAudioPlayed)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!actorAudioChannels.TryGetValue(actor, out ActorAudioChannels actorChannels))
|
audioClip = audio.AudioClips[randomIndex];
|
||||||
return false;
|
audio.lastAudioPlayed = randomIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
audioClip = audio.AudioClips[0];
|
||||||
|
}
|
||||||
|
|
||||||
if (!actorChannels.channelSources.TryGetValue(channel, out AudioSource existingAudioSource))
|
// Randomized pitch
|
||||||
return false;
|
float pitch;
|
||||||
|
if (pitchRange[0] < pitchRange[1])
|
||||||
|
pitch = (float)(pitchRange[0] + random.NextDouble() * (pitchRange[1] - pitchRange[0]));
|
||||||
|
else
|
||||||
|
pitch = pitchRange[0];
|
||||||
|
|
||||||
if (existingAudioSource && existingAudioSource != null &&
|
// Randomized start delay
|
||||||
existingAudioSource.State == AudioSource.States.Playing)
|
float randomDelay;
|
||||||
return true;
|
if (delayRange[0] < delayRange[1])
|
||||||
|
randomDelay = (float)(delayRange[0] + random.NextDouble() * (delayRange[1] - delayRange[0]));
|
||||||
|
else
|
||||||
|
randomDelay = delayRange[0];
|
||||||
|
|
||||||
|
AudioSourceDelayed audioSource = new AudioSourceDelayed();
|
||||||
|
audioSource.Delay = randomDelay;
|
||||||
|
audioSource.Clip = audioClip;
|
||||||
|
audioSource.Position = position;
|
||||||
|
audioSource.Parent = actor.Parent;
|
||||||
|
audioSource.Pitch = pitch;
|
||||||
|
audioSource.Name = Path.GetFileNameWithoutExtension(audioClip.Path);
|
||||||
|
audioSource.Volume = volume;
|
||||||
|
audioSource.PlayOnStart = randomDelay == 0;
|
||||||
|
|
||||||
|
if (volume != 1f)
|
||||||
|
audioSource.Name += ", vol: " + volume;
|
||||||
|
if (pitch != 1f)
|
||||||
|
audioSource.Name += ", pitch: " + pitch;
|
||||||
|
if (pitch != 0f)
|
||||||
|
audioSource.Name += ", delay: " + randomDelay;
|
||||||
|
Console.Print("playing sound " + audioSource.Name);
|
||||||
|
|
||||||
|
if (channel > 0)
|
||||||
|
actorChannels.channelSources[channel] = audioSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StopSound(Actor actor, int channel)
|
||||||
|
{
|
||||||
|
if (channel <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!actorAudioChannels.TryGetValue(actor, out ActorAudioChannels actorChannels))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!actorChannels.channelSources.TryGetValue(channel, out AudioSource existingAudioSource))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (existingAudioSource && existingAudioSource != null &&
|
||||||
|
existingAudioSource.State == AudioSource.States.Playing)
|
||||||
|
{
|
||||||
|
existingAudioSource.Stop();
|
||||||
|
Object.Destroy(existingAudioSource);
|
||||||
|
actorChannels.channelSources[channel] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsSoundPlaying(Actor actor, int channel)
|
||||||
|
{
|
||||||
|
if (channel <= 0)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
private static AudioInfo GetSound(string soundName)
|
if (!actorAudioChannels.TryGetValue(actor, out ActorAudioChannels actorChannels))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!actorChannels.channelSources.TryGetValue(channel, out AudioSource existingAudioSource))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (existingAudioSource && existingAudioSource != null &&
|
||||||
|
existingAudioSource.State == AudioSource.States.Playing)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AudioInfo GetSound(string soundName)
|
||||||
|
{
|
||||||
|
if (cachedAudioInfos.TryGetValue(soundName, out AudioInfo cachedAudio))
|
||||||
|
return cachedAudio;
|
||||||
|
|
||||||
|
AudioInfo audio = new AudioInfo();
|
||||||
|
|
||||||
|
string audioBasePath = Path.Combine(AssetManager.ContentPath, "Audio");
|
||||||
|
AudioClip audioClip = Content.Load<AudioClip>(Path.Combine(audioBasePath, soundName + ".flax"));
|
||||||
|
if (audioClip != null)
|
||||||
{
|
{
|
||||||
if (cachedAudioInfos.TryGetValue(soundName, out AudioInfo cachedAudio))
|
audio.AudioClips = new[] { audioClip };
|
||||||
return cachedAudio;
|
}
|
||||||
|
else
|
||||||
AudioInfo audio = new AudioInfo();
|
{
|
||||||
|
// Check if this audio has multiple variations
|
||||||
string audioBasePath = Path.Combine(AssetManager.ContentPath, "Audio");
|
var audioClips = new List<AudioClip>();
|
||||||
AudioClip audioClip = Content.Load<AudioClip>(Path.Combine(audioBasePath, soundName + ".flax"));
|
for (int i = 1; i < 50; i++)
|
||||||
if (audioClip != null)
|
|
||||||
{
|
{
|
||||||
audio.AudioClips = new[] { audioClip };
|
// TODO: make this more efficient, maybe get a list of assets and filter by name?
|
||||||
|
AudioClip audioClipVariation =
|
||||||
|
Content.Load<AudioClip>(Path.Combine(audioBasePath, soundName + "_var" + i + ".flax"));
|
||||||
|
if (audioClipVariation == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
audioClips.Add(audioClipVariation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (audioClips.Count > 0)
|
||||||
|
audio.AudioClips = audioClips.ToArray();
|
||||||
else
|
else
|
||||||
{
|
Console.PrintError("AudioClip '" + soundName + "' not found");
|
||||||
// Check if this audio has multiple variations
|
|
||||||
var audioClips = new List<AudioClip>();
|
|
||||||
for (int i = 1; i < 50; i++)
|
|
||||||
{
|
|
||||||
// TODO: make this more efficient, maybe get a list of assets and filter by name?
|
|
||||||
AudioClip audioClipVariation =
|
|
||||||
Content.Load<AudioClip>(Path.Combine(audioBasePath, soundName + "_var" + i + ".flax"));
|
|
||||||
if (audioClipVariation == null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
audioClips.Add(audioClipVariation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audioClips.Count > 0)
|
|
||||||
audio.AudioClips = audioClips.ToArray();
|
|
||||||
else
|
|
||||||
Console.PrintError("AudioClip '" + soundName + "' not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
audio.lastAudioPlayed = audio.AudioClips.Length + 1;
|
|
||||||
|
|
||||||
cachedAudioInfos.Add(soundName, audio);
|
|
||||||
return audio;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AudioInfo
|
audio.lastAudioPlayed = audio.AudioClips.Length + 1;
|
||||||
{
|
|
||||||
public AudioClip[] AudioClips;
|
|
||||||
public int lastAudioPlayed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ActorAudioChannels
|
cachedAudioInfos.Add(soundName, audio);
|
||||||
{
|
return audio;
|
||||||
public readonly Dictionary<int, AudioSource> channelSources;
|
}
|
||||||
|
|
||||||
public ActorAudioChannels()
|
private class AudioInfo
|
||||||
{
|
{
|
||||||
channelSources = new Dictionary<int, AudioSource>();
|
public AudioClip[] AudioClips;
|
||||||
}
|
public int lastAudioPlayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ActorAudioChannels
|
||||||
|
{
|
||||||
|
public readonly Dictionary<int, AudioSource> channelSources;
|
||||||
|
|
||||||
|
public ActorAudioChannels()
|
||||||
|
{
|
||||||
|
channelSources = new Dictionary<int, AudioSource>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,43 +5,42 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
{
|
|
||||||
public class AudioSourceDelayed : AudioSource
|
|
||||||
{
|
|
||||||
public float Delay = 0f;
|
|
||||||
public override void OnBeginPlay()
|
|
||||||
{
|
|
||||||
if (Delay >= 0f)
|
|
||||||
{
|
|
||||||
PlayOnStart = false;
|
|
||||||
var script = AddScript<AudioSourceDelayedScript>();
|
|
||||||
script.PlayStartTime = Delay + FlaxEngine.Time.GameTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.OnBeginPlay();
|
public class AudioSourceDelayed : AudioSource
|
||||||
|
{
|
||||||
|
public float Delay = 0f;
|
||||||
|
public override void OnBeginPlay()
|
||||||
|
{
|
||||||
|
if (Delay >= 0f)
|
||||||
|
{
|
||||||
|
PlayOnStart = false;
|
||||||
|
var script = AddScript<AudioSourceDelayedScript>();
|
||||||
|
script.PlayStartTime = Delay + FlaxEngine.Time.GameTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.OnBeginPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AudioSourceDelayedScript : Script
|
}
|
||||||
|
|
||||||
|
public class AudioSourceDelayedScript : Script
|
||||||
|
{
|
||||||
|
[NoSerialize]
|
||||||
|
[HideInEditor]
|
||||||
|
public float PlayStartTime = 0f;
|
||||||
|
|
||||||
|
public override void OnStart()
|
||||||
{
|
{
|
||||||
[NoSerialize]
|
FlaxEngine.Object.Destroy(Actor, Actor.As<AudioSourceDelayed>().Clip.Length + Actor.As<AudioSourceDelayed>().Delay);
|
||||||
[HideInEditor]
|
}
|
||||||
public float PlayStartTime = 0f;
|
|
||||||
|
|
||||||
public override void OnStart()
|
public override void OnFixedUpdate()
|
||||||
|
{
|
||||||
|
if (FlaxEngine.Time.GameTime >= PlayStartTime)
|
||||||
{
|
{
|
||||||
FlaxEngine.Object.Destroy(Actor, Actor.As<AudioSourceDelayed>().Clip.Length + Actor.As<AudioSourceDelayed>().Delay);
|
PlayStartTime = float.MaxValue;
|
||||||
}
|
Actor.As<AudioSourceDelayed>().Play();
|
||||||
|
|
||||||
public override void OnFixedUpdate()
|
|
||||||
{
|
|
||||||
if (FlaxEngine.Time.GameTime >= PlayStartTime)
|
|
||||||
{
|
|
||||||
PlayStartTime = float.MaxValue;
|
|
||||||
Actor.As<AudioSourceDelayed>().Play();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,99 +1,98 @@
|
|||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public class CameraMovement : Script
|
||||||
{
|
{
|
||||||
public class CameraMovement : Script
|
private float viewPitch;
|
||||||
|
private float viewRoll;
|
||||||
|
private float viewYaw;
|
||||||
|
|
||||||
|
[Limit(0, 9000)]
|
||||||
|
[Tooltip("Camera speed")]
|
||||||
|
public float MoveSpeed { get; set; } = 400;
|
||||||
|
|
||||||
|
public override void OnStart()
|
||||||
{
|
{
|
||||||
private float viewPitch;
|
Float3 initialEulerAngles = Actor.Orientation.EulerAngles;
|
||||||
private float viewRoll;
|
viewPitch = initialEulerAngles.X;
|
||||||
private float viewYaw;
|
viewYaw = initialEulerAngles.Y;
|
||||||
|
viewRoll = initialEulerAngles.Z;
|
||||||
|
}
|
||||||
|
|
||||||
[Limit(0, 9000)]
|
public override void OnUpdate()
|
||||||
[Tooltip("Camera speed")]
|
{
|
||||||
public float MoveSpeed { get; set; } = 400;
|
Transform camTrans = Actor.Transform;
|
||||||
|
Actor rootActor = Actor.GetChild(0);
|
||||||
|
Camera camera = rootActor.GetChild<Camera>();
|
||||||
|
|
||||||
public override void OnStart()
|
float xAxis = InputManager.GetAxisRaw("Mouse X");
|
||||||
|
float yAxis = InputManager.GetAxisRaw("Mouse Y");
|
||||||
|
if (xAxis != 0.0f || yAxis != 0.0f)
|
||||||
{
|
{
|
||||||
Float3 initialEulerAngles = Actor.Orientation.EulerAngles;
|
viewPitch += yAxis;
|
||||||
viewPitch = initialEulerAngles.X;
|
viewYaw += xAxis;
|
||||||
viewYaw = initialEulerAngles.Y;
|
|
||||||
viewRoll = initialEulerAngles.Z;
|
viewPitch = Mathf.Clamp(viewPitch, -90.0f, 90.0f);
|
||||||
|
|
||||||
|
// root orientation must be set first
|
||||||
|
rootActor.Orientation = Quaternion.Euler(0, viewYaw, 0);
|
||||||
|
camera.Orientation = Quaternion.Euler(viewPitch, viewYaw, viewRoll);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnUpdate()
|
float inputH = InputManager.GetAxis("Horizontal");
|
||||||
|
float inputV = InputManager.GetAxis("Vertical");
|
||||||
|
Float3 move = new Float3(inputH, 0.0f, inputV);
|
||||||
|
|
||||||
|
if (!move.IsZero)
|
||||||
{
|
{
|
||||||
Transform camTrans = Actor.Transform;
|
move.Normalize();
|
||||||
Actor rootActor = Actor.GetChild(0);
|
move = camera.Transform.TransformDirection(move) * MoveSpeed;
|
||||||
Camera camera = rootActor.GetChild<Camera>();
|
|
||||||
|
|
||||||
float xAxis = InputManager.GetAxisRaw("Mouse X");
|
|
||||||
float yAxis = InputManager.GetAxisRaw("Mouse Y");
|
|
||||||
if (xAxis != 0.0f || yAxis != 0.0f)
|
|
||||||
{
|
{
|
||||||
viewPitch += yAxis;
|
Float3 delta = move * Time.UnscaledDeltaTime;
|
||||||
viewYaw += xAxis;
|
float movementLeft = delta.Length;
|
||||||
|
|
||||||
viewPitch = Mathf.Clamp(viewPitch, -90.0f, 90.0f);
|
// TODO: check multiple times in case we get stuck in walls
|
||||||
|
|
||||||
// root orientation must be set first
|
float sphereRadius = 10.0f; // TODO: use collider radius
|
||||||
rootActor.Orientation = Quaternion.Euler(0, viewYaw, 0);
|
RayCastHit[] hitInfos;
|
||||||
camera.Orientation = Quaternion.Euler(viewPitch, viewYaw, viewRoll);
|
float moveDist = delta.Length;
|
||||||
}
|
Physics.SphereCastAll(Actor.Transform.Translation, sphereRadius, move.Normalized, out hitInfos,
|
||||||
|
moveDist);
|
||||||
float inputH = InputManager.GetAxis("Horizontal");
|
|
||||||
float inputV = InputManager.GetAxis("Vertical");
|
|
||||||
Float3 move = new Float3(inputH, 0.0f, inputV);
|
|
||||||
|
|
||||||
if (!move.IsZero)
|
|
||||||
{
|
|
||||||
move.Normalize();
|
|
||||||
move = camera.Transform.TransformDirection(move) * MoveSpeed;
|
|
||||||
|
|
||||||
|
//bool nohit = true;
|
||||||
|
float hitDistance = moveDist;
|
||||||
|
Float3 hitNormal = move.Normalized;
|
||||||
|
foreach (RayCastHit hitInfo in hitInfos)
|
||||||
{
|
{
|
||||||
Float3 delta = move * Time.UnscaledDeltaTime;
|
if (hitInfo.Collider.Parent == Parent)
|
||||||
float movementLeft = delta.Length;
|
continue;
|
||||||
|
|
||||||
// TODO: check multiple times in case we get stuck in walls
|
if (hitInfo.Distance < hitDistance)
|
||||||
|
|
||||||
float sphereRadius = 10.0f; // TODO: use collider radius
|
|
||||||
RayCastHit[] hitInfos;
|
|
||||||
float moveDist = delta.Length;
|
|
||||||
Physics.SphereCastAll(Actor.Transform.Translation, sphereRadius, move.Normalized, out hitInfos,
|
|
||||||
moveDist);
|
|
||||||
|
|
||||||
//bool nohit = true;
|
|
||||||
float hitDistance = moveDist;
|
|
||||||
Float3 hitNormal = move.Normalized;
|
|
||||||
foreach (RayCastHit hitInfo in hitInfos)
|
|
||||||
{
|
{
|
||||||
if (hitInfo.Collider.Parent == Parent)
|
hitDistance = hitInfo.Distance;
|
||||||
continue;
|
hitNormal = hitInfo.Normal;
|
||||||
|
|
||||||
if (hitInfo.Distance < hitDistance)
|
|
||||||
{
|
|
||||||
hitDistance = hitInfo.Distance;
|
|
||||||
hitNormal = hitInfo.Normal;
|
|
||||||
}
|
|
||||||
//nohit = false;
|
|
||||||
//break;
|
|
||||||
}
|
}
|
||||||
|
//nohit = false;
|
||||||
if (hitDistance != moveDist)
|
//break;
|
||||||
//camTrans.Translation = Float3.Lerp(Actor.Transform.Translation, camTrans.Translation, hitDistance);
|
|
||||||
|
|
||||||
//projected = normal * dot(direction, normal);
|
|
||||||
//direction = direction - projected
|
|
||||||
|
|
||||||
//camTrans.Translation += hitNormal * (moveDist - hitDistance); // correct?
|
|
||||||
//camTrans.Translation = hitNormal * (move * hitNormal); // correct?
|
|
||||||
//camTrans.Translation = Actor.Transform.Translation;
|
|
||||||
delta += -Float3.Dot(delta, hitNormal) * hitNormal; // correct?
|
|
||||||
|
|
||||||
camTrans.Translation += delta;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Actor.Transform = camTrans;
|
if (hitDistance != moveDist)
|
||||||
|
//camTrans.Translation = Float3.Lerp(Actor.Transform.Translation, camTrans.Translation, hitDistance);
|
||||||
|
|
||||||
|
//projected = normal * dot(direction, normal);
|
||||||
|
//direction = direction - projected
|
||||||
|
|
||||||
|
//camTrans.Translation += hitNormal * (moveDist - hitDistance); // correct?
|
||||||
|
//camTrans.Translation = hitNormal * (move * hitNormal); // correct?
|
||||||
|
//camTrans.Translation = Actor.Transform.Translation;
|
||||||
|
delta += -Float3.Dot(delta, hitNormal) * hitNormal; // correct?
|
||||||
|
|
||||||
|
camTrans.Translation += delta;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Actor.Transform = camTrans;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,182 +1,181 @@
|
|||||||
using FlaxEditor.Content.Settings;
|
using FlaxEditor.Content.Settings;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CameraRender Script.
|
||||||
|
/// </summary>
|
||||||
|
[ExecuteInEditMode]
|
||||||
|
public class CameraRender : PostProcessEffect
|
||||||
{
|
{
|
||||||
/// <summary>
|
public Camera camera;
|
||||||
/// CameraRender Script.
|
|
||||||
/// </summary>
|
private bool lastEnabled;
|
||||||
[ExecuteInEditMode]
|
public Material material;
|
||||||
public class CameraRender : PostProcessEffect
|
|
||||||
|
private MaterialInstance materialInstance;
|
||||||
|
private SceneRenderTask sceneTask;
|
||||||
|
private SceneRenderTask sceneTask2;
|
||||||
|
|
||||||
|
private GPUTexture texture;
|
||||||
|
private GPUTexture texture2;
|
||||||
|
|
||||||
|
//public override PostProcessEffectLocation Location => PostProcessEffectLocation.Default;
|
||||||
|
//public override int Order => 110;
|
||||||
|
//public override bool CanRender => camera.IsActive;
|
||||||
|
|
||||||
|
private bool useMainCamera = true;
|
||||||
|
public bool rescaleModel = true;
|
||||||
|
|
||||||
|
private Actor viewModelHolder;
|
||||||
|
|
||||||
|
private void CreateTextures(int width, int height)
|
||||||
{
|
{
|
||||||
public Camera camera;
|
GPUTextureDescription textureDesc = GPUTextureDescription.New2D(width, height, PixelFormat.R8G8B8A8_UNorm);
|
||||||
|
|
||||||
private bool lastEnabled;
|
// Prepare texture and SceneRenderTask for viewmodel camera
|
||||||
public Material material;
|
if (texture == null)
|
||||||
|
texture = new GPUTexture();
|
||||||
|
if (texture.Init(ref textureDesc))
|
||||||
|
Console.Print("Failed to create camera texture");
|
||||||
|
|
||||||
private MaterialInstance materialInstance;
|
// Prepare depth texture and SceneRenderTask for viewmodel camera
|
||||||
private SceneRenderTask sceneTask;
|
textureDesc.Format = PixelFormat.R8_UNorm;
|
||||||
private SceneRenderTask sceneTask2;
|
if (texture2 == null)
|
||||||
|
texture2 = new GPUTexture();
|
||||||
|
if (texture2.Init(ref textureDesc))
|
||||||
|
Console.Print("Failed to create camera depth texture");
|
||||||
|
}
|
||||||
|
|
||||||
private GPUTexture texture;
|
public override void OnAwake()
|
||||||
private GPUTexture texture2;
|
{
|
||||||
|
viewModelHolder = Parent.Parent.Parent.Parent.GetChild("ViewModelHolder");
|
||||||
//public override PostProcessEffectLocation Location => PostProcessEffectLocation.Default;
|
if (useMainCamera)
|
||||||
//public override int Order => 110;
|
|
||||||
//public override bool CanRender => camera.IsActive;
|
|
||||||
|
|
||||||
private bool useMainCamera = true;
|
|
||||||
public bool rescaleModel = true;
|
|
||||||
|
|
||||||
private Actor viewModelHolder;
|
|
||||||
|
|
||||||
private void CreateTextures(int width, int height)
|
|
||||||
{
|
{
|
||||||
GPUTextureDescription textureDesc = GPUTextureDescription.New2D(width, height, PixelFormat.R8G8B8A8_UNorm);
|
camera.IsActive = false;
|
||||||
|
void foo(Actor actor)
|
||||||
// Prepare texture and SceneRenderTask for viewmodel camera
|
|
||||||
if (texture == null)
|
|
||||||
texture = new GPUTexture();
|
|
||||||
if (texture.Init(ref textureDesc))
|
|
||||||
Console.Print("Failed to create camera texture");
|
|
||||||
|
|
||||||
// Prepare depth texture and SceneRenderTask for viewmodel camera
|
|
||||||
textureDesc.Format = PixelFormat.R8_UNorm;
|
|
||||||
if (texture2 == null)
|
|
||||||
texture2 = new GPUTexture();
|
|
||||||
if (texture2.Init(ref textureDesc))
|
|
||||||
Console.Print("Failed to create camera depth texture");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnAwake()
|
|
||||||
{
|
|
||||||
viewModelHolder = Parent.Parent.Parent.Parent.GetChild("ViewModelHolder");
|
|
||||||
if (useMainCamera)
|
|
||||||
{
|
{
|
||||||
camera.IsActive = false;
|
actor.Layer = 0;
|
||||||
void foo(Actor actor)
|
foreach (Actor actChild in actor.GetChildren<Actor>())
|
||||||
{
|
foo(actChild);
|
||||||
actor.Layer = 0;
|
|
||||||
foreach (Actor actChild in actor.GetChildren<Actor>())
|
|
||||||
foo(actChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
foo(viewModelHolder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!camera.IsActive)
|
foo(viewModelHolder);
|
||||||
return;
|
|
||||||
|
|
||||||
CreateTextures((int)camera.Viewport.Width, (int)camera.Viewport.Height);
|
|
||||||
|
|
||||||
// Color pass
|
|
||||||
sceneTask = new SceneRenderTask();
|
|
||||||
sceneTask.Order = -1;
|
|
||||||
sceneTask.Camera = camera;
|
|
||||||
sceneTask.ViewMode = ViewMode.Default;
|
|
||||||
sceneTask.Output = texture;
|
|
||||||
sceneTask.ViewFlags = ViewFlags.DefaultGame;
|
|
||||||
sceneTask.Enabled = true;
|
|
||||||
sceneTask.RenderingPercentage = MainRenderTask.Instance.RenderingPercentage;
|
|
||||||
|
|
||||||
// Depth pass
|
|
||||||
sceneTask2 = new SceneRenderTask();
|
|
||||||
sceneTask2.Order = -2;
|
|
||||||
sceneTask2.Camera = camera;
|
|
||||||
sceneTask2.ViewMode = ViewMode.Depth;
|
|
||||||
sceneTask2.Output = texture2;
|
|
||||||
sceneTask2.ViewFlags = ViewFlags.None;
|
|
||||||
sceneTask2.Enabled = true;
|
|
||||||
sceneTask2.RenderingPercentage = MainRenderTask.Instance.RenderingPercentage;
|
|
||||||
|
|
||||||
// Setup material instance and parameters
|
|
||||||
if (materialInstance == null)
|
|
||||||
materialInstance = material.CreateVirtualInstance();
|
|
||||||
materialInstance.SetParameterValue("Input", texture);
|
|
||||||
materialInstance.SetParameterValue("Depth", texture2);
|
|
||||||
|
|
||||||
materialInstance.SetParameterValue("New parameter", true);
|
|
||||||
materialInstance.SetParameterValue("New parameter 0", ChannelMask.Blue);
|
|
||||||
materialInstance.SetParameterValue("New parameter 1", new Color(0.67f));
|
|
||||||
|
|
||||||
materialInstance.SetParameterValue("New parameter 3", 123f);
|
|
||||||
materialInstance.SetParameterValue("New parameter 4", new Float2(1,2));
|
|
||||||
materialInstance.SetParameterValue("New parameter 5", new Float3(1,2,3));
|
|
||||||
materialInstance.SetParameterValue("New parameter 6", new Float4(1,2,3,4));
|
|
||||||
|
|
||||||
materialInstance.SetParameterValue("New parameter 8", 123);
|
|
||||||
materialInstance.SetParameterValue("New parameter 9", new Matrix(0.666f));
|
|
||||||
|
|
||||||
materialInstance.SetParameterValue("New parameter 11", new Quaternion(0.5f, 0.5f, 0.5f, 0.5f));
|
|
||||||
|
|
||||||
materialInstance.SetParameterValue("New parameter 13", new Vector2(1,2));
|
|
||||||
materialInstance.SetParameterValue("New parameter 14", new Vector3(1,2,3));
|
|
||||||
materialInstance.SetParameterValue("New parameter 15", new Vector4(1,2,3,4));
|
|
||||||
//materialInstance.SetParameterValue("New parameter 16", new Transform(new Vector3(1,2,3), new Quaternion(0.5f, 0.5f, 0.5f, 0.5f)));
|
|
||||||
|
|
||||||
lastEnabled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnDestroy()
|
if (!camera.IsActive)
|
||||||
{
|
return;
|
||||||
Destroy(ref sceneTask);
|
|
||||||
Destroy(ref sceneTask2);
|
|
||||||
Destroy(ref texture);
|
|
||||||
Destroy(ref texture2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input,
|
CreateTextures((int)camera.Viewport.Width, (int)camera.Viewport.Height);
|
||||||
GPUTexture output)
|
|
||||||
{
|
|
||||||
if (texture == null || texture2 == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Renderer.DrawPostFxMaterial(context, ref renderContext, materialInstance, output, input.View());
|
// Color pass
|
||||||
}
|
sceneTask = new SceneRenderTask();
|
||||||
|
sceneTask.Order = -1;
|
||||||
|
sceneTask.Camera = camera;
|
||||||
|
sceneTask.ViewMode = ViewMode.Default;
|
||||||
|
sceneTask.Output = texture;
|
||||||
|
sceneTask.ViewFlags = ViewFlags.DefaultGame;
|
||||||
|
sceneTask.Enabled = true;
|
||||||
|
sceneTask.RenderingPercentage = MainRenderTask.Instance.RenderingPercentage;
|
||||||
|
|
||||||
private bool lastRescale = false;
|
// Depth pass
|
||||||
public override void OnUpdate()
|
sceneTask2 = new SceneRenderTask();
|
||||||
{
|
sceneTask2.Order = -2;
|
||||||
|
sceneTask2.Camera = camera;
|
||||||
|
sceneTask2.ViewMode = ViewMode.Depth;
|
||||||
|
sceneTask2.Output = texture2;
|
||||||
|
sceneTask2.ViewFlags = ViewFlags.None;
|
||||||
|
sceneTask2.Enabled = true;
|
||||||
|
sceneTask2.RenderingPercentage = MainRenderTask.Instance.RenderingPercentage;
|
||||||
|
|
||||||
|
// Setup material instance and parameters
|
||||||
|
if (materialInstance == null)
|
||||||
|
materialInstance = material.CreateVirtualInstance();
|
||||||
|
materialInstance.SetParameterValue("Input", texture);
|
||||||
|
materialInstance.SetParameterValue("Depth", texture2);
|
||||||
|
|
||||||
|
materialInstance.SetParameterValue("New parameter", true);
|
||||||
|
materialInstance.SetParameterValue("New parameter 0", ChannelMask.Blue);
|
||||||
|
materialInstance.SetParameterValue("New parameter 1", new Color(0.67f));
|
||||||
|
|
||||||
|
materialInstance.SetParameterValue("New parameter 3", 123f);
|
||||||
|
materialInstance.SetParameterValue("New parameter 4", new Float2(1,2));
|
||||||
|
materialInstance.SetParameterValue("New parameter 5", new Float3(1,2,3));
|
||||||
|
materialInstance.SetParameterValue("New parameter 6", new Float4(1,2,3,4));
|
||||||
|
|
||||||
|
materialInstance.SetParameterValue("New parameter 8", 123);
|
||||||
|
materialInstance.SetParameterValue("New parameter 9", new Matrix(0.666f));
|
||||||
|
|
||||||
|
materialInstance.SetParameterValue("New parameter 11", new Quaternion(0.5f, 0.5f, 0.5f, 0.5f));
|
||||||
|
|
||||||
|
materialInstance.SetParameterValue("New parameter 13", new Vector2(1,2));
|
||||||
|
materialInstance.SetParameterValue("New parameter 14", new Vector3(1,2,3));
|
||||||
|
materialInstance.SetParameterValue("New parameter 15", new Vector4(1,2,3,4));
|
||||||
|
//materialInstance.SetParameterValue("New parameter 16", new Transform(new Vector3(1,2,3), new Quaternion(0.5f, 0.5f, 0.5f, 0.5f)));
|
||||||
|
|
||||||
|
lastEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDestroy()
|
||||||
|
{
|
||||||
|
Destroy(ref sceneTask);
|
||||||
|
Destroy(ref sceneTask2);
|
||||||
|
Destroy(ref texture);
|
||||||
|
Destroy(ref texture2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input,
|
||||||
|
GPUTexture output)
|
||||||
|
{
|
||||||
|
if (texture == null || texture2 == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Renderer.DrawPostFxMaterial(context, ref renderContext, materialInstance, output, input.View());
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool lastRescale = false;
|
||||||
|
public override void OnUpdate()
|
||||||
|
{
|
||||||
#if FLAX_EDITOR
|
#if FLAX_EDITOR
|
||||||
if (Input.GetKeyDown(KeyboardKeys.F7))
|
if (Input.GetKeyDown(KeyboardKeys.F7))
|
||||||
{
|
{
|
||||||
PhysicsSettings physicsSettings = GameSettings.Load<PhysicsSettings>();
|
PhysicsSettings physicsSettings = GameSettings.Load<PhysicsSettings>();
|
||||||
physicsSettings.EnableSubstepping = !physicsSettings.EnableSubstepping;
|
physicsSettings.EnableSubstepping = !physicsSettings.EnableSubstepping;
|
||||||
GameSettings.Save(physicsSettings);
|
GameSettings.Save(physicsSettings);
|
||||||
//GameSettings.Apply();
|
//GameSettings.Apply();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (lastEnabled != camera.IsActive)
|
if (lastEnabled != camera.IsActive)
|
||||||
{
|
|
||||||
lastEnabled = camera.IsActive;
|
|
||||||
sceneTask.Enabled = lastEnabled;
|
|
||||||
sceneTask2.Enabled = lastEnabled;
|
|
||||||
sceneTask.RenderingPercentage = MainRenderTask.Instance.RenderingPercentage * 0.5f;
|
|
||||||
sceneTask2.RenderingPercentage = MainRenderTask.Instance.RenderingPercentage * 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useMainCamera && rescaleModel != lastRescale)
|
|
||||||
{
|
|
||||||
lastRescale = rescaleModel;
|
|
||||||
if (rescaleModel)
|
|
||||||
viewModelHolder.Scale = new Float3(0.75f);
|
|
||||||
else
|
|
||||||
viewModelHolder.Scale = new Float3(1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!camera.IsActive)
|
|
||||||
return;
|
|
||||||
if (texture == null)
|
|
||||||
OnAwake();
|
|
||||||
|
|
||||||
if ((int)camera.Viewport.Width != texture.Width || (int)camera.Viewport.Height != texture.Height)
|
|
||||||
CreateTextures((int)camera.Viewport.Width, (int)camera.Viewport.Height);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnEnable()
|
|
||||||
{
|
{
|
||||||
//OnAwake();
|
lastEnabled = camera.IsActive;
|
||||||
|
sceneTask.Enabled = lastEnabled;
|
||||||
|
sceneTask2.Enabled = lastEnabled;
|
||||||
|
sceneTask.RenderingPercentage = MainRenderTask.Instance.RenderingPercentage * 0.5f;
|
||||||
|
sceneTask2.RenderingPercentage = MainRenderTask.Instance.RenderingPercentage * 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (useMainCamera && rescaleModel != lastRescale)
|
||||||
|
{
|
||||||
|
lastRescale = rescaleModel;
|
||||||
|
if (rescaleModel)
|
||||||
|
viewModelHolder.Scale = new Float3(0.75f);
|
||||||
|
else
|
||||||
|
viewModelHolder.Scale = new Float3(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!camera.IsActive)
|
||||||
|
return;
|
||||||
|
if (texture == null)
|
||||||
|
OnAwake();
|
||||||
|
|
||||||
|
if ((int)camera.Viewport.Width != texture.Width || (int)camera.Viewport.Height != texture.Height)
|
||||||
|
CreateTextures((int)camera.Viewport.Width, (int)camera.Viewport.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEnable()
|
||||||
|
{
|
||||||
|
//OnAwake();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,75 +1,74 @@
|
|||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public class CameraSpring : Script
|
||||||
{
|
{
|
||||||
public class CameraSpring : Script
|
private bool lastGround;
|
||||||
|
private Float3 lastPosition;
|
||||||
|
public float percY;
|
||||||
|
|
||||||
|
private Actor playerActor;
|
||||||
|
private PlayerMovement playerMovement;
|
||||||
|
|
||||||
|
public float speed = 240f;
|
||||||
|
private Float3 targetOffset;
|
||||||
|
private Actor viewModelHolder;
|
||||||
|
|
||||||
|
public override void OnStart()
|
||||||
{
|
{
|
||||||
private bool lastGround;
|
playerActor = Actor.Parent.Parent;
|
||||||
private Float3 lastPosition;
|
playerMovement = playerActor.GetScript<PlayerMovement>();
|
||||||
public float percY;
|
viewModelHolder = playerActor.GetChild("ViewModelHolder");
|
||||||
|
|
||||||
private Actor playerActor;
|
lastGround = playerMovement.OnGround;
|
||||||
private PlayerMovement playerMovement;
|
targetOffset = Actor.LocalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
public float speed = 240f;
|
private void UpdatePosition(Float3 position)
|
||||||
private Float3 targetOffset;
|
{
|
||||||
private Actor viewModelHolder;
|
Actor.Position = position;
|
||||||
|
viewModelHolder.Position = position;
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnStart()
|
public override void OnUpdate()
|
||||||
|
{
|
||||||
|
Float3 position = Actor.Parent.Position + targetOffset;
|
||||||
|
Float3 targetPosition = position;
|
||||||
|
|
||||||
|
if (playerMovement.OnGround)
|
||||||
{
|
{
|
||||||
playerActor = Actor.Parent.Parent;
|
float deltaY = position.Y - lastPosition.Y;
|
||||||
playerMovement = playerActor.GetScript<PlayerMovement>();
|
//if (Mathf.Abs(deltaY) < 10f)
|
||||||
viewModelHolder = playerActor.GetChild("ViewModelHolder");
|
if (deltaY > 0)
|
||||||
|
|
||||||
lastGround = playerMovement.OnGround;
|
|
||||||
targetOffset = Actor.LocalPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdatePosition(Float3 position)
|
|
||||||
{
|
|
||||||
Actor.Position = position;
|
|
||||||
viewModelHolder.Position = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnUpdate()
|
|
||||||
{
|
|
||||||
Float3 position = Actor.Parent.Position + targetOffset;
|
|
||||||
Float3 targetPosition = position;
|
|
||||||
|
|
||||||
if (playerMovement.OnGround)
|
|
||||||
{
|
{
|
||||||
float deltaY = position.Y - lastPosition.Y;
|
if (deltaY > 100f)
|
||||||
//if (Mathf.Abs(deltaY) < 10f)
|
|
||||||
if (deltaY > 0)
|
|
||||||
{
|
{
|
||||||
if (deltaY > 100f)
|
// Teleported, snap instantly
|
||||||
{
|
UpdatePosition(position);
|
||||||
// Teleported, snap instantly
|
}
|
||||||
UpdatePosition(position);
|
else
|
||||||
}
|
{
|
||||||
else
|
const float catchUpDistance = 10f;
|
||||||
{
|
const float catchUpMinMultip = 0.25f;
|
||||||
const float catchUpDistance = 10f;
|
percY = Mathf.Abs(deltaY) / catchUpDistance;
|
||||||
const float catchUpMinMultip = 0.25f;
|
percY = Mathf.Min(1.0f, percY + catchUpMinMultip);
|
||||||
percY = Mathf.Abs(deltaY) / catchUpDistance;
|
percY *= percY;
|
||||||
percY = Mathf.Min(1.0f, percY + catchUpMinMultip);
|
|
||||||
percY *= percY;
|
|
||||||
|
|
||||||
float adjustSpeed = speed * Time.DeltaTime * percY;
|
float adjustSpeed = speed * Time.DeltaTime * percY;
|
||||||
|
|
||||||
position.Y = lastPosition.Y; //-= deltaY;
|
position.Y = lastPosition.Y; //-= deltaY;
|
||||||
position.Y = Mathf.MoveTowards(position.Y, targetPosition.Y, adjustSpeed);
|
position.Y = Mathf.MoveTowards(position.Y, targetPosition.Y, adjustSpeed);
|
||||||
UpdatePosition(position);
|
UpdatePosition(position);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
UpdatePosition(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastPosition = position;
|
|
||||||
lastGround = playerMovement.OnGround;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdatePosition(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPosition = position;
|
||||||
|
lastGround = playerMovement.OnGround;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,83 +1,82 @@
|
|||||||
using System;
|
using System;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public class WeaponSway : Script
|
||||||
{
|
{
|
||||||
public class WeaponSway : Script
|
private Actor cameraHolder;
|
||||||
|
|
||||||
|
private Actor rootActor;
|
||||||
|
public float swaySpeed = 3000f;
|
||||||
|
|
||||||
|
private float timeRemainder;
|
||||||
|
|
||||||
|
public override void OnStart()
|
||||||
{
|
{
|
||||||
private Actor cameraHolder;
|
rootActor = Actor.Parent.GetChild("RootActor");
|
||||||
|
cameraHolder = rootActor.GetChild("CameraHolder");
|
||||||
|
Actor.LocalOrientation = GetRotation();
|
||||||
|
}
|
||||||
|
|
||||||
private Actor rootActor;
|
private Quaternion GetRotation()
|
||||||
public float swaySpeed = 3000f;
|
{
|
||||||
|
Quaternion pitch = cameraHolder.LocalOrientation;
|
||||||
|
Quaternion yawRoll = rootActor.LocalOrientation;
|
||||||
|
return yawRoll * pitch;
|
||||||
|
}
|
||||||
|
|
||||||
private float timeRemainder;
|
public override void OnLateUpdate()
|
||||||
|
{
|
||||||
|
Quaternion rotation = GetRotation();
|
||||||
|
|
||||||
public override void OnStart()
|
Float3 targetAngles = rotation.EulerAngles;
|
||||||
|
Float3 angles = Actor.LocalOrientation.EulerAngles;
|
||||||
|
|
||||||
|
// Ensure the swaying is smooth when framerate fluctuates slightly
|
||||||
|
float remaining = Time.DeltaTime + timeRemainder;
|
||||||
|
const float minTime = 1f / 120f;
|
||||||
|
do
|
||||||
{
|
{
|
||||||
rootActor = Actor.Parent.GetChild("RootActor");
|
float stepTime = Mathf.Min(Time.DeltaTime, minTime);
|
||||||
cameraHolder = rootActor.GetChild("CameraHolder");
|
remaining -= stepTime;
|
||||||
Actor.LocalOrientation = GetRotation();
|
float swaySpeedScaled = swaySpeed * stepTime;
|
||||||
}
|
|
||||||
|
|
||||||
private Quaternion GetRotation()
|
float deltaX = Mathf.DeltaAngle(angles.X, targetAngles.X);
|
||||||
{
|
float deltaY = Mathf.DeltaAngle(angles.Y, targetAngles.Y);
|
||||||
Quaternion pitch = cameraHolder.LocalOrientation;
|
float deltaZ = Mathf.DeltaAngle(angles.Z, targetAngles.Z);
|
||||||
Quaternion yawRoll = rootActor.LocalOrientation;
|
|
||||||
return yawRoll * pitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnLateUpdate()
|
const float maxAngle = 30f;
|
||||||
{
|
if (deltaX > maxAngle)
|
||||||
Quaternion rotation = GetRotation();
|
angles.X -= maxAngle - deltaX;
|
||||||
|
else if (deltaX < -maxAngle)
|
||||||
|
angles.X += maxAngle + deltaX;
|
||||||
|
if (deltaY > maxAngle)
|
||||||
|
angles.Y -= maxAngle - deltaY;
|
||||||
|
else if (deltaY < -maxAngle)
|
||||||
|
angles.Y += maxAngle + deltaY;
|
||||||
|
if (deltaZ > maxAngle)
|
||||||
|
angles.Z -= maxAngle - deltaZ;
|
||||||
|
else if (deltaZ < -maxAngle)
|
||||||
|
angles.Z += maxAngle + deltaZ;
|
||||||
|
|
||||||
Float3 targetAngles = rotation.EulerAngles;
|
float percX = Mathf.Abs(deltaX) / maxAngle;
|
||||||
Float3 angles = Actor.LocalOrientation.EulerAngles;
|
float percY = Mathf.Abs(deltaY) / maxAngle;
|
||||||
|
float percZ = Mathf.Abs(deltaZ) / maxAngle;
|
||||||
|
float minSpeed = swaySpeedScaled * 0.00001f * 0f;
|
||||||
|
|
||||||
// Ensure the swaying is smooth when framerate fluctuates slightly
|
Func<float, float> fun = f => Mathf.Pow(f, 1.3f);
|
||||||
float remaining = Time.DeltaTime + timeRemainder;
|
|
||||||
const float minTime = 1f / 120f;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
float stepTime = Mathf.Min(Time.DeltaTime, minTime);
|
|
||||||
remaining -= stepTime;
|
|
||||||
float swaySpeedScaled = swaySpeed * stepTime;
|
|
||||||
|
|
||||||
float deltaX = Mathf.DeltaAngle(angles.X, targetAngles.X);
|
angles.X = Mathf.MoveTowardsAngle(angles.X, targetAngles.X,
|
||||||
float deltaY = Mathf.DeltaAngle(angles.Y, targetAngles.Y);
|
Math.Max(swaySpeedScaled * fun(percX), minSpeed));
|
||||||
float deltaZ = Mathf.DeltaAngle(angles.Z, targetAngles.Z);
|
angles.Y = Mathf.MoveTowardsAngle(angles.Y, targetAngles.Y,
|
||||||
|
Math.Max(swaySpeedScaled * fun(percY), minSpeed));
|
||||||
|
angles.Z = Mathf.MoveTowardsAngle(angles.Z, targetAngles.Z,
|
||||||
|
Math.Max(swaySpeedScaled * fun(percZ), minSpeed));
|
||||||
|
} while (remaining > minTime);
|
||||||
|
|
||||||
const float maxAngle = 30f;
|
timeRemainder -= remaining;
|
||||||
if (deltaX > maxAngle)
|
|
||||||
angles.X -= maxAngle - deltaX;
|
|
||||||
else if (deltaX < -maxAngle)
|
|
||||||
angles.X += maxAngle + deltaX;
|
|
||||||
if (deltaY > maxAngle)
|
|
||||||
angles.Y -= maxAngle - deltaY;
|
|
||||||
else if (deltaY < -maxAngle)
|
|
||||||
angles.Y += maxAngle + deltaY;
|
|
||||||
if (deltaZ > maxAngle)
|
|
||||||
angles.Z -= maxAngle - deltaZ;
|
|
||||||
else if (deltaZ < -maxAngle)
|
|
||||||
angles.Z += maxAngle + deltaZ;
|
|
||||||
|
|
||||||
float percX = Mathf.Abs(deltaX) / maxAngle;
|
Actor.LocalOrientation = Quaternion.Euler(angles);
|
||||||
float percY = Mathf.Abs(deltaY) / maxAngle;
|
|
||||||
float percZ = Mathf.Abs(deltaZ) / maxAngle;
|
|
||||||
float minSpeed = swaySpeedScaled * 0.00001f * 0f;
|
|
||||||
|
|
||||||
Func<float, float> fun = f => Mathf.Pow(f, 1.3f);
|
|
||||||
|
|
||||||
angles.X = Mathf.MoveTowardsAngle(angles.X, targetAngles.X,
|
|
||||||
Math.Max(swaySpeedScaled * fun(percX), minSpeed));
|
|
||||||
angles.Y = Mathf.MoveTowardsAngle(angles.Y, targetAngles.Y,
|
|
||||||
Math.Max(swaySpeedScaled * fun(percY), minSpeed));
|
|
||||||
angles.Z = Mathf.MoveTowardsAngle(angles.Z, targetAngles.Z,
|
|
||||||
Math.Max(swaySpeedScaled * fun(percZ), minSpeed));
|
|
||||||
} while (remaining > minTime);
|
|
||||||
|
|
||||||
timeRemainder -= remaining;
|
|
||||||
|
|
||||||
Actor.LocalOrientation = Quaternion.Euler(angles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,42 +1,41 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
// Holds common miscellaneous Console variables and commands
|
||||||
|
public static class CommonCommands
|
||||||
{
|
{
|
||||||
// Holds common miscellaneous Console variables and commands
|
[ConsoleVariable("developer")]
|
||||||
public static class CommonCommands
|
public static string Developer
|
||||||
{
|
{
|
||||||
[ConsoleVariable("developer")]
|
get => Console.DebugVerbosity.ToString();
|
||||||
public static string Developer
|
set
|
||||||
{
|
{
|
||||||
get => Console.DebugVerbosity.ToString();
|
if (int.TryParse(value, out int intValue) && intValue >= 0)
|
||||||
set
|
Console.DebugVerbosity = intValue;
|
||||||
{
|
|
||||||
if (int.TryParse(value, out int intValue) && intValue >= 0)
|
|
||||||
Console.DebugVerbosity = intValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConsoleCommand("")]
|
|
||||||
public static void NullCommand()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConsoleCommand("echo")]
|
|
||||||
public static void EchoCommand()
|
|
||||||
{
|
|
||||||
Console.Print("nothing");
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConsoleCommand("echo")]
|
|
||||||
public static void EchoCommand(string[] text)
|
|
||||||
{
|
|
||||||
Console.Print(string.Join(" ", text));
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConsoleCommand("debugthrow")]
|
|
||||||
public static void DebugThrowCommand(string[] text)
|
|
||||||
{
|
|
||||||
throw new Exception(string.Join(" ", text));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("")]
|
||||||
|
public static void NullCommand()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("echo")]
|
||||||
|
public static void EchoCommand()
|
||||||
|
{
|
||||||
|
Console.Print("nothing");
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("echo")]
|
||||||
|
public static void EchoCommand(string[] text)
|
||||||
|
{
|
||||||
|
Console.Print(string.Join(" ", text));
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("debugthrow")]
|
||||||
|
public static void DebugThrowCommand(string[] text)
|
||||||
|
{
|
||||||
|
throw new Exception(string.Join(" ", text));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,34 +1,33 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public class Config
|
||||||
{
|
{
|
||||||
public class Config
|
private Dictionary<string, string> dictionary = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
public Config()
|
||||||
{
|
{
|
||||||
private Dictionary<string, string> dictionary = new Dictionary<string, string>();
|
}
|
||||||
|
|
||||||
public Config()
|
public string this[string key]
|
||||||
{
|
{
|
||||||
}
|
get => dictionary[key];
|
||||||
|
set => dictionary[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
public string this[string key]
|
// This is for debugging only, remove this later
|
||||||
{
|
public string[] Commands;
|
||||||
get => dictionary[key];
|
|
||||||
set => dictionary[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is for debugging only, remove this later
|
public string[] GetLines()
|
||||||
public string[] Commands;
|
{
|
||||||
|
string[] lines = new string[dictionary.Count + Commands.Length];
|
||||||
|
int lineIndex = 0;
|
||||||
|
foreach (var kvp in dictionary)
|
||||||
|
lines[lineIndex++] = $"{kvp.Key} {kvp.Value}";
|
||||||
|
foreach (var cmd in Commands)
|
||||||
|
lines[lineIndex++] = cmd;
|
||||||
|
|
||||||
public string[] GetLines()
|
return lines;
|
||||||
{
|
|
||||||
string[] lines = new string[dictionary.Count + Commands.Length];
|
|
||||||
int lineIndex = 0;
|
|
||||||
foreach (var kvp in dictionary)
|
|
||||||
lines[lineIndex++] = $"{kvp.Key} {kvp.Value}";
|
|
||||||
foreach (var cmd in Commands)
|
|
||||||
lines[lineIndex++] = cmd;
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,51 +1,50 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public class ConfigParser
|
||||||
{
|
{
|
||||||
public class ConfigParser
|
private ConfigParser()
|
||||||
{
|
{
|
||||||
private ConfigParser()
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Config ParseFile(string path)
|
||||||
|
{
|
||||||
|
Config config = new Config();
|
||||||
|
if (!File.Exists(path))
|
||||||
{
|
{
|
||||||
|
Console.Print($"Config file not found in path: {path}");
|
||||||
}
|
|
||||||
|
|
||||||
public static Config ParseFile(string path)
|
|
||||||
{
|
|
||||||
Config config = new Config();
|
|
||||||
if (!File.Exists(path))
|
|
||||||
{
|
|
||||||
Console.Print($"Config file not found in path: {path}");
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
/*using*/ FileStream file = File.OpenRead(path);
|
|
||||||
/*using*/ StreamReader sr = new StreamReader(file);
|
|
||||||
|
|
||||||
List<string> commands = new List<string>();
|
|
||||||
string line;
|
|
||||||
while ((line = sr.ReadLine()?.Trim()) != null)
|
|
||||||
{
|
|
||||||
if (line.StartsWith(@"//"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int spacePos = line.IndexOf(' ');
|
|
||||||
if (spacePos == -1)
|
|
||||||
{
|
|
||||||
commands.Add(line);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string key = line.Substring(0, spacePos);
|
|
||||||
string value = line.Substring(spacePos+1);
|
|
||||||
|
|
||||||
value = value.Trim('"');
|
|
||||||
|
|
||||||
config[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Commands = commands.ToArray();
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
/*using*/ FileStream file = File.OpenRead(path);
|
||||||
|
/*using*/ StreamReader sr = new StreamReader(file);
|
||||||
|
|
||||||
|
List<string> commands = new List<string>();
|
||||||
|
string line;
|
||||||
|
while ((line = sr.ReadLine()?.Trim()) != null)
|
||||||
|
{
|
||||||
|
if (line.StartsWith(@"//"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int spacePos = line.IndexOf(' ');
|
||||||
|
if (spacePos == -1)
|
||||||
|
{
|
||||||
|
commands.Add(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string key = line.Substring(0, spacePos);
|
||||||
|
string value = line.Substring(spacePos+1);
|
||||||
|
|
||||||
|
value = value.Trim('"');
|
||||||
|
|
||||||
|
config[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Commands = commands.ToArray();
|
||||||
|
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -2,48 +2,48 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.All)]
|
||||||
|
public abstract class ConsoleBaseAttribute : Attribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.All)]
|
// Additional aliases for this command, these should only be used with user interaction.
|
||||||
public abstract class ConsoleBaseAttribute : Attribute
|
// Commands such as 'cvarlist' should not list these in order to avoid clutter.
|
||||||
|
internal string[] aliases = new string[0];
|
||||||
|
internal string name;
|
||||||
|
|
||||||
|
public ConsoleBaseAttribute(string name)
|
||||||
{
|
{
|
||||||
// Additional aliases for this command, these should only be used with user interaction.
|
this.name = name.ToLowerInvariant();
|
||||||
// Commands such as 'cvarlist' should not list these in order to avoid clutter.
|
|
||||||
internal string[] aliases = new string[0];
|
|
||||||
internal string name;
|
|
||||||
|
|
||||||
public ConsoleBaseAttribute(string name)
|
|
||||||
{
|
|
||||||
this.name = name.ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConsoleBaseAttribute(params string[] names)
|
|
||||||
{
|
|
||||||
name = names[0].ToLowerInvariant();
|
|
||||||
aliases = new List<string>(names).Skip(1).Select(x => x.ToLowerInvariant()).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConsoleFlags flags { get; private set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.All)]
|
public ConsoleBaseAttribute(params string[] names)
|
||||||
public class ConsoleVariableAttribute : ConsoleBaseAttribute
|
|
||||||
{
|
{
|
||||||
public ConsoleVariableAttribute(string name) : base(name)
|
name = names[0].ToLowerInvariant();
|
||||||
{
|
aliases = new List<string>(names).Skip(1).Select(x => x.ToLowerInvariant()).ToArray();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.All)]
|
public ConsoleFlags flags { get; private set; }
|
||||||
public class ConsoleCommandAttribute : ConsoleBaseAttribute
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.All)]
|
||||||
|
public class ConsoleVariableAttribute : ConsoleBaseAttribute
|
||||||
|
{
|
||||||
|
public ConsoleVariableAttribute(string name) : base(name)
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.All)]
|
||||||
|
public class ConsoleCommandAttribute : ConsoleBaseAttribute
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a command to Console system.
|
/// Registers a command to Console system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">Name used for calling this command.</param>
|
/// <param name="name">Name used for calling this command.</param>
|
||||||
public ConsoleCommandAttribute(string name) : base(name)
|
public ConsoleCommandAttribute(string name) : base(name)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a command to Console system.
|
/// Registers a command to Console system.
|
||||||
@@ -53,15 +53,14 @@ namespace Game
|
|||||||
/// names are aliases.
|
/// names are aliases.
|
||||||
/// </param>
|
/// </param>
|
||||||
public ConsoleCommandAttribute(params string[] names) : base(names)
|
public ConsoleCommandAttribute(params string[] names) : base(names)
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor for the subsystem, must be called first before registering console commands.
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.All)]
|
|
||||||
public class ConsoleSubsystemInitializer : Attribute
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for the subsystem, must be called first before registering console commands.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.All)]
|
||||||
|
public class ConsoleSubsystemInitializer : Attribute
|
||||||
|
{
|
||||||
}
|
}
|
||||||
@@ -1,78 +1,77 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
internal struct ConsoleCommand
|
||||||
{
|
{
|
||||||
internal struct ConsoleCommand
|
public string name { get; }
|
||||||
|
|
||||||
|
private readonly MethodInfo[] methods;
|
||||||
|
|
||||||
|
public ConsoleCommand(string name, MethodInfo[] method)
|
||||||
{
|
{
|
||||||
public string name { get; }
|
this.name = name;
|
||||||
|
methods = method;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly MethodInfo[] methods;
|
public void Invoke()
|
||||||
|
{
|
||||||
public ConsoleCommand(string name, MethodInfo[] method)
|
foreach (MethodInfo method in methods)
|
||||||
{
|
{
|
||||||
this.name = name;
|
var methodParameters = method.GetParameters();
|
||||||
methods = method;
|
if (methodParameters.Length == 0)
|
||||||
|
method.Invoke(null, null);
|
||||||
|
else if (methodParameters.Length == 1 && methodParameters[0].ParameterType == typeof(string[]))
|
||||||
|
method.Invoke(null, new object[] { Array.Empty<string>() });
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Invoke()
|
throw new Exception("Unexpected number of parameters.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invoke(string[] parameters)
|
||||||
|
{
|
||||||
|
MethodInfo match = null;
|
||||||
|
foreach (MethodInfo method in methods)
|
||||||
{
|
{
|
||||||
foreach (MethodInfo method in methods)
|
var methodParameters = method.GetParameters();
|
||||||
|
if (methodParameters.Length == 1 && methodParameters[0].ParameterType == typeof(string[]))
|
||||||
{
|
{
|
||||||
var methodParameters = method.GetParameters();
|
match = method;
|
||||||
if (methodParameters.Length == 0)
|
continue;
|
||||||
method.Invoke(null, null);
|
|
||||||
else if (methodParameters.Length == 1 && methodParameters[0].ParameterType == typeof(string[]))
|
|
||||||
method.Invoke(null, new object[] { Array.Empty<string>() });
|
|
||||||
else
|
|
||||||
continue;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Exception("Unexpected number of parameters.");
|
if (methodParameters.Length != parameters.Length)
|
||||||
}
|
continue;
|
||||||
|
|
||||||
public void Invoke(string[] parameters)
|
// TODO: try to parse string parameters to needed types first,
|
||||||
{
|
// may require finding the exact match first instead of first matching one.
|
||||||
MethodInfo match = null;
|
for (int i = 0; i < methodParameters.Length; i++)
|
||||||
foreach (MethodInfo method in methods)
|
//if (methodParameters[i].ParameterType != parameters[i].GetType())
|
||||||
{
|
if (methodParameters[i].ParameterType != typeof(string))
|
||||||
var methodParameters = method.GetParameters();
|
|
||||||
if (methodParameters.Length == 1 && methodParameters[0].ParameterType == typeof(string[]))
|
|
||||||
{
|
|
||||||
match = method;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
|
if (match != null)
|
||||||
|
// Prefer exact number of parameters over string[] match
|
||||||
if (methodParameters.Length != parameters.Length)
|
if (methodParameters.Length != parameters.Length)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// TODO: try to parse string parameters to needed types first,
|
match = method;
|
||||||
// may require finding the exact match first instead of first matching one.
|
|
||||||
for (int i = 0; i < methodParameters.Length; i++)
|
|
||||||
//if (methodParameters[i].ParameterType != parameters[i].GetType())
|
|
||||||
if (methodParameters[i].ParameterType != typeof(string))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (match != null)
|
|
||||||
// Prefer exact number of parameters over string[] match
|
|
||||||
if (methodParameters.Length != parameters.Length)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
match = method;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match != null)
|
|
||||||
{
|
|
||||||
if (match.GetParameters().Length == 1 && match.GetParameters()[0].ParameterType == typeof(string[]))
|
|
||||||
match.Invoke(null, new object[] { parameters });
|
|
||||||
else
|
|
||||||
match.Invoke(null, parameters);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("Unexpected number of parameters.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (match != null)
|
||||||
|
{
|
||||||
|
if (match.GetParameters().Length == 1 && match.GetParameters()[0].ParameterType == typeof(string[]))
|
||||||
|
match.Invoke(null, new object[] { parameters });
|
||||||
|
else
|
||||||
|
match.Invoke(null, parameters);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Unexpected number of parameters.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -2,171 +2,170 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public class ConsoleInputTextBox : ConsoleTextBoxBase
|
||||||
{
|
{
|
||||||
public class ConsoleInputTextBox : ConsoleTextBoxBase
|
private readonly ConsoleContentTextBox contentBox;
|
||||||
|
|
||||||
|
private int inputHistoryIndex = -1;
|
||||||
|
|
||||||
|
public ConsoleInputTextBox()
|
||||||
{
|
{
|
||||||
private readonly ConsoleContentTextBox contentBox;
|
}
|
||||||
|
|
||||||
private int inputHistoryIndex = -1;
|
public ConsoleInputTextBox(ConsoleContentTextBox contentBox, float x, float y, float width, float height) :
|
||||||
|
base(x, y, width, height)
|
||||||
|
{
|
||||||
|
this.contentBox = contentBox;
|
||||||
|
IsMultiline = true; // Not really but behaves better than single-line box
|
||||||
|
}
|
||||||
|
|
||||||
public ConsoleInputTextBox()
|
public override string TextPrefix => Console.LinePrefix;
|
||||||
|
|
||||||
|
protected override Rectangle TextRectangle => new Rectangle(0, 0, Width, Height);
|
||||||
|
protected override Rectangle TextClipRectangle => new Rectangle(0, 0, Width, Height);
|
||||||
|
|
||||||
|
private bool IsConsoleKeyPressed(KeyboardKeys key = KeyboardKeys.None)
|
||||||
|
{
|
||||||
|
// Ignore any characters generated by the key which opens the console
|
||||||
|
string inputTextLower = Input.InputText.ToLowerInvariant();
|
||||||
|
|
||||||
|
IEnumerable<ActionConfig> consoleKeyMappings;
|
||||||
|
if (key == KeyboardKeys.None)
|
||||||
|
consoleKeyMappings = Input.ActionMappings.Where(x => x.Name == "Console" && x.Key != KeyboardKeys.None);
|
||||||
|
else
|
||||||
|
consoleKeyMappings = Input.ActionMappings.Where(x => x.Name == "Console" && x.Key == key);
|
||||||
|
foreach (ActionConfig mapping in consoleKeyMappings)
|
||||||
{
|
{
|
||||||
}
|
if (inputTextLower.Length > 0)
|
||||||
|
|
||||||
public ConsoleInputTextBox(ConsoleContentTextBox contentBox, float x, float y, float width, float height) :
|
|
||||||
base(x, y, width, height)
|
|
||||||
{
|
|
||||||
this.contentBox = contentBox;
|
|
||||||
IsMultiline = true; // Not really but behaves better than single-line box
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string TextPrefix => Console.LinePrefix;
|
|
||||||
|
|
||||||
protected override Rectangle TextRectangle => new Rectangle(0, 0, Width, Height);
|
|
||||||
protected override Rectangle TextClipRectangle => new Rectangle(0, 0, Width, Height);
|
|
||||||
|
|
||||||
private bool IsConsoleKeyPressed(KeyboardKeys key = KeyboardKeys.None)
|
|
||||||
{
|
|
||||||
// Ignore any characters generated by the key which opens the console
|
|
||||||
string inputTextLower = Input.InputText.ToLowerInvariant();
|
|
||||||
|
|
||||||
IEnumerable<ActionConfig> consoleKeyMappings;
|
|
||||||
if (key == KeyboardKeys.None)
|
|
||||||
consoleKeyMappings = Input.ActionMappings.Where(x => x.Name == "Console" && x.Key != KeyboardKeys.None);
|
|
||||||
else
|
|
||||||
consoleKeyMappings = Input.ActionMappings.Where(x => x.Name == "Console" && x.Key == key);
|
|
||||||
foreach (ActionConfig mapping in consoleKeyMappings)
|
|
||||||
{
|
{
|
||||||
if (inputTextLower.Length > 0)
|
if ((mapping.Key == KeyboardKeys.Backslash || mapping.Key == KeyboardKeys.BackQuote) &&
|
||||||
{
|
(inputTextLower.Contains('ö') ||
|
||||||
if ((mapping.Key == KeyboardKeys.Backslash || mapping.Key == KeyboardKeys.BackQuote) &&
|
inputTextLower.Contains('æ') ||
|
||||||
(inputTextLower.Contains('ö') ||
|
inputTextLower.Contains('ø')))
|
||||||
inputTextLower.Contains('æ') ||
|
continue; // Scandinavian keyboard layouts
|
||||||
inputTextLower.Contains('ø')))
|
if (mapping.Key == KeyboardKeys.BackQuote && inputTextLower.Contains('\''))
|
||||||
continue; // Scandinavian keyboard layouts
|
continue;
|
||||||
if (mapping.Key == KeyboardKeys.BackQuote && inputTextLower.Contains('\''))
|
if (mapping.Key == KeyboardKeys.Backslash &&
|
||||||
continue;
|
(inputTextLower.Contains('\\') || inputTextLower.Contains('|')))
|
||||||
if (mapping.Key == KeyboardKeys.Backslash &&
|
continue;
|
||||||
(inputTextLower.Contains('\\') || inputTextLower.Contains('|')))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Input.GetKey(mapping.Key))
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
if (Input.GetKey(mapping.Key))
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnCharInput(char c)
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnCharInput(char c)
|
||||||
|
{
|
||||||
|
if (IsConsoleKeyPressed())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return base.OnCharInput(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnKeyDown(KeyboardKeys key)
|
||||||
|
{
|
||||||
|
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
|
||||||
|
bool ctrlDown = Root.GetKey(KeyboardKeys.Control);
|
||||||
|
|
||||||
|
if (IsConsoleKeyPressed(key))
|
||||||
{
|
{
|
||||||
if (IsConsoleKeyPressed())
|
Clear();
|
||||||
return true;
|
|
||||||
|
|
||||||
return base.OnCharInput(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnKeyDown(KeyboardKeys key)
|
|
||||||
{
|
|
||||||
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
|
|
||||||
bool ctrlDown = Root.GetKey(KeyboardKeys.Control);
|
|
||||||
|
|
||||||
if (IsConsoleKeyPressed(key))
|
|
||||||
{
|
|
||||||
Clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == KeyboardKeys.Escape)
|
|
||||||
{
|
|
||||||
Console.Close();
|
|
||||||
Clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == KeyboardKeys.Return)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Console.Execute(Text, true);
|
|
||||||
inputHistoryIndex = -1;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
contentBox.ScrollOffset = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == KeyboardKeys.ArrowUp)
|
|
||||||
{
|
|
||||||
inputHistoryIndex++;
|
|
||||||
|
|
||||||
string line = Console.GetBufferHistory(inputHistoryIndex);
|
|
||||||
if (line == null)
|
|
||||||
{
|
|
||||||
inputHistoryIndex--;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetText(line);
|
|
||||||
SetSelection(TextLength);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == KeyboardKeys.ArrowDown)
|
|
||||||
{
|
|
||||||
if (inputHistoryIndex > 0)
|
|
||||||
inputHistoryIndex--;
|
|
||||||
|
|
||||||
string line = Console.GetBufferHistory(inputHistoryIndex);
|
|
||||||
if (line == null)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
SetText(line);
|
|
||||||
SetSelection(TextLength);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == KeyboardKeys.PageUp || key == KeyboardKeys.PageDown)
|
|
||||||
return contentBox.OnKeyDown(key);
|
|
||||||
|
|
||||||
#if FLAX_EDITOR
|
|
||||||
if (key == KeyboardKeys.F5)
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return base.OnKeyDown(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnLostFocus()
|
|
||||||
{
|
|
||||||
// Prevent caret location getting reset back to beginning
|
|
||||||
bool oldEditing = _isEditing;
|
|
||||||
_isEditing = false;
|
|
||||||
base.OnLostFocus();
|
|
||||||
_isEditing = oldEditing;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
|
||||||
{
|
|
||||||
base.OnMouseDown(location, button);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnMouseWheel(Float2 location, float delta)
|
if (key == KeyboardKeys.Escape)
|
||||||
{
|
{
|
||||||
return contentBox.OnMouseWheel(location, delta);
|
Console.Close();
|
||||||
|
Clear();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Draw()
|
if (key == KeyboardKeys.Return)
|
||||||
{
|
{
|
||||||
Profiler.BeginEvent("ConsoleInputTextBoxDraw");
|
try
|
||||||
base.Draw();
|
{
|
||||||
Profiler.EndEvent();
|
Console.Execute(Text, true);
|
||||||
|
inputHistoryIndex = -1;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
contentBox.ScrollOffset = 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key == KeyboardKeys.ArrowUp)
|
||||||
|
{
|
||||||
|
inputHistoryIndex++;
|
||||||
|
|
||||||
|
string line = Console.GetBufferHistory(inputHistoryIndex);
|
||||||
|
if (line == null)
|
||||||
|
{
|
||||||
|
inputHistoryIndex--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetText(line);
|
||||||
|
SetSelection(TextLength);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == KeyboardKeys.ArrowDown)
|
||||||
|
{
|
||||||
|
if (inputHistoryIndex > 0)
|
||||||
|
inputHistoryIndex--;
|
||||||
|
|
||||||
|
string line = Console.GetBufferHistory(inputHistoryIndex);
|
||||||
|
if (line == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
SetText(line);
|
||||||
|
SetSelection(TextLength);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == KeyboardKeys.PageUp || key == KeyboardKeys.PageDown)
|
||||||
|
return contentBox.OnKeyDown(key);
|
||||||
|
|
||||||
|
#if FLAX_EDITOR
|
||||||
|
if (key == KeyboardKeys.F5)
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return base.OnKeyDown(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnLostFocus()
|
||||||
|
{
|
||||||
|
// Prevent caret location getting reset back to beginning
|
||||||
|
bool oldEditing = _isEditing;
|
||||||
|
_isEditing = false;
|
||||||
|
base.OnLostFocus();
|
||||||
|
_isEditing = oldEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||||
|
{
|
||||||
|
base.OnMouseDown(location, button);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnMouseWheel(Float2 location, float delta)
|
||||||
|
{
|
||||||
|
return contentBox.OnMouseWheel(location, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
Profiler.BeginEvent("ConsoleInputTextBoxDraw");
|
||||||
|
base.Draw();
|
||||||
|
Profiler.EndEvent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,178 +6,177 @@ using System.Linq;
|
|||||||
using FlaxEditor;
|
using FlaxEditor;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public class ConsolePlugin : GamePlugin
|
||||||
{
|
{
|
||||||
public class ConsolePlugin : GamePlugin
|
public ConsolePlugin()
|
||||||
{
|
{
|
||||||
public ConsolePlugin()
|
|
||||||
{
|
|
||||||
#if FLAX_EDITOR
|
#if FLAX_EDITOR
|
||||||
_description = ConsoleEditorPlugin.DescriptionInternal;
|
_description = ConsoleEditorPlugin.DescriptionInternal;
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
private InputEvent onExit;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
//FlaxEngine.Debug.Log("ConsolePlugin initialized");
|
|
||||||
Console.Init();
|
|
||||||
|
|
||||||
onExit = new InputEvent("Exit");
|
|
||||||
onExit.Pressed += () =>
|
|
||||||
{
|
|
||||||
if (Console.IsSafeToQuit)
|
|
||||||
Engine.RequestExit();
|
|
||||||
};
|
|
||||||
|
|
||||||
//AssetManager.Init(); // TODO: move these elsewhere
|
|
||||||
#if !FLAX_EDITOR
|
|
||||||
Level.SceneLoading += OnSceneLoading;
|
|
||||||
Level.SceneLoaded += OnSceneLoaded;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Deinitialize()
|
|
||||||
{
|
|
||||||
onExit.Dispose();
|
|
||||||
#if !FLAX_EDITOR
|
|
||||||
Level.SceneLoading -= OnSceneLoading;
|
|
||||||
Level.SceneLoaded -= OnSceneLoaded;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSceneLoading(Scene scene, Guid guid)
|
|
||||||
{
|
|
||||||
Level.SceneLoading -= OnSceneLoading;
|
|
||||||
LoadConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSceneLoaded(Scene scene, Guid guid)
|
|
||||||
{
|
|
||||||
Level.SceneLoaded -= OnSceneLoaded;
|
|
||||||
#if !FLAX_EDITOR
|
|
||||||
|
|
||||||
//GameMode.Connect();
|
|
||||||
//GameMode.StartServer(true);
|
|
||||||
//NetworkManager.StartServer();
|
|
||||||
//GameModeManager.Init();
|
|
||||||
NetworkManager.ConnectServer();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadConfig()
|
|
||||||
{
|
|
||||||
Console.Print("Loading config file (GamePlugin)");
|
|
||||||
AssetManager.Globals.ResetValues();
|
|
||||||
|
|
||||||
foreach (var line in AssetManager.Config.GetLines())
|
|
||||||
Console.Execute(line, false, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FLAX_EDITOR
|
private InputEvent onExit;
|
||||||
public class ConsoleEditorPlugin : EditorPlugin
|
|
||||||
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
public static PluginDescription DescriptionInternal = new PluginDescription
|
//FlaxEngine.Debug.Log("ConsolePlugin initialized");
|
||||||
|
Console.Init();
|
||||||
|
|
||||||
|
onExit = new InputEvent("Exit");
|
||||||
|
onExit.Pressed += () =>
|
||||||
{
|
{
|
||||||
Author = "Ari Vuollet",
|
if (Console.IsSafeToQuit)
|
||||||
Name = "Console",
|
Engine.RequestExit();
|
||||||
Description = "Quake-like console",
|
|
||||||
Version = Version.Parse("0.1.0"),
|
|
||||||
IsAlpha = true,
|
|
||||||
Category = "Game"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public override Type GamePluginType => typeof(ConsolePlugin);
|
//AssetManager.Init(); // TODO: move these elsewhere
|
||||||
|
#if !FLAX_EDITOR
|
||||||
|
Level.SceneLoading += OnSceneLoading;
|
||||||
|
Level.SceneLoaded += OnSceneLoaded;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
public ConsoleEditorPlugin()
|
public override void Deinitialize()
|
||||||
|
{
|
||||||
|
onExit.Dispose();
|
||||||
|
#if !FLAX_EDITOR
|
||||||
|
Level.SceneLoading -= OnSceneLoading;
|
||||||
|
Level.SceneLoaded -= OnSceneLoaded;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSceneLoading(Scene scene, Guid guid)
|
||||||
|
{
|
||||||
|
Level.SceneLoading -= OnSceneLoading;
|
||||||
|
LoadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSceneLoaded(Scene scene, Guid guid)
|
||||||
|
{
|
||||||
|
Level.SceneLoaded -= OnSceneLoaded;
|
||||||
|
#if !FLAX_EDITOR
|
||||||
|
|
||||||
|
//GameMode.Connect();
|
||||||
|
//GameMode.StartServer(true);
|
||||||
|
//NetworkManager.StartServer();
|
||||||
|
//GameModeManager.Init();
|
||||||
|
NetworkManager.ConnectServer();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadConfig()
|
||||||
|
{
|
||||||
|
Console.Print("Loading config file (GamePlugin)");
|
||||||
|
AssetManager.Globals.ResetValues();
|
||||||
|
|
||||||
|
foreach (var line in AssetManager.Config.GetLines())
|
||||||
|
Console.Execute(line, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FLAX_EDITOR
|
||||||
|
public class ConsoleEditorPlugin : EditorPlugin
|
||||||
|
{
|
||||||
|
public static PluginDescription DescriptionInternal = new PluginDescription
|
||||||
|
{
|
||||||
|
Author = "Ari Vuollet",
|
||||||
|
Name = "Console",
|
||||||
|
Description = "Quake-like console",
|
||||||
|
Version = Version.Parse("0.1.0"),
|
||||||
|
IsAlpha = true,
|
||||||
|
Category = "Game"
|
||||||
|
};
|
||||||
|
|
||||||
|
public override Type GamePluginType => typeof(ConsolePlugin);
|
||||||
|
|
||||||
|
public ConsoleEditorPlugin()
|
||||||
|
{
|
||||||
|
_description = DescriptionInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
//private readonly InputEvent onExit = new InputEvent("Exit");
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
//FlaxEngine.Debug.Log("ConsolePlugin initialized");
|
||||||
|
Console.Init();
|
||||||
|
|
||||||
|
/*onExit.Triggered += () =>
|
||||||
{
|
{
|
||||||
_description = DescriptionInternal;
|
if (Console.IsSafeToQuit)
|
||||||
}
|
Engine.RequestExit();
|
||||||
|
};*/
|
||||||
|
|
||||||
//private readonly InputEvent onExit = new InputEvent("Exit");
|
//AssetManager.Init();
|
||||||
|
|
||||||
public override void Initialize()
|
Level.SceneLoading += OnSceneLoading;
|
||||||
{
|
|
||||||
//FlaxEngine.Debug.Log("ConsolePlugin initialized");
|
|
||||||
Console.Init();
|
|
||||||
|
|
||||||
/*onExit.Triggered += () =>
|
FlaxEditor.Editor.Instance.PlayModeBegin += OnPlayModeBegin;
|
||||||
{
|
FlaxEditor.Editor.Instance.PlayModeEnd += OnPlayModeEnd;
|
||||||
if (Console.IsSafeToQuit)
|
//Scripting.Exit += OnScriptingExit;
|
||||||
Engine.RequestExit();
|
|
||||||
};*/
|
|
||||||
|
|
||||||
//AssetManager.Init();
|
/*AssetManager.Init(); // TODO: move these elsewhere
|
||||||
|
AssetManager.Globals.ResetValues();
|
||||||
|
|
||||||
Level.SceneLoading += OnSceneLoading;
|
foreach (var line in AssetManager.Config.GetLines())
|
||||||
|
Console.Execute(line, false, true);*/
|
||||||
|
}
|
||||||
|
|
||||||
FlaxEditor.Editor.Instance.PlayModeBegin += OnPlayModeBegin;
|
/*private void OnScriptingExit()
|
||||||
FlaxEditor.Editor.Instance.PlayModeEnd += OnPlayModeEnd;
|
{
|
||||||
//Scripting.Exit += OnScriptingExit;
|
FlaxEditor.Editor.Instance.PlayModeBegin -= OnPlayModeBegin;
|
||||||
|
FlaxEditor.Editor.Instance.PlayModeEnd -= OnPlayModeEnd;
|
||||||
|
}*/
|
||||||
|
|
||||||
/*AssetManager.Init(); // TODO: move these elsewhere
|
|
||||||
AssetManager.Globals.ResetValues();
|
|
||||||
|
|
||||||
foreach (var line in AssetManager.Config.GetLines())
|
private void OnPlayModeBegin()
|
||||||
Console.Execute(line, false, true);*/
|
{
|
||||||
}
|
//FlaxEditor.Editor.Instance.PlayModeBegin -= Instance_PlayModeBegin;
|
||||||
|
LoadConfig();
|
||||||
|
//GameMode.Connect();
|
||||||
|
//WorldStateManager.Init();
|
||||||
|
NetworkManager.StartServer();
|
||||||
|
|
||||||
/*private void OnScriptingExit()
|
//GameMode.StartServer(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayModeEnd()
|
||||||
|
{
|
||||||
|
//onExit.Dispose();
|
||||||
|
//GameMode.StopServer();
|
||||||
|
NetworkManager.Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Deinitialize()
|
||||||
|
{
|
||||||
|
//onExit.Dispose();
|
||||||
|
//Level.SceneLoaded -= OnSceneLoaded;
|
||||||
|
Level.SceneLoading -= OnSceneLoading;
|
||||||
|
if (FlaxEditor.Editor.Instance != null)
|
||||||
{
|
{
|
||||||
FlaxEditor.Editor.Instance.PlayModeBegin -= OnPlayModeBegin;
|
FlaxEditor.Editor.Instance.PlayModeBegin -= OnPlayModeBegin;
|
||||||
FlaxEditor.Editor.Instance.PlayModeEnd -= OnPlayModeEnd;
|
FlaxEditor.Editor.Instance.PlayModeEnd -= OnPlayModeEnd;
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void OnPlayModeBegin()
|
|
||||||
{
|
|
||||||
//FlaxEditor.Editor.Instance.PlayModeBegin -= Instance_PlayModeBegin;
|
|
||||||
LoadConfig();
|
|
||||||
//GameMode.Connect();
|
|
||||||
//WorldStateManager.Init();
|
|
||||||
NetworkManager.StartServer();
|
|
||||||
|
|
||||||
//GameMode.StartServer(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlayModeEnd()
|
|
||||||
{
|
|
||||||
//onExit.Dispose();
|
|
||||||
//GameMode.StopServer();
|
|
||||||
NetworkManager.Cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Deinitialize()
|
|
||||||
{
|
|
||||||
//onExit.Dispose();
|
|
||||||
//Level.SceneLoaded -= OnSceneLoaded;
|
|
||||||
Level.SceneLoading -= OnSceneLoading;
|
|
||||||
if (FlaxEditor.Editor.Instance != null)
|
|
||||||
{
|
|
||||||
FlaxEditor.Editor.Instance.PlayModeBegin -= OnPlayModeBegin;
|
|
||||||
FlaxEditor.Editor.Instance.PlayModeEnd -= OnPlayModeEnd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSceneLoading(Scene scene, Guid guid)
|
|
||||||
{
|
|
||||||
//Level.SceneLoaded -= OnSceneLoaded;
|
|
||||||
Level.SceneLoading -= OnSceneLoading;
|
|
||||||
LoadConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadConfig()
|
|
||||||
{
|
|
||||||
Console.Print("Loading config file (EditorPlugin)");
|
|
||||||
|
|
||||||
AssetManager.Globals.ResetValues();
|
|
||||||
|
|
||||||
foreach (var line in AssetManager.Config.GetLines())
|
|
||||||
Console.Execute(line, false, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnSceneLoading(Scene scene, Guid guid)
|
||||||
|
{
|
||||||
|
//Level.SceneLoaded -= OnSceneLoaded;
|
||||||
|
Level.SceneLoading -= OnSceneLoading;
|
||||||
|
LoadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadConfig()
|
||||||
|
{
|
||||||
|
Console.Print("Loading config file (EditorPlugin)");
|
||||||
|
|
||||||
|
AssetManager.Globals.ResetValues();
|
||||||
|
|
||||||
|
foreach (var line in AssetManager.Config.GetLines())
|
||||||
|
Console.Execute(line, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
@@ -6,393 +6,392 @@ using FlaxEngine.Assertions;
|
|||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
using Object = FlaxEngine.Object;
|
using Object = FlaxEngine.Object;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public class ConsoleScript : Script
|
||||||
{
|
{
|
||||||
public class ConsoleScript : Script
|
public Color BackgroundColor;
|
||||||
|
|
||||||
|
public Texture BackgroundTexture;
|
||||||
|
private ConsoleContentTextBox consoleBox;
|
||||||
|
|
||||||
|
public FontAsset ConsoleFont;
|
||||||
|
[Limit(5, 720)] public int ConsoleFontSize = 16;
|
||||||
|
|
||||||
|
[Limit(0.05f, 1.0f)] public float ConsoleHeight = 0.65f;
|
||||||
|
private ConsoleInputTextBox consoleInputBox;
|
||||||
|
|
||||||
|
internal InputEvent consoleInputEvent;
|
||||||
|
private ConsoleContentTextBox consoleNotifyBox;
|
||||||
|
|
||||||
|
[Limit(0)] public int ConsoleNotifyLines = 15;
|
||||||
|
|
||||||
|
[Limit(0f)] public float ConsoleSpeed = 3500f;
|
||||||
|
|
||||||
|
private UIControl rootControl;
|
||||||
|
|
||||||
|
private int fontHeight;
|
||||||
|
|
||||||
|
public override void OnStart()
|
||||||
{
|
{
|
||||||
public Color BackgroundColor;
|
consoleInputEvent = new InputEvent("Console");
|
||||||
|
consoleInputEvent.Pressed += OnConsoleInputEvent;
|
||||||
|
|
||||||
public Texture BackgroundTexture;
|
FontReference fontReference = new FontReference(ConsoleFont, ConsoleFontSize);
|
||||||
private ConsoleContentTextBox consoleBox;
|
Font fontRaw = fontReference.GetFont();
|
||||||
|
fontHeight = (int)(fontRaw.Height / Platform.DpiScale);
|
||||||
|
// root actor which holds all the elements
|
||||||
|
//var rootContainerControl = new ContainerControl(new Rectangle(0, 0, screenSize.X, screenSize.Y));
|
||||||
|
ContainerControl rootContainerControl = new ContainerControl(new Rectangle());
|
||||||
|
rootContainerControl.SetAnchorPreset(AnchorPresets.StretchAll, false);
|
||||||
|
|
||||||
public FontAsset ConsoleFont;
|
rootControl = Actor.AddChild<UIControl>();
|
||||||
[Limit(5, 720)] public int ConsoleFontSize = 16;
|
rootControl.Name = "ConsoleRoot";
|
||||||
|
rootControl.Control = rootContainerControl;
|
||||||
|
|
||||||
[Limit(0.05f, 1.0f)] public float ConsoleHeight = 0.65f;
|
VerticalPanel contentContainer = new VerticalPanel
|
||||||
private ConsoleInputTextBox consoleInputBox;
|
|
||||||
|
|
||||||
internal InputEvent consoleInputEvent;
|
|
||||||
private ConsoleContentTextBox consoleNotifyBox;
|
|
||||||
|
|
||||||
[Limit(0)] public int ConsoleNotifyLines = 15;
|
|
||||||
|
|
||||||
[Limit(0f)] public float ConsoleSpeed = 3500f;
|
|
||||||
|
|
||||||
private UIControl rootControl;
|
|
||||||
|
|
||||||
private int fontHeight;
|
|
||||||
|
|
||||||
public override void OnStart()
|
|
||||||
{
|
{
|
||||||
consoleInputEvent = new InputEvent("Console");
|
AutoSize = true,
|
||||||
consoleInputEvent.Pressed += OnConsoleInputEvent;
|
Margin = Margin.Zero,
|
||||||
|
Spacing = 0,
|
||||||
|
Bounds = new Rectangle(),
|
||||||
|
BackgroundColor = BackgroundColor
|
||||||
|
};
|
||||||
|
contentContainer.SetAnchorPreset(AnchorPresets.StretchAll, true);
|
||||||
|
|
||||||
FontReference fontReference = new FontReference(ConsoleFont, ConsoleFontSize);
|
UIControl contentContainerControl = rootControl.AddChild<UIControl>();
|
||||||
Font fontRaw = fontReference.GetFont();
|
contentContainerControl.Name = "ContentContainer";
|
||||||
fontHeight = (int)(fontRaw.Height / Platform.DpiScale);
|
contentContainerControl.Control = contentContainer;
|
||||||
// root actor which holds all the elements
|
|
||||||
//var rootContainerControl = new ContainerControl(new Rectangle(0, 0, screenSize.X, screenSize.Y));
|
|
||||||
ContainerControl rootContainerControl = new ContainerControl(new Rectangle());
|
|
||||||
rootContainerControl.SetAnchorPreset(AnchorPresets.StretchAll, false);
|
|
||||||
|
|
||||||
rootControl = Actor.AddChild<UIControl>();
|
{
|
||||||
rootControl.Name = "ConsoleRoot";
|
if (consoleBox == null)
|
||||||
rootControl.Control = rootContainerControl;
|
|
||||||
|
|
||||||
VerticalPanel contentContainer = new VerticalPanel
|
|
||||||
{
|
{
|
||||||
AutoSize = true,
|
//consoleBox = new ConsoleContentTextBox(null, 0, 0, consoleSize.X, consoleSize.Y - fontHeight);
|
||||||
Margin = Margin.Zero,
|
consoleBox = new ConsoleContentTextBox(fontReference, null, 0, 0, 0, 0);
|
||||||
Spacing = 0,
|
|
||||||
Bounds = new Rectangle(),
|
|
||||||
BackgroundColor = BackgroundColor
|
|
||||||
};
|
|
||||||
contentContainer.SetAnchorPreset(AnchorPresets.StretchAll, true);
|
|
||||||
|
|
||||||
UIControl contentContainerControl = rootControl.AddChild<UIControl>();
|
|
||||||
contentContainerControl.Name = "ContentContainer";
|
|
||||||
contentContainerControl.Control = contentContainer;
|
|
||||||
|
|
||||||
{
|
|
||||||
if (consoleBox == null)
|
|
||||||
{
|
|
||||||
//consoleBox = new ConsoleContentTextBox(null, 0, 0, consoleSize.X, consoleSize.Y - fontHeight);
|
|
||||||
consoleBox = new ConsoleContentTextBox(fontReference, null, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
|
|
||||||
consoleBox.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, true);
|
consoleBox.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, true);
|
||||||
//consoleBox.AnchorMax = new Float2(1.0f, ConsoleHeight);
|
//consoleBox.AnchorMax = new Float2(1.0f, ConsoleHeight);
|
||||||
//consoleBox.Height = consoleSize.Y - fontHeight;
|
//consoleBox.Height = consoleSize.Y - fontHeight;
|
||||||
|
|
||||||
//consoleBox.HorizontalAlignment = TextAlignment.Near;
|
//consoleBox.HorizontalAlignment = TextAlignment.Near;
|
||||||
//consoleBox.VerticalAlignment = TextAlignment.Near;
|
//consoleBox.VerticalAlignment = TextAlignment.Near;
|
||||||
consoleBox.HeightMultiplier = ConsoleHeight;
|
consoleBox.HeightMultiplier = ConsoleHeight;
|
||||||
consoleBox.Wrapping = TextWrapping.WrapWords;
|
consoleBox.Wrapping = TextWrapping.WrapWords;
|
||||||
consoleBox.BackgroundColor = Color.Transparent;
|
consoleBox.BackgroundColor = Color.Transparent;
|
||||||
consoleBox.BackgroundSelectedColor = Color.Transparent;
|
consoleBox.BackgroundSelectedColor = Color.Transparent;
|
||||||
consoleBox.BackgroundSelectedFlashSpeed = 0;
|
consoleBox.BackgroundSelectedFlashSpeed = 0;
|
||||||
consoleBox.BorderSelectedColor = Color.Transparent;
|
consoleBox.BorderSelectedColor = Color.Transparent;
|
||||||
consoleBox.CaretFlashSpeed = 0;
|
consoleBox.CaretFlashSpeed = 0;
|
||||||
}
|
|
||||||
|
|
||||||
Float2 locationFix = consoleBox.Location;
|
|
||||||
UIControl parentControl = contentContainerControl.AddChild<UIControl>();
|
|
||||||
parentControl.Name = "ConsoleContent";
|
|
||||||
parentControl.Control = consoleBox;
|
|
||||||
consoleBox.Location = locationFix; // workaround to UIControl.Control overriding the old position
|
|
||||||
|
|
||||||
if (consoleNotifyBox == null)
|
|
||||||
{
|
|
||||||
//consoleBox = new ConsoleContentTextBox(null, 0, 0, consoleSize.X, consoleSize.Y - fontHeight);
|
|
||||||
consoleNotifyBox = new ConsoleContentTextBox(fontReference, null, 0, 0, 0, 0);
|
|
||||||
consoleNotifyBox.HeightMultiplier = 0;
|
|
||||||
consoleNotifyBox.Height = ConsoleNotifyLines * fontHeight;
|
|
||||||
consoleNotifyBox.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, true);
|
|
||||||
//consoleBox.AnchorMax = new Float2(1.0f, ConsoleHeight);
|
|
||||||
|
|
||||||
//consoleBox.HorizontalAlignment = TextAlignment.Near;
|
|
||||||
//consoleBox.VerticalAlignment = TextAlignment.Near;
|
|
||||||
//consoleNotifyBox.HeightMultiplier = ConsoleHeight;
|
|
||||||
consoleNotifyBox.Wrapping = TextWrapping.WrapWords;
|
|
||||||
consoleNotifyBox.BackgroundColor = Color.Transparent;
|
|
||||||
consoleNotifyBox.BackgroundSelectedColor = Color.Transparent;
|
|
||||||
consoleNotifyBox.BackgroundSelectedFlashSpeed = 0;
|
|
||||||
consoleNotifyBox.BorderSelectedColor = Color.Transparent;
|
|
||||||
consoleNotifyBox.CaretFlashSpeed = 0;
|
|
||||||
consoleNotifyBox.SelectionAllowed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Float2 locationFix2 = consoleNotifyBox.Location;
|
|
||||||
UIControl parentControl2 = Actor.AddChild<UIControl>();
|
|
||||||
parentControl2.Name = "ConsoleNotifyContent";
|
|
||||||
parentControl2.Control = consoleNotifyBox;
|
|
||||||
consoleNotifyBox.Location = locationFix2; // workaround to UIControl.Control overriding the old position
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Float2 locationFix = consoleBox.Location;
|
||||||
|
UIControl parentControl = contentContainerControl.AddChild<UIControl>();
|
||||||
|
parentControl.Name = "ConsoleContent";
|
||||||
|
parentControl.Control = consoleBox;
|
||||||
|
consoleBox.Location = locationFix; // workaround to UIControl.Control overriding the old position
|
||||||
|
|
||||||
|
if (consoleNotifyBox == null)
|
||||||
{
|
{
|
||||||
if (consoleInputBox == null)
|
//consoleBox = new ConsoleContentTextBox(null, 0, 0, consoleSize.X, consoleSize.Y - fontHeight);
|
||||||
{
|
consoleNotifyBox = new ConsoleContentTextBox(fontReference, null, 0, 0, 0, 0);
|
||||||
//consoleInputBox = new ConsoleInputTextBox(consoleBox, 0, consoleSize.Y - fontHeight, consoleSize.X, fontHeight);
|
consoleNotifyBox.HeightMultiplier = 0;
|
||||||
consoleInputBox = new ConsoleInputTextBox(consoleBox, 0, 0, 0, 0);
|
consoleNotifyBox.Height = ConsoleNotifyLines * fontHeight;
|
||||||
consoleInputBox.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, false);
|
consoleNotifyBox.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, true);
|
||||||
//consoleInputBox.Location = new Float2(0, consoleSize.Y - fontHeight);
|
//consoleBox.AnchorMax = new Float2(1.0f, ConsoleHeight);
|
||||||
consoleInputBox.Height = fontHeight;
|
|
||||||
|
|
||||||
consoleInputBox.Font = fontReference;
|
//consoleBox.HorizontalAlignment = TextAlignment.Near;
|
||||||
consoleBox.inputBox = consoleInputBox;
|
//consoleBox.VerticalAlignment = TextAlignment.Near;
|
||||||
|
//consoleNotifyBox.HeightMultiplier = ConsoleHeight;
|
||||||
consoleInputBox.Wrapping = TextWrapping.WrapWords;
|
consoleNotifyBox.Wrapping = TextWrapping.WrapWords;
|
||||||
consoleInputBox.BackgroundColor = Color.Transparent;
|
consoleNotifyBox.BackgroundColor = Color.Transparent;
|
||||||
consoleInputBox.BackgroundSelectedColor = Color.Transparent;
|
consoleNotifyBox.BackgroundSelectedColor = Color.Transparent;
|
||||||
consoleInputBox.BackgroundSelectedFlashSpeed = 0;
|
consoleNotifyBox.BackgroundSelectedFlashSpeed = 0;
|
||||||
consoleInputBox.BorderSelectedColor = Color.Transparent;
|
consoleNotifyBox.BorderSelectedColor = Color.Transparent;
|
||||||
consoleInputBox.CaretFlashSpeed = 0;
|
consoleNotifyBox.CaretFlashSpeed = 0;
|
||||||
}
|
consoleNotifyBox.SelectionAllowed = false;
|
||||||
|
|
||||||
|
|
||||||
Float2 locationFix = consoleInputBox.Location;
|
|
||||||
UIControl parentControl = contentContainerControl.AddChild<UIControl>();
|
|
||||||
parentControl.Name = "ConsoleInput";
|
|
||||||
parentControl.Control = consoleInputBox;
|
|
||||||
|
|
||||||
consoleInputBox.Location = locationFix; // workaround to UIControl.Control overriding the old position
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Float2 locationFix2 = consoleNotifyBox.Location;
|
||||||
|
UIControl parentControl2 = Actor.AddChild<UIControl>();
|
||||||
|
parentControl2.Name = "ConsoleNotifyContent";
|
||||||
|
parentControl2.Control = consoleNotifyBox;
|
||||||
|
consoleNotifyBox.Location = locationFix2; // workaround to UIControl.Control overriding the old position
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (consoleInputBox == null)
|
||||||
|
{
|
||||||
|
//consoleInputBox = new ConsoleInputTextBox(consoleBox, 0, consoleSize.Y - fontHeight, consoleSize.X, fontHeight);
|
||||||
|
consoleInputBox = new ConsoleInputTextBox(consoleBox, 0, 0, 0, 0);
|
||||||
|
consoleInputBox.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, false);
|
||||||
|
//consoleInputBox.Location = new Float2(0, consoleSize.Y - fontHeight);
|
||||||
|
consoleInputBox.Height = fontHeight;
|
||||||
|
|
||||||
|
consoleInputBox.Font = fontReference;
|
||||||
|
consoleBox.inputBox = consoleInputBox;
|
||||||
|
|
||||||
|
consoleInputBox.Wrapping = TextWrapping.WrapWords;
|
||||||
|
consoleInputBox.BackgroundColor = Color.Transparent;
|
||||||
|
consoleInputBox.BackgroundSelectedColor = Color.Transparent;
|
||||||
|
consoleInputBox.BackgroundSelectedFlashSpeed = 0;
|
||||||
|
consoleInputBox.BorderSelectedColor = Color.Transparent;
|
||||||
|
consoleInputBox.CaretFlashSpeed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Float2 locationFix = consoleInputBox.Location;
|
||||||
|
UIControl parentControl = contentContainerControl.AddChild<UIControl>();
|
||||||
|
parentControl.Name = "ConsoleInput";
|
||||||
|
parentControl.Control = consoleInputBox;
|
||||||
|
|
||||||
|
consoleInputBox.Location = locationFix; // workaround to UIControl.Control overriding the old position
|
||||||
|
}
|
||||||
|
|
||||||
#if false
|
#if false
|
||||||
//for (int i = 0; i < 10; i++)
|
//for (int i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
string[] teststr = {
|
string[] teststr = {
|
||||||
/*
|
/*
|
||||||
"...loading 'scripts/devilpunch.shader'",
|
"...loading 'scripts/devilpunch.shader'",
|
||||||
"...loading 'scripts/mkoxide.shader'",
|
"...loading 'scripts/mkoxide.shader'",
|
||||||
"...loading 'scripts/cpm22.shader'",
|
"...loading 'scripts/cpm22.shader'",
|
||||||
"...loading 'scripts/cpm27.shader'",
|
"...loading 'scripts/cpm27.shader'",
|
||||||
"...loading 'scripts/island.shader'",
|
"...loading 'scripts/island.shader'",
|
||||||
"...loading 'scripts/noodtex3.shader'",
|
"...loading 'scripts/noodtex3.shader'",
|
||||||
"...loading 'scripts/nood_cosdglass.shader'",
|
"...loading 'scripts/nood_cosdglass.shader'",
|
||||||
"...loading 'scripts/nood_fog_1000.shader'",
|
"...loading 'scripts/nood_fog_1000.shader'",
|
||||||
"...loading 'scripts/nood_lightbeams.shader'",
|
"...loading 'scripts/nood_lightbeams.shader'",
|
||||||
"...loading 'scripts/nood_nightsky_nolight.shader'",
|
"...loading 'scripts/nood_nightsky_nolight.shader'",
|
||||||
"Rescanning shaders",
|
"Rescanning shaders",
|
||||||
@"Raw input type 0: [0] \\?\HID#VID_046D&PID_C231#2&229a2ea&0&0000#",
|
@"Raw input type 0: [0] \\?\HID#VID_046D&PID_C231#2&229a2ea&0&0000#",
|
||||||
@"Raw input type 0: [18] \\?\HID#VID_046D&PID_C539&MI_01&Col01#8&24523410&0&0000#",
|
@"Raw input type 0: [18] \\?\HID#VID_046D&PID_C539&MI_01&Col01#8&24523410&0&0000#",
|
||||||
@"Raw input type 0: [19] \\?\HID#VID_28DE&PID_1102&MI_01#8&2fb9bb60&0&0000#",
|
@"Raw input type 0: [19] \\?\HID#VID_28DE&PID_1102&MI_01#8&2fb9bb60&0&0000#",
|
||||||
@"Raw input type 0: [20] \\?\HID#VID_04D9&PID_A131&MI_02&Col01#8&197f95af&0&0000#",
|
@"Raw input type 0: [20] \\?\HID#VID_04D9&PID_A131&MI_02&Col01#8&197f95af&0&0000#",
|
||||||
"Raw input: initialized with 5 mice and 0 keyboards",
|
"Raw input: initialized with 5 mice and 0 keyboards",
|
||||||
"WASAPI: overriding channels",
|
"WASAPI: overriding channels",
|
||||||
"WASAPI: 2 channel 32bit 48000khz non-exclusive",
|
"WASAPI: 2 channel 32bit 48000khz non-exclusive",
|
||||||
"WASAPI: requested periodicity: 128, default: 480, min: 128, max: 480, step: 16",
|
"WASAPI: requested periodicity: 128, default: 480, min: 128, max: 480, step: 16",
|
||||||
"WASAPI: Low latency mode enabled",
|
"WASAPI: Low latency mode enabled",
|
||||||
"WASAPI: buffer size: 280",
|
"WASAPI: buffer size: 280",
|
||||||
"OpenGL renderer initialized",
|
"OpenGL renderer initialized",
|
||||||
"video restart took 0.291480 seconds",
|
"video restart took 0.291480 seconds",
|
||||||
"main thread video restart took 0.291798 secs",
|
"main thread video restart took 0.291798 secs",
|
||||||
"[------ Goake Initialized ------]",
|
"[------ Goake Initialized ------]",
|
||||||
"Initializing menu.dat",
|
"Initializing menu.dat",
|
||||||
"Couldn't load sound/ambience/water1.wav",
|
"Couldn't load sound/ambience/water1.wav",
|
||||||
"Couldn't load sound/ambience/wind2.wav",
|
"Couldn't load sound/ambience/wind2.wav",
|
||||||
"Couldn't load sound/misc/menu2.wav",
|
"Couldn't load sound/misc/menu2.wav",
|
||||||
"Couldn't load sound/misc/menu3.wav",
|
"Couldn't load sound/misc/menu3.wav",
|
||||||
"]cl_maxfps 120",
|
"]cl_maxfps 120",
|
||||||
"a very very very long long long line in repeat a very very very long long long line in repeat a very very very long long long line in repeat a very very very long long long line in repeat a very very very long long long line in repeat a very very very long long long line in repeat"
|
"a very very very long long long line in repeat a very very very long long long line in repeat a very very very long long long line in repeat a very very very long long long line in repeat a very very very long long long line in repeat a very very very long long long line in repeat"
|
||||||
*/
|
*/
|
||||||
"Warning: Unsupported entity field 'light'",
|
"Warning: Unsupported entity field 'light'",
|
||||||
"Warning: Unsupported entity field '_keeplights'",
|
"Warning: Unsupported entity field '_keeplights'",
|
||||||
"Warning: Unsupported entity field 'light'",
|
"Warning: Unsupported entity field 'light'",
|
||||||
"Warning: Unsupported entity field '_keeplights'",
|
"Warning: Unsupported entity field '_keeplights'",
|
||||||
"Warning: Unsupported entity field 'light'",
|
"Warning: Unsupported entity field 'light'",
|
||||||
"Warning: Unsupported entity field '_keeplights'",
|
"Warning: Unsupported entity field '_keeplights'",
|
||||||
"Warning: Unsupported entity field 'light'",
|
"Warning: Unsupported entity field 'light'",
|
||||||
"Warning: Unsupported entity field '_keeplights'",
|
"Warning: Unsupported entity field '_keeplights'",
|
||||||
"maps/aerowalk.bsp: Using lightmap format E5BGR9_UF",
|
"maps/aerowalk.bsp: Using lightmap format E5BGR9_UF",
|
||||||
"maps/devbox.bsp: Using lightmap format E5BGR9_UF",
|
"maps/devbox.bsp: Using lightmap format E5BGR9_UF",
|
||||||
"what",
|
"what",
|
||||||
"Game mode changed to: Free For All",
|
"Game mode changed to: Free For All",
|
||||||
"what",
|
"what",
|
||||||
"641 additional FS searches",
|
"641 additional FS searches",
|
||||||
"fixangle frame: 1427",
|
"fixangle frame: 1427",
|
||||||
"Couldn't load sound/ambience/wind2.wav",
|
"Couldn't load sound/ambience/wind2.wav",
|
||||||
"Couldn't load sound/ambience/water1.wav"
|
"Couldn't load sound/ambience/water1.wav"
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var l in teststr)
|
foreach (var l in teststr)
|
||||||
Console.Print(l);
|
Console.Print(l);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
/*FlaxEditor.Editor.Options.OptionsChanged += (FlaxEditor.Options.EditorOptions options) =>
|
/*FlaxEditor.Editor.Options.OptionsChanged += (FlaxEditor.Options.EditorOptions options) =>
|
||||||
{
|
|
||||||
|
|
||||||
};*/
|
|
||||||
|
|
||||||
/*Console.Print("normal line");
|
|
||||||
Console.Print(
|
|
||||||
"a very very very long long long line in repeat a very very very long long long line in repeat 1 a very very ver"
|
|
||||||
+ "y long long long line in repeat a very very very 2 long long long line in repeat a very very very 3 long long"
|
|
||||||
+ " long line in repeat a very very very long long long 4 line in repeat");
|
|
||||||
Console.Print("another normal line");*/
|
|
||||||
|
|
||||||
Debug.Logger.LogHandler.SendLog += OnSendLog;
|
|
||||||
Debug.Logger.LogHandler.SendExceptionLog += OnSendExceptionLog;
|
|
||||||
|
|
||||||
|
|
||||||
Console.OnOpen += OnConsoleOpen;
|
|
||||||
Console.OnClose += OnConsoleClose;
|
|
||||||
Console.OnPrint += OnPrint;
|
|
||||||
|
|
||||||
// hide console by default, and close it instantly
|
|
||||||
Console.Close();
|
|
||||||
OnConsoleClose();
|
|
||||||
Float2 rootlocation = rootControl.Control.Location;
|
|
||||||
rootlocation.Y = -rootControl.Control.Height;
|
|
||||||
rootControl.Control.Location = rootlocation;
|
|
||||||
|
|
||||||
Console.Print("Renderer: " + GPUDevice.Instance.RendererType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSendLog(LogType level, string msg, Object obj, string stackTrace)
|
|
||||||
{
|
{
|
||||||
Console.Print("[DEBUG] " + msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSendExceptionLog(Exception exception, Object obj)
|
};*/
|
||||||
|
|
||||||
|
/*Console.Print("normal line");
|
||||||
|
Console.Print(
|
||||||
|
"a very very very long long long line in repeat a very very very long long long line in repeat 1 a very very ver"
|
||||||
|
+ "y long long long line in repeat a very very very 2 long long long line in repeat a very very very 3 long long"
|
||||||
|
+ " long line in repeat a very very very long long long 4 line in repeat");
|
||||||
|
Console.Print("another normal line");*/
|
||||||
|
|
||||||
|
Debug.Logger.LogHandler.SendLog += OnSendLog;
|
||||||
|
Debug.Logger.LogHandler.SendExceptionLog += OnSendExceptionLog;
|
||||||
|
|
||||||
|
|
||||||
|
Console.OnOpen += OnConsoleOpen;
|
||||||
|
Console.OnClose += OnConsoleClose;
|
||||||
|
Console.OnPrint += OnPrint;
|
||||||
|
|
||||||
|
// hide console by default, and close it instantly
|
||||||
|
Console.Close();
|
||||||
|
OnConsoleClose();
|
||||||
|
Float2 rootlocation = rootControl.Control.Location;
|
||||||
|
rootlocation.Y = -rootControl.Control.Height;
|
||||||
|
rootControl.Control.Location = rootlocation;
|
||||||
|
|
||||||
|
Console.Print("Renderer: " + GPUDevice.Instance.RendererType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSendLog(LogType level, string msg, Object obj, string stackTrace)
|
||||||
|
{
|
||||||
|
Console.Print("[DEBUG] " + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSendExceptionLog(Exception exception, Object obj)
|
||||||
|
{
|
||||||
|
AssertionException assert = exception as AssertionException;
|
||||||
|
if (assert != null)
|
||||||
{
|
{
|
||||||
AssertionException assert = exception as AssertionException;
|
string[] assertLines = assert.Message.Split('\n');
|
||||||
if (assert != null)
|
if (assertLines.Length > 2)
|
||||||
{
|
Console.Print("Assert Failure: " + assertLines[2]);
|
||||||
string[] assertLines = assert.Message.Split('\n');
|
|
||||||
if (assertLines.Length > 2)
|
|
||||||
Console.Print("Assert Failure: " + assertLines[2]);
|
|
||||||
else
|
|
||||||
Console.Print("Assert Failure: " + assert.Message);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
Console.Print("Assert Failure: " + assert.Message);
|
||||||
Console.Print("[EXCEP] " + exception.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
public override void OnDestroy()
|
|
||||||
{
|
{
|
||||||
base.OnDestroy();
|
Console.Print("[EXCEP] " + exception.ToString());
|
||||||
|
|
||||||
//consoleInputEvent.Triggered -= OnConsoleInputEvent;
|
|
||||||
consoleInputEvent?.Dispose();
|
|
||||||
consoleBox?.Dispose();
|
|
||||||
consoleNotifyBox?.Dispose();
|
|
||||||
|
|
||||||
Console.OnOpen -= OnConsoleOpen;
|
|
||||||
Console.OnClose -= OnConsoleClose;
|
|
||||||
Console.OnPrint -= OnPrint;
|
|
||||||
|
|
||||||
Debug.Logger.LogHandler.SendLog -= OnSendLog;
|
|
||||||
Debug.Logger.LogHandler.SendExceptionLog -= OnSendExceptionLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnConsoleInputEvent()
|
|
||||||
{
|
|
||||||
string currentInput = Input.InputText;
|
|
||||||
|
|
||||||
if (Input.InputText.Length > 0)
|
|
||||||
{
|
|
||||||
// Really need rawinput support with separate ActionConfig.RawKey values, bound to physical keys/scancode instead of virtual ones
|
|
||||||
var consoleKeys = Input.ActionMappings.Where(x => x.Name == "Console" && x.Key != KeyboardKeys.None);
|
|
||||||
bool backslash = consoleKeys.Any(x => x.Key == KeyboardKeys.Backslash);
|
|
||||||
bool backquote = consoleKeys.Any(x => x.Key == KeyboardKeys.BackQuote);
|
|
||||||
|
|
||||||
// Workaround to only trigger Console key from key bound to left side of 1 (tilde/backquote/backslash key)
|
|
||||||
if ((backslash || backquote) &&
|
|
||||||
(Input.InputText.ToLowerInvariant().Contains('ö') ||
|
|
||||||
Input.InputText.ToLowerInvariant().Contains('æ') ||
|
|
||||||
Input.InputText.ToLowerInvariant().Contains('ø'))) // Scandinavian keyboard layouts
|
|
||||||
return;
|
|
||||||
if (backquote && Input.InputText.ToLowerInvariant().Contains('\'')) // UK keyboard layouts
|
|
||||||
return;
|
|
||||||
if (backslash && (Input.InputText.ToLowerInvariant().Contains('\\') ||
|
|
||||||
Input.InputText.ToLowerInvariant()
|
|
||||||
.Contains('|'))) // US/International keyboard layouts
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!consoleInputBox.IsFocused)
|
|
||||||
Console.Open();
|
|
||||||
else
|
|
||||||
Console.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnConsoleOpen()
|
|
||||||
{
|
|
||||||
Screen.CursorVisible = true;
|
|
||||||
Screen.CursorLock = CursorLockMode.None;
|
|
||||||
|
|
||||||
consoleInputBox.Focus();
|
|
||||||
Parent.As<UICanvas>().ReceivesEvents = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnConsoleClose()
|
|
||||||
{
|
|
||||||
Screen.CursorVisible = false;
|
|
||||||
Screen.CursorLock = CursorLockMode.Locked;
|
|
||||||
|
|
||||||
consoleInputBox.Defocus();
|
|
||||||
#if FLAX_EDITOR
|
|
||||||
Editor.Instance.Windows.GameWin.Focus();
|
|
||||||
#endif
|
|
||||||
Parent.As<UICanvas>().ReceivesEvents = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnUpdate()
|
|
||||||
{
|
|
||||||
base.OnUpdate();
|
|
||||||
|
|
||||||
if (!Console.IsOpen && Input.GetAction("ClearConsole"))
|
|
||||||
Console.Clear();
|
|
||||||
|
|
||||||
float targetY;
|
|
||||||
float conHeight = rootControl.Control.Height /*/ Platform.DpiScale*/;
|
|
||||||
if (!Console.IsOpen)
|
|
||||||
targetY = -conHeight;
|
|
||||||
else
|
|
||||||
targetY = 0.0f;
|
|
||||||
|
|
||||||
Float2 location = rootControl.Control.Location;
|
|
||||||
if (location.Y != targetY)
|
|
||||||
{
|
|
||||||
if (location.Y > targetY)
|
|
||||||
{
|
|
||||||
// closing
|
|
||||||
location.Y -= Time.UnscaledDeltaTime * ConsoleSpeed;
|
|
||||||
if (location.Y < targetY)
|
|
||||||
location.Y = targetY;
|
|
||||||
|
|
||||||
if (location.Y < targetY * ConsoleHeight)
|
|
||||||
location.Y = targetY;
|
|
||||||
}
|
|
||||||
else if (location.Y < targetY)
|
|
||||||
{
|
|
||||||
// opening
|
|
||||||
if (location.Y < -conHeight * ConsoleHeight)
|
|
||||||
location.Y = -conHeight * ConsoleHeight;
|
|
||||||
|
|
||||||
location.Y += Time.UnscaledDeltaTime * ConsoleSpeed;
|
|
||||||
if (location.Y > targetY)
|
|
||||||
location.Y = targetY;
|
|
||||||
}
|
|
||||||
|
|
||||||
rootControl.Control.Location = location;
|
|
||||||
|
|
||||||
if (Console.IsOpen)
|
|
||||||
{
|
|
||||||
consoleNotifyBox.Visible = false;
|
|
||||||
consoleInputBox.Visible = true;
|
|
||||||
}
|
|
||||||
else if (!Console.IsOpen)
|
|
||||||
{
|
|
||||||
if (location.Y < -conHeight * ConsoleHeight + fontHeight)
|
|
||||||
{
|
|
||||||
consoleNotifyBox.Visible = true;
|
|
||||||
consoleInputBox.Visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnPrint(string text)
|
|
||||||
{
|
|
||||||
consoleNotifyBox.Height = Math.Min(ConsoleNotifyLines, Console.Lines.Length) * fontHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetInput(string text)
|
|
||||||
{
|
|
||||||
consoleInputBox.Text = text;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnDestroy()
|
||||||
|
{
|
||||||
|
base.OnDestroy();
|
||||||
|
|
||||||
|
//consoleInputEvent.Triggered -= OnConsoleInputEvent;
|
||||||
|
consoleInputEvent?.Dispose();
|
||||||
|
consoleBox?.Dispose();
|
||||||
|
consoleNotifyBox?.Dispose();
|
||||||
|
|
||||||
|
Console.OnOpen -= OnConsoleOpen;
|
||||||
|
Console.OnClose -= OnConsoleClose;
|
||||||
|
Console.OnPrint -= OnPrint;
|
||||||
|
|
||||||
|
Debug.Logger.LogHandler.SendLog -= OnSendLog;
|
||||||
|
Debug.Logger.LogHandler.SendExceptionLog -= OnSendExceptionLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConsoleInputEvent()
|
||||||
|
{
|
||||||
|
string currentInput = Input.InputText;
|
||||||
|
|
||||||
|
if (Input.InputText.Length > 0)
|
||||||
|
{
|
||||||
|
// Really need rawinput support with separate ActionConfig.RawKey values, bound to physical keys/scancode instead of virtual ones
|
||||||
|
var consoleKeys = Input.ActionMappings.Where(x => x.Name == "Console" && x.Key != KeyboardKeys.None);
|
||||||
|
bool backslash = consoleKeys.Any(x => x.Key == KeyboardKeys.Backslash);
|
||||||
|
bool backquote = consoleKeys.Any(x => x.Key == KeyboardKeys.BackQuote);
|
||||||
|
|
||||||
|
// Workaround to only trigger Console key from key bound to left side of 1 (tilde/backquote/backslash key)
|
||||||
|
if ((backslash || backquote) &&
|
||||||
|
(Input.InputText.ToLowerInvariant().Contains('ö') ||
|
||||||
|
Input.InputText.ToLowerInvariant().Contains('æ') ||
|
||||||
|
Input.InputText.ToLowerInvariant().Contains('ø'))) // Scandinavian keyboard layouts
|
||||||
|
return;
|
||||||
|
if (backquote && Input.InputText.ToLowerInvariant().Contains('\'')) // UK keyboard layouts
|
||||||
|
return;
|
||||||
|
if (backslash && (Input.InputText.ToLowerInvariant().Contains('\\') ||
|
||||||
|
Input.InputText.ToLowerInvariant()
|
||||||
|
.Contains('|'))) // US/International keyboard layouts
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!consoleInputBox.IsFocused)
|
||||||
|
Console.Open();
|
||||||
|
else
|
||||||
|
Console.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnConsoleOpen()
|
||||||
|
{
|
||||||
|
Screen.CursorVisible = true;
|
||||||
|
Screen.CursorLock = CursorLockMode.None;
|
||||||
|
|
||||||
|
consoleInputBox.Focus();
|
||||||
|
Parent.As<UICanvas>().ReceivesEvents = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnConsoleClose()
|
||||||
|
{
|
||||||
|
Screen.CursorVisible = false;
|
||||||
|
Screen.CursorLock = CursorLockMode.Locked;
|
||||||
|
|
||||||
|
consoleInputBox.Defocus();
|
||||||
|
#if FLAX_EDITOR
|
||||||
|
Editor.Instance.Windows.GameWin.Focus();
|
||||||
|
#endif
|
||||||
|
Parent.As<UICanvas>().ReceivesEvents = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnUpdate()
|
||||||
|
{
|
||||||
|
base.OnUpdate();
|
||||||
|
|
||||||
|
if (!Console.IsOpen && Input.GetAction("ClearConsole"))
|
||||||
|
Console.Clear();
|
||||||
|
|
||||||
|
float targetY;
|
||||||
|
float conHeight = rootControl.Control.Height /*/ Platform.DpiScale*/;
|
||||||
|
if (!Console.IsOpen)
|
||||||
|
targetY = -conHeight;
|
||||||
|
else
|
||||||
|
targetY = 0.0f;
|
||||||
|
|
||||||
|
Float2 location = rootControl.Control.Location;
|
||||||
|
if (location.Y != targetY)
|
||||||
|
{
|
||||||
|
if (location.Y > targetY)
|
||||||
|
{
|
||||||
|
// closing
|
||||||
|
location.Y -= Time.UnscaledDeltaTime * ConsoleSpeed;
|
||||||
|
if (location.Y < targetY)
|
||||||
|
location.Y = targetY;
|
||||||
|
|
||||||
|
if (location.Y < targetY * ConsoleHeight)
|
||||||
|
location.Y = targetY;
|
||||||
|
}
|
||||||
|
else if (location.Y < targetY)
|
||||||
|
{
|
||||||
|
// opening
|
||||||
|
if (location.Y < -conHeight * ConsoleHeight)
|
||||||
|
location.Y = -conHeight * ConsoleHeight;
|
||||||
|
|
||||||
|
location.Y += Time.UnscaledDeltaTime * ConsoleSpeed;
|
||||||
|
if (location.Y > targetY)
|
||||||
|
location.Y = targetY;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootControl.Control.Location = location;
|
||||||
|
|
||||||
|
if (Console.IsOpen)
|
||||||
|
{
|
||||||
|
consoleNotifyBox.Visible = false;
|
||||||
|
consoleInputBox.Visible = true;
|
||||||
|
}
|
||||||
|
else if (!Console.IsOpen)
|
||||||
|
{
|
||||||
|
if (location.Y < -conHeight * ConsoleHeight + fontHeight)
|
||||||
|
{
|
||||||
|
consoleNotifyBox.Visible = true;
|
||||||
|
consoleInputBox.Visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPrint(string text)
|
||||||
|
{
|
||||||
|
consoleNotifyBox.Height = Math.Min(ConsoleNotifyLines, Console.Lines.Length) * fontHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInput(string text)
|
||||||
|
{
|
||||||
|
consoleInputBox.Text = text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,389 +2,388 @@
|
|||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
// Mostly based on TextBox
|
||||||
|
public class ConsoleTextBoxBase : TextBoxBase
|
||||||
{
|
{
|
||||||
// Mostly based on TextBox
|
private readonly Stopwatch lastDoubleClick = new Stopwatch();
|
||||||
public class ConsoleTextBoxBase : TextBoxBase
|
protected TextLayoutOptions _layout;
|
||||||
|
|
||||||
|
private bool doubleClicked;
|
||||||
|
private Float2 lastDoubleClickLocation = new Float2(0, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the color of the selection (Transparent if not used).
|
||||||
|
/// </summary>
|
||||||
|
[EditorDisplay("Style")] [EditorOrder(2000)] [Tooltip("The color of the selection (Transparent if not used).")]
|
||||||
|
public Color SelectionColor = new Color(0x00, 0x7A, 0xCC);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the color of the text.
|
||||||
|
/// </summary>
|
||||||
|
[EditorDisplay("Style")] [EditorOrder(2000)] [Tooltip("The color of the text.")]
|
||||||
|
public Color TextColor = Color.White;
|
||||||
|
|
||||||
|
//[HideInEditor]
|
||||||
|
//public override string Text => _text;
|
||||||
|
|
||||||
|
public ConsoleTextBoxBase()
|
||||||
{
|
{
|
||||||
private readonly Stopwatch lastDoubleClick = new Stopwatch();
|
}
|
||||||
protected TextLayoutOptions _layout;
|
|
||||||
|
|
||||||
private bool doubleClicked;
|
public ConsoleTextBoxBase(float x, float y, float width, float height) : base(false, x, y, width)
|
||||||
private Float2 lastDoubleClickLocation = new Float2(0, 0);
|
{
|
||||||
|
Height = height;
|
||||||
|
|
||||||
/// <summary>
|
IsReadOnly = false;
|
||||||
/// Gets or sets the color of the selection (Transparent if not used).
|
CaretColor = new Color(1f, 1f, 1f, 1f);
|
||||||
/// </summary>
|
AutoFocus = true;
|
||||||
[EditorDisplay("Style")] [EditorOrder(2000)] [Tooltip("The color of the selection (Transparent if not used).")]
|
EndEditOnClick = false;
|
||||||
public Color SelectionColor = new Color(0x00, 0x7A, 0xCC);
|
|
||||||
|
|
||||||
/// <summary>
|
_layout = TextLayoutOptions.Default;
|
||||||
/// Gets or sets the color of the text.
|
_layout.VerticalAlignment = IsMultiline ? TextAlignment.Near : TextAlignment.Center;
|
||||||
/// </summary>
|
_layout.TextWrapping = TextWrapping.NoWrap;
|
||||||
[EditorDisplay("Style")] [EditorOrder(2000)] [Tooltip("The color of the text.")]
|
_layout.Bounds =
|
||||||
public Color TextColor = Color.White;
|
new Rectangle(0, 0, Width,
|
||||||
|
Height); //new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2);
|
||||||
|
}
|
||||||
|
|
||||||
//[HideInEditor]
|
/// <summary>
|
||||||
//public override string Text => _text;
|
/// Gets or sets the text wrapping within the control bounds.
|
||||||
|
/// </summary>
|
||||||
|
[EditorDisplay("Style")]
|
||||||
|
[EditorOrder(2000)]
|
||||||
|
[Tooltip("The text wrapping within the control bounds.")]
|
||||||
|
public TextWrapping Wrapping
|
||||||
|
{
|
||||||
|
get => _layout.TextWrapping;
|
||||||
|
set => _layout.TextWrapping = value;
|
||||||
|
}
|
||||||
|
|
||||||
public ConsoleTextBoxBase()
|
/// <summary>
|
||||||
|
/// Gets or sets the font.
|
||||||
|
/// </summary>
|
||||||
|
[EditorDisplay("Style")]
|
||||||
|
[EditorOrder(2000)]
|
||||||
|
public FontReference Font { get; set; }
|
||||||
|
|
||||||
|
[HideInEditor] public virtual string TextPrefix { get; set; } = "";
|
||||||
|
|
||||||
|
/*protected override void SetText(string value)
|
||||||
|
{
|
||||||
|
// Prevent from null problems
|
||||||
|
if (value == null)
|
||||||
|
value = string.Empty;
|
||||||
|
|
||||||
|
// Filter text
|
||||||
|
if (value.IndexOf('\r') != -1)
|
||||||
|
value = value.Replace("\r", "");
|
||||||
|
|
||||||
|
// Clamp length
|
||||||
|
if (value.Length > MaxLength)
|
||||||
|
value = value.Substring(0, MaxLength);
|
||||||
|
|
||||||
|
// Ensure to use only single line
|
||||||
|
if (_isMultiline == false && value.Length > 0)
|
||||||
{
|
{
|
||||||
|
// Extract only the first line
|
||||||
|
value = value.GetLines()[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConsoleTextBoxBase(float x, float y, float width, float height) : base(false, x, y, width)
|
if (Text != value)
|
||||||
{
|
{
|
||||||
Height = height;
|
Deselect();
|
||||||
|
ResetViewOffset();
|
||||||
|
|
||||||
IsReadOnly = false;
|
Text = value;
|
||||||
CaretColor = new Color(1f, 1f, 1f, 1f);
|
|
||||||
AutoFocus = true;
|
|
||||||
EndEditOnClick = false;
|
|
||||||
|
|
||||||
_layout = TextLayoutOptions.Default;
|
OnTextChanged();
|
||||||
_layout.VerticalAlignment = IsMultiline ? TextAlignment.Near : TextAlignment.Center;
|
}
|
||||||
_layout.TextWrapping = TextWrapping.NoWrap;
|
}*/
|
||||||
_layout.Bounds =
|
|
||||||
new Rectangle(0, 0, Width,
|
public int GetFontHeight()
|
||||||
Height); //new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2);
|
{
|
||||||
|
Font font = Font.GetFont();
|
||||||
|
if (font == null)
|
||||||
|
return (int)Height;
|
||||||
|
|
||||||
|
return (int)Mathf.Round(font.Height * Scale.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Clear()
|
||||||
|
{
|
||||||
|
// Can't clear the text while user is editing it...
|
||||||
|
bool oldEditing = _isEditing;
|
||||||
|
_isEditing = false;
|
||||||
|
base.Clear();
|
||||||
|
_isEditing = oldEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ResetViewOffset()
|
||||||
|
{
|
||||||
|
TargetViewOffset = new Float2(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public void ScrollToEnd()
|
||||||
|
{
|
||||||
|
float maxY = TextSize.Y - Height;
|
||||||
|
float spacing = GetRealLineSpacing();
|
||||||
|
maxY += spacing;
|
||||||
|
|
||||||
|
TargetViewOffset = new Float2(0, Math.Max(0, maxY));
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public override void ScrollToCaret()
|
||||||
|
{
|
||||||
|
if (Text.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Rectangle caretBounds = CaretBounds;
|
||||||
|
|
||||||
|
float maxY = TextSize.Y - Height;
|
||||||
|
|
||||||
|
Float2 newLocation = CaretBounds.Location;
|
||||||
|
TargetViewOffset = Float2.Clamp(newLocation, new Float2(0, 0), new Float2(_targetViewOffset.X, maxY));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*const bool smoothScrolling = false;
|
||||||
|
|
||||||
|
public override bool OnMouseWheel(Float2 location, float delta)
|
||||||
|
{
|
||||||
|
if (!IsMultiline || Text.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!smoothScrolling)
|
||||||
|
delta = GetFontHeight() * Math.Sign(delta) * 3;
|
||||||
|
else
|
||||||
|
delta *= 30;
|
||||||
|
|
||||||
|
float maxY = TextSize.Y - Height;
|
||||||
|
float offset = GetRealLineSpacing();
|
||||||
|
maxY += offset;
|
||||||
|
TargetViewOffset = Float2.Clamp(_targetViewOffset - new Float2(0, delta), new Float2(0, offset), new Float2(_targetViewOffset.X, maxY));
|
||||||
|
return true;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public override Float2 GetTextSize()
|
||||||
|
{
|
||||||
|
Font font = Font.GetFont();
|
||||||
|
if (font == null)
|
||||||
|
return Float2.Zero;
|
||||||
|
|
||||||
|
return font.MeasureText(Text, ref _layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Float2 GetCharPosition(int index, out float height)
|
||||||
|
{
|
||||||
|
Font font = Font.GetFont();
|
||||||
|
if (font == null)
|
||||||
|
{
|
||||||
|
height = Height;
|
||||||
|
return Float2.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
height = GetFontHeight();
|
||||||
/// Gets or sets the text wrapping within the control bounds.
|
return font.GetCharPosition(Text, index, ref _layout);
|
||||||
/// </summary>
|
}
|
||||||
[EditorDisplay("Style")]
|
|
||||||
[EditorOrder(2000)]
|
public override int HitTestText(Float2 location)
|
||||||
[Tooltip("The text wrapping within the control bounds.")]
|
{
|
||||||
public TextWrapping Wrapping
|
Font font = Font.GetFont();
|
||||||
|
if (font == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (TextPrefix != "")
|
||||||
{
|
{
|
||||||
get => _layout.TextWrapping;
|
Float2 prefixSize = font.MeasureText(TextPrefix);
|
||||||
set => _layout.TextWrapping = value;
|
location.X -= prefixSize.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return font.HitTestText(Text, location, ref _layout);
|
||||||
/// Gets or sets the font.
|
}
|
||||||
/// </summary>
|
|
||||||
[EditorDisplay("Style")]
|
|
||||||
[EditorOrder(2000)]
|
|
||||||
public FontReference Font { get; set; }
|
|
||||||
|
|
||||||
[HideInEditor] public virtual string TextPrefix { get; set; } = "";
|
protected override void OnIsMultilineChanged()
|
||||||
|
{
|
||||||
|
base.OnIsMultilineChanged();
|
||||||
|
|
||||||
/*protected override void SetText(string value)
|
_layout.VerticalAlignment = IsMultiline ? TextAlignment.Near : TextAlignment.Center;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnKeyDown(KeyboardKeys key)
|
||||||
|
{
|
||||||
|
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
|
||||||
|
bool ctrlDown = Root.GetKey(KeyboardKeys.Control);
|
||||||
|
|
||||||
|
if (shiftDown && key == KeyboardKeys.Delete)
|
||||||
|
Cut();
|
||||||
|
else if (ctrlDown && key == KeyboardKeys.Insert)
|
||||||
|
Copy();
|
||||||
|
else if (shiftDown && key == KeyboardKeys.Insert)
|
||||||
|
Paste();
|
||||||
|
if (shiftDown && key == KeyboardKeys.Home)
|
||||||
{
|
{
|
||||||
// Prevent from null problems
|
if (!IsReadOnly)
|
||||||
if (value == null)
|
SetSelection(_selectionStart, 0);
|
||||||
value = string.Empty;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Filter text
|
if (shiftDown && key == KeyboardKeys.End)
|
||||||
if (value.IndexOf('\r') != -1)
|
{
|
||||||
value = value.Replace("\r", "");
|
if (!IsReadOnly)
|
||||||
|
SetSelection(_selectionStart, TextLength);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Clamp length
|
return base.OnKeyDown(key);
|
||||||
if (value.Length > MaxLength)
|
}
|
||||||
value = value.Substring(0, MaxLength);
|
|
||||||
|
|
||||||
// Ensure to use only single line
|
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||||
if (_isMultiline == false && value.Length > 0)
|
{
|
||||||
|
if (doubleClicked && lastDoubleClick.Elapsed.TotalSeconds < 0.5 &&
|
||||||
|
location == lastDoubleClickLocation) // Windows defaults to 500ms window
|
||||||
|
{
|
||||||
|
doubleClicked = false;
|
||||||
|
if (OnMouseTripleClick(location, button))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnMouseDown(location, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||||
|
{
|
||||||
|
doubleClicked = true;
|
||||||
|
lastDoubleClick.Restart();
|
||||||
|
lastDoubleClickLocation = location;
|
||||||
|
|
||||||
|
return base.OnMouseDoubleClick(location, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnMouseTripleClick(Float2 location, MouseButton button)
|
||||||
|
{
|
||||||
|
if (!IsMultiline)
|
||||||
|
SelectAll();
|
||||||
|
else
|
||||||
|
// TODO: select the line
|
||||||
|
SelectAll();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSizeChanged()
|
||||||
|
{
|
||||||
|
base.OnSizeChanged();
|
||||||
|
|
||||||
|
_layout.Bounds = TextRectangle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
// Cache data
|
||||||
|
Rectangle rect = new Rectangle(Float2.Zero, Size);
|
||||||
|
Font font = Font.GetFont();
|
||||||
|
if (!font)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Background
|
||||||
|
Color backColor = BackgroundColor;
|
||||||
|
if (IsMouseOver)
|
||||||
|
backColor = BackgroundSelectedColor;
|
||||||
|
if (backColor.A > 0.0f)
|
||||||
|
Render2D.FillRectangle(rect, backColor);
|
||||||
|
|
||||||
|
Color borderColor = IsFocused ? BorderSelectedColor : BorderColor;
|
||||||
|
if (borderColor.A > 0.0f)
|
||||||
|
Render2D.DrawRectangle(rect, borderColor);
|
||||||
|
|
||||||
|
//string text = TextPrefix + Text;
|
||||||
|
string text = TextPrefix + Text;
|
||||||
|
|
||||||
|
if (text.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Apply view offset and clip mask
|
||||||
|
Render2D.PushClip(TextClipRectangle);
|
||||||
|
bool useViewOffset = !_viewOffset.IsZero;
|
||||||
|
if (useViewOffset)
|
||||||
|
Render2D.PushTransform(Matrix3x3.Translation2D(-_viewOffset));
|
||||||
|
|
||||||
|
// Check if any text is selected to draw selection
|
||||||
|
if (HasSelection)
|
||||||
|
{
|
||||||
|
Float2 leftEdge = font.GetCharPosition(text, SelectionLeft + TextPrefix.Length, ref _layout);
|
||||||
|
Float2 rightEdge = font.GetCharPosition(text, SelectionRight + TextPrefix.Length, ref _layout);
|
||||||
|
float fontHeight = GetFontHeight();
|
||||||
|
|
||||||
|
// Draw selection background
|
||||||
|
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
|
||||||
|
alpha = alpha * alpha;
|
||||||
|
Color selectionColor = SelectionColor * alpha;
|
||||||
|
|
||||||
|
int selectedLinesCount = 1 + Mathf.FloorToInt((rightEdge.Y - leftEdge.Y) / fontHeight);
|
||||||
|
if (selectedLinesCount == 1) // Selected is part of single line
|
||||||
{
|
{
|
||||||
// Extract only the first line
|
Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, rightEdge.X - leftEdge.X, fontHeight);
|
||||||
value = value.GetLines()[0];
|
Render2D.FillRectangle(r1, selectionColor);
|
||||||
}
|
}
|
||||||
|
else // Selected is more than one line
|
||||||
if (Text != value)
|
|
||||||
{
|
{
|
||||||
Deselect();
|
float leftMargin = _layout.Bounds.Location.X;
|
||||||
ResetViewOffset();
|
Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, 1000000000, fontHeight);
|
||||||
|
Render2D.FillRectangle(r1, selectionColor);
|
||||||
|
|
||||||
Text = value;
|
for (int i = 3; i <= selectedLinesCount; i++)
|
||||||
|
{
|
||||||
|
leftEdge.Y += fontHeight;
|
||||||
|
Rectangle r = new Rectangle(leftMargin, leftEdge.Y, 1000000000, fontHeight);
|
||||||
|
Render2D.FillRectangle(r, selectionColor);
|
||||||
|
}
|
||||||
|
|
||||||
OnTextChanged();
|
Rectangle r2 = new Rectangle(leftMargin, rightEdge.Y, rightEdge.X - leftMargin, fontHeight);
|
||||||
|
Render2D.FillRectangle(r2, selectionColor);
|
||||||
}
|
}
|
||||||
}*/
|
|
||||||
|
|
||||||
public int GetFontHeight()
|
|
||||||
{
|
|
||||||
Font font = Font.GetFont();
|
|
||||||
if (font == null)
|
|
||||||
return (int)Height;
|
|
||||||
|
|
||||||
return (int)Mathf.Round(font.Height * Scale.Y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Clear()
|
Render2D.DrawText(font, text, TextColor, ref _layout);
|
||||||
|
|
||||||
|
if (CaretPosition > -1)
|
||||||
{
|
{
|
||||||
// Can't clear the text while user is editing it...
|
Float2 prefixSize = TextPrefix != "" ? font.MeasureText(TextPrefix) : new Float2();
|
||||||
bool oldEditing = _isEditing;
|
|
||||||
_isEditing = false;
|
|
||||||
base.Clear();
|
|
||||||
_isEditing = oldEditing;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ResetViewOffset()
|
|
||||||
{
|
|
||||||
TargetViewOffset = new Float2(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*public void ScrollToEnd()
|
|
||||||
{
|
|
||||||
float maxY = TextSize.Y - Height;
|
|
||||||
float spacing = GetRealLineSpacing();
|
|
||||||
maxY += spacing;
|
|
||||||
|
|
||||||
TargetViewOffset = new Float2(0, Math.Max(0, maxY));
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public override void ScrollToCaret()
|
|
||||||
{
|
|
||||||
if (Text.Length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Rectangle caretBounds = CaretBounds;
|
Rectangle caretBounds = CaretBounds;
|
||||||
|
caretBounds.X += prefixSize.X;
|
||||||
|
|
||||||
float maxY = TextSize.Y - Height;
|
float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f);
|
||||||
|
alpha = alpha * alpha * alpha * alpha * alpha * alpha;
|
||||||
Float2 newLocation = CaretBounds.Location;
|
Render2D.FillRectangle(caretBounds, CaretColor * alpha);
|
||||||
TargetViewOffset = Float2.Clamp(newLocation, new Float2(0, 0), new Float2(_targetViewOffset.X, maxY));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*const bool smoothScrolling = false;
|
// Restore rendering state
|
||||||
|
if (useViewOffset)
|
||||||
|
Render2D.PopTransform();
|
||||||
|
Render2D.PopClip();
|
||||||
|
}
|
||||||
|
|
||||||
public override bool OnMouseWheel(Float2 location, float delta)
|
public override void Paste()
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string clipboardText = Clipboard.Text;
|
||||||
|
// Handle newlines in clipboard text
|
||||||
|
if (!string.IsNullOrEmpty(clipboardText))
|
||||||
{
|
{
|
||||||
if (!IsMultiline || Text.Length == 0)
|
// TODO: probably better to just split these lines and parse each line separately
|
||||||
return false;
|
clipboardText = clipboardText.Replace("\r\n", "");
|
||||||
|
clipboardText = clipboardText.Replace("\n", "");
|
||||||
if (!smoothScrolling)
|
|
||||||
delta = GetFontHeight() * Math.Sign(delta) * 3;
|
|
||||||
else
|
|
||||||
delta *= 30;
|
|
||||||
|
|
||||||
float maxY = TextSize.Y - Height;
|
|
||||||
float offset = GetRealLineSpacing();
|
|
||||||
maxY += offset;
|
|
||||||
TargetViewOffset = Float2.Clamp(_targetViewOffset - new Float2(0, delta), new Float2(0, offset), new Float2(_targetViewOffset.X, maxY));
|
|
||||||
return true;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public override Float2 GetTextSize()
|
|
||||||
{
|
|
||||||
Font font = Font.GetFont();
|
|
||||||
if (font == null)
|
|
||||||
return Float2.Zero;
|
|
||||||
|
|
||||||
return font.MeasureText(Text, ref _layout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Float2 GetCharPosition(int index, out float height)
|
if (!string.IsNullOrEmpty(clipboardText))
|
||||||
{
|
{
|
||||||
Font font = Font.GetFont();
|
int right = SelectionRight;
|
||||||
if (font == null)
|
Insert(clipboardText);
|
||||||
{
|
SetSelection(Mathf.Max(right, 0) + clipboardText.Length);
|
||||||
height = Height;
|
|
||||||
return Float2.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
height = GetFontHeight();
|
|
||||||
return font.GetCharPosition(Text, index, ref _layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int HitTestText(Float2 location)
|
|
||||||
{
|
|
||||||
Font font = Font.GetFont();
|
|
||||||
if (font == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (TextPrefix != "")
|
|
||||||
{
|
|
||||||
Float2 prefixSize = font.MeasureText(TextPrefix);
|
|
||||||
location.X -= prefixSize.X;
|
|
||||||
}
|
|
||||||
|
|
||||||
return font.HitTestText(Text, location, ref _layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnIsMultilineChanged()
|
|
||||||
{
|
|
||||||
base.OnIsMultilineChanged();
|
|
||||||
|
|
||||||
_layout.VerticalAlignment = IsMultiline ? TextAlignment.Near : TextAlignment.Center;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnKeyDown(KeyboardKeys key)
|
|
||||||
{
|
|
||||||
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
|
|
||||||
bool ctrlDown = Root.GetKey(KeyboardKeys.Control);
|
|
||||||
|
|
||||||
if (shiftDown && key == KeyboardKeys.Delete)
|
|
||||||
Cut();
|
|
||||||
else if (ctrlDown && key == KeyboardKeys.Insert)
|
|
||||||
Copy();
|
|
||||||
else if (shiftDown && key == KeyboardKeys.Insert)
|
|
||||||
Paste();
|
|
||||||
if (shiftDown && key == KeyboardKeys.Home)
|
|
||||||
{
|
|
||||||
if (!IsReadOnly)
|
|
||||||
SetSelection(_selectionStart, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shiftDown && key == KeyboardKeys.End)
|
|
||||||
{
|
|
||||||
if (!IsReadOnly)
|
|
||||||
SetSelection(_selectionStart, TextLength);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnKeyDown(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
|
||||||
{
|
|
||||||
if (doubleClicked && lastDoubleClick.Elapsed.TotalSeconds < 0.5 &&
|
|
||||||
location == lastDoubleClickLocation) // Windows defaults to 500ms window
|
|
||||||
{
|
|
||||||
doubleClicked = false;
|
|
||||||
if (OnMouseTripleClick(location, button))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnMouseDown(location, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
|
||||||
{
|
|
||||||
doubleClicked = true;
|
|
||||||
lastDoubleClick.Restart();
|
|
||||||
lastDoubleClickLocation = location;
|
|
||||||
|
|
||||||
return base.OnMouseDoubleClick(location, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnMouseTripleClick(Float2 location, MouseButton button)
|
|
||||||
{
|
|
||||||
if (!IsMultiline)
|
|
||||||
SelectAll();
|
|
||||||
else
|
|
||||||
// TODO: select the line
|
|
||||||
SelectAll();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnSizeChanged()
|
|
||||||
{
|
|
||||||
base.OnSizeChanged();
|
|
||||||
|
|
||||||
_layout.Bounds = TextRectangle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Draw()
|
|
||||||
{
|
|
||||||
// Cache data
|
|
||||||
Rectangle rect = new Rectangle(Float2.Zero, Size);
|
|
||||||
Font font = Font.GetFont();
|
|
||||||
if (!font)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Background
|
|
||||||
Color backColor = BackgroundColor;
|
|
||||||
if (IsMouseOver)
|
|
||||||
backColor = BackgroundSelectedColor;
|
|
||||||
if (backColor.A > 0.0f)
|
|
||||||
Render2D.FillRectangle(rect, backColor);
|
|
||||||
|
|
||||||
Color borderColor = IsFocused ? BorderSelectedColor : BorderColor;
|
|
||||||
if (borderColor.A > 0.0f)
|
|
||||||
Render2D.DrawRectangle(rect, borderColor);
|
|
||||||
|
|
||||||
//string text = TextPrefix + Text;
|
|
||||||
string text = TextPrefix + Text;
|
|
||||||
|
|
||||||
if (text.Length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Apply view offset and clip mask
|
|
||||||
Render2D.PushClip(TextClipRectangle);
|
|
||||||
bool useViewOffset = !_viewOffset.IsZero;
|
|
||||||
if (useViewOffset)
|
|
||||||
Render2D.PushTransform(Matrix3x3.Translation2D(-_viewOffset));
|
|
||||||
|
|
||||||
// Check if any text is selected to draw selection
|
|
||||||
if (HasSelection)
|
|
||||||
{
|
|
||||||
Float2 leftEdge = font.GetCharPosition(text, SelectionLeft + TextPrefix.Length, ref _layout);
|
|
||||||
Float2 rightEdge = font.GetCharPosition(text, SelectionRight + TextPrefix.Length, ref _layout);
|
|
||||||
float fontHeight = GetFontHeight();
|
|
||||||
|
|
||||||
// Draw selection background
|
|
||||||
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
|
|
||||||
alpha = alpha * alpha;
|
|
||||||
Color selectionColor = SelectionColor * alpha;
|
|
||||||
|
|
||||||
int selectedLinesCount = 1 + Mathf.FloorToInt((rightEdge.Y - leftEdge.Y) / fontHeight);
|
|
||||||
if (selectedLinesCount == 1) // Selected is part of single line
|
|
||||||
{
|
|
||||||
Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, rightEdge.X - leftEdge.X, fontHeight);
|
|
||||||
Render2D.FillRectangle(r1, selectionColor);
|
|
||||||
}
|
|
||||||
else // Selected is more than one line
|
|
||||||
{
|
|
||||||
float leftMargin = _layout.Bounds.Location.X;
|
|
||||||
Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, 1000000000, fontHeight);
|
|
||||||
Render2D.FillRectangle(r1, selectionColor);
|
|
||||||
|
|
||||||
for (int i = 3; i <= selectedLinesCount; i++)
|
|
||||||
{
|
|
||||||
leftEdge.Y += fontHeight;
|
|
||||||
Rectangle r = new Rectangle(leftMargin, leftEdge.Y, 1000000000, fontHeight);
|
|
||||||
Render2D.FillRectangle(r, selectionColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle r2 = new Rectangle(leftMargin, rightEdge.Y, rightEdge.X - leftMargin, fontHeight);
|
|
||||||
Render2D.FillRectangle(r2, selectionColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Render2D.DrawText(font, text, TextColor, ref _layout);
|
|
||||||
|
|
||||||
if (CaretPosition > -1)
|
|
||||||
{
|
|
||||||
Float2 prefixSize = TextPrefix != "" ? font.MeasureText(TextPrefix) : new Float2();
|
|
||||||
Rectangle caretBounds = CaretBounds;
|
|
||||||
caretBounds.X += prefixSize.X;
|
|
||||||
|
|
||||||
float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f);
|
|
||||||
alpha = alpha * alpha * alpha * alpha * alpha * alpha;
|
|
||||||
Render2D.FillRectangle(caretBounds, CaretColor * alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore rendering state
|
|
||||||
if (useViewOffset)
|
|
||||||
Render2D.PopTransform();
|
|
||||||
Render2D.PopClip();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Paste()
|
|
||||||
{
|
|
||||||
if (IsReadOnly)
|
|
||||||
return;
|
|
||||||
|
|
||||||
string clipboardText = Clipboard.Text;
|
|
||||||
// Handle newlines in clipboard text
|
|
||||||
if (!string.IsNullOrEmpty(clipboardText))
|
|
||||||
{
|
|
||||||
// TODO: probably better to just split these lines and parse each line separately
|
|
||||||
clipboardText = clipboardText.Replace("\r\n", "");
|
|
||||||
clipboardText = clipboardText.Replace("\n", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(clipboardText))
|
|
||||||
{
|
|
||||||
int right = SelectionRight;
|
|
||||||
Insert(clipboardText);
|
|
||||||
SetSelection(Mathf.Max(right, 0) + clipboardText.Length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,75 +1,74 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
{
|
|
||||||
[Flags]
|
|
||||||
public enum ConsoleFlags
|
|
||||||
{
|
|
||||||
NoSerialize = 1, // Value does not persist
|
|
||||||
|
|
||||||
Alias = NoSerialize
|
[Flags]
|
||||||
|
public enum ConsoleFlags
|
||||||
|
{
|
||||||
|
NoSerialize = 1, // Value does not persist
|
||||||
|
|
||||||
|
Alias = NoSerialize
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct ConsoleVariable
|
||||||
|
{
|
||||||
|
public string name { get; }
|
||||||
|
public ConsoleFlags flags { get; }
|
||||||
|
|
||||||
|
private readonly FieldInfo field;
|
||||||
|
private readonly MethodInfo getter;
|
||||||
|
private readonly MethodInfo setter;
|
||||||
|
|
||||||
|
public ConsoleVariable(string name, ConsoleFlags flags, FieldInfo field)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.flags = flags;
|
||||||
|
this.field = field;
|
||||||
|
getter = null;
|
||||||
|
setter = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal struct ConsoleVariable
|
public ConsoleVariable(string name, ConsoleFlags flags, MethodInfo getter, MethodInfo setter)
|
||||||
{
|
{
|
||||||
public string name { get; }
|
this.name = name;
|
||||||
public ConsoleFlags flags { get; }
|
this.flags = flags;
|
||||||
|
field = null;
|
||||||
|
this.getter = getter;
|
||||||
|
this.setter = setter;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly FieldInfo field;
|
public string GetValueString()
|
||||||
private readonly MethodInfo getter;
|
{
|
||||||
private readonly MethodInfo setter;
|
Type type = field != null ? field.FieldType : getter.ReturnType;
|
||||||
|
if (type == typeof(string))
|
||||||
public ConsoleVariable(string name, ConsoleFlags flags, FieldInfo field)
|
|
||||||
{
|
{
|
||||||
this.name = name;
|
if (field != null)
|
||||||
this.flags = flags;
|
return (string)field.GetValue(null);
|
||||||
this.field = field;
|
if (setter != null)
|
||||||
getter = null;
|
return (string)getter.Invoke(null, null);
|
||||||
setter = null;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("cvar is not type of string");
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConsoleVariable(string name, ConsoleFlags flags, MethodInfo getter, MethodInfo setter)
|
throw new Exception("GetValueString no field or getter specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValue(string value)
|
||||||
|
{
|
||||||
|
Type type = field != null ? field.FieldType : getter.ReturnType;
|
||||||
|
if (type == typeof(string))
|
||||||
{
|
{
|
||||||
this.name = name;
|
if (field != null)
|
||||||
this.flags = flags;
|
field.SetValue(null, value);
|
||||||
field = null;
|
else if (setter != null)
|
||||||
this.getter = getter;
|
setter.Invoke(null, new object[] { value });
|
||||||
this.setter = setter;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
public string GetValueString()
|
|
||||||
{
|
{
|
||||||
Type type = field != null ? field.FieldType : getter.ReturnType;
|
throw new Exception("Unsupported type for SetValue: " + type.Name);
|
||||||
if (type == typeof(string))
|
|
||||||
{
|
|
||||||
if (field != null)
|
|
||||||
return (string)field.GetValue(null);
|
|
||||||
if (setter != null)
|
|
||||||
return (string)getter.Invoke(null, null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception("cvar is not type of string");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("GetValueString no field or getter specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetValue(string value)
|
|
||||||
{
|
|
||||||
Type type = field != null ? field.FieldType : getter.ReturnType;
|
|
||||||
if (type == typeof(string))
|
|
||||||
{
|
|
||||||
if (field != null)
|
|
||||||
field.SetValue(null, value);
|
|
||||||
else if (setter != null)
|
|
||||||
setter.Invoke(null, new object[] { value });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception("Unsupported type for SetValue: " + type.Name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,25 @@
|
|||||||
using System;
|
using System;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public static class Utilities
|
||||||
{
|
{
|
||||||
public static class Utilities
|
public static ScopeProfiler ProfileScope(string eventName)
|
||||||
{
|
{
|
||||||
public static ScopeProfiler ProfileScope(string eventName)
|
return new ScopeProfiler(eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ScopeProfiler : IDisposable
|
||||||
|
{
|
||||||
|
public ScopeProfiler(string eventName)
|
||||||
{
|
{
|
||||||
return new ScopeProfiler(eventName);
|
Profiler.BeginEvent(eventName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ScopeProfiler : IDisposable
|
public void Dispose()
|
||||||
{
|
{
|
||||||
public ScopeProfiler(string eventName)
|
Profiler.EndEvent();
|
||||||
{
|
|
||||||
Profiler.BeginEvent(eventName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Profiler.EndEvent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,199 +7,198 @@ using FlaxEngine.Assertions;
|
|||||||
using FlaxEngine.Networking;
|
using FlaxEngine.Networking;
|
||||||
using Console = Game.Console;
|
using Console = Game.Console;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class NetworkPredictedAttribute : Attribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
}
|
||||||
public class NetworkPredictedAttribute : Attribute
|
|
||||||
|
// TODO: insert code to update variables with this attribute?
|
||||||
|
// rename to NetworkReplicatedAttribute?
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class NetworkedAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkMulticastAttribute: calls methods marked with this in all clients
|
||||||
|
|
||||||
|
public enum NetworkMessageType : byte
|
||||||
|
{
|
||||||
|
Handshake = 1,
|
||||||
|
Message
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static partial class NetworkManager
|
||||||
|
{
|
||||||
|
public delegate bool OnMessageDecl(ref NetworkEvent networkEvent);
|
||||||
|
|
||||||
|
private static bool initialized;
|
||||||
|
|
||||||
|
public static NetworkPeer server;
|
||||||
|
public static NetworkPeer client;
|
||||||
|
|
||||||
|
private static readonly ushort ServerPort = 59183;
|
||||||
|
private static string ServerAddress;
|
||||||
|
private static readonly ushort MTU = 1500;
|
||||||
|
private static readonly ushort MaximumClients = 32;
|
||||||
|
|
||||||
|
private static List<OnMessageDecl> OnClientMessageDelegates = new(3);
|
||||||
|
private static List<OnMessageDecl> OnServerMessageDelegates = new(3);
|
||||||
|
|
||||||
|
public static void RegisterClientMessageCallback(OnMessageDecl deleg)
|
||||||
{
|
{
|
||||||
|
Assert.IsTrue(!OnClientMessageDelegates.Contains(deleg));
|
||||||
|
OnClientMessageDelegates.Add(deleg);
|
||||||
|
}
|
||||||
|
public static void UnregisterClientMessageCallback(OnMessageDecl deleg)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(OnClientMessageDelegates.Contains(deleg));
|
||||||
|
OnClientMessageDelegates.Remove(deleg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: insert code to update variables with this attribute?
|
public static void RegisterServerMessageCallback(OnMessageDecl deleg)
|
||||||
// rename to NetworkReplicatedAttribute?
|
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
|
||||||
public class NetworkedAttribute : Attribute
|
|
||||||
{
|
{
|
||||||
|
Assert.IsTrue(!OnServerMessageDelegates.Contains(deleg));
|
||||||
|
OnServerMessageDelegates.Add(deleg);
|
||||||
|
}
|
||||||
|
public static void UnregisterServerMessageCallback(OnMessageDecl deleg)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(OnServerMessageDelegates.Contains(deleg));
|
||||||
|
OnServerMessageDelegates.Remove(deleg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkMulticastAttribute: calls methods marked with this in all clients
|
public static string DebugLastHandledMessage = "";
|
||||||
|
|
||||||
public enum NetworkMessageType : byte
|
//public static bool IsServer = false;
|
||||||
|
//public static bool IsClient = false;
|
||||||
|
// static bool IsLocalClient = false; // Context dependant, true when message is handled by local client
|
||||||
|
|
||||||
|
public static void Init()
|
||||||
{
|
{
|
||||||
Handshake = 1,
|
if (initialized)
|
||||||
Message
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
/*if (Engine.CommandLine.Contains("-server"))
|
||||||
public static partial class NetworkManager
|
|
||||||
{
|
|
||||||
public delegate bool OnMessageDecl(ref NetworkEvent networkEvent);
|
|
||||||
|
|
||||||
private static bool initialized;
|
|
||||||
|
|
||||||
public static NetworkPeer server;
|
|
||||||
public static NetworkPeer client;
|
|
||||||
|
|
||||||
private static readonly ushort ServerPort = 59183;
|
|
||||||
private static string ServerAddress;
|
|
||||||
private static readonly ushort MTU = 1500;
|
|
||||||
private static readonly ushort MaximumClients = 32;
|
|
||||||
|
|
||||||
private static List<OnMessageDecl> OnClientMessageDelegates = new(3);
|
|
||||||
private static List<OnMessageDecl> OnServerMessageDelegates = new(3);
|
|
||||||
|
|
||||||
public static void RegisterClientMessageCallback(OnMessageDecl deleg)
|
|
||||||
{
|
{
|
||||||
Assert.IsTrue(!OnClientMessageDelegates.Contains(deleg));
|
StartServer();
|
||||||
OnClientMessageDelegates.Add(deleg);
|
ServerAddress = "localhost";
|
||||||
|
ConnectServer();
|
||||||
}
|
}
|
||||||
public static void UnregisterClientMessageCallback(OnMessageDecl deleg)
|
else if (Engine.CommandLine.Contains("-client"))
|
||||||
{
|
{
|
||||||
Assert.IsTrue(OnClientMessageDelegates.Contains(deleg));
|
ServerAddress = "localhost";
|
||||||
OnClientMessageDelegates.Remove(deleg);
|
ConnectServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RegisterServerMessageCallback(OnMessageDecl deleg)
|
|
||||||
{
|
|
||||||
Assert.IsTrue(!OnServerMessageDelegates.Contains(deleg));
|
|
||||||
OnServerMessageDelegates.Add(deleg);
|
|
||||||
}
|
|
||||||
public static void UnregisterServerMessageCallback(OnMessageDecl deleg)
|
|
||||||
{
|
|
||||||
Assert.IsTrue(OnServerMessageDelegates.Contains(deleg));
|
|
||||||
OnServerMessageDelegates.Remove(deleg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string DebugLastHandledMessage = "";
|
|
||||||
|
|
||||||
//public static bool IsServer = false;
|
|
||||||
//public static bool IsClient = false;
|
|
||||||
// static bool IsLocalClient = false; // Context dependant, true when message is handled by local client
|
|
||||||
|
|
||||||
public static void Init()
|
|
||||||
{
|
|
||||||
if (initialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*if (Engine.CommandLine.Contains("-server"))
|
|
||||||
{
|
|
||||||
StartServer();
|
|
||||||
ServerAddress = "localhost";
|
|
||||||
ConnectServer();
|
|
||||||
}
|
|
||||||
else if (Engine.CommandLine.Contains("-client"))
|
|
||||||
{
|
|
||||||
ServerAddress = "localhost";
|
|
||||||
ConnectServer();
|
|
||||||
}
|
|
||||||
//#if FLAX_EDITOR
|
//#if FLAX_EDITOR
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
StartServer();
|
StartServer();
|
||||||
ServerAddress = "localhost";
|
ServerAddress = "localhost";
|
||||||
ConnectServer();
|
ConnectServer();
|
||||||
}*/
|
}*/
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
#if FLAX_EDITOR
|
#if FLAX_EDITOR
|
||||||
Editor.Instance.PlayModeEnd += Cleanup;
|
Editor.Instance.PlayModeEnd += Cleanup;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//WorldStateManager.Init(); // FIXME
|
//WorldStateManager.Init(); // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Cleanup()
|
||||||
|
{
|
||||||
|
Scripting.Exit -= Cleanup;
|
||||||
|
Scripting.FixedUpdate -= OnDemoUpdate;
|
||||||
|
Scripting.FixedUpdate -= OnServerNetworkUpdate;
|
||||||
|
Level.ActorSpawned -= OnServerActorSpawned;
|
||||||
|
Scripting.FixedUpdate -= OnClientNetworkUpdate;
|
||||||
|
Level.ActorSpawned -= OnClientActorSpawned;
|
||||||
|
|
||||||
|
if (server != null)
|
||||||
|
{
|
||||||
|
NetworkPeer.ShutdownPeer(server);
|
||||||
|
server = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Cleanup()
|
if (client != null)
|
||||||
{
|
{
|
||||||
Scripting.Exit -= Cleanup;
|
NetworkPeer.ShutdownPeer(client);
|
||||||
Scripting.FixedUpdate -= OnDemoUpdate;
|
client = null;
|
||||||
Scripting.FixedUpdate -= OnServerNetworkUpdate;
|
}
|
||||||
Level.ActorSpawned -= OnServerActorSpawned;
|
|
||||||
Scripting.FixedUpdate -= OnClientNetworkUpdate;
|
|
||||||
Level.ActorSpawned -= OnClientActorSpawned;
|
|
||||||
|
|
||||||
if (server != null)
|
LocalPlayerClientId = 0;
|
||||||
{
|
|
||||||
NetworkPeer.ShutdownPeer(server);
|
|
||||||
server = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client != null)
|
|
||||||
{
|
|
||||||
NetworkPeer.ShutdownPeer(client);
|
|
||||||
client = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalPlayerClientId = 0;
|
|
||||||
|
|
||||||
#if FLAX_EDITOR
|
#if FLAX_EDITOR
|
||||||
Editor.Instance.PlayModeEnd -= Cleanup;
|
Editor.Instance.PlayModeEnd -= Cleanup;
|
||||||
//GameModeManager.Cleanup(); // FIXME
|
//GameModeManager.Cleanup(); // FIXME
|
||||||
#endif
|
#endif
|
||||||
if (clientWorldStateManager != null)
|
if (clientWorldStateManager != null)
|
||||||
{
|
{
|
||||||
clientWorldStateManager.Cleanup();
|
clientWorldStateManager.Cleanup();
|
||||||
clientWorldStateManager = null;
|
clientWorldStateManager = null;
|
||||||
}
|
}
|
||||||
if (serverWorldStateManager != null)
|
if (serverWorldStateManager != null)
|
||||||
{
|
{
|
||||||
serverWorldStateManager.Cleanup();
|
serverWorldStateManager.Cleanup();
|
||||||
serverWorldStateManager = null;
|
serverWorldStateManager = null;
|
||||||
}
|
|
||||||
|
|
||||||
StopRecording();
|
|
||||||
|
|
||||||
initialized = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnNetworkMessage(ref NetworkEvent networkEvent, Span<OnMessageDecl> funcs)
|
StopRecording();
|
||||||
|
|
||||||
|
initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnNetworkMessage(ref NetworkEvent networkEvent, Span<OnMessageDecl> funcs)
|
||||||
|
{
|
||||||
|
byte messageTypeByte = networkEvent.Message.ReadByte();
|
||||||
|
if (!Enum.IsDefined(typeof(NetworkMessageType), messageTypeByte))
|
||||||
{
|
{
|
||||||
byte messageTypeByte = networkEvent.Message.ReadByte();
|
Console.PrintError($"Unsupported message type received from client: {messageTypeByte}");
|
||||||
if (!Enum.IsDefined(typeof(NetworkMessageType), messageTypeByte))
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkMessageType messageType = (NetworkMessageType)messageTypeByte;
|
||||||
|
|
||||||
|
switch (messageType)
|
||||||
|
{
|
||||||
|
case NetworkMessageType.Handshake:
|
||||||
{
|
{
|
||||||
Console.PrintError($"Unsupported message type received from client: {messageTypeByte}");
|
string message = networkEvent.Message.ReadString();
|
||||||
return;
|
Console.Print($"Received handshake from {networkEvent.Sender.ConnectionId}, msg: " + message);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case NetworkMessageType.Message:
|
||||||
NetworkMessageType messageType = (NetworkMessageType)messageTypeByte;
|
|
||||||
|
|
||||||
switch (messageType)
|
|
||||||
{
|
{
|
||||||
case NetworkMessageType.Handshake:
|
var messageStartPosition = networkEvent.Message.Position;
|
||||||
|
foreach (var func in funcs)
|
||||||
{
|
{
|
||||||
string message = networkEvent.Message.ReadString();
|
networkEvent.Message.Position = messageStartPosition;
|
||||||
Console.Print($"Received handshake from {networkEvent.Sender.ConnectionId}, msg: " + message);
|
bool handled = func(ref networkEvent);
|
||||||
break;
|
if (handled)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case NetworkMessageType.Message:
|
|
||||||
{
|
//if (OnMessage != null)
|
||||||
var messageStartPosition = networkEvent.Message.Position;
|
/*{
|
||||||
foreach (var func in funcs)
|
// GetInvocationList() allocates, use allocation-free but unsafe way to iterate
|
||||||
|
//
|
||||||
|
//foreach (OnMessageDecl func in OnMessage.GetInvocationList()
|
||||||
|
// .Cast<OnMessageDecl>().ToArray())
|
||||||
|
foreach (OnMessageDecl func in OnMessageDelegates)
|
||||||
{
|
{
|
||||||
networkEvent.Message.Position = messageStartPosition;
|
bool ret = func.Invoke(ref networkEvent);
|
||||||
bool handled = func(ref networkEvent);
|
if (ret)
|
||||||
if (handled)
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}*/
|
||||||
//if (OnMessage != null)
|
break;
|
||||||
/*{
|
|
||||||
// GetInvocationList() allocates, use allocation-free but unsafe way to iterate
|
|
||||||
//
|
|
||||||
//foreach (OnMessageDecl func in OnMessage.GetInvocationList()
|
|
||||||
// .Cast<OnMessageDecl>().ToArray())
|
|
||||||
foreach (OnMessageDecl func in OnMessageDelegates)
|
|
||||||
{
|
|
||||||
bool ret = func.Invoke(ref networkEvent);
|
|
||||||
if (ret)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
Console.PrintError($"Unsupported message type received from client: {messageTypeByte}");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
Console.PrintError($"Unsupported message type received from client: {messageTypeByte}");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,153 +6,152 @@ using FlaxEngine.Networking;
|
|||||||
using Console = Game.Console;
|
using Console = Game.Console;
|
||||||
using Object = FlaxEngine.Object;
|
using Object = FlaxEngine.Object;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public static partial class NetworkManager
|
||||||
{
|
{
|
||||||
public static partial class NetworkManager
|
public static uint LocalPlayerClientId { get; /*private*/ set; }
|
||||||
|
|
||||||
|
public static INetworkDriver ClientNetworkDriver { get; set; }
|
||||||
|
|
||||||
|
public static WorldStateManager clientWorldStateManager = null;
|
||||||
|
|
||||||
|
public static bool ConnectServer(string serverAddress = "localhost", bool listenServer = false)
|
||||||
{
|
{
|
||||||
public static uint LocalPlayerClientId { get; /*private*/ set; }
|
if (!listenServer)
|
||||||
|
|
||||||
public static INetworkDriver ClientNetworkDriver { get; set; }
|
|
||||||
|
|
||||||
public static WorldStateManager clientWorldStateManager = null;
|
|
||||||
|
|
||||||
public static bool ConnectServer(string serverAddress = "localhost", bool listenServer = false)
|
|
||||||
{
|
{
|
||||||
if (!listenServer)
|
Cleanup();
|
||||||
{
|
//WorldStateManager.Init();
|
||||||
Cleanup();
|
clientWorldStateManager = new WorldStateManager(isClient: true);
|
||||||
//WorldStateManager.Init();
|
}
|
||||||
clientWorldStateManager = new WorldStateManager(isClient: true);
|
else
|
||||||
}
|
{
|
||||||
else
|
clientWorldStateManager = new WorldStateManager(isLocalClient: true);
|
||||||
{
|
}
|
||||||
clientWorldStateManager = new WorldStateManager(isLocalClient: true);
|
ServerAddress = serverAddress;
|
||||||
}
|
|
||||||
ServerAddress = serverAddress;
|
|
||||||
|
|
||||||
//var driver = Object.New(typeof(ENetDriver));
|
//var driver = Object.New(typeof(ENetDriver));
|
||||||
//ClientNetworkDriver = null;
|
//ClientNetworkDriver = null;
|
||||||
NetworkLagDriver driver = Object.New<NetworkLagDriver>();
|
NetworkLagDriver driver = Object.New<NetworkLagDriver>();
|
||||||
driver.Lag = 0f;
|
driver.Lag = 0f;
|
||||||
ClientNetworkDriver = driver;
|
ClientNetworkDriver = driver;
|
||||||
|
|
||||||
|
|
||||||
|
client = NetworkPeer.CreatePeer(new NetworkConfig
|
||||||
|
{
|
||||||
|
NetworkDriver = driver,
|
||||||
|
ConnectionsLimit = MaximumClients,
|
||||||
|
MessagePoolSize = 2048,
|
||||||
|
MessageSize = MTU,
|
||||||
|
Address = ServerAddress == "localhost" ? "127.0.0.1" : ServerAddress,
|
||||||
|
Port = ServerPort
|
||||||
|
});
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
Console.Print("Failed to create NetworkPeer.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!client.Connect())
|
||||||
|
{
|
||||||
|
Console.Print("Failed to connect to the server.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scripting.FixedUpdate += OnClientNetworkUpdate;
|
||||||
|
if (!listenServer)
|
||||||
|
{
|
||||||
|
Scripting.Exit += Cleanup;
|
||||||
|
//Level.ActorSpawned += OnClientActorSpawned;
|
||||||
|
}
|
||||||
|
|
||||||
|
string demoPath = System.IO.Path.Combine(AssetManager.DemoPath, $"{DateTimeOffset.Now.UtcTicks}_v2.gdem");
|
||||||
|
RecordDemo(demoPath);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnClientNetworkUpdate()
|
||||||
|
{
|
||||||
|
using var _ = Utilities.ProfileScope("NetworkManager_OnClientNetworkUpdate");
|
||||||
|
|
||||||
|
while (client.PopEvent(out NetworkEvent networkEvent))
|
||||||
|
{
|
||||||
|
RecordMessage(ref networkEvent);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//IsLocalClient = server != null;
|
||||||
|
//IsClient = true;
|
||||||
|
OnClientReadMessage(ref networkEvent);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
//IsLocalClient = false;
|
||||||
|
//IsClient = false;
|
||||||
|
if (networkEvent.EventType == NetworkEventType.Message)
|
||||||
|
client.RecycleMessage(networkEvent.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
client = NetworkPeer.CreatePeer(new NetworkConfig
|
|
||||||
{
|
|
||||||
NetworkDriver = driver,
|
|
||||||
ConnectionsLimit = MaximumClients,
|
|
||||||
MessagePoolSize = 2048,
|
|
||||||
MessageSize = MTU,
|
|
||||||
Address = ServerAddress == "localhost" ? "127.0.0.1" : ServerAddress,
|
|
||||||
Port = ServerPort
|
|
||||||
});
|
|
||||||
if (client == null)
|
|
||||||
{
|
|
||||||
Console.Print("Failed to create NetworkPeer.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!client.Connect())
|
|
||||||
{
|
|
||||||
Console.Print("Failed to connect to the server.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Scripting.FixedUpdate += OnClientNetworkUpdate;
|
|
||||||
if (!listenServer)
|
|
||||||
{
|
|
||||||
Scripting.Exit += Cleanup;
|
|
||||||
//Level.ActorSpawned += OnClientActorSpawned;
|
|
||||||
}
|
|
||||||
|
|
||||||
string demoPath = System.IO.Path.Combine(AssetManager.DemoPath, $"{DateTimeOffset.Now.UtcTicks}_v2.gdem");
|
|
||||||
RecordDemo(demoPath);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnClientNetworkUpdate()
|
|
||||||
{
|
|
||||||
using var _ = Utilities.ProfileScope("NetworkManager_OnClientNetworkUpdate");
|
|
||||||
|
|
||||||
while (client.PopEvent(out NetworkEvent networkEvent))
|
|
||||||
{
|
|
||||||
RecordMessage(ref networkEvent);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//IsLocalClient = server != null;
|
|
||||||
//IsClient = true;
|
|
||||||
OnClientReadMessage(ref networkEvent);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
//IsLocalClient = false;
|
|
||||||
//IsClient = false;
|
|
||||||
if (networkEvent.EventType == NetworkEventType.Message)
|
|
||||||
client.RecycleMessage(networkEvent.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnClientReadMessage(ref NetworkEvent networkEvent)
|
|
||||||
{
|
|
||||||
using var _ = Utilities.ProfileScope("NetworkManager_OnClientReadMessage");
|
|
||||||
|
|
||||||
switch (networkEvent.EventType)
|
|
||||||
{
|
|
||||||
case NetworkEventType.Connected:
|
|
||||||
{
|
|
||||||
LocalPlayerClientId = networkEvent.Sender.ConnectionId;
|
|
||||||
Console.Print("Connected to server, ConnectionId: " + networkEvent.Sender.ConnectionId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case NetworkEventType.Disconnected:
|
|
||||||
{
|
|
||||||
Console.Print("Disconnected from server, timeout.");
|
|
||||||
LocalPlayerClientId = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case NetworkEventType.Timeout:
|
|
||||||
{
|
|
||||||
Console.Print("Disconnected from server, connection closed.");
|
|
||||||
LocalPlayerClientId = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case NetworkEventType.Message:
|
|
||||||
{
|
|
||||||
OnNetworkMessage(ref networkEvent, CollectionsMarshal.AsSpan(OnClientMessageDelegates));
|
|
||||||
|
|
||||||
if (networkEvent.Message.Position > 0 &&
|
|
||||||
networkEvent.Message.Position < networkEvent.Message.Length)
|
|
||||||
{
|
|
||||||
string err =
|
|
||||||
$"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}.\n";
|
|
||||||
|
|
||||||
networkEvent.Message.Position = 0;
|
|
||||||
byte[] messageBytes = new byte[networkEvent.Message.Length];
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
fixed (byte* messageBytePtr = &messageBytes[0])
|
|
||||||
networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
//string messageBytesStr = string.Join(", ", messageBytes.Select(x => "0x" + ((int)x).ToString("X2")));
|
|
||||||
string messageBytesStr = string.Join("", messageBytes.Select(x => ((int)x).ToString("X2")));
|
|
||||||
|
|
||||||
Console.PrintError(err + $"Message dump: {messageBytesStr}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
DebugLastHandledMessage = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnClientActorSpawned(Actor actor)
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OnClientReadMessage(ref NetworkEvent networkEvent)
|
||||||
|
{
|
||||||
|
using var _ = Utilities.ProfileScope("NetworkManager_OnClientReadMessage");
|
||||||
|
|
||||||
|
switch (networkEvent.EventType)
|
||||||
|
{
|
||||||
|
case NetworkEventType.Connected:
|
||||||
|
{
|
||||||
|
LocalPlayerClientId = networkEvent.Sender.ConnectionId;
|
||||||
|
Console.Print("Connected to server, ConnectionId: " + networkEvent.Sender.ConnectionId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NetworkEventType.Disconnected:
|
||||||
|
{
|
||||||
|
Console.Print("Disconnected from server, timeout.");
|
||||||
|
LocalPlayerClientId = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NetworkEventType.Timeout:
|
||||||
|
{
|
||||||
|
Console.Print("Disconnected from server, connection closed.");
|
||||||
|
LocalPlayerClientId = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NetworkEventType.Message:
|
||||||
|
{
|
||||||
|
OnNetworkMessage(ref networkEvent, CollectionsMarshal.AsSpan(OnClientMessageDelegates));
|
||||||
|
|
||||||
|
if (networkEvent.Message.Position > 0 &&
|
||||||
|
networkEvent.Message.Position < networkEvent.Message.Length)
|
||||||
|
{
|
||||||
|
string err =
|
||||||
|
$"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}.\n";
|
||||||
|
|
||||||
|
networkEvent.Message.Position = 0;
|
||||||
|
byte[] messageBytes = new byte[networkEvent.Message.Length];
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (byte* messageBytePtr = &messageBytes[0])
|
||||||
|
networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
//string messageBytesStr = string.Join(", ", messageBytes.Select(x => "0x" + ((int)x).ToString("X2")));
|
||||||
|
string messageBytesStr = string.Join("", messageBytes.Select(x => ((int)x).ToString("X2")));
|
||||||
|
|
||||||
|
Console.PrintError(err + $"Message dump: {messageBytesStr}");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugLastHandledMessage = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnClientActorSpawned(Actor actor)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,165 +11,195 @@ using FlaxEngine.Networking;
|
|||||||
using Console = Game.Console;
|
using Console = Game.Console;
|
||||||
using Object = FlaxEngine.Object;
|
using Object = FlaxEngine.Object;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public static unsafe partial class NetworkManager
|
||||||
{
|
{
|
||||||
public static unsafe partial class NetworkManager
|
public const byte DemoVer = 2;
|
||||||
|
private struct DemoNetworkEventHeader
|
||||||
|
{
|
||||||
|
public NetworkEventType EventType;
|
||||||
|
public uint SenderId;
|
||||||
|
public uint MessageId;
|
||||||
|
public uint Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<NetworkEvent> buffer;
|
||||||
|
private static IEnumerator<NetworkEvent> bufferEnumerable;
|
||||||
|
private static GZipStream demoStream; // record
|
||||||
|
private static FileStream demoFileStream;
|
||||||
|
|
||||||
|
public static bool IsRecording => demoStream != null;
|
||||||
|
private static long flushedFrames = 0;
|
||||||
|
|
||||||
|
public static bool IsDemoPlaying => bufferEnumerable != null;
|
||||||
|
|
||||||
|
public static bool PlayDemo(string demoName)
|
||||||
{
|
{
|
||||||
public const byte DemoVer = 2;
|
|
||||||
private struct DemoNetworkEventHeader
|
|
||||||
{
|
{
|
||||||
public NetworkEventType EventType;
|
Cleanup();
|
||||||
public uint SenderId;
|
clientWorldStateManager = new WorldStateManager(isServer: true);
|
||||||
public uint MessageId;
|
|
||||||
public uint Length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<NetworkEvent> buffer;
|
if (!ReadDemo(demoName))
|
||||||
private static IEnumerator<NetworkEvent> bufferEnumerable;
|
|
||||||
private static GZipStream demoStream; // record
|
|
||||||
private static FileStream demoFileStream;
|
|
||||||
|
|
||||||
public static bool IsRecording => demoStream != null;
|
|
||||||
private static long flushedFrames = 0;
|
|
||||||
|
|
||||||
public static bool IsDemoPlaying => bufferEnumerable != null;
|
|
||||||
|
|
||||||
public static bool PlayDemo(string demoName)
|
|
||||||
{
|
{
|
||||||
{
|
Console.Print("Failed to read demo.");
|
||||||
Cleanup();
|
return false;
|
||||||
clientWorldStateManager = new WorldStateManager(isServer: true);
|
}
|
||||||
}
|
|
||||||
|
/*if (client == null)
|
||||||
|
{
|
||||||
|
Console.Print("Failed to create NetworkPeer.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!client.Connect())
|
||||||
|
{
|
||||||
|
Console.Print("Failed to connect to the server.");
|
||||||
|
return false;
|
||||||
|
}*/
|
||||||
|
|
||||||
if (!ReadDemo(demoName))
|
Scripting.FixedUpdate += OnDemoUpdate;
|
||||||
|
//if (!listenServer)
|
||||||
|
{
|
||||||
|
Scripting.Exit += Cleanup;
|
||||||
|
//Level.ActorSpawned += OnClientActorSpawned;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RecordDemo(string demoPath)
|
||||||
|
{
|
||||||
|
if (IsRecording)
|
||||||
|
StopRecording();
|
||||||
|
|
||||||
|
buffer = new List<NetworkEvent>();
|
||||||
|
var demoFolder = Directory.GetParent(demoPath);
|
||||||
|
if (!demoFolder.Exists)
|
||||||
|
Directory.CreateDirectory(demoFolder.FullName);
|
||||||
|
|
||||||
|
demoFileStream = File.Open(demoPath, FileMode.Create, FileAccess.Write);
|
||||||
|
demoStream = new GZipStream(demoFileStream, CompressionMode.Compress);
|
||||||
|
demoStream.WriteByte(DemoVer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RecordMessage(ref NetworkEvent networkEvent)
|
||||||
|
{
|
||||||
|
if (!IsRecording)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NetworkEvent recordedEvent = networkEvent;
|
||||||
|
recordedEvent.Message.Buffer = (byte*)NativeMemory.Alloc(recordedEvent.Message.Length);
|
||||||
|
recordedEvent.Sender.ConnectionId = networkEvent.Sender.ConnectionId;
|
||||||
|
|
||||||
|
NativeMemory.Copy(networkEvent.Message.Buffer, recordedEvent.Message.Buffer, recordedEvent.Message.Length);
|
||||||
|
buffer.Add(recordedEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FlushDemo()
|
||||||
|
{
|
||||||
|
if (!IsRecording)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<DemoNetworkEventHeader>()];
|
||||||
|
foreach (ref NetworkEvent networkEvent in CollectionsMarshal.AsSpan(buffer))
|
||||||
|
{
|
||||||
|
DemoNetworkEventHeader header = new DemoNetworkEventHeader()
|
||||||
{
|
{
|
||||||
Console.Print("Failed to read demo.");
|
EventType = networkEvent.EventType,
|
||||||
return false;
|
SenderId = networkEvent.Sender.ConnectionId,
|
||||||
}
|
Length = networkEvent.Message.Length,
|
||||||
|
MessageId = networkEvent.Message.MessageId,
|
||||||
|
};
|
||||||
|
MemoryMarshal.Write(bytes, header);
|
||||||
|
demoStream.Write(bytes);
|
||||||
|
|
||||||
|
Span<byte> messageBytes = new Span<byte>(networkEvent.Message.Buffer, (int)networkEvent.Message.Length);
|
||||||
|
demoStream.Write(messageBytes);
|
||||||
|
|
||||||
/*if (client == null)
|
// TODO: free networkEvent buffer
|
||||||
{
|
|
||||||
Console.Print("Failed to create NetworkPeer.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!client.Connect())
|
|
||||||
{
|
|
||||||
Console.Print("Failed to connect to the server.");
|
|
||||||
return false;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
Scripting.FixedUpdate += OnDemoUpdate;
|
|
||||||
//if (!listenServer)
|
|
||||||
{
|
|
||||||
Scripting.Exit += Cleanup;
|
|
||||||
//Level.ActorSpawned += OnClientActorSpawned;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RecordDemo(string demoPath)
|
sw.Stop();
|
||||||
|
|
||||||
|
flushedFrames += buffer.Count;
|
||||||
|
buffer.Clear();
|
||||||
|
|
||||||
|
FlaxEngine.Debug.Write(LogType.Info, $"Wrote demo in {sw.Elapsed.TotalMilliseconds}ms, frames: {flushedFrames}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StopRecording()
|
||||||
|
{
|
||||||
|
if (!IsRecording)
|
||||||
|
return;
|
||||||
|
|
||||||
|
FlushDemo();
|
||||||
|
demoStream.Close();
|
||||||
|
demoStream = null;
|
||||||
|
demoFileStream.Close();
|
||||||
|
demoFileStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe bool ReadDemo(string demoPath)
|
||||||
|
{
|
||||||
|
if (!File.Exists(demoPath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
buffer = new List<NetworkEvent>();
|
||||||
|
|
||||||
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
using FileStream fileStream = File.OpenRead(demoPath);
|
||||||
|
using GZipStream stream = new GZipStream(fileStream, CompressionMode.Decompress);
|
||||||
|
|
||||||
|
int ver = stream.ReadByte();
|
||||||
|
if (ver != DemoVer)
|
||||||
{
|
{
|
||||||
if (IsRecording)
|
Console.Print($"Demo version mismatch, expected {DemoVer}, got {ver}");
|
||||||
StopRecording();
|
stream.Close();
|
||||||
|
return false;
|
||||||
buffer = new List<NetworkEvent>();
|
|
||||||
var demoFolder = Directory.GetParent(demoPath);
|
|
||||||
if (!demoFolder.Exists)
|
|
||||||
Directory.CreateDirectory(demoFolder.FullName);
|
|
||||||
|
|
||||||
demoFileStream = File.Open(demoPath, FileMode.Create, FileAccess.Write);
|
|
||||||
demoStream = new GZipStream(demoFileStream, CompressionMode.Compress);
|
|
||||||
demoStream.WriteByte(DemoVer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RecordMessage(ref NetworkEvent networkEvent)
|
int headerSize = Unsafe.SizeOf<DemoNetworkEventHeader>();
|
||||||
|
Span<byte> headerBuffer = stackalloc byte[headerSize];
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
if (!IsRecording)
|
|
||||||
return;
|
|
||||||
|
|
||||||
NetworkEvent recordedEvent = networkEvent;
|
|
||||||
recordedEvent.Message.Buffer = (byte*)NativeMemory.Alloc(recordedEvent.Message.Length);
|
|
||||||
recordedEvent.Sender.ConnectionId = networkEvent.Sender.ConnectionId;
|
|
||||||
|
|
||||||
NativeMemory.Copy(networkEvent.Message.Buffer, recordedEvent.Message.Buffer, recordedEvent.Message.Length);
|
|
||||||
buffer.Add(recordedEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void FlushDemo()
|
|
||||||
{
|
|
||||||
if (!IsRecording)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<DemoNetworkEventHeader>()];
|
|
||||||
foreach (ref NetworkEvent networkEvent in CollectionsMarshal.AsSpan(buffer))
|
|
||||||
{
|
{
|
||||||
DemoNetworkEventHeader header = new DemoNetworkEventHeader()
|
int bytesLeftInBuffer = headerSize;
|
||||||
|
do
|
||||||
{
|
{
|
||||||
EventType = networkEvent.EventType,
|
int readBytes = stream.Read(headerBuffer.Slice(headerSize - bytesLeftInBuffer, bytesLeftInBuffer));
|
||||||
SenderId = networkEvent.Sender.ConnectionId,
|
if (readBytes == 0)
|
||||||
Length = networkEvent.Message.Length,
|
break;
|
||||||
MessageId = networkEvent.Message.MessageId,
|
bytesLeftInBuffer -= readBytes;
|
||||||
};
|
} while (bytesLeftInBuffer > 0);
|
||||||
MemoryMarshal.Write(bytes, header);
|
|
||||||
demoStream.Write(bytes);
|
|
||||||
|
|
||||||
Span<byte> messageBytes = new Span<byte>(networkEvent.Message.Buffer, (int)networkEvent.Message.Length);
|
if (bytesLeftInBuffer > 0)
|
||||||
demoStream.Write(messageBytes);
|
break; // EOF;
|
||||||
|
|
||||||
// TODO: free networkEvent buffer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sw.Stop();
|
DemoNetworkEventHeader header = MemoryMarshal.Read<DemoNetworkEventHeader>(headerBuffer);
|
||||||
|
|
||||||
flushedFrames += buffer.Count;
|
|
||||||
buffer.Clear();
|
|
||||||
|
|
||||||
FlaxEngine.Debug.Write(LogType.Info, $"Wrote demo in {sw.Elapsed.TotalMilliseconds}ms, frames: {flushedFrames}");
|
//buffer.Add(new NetworkEvent());
|
||||||
}
|
//ref NetworkEvent networkEvent = ref buffer[buffer.Length]; // collectionmarshal
|
||||||
|
NetworkEvent networkEvent = new NetworkEvent();
|
||||||
|
networkEvent.EventType = header.EventType;
|
||||||
|
networkEvent.Message.Position = 0;
|
||||||
|
networkEvent.Message.MessageId = header.MessageId;
|
||||||
|
networkEvent.Message.Length = networkEvent.Message.BufferSize = header.Length;
|
||||||
|
networkEvent.Sender.ConnectionId = header.SenderId;
|
||||||
|
|
||||||
public static void StopRecording()
|
if (header.Length > 0)
|
||||||
{
|
|
||||||
if (!IsRecording)
|
|
||||||
return;
|
|
||||||
|
|
||||||
FlushDemo();
|
|
||||||
demoStream.Close();
|
|
||||||
demoStream = null;
|
|
||||||
demoFileStream.Close();
|
|
||||||
demoFileStream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static unsafe bool ReadDemo(string demoPath)
|
|
||||||
{
|
|
||||||
if (!File.Exists(demoPath))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
buffer = new List<NetworkEvent>();
|
|
||||||
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
using FileStream fileStream = File.OpenRead(demoPath);
|
|
||||||
using GZipStream stream = new GZipStream(fileStream, CompressionMode.Decompress);
|
|
||||||
|
|
||||||
int ver = stream.ReadByte();
|
|
||||||
if (ver != DemoVer)
|
|
||||||
{
|
{
|
||||||
Console.Print($"Demo version mismatch, expected {DemoVer}, got {ver}");
|
networkEvent.Message.Buffer = (byte*)NativeMemory.Alloc(header.Length);
|
||||||
stream.Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int headerSize = Unsafe.SizeOf<DemoNetworkEventHeader>();
|
Span<byte> messageBufferSpan = new Span<byte>(networkEvent.Message.Buffer, (int)networkEvent.Message.BufferSize);
|
||||||
Span<byte> headerBuffer = stackalloc byte[headerSize];
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
{
|
{
|
||||||
int bytesLeftInBuffer = headerSize;
|
int bytesLeftInBuffer = (int)header.Length;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
int readBytes = stream.Read(headerBuffer.Slice(headerSize - bytesLeftInBuffer, bytesLeftInBuffer));
|
int readBytes = stream.Read(messageBufferSpan.Slice((int)header.Length - bytesLeftInBuffer, bytesLeftInBuffer));
|
||||||
if (readBytes == 0)
|
if (readBytes == 0)
|
||||||
break;
|
break;
|
||||||
bytesLeftInBuffer -= readBytes;
|
bytesLeftInBuffer -= readBytes;
|
||||||
@@ -178,101 +208,70 @@ namespace Game
|
|||||||
if (bytesLeftInBuffer > 0)
|
if (bytesLeftInBuffer > 0)
|
||||||
break; // EOF;
|
break; // EOF;
|
||||||
}
|
}
|
||||||
|
|
||||||
DemoNetworkEventHeader header = MemoryMarshal.Read<DemoNetworkEventHeader>(headerBuffer);
|
|
||||||
|
|
||||||
//buffer.Add(new NetworkEvent());
|
|
||||||
//ref NetworkEvent networkEvent = ref buffer[buffer.Length]; // collectionmarshal
|
|
||||||
NetworkEvent networkEvent = new NetworkEvent();
|
|
||||||
networkEvent.EventType = header.EventType;
|
|
||||||
networkEvent.Message.Position = 0;
|
|
||||||
networkEvent.Message.MessageId = header.MessageId;
|
|
||||||
networkEvent.Message.Length = networkEvent.Message.BufferSize = header.Length;
|
|
||||||
networkEvent.Sender.ConnectionId = header.SenderId;
|
|
||||||
|
|
||||||
if (header.Length > 0)
|
|
||||||
{
|
|
||||||
networkEvent.Message.Buffer = (byte*)NativeMemory.Alloc(header.Length);
|
|
||||||
|
|
||||||
Span<byte> messageBufferSpan = new Span<byte>(networkEvent.Message.Buffer, (int)networkEvent.Message.BufferSize);
|
|
||||||
{
|
|
||||||
int bytesLeftInBuffer = (int)header.Length;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
int readBytes = stream.Read(messageBufferSpan.Slice((int)header.Length - bytesLeftInBuffer, bytesLeftInBuffer));
|
|
||||||
if (readBytes == 0)
|
|
||||||
break;
|
|
||||||
bytesLeftInBuffer -= readBytes;
|
|
||||||
} while (bytesLeftInBuffer > 0);
|
|
||||||
|
|
||||||
if (bytesLeftInBuffer > 0)
|
|
||||||
break; // EOF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.Add(networkEvent);
|
|
||||||
}
|
}
|
||||||
|
buffer.Add(networkEvent);
|
||||||
sw.Stop();
|
|
||||||
|
|
||||||
bufferEnumerable = buffer.GetEnumerator();
|
|
||||||
|
|
||||||
Console.Print($"Demo parse time {sw.Elapsed.TotalMilliseconds}ms, frames: {buffer.Count} ");
|
|
||||||
|
|
||||||
DemoEndFrame(); // advances to first frame
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DemoEndFrame()
|
sw.Stop();
|
||||||
|
|
||||||
|
bufferEnumerable = buffer.GetEnumerator();
|
||||||
|
|
||||||
|
Console.Print($"Demo parse time {sw.Elapsed.TotalMilliseconds}ms, frames: {buffer.Count} ");
|
||||||
|
|
||||||
|
DemoEndFrame(); // advances to first frame
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DemoEndFrame()
|
||||||
|
{
|
||||||
|
// TODO: check if the current state frame matches the current frame number before advancing
|
||||||
|
|
||||||
|
/*asdf++;
|
||||||
|
if (asdf < 8)
|
||||||
|
return;*/
|
||||||
|
|
||||||
|
if (bufferEnumerable == null || !bufferEnumerable.MoveNext())
|
||||||
{
|
{
|
||||||
// TODO: check if the current state frame matches the current frame number before advancing
|
if (buffer.Any())
|
||||||
|
|
||||||
/*asdf++;
|
|
||||||
if (asdf < 8)
|
|
||||||
return;*/
|
|
||||||
|
|
||||||
if (bufferEnumerable == null || !bufferEnumerable.MoveNext())
|
|
||||||
{
|
{
|
||||||
if (buffer.Any())
|
bufferEnumerable.Dispose();
|
||||||
{
|
bufferEnumerable = null;
|
||||||
bufferEnumerable.Dispose();
|
buffer.Clear();
|
||||||
bufferEnumerable = null;
|
Console.Print("Demo ended");
|
||||||
buffer.Clear();
|
|
||||||
Console.Print("Demo ended");
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//var actorState = currentState.actor;
|
return;
|
||||||
//currentState.input = bufferEnumerable.Current;
|
|
||||||
//frame++;
|
|
||||||
//currentState.actor = actorState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnDemoUpdate()
|
//var actorState = currentState.actor;
|
||||||
|
//currentState.input = bufferEnumerable.Current;
|
||||||
|
//frame++;
|
||||||
|
//currentState.actor = actorState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDemoUpdate()
|
||||||
|
{
|
||||||
|
if (!IsDemoPlaying)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using Utilities.ScopeProfiler _ = Utilities.ProfileScope("NetworkManager_OnDemoUpdate");
|
||||||
|
|
||||||
|
NetworkEvent demoEvent = bufferEnumerable.Current; // ref?
|
||||||
|
|
||||||
|
// TODO: change/randomize Sender.ConnectionId?
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (!IsDemoPlaying)
|
//IsLocalClient = server != null;
|
||||||
return;
|
//IsClient = true;
|
||||||
|
OnClientReadMessage(ref demoEvent);
|
||||||
using Utilities.ScopeProfiler _ = Utilities.ProfileScope("NetworkManager_OnDemoUpdate");
|
|
||||||
|
|
||||||
NetworkEvent demoEvent = bufferEnumerable.Current; // ref?
|
|
||||||
|
|
||||||
// TODO: change/randomize Sender.ConnectionId?
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//IsLocalClient = server != null;
|
|
||||||
//IsClient = true;
|
|
||||||
OnClientReadMessage(ref demoEvent);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
//IsLocalClient = false;
|
|
||||||
//IsClient = false;
|
|
||||||
//TODO: recycle event?
|
|
||||||
}
|
|
||||||
|
|
||||||
DemoEndFrame();
|
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
//IsLocalClient = false;
|
||||||
|
//IsClient = false;
|
||||||
|
//TODO: recycle event?
|
||||||
|
}
|
||||||
|
|
||||||
|
DemoEndFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,206 +8,205 @@ using FlaxEngine.Networking;
|
|||||||
using Console = Game.Console;
|
using Console = Game.Console;
|
||||||
using Object = FlaxEngine.Object;
|
using Object = FlaxEngine.Object;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public static partial class NetworkManager
|
||||||
{
|
{
|
||||||
public static partial class NetworkManager
|
private static List<NetworkConnection> ConnectedClients;
|
||||||
|
|
||||||
|
private static List<Type> NetworkedTypes;
|
||||||
|
|
||||||
|
public static INetworkDriver ServerNetworkDriver { get; set; }
|
||||||
|
|
||||||
|
public static WorldStateManager serverWorldStateManager = null;
|
||||||
|
|
||||||
|
public static bool StartServer(bool listenServer = true)
|
||||||
{
|
{
|
||||||
private static List<NetworkConnection> ConnectedClients;
|
Cleanup();
|
||||||
|
ConnectedClients = new List<NetworkConnection>(MaximumClients);
|
||||||
|
|
||||||
private static List<Type> NetworkedTypes;
|
|
||||||
|
|
||||||
public static INetworkDriver ServerNetworkDriver { get; set; }
|
//var driver = Object.New(typeof(ENetDriver));
|
||||||
|
//ServerNetworkDriver = null;
|
||||||
|
NetworkLagDriver driver = Object.New<NetworkLagDriver>();
|
||||||
|
driver.Lag = 200f;
|
||||||
|
ServerNetworkDriver = driver;
|
||||||
|
|
||||||
public static WorldStateManager serverWorldStateManager = null;
|
server = NetworkPeer.CreatePeer(new NetworkConfig
|
||||||
|
|
||||||
public static bool StartServer(bool listenServer = true)
|
|
||||||
{
|
{
|
||||||
Cleanup();
|
NetworkDriver = driver,
|
||||||
ConnectedClients = new List<NetworkConnection>(MaximumClients);
|
ConnectionsLimit = MaximumClients,
|
||||||
|
MessagePoolSize = 2048,
|
||||||
|
MessageSize = MTU,
|
||||||
|
Address = "any",
|
||||||
|
Port = ServerPort
|
||||||
|
});
|
||||||
|
if (!server.Listen())
|
||||||
|
{
|
||||||
|
Console.PrintError("Failed to start the server.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scripting.FixedUpdate += OnServerNetworkUpdate;
|
||||||
|
Scripting.Exit += Cleanup;
|
||||||
|
Level.ActorSpawned += OnServerActorSpawned;
|
||||||
|
|
||||||
//var driver = Object.New(typeof(ENetDriver));
|
NetworkedTypes = new List<Type>();
|
||||||
//ServerNetworkDriver = null;
|
|
||||||
NetworkLagDriver driver = Object.New<NetworkLagDriver>();
|
|
||||||
driver.Lag = 200f;
|
|
||||||
ServerNetworkDriver = driver;
|
|
||||||
|
|
||||||
server = NetworkPeer.CreatePeer(new NetworkConfig
|
|
||||||
{
|
|
||||||
NetworkDriver = driver,
|
|
||||||
ConnectionsLimit = MaximumClients,
|
|
||||||
MessagePoolSize = 2048,
|
|
||||||
MessageSize = MTU,
|
|
||||||
Address = "any",
|
|
||||||
Port = ServerPort
|
|
||||||
});
|
|
||||||
if (!server.Listen())
|
|
||||||
{
|
|
||||||
Console.PrintError("Failed to start the server.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Scripting.FixedUpdate += OnServerNetworkUpdate;
|
|
||||||
Scripting.Exit += Cleanup;
|
|
||||||
Level.ActorSpawned += OnServerActorSpawned;
|
|
||||||
|
|
||||||
NetworkedTypes = new List<Type>();
|
|
||||||
|
|
||||||
#if false
|
#if false
|
||||||
var assemblies = Utils.GetAssemblies();
|
var assemblies = Utils.GetAssemblies();
|
||||||
|
|
||||||
foreach (Assembly assembly in assemblies)
|
foreach (Assembly assembly in assemblies)
|
||||||
{
|
{
|
||||||
// Skip common assemblies
|
// Skip common assemblies
|
||||||
string assemblyName = assembly.GetName().Name;
|
string assemblyName = assembly.GetName().Name;
|
||||||
if (assemblyName == "System" ||
|
if (assemblyName == "System" ||
|
||||||
assemblyName.StartsWith("System.") ||
|
assemblyName.StartsWith("System.") ||
|
||||||
assemblyName.StartsWith("Mono.") ||
|
assemblyName.StartsWith("Mono.") ||
|
||||||
assemblyName == "mscorlib" ||
|
assemblyName == "mscorlib" ||
|
||||||
assemblyName == "Newtonsoft.Json" ||
|
assemblyName == "Newtonsoft.Json" ||
|
||||||
assemblyName == "Snippets" ||
|
assemblyName == "Snippets" ||
|
||||||
assemblyName == "netstandard" ||
|
assemblyName == "netstandard" ||
|
||||||
assemblyName == "Anonymously Hosted DynamicMethods Assembly" ||
|
assemblyName == "Anonymously Hosted DynamicMethods Assembly" ||
|
||||||
assemblyName.StartsWith("FlaxEngine.") ||
|
assemblyName.StartsWith("FlaxEngine.") ||
|
||||||
assemblyName.StartsWith("JetBrains.") ||
|
assemblyName.StartsWith("JetBrains.") ||
|
||||||
assemblyName.StartsWith("Microsoft.") ||
|
assemblyName.StartsWith("Microsoft.") ||
|
||||||
assemblyName.StartsWith("nunit."))
|
assemblyName.StartsWith("nunit."))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (Type type in assembly.GetTypes())
|
foreach (Type type in assembly.GetTypes())
|
||||||
if (type.GetCustomAttributes().Any(x => x is NetworkedAttribute))
|
if (type.GetCustomAttributes().Any(x => x is NetworkedAttribute))
|
||||||
NetworkedTypes.Add(type);
|
NetworkedTypes.Add(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Type type in NetworkedTypes)
|
foreach (Type type in NetworkedTypes)
|
||||||
Console.Print("tracking networked type: " + type.Name);
|
Console.Print("tracking networked type: " + type.Name);
|
||||||
#endif
|
#endif
|
||||||
serverWorldStateManager = new WorldStateManager(isServer: true);
|
serverWorldStateManager = new WorldStateManager(isServer: true);
|
||||||
//WorldStateManager.Init();
|
//WorldStateManager.Init();
|
||||||
|
|
||||||
if (listenServer)
|
if (listenServer)
|
||||||
return ConnectServer(listenServer: true);
|
return ConnectServer(listenServer: true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NetworkMessage ServerBeginSendMessage()
|
public static NetworkMessage ServerBeginSendMessage()
|
||||||
|
{
|
||||||
|
NetworkMessage message = server.BeginSendMessage();
|
||||||
|
message.WriteByte((byte)NetworkMessageType.Message);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ServerEndSendMessage(ref NetworkMessage message, NetworkConnection connection, NetworkChannelType channelType = NetworkChannelType.Reliable)
|
||||||
|
{
|
||||||
|
server.EndSendMessage(channelType, message, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ServerEndSendMessage(ref NetworkMessage message, NetworkConnection[] connections, NetworkChannelType channelType = NetworkChannelType.Reliable)
|
||||||
|
{
|
||||||
|
server.EndSendMessage(channelType, message, connections);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NetworkMessage ClientBeginSendMessage()
|
||||||
|
{
|
||||||
|
NetworkMessage message = client.BeginSendMessage();
|
||||||
|
message.WriteByte((byte)NetworkMessageType.Message);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ClientEndSendMessage(ref NetworkMessage message, NetworkChannelType channelType = NetworkChannelType.Reliable)
|
||||||
|
{
|
||||||
|
client.EndSendMessage(channelType, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnServerNetworkUpdate()
|
||||||
|
{
|
||||||
|
using var _ = Utilities.ProfileScope("NetworkManager_OnServerNetworkUpdate");
|
||||||
|
|
||||||
|
while (server.PopEvent(out NetworkEvent networkEvent))
|
||||||
|
OnServerReadMessage(ref networkEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnServerReadMessage(ref NetworkEvent networkEvent)
|
||||||
|
{
|
||||||
|
using var _ = Utilities.ProfileScope("NetworkManager_OnServerReadMessage");
|
||||||
|
|
||||||
|
switch (networkEvent.EventType)
|
||||||
{
|
{
|
||||||
NetworkMessage message = server.BeginSendMessage();
|
case NetworkEventType.Connected:
|
||||||
message.WriteByte((byte)NetworkMessageType.Message);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ServerEndSendMessage(ref NetworkMessage message, NetworkConnection connection, NetworkChannelType channelType = NetworkChannelType.Reliable)
|
|
||||||
{
|
|
||||||
server.EndSendMessage(channelType, message, connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ServerEndSendMessage(ref NetworkMessage message, NetworkConnection[] connections, NetworkChannelType channelType = NetworkChannelType.Reliable)
|
|
||||||
{
|
|
||||||
server.EndSendMessage(channelType, message, connections);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NetworkMessage ClientBeginSendMessage()
|
|
||||||
{
|
|
||||||
NetworkMessage message = client.BeginSendMessage();
|
|
||||||
message.WriteByte((byte)NetworkMessageType.Message);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ClientEndSendMessage(ref NetworkMessage message, NetworkChannelType channelType = NetworkChannelType.Reliable)
|
|
||||||
{
|
|
||||||
client.EndSendMessage(channelType, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnServerNetworkUpdate()
|
|
||||||
{
|
|
||||||
using var _ = Utilities.ProfileScope("NetworkManager_OnServerNetworkUpdate");
|
|
||||||
|
|
||||||
while (server.PopEvent(out NetworkEvent networkEvent))
|
|
||||||
OnServerReadMessage(ref networkEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnServerReadMessage(ref NetworkEvent networkEvent)
|
|
||||||
{
|
|
||||||
using var _ = Utilities.ProfileScope("NetworkManager_OnServerReadMessage");
|
|
||||||
|
|
||||||
switch (networkEvent.EventType)
|
|
||||||
{
|
{
|
||||||
case NetworkEventType.Connected:
|
Console.Print($"Client({networkEvent.Sender.ConnectionId}) is trying to connect");
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) is trying to connect");
|
//IsServer = true;
|
||||||
try
|
if (serverWorldStateManager.OnClientConnecting(networkEvent.Sender))
|
||||||
{
|
{
|
||||||
//IsServer = true;
|
ConnectedClients.Add(networkEvent.Sender);
|
||||||
if (serverWorldStateManager.OnClientConnecting(networkEvent.Sender))
|
Console.Print(
|
||||||
{
|
$"Client({networkEvent.Sender.ConnectionId}) connected. Total clients: {ConnectedClients.Count}");
|
||||||
ConnectedClients.Add(networkEvent.Sender);
|
|
||||||
Console.Print(
|
|
||||||
$"Client({networkEvent.Sender.ConnectionId}) connected. Total clients: {ConnectedClients.Count}");
|
|
||||||
|
|
||||||
serverWorldStateManager.OnClientConnected(networkEvent.Sender);
|
serverWorldStateManager.OnClientConnected(networkEvent.Sender);
|
||||||
}
|
|
||||||
else
|
|
||||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) connection refused");
|
|
||||||
}
|
}
|
||||||
finally
|
else
|
||||||
{
|
Console.Print($"Client({networkEvent.Sender.ConnectionId}) connection refused");
|
||||||
//IsServer = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case NetworkEventType.Disconnected:
|
finally
|
||||||
case NetworkEventType.Timeout:
|
|
||||||
{
|
{
|
||||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) disconnected!");
|
//IsServer = false;
|
||||||
|
|
||||||
ConnectedClients.Remove(networkEvent.Sender);
|
|
||||||
Console.Print("Connected clients: " + ConnectedClients.Count);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case NetworkEventType.Message:
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//IsServer = true;
|
|
||||||
OnNetworkMessage(ref networkEvent, CollectionsMarshal.AsSpan(OnServerMessageDelegates));
|
|
||||||
|
|
||||||
if (networkEvent.Message.Position > 0 &&
|
break;
|
||||||
networkEvent.Message.Position < networkEvent.Message.Length)
|
|
||||||
{
|
|
||||||
string err =
|
|
||||||
$"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}.";
|
|
||||||
|
|
||||||
networkEvent.Message.Position = 0;
|
|
||||||
byte[] messageBytes = new byte[networkEvent.Message.Length];
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
fixed (byte* messageBytePtr = &messageBytes[0])
|
|
||||||
networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
string messageBytesStr = string.Join(", ",
|
|
||||||
messageBytes.Select(x => "0x" + ((int)x).ToString("X2")));
|
|
||||||
|
|
||||||
Console.PrintError(err + $"Message dump: {messageBytesStr}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
//IsServer = false;
|
|
||||||
server.RecycleMessage(networkEvent.Message);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
}
|
||||||
}
|
case NetworkEventType.Disconnected:
|
||||||
|
case NetworkEventType.Timeout:
|
||||||
|
{
|
||||||
|
Console.Print($"Client({networkEvent.Sender.ConnectionId}) disconnected!");
|
||||||
|
|
||||||
private static void OnServerActorSpawned(Actor actor)
|
ConnectedClients.Remove(networkEvent.Sender);
|
||||||
{
|
Console.Print("Connected clients: " + ConnectedClients.Count);
|
||||||
//Console.Print($"actor spawned: {actor.Name} ({actor.TypeName})");
|
break;
|
||||||
|
}
|
||||||
|
case NetworkEventType.Message:
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//IsServer = true;
|
||||||
|
OnNetworkMessage(ref networkEvent, CollectionsMarshal.AsSpan(OnServerMessageDelegates));
|
||||||
|
|
||||||
|
if (networkEvent.Message.Position > 0 &&
|
||||||
|
networkEvent.Message.Position < networkEvent.Message.Length)
|
||||||
|
{
|
||||||
|
string err =
|
||||||
|
$"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}.";
|
||||||
|
|
||||||
|
networkEvent.Message.Position = 0;
|
||||||
|
byte[] messageBytes = new byte[networkEvent.Message.Length];
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (byte* messageBytePtr = &messageBytes[0])
|
||||||
|
networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
string messageBytesStr = string.Join(", ",
|
||||||
|
messageBytes.Select(x => "0x" + ((int)x).ToString("X2")));
|
||||||
|
|
||||||
|
Console.PrintError(err + $"Message dump: {messageBytesStr}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
//IsServer = false;
|
||||||
|
server.RecycleMessage(networkEvent.Message);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OnServerActorSpawned(Actor actor)
|
||||||
|
{
|
||||||
|
//Console.Print($"actor spawned: {actor.Name} ({actor.TypeName})");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -3,23 +3,22 @@ using System.Diagnostics;
|
|||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
{
|
|
||||||
public class CrosshairWidget : Script
|
public class CrosshairWidget : Script
|
||||||
|
{
|
||||||
|
private Image control;
|
||||||
|
|
||||||
|
public override void OnAwake()
|
||||||
{
|
{
|
||||||
private Image control;
|
var uiControl = Actor.As<UIControl>();
|
||||||
|
//control = new Image();
|
||||||
|
//uiControl.Control = control;
|
||||||
|
|
||||||
public override void OnAwake()
|
control = (Image)uiControl.Control;
|
||||||
{
|
}
|
||||||
var uiControl = Actor.As<UIControl>();
|
|
||||||
//control = new Image();
|
|
||||||
//uiControl.Control = control;
|
|
||||||
|
|
||||||
control = (Image)uiControl.Control;
|
public override void OnUpdate()
|
||||||
}
|
{
|
||||||
|
|
||||||
public override void OnUpdate()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,71 +3,71 @@ using System.Diagnostics;
|
|||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
[ExecuteInEditMode]
|
||||||
|
public class PerformanceWidget : Script
|
||||||
{
|
{
|
||||||
[ExecuteInEditMode]
|
private const double updateInterval = 0.2;
|
||||||
public class PerformanceWidget : Script
|
|
||||||
|
public UIControl control;
|
||||||
|
private Label label;
|
||||||
|
|
||||||
|
private Stopwatch stopwatch;
|
||||||
|
private Stopwatch stopwatch2;
|
||||||
|
|
||||||
|
private double updateTimeAvg;
|
||||||
|
private double updateTimeAvg2;
|
||||||
|
private ulong updateTimeCount;
|
||||||
|
private ulong updateTimeCount2;
|
||||||
|
|
||||||
|
public override void OnAwake()
|
||||||
{
|
{
|
||||||
private const double updateInterval = 0.2;
|
label = (Label)control.Control;
|
||||||
|
label.Text = $"0fps";
|
||||||
|
control.HideFlags = HideFlags.None;
|
||||||
|
|
||||||
public UIControl control;
|
stopwatch = Stopwatch.StartNew();
|
||||||
private Label label;
|
stopwatch2 = Stopwatch.StartNew();
|
||||||
|
}
|
||||||
|
|
||||||
private Stopwatch stopwatch;
|
public override void OnUpdate()
|
||||||
private Stopwatch stopwatch2;
|
{
|
||||||
|
updateTimeCount2++;
|
||||||
private double updateTimeAvg;
|
double elapsed2 = stopwatch2.Elapsed.TotalSeconds;
|
||||||
private double updateTimeAvg2;
|
if (elapsed2 >= updateInterval * 10)
|
||||||
private ulong updateTimeCount;
|
|
||||||
private ulong updateTimeCount2;
|
|
||||||
|
|
||||||
public override void OnAwake()
|
|
||||||
{
|
{
|
||||||
label = (Label)control.Control;
|
stopwatch2.Restart();
|
||||||
label.Text = $"0fps";
|
updateTimeAvg2 = elapsed2 / updateTimeCount2;
|
||||||
control.HideFlags = HideFlags.None;
|
updateTimeCount2 = 0;
|
||||||
|
|
||||||
stopwatch = Stopwatch.StartNew();
|
|
||||||
stopwatch2 = Stopwatch.StartNew();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnUpdate()
|
updateTimeCount++;
|
||||||
|
double elapsed = stopwatch.Elapsed.TotalSeconds;
|
||||||
|
if (elapsed >= updateInterval)
|
||||||
{
|
{
|
||||||
updateTimeCount2++;
|
stopwatch.Restart();
|
||||||
double elapsed2 = stopwatch2.Elapsed.TotalSeconds;
|
updateTimeAvg = elapsed / updateTimeCount;
|
||||||
if (elapsed2 >= updateInterval * 10)
|
updateTimeCount = 0;
|
||||||
{
|
|
||||||
stopwatch2.Restart();
|
|
||||||
updateTimeAvg2 = elapsed2 / updateTimeCount2;
|
|
||||||
updateTimeCount2 = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTimeCount++;
|
label.Text = "";
|
||||||
double elapsed = stopwatch.Elapsed.TotalSeconds;
|
|
||||||
if (elapsed >= updateInterval)
|
|
||||||
{
|
|
||||||
stopwatch.Restart();
|
|
||||||
updateTimeAvg = elapsed / updateTimeCount;
|
|
||||||
updateTimeCount = 0;
|
|
||||||
|
|
||||||
label.Text = "";
|
long triangles = 0;
|
||||||
|
long drawCalls = 0;
|
||||||
long triangles = 0;
|
|
||||||
long drawCalls = 0;
|
|
||||||
|
|
||||||
#if BUILD_DEBUG || BUILD_DEVELOPMENT
|
#if BUILD_DEBUG || BUILD_DEVELOPMENT
|
||||||
var gpuEvents = ProfilingTools.EventsGPU;
|
var gpuEvents = ProfilingTools.EventsGPU;
|
||||||
if (gpuEvents.Length > 0)
|
if (gpuEvents.Length > 0)
|
||||||
{
|
{
|
||||||
triangles = gpuEvents[0].Stats.Triangles;
|
triangles = gpuEvents[0].Stats.Triangles;
|
||||||
drawCalls = gpuEvents[0].Stats.DrawCalls;
|
drawCalls = gpuEvents[0].Stats.DrawCalls;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
label.Text += $"{triangles} tris\n {drawCalls} drawcalls\n";
|
label.Text += $"{triangles} tris\n {drawCalls} drawcalls\n";
|
||||||
label.Text += $"{(int)Math.Round(1.0f / updateTimeAvg2)}fps2\n";
|
label.Text += $"{(int)Math.Round(1.0f / updateTimeAvg2)}fps2\n";
|
||||||
label.Text += $"{(int)Math.Round(1.0f / updateTimeAvg)}fps";
|
label.Text += $"{(int)Math.Round(1.0f / updateTimeAvg)}fps";
|
||||||
}
|
}
|
||||||
|
|
||||||
#if false
|
#if false
|
||||||
#if BUILD_DEVELOPMENT
|
#if BUILD_DEVELOPMENT
|
||||||
@@ -98,6 +98,5 @@ namespace Game
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,18 +3,17 @@ using System.Diagnostics;
|
|||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public class SpeedWidget : Script
|
||||||
{
|
{
|
||||||
public class SpeedWidget : Script
|
public override void OnAwake()
|
||||||
{
|
{
|
||||||
public override void OnAwake()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnUpdate()
|
public override void OnUpdate()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of supported materials for loaded levels.
|
||||||
|
/// Maps the given texture/shader name to Flax Material/MaterialInstance.
|
||||||
|
/// </summary>
|
||||||
|
public class BrushMaterialList
|
||||||
{
|
{
|
||||||
/// <summary>
|
[EditorDisplay(name: "Material Assets")]
|
||||||
/// List of supported materials for loaded levels.
|
public BrushMaterialListEntry[] materialAssets;
|
||||||
/// Maps the given texture/shader name to Flax Material/MaterialInstance.
|
}
|
||||||
/// </summary>
|
|
||||||
public class BrushMaterialList
|
|
||||||
{
|
|
||||||
[EditorDisplay(name: "Material Assets")]
|
|
||||||
public BrushMaterialListEntry[] materialAssets;
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct BrushMaterialListEntry
|
public struct BrushMaterialListEntry
|
||||||
{
|
{
|
||||||
[EditorOrder(1)] [EditorDisplay(name: "Name")]
|
[EditorOrder(1)] [EditorDisplay(name: "Name")]
|
||||||
public string name;
|
public string name;
|
||||||
|
|
||||||
[EditorOrder(2)] [EditorDisplay(name: "Material")]
|
[EditorOrder(2)] [EditorDisplay(name: "Material")]
|
||||||
public MaterialBase asset;
|
public MaterialBase asset;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
[ExecuteInEditMode]
|
||||||
|
public class BrushScript : Script
|
||||||
{
|
{
|
||||||
[ExecuteInEditMode]
|
|
||||||
public class BrushScript : Script
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,17 @@
|
|||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEditor;
|
using FlaxEditor;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
{
|
|
||||||
[ExecuteInEditMode]
|
|
||||||
public class LevelScript : Script
|
|
||||||
{
|
|
||||||
public string MapName;
|
|
||||||
public DateTime MapTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LevelScript2 : Script
|
[ExecuteInEditMode]
|
||||||
{
|
public class LevelScript : Script
|
||||||
public string MapName;
|
{
|
||||||
public DateTime MapTimestamp;
|
public string MapName;
|
||||||
}
|
public DateTime MapTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LevelScript2 : Script
|
||||||
|
{
|
||||||
|
public string MapName;
|
||||||
|
public DateTime MapTimestamp;
|
||||||
}
|
}
|
||||||
@@ -7,523 +7,522 @@ using FlaxEngine;
|
|||||||
// https://web.archive.org/web/20160316213335/http://forums.ubergames.net/topic/2658-understanding-the-quake-3-map-format/
|
// https://web.archive.org/web/20160316213335/http://forums.ubergames.net/topic/2658-understanding-the-quake-3-map-format/
|
||||||
// https://web.archive.org/web/20210228125854/https://forums.thedarkmod.com/index.php?/topic/15668-plugin-request-save-map-in-quake-3-format/
|
// https://web.archive.org/web/20210228125854/https://forums.thedarkmod.com/index.php?/topic/15668-plugin-request-save-map-in-quake-3-format/
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public struct MapFacePlane
|
||||||
{
|
{
|
||||||
public struct MapFacePlane
|
public Float3 v1, v2, v3;
|
||||||
{
|
public Plane plane;
|
||||||
public Float3 v1, v2, v3;
|
public string texture;
|
||||||
public Plane plane;
|
public Float2 offset;
|
||||||
public string texture;
|
public float rotation;
|
||||||
public Float2 offset;
|
public Float2 scale;
|
||||||
public float rotation;
|
public int contentFlags, surfaceFlags, surfaceValue;
|
||||||
public Float2 scale;
|
}
|
||||||
public int contentFlags, surfaceFlags, surfaceValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MapBrush
|
public class MapBrush
|
||||||
{
|
{
|
||||||
public MapFacePlane[] planes;
|
public MapFacePlane[] planes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MapPatch
|
public class MapPatch
|
||||||
{
|
{
|
||||||
public string name;
|
public string name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct PatchVertex
|
public struct PatchVertex
|
||||||
{
|
{
|
||||||
public Float3 v;
|
public Float3 v;
|
||||||
public Float2 uv;
|
public Float2 uv;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MapEntity
|
public class MapEntity
|
||||||
{
|
{
|
||||||
public List<MapBrush> brushes = new List<MapBrush>();
|
public List<MapBrush> brushes = new List<MapBrush>();
|
||||||
public List<MapEntity> entities = new List<MapEntity>();
|
public List<MapEntity> entities = new List<MapEntity>();
|
||||||
public List<MapPatch> patches = new List<MapPatch>();
|
public List<MapPatch> patches = new List<MapPatch>();
|
||||||
public Dictionary<string, string> properties = new Dictionary<string, string>();
|
public Dictionary<string, string> properties = new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MapParser
|
public static class MapParser
|
||||||
|
{
|
||||||
|
public static MapEntity Parse(byte[] data)
|
||||||
{
|
{
|
||||||
public static MapEntity Parse(byte[] data)
|
if (data.Length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
MapEntity rootEntity = new MapEntity();
|
||||||
|
MapEntity currentEntity = rootEntity;
|
||||||
|
|
||||||
|
int level = 0;
|
||||||
|
int index = 0;
|
||||||
|
//for (int i=0; i<data.Length; i++)
|
||||||
|
do
|
||||||
{
|
{
|
||||||
if (data.Length == 0)
|
char c = (char)data[index];
|
||||||
return null;
|
char c1 = index + 1 < data.Length ? (char)data[index + 1] : (char)0;
|
||||||
|
|
||||||
MapEntity rootEntity = new MapEntity();
|
switch (c)
|
||||||
MapEntity currentEntity = rootEntity;
|
|
||||||
|
|
||||||
int level = 0;
|
|
||||||
int index = 0;
|
|
||||||
//for (int i=0; i<data.Length; i++)
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
char c = (char)data[index];
|
case '\n':
|
||||||
char c1 = index + 1 < data.Length ? (char)data[index + 1] : (char)0;
|
case '\r':
|
||||||
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case '\n':
|
|
||||||
case '\r':
|
|
||||||
break;
|
|
||||||
case '/':
|
|
||||||
if (c1 == '/')
|
|
||||||
ParseComment(data, ref index);
|
|
||||||
else
|
|
||||||
throw new Exception("unexpected character: '" + c + "'");
|
|
||||||
break;
|
|
||||||
case '{':
|
|
||||||
{
|
|
||||||
currentEntity = new MapEntity();
|
|
||||||
rootEntity.entities.Add(currentEntity);
|
|
||||||
|
|
||||||
level++;
|
|
||||||
|
|
||||||
for (; index < data.Length; index++)
|
|
||||||
if (data[index] == '\n')
|
|
||||||
break;
|
|
||||||
index++;
|
|
||||||
|
|
||||||
ParseEntity(currentEntity, data, ref index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '}':
|
|
||||||
{
|
|
||||||
//currentEntity = rootEntity;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new Exception("unsupported character: '" + c + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
//if (level < 0 || level > 2)
|
|
||||||
// throw new Exception("Failed to parse .map file: unexpected entity found at line " + lineNumber.ToString());
|
|
||||||
} while (++index < data.Length);
|
|
||||||
|
|
||||||
return rootEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ParseComment(byte[] data, ref int index)
|
|
||||||
{
|
|
||||||
for (; index < data.Length; index++)
|
|
||||||
if (data[index] == '\n')
|
|
||||||
break;
|
break;
|
||||||
}
|
case '/':
|
||||||
|
if (c1 == '/')
|
||||||
private static void ParseEntity(MapEntity currentEntity, byte[] data, ref int index)
|
ParseComment(data, ref index);
|
||||||
{
|
else
|
||||||
bool entityParsed = false;
|
throw new Exception("unexpected character: '" + c + "'");
|
||||||
do
|
break;
|
||||||
{
|
case '{':
|
||||||
char c = (char)data[index];
|
|
||||||
char c1 = index + 1 < data.Length ? (char)data[index + 1] : (char)0;
|
|
||||||
|
|
||||||
switch (c)
|
|
||||||
{
|
{
|
||||||
case '\n':
|
currentEntity = new MapEntity();
|
||||||
case '\r':
|
rootEntity.entities.Add(currentEntity);
|
||||||
break;
|
|
||||||
|
|
||||||
case '/':
|
level++;
|
||||||
if (c1 == '/')
|
|
||||||
ParseComment(data, ref index);
|
|
||||||
else
|
|
||||||
throw new Exception("unexpected character: '" + c + "'");
|
|
||||||
break;
|
|
||||||
|
|
||||||
// "name" "value"
|
for (; index < data.Length; index++)
|
||||||
case '"':
|
if (data[index] == '\n')
|
||||||
{
|
|
||||||
string propName = ParseQuotedString(data, ref index);
|
|
||||||
string propValue = ParseQuotedString(data, ref index);
|
|
||||||
|
|
||||||
if (currentEntity.properties.ContainsKey(propName))
|
|
||||||
throw new Exception("Failed to parse .map file: multiple properties defined for " +
|
|
||||||
propName +
|
|
||||||
" at line ?"); // + lineNumber.ToString());
|
|
||||||
currentEntity.properties.Add(propName, propValue);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// brush
|
|
||||||
case '{':
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
for (; index < data.Length; index++)
|
|
||||||
{
|
|
||||||
if (data[index] != ' ' && data[index] != '\r' && data[index] != '\n')
|
|
||||||
break;
|
|
||||||
//index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index >= data.Length)
|
|
||||||
break;
|
break;
|
||||||
|
index++;
|
||||||
|
|
||||||
if (data[index] == '(')
|
ParseEntity(currentEntity, data, ref index);
|
||||||
currentEntity.brushes.Add(ParseBrush(data, ref index));
|
break;
|
||||||
else if (char.IsLetter((char)data[index]) || char.IsNumber((char)data[index]))
|
|
||||||
currentEntity.patches.Add(ParsePatch(data, ref index));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '}':
|
|
||||||
{
|
|
||||||
entityParsed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Exception("unsupported character: '" + c + "'");
|
|
||||||
}
|
}
|
||||||
} while (index++ < data.Length && !entityParsed);
|
case '}':
|
||||||
}
|
|
||||||
|
|
||||||
private static string ParseQuotedString(byte[] data, ref int index)
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
index++;
|
|
||||||
|
|
||||||
for (; index < data.Length; index++)
|
|
||||||
{
|
|
||||||
if (data[index] == '"')
|
|
||||||
break;
|
|
||||||
sb.Append((char)data[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
|
|
||||||
while (index < data.Length)
|
|
||||||
{
|
|
||||||
if (data[index] != ' ' && data[index] != '\t')
|
|
||||||
break;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ParseString(byte[] data, ref int index)
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
for (; index < data.Length; index++)
|
|
||||||
{
|
|
||||||
if (data[index] == ' ' || data[index] == '\r' || data[index] == '\n')
|
|
||||||
break;
|
|
||||||
sb.Append((char)data[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
//if (data[index] == '\n')
|
|
||||||
// index++;
|
|
||||||
|
|
||||||
while (index < data.Length)
|
|
||||||
{
|
|
||||||
if (data[index] != ' ')
|
|
||||||
break;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float ParseFloat(byte[] data, ref int index)
|
|
||||||
{
|
|
||||||
string fs = ParseString(data, ref index);
|
|
||||||
|
|
||||||
if (float.TryParse(fs, NumberStyles.Float, CultureInfo.InvariantCulture, out float value))
|
|
||||||
return value;
|
|
||||||
//else if (float.TryParse(fs, CultureInfo.InvariantCulture, out intValue))
|
|
||||||
// return intValue;
|
|
||||||
throw new Exception("failed to ParseFloat: " + fs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int ParseInt(byte[] data, ref int index)
|
|
||||||
{
|
|
||||||
string fs = ParseString(data, ref index);
|
|
||||||
|
|
||||||
if (int.TryParse(fs, out int value))
|
|
||||||
return value;
|
|
||||||
throw new Exception("failed to ParseInt: " + fs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Float3 ParseFloat3(byte[] data, ref int index)
|
|
||||||
{
|
|
||||||
return new Float3(
|
|
||||||
ParseFloat(data, ref index),
|
|
||||||
ParseFloat(data, ref index),
|
|
||||||
ParseFloat(data, ref index)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Float2 ParseFloat2(byte[] data, ref int index)
|
|
||||||
{
|
|
||||||
return new Float2(
|
|
||||||
ParseFloat(data, ref index),
|
|
||||||
ParseFloat(data, ref index)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Float3 ParsePlaneFloat3(byte[] data, ref int index)
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
while (index < data.Length)
|
|
||||||
{
|
|
||||||
if (data[index] != ' ')
|
|
||||||
break;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
Float3 vector = ParseFloat3(data, ref index);
|
|
||||||
// rounding
|
|
||||||
/*float temp = vector.Z;
|
|
||||||
vector.Z = vector.Y;
|
|
||||||
vector.Y = temp;*/
|
|
||||||
/*vector.X = (float)Math.Round(vector.X, 1);
|
|
||||||
vector.Y = (float)Math.Round(vector.Y, 1);
|
|
||||||
vector.Z = (float)Math.Round(vector.Z, 1);*/
|
|
||||||
|
|
||||||
while (index < data.Length)
|
|
||||||
{
|
|
||||||
if (data[index] == ')')
|
|
||||||
break;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
while (index < data.Length)
|
|
||||||
{
|
|
||||||
if (data[index] != ' ')
|
|
||||||
break;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return vector;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MapBrush ParseBrush(byte[] data, ref int index)
|
|
||||||
{
|
|
||||||
MapBrush brush = new MapBrush();
|
|
||||||
|
|
||||||
var planes = new List<MapFacePlane>(6);
|
|
||||||
|
|
||||||
bool brushParsed = false;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
char c = (char)data[index];
|
|
||||||
//char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0;
|
|
||||||
|
|
||||||
switch (c)
|
|
||||||
{
|
{
|
||||||
case '\r':
|
//currentEntity = rootEntity;
|
||||||
case '\n':
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Exception("unsupported character: '" + c + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (level < 0 || level > 2)
|
||||||
|
// throw new Exception("Failed to parse .map file: unexpected entity found at line " + lineNumber.ToString());
|
||||||
|
} while (++index < data.Length);
|
||||||
|
|
||||||
|
return rootEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ParseComment(byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
for (; index < data.Length; index++)
|
||||||
|
if (data[index] == '\n')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ParseEntity(MapEntity currentEntity, byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
bool entityParsed = false;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
char c = (char)data[index];
|
||||||
|
char c1 = index + 1 < data.Length ? (char)data[index + 1] : (char)0;
|
||||||
|
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '\n':
|
||||||
|
case '\r':
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
if (c1 == '/')
|
||||||
|
ParseComment(data, ref index);
|
||||||
|
else
|
||||||
|
throw new Exception("unexpected character: '" + c + "'");
|
||||||
|
break;
|
||||||
|
|
||||||
|
// "name" "value"
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
string propName = ParseQuotedString(data, ref index);
|
||||||
|
string propValue = ParseQuotedString(data, ref index);
|
||||||
|
|
||||||
|
if (currentEntity.properties.ContainsKey(propName))
|
||||||
|
throw new Exception("Failed to parse .map file: multiple properties defined for " +
|
||||||
|
propName +
|
||||||
|
" at line ?"); // + lineNumber.ToString());
|
||||||
|
currentEntity.properties.Add(propName, propValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// brush
|
||||||
|
case '{':
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
for (; index < data.Length; index++)
|
||||||
|
{
|
||||||
|
if (data[index] != ' ' && data[index] != '\r' && data[index] != '\n')
|
||||||
|
break;
|
||||||
|
//index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= data.Length)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// brush face (quake format):
|
if (data[index] == '(')
|
||||||
// ( <v1> ) ( <v2> ) ( <v3> ) <shader> <x_shift> <y_shift> <rotation> <x_scale> <y_scale>
|
currentEntity.brushes.Add(ParseBrush(data, ref index));
|
||||||
|
else if (char.IsLetter((char)data[index]) || char.IsNumber((char)data[index]))
|
||||||
|
currentEntity.patches.Add(ParsePatch(data, ref index));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '}':
|
||||||
|
{
|
||||||
|
entityParsed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// brush face (quake3 format):
|
default:
|
||||||
// ( <v1> ) ( <v2> ) ( <v3> ) <shader> <x_shift> <y_shift> <rotation> <x_scale> <y_scale> <content_flags> <surface_flags> <value>
|
throw new Exception("unsupported character: '" + c + "'");
|
||||||
|
}
|
||||||
|
} while (index++ < data.Length && !entityParsed);
|
||||||
|
}
|
||||||
|
|
||||||
// brush face (valve format):
|
private static string ParseQuotedString(byte[] data, ref int index)
|
||||||
// ( <v1> ) ( <v2> ) ( <v3> ) <shader> [ <tx1> <ty1> <tz1> <toffs1> ] [ <tx2> <ty2> <tz2> <toffs2> ] <rotation> <x_scale> <y_scale>
|
{
|
||||||
case '(':
|
StringBuilder sb = new StringBuilder();
|
||||||
|
index++;
|
||||||
|
|
||||||
|
for (; index < data.Length; index++)
|
||||||
|
{
|
||||||
|
if (data[index] == '"')
|
||||||
|
break;
|
||||||
|
sb.Append((char)data[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
|
||||||
|
while (index < data.Length)
|
||||||
|
{
|
||||||
|
if (data[index] != ' ' && data[index] != '\t')
|
||||||
|
break;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ParseString(byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (; index < data.Length; index++)
|
||||||
|
{
|
||||||
|
if (data[index] == ' ' || data[index] == '\r' || data[index] == '\n')
|
||||||
|
break;
|
||||||
|
sb.Append((char)data[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (data[index] == '\n')
|
||||||
|
// index++;
|
||||||
|
|
||||||
|
while (index < data.Length)
|
||||||
|
{
|
||||||
|
if (data[index] != ' ')
|
||||||
|
break;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float ParseFloat(byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
string fs = ParseString(data, ref index);
|
||||||
|
|
||||||
|
if (float.TryParse(fs, NumberStyles.Float, CultureInfo.InvariantCulture, out float value))
|
||||||
|
return value;
|
||||||
|
//else if (float.TryParse(fs, CultureInfo.InvariantCulture, out intValue))
|
||||||
|
// return intValue;
|
||||||
|
throw new Exception("failed to ParseFloat: " + fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ParseInt(byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
string fs = ParseString(data, ref index);
|
||||||
|
|
||||||
|
if (int.TryParse(fs, out int value))
|
||||||
|
return value;
|
||||||
|
throw new Exception("failed to ParseInt: " + fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Float3 ParseFloat3(byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
return new Float3(
|
||||||
|
ParseFloat(data, ref index),
|
||||||
|
ParseFloat(data, ref index),
|
||||||
|
ParseFloat(data, ref index)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Float2 ParseFloat2(byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
return new Float2(
|
||||||
|
ParseFloat(data, ref index),
|
||||||
|
ParseFloat(data, ref index)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Float3 ParsePlaneFloat3(byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
while (index < data.Length)
|
||||||
|
{
|
||||||
|
if (data[index] != ' ')
|
||||||
|
break;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Float3 vector = ParseFloat3(data, ref index);
|
||||||
|
// rounding
|
||||||
|
/*float temp = vector.Z;
|
||||||
|
vector.Z = vector.Y;
|
||||||
|
vector.Y = temp;*/
|
||||||
|
/*vector.X = (float)Math.Round(vector.X, 1);
|
||||||
|
vector.Y = (float)Math.Round(vector.Y, 1);
|
||||||
|
vector.Z = (float)Math.Round(vector.Z, 1);*/
|
||||||
|
|
||||||
|
while (index < data.Length)
|
||||||
|
{
|
||||||
|
if (data[index] == ')')
|
||||||
|
break;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
while (index < data.Length)
|
||||||
|
{
|
||||||
|
if (data[index] != ' ')
|
||||||
|
break;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MapBrush ParseBrush(byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
MapBrush brush = new MapBrush();
|
||||||
|
|
||||||
|
var planes = new List<MapFacePlane>(6);
|
||||||
|
|
||||||
|
bool brushParsed = false;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
char c = (char)data[index];
|
||||||
|
//char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0;
|
||||||
|
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
break;
|
||||||
|
|
||||||
|
// brush face (quake format):
|
||||||
|
// ( <v1> ) ( <v2> ) ( <v3> ) <shader> <x_shift> <y_shift> <rotation> <x_scale> <y_scale>
|
||||||
|
|
||||||
|
// brush face (quake3 format):
|
||||||
|
// ( <v1> ) ( <v2> ) ( <v3> ) <shader> <x_shift> <y_shift> <rotation> <x_scale> <y_scale> <content_flags> <surface_flags> <value>
|
||||||
|
|
||||||
|
// brush face (valve format):
|
||||||
|
// ( <v1> ) ( <v2> ) ( <v3> ) <shader> [ <tx1> <ty1> <tz1> <toffs1> ] [ <tx2> <ty2> <tz2> <toffs2> ] <rotation> <x_scale> <y_scale>
|
||||||
|
case '(':
|
||||||
|
{
|
||||||
|
MapFacePlane plane = new MapFacePlane();
|
||||||
|
plane.v1 = ParsePlaneFloat3(data, ref index);
|
||||||
|
plane.v2 = ParsePlaneFloat3(data, ref index);
|
||||||
|
plane.v3 = ParsePlaneFloat3(data, ref index);
|
||||||
|
plane.texture = ParseString(data, ref index);
|
||||||
|
|
||||||
|
if (true) // quake or quake3 format
|
||||||
{
|
{
|
||||||
MapFacePlane plane = new MapFacePlane();
|
plane.offset = ParseFloat2(data, ref index);
|
||||||
plane.v1 = ParsePlaneFloat3(data, ref index);
|
plane.rotation = ParseFloat(data, ref index);
|
||||||
plane.v2 = ParsePlaneFloat3(data, ref index);
|
plane.scale = ParseFloat2(data, ref index);
|
||||||
plane.v3 = ParsePlaneFloat3(data, ref index);
|
|
||||||
plane.texture = ParseString(data, ref index);
|
|
||||||
|
|
||||||
if (true) // quake or quake3 format
|
char peekChar = (char)data[index];
|
||||||
|
if (peekChar != '\n') // quake3 format
|
||||||
{
|
{
|
||||||
plane.offset = ParseFloat2(data, ref index);
|
plane.contentFlags = ParseInt(data, ref index);
|
||||||
plane.rotation = ParseFloat(data, ref index);
|
plane.surfaceFlags = ParseInt(data, ref index);
|
||||||
plane.scale = ParseFloat2(data, ref index);
|
plane.surfaceValue = ParseInt(data, ref index);
|
||||||
|
|
||||||
char peekChar = (char)data[index];
|
|
||||||
if (peekChar != '\n') // quake3 format
|
|
||||||
{
|
|
||||||
plane.contentFlags = ParseInt(data, ref index);
|
|
||||||
plane.surfaceFlags = ParseInt(data, ref index);
|
|
||||||
plane.surfaceValue = ParseInt(data, ref index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flip Y and Z
|
|
||||||
plane.v1 = new Float3(plane.v1.X, plane.v1.Z, plane.v1.Y);
|
|
||||||
plane.v2 = new Float3(plane.v2.X, plane.v2.Z, plane.v2.Y);
|
|
||||||
plane.v3 = new Float3(plane.v3.X, plane.v3.Z, plane.v3.Y);
|
|
||||||
|
|
||||||
plane.plane = new Plane(plane.v1, plane.v2, plane.v3);
|
|
||||||
|
|
||||||
planes.Add(plane);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case '}':
|
// Flip Y and Z
|
||||||
{
|
plane.v1 = new Float3(plane.v1.X, plane.v1.Z, plane.v1.Y);
|
||||||
brushParsed = true;
|
plane.v2 = new Float3(plane.v2.X, plane.v2.Z, plane.v2.Y);
|
||||||
break;
|
plane.v3 = new Float3(plane.v3.X, plane.v3.Z, plane.v3.Y);
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
plane.plane = new Plane(plane.v1, plane.v2, plane.v3);
|
||||||
if (char.IsLetter(c) || char.IsNumber(c))
|
|
||||||
{
|
|
||||||
// patch name
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("unsupported character: '" + c + "'");
|
planes.Add(plane);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} while (index++ < data.Length && !brushParsed);
|
|
||||||
|
|
||||||
brush.planes = planes.ToArray();
|
case '}':
|
||||||
|
{
|
||||||
|
brushParsed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return brush;
|
default:
|
||||||
|
if (char.IsLetter(c) || char.IsNumber(c))
|
||||||
|
{
|
||||||
|
// patch name
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("unsupported character: '" + c + "'");
|
||||||
|
}
|
||||||
|
} while (index++ < data.Length && !brushParsed);
|
||||||
|
|
||||||
|
brush.planes = planes.ToArray();
|
||||||
|
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MapPatch ParsePatch(byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
MapPatch patch = new MapPatch();
|
||||||
|
|
||||||
|
patch.name = ParseString(data, ref index);
|
||||||
|
|
||||||
|
bool patchParsed = false;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
char c = (char)data[index];
|
||||||
|
//char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0;
|
||||||
|
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
ParsePatchInner(patch, data, ref index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '}':
|
||||||
|
{
|
||||||
|
patchParsed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception("unsupported character: '" + c + "'");
|
||||||
|
}
|
||||||
|
} while (index++ < data.Length && !patchParsed);
|
||||||
|
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unfinished and untested
|
||||||
|
private static void ParsePatchInner(MapPatch patch, byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
string shaderName = ParseString(data, ref index);
|
||||||
|
|
||||||
|
while (index < data.Length)
|
||||||
|
{
|
||||||
|
if (data[index] == '(')
|
||||||
|
break;
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MapPatch ParsePatch(byte[] data, ref int index)
|
index++;
|
||||||
|
|
||||||
|
int width = ParseInt(data, ref index);
|
||||||
|
int height = ParseInt(data, ref index);
|
||||||
|
int dummy1 = ParseInt(data, ref index);
|
||||||
|
int dummy2 = ParseInt(data, ref index);
|
||||||
|
int dummy3 = ParseInt(data, ref index);
|
||||||
|
|
||||||
|
while (index < data.Length)
|
||||||
{
|
{
|
||||||
MapPatch patch = new MapPatch();
|
if (data[index] == ')')
|
||||||
|
break;
|
||||||
patch.name = ParseString(data, ref index);
|
index++;
|
||||||
|
|
||||||
bool patchParsed = false;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
char c = (char)data[index];
|
|
||||||
//char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0;
|
|
||||||
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case '\r':
|
|
||||||
case '\n':
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '{':
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
ParsePatchInner(patch, data, ref index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '}':
|
|
||||||
{
|
|
||||||
patchParsed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Exception("unsupported character: '" + c + "'");
|
|
||||||
}
|
|
||||||
} while (index++ < data.Length && !patchParsed);
|
|
||||||
|
|
||||||
return patch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// unfinished and untested
|
index++;
|
||||||
private static void ParsePatchInner(MapPatch patch, byte[] data, ref int index)
|
|
||||||
|
while (index < data.Length)
|
||||||
{
|
{
|
||||||
string shaderName = ParseString(data, ref index);
|
if (data[index] == '(')
|
||||||
|
break;
|
||||||
while (index < data.Length)
|
|
||||||
{
|
|
||||||
if (data[index] == '(')
|
|
||||||
break;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
int width = ParseInt(data, ref index);
|
index++;
|
||||||
int height = ParseInt(data, ref index);
|
|
||||||
int dummy1 = ParseInt(data, ref index);
|
|
||||||
int dummy2 = ParseInt(data, ref index);
|
|
||||||
int dummy3 = ParseInt(data, ref index);
|
|
||||||
|
|
||||||
while (index < data.Length)
|
var vertices = new PatchVertex[width * height];
|
||||||
|
|
||||||
|
bool verticesParsed = false;
|
||||||
|
int vertexIndex = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
char c = (char)data[index];
|
||||||
|
//char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0;
|
||||||
|
|
||||||
|
switch (c)
|
||||||
{
|
{
|
||||||
if (data[index] == ')')
|
case '\r':
|
||||||
|
case '\n':
|
||||||
break;
|
break;
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
case '(':
|
||||||
|
|
||||||
while (index < data.Length)
|
|
||||||
{
|
|
||||||
if (data[index] == '(')
|
|
||||||
break;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
|
|
||||||
var vertices = new PatchVertex[width * height];
|
|
||||||
|
|
||||||
bool verticesParsed = false;
|
|
||||||
int vertexIndex = 0;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
char c = (char)data[index];
|
|
||||||
//char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0;
|
|
||||||
|
|
||||||
switch (c)
|
|
||||||
{
|
{
|
||||||
case '\r':
|
index++;
|
||||||
case '\n':
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '(':
|
for (int iw = 0; iw < width; iw++)
|
||||||
{
|
{
|
||||||
index++;
|
while (index < data.Length)
|
||||||
|
|
||||||
for (int iw = 0; iw < width; iw++)
|
|
||||||
{
|
{
|
||||||
while (index < data.Length)
|
if (data[index] == '(')
|
||||||
{
|
break;
|
||||||
if (data[index] == '(')
|
|
||||||
break;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
|
|
||||||
while (index < data.Length)
|
|
||||||
{
|
|
||||||
if (data[index] != ' ')
|
|
||||||
break;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
vertices[vertexIndex].v = ParseFloat3(data, ref index);
|
|
||||||
vertices[vertexIndex].uv =
|
|
||||||
new Float2(ParseFloat(data, ref index), ParseFloat(data, ref index));
|
|
||||||
vertexIndex++;
|
|
||||||
|
|
||||||
while (index < data.Length)
|
|
||||||
{
|
|
||||||
if (data[index] == ')')
|
|
||||||
break;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
|
||||||
break;
|
while (index < data.Length)
|
||||||
|
{
|
||||||
|
if (data[index] != ' ')
|
||||||
|
break;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices[vertexIndex].v = ParseFloat3(data, ref index);
|
||||||
|
vertices[vertexIndex].uv =
|
||||||
|
new Float2(ParseFloat(data, ref index), ParseFloat(data, ref index));
|
||||||
|
vertexIndex++;
|
||||||
|
|
||||||
|
while (index < data.Length)
|
||||||
|
{
|
||||||
|
if (data[index] == ')')
|
||||||
|
break;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
case '}':
|
|
||||||
{
|
|
||||||
verticesParsed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
break;
|
||||||
throw new Exception("unsupported character: '" + c + "'");
|
|
||||||
}
|
}
|
||||||
} while (index++ < data.Length && !verticesParsed);
|
|
||||||
}
|
case '}':
|
||||||
|
{
|
||||||
|
verticesParsed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception("unsupported character: '" + c + "'");
|
||||||
|
}
|
||||||
|
} while (index++ < data.Length && !verticesParsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -51,114 +51,114 @@ namespace Game;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ConvexHullCalculator
|
public class ConvexHullCalculator
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constant representing a point that has yet to be assigned to a
|
/// Constant representing a point that has yet to be assigned to a
|
||||||
/// face. It's only used immediately after constructing the seed hull.
|
/// face. It's only used immediately after constructing the seed hull.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int UNASSIGNED = -2;
|
private const int UNASSIGNED = -2;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constant representing a point that is inside the convex hull, and
|
/// Constant representing a point that is inside the convex hull, and
|
||||||
/// thus is behind all faces. In the openSet array, all points with
|
/// thus is behind all faces. In the openSet array, all points with
|
||||||
/// INSIDE are at the end of the array, with indexes larger
|
/// INSIDE are at the end of the array, with indexes larger
|
||||||
/// openSetTail.
|
/// openSetTail.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int INSIDE = -1;
|
private const int INSIDE = -1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Epsilon value. If the coordinates of the point space are
|
/// Epsilon value. If the coordinates of the point space are
|
||||||
/// exceptionally close to each other, this value might need to be
|
/// exceptionally close to each other, this value might need to be
|
||||||
/// adjusted.
|
/// adjusted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const float EPSILON = 0.001f;
|
private const float EPSILON = 0.001f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When adding a new face to the faces Dictionary, use this for the
|
/// When adding a new face to the faces Dictionary, use this for the
|
||||||
/// key and then increment it.
|
/// key and then increment it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int faceCount;
|
private int faceCount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dictionary storing the faces of the currently generated convex
|
/// A dictionary storing the faces of the currently generated convex
|
||||||
/// hull. The key is the id of the face, used in the Face, PointFace
|
/// hull. The key is the id of the face, used in the Face, PointFace
|
||||||
/// and HorizonEdge struct.
|
/// and HorizonEdge struct.
|
||||||
/// This is a Dictionary, because we need both random access to it,
|
/// This is a Dictionary, because we need both random access to it,
|
||||||
/// the ability to loop through it, and ability to quickly delete
|
/// the ability to loop through it, and ability to quickly delete
|
||||||
/// faces (in the ConstructCone method), and Dictionary is the obvious
|
/// faces (in the ConstructCone method), and Dictionary is the obvious
|
||||||
/// candidate that can do all of those things.
|
/// candidate that can do all of those things.
|
||||||
/// I'm wondering if using a Dictionary is best idea, though. It might
|
/// I'm wondering if using a Dictionary is best idea, though. It might
|
||||||
/// be better to just have them in a <![CDATA[List<Face>]]> and mark a face as
|
/// be better to just have them in a <![CDATA[List<Face>]]> and mark a face as
|
||||||
/// deleted by adding a field to the Face struct. The downside is that
|
/// deleted by adding a field to the Face struct. The downside is that
|
||||||
/// we would need an extra field in the Face struct, and when we're
|
/// we would need an extra field in the Face struct, and when we're
|
||||||
/// looping through the points in openSet, we would have to loop
|
/// looping through the points in openSet, we would have to loop
|
||||||
/// through all the Faces EVER created in the algorithm, and skip the
|
/// through all the Faces EVER created in the algorithm, and skip the
|
||||||
/// ones that have been marked as deleted. However, looping through a
|
/// ones that have been marked as deleted. However, looping through a
|
||||||
/// list is fairly fast, and it might be worth it to avoid Dictionary
|
/// list is fairly fast, and it might be worth it to avoid Dictionary
|
||||||
/// overhead.
|
/// overhead.
|
||||||
/// TODO test converting to a <![CDATA[List<Face>]]> instead.
|
/// TODO test converting to a <![CDATA[List<Face>]]> instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Dictionary<int, Face> faces;
|
private Dictionary<int, Face> faces;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current horizon. Generated by the FindHorizon() DFS search,
|
/// The current horizon. Generated by the FindHorizon() DFS search,
|
||||||
/// and used in ConstructCone to construct new faces. The list of
|
/// and used in ConstructCone to construct new faces. The list of
|
||||||
/// edges are in CCW order.
|
/// edges are in CCW order.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<HorizonEdge> horizon;
|
private List<HorizonEdge> horizon;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If SplitVerts is false, this Dictionary is used to keep track of
|
/// If SplitVerts is false, this Dictionary is used to keep track of
|
||||||
/// which points we've added to the final mesh.
|
/// which points we've added to the final mesh.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Dictionary<int, int> hullVerts;
|
private Dictionary<int, int> hullVerts;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set of faces which are "lit" by the current point in the set. This
|
/// Set of faces which are "lit" by the current point in the set. This
|
||||||
/// is used in the FindHorizon() DFS search to keep track of which
|
/// is used in the FindHorizon() DFS search to keep track of which
|
||||||
/// faces we've already visited, and in the ReassignPoints() method to
|
/// faces we've already visited, and in the ReassignPoints() method to
|
||||||
/// know which points need to be reassigned.
|
/// know which points need to be reassigned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private HashSet<int> litFaces;
|
private HashSet<int> litFaces;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The set of points to be processed. "openSet" is a misleading name,
|
/// The set of points to be processed. "openSet" is a misleading name,
|
||||||
/// because it's both the open set (points which are still outside the
|
/// because it's both the open set (points which are still outside the
|
||||||
/// convex hull) and the closed set (points that are inside the convex
|
/// convex hull) and the closed set (points that are inside the convex
|
||||||
/// hull). The first part of the array (with <![CDATA[indexes <= openSetTail]]>)
|
/// hull). The first part of the array (with <![CDATA[indexes <= openSetTail]]>)
|
||||||
/// is the openSet, the last part of the array (with <![CDATA[indexes >
|
/// is the openSet, the last part of the array (with <![CDATA[indexes >
|
||||||
/// openSetTail]]>) are the closed set, with
|
/// openSetTail]]>) are the closed set, with
|
||||||
/// Face set to INSIDE. The
|
/// Face set to INSIDE. The
|
||||||
/// closed set is largely irrelevant to the algorithm, the open set is
|
/// closed set is largely irrelevant to the algorithm, the open set is
|
||||||
/// what matters.
|
/// what matters.
|
||||||
/// Storing the entire open set in one big list has a downside: when
|
/// Storing the entire open set in one big list has a downside: when
|
||||||
/// we're reassigning points after ConstructCone, we only need to
|
/// we're reassigning points after ConstructCone, we only need to
|
||||||
/// reassign points that belong to the faces that have been removed,
|
/// reassign points that belong to the faces that have been removed,
|
||||||
/// but storing it in one array, we have to loop through the entire
|
/// but storing it in one array, we have to loop through the entire
|
||||||
/// list, and checking litFaces to determine which we can skip and
|
/// list, and checking litFaces to determine which we can skip and
|
||||||
/// which need to be reassigned.
|
/// which need to be reassigned.
|
||||||
/// The alternative here is to give each face in Face array it's own
|
/// The alternative here is to give each face in Face array it's own
|
||||||
/// openSet. I don't like that solution, because then you have to
|
/// openSet. I don't like that solution, because then you have to
|
||||||
/// juggle so many more heap-allocated <![CDATA[List<T>'s]]>, we'd have to use
|
/// juggle so many more heap-allocated <![CDATA[List<T>'s]]>, we'd have to use
|
||||||
/// object pools and such. It would do a lot more allocation, and it
|
/// object pools and such. It would do a lot more allocation, and it
|
||||||
/// would have worse locality. I should maybe test that solution, but
|
/// would have worse locality. I should maybe test that solution, but
|
||||||
/// it probably wont be faster enough (if at all) to justify the extra
|
/// it probably wont be faster enough (if at all) to justify the extra
|
||||||
/// allocations.
|
/// allocations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<PointFace> openSet;
|
private List<PointFace> openSet;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The "tail" of the openSet, the last index of a vertex that has
|
/// The "tail" of the openSet, the last index of a vertex that has
|
||||||
/// been assigned to a face.
|
/// been assigned to a face.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int openSetTail = -1;
|
private int openSetTail = -1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate a convex hull from points in points array, and store the
|
/// Generate a convex hull from points in points array, and store the
|
||||||
/// mesh in Unity-friendly format in verts and tris. If splitVerts is
|
/// mesh in Unity-friendly format in verts and tris. If splitVerts is
|
||||||
/// true, the the verts will be split, if false, the same vert will be
|
/// true, the the verts will be split, if false, the same vert will be
|
||||||
/// used for more than one triangle.
|
/// used for more than one triangle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void GenerateHull(
|
public void GenerateHull(
|
||||||
List<Float3> points,
|
List<Float3> points,
|
||||||
bool splitVerts,
|
bool splitVerts,
|
||||||
ref List<Float3> verts,
|
ref List<Float3> verts,
|
||||||
@@ -179,11 +179,11 @@ public class ConvexHullCalculator
|
|||||||
//VerifyMesh(points, ref verts, ref tris);
|
//VerifyMesh(points, ref verts, ref tris);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Make sure all the buffers and variables needed for the algorithm
|
/// Make sure all the buffers and variables needed for the algorithm
|
||||||
/// are initialized.
|
/// are initialized.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void Initialize(List<Float3> points, bool splitVerts)
|
private void Initialize(List<Float3> points, bool splitVerts)
|
||||||
{
|
{
|
||||||
faceCount = 0;
|
faceCount = 0;
|
||||||
openSetTail = -1;
|
openSetTail = -1;
|
||||||
@@ -225,10 +225,10 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create initial seed hull.
|
/// Create initial seed hull.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void GenerateInitialHull(List<Float3> points)
|
private void GenerateInitialHull(List<Float3> points)
|
||||||
{
|
{
|
||||||
// Find points suitable for use as the seed hull. Some varieties of
|
// Find points suitable for use as the seed hull. Some varieties of
|
||||||
// this algorithm pick extreme points here, but I'm not convinced
|
// this algorithm pick extreme points here, but I'm not convinced
|
||||||
@@ -356,11 +356,11 @@ public class ConvexHullCalculator
|
|||||||
VerifyOpenSet(points);
|
VerifyOpenSet(points);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Find four points in the point cloud that are not coplanar for the
|
/// Find four points in the point cloud that are not coplanar for the
|
||||||
/// seed hull
|
/// seed hull
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void FindInitialHullIndices(List<Float3> points, out int b0, out int b1, out int b2, out int b3)
|
private void FindInitialHullIndices(List<Float3> points, out int b0, out int b1, out int b2, out int b3)
|
||||||
{
|
{
|
||||||
int count = points.Count;
|
int count = points.Count;
|
||||||
|
|
||||||
@@ -399,12 +399,12 @@ public class ConvexHullCalculator
|
|||||||
throw new ArgumentException("Can't generate hull, points are coplanar");
|
throw new ArgumentException("Can't generate hull, points are coplanar");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Grow the hull. This method takes the current hull, and expands it
|
/// Grow the hull. This method takes the current hull, and expands it
|
||||||
/// to encompass the point in openSet with the point furthest away
|
/// to encompass the point in openSet with the point furthest away
|
||||||
/// from its face.
|
/// from its face.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void GrowHull(List<Float3> points)
|
private void GrowHull(List<Float3> points)
|
||||||
{
|
{
|
||||||
Assert.IsTrue(openSetTail >= 0);
|
Assert.IsTrue(openSetTail >= 0);
|
||||||
Assert.IsTrue(openSet[0].Face != INSIDE);
|
Assert.IsTrue(openSet[0].Face != INSIDE);
|
||||||
@@ -439,21 +439,21 @@ public class ConvexHullCalculator
|
|||||||
ReassignPoints(points);
|
ReassignPoints(points);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start the search for the horizon.
|
/// Start the search for the horizon.
|
||||||
/// The search is a DFS search that searches neighboring triangles in
|
/// The search is a DFS search that searches neighboring triangles in
|
||||||
/// a counter-clockwise fashion. When it find a neighbor which is not
|
/// a counter-clockwise fashion. When it find a neighbor which is not
|
||||||
/// lit, that edge will be a line on the horizon. If the search always
|
/// lit, that edge will be a line on the horizon. If the search always
|
||||||
/// proceeds counter-clockwise, the edges of the horizon will be found
|
/// proceeds counter-clockwise, the edges of the horizon will be found
|
||||||
/// in counter-clockwise order.
|
/// in counter-clockwise order.
|
||||||
/// The heart of the search can be found in the recursive
|
/// The heart of the search can be found in the recursive
|
||||||
/// SearchHorizon() method, but the the first iteration of the search
|
/// SearchHorizon() method, but the the first iteration of the search
|
||||||
/// is special, because it has to visit three neighbors (all the
|
/// is special, because it has to visit three neighbors (all the
|
||||||
/// neighbors of the initial triangle), while the rest of the search
|
/// neighbors of the initial triangle), while the rest of the search
|
||||||
/// only has to visit two (because one of them has already been
|
/// only has to visit two (because one of them has already been
|
||||||
/// visited, the one you came from).
|
/// visited, the one you came from).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void FindHorizon(List<Float3> points, Float3 point, int fi, Face face)
|
private void FindHorizon(List<Float3> points, Float3 point, int fi, Face face)
|
||||||
{
|
{
|
||||||
// TODO should I use epsilon in the PointFaceDistance comparisons?
|
// TODO should I use epsilon in the PointFaceDistance comparisons?
|
||||||
|
|
||||||
@@ -529,10 +529,10 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recursively search to find the horizon or lit set.
|
/// Recursively search to find the horizon or lit set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SearchHorizon(List<Float3> points, Float3 point, int prevFaceIndex, int faceCount, Face face)
|
private void SearchHorizon(List<Float3> points, Float3 point, int prevFaceIndex, int faceCount, Face face)
|
||||||
{
|
{
|
||||||
Assert.IsTrue(prevFaceIndex >= 0);
|
Assert.IsTrue(prevFaceIndex >= 0);
|
||||||
Assert.IsTrue(litFaces.Contains(prevFaceIndex));
|
Assert.IsTrue(litFaces.Contains(prevFaceIndex));
|
||||||
@@ -622,18 +622,18 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove all lit faces and construct new faces from the horizon in a
|
/// Remove all lit faces and construct new faces from the horizon in a
|
||||||
/// "cone-like" fashion.
|
/// "cone-like" fashion.
|
||||||
/// This is a relatively straight-forward procedure, given that the
|
/// This is a relatively straight-forward procedure, given that the
|
||||||
/// horizon is handed to it in already sorted counter-clockwise. The
|
/// horizon is handed to it in already sorted counter-clockwise. The
|
||||||
/// neighbors of the new faces are easy to find: they're the previous
|
/// neighbors of the new faces are easy to find: they're the previous
|
||||||
/// and next faces to be constructed in the cone, as well as the face
|
/// and next faces to be constructed in the cone, as well as the face
|
||||||
/// on the other side of the horizon. We also have to update the face
|
/// on the other side of the horizon. We also have to update the face
|
||||||
/// on the other side of the horizon to reflect it's new neighbor from
|
/// on the other side of the horizon to reflect it's new neighbor from
|
||||||
/// the cone.
|
/// the cone.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ConstructCone(List<Float3> points, int farthestPoint)
|
private void ConstructCone(List<Float3> points, int farthestPoint)
|
||||||
{
|
{
|
||||||
foreach (int fi in litFaces)
|
foreach (int fi in litFaces)
|
||||||
{
|
{
|
||||||
@@ -713,21 +713,21 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reassign points based on the new faces added by ConstructCone().
|
/// Reassign points based on the new faces added by ConstructCone().
|
||||||
/// Only points that were previous assigned to a removed face need to
|
/// Only points that were previous assigned to a removed face need to
|
||||||
/// be updated, so check litFaces while looping through the open set.
|
/// be updated, so check litFaces while looping through the open set.
|
||||||
/// There is a potential optimization here: there's no reason to loop
|
/// There is a potential optimization here: there's no reason to loop
|
||||||
/// through the entire openSet here. If each face had it's own
|
/// through the entire openSet here. If each face had it's own
|
||||||
/// openSet, we could just loop through the openSets in the removed
|
/// openSet, we could just loop through the openSets in the removed
|
||||||
/// faces. That would make the loop here shorter.
|
/// faces. That would make the loop here shorter.
|
||||||
/// However, to do that, we would have to juggle A LOT more <![CDATA[List<T>'s]]>,
|
/// However, to do that, we would have to juggle A LOT more <![CDATA[List<T>'s]]>,
|
||||||
/// and we would need an object pool to manage them all without
|
/// and we would need an object pool to manage them all without
|
||||||
/// generating a whole bunch of garbage. I don't think it's worth
|
/// generating a whole bunch of garbage. I don't think it's worth
|
||||||
/// doing that to make this loop shorter, a straight for-loop through
|
/// doing that to make this loop shorter, a straight for-loop through
|
||||||
/// a list is pretty darn fast. Still, it might be worth trying
|
/// a list is pretty darn fast. Still, it might be worth trying
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ReassignPoints(List<Float3> points)
|
private void ReassignPoints(List<Float3> points)
|
||||||
{
|
{
|
||||||
for (int i = 0; i <= openSetTail; i++)
|
for (int i = 0; i <= openSetTail; i++)
|
||||||
{
|
{
|
||||||
@@ -780,13 +780,13 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Final step in algorithm, export the faces of the convex hull in a
|
/// Final step in algorithm, export the faces of the convex hull in a
|
||||||
/// mesh-friendly format.
|
/// mesh-friendly format.
|
||||||
/// TODO normals calculation for non-split vertices. Right now it just
|
/// TODO normals calculation for non-split vertices. Right now it just
|
||||||
/// leaves the normal array empty.
|
/// leaves the normal array empty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ExportMesh(
|
private void ExportMesh(
|
||||||
List<Float3> points,
|
List<Float3> points,
|
||||||
bool splitVerts,
|
bool splitVerts,
|
||||||
ref List<Float3> verts,
|
ref List<Float3> verts,
|
||||||
@@ -855,40 +855,40 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signed distance from face to point (a positive number means that
|
/// Signed distance from face to point (a positive number means that
|
||||||
/// the point is above the face)
|
/// the point is above the face)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private float PointFaceDistance(Float3 point, Float3 pointOnFace, Face face)
|
private float PointFaceDistance(Float3 point, Float3 pointOnFace, Face face)
|
||||||
{
|
{
|
||||||
return Dot(face.Normal, point - pointOnFace);
|
return Dot(face.Normal, point - pointOnFace);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculate normal for triangle
|
/// Calculate normal for triangle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private Float3 Normal(Float3 v0, Float3 v1, Float3 v2)
|
private Float3 Normal(Float3 v0, Float3 v1, Float3 v2)
|
||||||
{
|
{
|
||||||
return Cross(v1 - v0, v2 - v0).Normalized;
|
return Cross(v1 - v0, v2 - v0).Normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dot product, for convenience.
|
/// Dot product, for convenience.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static float Dot(Float3 a, Float3 b)
|
private static float Dot(Float3 a, Float3 b)
|
||||||
{
|
{
|
||||||
return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
|
return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Float3.Cross i left-handed, the algorithm is right-handed. Also,
|
/// Float3.Cross i left-handed, the algorithm is right-handed. Also,
|
||||||
/// i wanna test to see if using aggressive inlining makes any
|
/// i wanna test to see if using aggressive inlining makes any
|
||||||
/// difference here.
|
/// difference here.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static Float3 Cross(Float3 a, Float3 b)
|
private static Float3 Cross(Float3 a, Float3 b)
|
||||||
{
|
{
|
||||||
return new Float3(
|
return new Float3(
|
||||||
@@ -897,28 +897,28 @@ public class ConvexHullCalculator
|
|||||||
a.X * b.Y - a.Y * b.X);
|
a.X * b.Y - a.Y * b.X);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if two points are coincident
|
/// Check if two points are coincident
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private bool AreCoincident(Float3 a, Float3 b)
|
private bool AreCoincident(Float3 a, Float3 b)
|
||||||
{
|
{
|
||||||
return (a - b).Length <= EPSILON;
|
return (a - b).Length <= EPSILON;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if three points are collinear
|
/// Check if three points are collinear
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private bool AreCollinear(Float3 a, Float3 b, Float3 c)
|
private bool AreCollinear(Float3 a, Float3 b, Float3 c)
|
||||||
{
|
{
|
||||||
return Cross(c - a, c - b).Length <= EPSILON;
|
return Cross(c - a, c - b).Length <= EPSILON;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if four points are coplanar
|
/// Check if four points are coplanar
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private bool AreCoplanar(Float3 a, Float3 b, Float3 c, Float3 d)
|
private bool AreCoplanar(Float3 a, Float3 b, Float3 c, Float3 d)
|
||||||
{
|
{
|
||||||
Float3 n1 = Cross(c - a, c - b);
|
Float3 n1 = Cross(c - a, c - b);
|
||||||
@@ -934,12 +934,12 @@ public class ConvexHullCalculator
|
|||||||
1.0f / m2 * n2);
|
1.0f / m2 * n2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Method used for debugging, verifies that the openSet is in a
|
/// Method used for debugging, verifies that the openSet is in a
|
||||||
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
||||||
/// defined.
|
/// defined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void VerifyOpenSet(List<Float3> points)
|
private void VerifyOpenSet(List<Float3> points)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < openSet.Count; i++)
|
for (int i = 0; i < openSet.Count; i++)
|
||||||
if (i > openSetTail)
|
if (i > openSetTail)
|
||||||
@@ -958,12 +958,12 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Method used for debugging, verifies that the horizon is in a
|
/// Method used for debugging, verifies that the horizon is in a
|
||||||
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
||||||
/// defined.
|
/// defined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void VerifyHorizon()
|
private void VerifyHorizon()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < horizon.Count; i++)
|
for (int i = 0; i < horizon.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -974,12 +974,12 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Method used for debugging, verifies that the faces array is in a
|
/// Method used for debugging, verifies that the faces array is in a
|
||||||
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
||||||
/// defined.
|
/// defined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void VerifyFaces(List<Float3> points)
|
private void VerifyFaces(List<Float3> points)
|
||||||
{
|
{
|
||||||
foreach (var kvp in faces)
|
foreach (var kvp in faces)
|
||||||
{
|
{
|
||||||
@@ -1009,12 +1009,12 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Method used for debugging, verifies that the final mesh is
|
/// Method used for debugging, verifies that the final mesh is
|
||||||
/// actually a convex hull of all the points. Conditionally compiled
|
/// actually a convex hull of all the points. Conditionally compiled
|
||||||
/// if DEBUG_QUICKHULL if defined.
|
/// if DEBUG_QUICKHULL if defined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void VerifyMesh(List<Float3> points, ref List<Float3> verts, ref List<int> tris)
|
private void VerifyMesh(List<Float3> points, ref List<Float3> verts, ref List<int> tris)
|
||||||
{
|
{
|
||||||
Assert.IsTrue(tris.Count % 3 == 0);
|
Assert.IsTrue(tris.Count % 3 == 0);
|
||||||
|
|
||||||
@@ -1032,28 +1032,28 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Does face f have a face with vertexes e0 and e1? Used only for
|
/// Does face f have a face with vertexes e0 and e1? Used only for
|
||||||
/// debugging.
|
/// debugging.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool HasEdge(Face f, int e0, int e1)
|
private bool HasEdge(Face f, int e0, int e1)
|
||||||
{
|
{
|
||||||
return (f.Vertex0 == e0 && f.Vertex1 == e1)
|
return (f.Vertex0 == e0 && f.Vertex1 == e1)
|
||||||
|| (f.Vertex1 == e0 && f.Vertex2 == e1)
|
|| (f.Vertex1 == e0 && f.Vertex2 == e1)
|
||||||
|| (f.Vertex2 == e0 && f.Vertex0 == e1);
|
|| (f.Vertex2 == e0 && f.Vertex0 == e1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Struct representing a single face.
|
/// Struct representing a single face.
|
||||||
/// Vertex0, Vertex1 and Vertex2 are the vertices in CCW order. They
|
/// Vertex0, Vertex1 and Vertex2 are the vertices in CCW order. They
|
||||||
/// acutal points are stored in the points array, these are just
|
/// acutal points are stored in the points array, these are just
|
||||||
/// indexes into that array.
|
/// indexes into that array.
|
||||||
/// Opposite0, Opposite1 and Opposite2 are the keys to the faces which
|
/// Opposite0, Opposite1 and Opposite2 are the keys to the faces which
|
||||||
/// share an edge with this face. Opposite0 is the face opposite
|
/// share an edge with this face. Opposite0 is the face opposite
|
||||||
/// Vertex0 (so it has an edge with Vertex2 and Vertex1), etc.
|
/// Vertex0 (so it has an edge with Vertex2 and Vertex1), etc.
|
||||||
/// Normal is (unsurprisingly) the normal of the triangle.
|
/// Normal is (unsurprisingly) the normal of the triangle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private struct Face
|
private struct Face
|
||||||
{
|
{
|
||||||
public readonly int Vertex0;
|
public readonly int Vertex0;
|
||||||
public readonly int Vertex1;
|
public readonly int Vertex1;
|
||||||
@@ -1088,14 +1088,14 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Struct representing a mapping between a point and a face. These
|
/// Struct representing a mapping between a point and a face. These
|
||||||
/// are used in the openSet array.
|
/// are used in the openSet array.
|
||||||
/// Point is the index of the point in the points array, Face is the
|
/// Point is the index of the point in the points array, Face is the
|
||||||
/// key of the face in the Key dictionary, Distance is the distance
|
/// key of the face in the Key dictionary, Distance is the distance
|
||||||
/// from the face to the point.
|
/// from the face to the point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private struct PointFace
|
private struct PointFace
|
||||||
{
|
{
|
||||||
public readonly int Point;
|
public readonly int Point;
|
||||||
public int Face;
|
public int Face;
|
||||||
@@ -1109,14 +1109,14 @@ public class ConvexHullCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Struct representing a single edge in the horizon.
|
/// Struct representing a single edge in the horizon.
|
||||||
/// Edge0 and Edge1 are the vertexes of edge in CCW order, Face is the
|
/// Edge0 and Edge1 are the vertexes of edge in CCW order, Face is the
|
||||||
/// face on the other side of the horizon.
|
/// face on the other side of the horizon.
|
||||||
/// TODO Edge1 isn't actually needed, you can just index the next item
|
/// TODO Edge1 isn't actually needed, you can just index the next item
|
||||||
/// in the horizon array.
|
/// in the horizon array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private struct HorizonEdge
|
private struct HorizonEdge
|
||||||
{
|
{
|
||||||
public int Face;
|
public int Face;
|
||||||
public int Edge0;
|
public int Edge0;
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public static class InputManager
|
||||||
{
|
{
|
||||||
public static class InputManager
|
public static bool GetAction(string name)
|
||||||
{
|
{
|
||||||
public static bool GetAction(string name)
|
if (Console.IsOpen)
|
||||||
{
|
return false;
|
||||||
if (Console.IsOpen)
|
return Input.GetAction(name);
|
||||||
return false;
|
}
|
||||||
return Input.GetAction(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float GetAxis(string name)
|
public static float GetAxis(string name)
|
||||||
{
|
{
|
||||||
if (Console.IsOpen)
|
if (Console.IsOpen)
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
return Input.GetAxis(name);
|
return Input.GetAxis(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float GetAxisRaw(string name)
|
public static float GetAxisRaw(string name)
|
||||||
{
|
{
|
||||||
if (Console.IsOpen)
|
if (Console.IsOpen)
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
return Input.GetAxisRaw(name);
|
return Input.GetAxisRaw(name);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,173 +2,172 @@
|
|||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEngine.Networking;
|
using FlaxEngine.Networking;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct PlayerInputState
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
/*static PlayerInputState()
|
||||||
public struct PlayerInputState
|
|
||||||
{
|
{
|
||||||
/*static PlayerInputState()
|
NetworkReplicator.AddSerializer(typeof(PlayerInputState),
|
||||||
{
|
(ptr, streamPtr) =>
|
||||||
NetworkReplicator.AddSerializer(typeof(PlayerInputState),
|
|
||||||
(ptr, streamPtr) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
},
|
|
||||||
(ptr, streamPtr) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
});
|
|
||||||
}*/
|
|
||||||
|
|
||||||
#if false
|
|
||||||
public PlayerInputState(ulong frame, float viewDeltaX, float viewDeltaY, float moveForward, float moveRight,
|
|
||||||
bool attacking, bool jumping)
|
|
||||||
{
|
|
||||||
this.frame = frame;
|
|
||||||
this.viewDeltaX = viewDeltaX;
|
|
||||||
this.viewDeltaY = viewDeltaY;
|
|
||||||
this.moveForward = moveForward;
|
|
||||||
this.moveRight = moveRight;
|
|
||||||
this.attacking = attacking;
|
|
||||||
this.jumping = jumping;
|
|
||||||
/*this.verificationPosition = verificationPosition;
|
|
||||||
this.verificationVelocity = verificationVelocity;
|
|
||||||
this.verificationViewAngles = verificationViewAngles;
|
|
||||||
this.verificationOrientation = verificationOrientation;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerInputState(ulong frame, float viewDeltaX, float viewDeltaY, float moveForward, float moveRight,
|
|
||||||
bool attacking, bool jumping, Float3 verificationPosition, Float3 verificationVelocity,
|
|
||||||
Float3 verificationViewAngles, Quaternion verificationOrientation)
|
|
||||||
{
|
|
||||||
this.frame = frame;
|
|
||||||
this.viewDeltaX = viewDeltaX;
|
|
||||||
this.viewDeltaY = viewDeltaY;
|
|
||||||
this.moveForward = moveForward;
|
|
||||||
this.moveRight = moveRight;
|
|
||||||
this.attacking = attacking;
|
|
||||||
this.jumping = jumping;
|
|
||||||
this.verificationPosition = verificationPosition;
|
|
||||||
this.verificationVelocity = verificationVelocity;
|
|
||||||
this.verificationViewAngles = verificationViewAngles;
|
|
||||||
this.verificationOrientation = verificationOrientation;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public ulong frame;
|
|
||||||
public float viewDeltaX, viewDeltaY;
|
|
||||||
public float moveForward;
|
|
||||||
public float moveRight;
|
|
||||||
public bool attacking;
|
|
||||||
public bool jumping;
|
|
||||||
|
|
||||||
public Float3 verificationPosition;
|
|
||||||
public Float3 verificationVelocity;
|
|
||||||
public Float3 verificationViewAngles;
|
|
||||||
public Quaternion verificationOrientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct PlayerActorState
|
|
||||||
{
|
|
||||||
public Float3 position;
|
|
||||||
public Float3 velocity;
|
|
||||||
public Quaternion orientation;
|
|
||||||
public Float3 viewAngles; // yaw, pitch, roll
|
|
||||||
public float lastJumpTime;
|
|
||||||
public int numJumps;
|
|
||||||
public bool jumped;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct PlayerState
|
|
||||||
{
|
|
||||||
public PlayerInputState input;
|
|
||||||
public PlayerActorState actor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PlayerInput
|
|
||||||
{
|
|
||||||
public const byte DemoVer = 1;
|
|
||||||
public const int MaxPlayerStates = 120;
|
|
||||||
|
|
||||||
public PlayerState currentState;
|
|
||||||
public ulong frame;
|
|
||||||
//public ulong oldestFrame;
|
|
||||||
|
|
||||||
private PlayerState[] states = new PlayerState[MaxPlayerStates];
|
|
||||||
|
|
||||||
public virtual bool Predict => false;
|
|
||||||
|
|
||||||
public virtual void OnUpdate()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnFixedUpdate()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnEndFrame()
|
|
||||||
{
|
|
||||||
//Console.Print("recorded frame " + frame);
|
|
||||||
|
|
||||||
states[frame % MaxPlayerStates] = currentState;
|
|
||||||
|
|
||||||
/*ulong oldest = ulong.MaxValue;
|
|
||||||
for (int i = 0; i < 120; i++)
|
|
||||||
oldest = states[i].input.frame < oldest ? states[i].input.frame : oldest;
|
|
||||||
oldestFrame = oldest;*/
|
|
||||||
|
|
||||||
frame++;
|
|
||||||
currentState.input.frame = frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void RecordCurrentActorState(PlayerActorState actorState)
|
|
||||||
{
|
|
||||||
if (actorState.position.Length <= 0.01)
|
|
||||||
Console.Print("wrong recorded position?");
|
|
||||||
currentState.actor = actorState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool GetState(ulong frame, out PlayerInputState inputState, out PlayerActorState actorState)
|
|
||||||
{
|
|
||||||
int frameIndex = (int)frame % MaxPlayerStates;
|
|
||||||
if (states[frameIndex].input.frame != frame)
|
|
||||||
{
|
{
|
||||||
inputState = default;
|
|
||||||
actorState = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputState = states[frameIndex].input;
|
},
|
||||||
actorState = states[frameIndex].actor;
|
(ptr, streamPtr) =>
|
||||||
return true;
|
{
|
||||||
}
|
|
||||||
|
|
||||||
public void SetState(ulong frame, PlayerInputState inputState)
|
});
|
||||||
|
}*/
|
||||||
|
|
||||||
|
#if false
|
||||||
|
public PlayerInputState(ulong frame, float viewDeltaX, float viewDeltaY, float moveForward, float moveRight,
|
||||||
|
bool attacking, bool jumping)
|
||||||
|
{
|
||||||
|
this.frame = frame;
|
||||||
|
this.viewDeltaX = viewDeltaX;
|
||||||
|
this.viewDeltaY = viewDeltaY;
|
||||||
|
this.moveForward = moveForward;
|
||||||
|
this.moveRight = moveRight;
|
||||||
|
this.attacking = attacking;
|
||||||
|
this.jumping = jumping;
|
||||||
|
/*this.verificationPosition = verificationPosition;
|
||||||
|
this.verificationVelocity = verificationVelocity;
|
||||||
|
this.verificationViewAngles = verificationViewAngles;
|
||||||
|
this.verificationOrientation = verificationOrientation;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerInputState(ulong frame, float viewDeltaX, float viewDeltaY, float moveForward, float moveRight,
|
||||||
|
bool attacking, bool jumping, Float3 verificationPosition, Float3 verificationVelocity,
|
||||||
|
Float3 verificationViewAngles, Quaternion verificationOrientation)
|
||||||
|
{
|
||||||
|
this.frame = frame;
|
||||||
|
this.viewDeltaX = viewDeltaX;
|
||||||
|
this.viewDeltaY = viewDeltaY;
|
||||||
|
this.moveForward = moveForward;
|
||||||
|
this.moveRight = moveRight;
|
||||||
|
this.attacking = attacking;
|
||||||
|
this.jumping = jumping;
|
||||||
|
this.verificationPosition = verificationPosition;
|
||||||
|
this.verificationVelocity = verificationVelocity;
|
||||||
|
this.verificationViewAngles = verificationViewAngles;
|
||||||
|
this.verificationOrientation = verificationOrientation;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public ulong frame;
|
||||||
|
public float viewDeltaX, viewDeltaY;
|
||||||
|
public float moveForward;
|
||||||
|
public float moveRight;
|
||||||
|
public bool attacking;
|
||||||
|
public bool jumping;
|
||||||
|
|
||||||
|
public Float3 verificationPosition;
|
||||||
|
public Float3 verificationVelocity;
|
||||||
|
public Float3 verificationViewAngles;
|
||||||
|
public Quaternion verificationOrientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct PlayerActorState
|
||||||
|
{
|
||||||
|
public Float3 position;
|
||||||
|
public Float3 velocity;
|
||||||
|
public Quaternion orientation;
|
||||||
|
public Float3 viewAngles; // yaw, pitch, roll
|
||||||
|
public float lastJumpTime;
|
||||||
|
public int numJumps;
|
||||||
|
public bool jumped;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct PlayerState
|
||||||
|
{
|
||||||
|
public PlayerInputState input;
|
||||||
|
public PlayerActorState actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlayerInput
|
||||||
|
{
|
||||||
|
public const byte DemoVer = 1;
|
||||||
|
public const int MaxPlayerStates = 120;
|
||||||
|
|
||||||
|
public PlayerState currentState;
|
||||||
|
public ulong frame;
|
||||||
|
//public ulong oldestFrame;
|
||||||
|
|
||||||
|
private PlayerState[] states = new PlayerState[MaxPlayerStates];
|
||||||
|
|
||||||
|
public virtual bool Predict => false;
|
||||||
|
|
||||||
|
public virtual void OnUpdate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnFixedUpdate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnEndFrame()
|
||||||
|
{
|
||||||
|
//Console.Print("recorded frame " + frame);
|
||||||
|
|
||||||
|
states[frame % MaxPlayerStates] = currentState;
|
||||||
|
|
||||||
|
/*ulong oldest = ulong.MaxValue;
|
||||||
|
for (int i = 0; i < 120; i++)
|
||||||
|
oldest = states[i].input.frame < oldest ? states[i].input.frame : oldest;
|
||||||
|
oldestFrame = oldest;*/
|
||||||
|
|
||||||
|
frame++;
|
||||||
|
currentState.input.frame = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RecordCurrentActorState(PlayerActorState actorState)
|
||||||
|
{
|
||||||
|
if (actorState.position.Length <= 0.01)
|
||||||
|
Console.Print("wrong recorded position?");
|
||||||
|
currentState.actor = actorState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetState(ulong frame, out PlayerInputState inputState, out PlayerActorState actorState)
|
||||||
|
{
|
||||||
|
int frameIndex = (int)frame % MaxPlayerStates;
|
||||||
|
if (states[frameIndex].input.frame != frame)
|
||||||
{
|
{
|
||||||
int frameIndex = (int)frame % MaxPlayerStates;
|
inputState = default;
|
||||||
states[frameIndex].input = inputState;
|
actorState = default;
|
||||||
states[frameIndex].input.frame = frame;
|
return false;
|
||||||
states[frameIndex].actor = new PlayerActorState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetState(ulong frame, PlayerInputState inputState, PlayerActorState actorState)
|
inputState = states[frameIndex].input;
|
||||||
{
|
actorState = states[frameIndex].actor;
|
||||||
int frameIndex = (int)frame % MaxPlayerStates;
|
return true;
|
||||||
states[frameIndex].input = inputState;
|
}
|
||||||
states[frameIndex].input.frame = frame;
|
|
||||||
states[frameIndex].actor = actorState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerInputState GetCurrentInputState()
|
public void SetState(ulong frame, PlayerInputState inputState)
|
||||||
{
|
{
|
||||||
return currentState.input;
|
int frameIndex = (int)frame % MaxPlayerStates;
|
||||||
}
|
states[frameIndex].input = inputState;
|
||||||
|
states[frameIndex].input.frame = frame;
|
||||||
|
states[frameIndex].actor = new PlayerActorState();
|
||||||
|
}
|
||||||
|
|
||||||
public PlayerActorState GetCurrentActorState()
|
public void SetState(ulong frame, PlayerInputState inputState, PlayerActorState actorState)
|
||||||
{
|
{
|
||||||
return currentState.actor;
|
int frameIndex = (int)frame % MaxPlayerStates;
|
||||||
}
|
states[frameIndex].input = inputState;
|
||||||
|
states[frameIndex].input.frame = frame;
|
||||||
|
states[frameIndex].actor = actorState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerInputState GetCurrentInputState()
|
||||||
|
{
|
||||||
|
return currentState.input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerActorState GetCurrentActorState()
|
||||||
|
{
|
||||||
|
return currentState.actor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,95 +8,94 @@ using System.Runtime.CompilerServices;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Console = Game.Console;
|
using Console = Game.Console;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public class PlayerInputDemo : PlayerInput
|
||||||
{
|
{
|
||||||
public class PlayerInputDemo : PlayerInput
|
protected List<PlayerInputState> buffer = new List<PlayerInputState>();
|
||||||
|
protected IEnumerator<PlayerInputState> bufferEnumerable;
|
||||||
|
|
||||||
|
public bool IsPlaying
|
||||||
{
|
{
|
||||||
protected List<PlayerInputState> buffer = new List<PlayerInputState>();
|
get
|
||||||
protected IEnumerator<PlayerInputState> bufferEnumerable;
|
|
||||||
|
|
||||||
public bool IsPlaying
|
|
||||||
{
|
{
|
||||||
get
|
return bufferEnumerable != null;
|
||||||
{
|
|
||||||
return bufferEnumerable != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerInputDemo(string demoPath)
|
|
||||||
{
|
|
||||||
if (!File.Exists(demoPath))
|
|
||||||
return;
|
|
||||||
|
|
||||||
int expectedPlayerInputStateSize = Unsafe.SizeOf<PlayerInputState>();
|
|
||||||
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
using FileStream fileStream = File.OpenRead(demoPath);
|
|
||||||
using GZipStream stream = new GZipStream(fileStream, CompressionMode.Decompress);
|
|
||||||
|
|
||||||
int ver = stream.ReadByte();
|
|
||||||
int inputStateSize = stream.ReadByte();
|
|
||||||
if (ver != DemoVer || inputStateSize != expectedPlayerInputStateSize)
|
|
||||||
{
|
|
||||||
Console.Print("demover mismatch: version " + ver + " != " + DemoVer + ", inputStateSize " +
|
|
||||||
inputStateSize + " != " + Unsafe.SizeOf<PlayerInputState>());
|
|
||||||
stream.Close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Span<byte> b = stackalloc byte[expectedPlayerInputStateSize];
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
int bytesLeftInBuffer = expectedPlayerInputStateSize;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
int readBytes = stream.Read(b.Slice(expectedPlayerInputStateSize - bytesLeftInBuffer, bytesLeftInBuffer));
|
|
||||||
if (readBytes == 0)
|
|
||||||
break;
|
|
||||||
bytesLeftInBuffer -= readBytes;
|
|
||||||
} while (bytesLeftInBuffer > 0);
|
|
||||||
|
|
||||||
if (bytesLeftInBuffer > 0)
|
|
||||||
break; // EOF;
|
|
||||||
|
|
||||||
buffer.Add(MemoryMarshal.Read<PlayerInputState>(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
sw.Stop();
|
|
||||||
|
|
||||||
bufferEnumerable = buffer.GetEnumerator();
|
|
||||||
|
|
||||||
Console.Print($"Demo parse time {sw.Elapsed.TotalMilliseconds}ms, frames: {buffer.Count} ");
|
|
||||||
|
|
||||||
OnEndFrame(); // advances to first frame
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnEndFrame()
|
|
||||||
{
|
|
||||||
// TODO: check if the current state frame matches the current frame number before advancing
|
|
||||||
|
|
||||||
/*asdf++;
|
|
||||||
if (asdf < 8)
|
|
||||||
return;*/
|
|
||||||
|
|
||||||
if (bufferEnumerable == null || !bufferEnumerable.MoveNext())
|
|
||||||
{
|
|
||||||
if (buffer.Any())
|
|
||||||
{
|
|
||||||
bufferEnumerable.Dispose();
|
|
||||||
bufferEnumerable = null;
|
|
||||||
buffer.Clear();
|
|
||||||
Console.Print("Demo ended");
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//var actorState = currentState.actor;
|
|
||||||
currentState.input = bufferEnumerable.Current;
|
|
||||||
//frame++;
|
|
||||||
//currentState.actor = actorState;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PlayerInputDemo(string demoPath)
|
||||||
|
{
|
||||||
|
if (!File.Exists(demoPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int expectedPlayerInputStateSize = Unsafe.SizeOf<PlayerInputState>();
|
||||||
|
|
||||||
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
using FileStream fileStream = File.OpenRead(demoPath);
|
||||||
|
using GZipStream stream = new GZipStream(fileStream, CompressionMode.Decompress);
|
||||||
|
|
||||||
|
int ver = stream.ReadByte();
|
||||||
|
int inputStateSize = stream.ReadByte();
|
||||||
|
if (ver != DemoVer || inputStateSize != expectedPlayerInputStateSize)
|
||||||
|
{
|
||||||
|
Console.Print("demover mismatch: version " + ver + " != " + DemoVer + ", inputStateSize " +
|
||||||
|
inputStateSize + " != " + Unsafe.SizeOf<PlayerInputState>());
|
||||||
|
stream.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> b = stackalloc byte[expectedPlayerInputStateSize];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int bytesLeftInBuffer = expectedPlayerInputStateSize;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int readBytes = stream.Read(b.Slice(expectedPlayerInputStateSize - bytesLeftInBuffer, bytesLeftInBuffer));
|
||||||
|
if (readBytes == 0)
|
||||||
|
break;
|
||||||
|
bytesLeftInBuffer -= readBytes;
|
||||||
|
} while (bytesLeftInBuffer > 0);
|
||||||
|
|
||||||
|
if (bytesLeftInBuffer > 0)
|
||||||
|
break; // EOF;
|
||||||
|
|
||||||
|
buffer.Add(MemoryMarshal.Read<PlayerInputState>(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
|
||||||
|
bufferEnumerable = buffer.GetEnumerator();
|
||||||
|
|
||||||
|
Console.Print($"Demo parse time {sw.Elapsed.TotalMilliseconds}ms, frames: {buffer.Count} ");
|
||||||
|
|
||||||
|
OnEndFrame(); // advances to first frame
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEndFrame()
|
||||||
|
{
|
||||||
|
// TODO: check if the current state frame matches the current frame number before advancing
|
||||||
|
|
||||||
|
/*asdf++;
|
||||||
|
if (asdf < 8)
|
||||||
|
return;*/
|
||||||
|
|
||||||
|
if (bufferEnumerable == null || !bufferEnumerable.MoveNext())
|
||||||
|
{
|
||||||
|
if (buffer.Any())
|
||||||
|
{
|
||||||
|
bufferEnumerable.Dispose();
|
||||||
|
bufferEnumerable = null;
|
||||||
|
buffer.Clear();
|
||||||
|
Console.Print("Demo ended");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//var actorState = currentState.actor;
|
||||||
|
currentState.input = bufferEnumerable.Current;
|
||||||
|
//frame++;
|
||||||
|
//currentState.actor = actorState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,142 +9,141 @@ using FlaxEngine;
|
|||||||
using FlaxEngine.Networking;
|
using FlaxEngine.Networking;
|
||||||
using Console = Game.Console;
|
using Console = Game.Console;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public class PlayerInputLocal : PlayerInput
|
||||||
{
|
{
|
||||||
public class PlayerInputLocal : PlayerInput
|
protected List<PlayerInputState> buffer = new List<PlayerInputState>();
|
||||||
|
protected GZipStream demoStream;
|
||||||
|
protected FileStream demoFileStream;
|
||||||
|
|
||||||
|
private PlayerActor playerActor;
|
||||||
|
public bool IsNetworked => NetworkManager.client != null;
|
||||||
|
private long flushedFrames = 0;
|
||||||
|
|
||||||
|
/*public PlayerInputLocal()
|
||||||
{
|
{
|
||||||
protected List<PlayerInputState> buffer = new List<PlayerInputState>();
|
}*/
|
||||||
protected GZipStream demoStream;
|
|
||||||
protected FileStream demoFileStream;
|
|
||||||
|
|
||||||
private PlayerActor playerActor;
|
public override bool Predict => true;
|
||||||
public bool IsNetworked => NetworkManager.client != null;
|
|
||||||
private long flushedFrames = 0;
|
|
||||||
|
|
||||||
/*public PlayerInputLocal()
|
public PlayerInputLocal(PlayerActor playerActor, string demoPath = null)
|
||||||
|
{
|
||||||
|
this.playerActor = playerActor;
|
||||||
|
if (demoPath == null)
|
||||||
|
return;
|
||||||
|
var demoFolder = Directory.GetParent(demoPath);
|
||||||
|
if (!demoFolder.Exists)
|
||||||
|
Directory.CreateDirectory(demoFolder.FullName);
|
||||||
|
|
||||||
|
demoFileStream = File.Open(demoPath, FileMode.Create, FileAccess.Write);
|
||||||
|
demoStream = new GZipStream(demoFileStream, CompressionMode.Compress);
|
||||||
|
demoStream.WriteByte(DemoVer);
|
||||||
|
demoStream.WriteByte((byte)Unsafe.SizeOf<PlayerInputState>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRecording => demoStream != null;
|
||||||
|
|
||||||
|
public override void OnUpdate()
|
||||||
|
{
|
||||||
|
// Collect all input here
|
||||||
|
// All axis values here should be accumulated
|
||||||
|
float sensitivity = 1.0f / 8.0f;
|
||||||
|
sensitivity = 1.0f;
|
||||||
|
|
||||||
|
//var asf = InputManager.GetAxisRaw("Mouse X");
|
||||||
|
//if (asf != 0.0f)
|
||||||
|
// Console.Print(InputManager.GetAxisRaw("Mouse X").ToString("G9", System.Globalization.CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
currentState.input.viewDeltaX += InputManager.GetAxisRaw("Mouse X") * sensitivity;
|
||||||
|
currentState.input.viewDeltaY += InputManager.GetAxisRaw("Mouse Y") * sensitivity;
|
||||||
|
currentState.input.viewDeltaX += InputManager.GetAxisRaw("LookRight") * Time.DeltaTime * 100;
|
||||||
|
currentState.input.viewDeltaY += -InputManager.GetAxisRaw("LookUp") * Time.DeltaTime * 100;
|
||||||
|
|
||||||
|
currentState.input.moveForward = InputManager.GetAxis("Vertical");
|
||||||
|
currentState.input.moveRight = InputManager.GetAxis("Horizontal");
|
||||||
|
currentState.input.attacking = InputManager.GetAction("Attack");
|
||||||
|
currentState.input.jumping = InputManager.GetAction("Jump");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFixedUpdate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEndFrame()
|
||||||
|
{
|
||||||
|
currentState.input.frame = frame;
|
||||||
|
if (IsRecording)
|
||||||
{
|
{
|
||||||
}*/
|
currentState.input.verificationPosition = currentState.actor.position;
|
||||||
|
currentState.input.verificationVelocity = currentState.actor.velocity;
|
||||||
|
currentState.input.verificationViewAngles = currentState.actor.viewAngles;
|
||||||
|
currentState.input.verificationOrientation = currentState.actor.orientation;
|
||||||
|
|
||||||
public override bool Predict => true;
|
buffer.Add(currentState.input);
|
||||||
|
|
||||||
public PlayerInputLocal(PlayerActor playerActor, string demoPath = null)
|
|
||||||
{
|
|
||||||
this.playerActor = playerActor;
|
|
||||||
if (demoPath == null)
|
|
||||||
return;
|
|
||||||
var demoFolder = Directory.GetParent(demoPath);
|
|
||||||
if (!demoFolder.Exists)
|
|
||||||
Directory.CreateDirectory(demoFolder.FullName);
|
|
||||||
|
|
||||||
demoFileStream = File.Open(demoPath, FileMode.Create, FileAccess.Write);
|
|
||||||
demoStream = new GZipStream(demoFileStream, CompressionMode.Compress);
|
|
||||||
demoStream.WriteByte(DemoVer);
|
|
||||||
demoStream.WriteByte((byte)Unsafe.SizeOf<PlayerInputState>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsRecording => demoStream != null;
|
if (playerActor != null)
|
||||||
|
|
||||||
public override void OnUpdate()
|
|
||||||
{
|
{
|
||||||
// Collect all input here
|
//playerActor.UpdateNetworkInput(currentState.input.frame, new Float4(currentState.input.viewDeltaX, currentState.input.viewDeltaY, currentState.input.moveForward, currentState.input.moveRight), currentState.input.attacking, currentState.input.jumping);
|
||||||
// All axis values here should be accumulated
|
|
||||||
float sensitivity = 1.0f / 8.0f;
|
|
||||||
sensitivity = 1.0f;
|
|
||||||
|
|
||||||
//var asf = InputManager.GetAxisRaw("Mouse X");
|
|
||||||
//if (asf != 0.0f)
|
|
||||||
// Console.Print(InputManager.GetAxisRaw("Mouse X").ToString("G9", System.Globalization.CultureInfo.InvariantCulture));
|
|
||||||
|
|
||||||
currentState.input.viewDeltaX += InputManager.GetAxisRaw("Mouse X") * sensitivity;
|
|
||||||
currentState.input.viewDeltaY += InputManager.GetAxisRaw("Mouse Y") * sensitivity;
|
|
||||||
currentState.input.viewDeltaX += InputManager.GetAxisRaw("LookRight") * Time.DeltaTime * 100;
|
|
||||||
currentState.input.viewDeltaY += -InputManager.GetAxisRaw("LookUp") * Time.DeltaTime * 100;
|
|
||||||
|
|
||||||
currentState.input.moveForward = InputManager.GetAxis("Vertical");
|
|
||||||
currentState.input.moveRight = InputManager.GetAxis("Horizontal");
|
|
||||||
currentState.input.attacking = InputManager.GetAction("Attack");
|
|
||||||
currentState.input.jumping = InputManager.GetAction("Jump");
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnFixedUpdate()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnEndFrame()
|
|
||||||
{
|
|
||||||
currentState.input.frame = frame;
|
|
||||||
if (IsRecording)
|
|
||||||
{
|
|
||||||
currentState.input.verificationPosition = currentState.actor.position;
|
|
||||||
currentState.input.verificationVelocity = currentState.actor.velocity;
|
|
||||||
currentState.input.verificationViewAngles = currentState.actor.viewAngles;
|
|
||||||
currentState.input.verificationOrientation = currentState.actor.orientation;
|
|
||||||
|
|
||||||
buffer.Add(currentState.input);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playerActor != null)
|
|
||||||
{
|
|
||||||
//playerActor.UpdateNetworkInput(currentState.input.frame, new Float4(currentState.input.viewDeltaX, currentState.input.viewDeltaY, currentState.input.moveForward, currentState.input.moveRight), currentState.input.attacking, currentState.input.jumping);
|
|
||||||
|
|
||||||
}
|
|
||||||
//playerActor.UpdateNetworkInput(currentState.input.frame, currentState.input.viewDeltaX, currentState.input.viewDeltaY, currentState.input.moveForward, currentState.input.moveRight, currentState.input.attacking, currentState.input.jumping, currentState.input.verificationPosition, currentState.input.verificationVelocity, currentState.input.verificationViewAngles, currentState.input.verificationOrientation);
|
|
||||||
|
|
||||||
if (IsNetworked)
|
|
||||||
{
|
|
||||||
var message = NetworkManager.ClientBeginSendMessage();
|
|
||||||
message.WriteByte((byte)GameModeMessageType.PlayerInput);
|
|
||||||
message.WriteUInt64(currentState.input.frame);
|
|
||||||
message.WriteSingle(currentState.input.viewDeltaX);
|
|
||||||
message.WriteSingle(currentState.input.viewDeltaY);
|
|
||||||
message.WriteSingle(currentState.input.moveForward);
|
|
||||||
message.WriteSingle(currentState.input.moveRight);
|
|
||||||
message.WriteBoolean(currentState.input.attacking);
|
|
||||||
message.WriteBoolean(currentState.input.jumping);
|
|
||||||
NetworkManager.ClientEndSendMessage(ref message);
|
|
||||||
}
|
|
||||||
|
|
||||||
base.OnEndFrame();
|
|
||||||
|
|
||||||
// Reset anything accumulatable here
|
|
||||||
currentState.input.viewDeltaX = 0;
|
|
||||||
currentState.input.viewDeltaY = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FlushDemo()
|
|
||||||
{
|
|
||||||
if (!IsRecording)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<PlayerInputState>()];
|
|
||||||
foreach (ref PlayerInputState state in CollectionsMarshal.AsSpan(buffer))
|
|
||||||
{
|
|
||||||
MemoryMarshal.Write(bytes, state);
|
|
||||||
demoStream.Write(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
sw.Stop();
|
|
||||||
|
|
||||||
flushedFrames += buffer.Count;
|
|
||||||
buffer.Clear();
|
|
||||||
|
|
||||||
FlaxEngine.Debug.Write(LogType.Info, $"Wrote demo in {sw.Elapsed.TotalMilliseconds}ms, frames: {flushedFrames}");
|
|
||||||
}
|
}
|
||||||
|
//playerActor.UpdateNetworkInput(currentState.input.frame, currentState.input.viewDeltaX, currentState.input.viewDeltaY, currentState.input.moveForward, currentState.input.moveRight, currentState.input.attacking, currentState.input.jumping, currentState.input.verificationPosition, currentState.input.verificationVelocity, currentState.input.verificationViewAngles, currentState.input.verificationOrientation);
|
||||||
|
|
||||||
public void StopRecording()
|
if (IsNetworked)
|
||||||
{
|
{
|
||||||
if (!IsRecording)
|
var message = NetworkManager.ClientBeginSendMessage();
|
||||||
return;
|
message.WriteByte((byte)GameModeMessageType.PlayerInput);
|
||||||
|
message.WriteUInt64(currentState.input.frame);
|
||||||
FlushDemo();
|
message.WriteSingle(currentState.input.viewDeltaX);
|
||||||
demoStream.Close();
|
message.WriteSingle(currentState.input.viewDeltaY);
|
||||||
demoStream = null;
|
message.WriteSingle(currentState.input.moveForward);
|
||||||
demoFileStream.Close();
|
message.WriteSingle(currentState.input.moveRight);
|
||||||
demoFileStream = null;
|
message.WriteBoolean(currentState.input.attacking);
|
||||||
|
message.WriteBoolean(currentState.input.jumping);
|
||||||
|
NetworkManager.ClientEndSendMessage(ref message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.OnEndFrame();
|
||||||
|
|
||||||
|
// Reset anything accumulatable here
|
||||||
|
currentState.input.viewDeltaX = 0;
|
||||||
|
currentState.input.viewDeltaY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FlushDemo()
|
||||||
|
{
|
||||||
|
if (!IsRecording)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<PlayerInputState>()];
|
||||||
|
foreach (ref PlayerInputState state in CollectionsMarshal.AsSpan(buffer))
|
||||||
|
{
|
||||||
|
MemoryMarshal.Write(bytes, state);
|
||||||
|
demoStream.Write(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
|
||||||
|
flushedFrames += buffer.Count;
|
||||||
|
buffer.Clear();
|
||||||
|
|
||||||
|
FlaxEngine.Debug.Write(LogType.Info, $"Wrote demo in {sw.Elapsed.TotalMilliseconds}ms, frames: {flushedFrames}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopRecording()
|
||||||
|
{
|
||||||
|
if (!IsRecording)
|
||||||
|
return;
|
||||||
|
|
||||||
|
FlushDemo();
|
||||||
|
demoStream.Close();
|
||||||
|
demoStream = null;
|
||||||
|
demoFileStream.Close();
|
||||||
|
demoFileStream = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
namespace Game
|
namespace Game;
|
||||||
{
|
|
||||||
public class PlayerInputNetwork : PlayerInput
|
|
||||||
{
|
|
||||||
public override bool Predict => true;
|
|
||||||
|
|
||||||
public override void OnEndFrame()
|
public class PlayerInputNetwork : PlayerInput
|
||||||
{
|
{
|
||||||
currentState.input.frame = frame;
|
public override bool Predict => true;
|
||||||
base.OnEndFrame();
|
|
||||||
}
|
public override void OnEndFrame()
|
||||||
|
{
|
||||||
|
currentState.input.frame = frame;
|
||||||
|
base.OnEndFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,27 +1,26 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
|
||||||
namespace Game
|
namespace Game;
|
||||||
|
|
||||||
|
public static class AssetManager
|
||||||
{
|
{
|
||||||
public static class AssetManager
|
public static string ContentPath { get; private set; } =
|
||||||
|
Path.Combine(Directory.GetCurrentDirectory(), "Content");
|
||||||
|
|
||||||
|
public static string DemoPath { get; private set; } =
|
||||||
|
Path.Combine(Directory.GetCurrentDirectory(), "Demos");
|
||||||
|
|
||||||
|
public static string CachePath { get; private set; } =
|
||||||
|
Path.Combine(Directory.GetCurrentDirectory(), "Cache");
|
||||||
|
|
||||||
|
public static GameplayGlobals Globals { get; private set; }
|
||||||
|
public static Config Config { get; private set; }
|
||||||
|
|
||||||
|
//public static void Init()
|
||||||
|
static AssetManager()
|
||||||
{
|
{
|
||||||
public static string ContentPath { get; private set; } =
|
Globals = Content.Load<GameplayGlobals>(Path.Combine(ContentPath, "Settings", "GameSettings", "GameplayGlobals.flax"));
|
||||||
Path.Combine(Directory.GetCurrentDirectory(), "Content");
|
Config = ConfigParser.ParseFile(Path.Combine(ContentPath, "config.cfg"));
|
||||||
|
|
||||||
public static string DemoPath { get; private set; } =
|
|
||||||
Path.Combine(Directory.GetCurrentDirectory(), "Demos");
|
|
||||||
|
|
||||||
public static string CachePath { get; private set; } =
|
|
||||||
Path.Combine(Directory.GetCurrentDirectory(), "Cache");
|
|
||||||
|
|
||||||
public static GameplayGlobals Globals { get; private set; }
|
|
||||||
public static Config Config { get; private set; }
|
|
||||||
|
|
||||||
//public static void Init()
|
|
||||||
static AssetManager()
|
|
||||||
{
|
|
||||||
Globals = Content.Load<GameplayGlobals>(Path.Combine(ContentPath, "Settings", "GameSettings", "GameplayGlobals.flax"));
|
|
||||||
Config = ConfigParser.ParseFile(Path.Combine(ContentPath, "config.cfg"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user