diff --git a/Braver.Core/SaveData.cs b/Braver.Core/SaveData.cs
index 1007f3b..8b3f814 100644
--- a/Braver.Core/SaveData.cs
+++ b/Braver.Core/SaveData.cs
@@ -56,6 +56,7 @@ namespace Braver {
Party3 = 0x40,
Available = 0x100,
+ Locked = 0x200,
ANY_PARTY_SLOT = Party1 | Party2 | Party3,
}
diff --git a/F7/Braver.csproj b/F7/Braver.csproj
index 36cb855..29301b4 100644
--- a/F7/Braver.csproj
+++ b/F7/Braver.csproj
@@ -10,6 +10,7 @@
app.manifest
Icon.ico
+ True
diff --git a/F7/FGame.cs b/F7/FGame.cs
index 547da66..9dae5fe 100644
--- a/F7/FGame.cs
+++ b/F7/FGame.cs
@@ -31,6 +31,7 @@ namespace Braver {
public Audio Audio { get; }
public Screen Screen => _screens.Peek();
+ public string FFMpegPath { get; private set; } //TODO - better place to put/calculate this?
private Dictionary _prefs;
@@ -64,6 +65,7 @@ namespace Braver {
L = _data[category] = new();
L.Add(new FileDataSource(dir));
}
+ FFMpegPath = Path.Combine(bdata, "ffmpeg.exe");
Audio = new Audio(this, data);
diff --git a/F7/Field/Background.cs b/F7/Field/Background.cs
index cbf3c56..5fad7f3 100644
--- a/F7/Field/Background.cs
+++ b/F7/Field/Background.cs
@@ -18,20 +18,12 @@ namespace Braver.Field {
public int MinX { get; private set; }
public int MinY { get; private set; }
- private enum BlendType {
- Blend,
- Additive,
- Subtractive,
- QuarterAdd,
- None = 0xff,
- }
-
private class TexLayer {
public Texture2D Tex;
public VertexPositionTexture[] Verts;
public IEnumerable Sprites;
public List Data;
- public BlendType Blend;
+ public Ficedula.FF7.Field.BlendType Blend;
public int Parameter;
public int Mask;
public int OffsetX, OffsetY;
@@ -110,7 +102,7 @@ namespace Braver.Field {
Tex = new Texture2D(graphics, texWidth, texHeight, false, SurfaceFormat.Color),
OffsetX = -minX,
OffsetY = -minY,
- Blend = (BlendType)group.First().TypeTrans,
+ Blend = (Ficedula.FF7.Field.BlendType)group.First().TypeTrans,
Sprites = group.ToArray(),
Data = Enumerable.Range(0, texHeight)
.Select(_ => new uint[texWidth])
@@ -184,11 +176,11 @@ namespace Braver.Field {
continue;
switch (layer.Blend) {
- case BlendType.None:
- case BlendType.Blend:
+ case Ficedula.FF7.Field.BlendType.None:
+ case Ficedula.FF7.Field.BlendType.Blend:
_graphics.BlendState = BlendState.AlphaBlend;
break;
- case BlendType.Additive:
+ case Ficedula.FF7.Field.BlendType.Additive:
_graphics.BlendState = BlendState.Additive;
break;
default: //TODO NO
diff --git a/F7/Field/FieldScreen.cs b/F7/Field/FieldScreen.cs
index 38de848..ca87702 100644
--- a/F7/Field/FieldScreen.cs
+++ b/F7/Field/FieldScreen.cs
@@ -1,17 +1,29 @@
using Ficedula.FF7.Field;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
-using System.Configuration;
using System.Linq;
-using System.Net.Http.Headers;
-using System.Text;
-using System.Threading.Tasks;
-using System.Xml.Serialization;
namespace Braver.Field {
+ [Flags]
+ public enum BattleFlags {
+ None = 0,
+ TimedBattle = 0x2,
+ Preemptive = 0x4,
+ NoEscape = 0x8,
+ NoVictoryMusic = 0x20,
+ BattleArena = 0x40,
+ NoVictoryScreens = 0x80,
+ NoGameOver = 0x100,
+ }
+
+ public class BattleOptions {
+ public string OverrideMusic { get; set; }
+ public string PostBattleMusic { get; set; } //will play in field
+ public bool BattlesEnabled { get; set; } = false; //TODO - reasonable default?
+ public BattleFlags Flags { get; set; } = BattleFlags.None;
+ }
+
public class FieldInfo {
public float BGZFrom { get; set; }
public float BGZTo { get; set; }
@@ -38,6 +50,7 @@ namespace Braver.Field {
MenuEnabled = 0x4,
CameraTracksPlayer = 0x8,
CameraIsAsyncScrolling = 0x10,
+ MusicLocked = 0x20,
DEFAULT = PlayerControls | MenuEnabled | CameraTracksPlayer,
}
@@ -65,14 +78,16 @@ namespace Braver.Field {
public HashSet DisabledWalkmeshTriangles { get; } = new HashSet();
public Background Background { get; private set; }
+ public Movie Movie { get; private set; }
public List Entities { get; private set; }
public Entity Player { get; private set; }
public List FieldModels { get; private set; }
public DialogEvent FieldDialog { get; private set; }
public FieldOptions Options { get; set; } = FieldOptions.DEFAULT;
public Dialog Dialog { get; private set; }
+ public Overlay Overlay { get; private set; }
- public string OverrideBattleMusic { get; set; }
+ public BattleOptions BattleOptions { get; } = new();
private HashSet _activeTriggers = new();
@@ -89,6 +104,8 @@ namespace Braver.Field {
g.Net.Listen(this);
g.Net.Listen(this);
+ Overlay = new Overlay(g, graphics);
+
var mapList = g.Singleton(() => new MapList(g.Open("field", "maplist")));
string file = mapList.Items[_destination.DestinationFieldID];
@@ -97,6 +114,7 @@ namespace Braver.Field {
using (var s = g.Open("field", file)) {
var field = new FieldFile(s);
Background = new Background(g, graphics, field.GetBackground());
+ Movie = new Movie(g, graphics);
FieldDialog = field.GetDialogEvent();
Entities = FieldDialog.Entities
@@ -297,7 +315,10 @@ namespace Braver.Field {
}
BringPlayerIntoView();
- FadeIn(null);
+
+ if (!Overlay.HasTriggered)
+ Overlay.Fade(30, GraphicsUtil.BlendSubtractive, Color.White, Color.Black, null);
+
g.Net.Send(new Net.ScreenReadyMessage());
}
Entity.DEBUG_OUT = false;
@@ -311,8 +332,12 @@ namespace Braver.Field {
protected override void DoRender() {
Graphics.DepthStencilState = DepthStencilState.Default;
Graphics.BlendState = BlendState.AlphaBlend;
- if (_renderBG)
- Background.Render(_view2D, _bgZFrom, _bgZTo);
+ if (_renderBG) {
+ if (Movie.Active)
+ Movie.Render();
+ else
+ Background.Render(_view2D, _bgZFrom, _bgZTo);
+ }
if (_renderDebug)
_debug.Render(_view3D);
@@ -322,6 +347,8 @@ namespace Braver.Field {
if ((entity.Model != null) && entity.Model.Visible)
entity.Model.Render(_view3D);
+ Overlay.Render();
+
Dialog.Render();
}
@@ -340,6 +367,7 @@ namespace Braver.Field {
protected override void DoStep(GameTime elapsed) {
if (Game.Net is Net.Server) {
if ((frame % 2) == 0) {
+ Overlay.Step();
foreach (var entity in Entities) {
entity.Run(1000);
entity.Model?.FrameStep();
@@ -353,6 +381,7 @@ namespace Braver.Field {
}
Dialog.Step();
+ Movie.Step();
Background.Step();
} else {
if ((frame % 2) == 0) {
@@ -786,6 +815,8 @@ namespace Braver.Field {
if (newHeight != null) {
//We're staying in the same tri, so just update height
eMove.Model.Translation = newPosition.WithZ(newHeight.Value);
+ if (eMove.Name == "ba")
+ System.Diagnostics.Debug.WriteLine($"Moving {eMove.Name} to {eMove.Model.Translation}");
return true;
} else {
if (!DoesLeaveTri(eMove.Model.Translation.XY(), newPosition.XY(), currentTri, out float dist, out short? newTri)) {
@@ -819,6 +850,8 @@ namespace Braver.Field {
}
testLocation = testFrom;
+ if (eMove.Name == "ba")
+ System.Diagnostics.Debug.WriteLine($"Moving {eMove.Name} from wmtri {eMove.WalkmeshTri} to {newTri.Value}");
movingToTri = _walkmesh[newTri.Value];
newHeight = HeightInTriangle(
movingToTri.V0.ToX(), movingToTri.V1.ToX(), movingToTri.V2.ToX(),
@@ -828,8 +861,9 @@ namespace Braver.Field {
eMove.WalkmeshTri = newTri.Value;
eMove.Model.Translation = newPosition.WithZ(newHeight.Value);
- return true;
-
+ if (eMove.Name == "ba")
+ System.Diagnostics.Debug.WriteLine($"Moving {eMove.Name} to {eMove.Model.Translation}");
+ return true;
}
}
}
diff --git a/F7/Field/Movie.cs b/F7/Field/Movie.cs
new file mode 100644
index 0000000..4beac97
--- /dev/null
+++ b/F7/Field/Movie.cs
@@ -0,0 +1,217 @@
+using Microsoft.Xna.Framework.Audio;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace Braver.Field {
+ public class Movie {
+
+ private int _frame;
+ private SpriteBatch _spriteBatch;
+ private GraphicsDevice _graphics;
+
+ public int Frame => _frame;
+ public bool Active => (_process != null) && (_frame >= 0);
+
+ private string _ffPath;
+
+ private Process _process;
+ private Action _onComplete;
+ private SoundEffect _soundEffect;
+ private SoundEffectInstance _effectInstance;
+ private Texture2D _texture;
+ private float _gameFramesPerVideoFrame, _frameIncrement;
+ private byte[] _framebuffer;
+
+ private static Regex _reSize = new Regex(@"(\d+)x(\d+)");
+
+ private string[] _files;
+
+ public Movie(FGame g, GraphicsDevice graphics) {
+ _graphics = graphics;
+ _spriteBatch = new SpriteBatch(graphics);
+ _ffPath = g.FFMpegPath;
+
+ _files = g.OpenString("movies", "movielist.txt")
+ .Split('\n')
+ .Select(s => s.Trim('\r'))
+ .Select(s => Path.Combine(Path.GetDirectoryName(_ffPath), "movies", s + ".mp4")) //TODO!!!
+ .ToArray();
+ }
+
+ private unsafe void GetAudioData(string filename, string customCommand) {
+
+ var psi = new ProcessStartInfo {
+ FileName = _ffPath,
+ ArgumentList = {
+ "-i", filename,
+ "-map", "0:a",
+ "-acodec", "pcm_s16le",
+ "-f", "s16le",
+ "-"
+ },
+ RedirectStandardError = true,
+ RedirectStandardInput = true,
+ RedirectStandardOutput = true,
+ CreateNoWindow = true,
+ };
+
+ if (!string.IsNullOrWhiteSpace(customCommand)) {
+ psi.ArgumentList.Clear();
+ psi.Arguments = string.Format(customCommand, filename);
+ }
+
+ _process = Process.Start(psi);
+
+ bool stereo = false;
+ int freq = 0;
+
+ do {
+ string s = _process.StandardError.ReadLine();
+ if (s == null) break;
+ if (s.Contains("Audio: pcm_s16le")) {
+ foreach (string part in s.Split(',')) {
+ if (part.EndsWith("Hz"))
+ freq = int.Parse(part.Substring(0, part.Length - 2).Trim());
+ else if (part.Trim().Equals("stereo", StringComparison.InvariantCultureIgnoreCase))
+ stereo = true;
+ }
+ }
+ } while (freq == 0);
+
+ _process.StandardError.Close();
+
+ if (freq > 0) {
+ var ms = new System.IO.MemoryStream();
+ _process.StandardOutput.BaseStream.CopyTo(ms);
+ byte[] data = ms.ToArray();
+
+ _soundEffect = new SoundEffect(data, freq, stereo ? AudioChannels.Stereo : AudioChannels.Mono);
+ _effectInstance = _soundEffect.CreateInstance();
+ }
+
+ }
+
+
+ public void Prepare(int movie) {
+
+ string filename = _files[movie];
+
+ GetAudioData(filename, "");
+
+ string ffFormat;
+ int bytesPerPixel;
+ ffFormat = "rgba";
+ bytesPerPixel = 4;
+
+ var psi = new ProcessStartInfo {
+ FileName = _ffPath,
+ ArgumentList = {
+ "-hwaccel", "auto",
+ "-i", filename,
+ "-f", "image2pipe",
+ "-pix_fmt", ffFormat,
+ "-vcodec", "rawvideo",
+ "-blocksize", "65536",
+ "-"
+ },
+ RedirectStandardError = true,
+ RedirectStandardInput = true,
+ RedirectStandardOutput = true,
+ CreateNoWindow = true,
+ };
+
+ _process = Process.Start(psi);
+
+ int stride;
+ int width = 0, height = 0;
+ float fps = 0;
+
+ do {
+ string s = _process.StandardError.ReadLine();
+ if (s == null) return;
+ if (s.Contains("Video: rawvideo")) {
+ foreach (string part in s.Split(',')) {
+ if (part.EndsWith("fps"))
+ fps = float.Parse(part.Substring(0, part.Length - 3).Trim());
+ var m = _reSize.Match(part.Trim());
+ if (m.Success) {
+ width = int.Parse(m.Groups[1].Value);
+ height = int.Parse(m.Groups[2].Value);
+ }
+ }
+ }
+ } while (width == 0);
+
+ _process.StandardError.Close();
+ if (fps == 0) fps = 30;
+
+ _gameFramesPerVideoFrame = 60 / fps;
+ _frameIncrement = 0;
+
+ stride = width * bytesPerPixel;
+ /* //TODO: Necessary or not?
+ if ((stride % 32) != 0)
+ stride += 32 - (stride % 32);
+ */
+
+ _texture = new Texture2D(_graphics, stride / bytesPerPixel, height, false, SurfaceFormat.Color);
+ _framebuffer = new byte[_texture.Width * _texture.Height * 4];
+ _texture.SetData(_framebuffer);
+
+ _frame = -1;
+ }
+
+ public void Play(Action onComplete) {
+ _frame = 0;
+ _effectInstance?.Play();
+ _onComplete = onComplete;
+ }
+
+ public void Render() {
+ if (_frame >= 0) {
+ _spriteBatch.Begin(depthStencilState: DepthStencilState.None);
+
+ _spriteBatch.Draw(_texture, new Rectangle(0, 0, _graphics.Viewport.Width, _graphics.Viewport.Height), Color.White);
+
+ _spriteBatch.End();
+ }
+ }
+
+ public unsafe void Step() {
+ if (_process == null) return;
+
+ _frameIncrement++;
+
+ if (_frameIncrement >= _gameFramesPerVideoFrame) {
+ int read = 0, size = _texture.Width * _texture.Height * 4;
+ fixed (byte* baseptr = &_framebuffer[0]) {
+ while (read < size) {
+ byte* ptr = baseptr + read;
+ int count = _process.StandardOutput.BaseStream.Read(new Span(ptr, size - read));
+
+ if (count <= 0) {
+ _process.Kill();
+ _process.Dispose();
+ _process = null;
+ _onComplete?.Invoke();
+ break;
+ }
+
+ read += count;
+ }
+ }
+ _texture.SetData(_framebuffer);
+ _frame++;
+ _frameIncrement -= _gameFramesPerVideoFrame;
+ } else {
+ //
+ }
+ }
+ }
+}
diff --git a/F7/Field/Overlay.cs b/F7/Field/Overlay.cs
new file mode 100644
index 0000000..b4990cf
--- /dev/null
+++ b/F7/Field/Overlay.cs
@@ -0,0 +1,56 @@
+using Braver.UI;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Braver.Field {
+ public class Overlay {
+
+ private Color _color = Color.Black;
+ private SpriteBatch _spriteBatch;
+ private BlendState _blend;
+ private Texture2D _tex;
+ private Rectangle _rect;
+
+ private Action _onComplete;
+ private int _progress, _duration;
+ private Color _cFrom, _cTo;
+
+ public bool HasTriggered { get; private set; }
+
+ public Overlay(FGame g, GraphicsDevice graphics) {
+ _spriteBatch = new SpriteBatch(graphics);
+ g.Singleton(() => new CompositeImages(graphics, g)).Find("white", out _tex, out _rect, out _);
+ }
+
+ public void Fade(int frames, BlendState blend, Color cFrom, Color cTo, Action onComplete) {
+ _color = _cFrom = cFrom;
+ _cTo = cTo;
+ _onComplete = onComplete;
+ _progress = 0;
+ _duration = frames;
+ _blend = blend;
+ HasTriggered = true;
+ }
+
+ public void Render() {
+ if ((_color.PackedValue & 0xffffff) != 0) {
+ _spriteBatch.Begin(blendState: _blend, depthStencilState: DepthStencilState.None);
+ _spriteBatch.Draw(_tex, new Rectangle(0, 0, 9000, 9000), _rect, _color);
+ _spriteBatch.End();
+ }
+ }
+
+ public void Step() {
+ if (_progress == _duration) {
+ _color = _cTo;
+ _onComplete?.Invoke();
+ } else {
+ _progress++;
+ _color = Color.Lerp(_cFrom, _cTo, 1f * _progress / _duration);
+ }
+ }
+ }
+}
diff --git a/F7/Field/VM.cs b/F7/Field/VM.cs
index e39fe50..fdb4fab 100644
--- a/F7/Field/VM.cs
+++ b/F7/Field/VM.cs
@@ -1,15 +1,7 @@
-using Microsoft.Xna.Framework;
-using NAudio.Gui;
-using SharpDX.Direct2D1;
-using SharpDX.Direct3D9;
-using SixLabors.ImageSharp;
-using System;
+using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading.Tasks;
-using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace Braver.Field {
public enum OpCode {
@@ -383,6 +375,7 @@ namespace Braver.Field {
Register(typeof(PartyInventory));
Register(typeof(FieldModels));
Register(typeof(Maths));
+ Register(typeof(SystemControl));
}
public static OpResult Execute(OpCode op, Fiber f, Entity e, FieldScreen s) {
@@ -783,6 +776,17 @@ namespace Braver.Field {
return OpResult.Continue;
}
+ public static OpResult CANIM2(Fiber f, Entity e, FieldScreen s) {
+ byte anim = f.ReadU8(), first = f.ReadU8(), last = f.ReadU8(), speed = f.ReadU8();
+ f.Pause();
+ var state = e.Model.AnimationState;
+ Action onComplete = () => {
+ f.Resume();
+ e.Model.AnimationState = state;
+ };
+ e.Model.PlayAnimation(anim, false, 1f / speed, onComplete, first, last); //TODO is this speed even vaguely correct?
+ return OpResult.Continue;
+ }
public static OpResult ANIME1(Fiber f, Entity e, FieldScreen s) {
byte anim = f.ReadU8(), speed = f.ReadU8();
f.Pause();
@@ -937,6 +941,11 @@ namespace Braver.Field {
return OpResult.Continue;
}
+ public static OpResult MMBLK(Fiber f, Entity e, FieldScreen s) {
+ byte charID = f.ReadU8();
+ s.Game.SaveData.Characters[charID].Flags |= CharFlags.Locked;
+ return OpResult.Continue;
+ }
}
internal static class WindowMenu {
@@ -1051,14 +1060,76 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
public static OpResult MUSIC(Fiber f, Entity e, FieldScreen s) {
byte track = f.ReadU8();
- s.Game.Audio.PlayMusic(_trackNames[s.FieldDialog.AkaoMusicIDs[track]]);
+ if (!s.Options.HasFlag(FieldOptions.MusicLocked))
+ s.Game.Audio.PlayMusic(_trackNames[s.FieldDialog.AkaoMusicIDs[track]]);
return OpResult.Continue;
}
public static OpResult BMUSC(Fiber f, Entity e, FieldScreen s) {
byte track = f.ReadU8();
- s.OverrideBattleMusic = _trackNames[s.FieldDialog.AkaoMusicIDs[track]];
+ s.BattleOptions.OverrideMusic = _trackNames[s.FieldDialog.AkaoMusicIDs[track]];
return OpResult.Continue;
}
+ public static OpResult FMUSC(Fiber f, Entity e, FieldScreen s) {
+ byte track = f.ReadU8();
+ s.BattleOptions.PostBattleMusic = _trackNames[s.FieldDialog.AkaoMusicIDs[track]];
+ return OpResult.Continue;
+ }
+ public static OpResult MULCK(Fiber f, Entity e, FieldScreen s) {
+ byte locked = f.ReadU8();
+ if (locked != 0)
+ s.Options |= FieldOptions.MusicLocked;
+ else
+ s.Options &= ~FieldOptions.MusicLocked;
+ return OpResult.Continue;
+ }
+
+ private static OpResult DoAKAO(Fiber f, Entity e, FieldScreen s, byte op, int parm1, int parm2, int parm3, int parm4, int parm5) {
+ switch (op) {
+ case 0xc8:
+ case 0xc9:
+ case 0xca:
+ //Music pan - Not implemented in original FF7 (but maybe we could?)
+ break;
+
+ case 0xf0:
+ if (!s.Options.HasFlag(FieldOptions.MusicLocked))
+ s.Game.Audio.StopMusic();
+ break;
+
+ default: //TODO - all ops!
+ throw new NotImplementedException();
+ }
+
+ return OpResult.Continue;
+ }
+
+ public static OpResult AKAO(Fiber f, Entity e, FieldScreen s) {
+ byte bank12 = f.ReadU8(), bank34 = f.ReadU8(), bank5 = f.ReadU8(),
+ op = f.ReadU8(), p1 = f.ReadU8();
+ ushort p2 = f.ReadU16(), p3 = f.ReadU16(), p4 = f.ReadU16(), p5 = f.ReadU16();
+
+ int parm1 = s.Game.Memory.Read(bank12 >> 4, p1),
+ parm2 = s.Game.Memory.Read(bank12 & 0xf, p2),
+ parm3 = s.Game.Memory.Read(bank34 >> 4, p3),
+ parm4 = s.Game.Memory.Read(bank34 & 0xf, p4),
+ parm5 = s.Game.Memory.Read(bank5 >> 4, p5);
+
+ return DoAKAO(f, e, s, op, parm1, parm2, parm3, parm4, parm5);
+ }
+ public static OpResult AKAO2(Fiber f, Entity e, FieldScreen s) {
+ byte bank12 = f.ReadU8(), bank34 = f.ReadU8(), bank5 = f.ReadU8(),
+ op = f.ReadU8();
+ ushort p1 = f.ReadU16(), p2 = f.ReadU16(), p3 = f.ReadU16(), p4 = f.ReadU16(), p5 = f.ReadU16();
+
+ int parm1 = s.Game.Memory.Read(bank12 >> 4, p1),
+ parm2 = s.Game.Memory.Read(bank12 & 0xf, p2),
+ parm3 = s.Game.Memory.Read(bank34 >> 4, p3),
+ parm4 = s.Game.Memory.Read(bank34 & 0xf, p4),
+ parm5 = s.Game.Memory.Read(bank5 >> 4, p5);
+
+ return DoAKAO(f, e, s, op, parm1, parm2, parm3, parm4, parm5);
+ }
+
public static OpResult SOUND(Fiber f, Entity e, FieldScreen s) {
byte banks = f.ReadU8();
@@ -1070,6 +1141,23 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
return OpResult.Continue;
}
+ public static OpResult PMVIE(Fiber f, Entity e, FieldScreen s) {
+ byte which = f.ReadU8();
+ s.Movie.Prepare(which);
+ return OpResult.Continue;
+ }
+ public static OpResult MOVIE(Fiber f, Entity e, FieldScreen s) {
+ f.Pause();
+ s.Movie.Play(f.Resume);
+ 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));
+ return OpResult.Continue;
+ }
+
+
public static OpResult SCRLW(Fiber f, Entity e, FieldScreen s) {
if (s.Options.HasFlag(FieldOptions.CameraIsAsyncScrolling))
return OpResult.Restart;
@@ -1087,6 +1175,41 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
return OpResult.Continue;
}
+ private class ScrollState {
+ public Vector2 Start;
+ public int Frame;
+ }
+ public static OpResult SCR2DC(Fiber f, Entity e, FieldScreen s) {
+ byte banks = f.ReadU8(), bankS = f.ReadU8();
+ short sx = f.ReadS16(), sy = f.ReadS16(), ss = f.ReadS16();
+ int x = s.Game.Memory.Read(banks & 0xf, sx),
+ y = s.Game.Memory.Read(banks >> 4, sy),
+ speed = s.Game.Memory.Read(bankS, ss);
+
+ ScrollState state;
+ if (f.ResumeState == null) {
+ var scroll = s.GetBGScroll();
+ state = new ScrollState {
+ Start = new Vector2(scroll.x, scroll.y),
+ };
+ f.ResumeState = state;
+ } else
+ state = (ScrollState)f.ResumeState;
+
+ Vector2 end = new Vector2(x, y);
+ var progress = Easings.QuadraticInOut(1f * state.Frame / speed); //TODO - is interpreting speed as framecount vaguely correct?
+
+ if (progress >= 1f) {
+ s.BGScroll(x, y);
+ return OpResult.Continue;
+ } else {
+ var pos = state.Start + (end - state.Start) * progress;
+ s.BGScroll(pos.X, pos.Y);
+ state.Frame++;
+ return OpResult.Restart;
+ }
+ }
+
public static OpResult SCRLC(Fiber f, Entity e, FieldScreen s) {
byte bank = f.ReadU8();
byte sspeed = f.ReadU8();
@@ -1157,6 +1280,42 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
return OpResult.Continue;
}
+
+ private static OpResult DoFade(Fiber f, Entity e, FieldScreen s, byte bankRG, byte bankB,
+ byte r, byte g, byte b, byte frames, byte fadeType, byte adjust) {
+
+ int cR = (byte)s.Game.Memory.Read(bankRG >> 4, r),
+ cG = (byte)s.Game.Memory.Read(bankRG & 0xf, g),
+ cB = (byte)s.Game.Memory.Read(bankB, b);
+
+ Color cStandard = new Color(cR, cG, cB, 0xff),
+ cInverse = new Color(0xff - cR, 0xff - cG, 0xff - cB, 0xff),
+ cInverse4 = new Color(4 * (0xff - cR), 4 * (0xff - cG), 4 * (0xff - cB), 0xff);
+
+ switch (fadeType) {
+ case 0: //NFADE type 0
+ case 4: //FADE type 4 - both basically the same...?
+ s.Overlay.Fade(0, BlendState.Additive, Color.Black, Color.Black, null);
+ return OpResult.Continue;
+ default: //TODO - other types!
+ throw new NotImplementedException();
+ }
+ }
+
+ public static OpResult NFADE(Fiber f, Entity e, FieldScreen s) {
+ byte bankRG = f.ReadU8(), bankB = f.ReadU8(), fadeType = f.ReadU8(),
+ r = f.ReadU8(), g = f.ReadU8(), b = f.ReadU8(),
+ speed = f.ReadU8(), adjust = f.ReadU8();
+
+ return DoFade(f, e, s, bankRG, bankB, r, g, b, speed, fadeType, adjust);
+ }
+ public static OpResult FADE(Fiber f, Entity e, FieldScreen s) {
+ byte bankRG = f.ReadU8(), bankB = f.ReadU8(), r = f.ReadU8(), g = f.ReadU8(), b = f.ReadU8(),
+ speed = f.ReadU8(), fadeType = f.ReadU8(), adjust = f.ReadU8();
+
+ return DoFade(f, e, s, bankRG, bankB, r, g, b, (byte)(240 / speed), fadeType, adjust);
+ }
+
}
internal static class Maths {
@@ -1214,4 +1373,16 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
return OpResult.Continue;
}
}
+
+ public static class SystemControl {
+ public static OpResult BTLON(Fiber f, Entity e, FieldScreen s) {
+ s.BattleOptions.BattlesEnabled = f.ReadU8() == 0;
+ return OpResult.Continue;
+ }
+
+ public static OpResult BTLMD(Fiber f, Entity e, FieldScreen s) {
+ s.BattleOptions.Flags = (BattleFlags)f.ReadU16();
+ return OpResult.Continue;
+ }
+ }
}
diff --git a/F7/GraphicsUtil.cs b/F7/GraphicsUtil.cs
index 9d06a53..7d60199 100644
--- a/F7/GraphicsUtil.cs
+++ b/F7/GraphicsUtil.cs
@@ -33,6 +33,23 @@ namespace Braver {
public static class GraphicsUtil {
+ public static readonly BlendState BlendSubtractive = new BlendState {
+ AlphaBlendFunction = BlendFunction.ReverseSubtract,
+ AlphaDestinationBlend = Blend.One,
+ AlphaSourceBlend = Blend.SourceAlpha,
+ ColorBlendFunction = BlendFunction.ReverseSubtract,
+ ColorDestinationBlend = Blend.One,
+ ColorSourceBlend = Blend.SourceAlpha,
+ };
+ public static readonly BlendState BlendDarken = new BlendState {
+ AlphaBlendFunction = BlendFunction.ReverseSubtract,
+ AlphaDestinationBlend = Blend.One,
+ AlphaSourceBlend = Blend.One,
+ ColorBlendFunction = BlendFunction.ReverseSubtract,
+ ColorDestinationBlend = Blend.One,
+ ColorSourceBlend = Blend.InverseSourceColor,
+ };
+
public static int MakePowerOfTwo(int i) {
int n = 1;
while (n < i)
diff --git a/F7/TODO.txt b/F7/TODO.txt
index 4a6babe..5550111 100644
--- a/F7/TODO.txt
+++ b/F7/TODO.txt
@@ -6,17 +6,17 @@
Background load razor engine - do other templates? Automatically load all of them after any specified
"priority" templates like MainMenu?
-Moving through walkmap triangles on field is still broken, esp. when running
-
IFKEYON etc not fully working - prob. due to IsJustPressed one-frame check conflicting with field scripts
not running every frame?
Shops!
-ASK opcode
-
Compressed/single file saves
Music looping
-Item menu: Icons, actually do arrange
\ No newline at end of file
+Item menu: Icons, actually do arrange
+
+Field Fade network msg, movie msg
+
+NEED TO IMPLEMENT field walkmesh tri sliding for first map to complete!
\ No newline at end of file
diff --git a/F7/UI/Splash.cs b/F7/UI/Splash.cs
index e797203..7124eae 100644
--- a/F7/UI/Splash.cs
+++ b/F7/UI/Splash.cs
@@ -59,11 +59,17 @@ namespace Braver.UI {
Game.NewGame();
Game.ChangeScreen(this, new Field.FieldScreen(
new Ficedula.FF7.Field.FieldDestination {
- X = 7, Y = -107, Triangle = 21,
+ X = 3600, Y = 27320, Triangle = 28,
Orientation = 132,
- DestinationFieldID = 180,
+ DestinationFieldID = 116,
}
-
+ /*
+ new Ficedula.FF7.Field.FieldDestination {
+ X = 7, Y = -107, Triangle = 21,
+ Orientation = 132,
+ DestinationFieldID = 180,
+ }
+ */
/*
new Ficedula.FF7.Field.FieldDestination {
X = -225, Y = -830, Triangle = 152,
diff --git a/data/movies/movielist.txt b/data/movies/movielist.txt
new file mode 100644
index 0000000..c37a3eb
--- /dev/null
+++ b/data/movies/movielist.txt
@@ -0,0 +1,54 @@
+fship2
+
+d_ropego
+d_ropein
+u_ropein
+u_ropego
+gold2
+gold3
+gold4
+gold6
+gold5
+boogup
+boogdown
+junair_u
+junair_d
+junelein
+junelego
+junin_in
+junin_go
+moriya
+mkup
+northmk
+mk8
+ontrain
+mainplr
+smk
+southmk
+plrexp
+fallpl
+monitor
+bike
+mtnvl
+mtnvl2
+brgnvl
+nvlmk
+nivlsfs
+jenova_e
+junon
+hiwind0
+mtcrl
+gold1
+biskdead
+boogdemo
+boogstar
+setogake
+rcktfail
+jairofly
+jairofal
+gold7
+gold7_2
+earithdd
+funeral
+car_1209
+opening
\ No newline at end of file