Fix MenuHidden in savemap to be MenuVisible instead

Improve debug autosave to not overwrite initial file and keep 2 copies of previous autosaves
Add music volume ramp
Field
        Fix priority of dialog windows
        Fix movie playback race condition
        Fix moving non-player models ignoring collision flag
        Implement MVCAM op, AKAO music volume ramps, AKAO channel volume
        Reduce required angle for walkmesh sliding
        Fix line entities ignoring height when checking collision
        Don't render walkmesh by default any more
        Fix looping channel audio crashing when stopped
This commit is contained in:
ficedula 2023-02-15 21:50:11 +00:00
parent b387346c2e
commit af672ac5da
12 changed files with 167 additions and 60 deletions

View File

@ -7,9 +7,8 @@ using System.Threading.Tasks;
namespace Braver {
internal static class NewGame {
public static void Init(BGame game) {
game.SaveMap.MenuLocked = MenuMask.PHS | MenuMask.Save;
game.SaveMap.MenuHidden = MenuMask.PHS;
game.SaveMap.PPV = 0;
game.Memory.ResetAll();
//Can rely on md1stin to init everything that needs it?!
}
}
}

View File

@ -14,7 +14,7 @@ namespace Braver {
public byte CounterFrames { get => (byte)_memory.Read(1, 0x17); set => _memory.Write(1, 0x17, (byte)value); }
public ushort NumBattlesFought { get => (ushort)_memory.Read(2, 0x18); set => _memory.Write(2, 0x18, (ushort)value); }
public ushort NumEscapes { get => (ushort)_memory.Read(2, 0x1a); set => _memory.Write(2, 0x1a, (ushort)value); }
public MenuMask MenuHidden { get => (MenuMask)_memory.Read(2, 0x1c); set => _memory.Write(2, 0x1c, (ushort)value); }
public MenuMask MenuVisible { get => (MenuMask)_memory.Read(2, 0x1c); set => _memory.Write(2, 0x1c, (ushort)value); }
public MenuMask MenuLocked { get => (MenuMask)_memory.Read(2, 0x1e); set => _memory.Write(2, 0x1e, (ushort)value); }
}
}

View File

@ -16,5 +16,5 @@ BBB CounterFrames u8
BBC NumBattlesFought u16
BBE NumEscapes u16
BC0 MenuHidden u16 MenuMask
BC0 MenuVisible u16 MenuMask
BC2 MenuLocked u16 MenuMask

View File

