namespacify everything
This commit is contained in:
@@ -6,251 +6,250 @@ using FlaxEngine;
|
||||
using Console = Game.Console;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
[Flags]
|
||||
public enum AudioFlags
|
||||
{
|
||||
None = 0,
|
||||
namespace Game;
|
||||
|
||||
/// Avoid replacing the existing playing audio source in this channel.
|
||||
ContinuePlayingExistingSource = 1
|
||||
[Flags]
|
||||
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 =
|
||||
new Dictionary<Actor, ActorAudioChannels>();
|
||||
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, 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);
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
if (!actorAudioChannels.TryGetValue(actor, out 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;
|
||||
}
|
||||
actorChannels = new ActorAudioChannels();
|
||||
actorAudioChannels.Add(actor, actorChannels);
|
||||
}
|
||||
|
||||
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))
|
||||
return;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSoundPlaying(Actor actor, int channel)
|
||||
AudioClip audioClip;
|
||||
if (audio.AudioClips.Length > 1)
|
||||
{
|
||||
if (channel <= 0)
|
||||
return false;
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (!actorAudioChannels.TryGetValue(actor, out ActorAudioChannels actorChannels))
|
||||
return false;
|
||||
audioClip = audio.AudioClips[randomIndex];
|
||||
audio.lastAudioPlayed = randomIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
audioClip = audio.AudioClips[0];
|
||||
}
|
||||
|
||||
if (!actorChannels.channelSources.TryGetValue(channel, out AudioSource existingAudioSource))
|
||||
return false;
|
||||
// Randomized pitch
|
||||
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 &&
|
||||
existingAudioSource.State == AudioSource.States.Playing)
|
||||
return true;
|
||||
// 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))
|
||||
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;
|
||||
}
|
||||
|
||||
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))
|
||||
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)
|
||||
audio.AudioClips = new[] { audioClip };
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if this audio has multiple variations
|
||||
var audioClips = new List<AudioClip>();
|
||||
for (int i = 1; i < 50; i++)
|
||||
{
|
||||
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
|
||||
{
|
||||
// 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;
|
||||
Console.PrintError("AudioClip '" + soundName + "' not found");
|
||||
}
|
||||
|
||||
private class AudioInfo
|
||||
{
|
||||
public AudioClip[] AudioClips;
|
||||
public int lastAudioPlayed;
|
||||
}
|
||||
audio.lastAudioPlayed = audio.AudioClips.Length + 1;
|
||||
|
||||
private class ActorAudioChannels
|
||||
{
|
||||
public readonly Dictionary<int, AudioSource> channelSources;
|
||||
cachedAudioInfos.Add(soundName, audio);
|
||||
return audio;
|
||||
}
|
||||
|
||||
public ActorAudioChannels()
|
||||
{
|
||||
channelSources = new Dictionary<int, AudioSource>();
|
||||
}
|
||||
private class AudioInfo
|
||||
{
|
||||
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.Threading.Tasks;
|
||||
|
||||
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;
|
||||
}
|
||||
namespace Game;
|
||||
|
||||
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]
|
||||
[HideInEditor]
|
||||
public float PlayStartTime = 0f;
|
||||
FlaxEngine.Object.Destroy(Actor, Actor.As<AudioSourceDelayed>().Clip.Length + Actor.As<AudioSourceDelayed>().Delay);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public override void OnFixedUpdate()
|
||||
{
|
||||
if (FlaxEngine.Time.GameTime >= PlayStartTime)
|
||||
{
|
||||
PlayStartTime = float.MaxValue;
|
||||
Actor.As<AudioSourceDelayed>().Play();
|
||||
}
|
||||
PlayStartTime = float.MaxValue;
|
||||
Actor.As<AudioSourceDelayed>().Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +1,98 @@
|
||||
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;
|
||||
private float viewRoll;
|
||||
private float viewYaw;
|
||||
Float3 initialEulerAngles = Actor.Orientation.EulerAngles;
|
||||
viewPitch = initialEulerAngles.X;
|
||||
viewYaw = initialEulerAngles.Y;
|
||||
viewRoll = initialEulerAngles.Z;
|
||||
}
|
||||
|
||||
[Limit(0, 9000)]
|
||||
[Tooltip("Camera speed")]
|
||||
public float MoveSpeed { get; set; } = 400;
|
||||
public override void OnUpdate()
|
||||
{
|
||||
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 = initialEulerAngles.X;
|
||||
viewYaw = initialEulerAngles.Y;
|
||||
viewRoll = initialEulerAngles.Z;
|
||||
viewPitch += yAxis;
|
||||
viewYaw += xAxis;
|
||||
|
||||
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;
|
||||
Actor rootActor = Actor.GetChild(0);
|
||||
Camera camera = rootActor.GetChild<Camera>();
|
||||
move.Normalize();
|
||||
move = camera.Transform.TransformDirection(move) * MoveSpeed;
|
||||
|
||||
float xAxis = InputManager.GetAxisRaw("Mouse X");
|
||||
float yAxis = InputManager.GetAxisRaw("Mouse Y");
|
||||
if (xAxis != 0.0f || yAxis != 0.0f)
|
||||
{
|
||||
viewPitch += yAxis;
|
||||
viewYaw += xAxis;
|
||||
Float3 delta = move * Time.UnscaledDeltaTime;
|
||||
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
|
||||
rootActor.Orientation = Quaternion.Euler(0, viewYaw, 0);
|
||||
camera.Orientation = Quaternion.Euler(viewPitch, viewYaw, viewRoll);
|
||||
}
|
||||
|
||||
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;
|
||||
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)
|
||||
{
|
||||
Float3 delta = move * Time.UnscaledDeltaTime;
|
||||
float movementLeft = delta.Length;
|
||||
if (hitInfo.Collider.Parent == Parent)
|
||||
continue;
|
||||
|
||||
// TODO: check multiple times in case we get stuck in walls
|
||||
|
||||
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.Distance < hitDistance)
|
||||
{
|
||||
if (hitInfo.Collider.Parent == Parent)
|
||||
continue;
|
||||
|
||||
if (hitInfo.Distance < hitDistance)
|
||||
{
|
||||
hitDistance = hitInfo.Distance;
|
||||
hitNormal = hitInfo.Normal;
|
||||
}
|
||||
//nohit = false;
|
||||
//break;
|
||||
hitDistance = hitInfo.Distance;
|
||||
hitNormal = hitInfo.Normal;
|
||||
}
|
||||
|
||||
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;
|
||||
//nohit = false;
|
||||
//break;
|
||||
}
|
||||
}
|
||||
|
||||
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 FlaxEngine;
|
||||
|
||||
namespace Game
|
||||
namespace Game;
|
||||
|
||||
/// <summary>
|
||||
/// CameraRender Script.
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
public class CameraRender : PostProcessEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// CameraRender Script.
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
public class CameraRender : PostProcessEffect
|
||||
public Camera camera;
|
||||
|
||||
private bool lastEnabled;
|
||||
public Material material;
|
||||
|
||||
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;
|
||||
public Material material;
|
||||
// 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");
|
||||
|
||||
private MaterialInstance materialInstance;
|
||||
private SceneRenderTask sceneTask;
|
||||
private SceneRenderTask sceneTask2;
|
||||
// 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");
|
||||
}
|
||||
|
||||
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 override void OnAwake()
|
||||
{
|
||||
viewModelHolder = Parent.Parent.Parent.Parent.GetChild("ViewModelHolder");
|
||||
if (useMainCamera)
|
||||
{
|
||||
GPUTextureDescription textureDesc = GPUTextureDescription.New2D(width, height, PixelFormat.R8G8B8A8_UNorm);
|
||||
|
||||
// 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;
|
||||
void foo(Actor actor)
|
||||
{
|
||||
camera.IsActive = false;
|
||||
void foo(Actor actor)
|
||||
{
|
||||
actor.Layer = 0;
|
||||
foreach (Actor actChild in actor.GetChildren<Actor>())
|
||||
foo(actChild);
|
||||
}
|
||||
|
||||
foo(viewModelHolder);
|
||||
actor.Layer = 0;
|
||||
foreach (Actor actChild in actor.GetChildren<Actor>())
|
||||
foo(actChild);
|
||||
}
|
||||
|
||||
if (!camera.IsActive)
|
||||
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;
|
||||
foo(viewModelHolder);
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Destroy(ref sceneTask);
|
||||
Destroy(ref sceneTask2);
|
||||
Destroy(ref texture);
|
||||
Destroy(ref texture2);
|
||||
}
|
||||
if (!camera.IsActive)
|
||||
return;
|
||||
|
||||
public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input,
|
||||
GPUTexture output)
|
||||
{
|
||||
if (texture == null || texture2 == null)
|
||||
return;
|
||||
CreateTextures((int)camera.Viewport.Width, (int)camera.Viewport.Height);
|
||||
|
||||
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;
|
||||
public override void OnUpdate()
|
||||
{
|
||||
// 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()
|
||||
{
|
||||
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 (Input.GetKeyDown(KeyboardKeys.F7))
|
||||
{
|
||||
PhysicsSettings physicsSettings = GameSettings.Load<PhysicsSettings>();
|
||||
physicsSettings.EnableSubstepping = !physicsSettings.EnableSubstepping;
|
||||
GameSettings.Save(physicsSettings);
|
||||
//GameSettings.Apply();
|
||||
}
|
||||
if (Input.GetKeyDown(KeyboardKeys.F7))
|
||||
{
|
||||
PhysicsSettings physicsSettings = GameSettings.Load<PhysicsSettings>();
|
||||
physicsSettings.EnableSubstepping = !physicsSettings.EnableSubstepping;
|
||||
GameSettings.Save(physicsSettings);
|
||||
//GameSettings.Apply();
|
||||
}
|
||||
#endif
|
||||
|
||||
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()
|
||||
if (lastEnabled != camera.IsActive)
|
||||
{
|
||||
//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;
|
||||
|
||||
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;
|
||||
private Float3 lastPosition;
|
||||
public float percY;
|
||||
playerActor = Actor.Parent.Parent;
|
||||
playerMovement = playerActor.GetScript<PlayerMovement>();
|
||||
viewModelHolder = playerActor.GetChild("ViewModelHolder");
|
||||
|
||||
private Actor playerActor;
|
||||
private PlayerMovement playerMovement;
|
||||
lastGround = playerMovement.OnGround;
|
||||
targetOffset = Actor.LocalPosition;
|
||||
}
|
||||
|
||||
public float speed = 240f;
|
||||
private Float3 targetOffset;
|
||||
private Actor viewModelHolder;
|
||||
private void UpdatePosition(Float3 position)
|
||||
{
|
||||
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;
|
||||
playerMovement = playerActor.GetScript<PlayerMovement>();
|
||||
viewModelHolder = playerActor.GetChild("ViewModelHolder");
|
||||
|
||||
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 (Mathf.Abs(deltaY) < 10f)
|
||||
if (deltaY > 0)
|
||||
{
|
||||
float deltaY = position.Y - lastPosition.Y;
|
||||
//if (Mathf.Abs(deltaY) < 10f)
|
||||
if (deltaY > 0)
|
||||
if (deltaY > 100f)
|
||||
{
|
||||
if (deltaY > 100f)
|
||||
{
|
||||
// Teleported, snap instantly
|
||||
UpdatePosition(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
const float catchUpDistance = 10f;
|
||||
const float catchUpMinMultip = 0.25f;
|
||||
percY = Mathf.Abs(deltaY) / catchUpDistance;
|
||||
percY = Mathf.Min(1.0f, percY + catchUpMinMultip);
|
||||
percY *= percY;
|
||||
// Teleported, snap instantly
|
||||
UpdatePosition(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
const float catchUpDistance = 10f;
|
||||
const float catchUpMinMultip = 0.25f;
|
||||
percY = Mathf.Abs(deltaY) / catchUpDistance;
|
||||
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 = Mathf.MoveTowards(position.Y, targetPosition.Y, adjustSpeed);
|
||||
UpdatePosition(position);
|
||||
}
|
||||
position.Y = lastPosition.Y; //-= deltaY;
|
||||
position.Y = Mathf.MoveTowards(position.Y, targetPosition.Y, adjustSpeed);
|
||||
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 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;
|
||||
public float swaySpeed = 3000f;
|
||||
private Quaternion GetRotation()
|
||||
{
|
||||
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");
|
||||
cameraHolder = rootActor.GetChild("CameraHolder");
|
||||
Actor.LocalOrientation = GetRotation();
|
||||
}
|
||||
float stepTime = Mathf.Min(Time.DeltaTime, minTime);
|
||||
remaining -= stepTime;
|
||||
float swaySpeedScaled = swaySpeed * stepTime;
|
||||
|
||||
private Quaternion GetRotation()
|
||||
{
|
||||
Quaternion pitch = cameraHolder.LocalOrientation;
|
||||
Quaternion yawRoll = rootActor.LocalOrientation;
|
||||
return yawRoll * pitch;
|
||||
}
|
||||
float deltaX = Mathf.DeltaAngle(angles.X, targetAngles.X);
|
||||
float deltaY = Mathf.DeltaAngle(angles.Y, targetAngles.Y);
|
||||
float deltaZ = Mathf.DeltaAngle(angles.Z, targetAngles.Z);
|
||||
|
||||
public override void OnLateUpdate()
|
||||
{
|
||||
Quaternion rotation = GetRotation();
|
||||
const float maxAngle = 30f;
|
||||
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;
|
||||
|
||||
Float3 targetAngles = rotation.EulerAngles;
|
||||
Float3 angles = Actor.LocalOrientation.EulerAngles;
|
||||
float percX = Mathf.Abs(deltaX) / maxAngle;
|
||||
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
|
||||
float remaining = Time.DeltaTime + timeRemainder;
|
||||
const float minTime = 1f / 120f;
|
||||
do
|
||||
{
|
||||
float stepTime = Mathf.Min(Time.DeltaTime, minTime);
|
||||
remaining -= stepTime;
|
||||
float swaySpeedScaled = swaySpeed * stepTime;
|
||||
Func<float, float> fun = f => Mathf.Pow(f, 1.3f);
|
||||
|
||||
float deltaX = Mathf.DeltaAngle(angles.X, targetAngles.X);
|
||||
float deltaY = Mathf.DeltaAngle(angles.Y, targetAngles.Y);
|
||||
float deltaZ = Mathf.DeltaAngle(angles.Z, targetAngles.Z);
|
||||
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);
|
||||
|
||||
const float maxAngle = 30f;
|
||||
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;
|
||||
timeRemainder -= remaining;
|
||||
|
||||
float percX = Mathf.Abs(deltaX) / maxAngle;
|
||||
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);
|
||||
}
|
||||
Actor.LocalOrientation = Quaternion.Euler(angles);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,41 @@
|
||||
using System;
|
||||
|
||||
namespace Game
|
||||
namespace Game;
|
||||
|
||||
// Holds common miscellaneous Console variables and commands
|
||||
public static class CommonCommands
|
||||
{
|
||||
// Holds common miscellaneous Console variables and commands
|
||||
public static class CommonCommands
|
||||
[ConsoleVariable("developer")]
|
||||
public static string Developer
|
||||
{
|
||||
[ConsoleVariable("developer")]
|
||||
public static string Developer
|
||||
get => Console.DebugVerbosity.ToString();
|
||||
set
|
||||
{
|
||||
get => Console.DebugVerbosity.ToString();
|
||||
set
|
||||
{
|
||||
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));
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,33 @@
|
||||
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]
|
||||
{
|
||||
get => dictionary[key];
|
||||
set => dictionary[key] = value;
|
||||
}
|
||||
// This is for debugging only, remove this later
|
||||
public string[] Commands;
|
||||
|
||||
// This is for debugging only, remove this later
|
||||
public string[] Commands;
|
||||
public string[] GetLines()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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;
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
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))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,48 +2,48 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game
|
||||
namespace Game;
|
||||
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public abstract class ConsoleBaseAttribute : Attribute
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public abstract class ConsoleBaseAttribute : Attribute
|
||||
// Additional aliases for this command, these should only be used with user interaction.
|
||||
// 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.
|
||||
// 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; }
|
||||
this.name = name.ToLowerInvariant();
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public class ConsoleVariableAttribute : ConsoleBaseAttribute
|
||||
public ConsoleBaseAttribute(params string[] names)
|
||||
{
|
||||
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 class ConsoleCommandAttribute : ConsoleBaseAttribute
|
||||
public ConsoleFlags flags { get; private set; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public class ConsoleVariableAttribute : ConsoleBaseAttribute
|
||||
{
|
||||
public ConsoleVariableAttribute(string name) : base(name)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public class ConsoleCommandAttribute : ConsoleBaseAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a command to Console system.
|
||||
/// </summary>
|
||||
/// <param name="name">Name used for calling this command.</param>
|
||||
public ConsoleCommandAttribute(string name) : base(name)
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a command to Console system.
|
||||
@@ -53,15 +53,14 @@ namespace Game
|
||||
/// names are aliases.
|
||||
/// </param>
|
||||
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.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 ConsoleCommand(string name, MethodInfo[] method)
|
||||
public void Invoke()
|
||||
{
|
||||
foreach (MethodInfo method in methods)
|
||||
{
|
||||
this.name = name;
|
||||
methods = method;
|
||||
var methodParameters = method.GetParameters();
|
||||
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();
|
||||
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;
|
||||
match = method;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Exception("Unexpected number of parameters.");
|
||||
}
|
||||
if (methodParameters.Length != parameters.Length)
|
||||
continue;
|
||||
|
||||
public void Invoke(string[] parameters)
|
||||
{
|
||||
MethodInfo match = null;
|
||||
foreach (MethodInfo method in methods)
|
||||
{
|
||||
var methodParameters = method.GetParameters();
|
||||
if (methodParameters.Length == 1 && methodParameters[0].ParameterType == typeof(string[]))
|
||||
{
|
||||
match = method;
|
||||
// TODO: try to parse string parameters to needed types first,
|
||||
// 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;
|
||||
|
||||
// TODO: try to parse string parameters to needed types first,
|
||||
// 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.");
|
||||
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.");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,171 +2,170 @@
|
||||
using System.Linq;
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
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 (inputTextLower.Length > 0)
|
||||
{
|
||||
if ((mapping.Key == KeyboardKeys.Backslash || mapping.Key == KeyboardKeys.BackQuote) &&
|
||||
(inputTextLower.Contains('ö') ||
|
||||
inputTextLower.Contains('æ') ||
|
||||
inputTextLower.Contains('ø')))
|
||||
continue; // Scandinavian keyboard layouts
|
||||
if (mapping.Key == KeyboardKeys.BackQuote && inputTextLower.Contains('\''))
|
||||
continue;
|
||||
if (mapping.Key == KeyboardKeys.Backslash &&
|
||||
(inputTextLower.Contains('\\') || inputTextLower.Contains('|')))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Input.GetKey(mapping.Key))
|
||||
return true;
|
||||
if ((mapping.Key == KeyboardKeys.Backslash || mapping.Key == KeyboardKeys.BackQuote) &&
|
||||
(inputTextLower.Contains('ö') ||
|
||||
inputTextLower.Contains('æ') ||
|
||||
inputTextLower.Contains('ø')))
|
||||
continue; // Scandinavian keyboard layouts
|
||||
if (mapping.Key == KeyboardKeys.BackQuote && inputTextLower.Contains('\''))
|
||||
continue;
|
||||
if (mapping.Key == KeyboardKeys.Backslash &&
|
||||
(inputTextLower.Contains('\\') || inputTextLower.Contains('|')))
|
||||
continue;
|
||||
}
|
||||
|
||||
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())
|
||||
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);
|
||||
Clear();
|
||||
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");
|
||||
base.Draw();
|
||||
Profiler.EndEvent();
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
#endif
|
||||
|
||||
namespace Game
|
||||
namespace Game;
|
||||
|
||||
public class ConsolePlugin : GamePlugin
|
||||
{
|
||||
public class ConsolePlugin : GamePlugin
|
||||
public ConsolePlugin()
|
||||
{
|
||||
public ConsolePlugin()
|
||||
{
|
||||
#if FLAX_EDITOR
|
||||
_description = ConsoleEditorPlugin.DescriptionInternal;
|
||||
_description = ConsoleEditorPlugin.DescriptionInternal;
|
||||
#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
|
||||
public class ConsoleEditorPlugin : EditorPlugin
|
||||
private InputEvent onExit;
|
||||
|
||||
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",
|
||||
Name = "Console",
|
||||
Description = "Quake-like console",
|
||||
Version = Version.Parse("0.1.0"),
|
||||
IsAlpha = true,
|
||||
Category = "Game"
|
||||
if (Console.IsSafeToQuit)
|
||||
Engine.RequestExit();
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
//FlaxEngine.Debug.Log("ConsolePlugin initialized");
|
||||
Console.Init();
|
||||
Level.SceneLoading += OnSceneLoading;
|
||||
|
||||
/*onExit.Triggered += () =>
|
||||
{
|
||||
if (Console.IsSafeToQuit)
|
||||
Engine.RequestExit();
|
||||
};*/
|
||||
FlaxEditor.Editor.Instance.PlayModeBegin += OnPlayModeBegin;
|
||||
FlaxEditor.Editor.Instance.PlayModeEnd += OnPlayModeEnd;
|
||||
//Scripting.Exit += OnScriptingExit;
|
||||
|
||||
//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;
|
||||
FlaxEditor.Editor.Instance.PlayModeEnd += OnPlayModeEnd;
|
||||
//Scripting.Exit += OnScriptingExit;
|
||||
/*private void 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())
|
||||
Console.Execute(line, false, true);*/
|
||||
}
|
||||
private void OnPlayModeBegin()
|
||||
{
|
||||
//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.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
|
||||
}
|
||||
@@ -6,393 +6,392 @@ using FlaxEngine.Assertions;
|
||||
using FlaxEngine.GUI;
|
||||
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;
|
||||
private ConsoleContentTextBox consoleBox;
|
||||
FontReference fontReference = new FontReference(ConsoleFont, ConsoleFontSize);
|
||||
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;
|
||||
[Limit(5, 720)] public int ConsoleFontSize = 16;
|
||||
rootControl = Actor.AddChild<UIControl>();
|
||||
rootControl.Name = "ConsoleRoot";
|
||||
rootControl.Control = rootContainerControl;
|
||||
|
||||
[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()
|
||||
VerticalPanel contentContainer = new VerticalPanel
|
||||
{
|
||||
consoleInputEvent = new InputEvent("Console");
|
||||
consoleInputEvent.Pressed += OnConsoleInputEvent;
|
||||
AutoSize = true,
|
||||
Margin = Margin.Zero,
|
||||
Spacing = 0,
|
||||
Bounds = new Rectangle(),
|
||||
BackgroundColor = BackgroundColor
|
||||
};
|
||||
contentContainer.SetAnchorPreset(AnchorPresets.StretchAll, true);
|
||||
|
||||
FontReference fontReference = new FontReference(ConsoleFont, ConsoleFontSize);
|
||||
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);
|
||||
UIControl contentContainerControl = rootControl.AddChild<UIControl>();
|
||||
contentContainerControl.Name = "ContentContainer";
|
||||
contentContainerControl.Control = contentContainer;
|
||||
|
||||
rootControl = Actor.AddChild<UIControl>();
|
||||
rootControl.Name = "ConsoleRoot";
|
||||
rootControl.Control = rootContainerControl;
|
||||
|
||||
VerticalPanel contentContainer = new VerticalPanel
|
||||
{
|
||||
if (consoleBox == null)
|
||||
{
|
||||
AutoSize = true,
|
||||
Margin = Margin.Zero,
|
||||
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 = 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.AnchorMax = new Float2(1.0f, ConsoleHeight);
|
||||
//consoleBox.Height = consoleSize.Y - fontHeight;
|
||||
consoleBox.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, true);
|
||||
//consoleBox.AnchorMax = new Float2(1.0f, ConsoleHeight);
|
||||
//consoleBox.Height = consoleSize.Y - fontHeight;
|
||||
|
||||
//consoleBox.HorizontalAlignment = TextAlignment.Near;
|
||||
//consoleBox.VerticalAlignment = TextAlignment.Near;
|
||||
consoleBox.HeightMultiplier = ConsoleHeight;
|
||||
consoleBox.Wrapping = TextWrapping.WrapWords;
|
||||
consoleBox.BackgroundColor = Color.Transparent;
|
||||
consoleBox.BackgroundSelectedColor = Color.Transparent;
|
||||
consoleBox.BackgroundSelectedFlashSpeed = 0;
|
||||
consoleBox.BorderSelectedColor = Color.Transparent;
|
||||
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
|
||||
//consoleBox.HorizontalAlignment = TextAlignment.Near;
|
||||
//consoleBox.VerticalAlignment = TextAlignment.Near;
|
||||
consoleBox.HeightMultiplier = ConsoleHeight;
|
||||
consoleBox.Wrapping = TextWrapping.WrapWords;
|
||||
consoleBox.BackgroundColor = Color.Transparent;
|
||||
consoleBox.BackgroundSelectedColor = Color.Transparent;
|
||||
consoleBox.BackgroundSelectedFlashSpeed = 0;
|
||||
consoleBox.BorderSelectedColor = Color.Transparent;
|
||||
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)
|
||||
{
|
||||
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;
|
||||
//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);
|
||||
|
||||
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
|
||||
//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
|
||||
}
|
||||
{
|
||||
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
|
||||
//for (int i = 0; i < 10; i++)
|
||||
{
|
||||
string[] teststr = {
|
||||
/*
|
||||
"...loading 'scripts/devilpunch.shader'",
|
||||
"...loading 'scripts/mkoxide.shader'",
|
||||
"...loading 'scripts/cpm22.shader'",
|
||||
"...loading 'scripts/cpm27.shader'",
|
||||
"...loading 'scripts/island.shader'",
|
||||
"...loading 'scripts/noodtex3.shader'",
|
||||
"...loading 'scripts/nood_cosdglass.shader'",
|
||||
"...loading 'scripts/nood_fog_1000.shader'",
|
||||
"...loading 'scripts/nood_lightbeams.shader'",
|
||||
"...loading 'scripts/nood_nightsky_nolight.shader'",
|
||||
"Rescanning shaders",
|
||||
@"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: [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: initialized with 5 mice and 0 keyboards",
|
||||
"WASAPI: overriding channels",
|
||||
"WASAPI: 2 channel 32bit 48000khz non-exclusive",
|
||||
"WASAPI: requested periodicity: 128, default: 480, min: 128, max: 480, step: 16",
|
||||
"WASAPI: Low latency mode enabled",
|
||||
"WASAPI: buffer size: 280",
|
||||
"OpenGL renderer initialized",
|
||||
"video restart took 0.291480 seconds",
|
||||
"main thread video restart took 0.291798 secs",
|
||||
"[------ Goake Initialized ------]",
|
||||
"Initializing menu.dat",
|
||||
"Couldn't load sound/ambience/water1.wav",
|
||||
"Couldn't load sound/ambience/wind2.wav",
|
||||
"Couldn't load sound/misc/menu2.wav",
|
||||
"Couldn't load sound/misc/menu3.wav",
|
||||
"]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"
|
||||
*/
|
||||
"Warning: Unsupported entity field 'light'",
|
||||
"Warning: Unsupported entity field '_keeplights'",
|
||||
"Warning: Unsupported entity field 'light'",
|
||||
"Warning: Unsupported entity field '_keeplights'",
|
||||
"Warning: Unsupported entity field 'light'",
|
||||
"Warning: Unsupported entity field '_keeplights'",
|
||||
"Warning: Unsupported entity field 'light'",
|
||||
"Warning: Unsupported entity field '_keeplights'",
|
||||
"maps/aerowalk.bsp: Using lightmap format E5BGR9_UF",
|
||||
"maps/devbox.bsp: Using lightmap format E5BGR9_UF",
|
||||
"what",
|
||||
"Game mode changed to: Free For All",
|
||||
"what",
|
||||
"641 additional FS searches",
|
||||
"fixangle frame: 1427",
|
||||
"Couldn't load sound/ambience/wind2.wav",
|
||||
"Couldn't load sound/ambience/water1.wav"
|
||||
};
|
||||
//for (int i = 0; i < 10; i++)
|
||||
{
|
||||
string[] teststr = {
|
||||
/*
|
||||
"...loading 'scripts/devilpunch.shader'",
|
||||
"...loading 'scripts/mkoxide.shader'",
|
||||
"...loading 'scripts/cpm22.shader'",
|
||||
"...loading 'scripts/cpm27.shader'",
|
||||
"...loading 'scripts/island.shader'",
|
||||
"...loading 'scripts/noodtex3.shader'",
|
||||
"...loading 'scripts/nood_cosdglass.shader'",
|
||||
"...loading 'scripts/nood_fog_1000.shader'",
|
||||
"...loading 'scripts/nood_lightbeams.shader'",
|
||||
"...loading 'scripts/nood_nightsky_nolight.shader'",
|
||||
"Rescanning shaders",
|
||||
@"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: [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: initialized with 5 mice and 0 keyboards",
|
||||
"WASAPI: overriding channels",
|
||||
"WASAPI: 2 channel 32bit 48000khz non-exclusive",
|
||||
"WASAPI: requested periodicity: 128, default: 480, min: 128, max: 480, step: 16",
|
||||
"WASAPI: Low latency mode enabled",
|
||||
"WASAPI: buffer size: 280",
|
||||
"OpenGL renderer initialized",
|
||||
"video restart took 0.291480 seconds",
|
||||
"main thread video restart took 0.291798 secs",
|
||||
"[------ Goake Initialized ------]",
|
||||
"Initializing menu.dat",
|
||||
"Couldn't load sound/ambience/water1.wav",
|
||||
"Couldn't load sound/ambience/wind2.wav",
|
||||
"Couldn't load sound/misc/menu2.wav",
|
||||
"Couldn't load sound/misc/menu3.wav",
|
||||
"]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"
|
||||
*/
|
||||
"Warning: Unsupported entity field 'light'",
|
||||
"Warning: Unsupported entity field '_keeplights'",
|
||||
"Warning: Unsupported entity field 'light'",
|
||||
"Warning: Unsupported entity field '_keeplights'",
|
||||
"Warning: Unsupported entity field 'light'",
|
||||
"Warning: Unsupported entity field '_keeplights'",
|
||||
"Warning: Unsupported entity field 'light'",
|
||||
"Warning: Unsupported entity field '_keeplights'",
|
||||
"maps/aerowalk.bsp: Using lightmap format E5BGR9_UF",
|
||||
"maps/devbox.bsp: Using lightmap format E5BGR9_UF",
|
||||
"what",
|
||||
"Game mode changed to: Free For All",
|
||||
"what",
|
||||
"641 additional FS searches",
|
||||
"fixangle frame: 1427",
|
||||
"Couldn't load sound/ambience/wind2.wav",
|
||||
"Couldn't load sound/ambience/water1.wav"
|
||||
};
|
||||
|
||||
foreach (var l in teststr)
|
||||
Console.Print(l);
|
||||
}
|
||||
foreach (var l in teststr)
|
||||
Console.Print(l);
|
||||
}
|
||||
#endif
|
||||
/*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)
|
||||
/*FlaxEditor.Editor.Options.OptionsChanged += (FlaxEditor.Options.EditorOptions options) =>
|
||||
{
|
||||
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;
|
||||
if (assert != null)
|
||||
{
|
||||
string[] assertLines = assert.Message.Split('\n');
|
||||
if (assertLines.Length > 2)
|
||||
Console.Print("Assert Failure: " + assertLines[2]);
|
||||
else
|
||||
Console.Print("Assert Failure: " + assert.Message);
|
||||
}
|
||||
string[] assertLines = assert.Message.Split('\n');
|
||||
if (assertLines.Length > 2)
|
||||
Console.Print("Assert Failure: " + assertLines[2]);
|
||||
else
|
||||
{
|
||||
Console.Print("[EXCEP] " + exception.ToString());
|
||||
}
|
||||
Console.Print("Assert Failure: " + assert.Message);
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
else
|
||||
{
|
||||
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;
|
||||
Console.Print("[EXCEP] " + exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
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.GUI;
|
||||
|
||||
namespace Game
|
||||
namespace Game;
|
||||
|
||||
// Mostly based on TextBox
|
||||
public class ConsoleTextBoxBase : TextBoxBase
|
||||
{
|
||||
// Mostly based on TextBox
|
||||
public class ConsoleTextBoxBase : TextBoxBase
|
||||
private readonly Stopwatch lastDoubleClick = new Stopwatch();
|
||||
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;
|
||||
private Float2 lastDoubleClickLocation = new Float2(0, 0);
|
||||
public ConsoleTextBoxBase(float x, float y, float width, float height) : base(false, x, y, width)
|
||||
{
|
||||
Height = height;
|
||||
|
||||
/// <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);
|
||||
IsReadOnly = false;
|
||||
CaretColor = new Color(1f, 1f, 1f, 1f);
|
||||
AutoFocus = true;
|
||||
EndEditOnClick = false;
|
||||
|
||||
/// <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;
|
||||
_layout = TextLayoutOptions.Default;
|
||||
_layout.VerticalAlignment = IsMultiline ? TextAlignment.Near : TextAlignment.Center;
|
||||
_layout.TextWrapping = TextWrapping.NoWrap;
|
||||
_layout.Bounds =
|
||||
new Rectangle(0, 0, Width,
|
||||
Height); //new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2);
|
||||
}
|
||||
|
||||
//[HideInEditor]
|
||||
//public override string Text => _text;
|
||||
/// <summary>
|
||||
/// 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;
|
||||
CaretColor = new Color(1f, 1f, 1f, 1f);
|
||||
AutoFocus = true;
|
||||
EndEditOnClick = false;
|
||||
Text = value;
|
||||
|
||||
_layout = TextLayoutOptions.Default;
|
||||
_layout.VerticalAlignment = IsMultiline ? TextAlignment.Near : TextAlignment.Center;
|
||||
_layout.TextWrapping = TextWrapping.NoWrap;
|
||||
_layout.Bounds =
|
||||
new Rectangle(0, 0, Width,
|
||||
Height); //new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2);
|
||||
OnTextChanged();
|
||||
}
|
||||
}*/
|
||||
|
||||
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()
|
||||
{
|
||||
// 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>
|
||||
/// 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
|
||||
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 != "")
|
||||
{
|
||||
get => _layout.TextWrapping;
|
||||
set => _layout.TextWrapping = value;
|
||||
Float2 prefixSize = font.MeasureText(TextPrefix);
|
||||
location.X -= prefixSize.X;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the font.
|
||||
/// </summary>
|
||||
[EditorDisplay("Style")]
|
||||
[EditorOrder(2000)]
|
||||
public FontReference Font { get; set; }
|
||||
return font.HitTestText(Text, location, ref _layout);
|
||||
}
|
||||
|
||||
[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 (value == null)
|
||||
value = string.Empty;
|
||||
if (!IsReadOnly)
|
||||
SetSelection(_selectionStart, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Filter text
|
||||
if (value.IndexOf('\r') != -1)
|
||||
value = value.Replace("\r", "");
|
||||
if (shiftDown && key == KeyboardKeys.End)
|
||||
{
|
||||
if (!IsReadOnly)
|
||||
SetSelection(_selectionStart, TextLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clamp length
|
||||
if (value.Length > MaxLength)
|
||||
value = value.Substring(0, MaxLength);
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
// Ensure to use only single line
|
||||
if (_isMultiline == false && value.Length > 0)
|
||||
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
|
||||
{
|
||||
// Extract only the first line
|
||||
value = value.GetLines()[0];
|
||||
Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, rightEdge.X - leftEdge.X, fontHeight);
|
||||
Render2D.FillRectangle(r1, selectionColor);
|
||||
}
|
||||
|
||||
if (Text != value)
|
||||
else // Selected is more than one line
|
||||
{
|
||||
Deselect();
|
||||
ResetViewOffset();
|
||||
float leftMargin = _layout.Bounds.Location.X;
|
||||
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...
|
||||
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;
|
||||
|
||||
Float2 prefixSize = TextPrefix != "" ? font.MeasureText(TextPrefix) : new Float2();
|
||||
Rectangle caretBounds = CaretBounds;
|
||||
caretBounds.X += prefixSize.X;
|
||||
|
||||
float maxY = TextSize.Y - Height;
|
||||
|
||||
Float2 newLocation = CaretBounds.Location;
|
||||
TargetViewOffset = Float2.Clamp(newLocation, new Float2(0, 0), new Float2(_targetViewOffset.X, maxY));
|
||||
float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f);
|
||||
alpha = alpha * alpha * alpha * alpha * alpha * alpha;
|
||||
Render2D.FillRectangle(caretBounds, CaretColor * alpha);
|
||||
}
|
||||
|
||||
/*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)
|
||||
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);
|
||||
// TODO: probably better to just split these lines and parse each line separately
|
||||
clipboardText = clipboardText.Replace("\r\n", "");
|
||||
clipboardText = clipboardText.Replace("\n", "");
|
||||
}
|
||||
|
||||
public override Float2 GetCharPosition(int index, out float height)
|
||||
if (!string.IsNullOrEmpty(clipboardText))
|
||||
{
|
||||
Font font = Font.GetFont();
|
||||
if (font == null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
int right = SelectionRight;
|
||||
Insert(clipboardText);
|
||||
SetSelection(Mathf.Max(right, 0) + clipboardText.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +1,74 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
[Flags]
|
||||
public enum ConsoleFlags
|
||||
{
|
||||
NoSerialize = 1, // Value does not persist
|
||||
namespace Game;
|
||||
|
||||
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; }
|
||||
public ConsoleFlags flags { get; }
|
||||
this.name = name;
|
||||
this.flags = flags;
|
||||
field = null;
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
private readonly FieldInfo field;
|
||||
private readonly MethodInfo getter;
|
||||
private readonly MethodInfo setter;
|
||||
|
||||
public ConsoleVariable(string name, ConsoleFlags flags, FieldInfo field)
|
||||
public string GetValueString()
|
||||
{
|
||||
Type type = field != null ? field.FieldType : getter.ReturnType;
|
||||
if (type == typeof(string))
|
||||
{
|
||||
this.name = name;
|
||||
this.flags = flags;
|
||||
this.field = field;
|
||||
getter = null;
|
||||
setter = null;
|
||||
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");
|
||||
}
|
||||
|
||||
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;
|
||||
this.flags = flags;
|
||||
field = null;
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
if (field != null)
|
||||
field.SetValue(null, value);
|
||||
else if (setter != null)
|
||||
setter.Invoke(null, new object[] { value });
|
||||
}
|
||||
|
||||
public string GetValueString()
|
||||
else
|
||||
{
|
||||
Type type = field != null ? field.FieldType : getter.ReturnType;
|
||||
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);
|
||||
}
|
||||
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 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.BeginEvent(eventName);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,199 +7,198 @@ using FlaxEngine.Assertions;
|
||||
using FlaxEngine.Networking;
|
||||
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?
|
||||
// rename to NetworkReplicatedAttribute?
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class NetworkedAttribute : Attribute
|
||||
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);
|
||||
}
|
||||
|
||||
// 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,
|
||||
Message
|
||||
}
|
||||
if (initialized)
|
||||
return;
|
||||
|
||||
|
||||
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)
|
||||
/*if (Engine.CommandLine.Contains("-server"))
|
||||
{
|
||||
Assert.IsTrue(!OnClientMessageDelegates.Contains(deleg));
|
||||
OnClientMessageDelegates.Add(deleg);
|
||||
StartServer();
|
||||
ServerAddress = "localhost";
|
||||
ConnectServer();
|
||||
}
|
||||
public static void UnregisterClientMessageCallback(OnMessageDecl deleg)
|
||||
else if (Engine.CommandLine.Contains("-client"))
|
||||
{
|
||||
Assert.IsTrue(OnClientMessageDelegates.Contains(deleg));
|
||||
OnClientMessageDelegates.Remove(deleg);
|
||||
ServerAddress = "localhost";
|
||||
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
|
||||
else
|
||||
{
|
||||
StartServer();
|
||||
ServerAddress = "localhost";
|
||||
ConnectServer();
|
||||
}*/
|
||||
else
|
||||
{
|
||||
StartServer();
|
||||
ServerAddress = "localhost";
|
||||
ConnectServer();
|
||||
}*/
|
||||
//#endif
|
||||
|
||||
initialized = true;
|
||||
initialized = true;
|
||||
#if FLAX_EDITOR
|
||||
Editor.Instance.PlayModeEnd += Cleanup;
|
||||
Editor.Instance.PlayModeEnd += Cleanup;
|
||||
#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;
|
||||
Scripting.FixedUpdate -= OnDemoUpdate;
|
||||
Scripting.FixedUpdate -= OnServerNetworkUpdate;
|
||||
Level.ActorSpawned -= OnServerActorSpawned;
|
||||
Scripting.FixedUpdate -= OnClientNetworkUpdate;
|
||||
Level.ActorSpawned -= OnClientActorSpawned;
|
||||
NetworkPeer.ShutdownPeer(client);
|
||||
client = null;
|
||||
}
|
||||
|
||||
if (server != null)
|
||||
{
|
||||
NetworkPeer.ShutdownPeer(server);
|
||||
server = null;
|
||||
}
|
||||
|
||||
if (client != null)
|
||||
{
|
||||
NetworkPeer.ShutdownPeer(client);
|
||||
client = null;
|
||||
}
|
||||
|
||||
LocalPlayerClientId = 0;
|
||||
LocalPlayerClientId = 0;
|
||||
|
||||
#if FLAX_EDITOR
|
||||
Editor.Instance.PlayModeEnd -= Cleanup;
|
||||
//GameModeManager.Cleanup(); // FIXME
|
||||
Editor.Instance.PlayModeEnd -= Cleanup;
|
||||
//GameModeManager.Cleanup(); // FIXME
|
||||
#endif
|
||||
if (clientWorldStateManager != null)
|
||||
{
|
||||
clientWorldStateManager.Cleanup();
|
||||
clientWorldStateManager = null;
|
||||
}
|
||||
if (serverWorldStateManager != null)
|
||||
{
|
||||
serverWorldStateManager.Cleanup();
|
||||
serverWorldStateManager = null;
|
||||
}
|
||||
|
||||
StopRecording();
|
||||
|
||||
initialized = false;
|
||||
if (clientWorldStateManager != null)
|
||||
{
|
||||
clientWorldStateManager.Cleanup();
|
||||
clientWorldStateManager = null;
|
||||
}
|
||||
if (serverWorldStateManager != null)
|
||||
{
|
||||
serverWorldStateManager.Cleanup();
|
||||
serverWorldStateManager = null;
|
||||
}
|
||||
|
||||
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();
|
||||
if (!Enum.IsDefined(typeof(NetworkMessageType), messageTypeByte))
|
||||
Console.PrintError($"Unsupported message type received from client: {messageTypeByte}");
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkMessageType messageType = (NetworkMessageType)messageTypeByte;
|
||||
|
||||
switch (messageType)
|
||||
{
|
||||
case NetworkMessageType.Handshake:
|
||||
{
|
||||
Console.PrintError($"Unsupported message type received from client: {messageTypeByte}");
|
||||
return;
|
||||
string message = networkEvent.Message.ReadString();
|
||||
Console.Print($"Received handshake from {networkEvent.Sender.ConnectionId}, msg: " + message);
|
||||
break;
|
||||
}
|
||||
|
||||
NetworkMessageType messageType = (NetworkMessageType)messageTypeByte;
|
||||
|
||||
switch (messageType)
|
||||
case NetworkMessageType.Message:
|
||||
{
|
||||
case NetworkMessageType.Handshake:
|
||||
var messageStartPosition = networkEvent.Message.Position;
|
||||
foreach (var func in funcs)
|
||||
{
|
||||
string message = networkEvent.Message.ReadString();
|
||||
Console.Print($"Received handshake from {networkEvent.Sender.ConnectionId}, msg: " + message);
|
||||
break;
|
||||
networkEvent.Message.Position = messageStartPosition;
|
||||
bool handled = func(ref networkEvent);
|
||||
if (handled)
|
||||
break;
|
||||
}
|
||||
case NetworkMessageType.Message:
|
||||
{
|
||||
var messageStartPosition = networkEvent.Message.Position;
|
||||
foreach (var func in funcs)
|
||||
|
||||
//if (OnMessage != null)
|
||||
/*{
|
||||
// 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 handled = func(ref networkEvent);
|
||||
if (handled)
|
||||
bool ret = func.Invoke(ref networkEvent);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
//if (OnMessage != null)
|
||||
/*{
|
||||
// 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;
|
||||
}*/
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Console.PrintError($"Unsupported message type received from client: {messageTypeByte}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,153 +6,152 @@ using FlaxEngine.Networking;
|
||||
using Console = Game.Console;
|
||||
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; }
|
||||
|
||||
public static INetworkDriver ClientNetworkDriver { get; set; }
|
||||
|
||||
public static WorldStateManager clientWorldStateManager = null;
|
||||
|
||||
public static bool ConnectServer(string serverAddress = "localhost", bool listenServer = false)
|
||||
if (!listenServer)
|
||||
{
|
||||
if (!listenServer)
|
||||
{
|
||||
Cleanup();
|
||||
//WorldStateManager.Init();
|
||||
clientWorldStateManager = new WorldStateManager(isClient: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
clientWorldStateManager = new WorldStateManager(isLocalClient: true);
|
||||
}
|
||||
ServerAddress = serverAddress;
|
||||
Cleanup();
|
||||
//WorldStateManager.Init();
|
||||
clientWorldStateManager = new WorldStateManager(isClient: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
clientWorldStateManager = new WorldStateManager(isLocalClient: true);
|
||||
}
|
||||
ServerAddress = serverAddress;
|
||||
|
||||
//var driver = Object.New(typeof(ENetDriver));
|
||||
//ClientNetworkDriver = null;
|
||||
NetworkLagDriver driver = Object.New<NetworkLagDriver>();
|
||||
driver.Lag = 0f;
|
||||
ClientNetworkDriver = driver;
|
||||
//var driver = Object.New(typeof(ENetDriver));
|
||||
//ClientNetworkDriver = null;
|
||||
NetworkLagDriver driver = Object.New<NetworkLagDriver>();
|
||||
driver.Lag = 0f;
|
||||
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 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;
|
||||
public uint SenderId;
|
||||
public uint MessageId;
|
||||
public uint Length;
|
||||
Cleanup();
|
||||
clientWorldStateManager = new WorldStateManager(isServer: true);
|
||||
}
|
||||
|
||||
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)
|
||||
if (!ReadDemo(demoName))
|
||||
{
|
||||
{
|
||||
Cleanup();
|
||||
clientWorldStateManager = new WorldStateManager(isServer: true);
|
||||
}
|
||||
Console.Print("Failed to read demo.");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*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.");
|
||||
return false;
|
||||
}
|
||||
EventType = networkEvent.EventType,
|
||||
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)
|
||||
{
|
||||
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;
|
||||
// TODO: free networkEvent buffer
|
||||
}
|
||||
|
||||
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)
|
||||
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);
|
||||
Console.Print($"Demo version mismatch, expected {DemoVer}, got {ver}");
|
||||
stream.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
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,
|
||||
SenderId = networkEvent.Sender.ConnectionId,
|
||||
Length = networkEvent.Message.Length,
|
||||
MessageId = networkEvent.Message.MessageId,
|
||||
};
|
||||
MemoryMarshal.Write(bytes, header);
|
||||
demoStream.Write(bytes);
|
||||
int readBytes = stream.Read(headerBuffer.Slice(headerSize - bytesLeftInBuffer, bytesLeftInBuffer));
|
||||
if (readBytes == 0)
|
||||
break;
|
||||
bytesLeftInBuffer -= readBytes;
|
||||
} while (bytesLeftInBuffer > 0);
|
||||
|
||||
Span<byte> messageBytes = new Span<byte>(networkEvent.Message.Buffer, (int)networkEvent.Message.Length);
|
||||
demoStream.Write(messageBytes);
|
||||
|
||||
// TODO: free networkEvent buffer
|
||||
if (bytesLeftInBuffer > 0)
|
||||
break; // EOF;
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
|
||||
flushedFrames += buffer.Count;
|
||||
buffer.Clear();
|
||||
DemoNetworkEventHeader header = MemoryMarshal.Read<DemoNetworkEventHeader>(headerBuffer);
|
||||
|
||||
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 (!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 (header.Length > 0)
|
||||
{
|
||||
Console.Print($"Demo version mismatch, expected {DemoVer}, got {ver}");
|
||||
stream.Close();
|
||||
return false;
|
||||
}
|
||||
networkEvent.Message.Buffer = (byte*)NativeMemory.Alloc(header.Length);
|
||||
|
||||
int headerSize = Unsafe.SizeOf<DemoNetworkEventHeader>();
|
||||
Span<byte> headerBuffer = stackalloc byte[headerSize];
|
||||
while (true)
|
||||
{
|
||||
Span<byte> messageBufferSpan = new Span<byte>(networkEvent.Message.Buffer, (int)networkEvent.Message.BufferSize);
|
||||
{
|
||||
int bytesLeftInBuffer = headerSize;
|
||||
int bytesLeftInBuffer = (int)header.Length;
|
||||
do
|
||||
{
|
||||
int readBytes = stream.Read(headerBuffer.Slice(headerSize - bytesLeftInBuffer, bytesLeftInBuffer));
|
||||
int readBytes = stream.Read(messageBufferSpan.Slice((int)header.Length - bytesLeftInBuffer, bytesLeftInBuffer));
|
||||
if (readBytes == 0)
|
||||
break;
|
||||
bytesLeftInBuffer -= readBytes;
|
||||
@@ -178,101 +208,70 @@ namespace Game
|
||||
if (bytesLeftInBuffer > 0)
|
||||
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);
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
|
||||
bufferEnumerable = buffer.GetEnumerator();
|
||||
|
||||
Console.Print($"Demo parse time {sw.Elapsed.TotalMilliseconds}ms, frames: {buffer.Count} ");
|
||||
|
||||
DemoEndFrame(); // advances to first frame
|
||||
return true;
|
||||
buffer.Add(networkEvent);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
/*asdf++;
|
||||
if (asdf < 8)
|
||||
return;*/
|
||||
|
||||
if (bufferEnumerable == null || !bufferEnumerable.MoveNext())
|
||||
if (buffer.Any())
|
||||
{
|
||||
if (buffer.Any())
|
||||
{
|
||||
bufferEnumerable.Dispose();
|
||||
bufferEnumerable = null;
|
||||
buffer.Clear();
|
||||
Console.Print("Demo ended");
|
||||
}
|
||||
|
||||
return;
|
||||
bufferEnumerable.Dispose();
|
||||
bufferEnumerable = null;
|
||||
buffer.Clear();
|
||||
Console.Print("Demo ended");
|
||||
}
|
||||
|
||||
//var actorState = currentState.actor;
|
||||
//currentState.input = bufferEnumerable.Current;
|
||||
//frame++;
|
||||
//currentState.actor = actorState;
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
|
||||
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();
|
||||
//IsLocalClient = server != null;
|
||||
//IsClient = true;
|
||||
OnClientReadMessage(ref demoEvent);
|
||||
}
|
||||
finally
|
||||
{
|
||||
//IsLocalClient = false;
|
||||
//IsClient = false;
|
||||
//TODO: recycle event?
|
||||
}
|
||||
|
||||
DemoEndFrame();
|
||||
}
|
||||
}
|
||||
@@ -8,206 +8,205 @@ using FlaxEngine.Networking;
|
||||
using Console = Game.Console;
|
||||
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;
|
||||
|
||||
public static bool StartServer(bool listenServer = true)
|
||||
server = NetworkPeer.CreatePeer(new NetworkConfig
|
||||
{
|
||||
Cleanup();
|
||||
ConnectedClients = new List<NetworkConnection>(MaximumClients);
|
||||
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;
|
||||
|
||||
//var driver = Object.New(typeof(ENetDriver));
|
||||
//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>();
|
||||
NetworkedTypes = new List<Type>();
|
||||
|
||||
#if false
|
||||
var assemblies = Utils.GetAssemblies();
|
||||
var assemblies = Utils.GetAssemblies();
|
||||
|
||||
foreach (Assembly assembly in assemblies)
|
||||
{
|
||||
// Skip common assemblies
|
||||
string assemblyName = assembly.GetName().Name;
|
||||
if (assemblyName == "System" ||
|
||||
assemblyName.StartsWith("System.") ||
|
||||
assemblyName.StartsWith("Mono.") ||
|
||||
assemblyName == "mscorlib" ||
|
||||
assemblyName == "Newtonsoft.Json" ||
|
||||
assemblyName == "Snippets" ||
|
||||
assemblyName == "netstandard" ||
|
||||
assemblyName == "Anonymously Hosted DynamicMethods Assembly" ||
|
||||
assemblyName.StartsWith("FlaxEngine.") ||
|
||||
assemblyName.StartsWith("JetBrains.") ||
|
||||
assemblyName.StartsWith("Microsoft.") ||
|
||||
assemblyName.StartsWith("nunit."))
|
||||
continue;
|
||||
foreach (Assembly assembly in assemblies)
|
||||
{
|
||||
// Skip common assemblies
|
||||
string assemblyName = assembly.GetName().Name;
|
||||
if (assemblyName == "System" ||
|
||||
assemblyName.StartsWith("System.") ||
|
||||
assemblyName.StartsWith("Mono.") ||
|
||||
assemblyName == "mscorlib" ||
|
||||
assemblyName == "Newtonsoft.Json" ||
|
||||
assemblyName == "Snippets" ||
|
||||
assemblyName == "netstandard" ||
|
||||
assemblyName == "Anonymously Hosted DynamicMethods Assembly" ||
|
||||
assemblyName.StartsWith("FlaxEngine.") ||
|
||||
assemblyName.StartsWith("JetBrains.") ||
|
||||
assemblyName.StartsWith("Microsoft.") ||
|
||||
assemblyName.StartsWith("nunit."))
|
||||
continue;
|
||||
|
||||
foreach (Type type in assembly.GetTypes())
|
||||
if (type.GetCustomAttributes().Any(x => x is NetworkedAttribute))
|
||||
NetworkedTypes.Add(type);
|
||||
}
|
||||
foreach (Type type in assembly.GetTypes())
|
||||
if (type.GetCustomAttributes().Any(x => x is NetworkedAttribute))
|
||||
NetworkedTypes.Add(type);
|
||||
}
|
||||
|
||||
foreach (Type type in NetworkedTypes)
|
||||
Console.Print("tracking networked type: " + type.Name);
|
||||
foreach (Type type in NetworkedTypes)
|
||||
Console.Print("tracking networked type: " + type.Name);
|
||||
#endif
|
||||
serverWorldStateManager = new WorldStateManager(isServer: true);
|
||||
//WorldStateManager.Init();
|
||||
serverWorldStateManager = new WorldStateManager(isServer: true);
|
||||
//WorldStateManager.Init();
|
||||
|
||||
if (listenServer)
|
||||
return ConnectServer(listenServer: true);
|
||||
return true;
|
||||
}
|
||||
if (listenServer)
|
||||
return ConnectServer(listenServer: 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();
|
||||
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:
|
||||
{
|
||||
case NetworkEventType.Connected:
|
||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) is trying to connect");
|
||||
try
|
||||
{
|
||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) is trying to connect");
|
||||
try
|
||||
//IsServer = true;
|
||||
if (serverWorldStateManager.OnClientConnecting(networkEvent.Sender))
|
||||
{
|
||||
//IsServer = true;
|
||||
if (serverWorldStateManager.OnClientConnecting(networkEvent.Sender))
|
||||
{
|
||||
ConnectedClients.Add(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);
|
||||
}
|
||||
else
|
||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) connection refused");
|
||||
serverWorldStateManager.OnClientConnected(networkEvent.Sender);
|
||||
}
|
||||
finally
|
||||
{
|
||||
//IsServer = false;
|
||||
}
|
||||
|
||||
break;
|
||||
else
|
||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) connection refused");
|
||||
}
|
||||
case NetworkEventType.Disconnected:
|
||||
case NetworkEventType.Timeout:
|
||||
finally
|
||||
{
|
||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) disconnected!");
|
||||
|
||||
ConnectedClients.Remove(networkEvent.Sender);
|
||||
Console.Print("Connected clients: " + ConnectedClients.Count);
|
||||
break;
|
||||
//IsServer = false;
|
||||
}
|
||||
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();
|
||||
break;
|
||||
}
|
||||
}
|
||||
case NetworkEventType.Disconnected:
|
||||
case NetworkEventType.Timeout:
|
||||
{
|
||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) disconnected!");
|
||||
|
||||
private static void OnServerActorSpawned(Actor actor)
|
||||
{
|
||||
//Console.Print($"actor spawned: {actor.Name} ({actor.TypeName})");
|
||||
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 &&
|
||||
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.GUI;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
namespace Game;
|
||||
|
||||
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()
|
||||
{
|
||||
var uiControl = Actor.As<UIControl>();
|
||||
//control = new Image();
|
||||
//uiControl.Control = control;
|
||||
control = (Image)uiControl.Control;
|
||||
}
|
||||
|
||||
control = (Image)uiControl.Control;
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
}
|
||||
public override void OnUpdate()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -3,71 +3,71 @@ using System.Diagnostics;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace Game
|
||||
namespace Game;
|
||||
|
||||
[ExecuteInEditMode]
|
||||
public class PerformanceWidget : Script
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
public class PerformanceWidget : Script
|
||||
private const double updateInterval = 0.2;
|
||||
|
||||
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;
|
||||
private Label label;
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
stopwatch2 = Stopwatch.StartNew();
|
||||
}
|
||||
|
||||
private Stopwatch stopwatch;
|
||||
private Stopwatch stopwatch2;
|
||||
|
||||
private double updateTimeAvg;
|
||||
private double updateTimeAvg2;
|
||||
private ulong updateTimeCount;
|
||||
private ulong updateTimeCount2;
|
||||
|
||||
public override void OnAwake()
|
||||
public override void OnUpdate()
|
||||
{
|
||||
updateTimeCount2++;
|
||||
double elapsed2 = stopwatch2.Elapsed.TotalSeconds;
|
||||
if (elapsed2 >= updateInterval * 10)
|
||||
{
|
||||
label = (Label)control.Control;
|
||||
label.Text = $"0fps";
|
||||
control.HideFlags = HideFlags.None;
|
||||
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
stopwatch2 = Stopwatch.StartNew();
|
||||
stopwatch2.Restart();
|
||||
updateTimeAvg2 = elapsed2 / updateTimeCount2;
|
||||
updateTimeCount2 = 0;
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
updateTimeCount++;
|
||||
double elapsed = stopwatch.Elapsed.TotalSeconds;
|
||||
if (elapsed >= updateInterval)
|
||||
{
|
||||
updateTimeCount2++;
|
||||
double elapsed2 = stopwatch2.Elapsed.TotalSeconds;
|
||||
if (elapsed2 >= updateInterval * 10)
|
||||
{
|
||||
stopwatch2.Restart();
|
||||
updateTimeAvg2 = elapsed2 / updateTimeCount2;
|
||||
updateTimeCount2 = 0;
|
||||
}
|
||||
stopwatch.Restart();
|
||||
updateTimeAvg = elapsed / updateTimeCount;
|
||||
updateTimeCount = 0;
|
||||
|
||||
updateTimeCount++;
|
||||
double elapsed = stopwatch.Elapsed.TotalSeconds;
|
||||
if (elapsed >= updateInterval)
|
||||
{
|
||||
stopwatch.Restart();
|
||||
updateTimeAvg = elapsed / updateTimeCount;
|
||||
updateTimeCount = 0;
|
||||
label.Text = "";
|
||||
|
||||
label.Text = "";
|
||||
|
||||
long triangles = 0;
|
||||
long drawCalls = 0;
|
||||
long triangles = 0;
|
||||
long drawCalls = 0;
|
||||
|
||||
#if BUILD_DEBUG || BUILD_DEVELOPMENT
|
||||
var gpuEvents = ProfilingTools.EventsGPU;
|
||||
if (gpuEvents.Length > 0)
|
||||
{
|
||||
var gpuEvents = ProfilingTools.EventsGPU;
|
||||
if (gpuEvents.Length > 0)
|
||||
{
|
||||
triangles = gpuEvents[0].Stats.Triangles;
|
||||
drawCalls = gpuEvents[0].Stats.DrawCalls;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
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 / updateTimeAvg)}fps";
|
||||
}
|
||||
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 / updateTimeAvg)}fps";
|
||||
}
|
||||
|
||||
#if false
|
||||
#if BUILD_DEVELOPMENT
|
||||
@@ -98,6 +98,5 @@ namespace Game
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,17 @@ using System.Diagnostics;
|
||||
using FlaxEngine;
|
||||
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;
|
||||
|
||||
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>
|
||||
/// List of supported materials for loaded levels.
|
||||
/// Maps the given texture/shader name to Flax Material/MaterialInstance.
|
||||
/// </summary>
|
||||
public class BrushMaterialList
|
||||
{
|
||||
[EditorDisplay(name: "Material Assets")]
|
||||
public BrushMaterialListEntry[] materialAssets;
|
||||
}
|
||||
[EditorDisplay(name: "Material Assets")]
|
||||
public BrushMaterialListEntry[] materialAssets;
|
||||
}
|
||||
|
||||
public struct BrushMaterialListEntry
|
||||
{
|
||||
[EditorOrder(1)] [EditorDisplay(name: "Name")]
|
||||
public string name;
|
||||
public struct BrushMaterialListEntry
|
||||
{
|
||||
[EditorOrder(1)] [EditorDisplay(name: "Name")]
|
||||
public string name;
|
||||
|
||||
[EditorOrder(2)] [EditorDisplay(name: "Material")]
|
||||
public MaterialBase asset;
|
||||
}
|
||||
[EditorOrder(2)] [EditorDisplay(name: "Material")]
|
||||
public MaterialBase asset;
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace Game
|
||||
namespace Game;
|
||||
|
||||
[ExecuteInEditMode]
|
||||
public class BrushScript : Script
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
public class BrushScript : Script
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,17 @@
|
||||
using FlaxEngine;
|
||||
using FlaxEditor;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
public class LevelScript : Script
|
||||
{
|
||||
public string MapName;
|
||||
public DateTime MapTimestamp;
|
||||
}
|
||||
namespace Game;
|
||||
|
||||
public class LevelScript2 : Script
|
||||
{
|
||||
public string MapName;
|
||||
public DateTime MapTimestamp;
|
||||
}
|
||||
[ExecuteInEditMode]
|
||||
public class LevelScript : Script
|
||||
{
|
||||
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/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 string texture;
|
||||
public Float2 offset;
|
||||
public float rotation;
|
||||
public Float2 scale;
|
||||
public int contentFlags, surfaceFlags, surfaceValue;
|
||||
}
|
||||
public Float3 v1, v2, v3;
|
||||
public Plane plane;
|
||||
public string texture;
|
||||
public Float2 offset;
|
||||
public float rotation;
|
||||
public Float2 scale;
|
||||
public int contentFlags, surfaceFlags, surfaceValue;
|
||||
}
|
||||
|
||||
public class MapBrush
|
||||
{
|
||||
public MapFacePlane[] planes;
|
||||
}
|
||||
public class MapBrush
|
||||
{
|
||||
public MapFacePlane[] planes;
|
||||
}
|
||||
|
||||
public class MapPatch
|
||||
{
|
||||
public string name;
|
||||
}
|
||||
public class MapPatch
|
||||
{
|
||||
public string name;
|
||||
}
|
||||
|
||||
public struct PatchVertex
|
||||
{
|
||||
public Float3 v;
|
||||
public Float2 uv;
|
||||
}
|
||||
public struct PatchVertex
|
||||
{
|
||||
public Float3 v;
|
||||
public Float2 uv;
|
||||
}
|
||||
|
||||
public class MapEntity
|
||||
{
|
||||
public List<MapBrush> brushes = new List<MapBrush>();
|
||||
public List<MapEntity> entities = new List<MapEntity>();
|
||||
public List<MapPatch> patches = new List<MapPatch>();
|
||||
public Dictionary<string, string> properties = new Dictionary<string, string>();
|
||||
}
|
||||
public class MapEntity
|
||||
{
|
||||
public List<MapBrush> brushes = new List<MapBrush>();
|
||||
public List<MapEntity> entities = new List<MapEntity>();
|
||||
public List<MapPatch> patches = new List<MapPatch>();
|
||||
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)
|
||||
return null;
|
||||
char c = (char)data[index];
|
||||
char c1 = index + 1 < data.Length ? (char)data[index + 1] : (char)0;
|
||||
|
||||
MapEntity rootEntity = new MapEntity();
|
||||
MapEntity currentEntity = rootEntity;
|
||||
|
||||
int level = 0;
|
||||
int index = 0;
|
||||
//for (int i=0; i<data.Length; i++)
|
||||
do
|
||||
switch (c)
|
||||
{
|
||||
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;
|
||||
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')
|
||||
case '\n':
|
||||
case '\r':
|
||||
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 '/':
|
||||
if (c1 == '/')
|
||||
ParseComment(data, ref index);
|
||||
else
|
||||
throw new Exception("unexpected character: '" + c + "'");
|
||||
break;
|
||||
case '{':
|
||||
{
|
||||
case '\n':
|
||||
case '\r':
|
||||
break;
|
||||
currentEntity = new MapEntity();
|
||||
rootEntity.entities.Add(currentEntity);
|
||||
|
||||
case '/':
|
||||
if (c1 == '/')
|
||||
ParseComment(data, ref index);
|
||||
else
|
||||
throw new Exception("unexpected character: '" + c + "'");
|
||||
break;
|
||||
level++;
|
||||
|
||||
// "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)
|
||||
for (; index < data.Length; index++)
|
||||
if (data[index] == '\n')
|
||||
break;
|
||||
index++;
|
||||
|
||||
if (data[index] == '(')
|
||||
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;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Exception("unsupported character: '" + c + "'");
|
||||
ParseEntity(currentEntity, data, ref index);
|
||||
break;
|
||||
}
|
||||
} while (index++ < data.Length && !entityParsed);
|
||||
}
|
||||
|
||||
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 '}':
|
||||
{
|
||||
case '\r':
|
||||
case '\n':
|
||||
//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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// brush face (quake format):
|
||||
// ( <v1> ) ( <v2> ) ( <v3> ) <shader> <x_shift> <y_shift> <rotation> <x_scale> <y_scale>
|
||||
if (data[index] == '(')
|
||||
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):
|
||||
// ( <v1> ) ( <v2> ) ( <v3> ) <shader> <x_shift> <y_shift> <rotation> <x_scale> <y_scale> <content_flags> <surface_flags> <value>
|
||||
default:
|
||||
throw new Exception("unsupported character: '" + c + "'");
|
||||
}
|
||||
} while (index++ < data.Length && !entityParsed);
|
||||
}
|
||||
|
||||
// brush face (valve format):
|
||||
// ( <v1> ) ( <v2> ) ( <v3> ) <shader> [ <tx1> <ty1> <tz1> <toffs1> ] [ <tx2> <ty2> <tz2> <toffs2> ] <rotation> <x_scale> <y_scale>
|
||||
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':
|
||||
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.v1 = ParsePlaneFloat3(data, ref index);
|
||||
plane.v2 = ParsePlaneFloat3(data, ref index);
|
||||
plane.v3 = ParsePlaneFloat3(data, ref index);
|
||||
plane.texture = ParseString(data, ref index);
|
||||
plane.offset = ParseFloat2(data, ref index);
|
||||
plane.rotation = ParseFloat(data, ref index);
|
||||
plane.scale = ParseFloat2(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.rotation = ParseFloat(data, ref index);
|
||||
plane.scale = ParseFloat2(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);
|
||||
}
|
||||
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 '}':
|
||||
{
|
||||
brushParsed = true;
|
||||
break;
|
||||
}
|
||||
// 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);
|
||||
|
||||
default:
|
||||
if (char.IsLetter(c) || char.IsNumber(c))
|
||||
{
|
||||
// patch name
|
||||
}
|
||||
plane.plane = new Plane(plane.v1, plane.v2, plane.v3);
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
if (data[index] == ')')
|
||||
break;
|
||||
index++;
|
||||
}
|
||||
|
||||
// unfinished and untested
|
||||
private static void ParsePatchInner(MapPatch patch, byte[] data, ref int index)
|
||||
index++;
|
||||
|
||||
while (index < data.Length)
|
||||
{
|
||||
string shaderName = ParseString(data, ref index);
|
||||
|
||||
while (index < data.Length)
|
||||
{
|
||||
if (data[index] == '(')
|
||||
break;
|
||||
index++;
|
||||
}
|
||||
|
||||
if (data[index] == '(')
|
||||
break;
|
||||
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);
|
||||
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;
|
||||
index++;
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
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 '(':
|
||||
{
|
||||
case '\r':
|
||||
case '\n':
|
||||
break;
|
||||
index++;
|
||||
|
||||
case '(':
|
||||
for (int iw = 0; iw < width; iw++)
|
||||
{
|
||||
index++;
|
||||
|
||||
for (int iw = 0; iw < width; iw++)
|
||||
while (index < data.Length)
|
||||
{
|
||||
while (index < data.Length)
|
||||
{
|
||||
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++;
|
||||
}
|
||||
|
||||
if (data[index] == '(')
|
||||
break;
|
||||
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:
|
||||
throw new Exception("unsupported character: '" + c + "'");
|
||||
break;
|
||||
}
|
||||
} 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>
|
||||
public class ConvexHullCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant representing a point that has yet to be assigned to a
|
||||
/// face. It's only used immediately after constructing the seed hull.
|
||||
/// </summary>
|
||||
private const int UNASSIGNED = -2;
|
||||
/// <summary>
|
||||
/// Constant representing a point that has yet to be assigned to a
|
||||
/// face. It's only used immediately after constructing the seed hull.
|
||||
/// </summary>
|
||||
private const int UNASSIGNED = -2;
|
||||
|
||||
/// <summary>
|
||||
/// Constant representing a point that is inside the convex hull, and
|
||||
/// thus is behind all faces. In the openSet array, all points with
|
||||
/// INSIDE are at the end of the array, with indexes larger
|
||||
/// openSetTail.
|
||||
/// </summary>
|
||||
private const int INSIDE = -1;
|
||||
/// <summary>
|
||||
/// Constant representing a point that is inside the convex hull, and
|
||||
/// thus is behind all faces. In the openSet array, all points with
|
||||
/// INSIDE are at the end of the array, with indexes larger
|
||||
/// openSetTail.
|
||||
/// </summary>
|
||||
private const int INSIDE = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Epsilon value. If the coordinates of the point space are
|
||||
/// exceptionally close to each other, this value might need to be
|
||||
/// adjusted.
|
||||
/// </summary>
|
||||
private const float EPSILON = 0.001f;
|
||||
/// <summary>
|
||||
/// Epsilon value. If the coordinates of the point space are
|
||||
/// exceptionally close to each other, this value might need to be
|
||||
/// adjusted.
|
||||
/// </summary>
|
||||
private const float EPSILON = 0.001f;
|
||||
|
||||
/// <summary>
|
||||
/// When adding a new face to the faces Dictionary, use this for the
|
||||
/// key and then increment it.
|
||||
/// </summary>
|
||||
private int faceCount;
|
||||
/// <summary>
|
||||
/// When adding a new face to the faces Dictionary, use this for the
|
||||
/// key and then increment it.
|
||||
/// </summary>
|
||||
private int faceCount;
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary storing the faces of the currently generated convex
|
||||
/// hull. The key is the id of the face, used in the Face, PointFace
|
||||
/// and HorizonEdge struct.
|
||||
/// This is a Dictionary, because we need both random access to it,
|
||||
/// the ability to loop through it, and ability to quickly delete
|
||||
/// faces (in the ConstructCone method), and Dictionary is the obvious
|
||||
/// candidate that can do all of those things.
|
||||
/// 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
|
||||
/// 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
|
||||
/// looping through the points in openSet, we would have to loop
|
||||
/// through all the Faces EVER created in the algorithm, and skip the
|
||||
/// ones that have been marked as deleted. However, looping through a
|
||||
/// list is fairly fast, and it might be worth it to avoid Dictionary
|
||||
/// overhead.
|
||||
/// TODO test converting to a <![CDATA[List<Face>]]> instead.
|
||||
/// </summary>
|
||||
private Dictionary<int, Face> faces;
|
||||
/// <summary>
|
||||
/// A dictionary storing the faces of the currently generated convex
|
||||
/// hull. The key is the id of the face, used in the Face, PointFace
|
||||
/// and HorizonEdge struct.
|
||||
/// This is a Dictionary, because we need both random access to it,
|
||||
/// the ability to loop through it, and ability to quickly delete
|
||||
/// faces (in the ConstructCone method), and Dictionary is the obvious
|
||||
/// candidate that can do all of those things.
|
||||
/// 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
|
||||
/// 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
|
||||
/// looping through the points in openSet, we would have to loop
|
||||
/// through all the Faces EVER created in the algorithm, and skip the
|
||||
/// ones that have been marked as deleted. However, looping through a
|
||||
/// list is fairly fast, and it might be worth it to avoid Dictionary
|
||||
/// overhead.
|
||||
/// TODO test converting to a <![CDATA[List<Face>]]> instead.
|
||||
/// </summary>
|
||||
private Dictionary<int, Face> faces;
|
||||
|
||||
/// <summary>
|
||||
/// The current horizon. Generated by the FindHorizon() DFS search,
|
||||
/// and used in ConstructCone to construct new faces. The list of
|
||||
/// edges are in CCW order.
|
||||
/// </summary>
|
||||
private List<HorizonEdge> horizon;
|
||||
/// <summary>
|
||||
/// The current horizon. Generated by the FindHorizon() DFS search,
|
||||
/// and used in ConstructCone to construct new faces. The list of
|
||||
/// edges are in CCW order.
|
||||
/// </summary>
|
||||
private List<HorizonEdge> horizon;
|
||||
|
||||
/// <summary>
|
||||
/// If SplitVerts is false, this Dictionary is used to keep track of
|
||||
/// which points we've added to the final mesh.
|
||||
/// </summary>
|
||||
private Dictionary<int, int> hullVerts;
|
||||
/// <summary>
|
||||
/// If SplitVerts is false, this Dictionary is used to keep track of
|
||||
/// which points we've added to the final mesh.
|
||||
/// </summary>
|
||||
private Dictionary<int, int> hullVerts;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// faces we've already visited, and in the ReassignPoints() method to
|
||||
/// know which points need to be reassigned.
|
||||
/// </summary>
|
||||
private HashSet<int> litFaces;
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// faces we've already visited, and in the ReassignPoints() method to
|
||||
/// know which points need to be reassigned.
|
||||
/// </summary>
|
||||
private HashSet<int> litFaces;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// convex hull) and the closed set (points that are inside the convex
|
||||
/// hull). The first part of the array (with <![CDATA[indexes <= openSetTail]]>)
|
||||
/// is the openSet, the last part of the array (with <![CDATA[indexes >
|
||||
/// openSetTail]]>) are the closed set, with
|
||||
/// Face set to INSIDE. The
|
||||
/// closed set is largely irrelevant to the algorithm, the open set is
|
||||
/// what matters.
|
||||
/// Storing the entire open set in one big list has a downside: when
|
||||
/// we're reassigning points after ConstructCone, we only need to
|
||||
/// reassign points that belong to the faces that have been removed,
|
||||
/// but storing it in one array, we have to loop through the entire
|
||||
/// list, and checking litFaces to determine which we can skip and
|
||||
/// which need to be reassigned.
|
||||
/// 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
|
||||
/// 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
|
||||
/// would have worse locality. I should maybe test that solution, but
|
||||
/// it probably wont be faster enough (if at all) to justify the extra
|
||||
/// allocations.
|
||||
/// </summary>
|
||||
private List<PointFace> openSet;
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// convex hull) and the closed set (points that are inside the convex
|
||||
/// hull). The first part of the array (with <![CDATA[indexes <= openSetTail]]>)
|
||||
/// is the openSet, the last part of the array (with <![CDATA[indexes >
|
||||
/// openSetTail]]>) are the closed set, with
|
||||
/// Face set to INSIDE. The
|
||||
/// closed set is largely irrelevant to the algorithm, the open set is
|
||||
/// what matters.
|
||||
/// Storing the entire open set in one big list has a downside: when
|
||||
/// we're reassigning points after ConstructCone, we only need to
|
||||
/// reassign points that belong to the faces that have been removed,
|
||||
/// but storing it in one array, we have to loop through the entire
|
||||
/// list, and checking litFaces to determine which we can skip and
|
||||
/// which need to be reassigned.
|
||||
/// 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
|
||||
/// 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
|
||||
/// would have worse locality. I should maybe test that solution, but
|
||||
/// it probably wont be faster enough (if at all) to justify the extra
|
||||
/// allocations.
|
||||
/// </summary>
|
||||
private List<PointFace> openSet;
|
||||
|
||||
/// <summary>
|
||||
/// The "tail" of the openSet, the last index of a vertex that has
|
||||
/// been assigned to a face.
|
||||
/// </summary>
|
||||
private int openSetTail = -1;
|
||||
/// <summary>
|
||||
/// The "tail" of the openSet, the last index of a vertex that has
|
||||
/// been assigned to a face.
|
||||
/// </summary>
|
||||
private int openSetTail = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Generate a convex hull from points in points array, and store the
|
||||
/// 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
|
||||
/// used for more than one triangle.
|
||||
/// </summary>
|
||||
public void GenerateHull(
|
||||
/// <summary>
|
||||
/// Generate a convex hull from points in points array, and store the
|
||||
/// 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
|
||||
/// used for more than one triangle.
|
||||
/// </summary>
|
||||
public void GenerateHull(
|
||||
List<Float3> points,
|
||||
bool splitVerts,
|
||||
ref List<Float3> verts,
|
||||
@@ -179,11 +179,11 @@ public class ConvexHullCalculator
|
||||
//VerifyMesh(points, ref verts, ref tris);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure all the buffers and variables needed for the algorithm
|
||||
/// are initialized.
|
||||
/// </summary>
|
||||
private void Initialize(List<Float3> points, bool splitVerts)
|
||||
/// <summary>
|
||||
/// Make sure all the buffers and variables needed for the algorithm
|
||||
/// are initialized.
|
||||
/// </summary>
|
||||
private void Initialize(List<Float3> points, bool splitVerts)
|
||||
{
|
||||
faceCount = 0;
|
||||
openSetTail = -1;
|
||||
@@ -225,10 +225,10 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create initial seed hull.
|
||||
/// </summary>
|
||||
private void GenerateInitialHull(List<Float3> points)
|
||||
/// <summary>
|
||||
/// Create initial seed hull.
|
||||
/// </summary>
|
||||
private void GenerateInitialHull(List<Float3> points)
|
||||
{
|
||||
// Find points suitable for use as the seed hull. Some varieties of
|
||||
// this algorithm pick extreme points here, but I'm not convinced
|
||||
@@ -356,11 +356,11 @@ public class ConvexHullCalculator
|
||||
VerifyOpenSet(points);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find four points in the point cloud that are not coplanar for the
|
||||
/// seed hull
|
||||
/// </summary>
|
||||
private void FindInitialHullIndices(List<Float3> points, out int b0, out int b1, out int b2, out int b3)
|
||||
/// <summary>
|
||||
/// Find four points in the point cloud that are not coplanar for the
|
||||
/// seed hull
|
||||
/// </summary>
|
||||
private void FindInitialHullIndices(List<Float3> points, out int b0, out int b1, out int b2, out int b3)
|
||||
{
|
||||
int count = points.Count;
|
||||
|
||||
@@ -399,12 +399,12 @@ public class ConvexHullCalculator
|
||||
throw new ArgumentException("Can't generate hull, points are coplanar");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grow the hull. This method takes the current hull, and expands it
|
||||
/// to encompass the point in openSet with the point furthest away
|
||||
/// from its face.
|
||||
/// </summary>
|
||||
private void GrowHull(List<Float3> points)
|
||||
/// <summary>
|
||||
/// Grow the hull. This method takes the current hull, and expands it
|
||||
/// to encompass the point in openSet with the point furthest away
|
||||
/// from its face.
|
||||
/// </summary>
|
||||
private void GrowHull(List<Float3> points)
|
||||
{
|
||||
Assert.IsTrue(openSetTail >= 0);
|
||||
Assert.IsTrue(openSet[0].Face != INSIDE);
|
||||
@@ -439,21 +439,21 @@ public class ConvexHullCalculator
|
||||
ReassignPoints(points);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the search for the horizon.
|
||||
/// The search is a DFS search that searches neighboring triangles in
|
||||
/// 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
|
||||
/// proceeds counter-clockwise, the edges of the horizon will be found
|
||||
/// in counter-clockwise order.
|
||||
/// The heart of the search can be found in the recursive
|
||||
/// SearchHorizon() method, but the the first iteration of the search
|
||||
/// is special, because it has to visit three neighbors (all the
|
||||
/// neighbors of the initial triangle), while the rest of the search
|
||||
/// only has to visit two (because one of them has already been
|
||||
/// visited, the one you came from).
|
||||
/// </summary>
|
||||
private void FindHorizon(List<Float3> points, Float3 point, int fi, Face face)
|
||||
/// <summary>
|
||||
/// Start the search for the horizon.
|
||||
/// The search is a DFS search that searches neighboring triangles in
|
||||
/// 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
|
||||
/// proceeds counter-clockwise, the edges of the horizon will be found
|
||||
/// in counter-clockwise order.
|
||||
/// The heart of the search can be found in the recursive
|
||||
/// SearchHorizon() method, but the the first iteration of the search
|
||||
/// is special, because it has to visit three neighbors (all the
|
||||
/// neighbors of the initial triangle), while the rest of the search
|
||||
/// only has to visit two (because one of them has already been
|
||||
/// visited, the one you came from).
|
||||
/// </summary>
|
||||
private void FindHorizon(List<Float3> points, Float3 point, int fi, Face face)
|
||||
{
|
||||
// TODO should I use epsilon in the PointFaceDistance comparisons?
|
||||
|
||||
@@ -529,10 +529,10 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively search to find the horizon or lit set.
|
||||
/// </summary>
|
||||
private void SearchHorizon(List<Float3> points, Float3 point, int prevFaceIndex, int faceCount, Face face)
|
||||
/// <summary>
|
||||
/// Recursively search to find the horizon or lit set.
|
||||
/// </summary>
|
||||
private void SearchHorizon(List<Float3> points, Float3 point, int prevFaceIndex, int faceCount, Face face)
|
||||
{
|
||||
Assert.IsTrue(prevFaceIndex >= 0);
|
||||
Assert.IsTrue(litFaces.Contains(prevFaceIndex));
|
||||
@@ -622,18 +622,18 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all lit faces and construct new faces from the horizon in a
|
||||
/// "cone-like" fashion.
|
||||
/// This is a relatively straight-forward procedure, given that 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
|
||||
/// 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 to reflect it's new neighbor from
|
||||
/// the cone.
|
||||
/// </summary>
|
||||
private void ConstructCone(List<Float3> points, int farthestPoint)
|
||||
/// <summary>
|
||||
/// Remove all lit faces and construct new faces from the horizon in a
|
||||
/// "cone-like" fashion.
|
||||
/// This is a relatively straight-forward procedure, given that 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
|
||||
/// 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 to reflect it's new neighbor from
|
||||
/// the cone.
|
||||
/// </summary>
|
||||
private void ConstructCone(List<Float3> points, int farthestPoint)
|
||||
{
|
||||
foreach (int fi in litFaces)
|
||||
{
|
||||
@@ -713,21 +713,21 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reassign points based on the new faces added by ConstructCone().
|
||||
/// Only points that were previous assigned to a removed face need to
|
||||
/// be updated, so check litFaces while looping through the open set.
|
||||
/// There is a potential optimization here: there's no reason to loop
|
||||
/// through the entire openSet here. If each face had it's own
|
||||
/// openSet, we could just loop through the openSets in the removed
|
||||
/// faces. That would make the loop here shorter.
|
||||
/// 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
|
||||
/// 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
|
||||
/// a list is pretty darn fast. Still, it might be worth trying
|
||||
/// </summary>
|
||||
private void ReassignPoints(List<Float3> points)
|
||||
/// <summary>
|
||||
/// Reassign points based on the new faces added by ConstructCone().
|
||||
/// Only points that were previous assigned to a removed face need to
|
||||
/// be updated, so check litFaces while looping through the open set.
|
||||
/// There is a potential optimization here: there's no reason to loop
|
||||
/// through the entire openSet here. If each face had it's own
|
||||
/// openSet, we could just loop through the openSets in the removed
|
||||
/// faces. That would make the loop here shorter.
|
||||
/// 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
|
||||
/// 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
|
||||
/// a list is pretty darn fast. Still, it might be worth trying
|
||||
/// </summary>
|
||||
private void ReassignPoints(List<Float3> points)
|
||||
{
|
||||
for (int i = 0; i <= openSetTail; i++)
|
||||
{
|
||||
@@ -780,13 +780,13 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Final step in algorithm, export the faces of the convex hull in a
|
||||
/// mesh-friendly format.
|
||||
/// TODO normals calculation for non-split vertices. Right now it just
|
||||
/// leaves the normal array empty.
|
||||
/// </summary>
|
||||
private void ExportMesh(
|
||||
/// <summary>
|
||||
/// Final step in algorithm, export the faces of the convex hull in a
|
||||
/// mesh-friendly format.
|
||||
/// TODO normals calculation for non-split vertices. Right now it just
|
||||
/// leaves the normal array empty.
|
||||
/// </summary>
|
||||
private void ExportMesh(
|
||||
List<Float3> points,
|
||||
bool splitVerts,
|
||||
ref List<Float3> verts,
|
||||
@@ -855,40 +855,40 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signed distance from face to point (a positive number means that
|
||||
/// the point is above the face)
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
/// <summary>
|
||||
/// Signed distance from face to point (a positive number means that
|
||||
/// the point is above the face)
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private float PointFaceDistance(Float3 point, Float3 pointOnFace, Face face)
|
||||
{
|
||||
return Dot(face.Normal, point - pointOnFace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate normal for triangle
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
/// <summary>
|
||||
/// Calculate normal for triangle
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Float3 Normal(Float3 v0, Float3 v1, Float3 v2)
|
||||
{
|
||||
return Cross(v1 - v0, v2 - v0).Normalized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dot product, for convenience.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
/// <summary>
|
||||
/// Dot product, for convenience.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static float Dot(Float3 a, Float3 b)
|
||||
{
|
||||
return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Float3.Cross i left-handed, the algorithm is right-handed. Also,
|
||||
/// i wanna test to see if using aggressive inlining makes any
|
||||
/// difference here.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
/// <summary>
|
||||
/// Float3.Cross i left-handed, the algorithm is right-handed. Also,
|
||||
/// i wanna test to see if using aggressive inlining makes any
|
||||
/// difference here.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Float3 Cross(Float3 a, Float3 b)
|
||||
{
|
||||
return new Float3(
|
||||
@@ -897,28 +897,28 @@ public class ConvexHullCalculator
|
||||
a.X * b.Y - a.Y * b.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if two points are coincident
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
/// <summary>
|
||||
/// Check if two points are coincident
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool AreCoincident(Float3 a, Float3 b)
|
||||
{
|
||||
return (a - b).Length <= EPSILON;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if three points are collinear
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
/// <summary>
|
||||
/// Check if three points are collinear
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool AreCollinear(Float3 a, Float3 b, Float3 c)
|
||||
{
|
||||
return Cross(c - a, c - b).Length <= EPSILON;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if four points are coplanar
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
/// <summary>
|
||||
/// Check if four points are coplanar
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool AreCoplanar(Float3 a, Float3 b, Float3 c, Float3 d)
|
||||
{
|
||||
Float3 n1 = Cross(c - a, c - b);
|
||||
@@ -934,12 +934,12 @@ public class ConvexHullCalculator
|
||||
1.0f / m2 * n2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method used for debugging, verifies that the openSet is in a
|
||||
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
||||
/// defined.
|
||||
/// </summary>
|
||||
private void VerifyOpenSet(List<Float3> points)
|
||||
/// <summary>
|
||||
/// Method used for debugging, verifies that the openSet is in a
|
||||
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
||||
/// defined.
|
||||
/// </summary>
|
||||
private void VerifyOpenSet(List<Float3> points)
|
||||
{
|
||||
for (int i = 0; i < openSet.Count; i++)
|
||||
if (i > openSetTail)
|
||||
@@ -958,12 +958,12 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method used for debugging, verifies that the horizon is in a
|
||||
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
||||
/// defined.
|
||||
/// </summary>
|
||||
private void VerifyHorizon()
|
||||
/// <summary>
|
||||
/// Method used for debugging, verifies that the horizon is in a
|
||||
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
||||
/// defined.
|
||||
/// </summary>
|
||||
private void VerifyHorizon()
|
||||
{
|
||||
for (int i = 0; i < horizon.Count; i++)
|
||||
{
|
||||
@@ -974,12 +974,12 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method used for debugging, verifies that the faces array is in a
|
||||
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
||||
/// defined.
|
||||
/// </summary>
|
||||
private void VerifyFaces(List<Float3> points)
|
||||
/// <summary>
|
||||
/// Method used for debugging, verifies that the faces array is in a
|
||||
/// sensible state. Conditionally compiled if DEBUG_QUICKHULL if
|
||||
/// defined.
|
||||
/// </summary>
|
||||
private void VerifyFaces(List<Float3> points)
|
||||
{
|
||||
foreach (var kvp in faces)
|
||||
{
|
||||
@@ -1009,12 +1009,12 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method used for debugging, verifies that the final mesh is
|
||||
/// actually a convex hull of all the points. Conditionally compiled
|
||||
/// if DEBUG_QUICKHULL if defined.
|
||||
/// </summary>
|
||||
private void VerifyMesh(List<Float3> points, ref List<Float3> verts, ref List<int> tris)
|
||||
/// <summary>
|
||||
/// Method used for debugging, verifies that the final mesh is
|
||||
/// actually a convex hull of all the points. Conditionally compiled
|
||||
/// if DEBUG_QUICKHULL if defined.
|
||||
/// </summary>
|
||||
private void VerifyMesh(List<Float3> points, ref List<Float3> verts, ref List<int> tris)
|
||||
{
|
||||
Assert.IsTrue(tris.Count % 3 == 0);
|
||||
|
||||
@@ -1032,28 +1032,28 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does face f have a face with vertexes e0 and e1? Used only for
|
||||
/// debugging.
|
||||
/// </summary>
|
||||
private bool HasEdge(Face f, int e0, int e1)
|
||||
/// <summary>
|
||||
/// Does face f have a face with vertexes e0 and e1? Used only for
|
||||
/// debugging.
|
||||
/// </summary>
|
||||
private bool HasEdge(Face f, int e0, int e1)
|
||||
{
|
||||
return (f.Vertex0 == e0 && f.Vertex1 == e1)
|
||||
|| (f.Vertex1 == e0 && f.Vertex2 == e1)
|
||||
|| (f.Vertex2 == e0 && f.Vertex0 == e1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struct representing a single face.
|
||||
/// Vertex0, Vertex1 and Vertex2 are the vertices in CCW order. They
|
||||
/// acutal points are stored in the points array, these are just
|
||||
/// indexes into that array.
|
||||
/// Opposite0, Opposite1 and Opposite2 are the keys to the faces which
|
||||
/// share an edge with this face. Opposite0 is the face opposite
|
||||
/// Vertex0 (so it has an edge with Vertex2 and Vertex1), etc.
|
||||
/// Normal is (unsurprisingly) the normal of the triangle.
|
||||
/// </summary>
|
||||
private struct Face
|
||||
/// <summary>
|
||||
/// Struct representing a single face.
|
||||
/// Vertex0, Vertex1 and Vertex2 are the vertices in CCW order. They
|
||||
/// acutal points are stored in the points array, these are just
|
||||
/// indexes into that array.
|
||||
/// Opposite0, Opposite1 and Opposite2 are the keys to the faces which
|
||||
/// share an edge with this face. Opposite0 is the face opposite
|
||||
/// Vertex0 (so it has an edge with Vertex2 and Vertex1), etc.
|
||||
/// Normal is (unsurprisingly) the normal of the triangle.
|
||||
/// </summary>
|
||||
private struct Face
|
||||
{
|
||||
public readonly int Vertex0;
|
||||
public readonly int Vertex1;
|
||||
@@ -1088,14 +1088,14 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struct representing a mapping between a point and a face. These
|
||||
/// are used in the openSet array.
|
||||
/// 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
|
||||
/// from the face to the point.
|
||||
/// </summary>
|
||||
private struct PointFace
|
||||
/// <summary>
|
||||
/// Struct representing a mapping between a point and a face. These
|
||||
/// are used in the openSet array.
|
||||
/// 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
|
||||
/// from the face to the point.
|
||||
/// </summary>
|
||||
private struct PointFace
|
||||
{
|
||||
public readonly int Point;
|
||||
public int Face;
|
||||
@@ -1109,14 +1109,14 @@ public class ConvexHullCalculator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struct representing a single edge in the horizon.
|
||||
/// Edge0 and Edge1 are the vertexes of edge in CCW order, Face is the
|
||||
/// face on the other side of the horizon.
|
||||
/// TODO Edge1 isn't actually needed, you can just index the next item
|
||||
/// in the horizon array.
|
||||
/// </summary>
|
||||
private struct HorizonEdge
|
||||
/// <summary>
|
||||
/// Struct representing a single edge in the horizon.
|
||||
/// Edge0 and Edge1 are the vertexes of edge in CCW order, Face is the
|
||||
/// face on the other side of the horizon.
|
||||
/// TODO Edge1 isn't actually needed, you can just index the next item
|
||||
/// in the horizon array.
|
||||
/// </summary>
|
||||
private struct HorizonEdge
|
||||
{
|
||||
public int Face;
|
||||
public int Edge0;
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
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;
|
||||
return Input.GetAction(name);
|
||||
}
|
||||
if (Console.IsOpen)
|
||||
return false;
|
||||
return Input.GetAction(name);
|
||||
}
|
||||
|
||||
public static float GetAxis(string name)
|
||||
{
|
||||
if (Console.IsOpen)
|
||||
return 0.0f;
|
||||
return Input.GetAxis(name);
|
||||
}
|
||||
public static float GetAxis(string name)
|
||||
{
|
||||
if (Console.IsOpen)
|
||||
return 0.0f;
|
||||
return Input.GetAxis(name);
|
||||
}
|
||||
|
||||
public static float GetAxisRaw(string name)
|
||||
{
|
||||
if (Console.IsOpen)
|
||||
return 0.0f;
|
||||
return Input.GetAxisRaw(name);
|
||||
}
|
||||
public static float GetAxisRaw(string name)
|
||||
{
|
||||
if (Console.IsOpen)
|
||||
return 0.0f;
|
||||
return Input.GetAxisRaw(name);
|
||||
}
|
||||
}
|
||||
@@ -2,173 +2,172 @@
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Networking;
|
||||
|
||||
namespace Game
|
||||
namespace Game;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct PlayerInputState
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct PlayerInputState
|
||||
/*static PlayerInputState()
|
||||
{
|
||||
/*static PlayerInputState()
|
||||
{
|
||||
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)
|
||||
NetworkReplicator.AddSerializer(typeof(PlayerInputState),
|
||||
(ptr, streamPtr) =>
|
||||
{
|
||||
inputState = default;
|
||||
actorState = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
inputState = states[frameIndex].input;
|
||||
actorState = states[frameIndex].actor;
|
||||
return true;
|
||||
}
|
||||
},
|
||||
(ptr, streamPtr) =>
|
||||
{
|
||||
|
||||
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;
|
||||
states[frameIndex].input = inputState;
|
||||
states[frameIndex].input.frame = frame;
|
||||
states[frameIndex].actor = new PlayerActorState();
|
||||
inputState = default;
|
||||
actorState = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetState(ulong frame, PlayerInputState inputState, PlayerActorState actorState)
|
||||
{
|
||||
int frameIndex = (int)frame % MaxPlayerStates;
|
||||
states[frameIndex].input = inputState;
|
||||
states[frameIndex].input.frame = frame;
|
||||
states[frameIndex].actor = actorState;
|
||||
}
|
||||
inputState = states[frameIndex].input;
|
||||
actorState = states[frameIndex].actor;
|
||||
return true;
|
||||
}
|
||||
|
||||
public PlayerInputState GetCurrentInputState()
|
||||
{
|
||||
return currentState.input;
|
||||
}
|
||||
public void SetState(ulong frame, PlayerInputState inputState)
|
||||
{
|
||||
int frameIndex = (int)frame % MaxPlayerStates;
|
||||
states[frameIndex].input = inputState;
|
||||
states[frameIndex].input.frame = frame;
|
||||
states[frameIndex].actor = new PlayerActorState();
|
||||
}
|
||||
|
||||
public PlayerActorState GetCurrentActorState()
|
||||
{
|
||||
return currentState.actor;
|
||||
}
|
||||
public void SetState(ulong frame, PlayerInputState inputState, PlayerActorState actorState)
|
||||
{
|
||||
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 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>();
|
||||
protected IEnumerator<PlayerInputState> bufferEnumerable;
|
||||
|
||||
public bool IsPlaying
|
||||
get
|
||||
{
|
||||
get
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -9,142 +9,141 @@ using FlaxEngine;
|
||||
using FlaxEngine.Networking;
|
||||
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 bool IsNetworked => NetworkManager.client != null;
|
||||
private long flushedFrames = 0;
|
||||
public override bool Predict => true;
|
||||
|
||||
/*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;
|
||||
|
||||
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>());
|
||||
buffer.Add(currentState.input);
|
||||
}
|
||||
|
||||
public bool IsRecording => demoStream != null;
|
||||
|
||||
public override void OnUpdate()
|
||||
if (playerActor != null)
|
||||
{
|
||||
// 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;
|
||||
|
||||
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();
|
||||
//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);
|
||||
|
||||
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)
|
||||
return;
|
||||
|
||||
FlushDemo();
|
||||
demoStream.Close();
|
||||
demoStream = null;
|
||||
demoFileStream.Close();
|
||||
demoFileStream = null;
|
||||
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}");
|
||||
}
|
||||
|
||||
public void StopRecording()
|
||||
{
|
||||
if (!IsRecording)
|
||||
return;
|
||||
|
||||
FlushDemo();
|
||||
demoStream.Close();
|
||||
demoStream = null;
|
||||
demoFileStream.Close();
|
||||
demoFileStream = null;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
namespace Game
|
||||
{
|
||||
public class PlayerInputNetwork : PlayerInput
|
||||
{
|
||||
public override bool Predict => true;
|
||||
namespace Game;
|
||||
|
||||
public override void OnEndFrame()
|
||||
{
|
||||
currentState.input.frame = frame;
|
||||
base.OnEndFrame();
|
||||
}
|
||||
public class PlayerInputNetwork : PlayerInput
|
||||
{
|
||||
public override bool Predict => true;
|
||||
|
||||
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 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; } =
|
||||
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()
|
||||
{
|
||||
Globals = Content.Load<GameplayGlobals>(Path.Combine(ContentPath, "Settings", "GameSettings", "GameplayGlobals.flax"));
|
||||
Config = ConfigParser.ParseFile(Path.Combine(ContentPath, "config.cfg"));
|
||||
}
|
||||
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