@ -1,5 +1,4 @@
using Braver.UI.Layout;
using NAudio.Wave;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Linq;
@ -14,6 +13,7 @@ namespace Braver {
private Channel<MusicCommand> _channel;
private Ficedula.FF7.Audio _sfxSource;
private FGame _game;
private byte _volume = 127;
public Audio(FGame game, string ff7dir) {
_game = game;
@ -117,7 +117,6 @@ namespace Braver {
private async Task RunMusic() {
var contexts = new Stack<MusicContext>();
contexts.Push(new MusicContext());
byte volume = 127;
void DoStop() {
var context = contexts.Peek();
@ -141,7 +140,7 @@ namespace Braver {
current.WaveOut = new NAudio.Wave.WaveOut();
//current.WaveOut.Init(current.Vorbis);
current.WaveOut.Init(new LoopProvider(current.Vorbis, loopStart, loopEnd).ToWaveProvider());
current.WaveOut.Volume = volume / 127f;
current.WaveOut.Volume = _volume / 127f;
current.WaveOut.Play();
current.Track = track;
}
@ -152,9 +151,9 @@ namespace Braver {
switch (command.Command) {
case CommandType.SetVolume:
volume = command.Param;
_volume = command.Param;
if (contexts.Peek().WaveOut != null)
contexts.Peek().WaveOut.Volume = volume / 127f;
contexts.Peek().WaveOut.Volume = _volume / 127f;
break;
case CommandType.Play:
@ -184,13 +183,34 @@ namespace Braver {
}
}
public void SetMusicVolume(byte volume) {
_channel.Writer.TryWrite(new MusicCommand {
Command = CommandType.SetVolume,
Param = volume,
});
public void SetMusicVolume(byte? volumeFrom, byte volumeTo, float duration) {
//TODO net
if (duration <= 0) {
_channel.Writer.TryWrite(new MusicCommand {
Command = CommandType.SetVolume,
Param = volumeTo,
});
} else {
byte current = volumeFrom ?? _volume;
Task.Run(async () => {
var sw = System.Diagnostics.Stopwatch.StartNew();
while(sw.Elapsed.TotalSeconds < duration) {
byte v = (byte)(current + (volumeTo - current) * (sw.Elapsed.TotalSeconds / duration));
_channel.Writer.TryWrite(new MusicCommand {
Command = CommandType.SetVolume,
Param = v,
});
await Task.Delay(10);
}
_channel.Writer.TryWrite(new MusicCommand {
Command = CommandType.SetVolume,
Param = volumeTo,
});
});
}
}
public void SetMusicVolume(byte volume) => SetMusicVolume(null, volume, 0);
public void PlayMusic(string name, bool pushContext = false) {
_channel.Writer.TryWrite(new MusicCommand {
Track = name,
@ -238,9 +258,22 @@ namespace Braver {
instance.Stop();
instance.Dispose();
_channels.Remove(channel);
_activeLoops.Remove(instance);
//TODO net
}
}
public void ChannelProperty(int channel, float? pan, float? volume) {
if (_channels.TryGetValue(channel, out var instance)) {
if (pan != null)
instance.Pan = pan.Value;
if (volume != null)
instance.Volume = volume.Value;
}
//TODO net
}
public void PlaySfx(Sfx which, float volume, float pan, int? channel = null) => PlaySfx((int)which, volume, pan, channel);
public void PlaySfx(int which, float volume, float pan, int? channel = null) {
LoadedEffect effect;

View File

@ -119,6 +119,16 @@ namespace Braver {
}
}
public void AutoSave() {
string path = GetSavePath();
foreach (string file1 in Directory.GetFiles(path, "auto1.*"))
File.Move(file1, Path.Combine(path, "auto2" + Path.GetExtension(file1)), true);
foreach (string file0 in Directory.GetFiles(path, "auto.*"))
File.Move(file0, Path.Combine(path, "auto1" + Path.GetExtension(file0)), true);
Save(Path.Combine(path, "auto"));
}
public Stream WriteDebugBData(string category, string file) {
return new FileStream(Path.Combine(_bdata, category, file), FileMode.Create);
}

View File

@ -118,7 +118,7 @@ namespace Braver.Field {
}
public void ProcessInput(InputState input) {
foreach(var window in _windows) {
foreach(var window in _windows.Reverse<Window>()) {
switch (window.State) {
case WindowState.Displaying:
if (input.IsJustDown(InputKey.OK)) {
@ -228,7 +228,7 @@ namespace Braver.Field {
}
}
foreach (var window in _windows) {
foreach (var window in _windows.Reverse<Window>()) { //lower ID windows should appear on top of higher IDs {
float alpha = window.Options.HasFlag(DialogOptions.Transparent) ? 0.5f : 1f;
bool box = !window.Options.HasFlag(DialogOptions.NoBorder);
switch (window.State) {

View File

@ -24,11 +24,15 @@ namespace Braver.Field {
public Vector3 P1 { get; set; }
public bool Active { get; set; } = true;
public bool IntersectsWith(Vector3 entity, float entityRadius) {
public bool IntersectsWith(FieldModel m, float entityRadius) {
if (!Active)
return false;
return GraphicsUtil.LineCircleIntersect(P0.XY(), P1.XY(), entity.XY(), entityRadius);
if ((m.Translation.Z - 5) > Math.Max(P0.Z, P1.Z)) return false;
float entHeight = (m.MaxBounds.Y - m.MinBounds.Y) * m.Scale;
if ((m.Translation.Z + entHeight + 5) < Math.Min(P0.Z, P1.Z)) return false;
return GraphicsUtil.LineCircleIntersect(P0.XY(), P1.XY(), m.Translation.XY(), entityRadius);
}
}
@ -41,10 +45,11 @@ namespace Braver.Field {
CameraTracksPlayer = 0x8,
CameraIsAsyncScrolling = 0x10,
MusicLocked = 0x20,
UseMovieCam = 0x40,
NoScripts = 0x100,
DEFAULT = PlayerControls | MenuEnabled | CameraTracksPlayer,
DEFAULT = PlayerControls | MenuEnabled | CameraTracksPlayer | UseMovieCam,
}
public class FieldScreen : Screen, Net.IListen<Net.FieldModelMessage>, Net.IListen<Net.FieldBGMessage>,
@ -58,7 +63,7 @@ namespace Braver.Field {
private string _file;
private bool _debugMode = false;
private bool _renderBG = true, _renderDebug = true, _renderModels = true;
private bool _renderBG = true, _renderDebug = false, _renderModels = true;
private float _controlRotation;
private bool _renderUI = true;
@ -132,13 +137,15 @@ namespace Braver.Field {
};
}
private static bool _isFirstLoad = true;
public override void Init(FGame g, GraphicsDevice graphics) {
base.Init(g, graphics);
UpdateSaveLocation();
if (g.DebugOptions.AutoSaveOnFieldEntry)
Game.Save(System.IO.Path.Combine(FGame.GetSavePath(), "auto"));
if (g.DebugOptions.AutoSaveOnFieldEntry && !_isFirstLoad)
Game.AutoSave();
_isFirstLoad = false;
g.Net.Listen<Net.FieldModelMessage>(this);
g.Net.Listen<Net.FieldBGMessage>(this);
@ -373,7 +380,10 @@ namespace Braver.Field {
Background.Render(_view2D, _bgZFrom, _bgZTo, false);
}
Viewer viewer3D = ViewFromCamera(Movie.Camera) ?? _view3D;
Viewer viewer3D = null;
if (Options.HasFlag(FieldOptions.UseMovieCam) && Movie.Active)
viewer3D = ViewFromCamera(Movie.Camera);
viewer3D ??= _view3D;
if (_renderDebug)
_debug.Render(viewer3D);
@ -677,7 +687,7 @@ namespace Braver.Field {
var oldLines = Player.LinesCollidingWith.ToArray();
Player.LinesCollidingWith.Clear();
foreach (var lineEnt in Entities.Where(e => e.Line != null)) {
if (lineEnt.Line.IntersectsWith(Player.Model.Translation, Player.CollideDistance))
if (lineEnt.Line.IntersectsWith(Player.Model, Player.CollideDistance))
Player.LinesCollidingWith.Add(lineEnt);
}
@ -979,7 +989,7 @@ namespace Braver.Field {
//to slide along an edge to end up closer to our desired end point.
//Calculate angles from end-start-v0 and end-start-v1 to find which vert we can slide towards
//while minimising the change in direction from our original heading.
//Only slide if the edge is < 90 degrees off our original heading as it's weird otherwise!
//Only slide if the edge is < 60 degrees off our original heading as it's weird otherwise!
var v0dir = (tv0 - startPos);
var v0Distance = v0dir.Length();
@ -992,14 +1002,14 @@ namespace Braver.Field {
double v0angle = AngleBetweenVectors(v0dir, origDir),
v1angle = AngleBetweenVectors(v1dir, origDir);
if ((Math.Abs(v0angle) < Math.Abs(v1angle)) && (v0angle < (Math.PI / 2))) {
if ((Math.Abs(v0angle) < Math.Abs(v1angle)) && (v0angle < (Math.PI / 3))) {
//Try to slide towards v0
if (v0Distance < origDistance)
newDestination = tv0;
else
newDestination = startPos + v0dir * origDistance;
return LeaveTriResult.SlideCurrentTri;
} else if (Math.Abs(v1angle) < (Math.PI / 2)) {
} else if (Math.Abs(v1angle) < (Math.PI / 3)) {
//Try to slide towards v1
if (v1Distance < origDistance)
newDestination = tv1;

View File

@ -1,4 +1,5 @@
using Microsoft.Xna.Framework.Audio;
using SharpDX.MediaFoundation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -17,7 +18,7 @@ namespace Braver.Field {
public int Frame => _frame;
public bool Active => (_process != null) && (_frame >= 0);
public Ficedula.FF7.Field.CameraMatrix Camera => Active ? _cam.Camera.ElementAtOrDefault(Frame) : null;
public Ficedula.FF7.Field.CameraMatrix Camera => _cam.Camera.ElementAtOrDefault(Frame);
private string _ffPath;
@ -72,13 +73,13 @@ namespace Braver.Field {
psi.Arguments = string.Format(customCommand, filename);
}
_process = Process.Start(psi);
var process = Process.Start(psi);
bool stereo = false;
int freq = 0;
do {
string s = _process.StandardError.ReadLine();
string s = process.StandardError.ReadLine();
if (s == null) break;
if (s.Contains("Audio: pcm_s16le")) {
foreach (string part in s.Split(',')) {
@ -90,17 +91,18 @@ namespace Braver.Field {
}
} while (freq == 0);
_process.StandardError.Close();
process.StandardError.Close();
if (freq > 0) {
var ms = new System.IO.MemoryStream();
_process.StandardOutput.BaseStream.CopyTo(ms);
process.StandardOutput.BaseStream.CopyTo(ms);
byte[] data = ms.ToArray();
_soundEffect = new SoundEffect(data, freq, stereo ? AudioChannels.Stereo : AudioChannels.Mono);
_effectInstance = _soundEffect.CreateInstance();
}
process.Dispose();
}
@ -108,6 +110,9 @@ namespace Braver.Field {
string filename = _files[movie];
if (!File.Exists(filename))
throw new Exception($"Movie file {filename} not found");
using (var s = _game.Open("cd", Path.GetFileName(Path.ChangeExtension(filename, ".cam"))))
_cam = new Ficedula.FF7.Field.MovieCam(s);
@ -135,14 +140,14 @@ namespace Braver.Field {
CreateNoWindow = true,
};
_process = Process.Start(psi);
var process = Process.Start(psi);
int stride;
int width = 0, height = 0;
float fps = 0;
do {
string s = _process.StandardError.ReadLine();
string s = process.StandardError.ReadLine();
if (s == null) return;
if (s.Contains("Video: rawvideo")) {
foreach (string part in s.Split(',')) {
@ -157,7 +162,7 @@ namespace Braver.Field {
}
} while (width == 0);
_process.StandardError.Close();
process.StandardError.Close();
if (fps == 0) fps = 30;
_gameFramesPerVideoFrame = 60 / fps;
@ -173,6 +178,8 @@ namespace Braver.Field {
_framebuffer = new byte[_texture.Width * _texture.Height * 4];
_texture.SetData(_framebuffer);
//Now we have a texture, make it available to be played
_process = process;
_frame = -1;
}
@ -184,6 +191,9 @@ namespace Braver.Field {
public void Render() {
if (_frame >= 0) {
Rectangle bar0, bar1;
_spriteBatch.Begin(depthStencilState: DepthStencilState.None);
float srcRatio = 1f * _texture.Width / _texture.Height,
@ -191,12 +201,25 @@ namespace Braver.Field {
if (srcRatio <= destRatio) {
int widthUsed = (int)(_graphics.Viewport.Height * srcRatio);
_spriteBatch.Draw(_texture, new Rectangle((_graphics.Viewport.Width - widthUsed) / 2, 0, widthUsed, _graphics.Viewport.Height), Color.White);
int xoffset = (_graphics.Viewport.Width - widthUsed) / 2;
_spriteBatch.Draw(_texture, new Rectangle(xoffset, 0, widthUsed, _graphics.Viewport.Height), Color.White);
bar0 = new Rectangle(0, 0, xoffset, _graphics.Viewport.Height);
bar1 = new Rectangle(_graphics.Viewport.Width - xoffset, 0, xoffset, _graphics.Viewport.Height);
} else {
int heightUsed = (int)(_texture.Width / srcRatio);
_spriteBatch.Draw(_texture, new Rectangle(0, (_graphics.Viewport.Height - heightUsed) / 2, _graphics.Viewport.Width, heightUsed), Color.White);
int yoffset = (_graphics.Viewport.Height - heightUsed) / 2;
_spriteBatch.Draw(_texture, new Rectangle(0, yoffset, _graphics.Viewport.Width, heightUsed), Color.White);
bar0 = new Rectangle(0, 0, _graphics.Viewport.Width, yoffset);
bar1 = new Rectangle(0, _graphics.Viewport.Height - yoffset, _graphics.Viewport.Width, yoffset);
}
_spriteBatch.End();
_spriteBatch.Begin(
depthStencilState: DepthStencilState.Default,
transformMatrix: Matrix.CreateScale(1, 1, 0)
);
_spriteBatch.Draw(_texture, bar0, Color.Black);
_spriteBatch.Draw(_texture, bar1, Color.Black);
_spriteBatch.End();
}
}

View File

@ -1,7 +1,9 @@
using System;
using MonoGame.Framework.Utilities.Deflate;
using System;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Braver.Field {
public enum OpCode {
@ -316,11 +318,13 @@ namespace Braver.Field {
ResumeState = null;
}
public void Pause() {
public void Pause([CallerMemberName] string caller = null) {
Active = false;
System.Diagnostics.Debug.WriteLine($"Fiber {this._entity} paused by {caller}");
}
public void Resume() {
public void Resume([CallerMemberName] string caller = null) {
Active = true;
System.Diagnostics.Debug.WriteLine($"Fiber {this._entity} resumed by {caller}");
}
public void Stop() {
@ -588,7 +592,7 @@ namespace Braver.Field {
}
public static OpResult REQEW(Fiber f, Entity e, FieldScreen s) {
int entity = f.ReadU8(), parm = f.ReadU8();
if (s.Entities[entity].Call(parm >> 5, parm & 0x1f, f.Resume)) {
if (s.Entities[entity].Call(parm >> 5, parm & 0x1f, () => f.Resume())) {
f.Pause();
return OpResult.Continue;
} else
@ -994,7 +998,7 @@ namespace Braver.Field {
f.ResumeState = e.Model.AnimationState;
if (remaining.Length() <= (e.MoveSpeed * 4)) {
if (s.TryWalk(e, new Vector3(x, y, e.Model.Translation.Z), true)) {
if (s.TryWalk(e, new Vector3(x, y, e.Model.Translation.Z), e.Flags.HasFlag(EntityFlags.CanCollide))) {
if (f.ResumeState != null)
e.Model.AnimationState = (AnimationState)f.ResumeState;
return OpResult.Continue;
@ -1004,7 +1008,7 @@ namespace Braver.Field {
} else {
remaining.Normalize();
remaining *= e.MoveSpeed * 4;
s.TryWalk(e, e.Model.Translation + new Vector3(remaining.X, remaining.Y, 0), true);
s.TryWalk(e, e.Model.Translation + new Vector3(remaining.X, remaining.Y, 0), e.Flags.HasFlag(EntityFlags.CanCollide));
if (doAnimation && (e.Model.AnimationState.Animation != 1))
e.Model.PlayAnimation(1, true, 1f, null);
return OpResult.Restart;
@ -1462,7 +1466,7 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
public static OpResult MESSAGE(Fiber f, Entity e, FieldScreen s) {
byte id = f.ReadU8(), dlg = f.ReadU8();
f.Pause();
s.Dialog.Show(id, s.FieldDialog.Dialogs[dlg], f.Resume);
s.Dialog.Show(id, s.FieldDialog.Dialogs[dlg], () => f.Resume());
return OpResult.Continue;
}
@ -1573,9 +1577,22 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
DoChannel(5, 0);
break;
case 0xa0:
case 0xa1:
case 0xa2:
case 0xa3:
s.Game.Audio.ChannelProperty(op - 0xa0, null, parm1 / 127f);
break;
case 0xc0:
s.Game.Audio.SetMusicVolume((byte)parm1);
break;
case 0xc1:
s.Game.Audio.SetMusicVolume(null, (byte)parm2, parm1 / 60f);
break;
case 0xc2:
s.Game.Audio.SetMusicVolume((byte)parm2, (byte)parm3, parm1 / 60f);
break;
case 0xc8:
case 0xc9:
@ -1643,9 +1660,17 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
}
public static OpResult MOVIE(Fiber f, Entity e, FieldScreen s) {
f.Pause();
s.Movie.Play(f.Resume);
s.Movie.Play(() => f.Resume());
return OpResult.Continue;
}
public static OpResult MVCAM(Fiber f, Entity e, FieldScreen s) {
if (f.ReadU8() == 0)
s.Options |= FieldOptions.UseMovieCam;
else
s.Options &= ~FieldOptions.UseMovieCam;
return OpResult.Continue;
}
public static OpResult MVIEF(Fiber f, Entity e, FieldScreen s) {
byte bank = f.ReadU8(), addr = f.ReadU8();
s.Game.Memory.Write(bank, addr, (ushort)Math.Max(0, s.Movie.Frame));

View File

@ -20,9 +20,14 @@ Field - model lighting looks a bit too bright
- Trigger sound effects are just wrong
- Jessie's rotation in nmkin_3 far too slow
- save point transparency is wrong
- md1_1 - biggs gets stuck
Music looping works but still a noticeable break at the loop point
Logging
Gamepad support
Same with audio sfx looping!
Loop detect not quite right - bomb setup sfx in nmkin_5 loop but shouldn't?
@ -41,4 +46,5 @@ Battle
Don't allow targetting dead enemies
Animations?!?!?!?!?!
Hit effects...
Run away!
Actions that shouldn't do anything still register a miss or 1pt of damage - see Guard Scorpion in battle 324

View File

@ -455,7 +455,8 @@ namespace Braver.WorldMap {
Game.SaveData.WorldMapY = _avatarModel.Translation.Z;
Game.SaveData.Module = Module.WorldMap;
Game.SaveMap.MenuLocked &= ~(MenuMask.Save | MenuMask.PHS);
Game.SaveMap.MenuHidden &= ~(MenuMask.Save | MenuMask.PHS);
Game.SaveMap.MenuVisible |= MenuMask.Save | MenuMask.PHS;
//TODO - can we skip these on the assumption script should have configured them already?!
Game.PushScreen(new UI.Layout.LayoutScreen("MainMenu"));
InputEnabled = true;
});

View File

@ -11,34 +11,34 @@
</Component>
<Component xsi:type="Box" X="1050" Y="0" W="230" H="480" ID="Menu">
@if(!Model.SaveMap.MenuHidden.HasFlag(Braver.MenuMask.Item)) {
<Component xsi:type="Label" Click="MenuSelected" Y="30" X="50" ID="lItem">Item</Component>
@if(Model.SaveMap.MenuVisible.HasFlag(Braver.MenuMask.Item)) {
<Component xsi:type="Label" Click="MenuSelected" Y="30" X="50" ID="lItem">Item</Component>
}
@if(!Model.SaveMap.MenuHidden.HasFlag(Braver.MenuMask.Magic)) {
@if(Model.SaveMap.MenuVisible.HasFlag(Braver.MenuMask.Magic)) {
<Component xsi:type="Label" Click="MenuSelected" Y="70" X="50" ID="lMagic">Magic</Component>
}
@if(!Model.SaveMap.MenuHidden.HasFlag(Braver.MenuMask.Materia)) {
@if(Model.SaveMap.MenuVisible.HasFlag(Braver.MenuMask.Materia)) {
<Component xsi:type="Label" Click="MenuSelected" Y="110" X="50" ID="lMateria">Materia</Component>
}
@if(!Model.SaveMap.MenuHidden.HasFlag(Braver.MenuMask.Equip)) {
@if(Model.SaveMap.MenuVisible.HasFlag(Braver.MenuMask.Equip)) {
<Component xsi:type="Label" Click="MenuSelected" Y="150" X="50" ID="lEquip">Equip</Component>
}
@if(!Model.SaveMap.MenuHidden.HasFlag(Braver.MenuMask.Status)) {
@if(Model.SaveMap.MenuVisible.HasFlag(Braver.MenuMask.Status)) {
<Component xsi:type="Label" Click="MenuSelected" Y="190" X="50" ID="lStatus">Status</Component>
}
@if(!Model.SaveMap.MenuHidden.HasFlag(Braver.MenuMask.Order)) {
@if(Model.SaveMap.MenuVisible.HasFlag(Braver.MenuMask.Order)) {
<Component xsi:type="Label" Click="MenuSelected" Y="230" X="50" ID="lOrder">Order</Component>
}
@if(!Model.SaveMap.MenuHidden.HasFlag(Braver.MenuMask.Limit)) {
@if(Model.SaveMap.MenuVisible.HasFlag(Braver.MenuMask.Limit)) {
<Component xsi:type="Label" Click="MenuSelected" Y="270" X="50" ID="lLimit">Limit</Component>
}
@if(!Model.SaveMap.MenuHidden.HasFlag(Braver.MenuMask.Config)) {
@if(Model.SaveMap.MenuVisible.HasFlag(Braver.MenuMask.Config)) {
<Component xsi:type="Label" Click="MenuSelected" Y="310" X="50" ID="lConfig">Config</Component>
}
@if(!Model.SaveMap.MenuHidden.HasFlag(Braver.MenuMask.PHS)) {
@if(Model.SaveMap.MenuVisible.HasFlag(Braver.MenuMask.PHS)) {
<Component xsi:type="Label" Click="MenuSelected" Y="350" X="50" ID="lPHS" Enabled="@Bool(!Model.SaveMap.MenuLocked.HasFlag(Braver.MenuMask.PHS))">PHS</Component>
}
@if(!Model.SaveMap.MenuHidden.HasFlag(Braver.MenuMask.Save)) {
@if(Model.SaveMap.MenuVisible.HasFlag(Braver.MenuMask.Save)) {
<Component xsi:type="Label" Click="MenuSelected" Y="390" X="50" ID="lSave" Enabled="@Bool(!Model.SaveMap.MenuLocked.HasFlag(Braver.MenuMask.Save))">Save</Component>
}
<Component xsi:type="Label" Click="MenuSelected" Y="430" X="50" ID="lQuit">Quit</Component>