diff --git a/Braver.Core/BGame.cs b/Braver.Core/BGame.cs index b164a66..e772cf2 100644 --- a/Braver.Core/BGame.cs +++ b/Braver.Core/BGame.cs @@ -121,9 +121,11 @@ namespace Braver { protected Dictionary> _data = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); private Dictionary _singletons = new(); protected Dictionary _paths = new(StringComparer.InvariantCultureIgnoreCase); + private Random _random; public BGame() { SaveMap = new SaveMap(Memory); + _random = new Random(); } public int GameTimeFrames => GameTimeSeconds * 30 + SaveMap.GameTimeFrames; @@ -216,6 +218,11 @@ namespace Braver { SaveData.CleanUp(); } + public int NextRandom(int max) { + lock (_random) + return _random.Next(max); + } + public T Singleton() where T : Cacheable, new() { return Singleton(() => { T t = new T(); diff --git a/Braver.Core/Battle/AI.cs b/Braver.Core/Battle/AI.cs index 718eff7..f23cbe2 100644 --- a/Braver.Core/Battle/AI.cs +++ b/Braver.Core/Battle/AI.cs @@ -211,6 +211,9 @@ namespace Braver.Battle { case 0x190: getValue = (c, index) => (ushort)(c.MaxHP >> 16); break; + case 0x270: + getValue = (c, index) => (ushort)c.Row; break; + default: throw new NotImplementedException(); } diff --git a/Braver.Core/ExeData.cs b/Braver.Core/ExeData.cs new file mode 100644 index 0000000..b86038e --- /dev/null +++ b/Braver.Core/ExeData.cs @@ -0,0 +1,47 @@ +// This program and the accompanying materials are made available under the terms of the +// Eclipse Public License v2.0 which accompanies this distribution, and is available at +// https://www.eclipse.org/legal/epl-v20.html +// +// SPDX-License-Identifier: EPL-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Braver { + public class ExeData : DataSource, IDisposable { + + //TODO: This doesn't really deal with different exe versions usefully + //Ideally it needs to be able to shift offsets by searching for content or something + + private System.Reflection.PortableExecutable.PEReader _peReader; + private Dictionary _files = new(StringComparer.InvariantCultureIgnoreCase); + + public ExeData(string sourceFile, string sectionsFile, BGame game) { + _peReader = new System.Reflection.PortableExecutable.PEReader(File.OpenRead(sourceFile)); + foreach(string line in game.OpenString("braver", sectionsFile).Split('\r', '\n')) { + if (string.IsNullOrWhiteSpace(line) || line.StartsWith('#')) + continue; + string[] parts = line.Split((char[])null, StringSplitOptions.RemoveEmptyEntries); + _files[parts[0]] = (int.Parse(parts[1], System.Globalization.NumberStyles.HexNumber), int.Parse(parts[2], System.Globalization.NumberStyles.HexNumber)); + } + } + + public void Dispose() { + _peReader.Dispose(); + } + + public override IEnumerable Scan() { + return _files.Keys; + } + + public override Stream TryOpen(string file) { + if (_files.TryGetValue(file, out var which)) { + var data = _peReader.GetSectionData(which.address - (int)_peReader.PEHeaders.PEHeader.ImageBase); + var content = data.GetContent(); + return new MemoryStream(content.Take(Math.Min(which.size, content.Length)).ToArray()); + } else + return null; + } + } +} diff --git a/Braver/Battle/AnimScriptExecutor.cs b/Braver/Battle/AnimScriptExecutor.cs index 47c51fa..3a16816 100644 --- a/Braver/Battle/AnimScriptExecutor.cs +++ b/Braver/Battle/AnimScriptExecutor.cs @@ -81,7 +81,7 @@ namespace Braver.Battle { Action effRender = null; var pos = model.Translation + model.Translation2; effRender = () => { - if (effect.Render(pos, _screen.Renderer.View3D, frame++)) + if (effect.Render(pos, _screen.CameraController.View, frame++)) _renderers.Remove(effRender); }; _renderers.Add(effRender); diff --git a/Braver/Battle/BattleDebug.cs b/Braver/Battle/BattleDebug.cs index dd74eb9..469f1d2 100644 --- a/Braver/Battle/BattleDebug.cs +++ b/Braver/Battle/BattleDebug.cs @@ -96,6 +96,15 @@ namespace Braver.Battle { ); } + if (input.IsJustDown(InputKey.Select)) { + _screen.CameraController.Execute( + 36, + _screen.Renderer.Models[_engine.ActiveCombatants.ElementAt(_cMenu)], + _engine.ActiveCombatants.Where(c => !c.IsPlayer).Select(c => _screen.Renderer.Models[c]), + () => _screen.CameraController.ResetToIdleCamera() + ); + } + if (input.IsJustDown(InputKey.Start)) { /* var sprite = new LoadedSprite(_game, _graphics, "fi_a01.s", new[] { "fire00.tex", "fire01.tex" }); diff --git a/Braver/Battle/BattleEffectManager.cs b/Braver/Battle/BattleEffectManager.cs index 2df4c0a..6379e2c 100644 --- a/Braver/Battle/BattleEffectManager.cs +++ b/Braver/Battle/BattleEffectManager.cs @@ -333,7 +333,7 @@ namespace Braver.Battle { * * SPRITE * SFX - * CAMERA ... + * CAMERA |auto * ANIM * RESUME * WAIT ... diff --git a/Braver/Battle/BattleRenderer.cs b/Braver/Battle/BattleRenderer.cs index 833dc95..3cf0cfc 100644 --- a/Braver/Battle/BattleRenderer.cs +++ b/Braver/Battle/BattleRenderer.cs @@ -11,8 +11,27 @@ using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; +using System.Windows.Forms; namespace Braver.Battle { + + public interface ICameraView { + PerspView3D View { get; } + } + + public static class CameraUtil { + public static PerspView3D ToView3D(this BattleCamera cam) { + return new PerspView3D { + CameraPosition = new Vector3(cam.X, cam.Y, cam.Z), + CameraForwards = new Vector3(cam.LookAtX - cam.X, cam.LookAtY - cam.Y, cam.LookAtZ - cam.Z), + CameraUp = -Vector3.UnitY, //TODO!! + ZNear = 100, + ZFar = 100000, + FOV = 25f, + }; + } + } + public class BattleRenderer { private BackgroundKind _backgroundKind; @@ -27,34 +46,22 @@ namespace Braver.Battle { private VertexBuffer _vertexBuffer; private IndexBuffer _indexBuffer; - private PerspView3D _view; private Screen _ui; + private ICameraView _view; public FGame Game { get; private set; } public GraphicsDevice Graphics { get; private set; } public Dictionary Models { get; } = new(); public SpriteRenderer Sprites { get; } - public PerspView3D View3D => _view; - public BattleRenderer(FGame game, GraphicsDevice graphics, Screen uiScreen) { + public BattleRenderer(FGame game, GraphicsDevice graphics, Screen uiScreen, ICameraView view) { Game = game; Graphics = graphics; _ui = uiScreen; + _view = view; Sprites = new SpriteRenderer(graphics); } - public void SetCamera(BattleCamera cam) { - _view = new PerspView3D { - CameraPosition = new Vector3(cam.X, cam.Y, cam.Z), - CameraForwards = new Vector3(cam.LookAtX - cam.X, cam.LookAtY - cam.Y, cam.LookAtZ - cam.Z), - CameraUp = -Vector3.UnitY, //TODO!! - ZNear = 100, - ZFar = 100000, - FOV = 51f, //Seems maybe vaguely correct, more or less what Proud Clod uses for its preview... - }; - Game.Net.Send(new SetBattleCameraMessage { Camera = cam }); - } - public void LoadBackground(int locationID) { string prefix = SceneDecoder.LocationIDToFileName(locationID); @@ -140,8 +147,8 @@ namespace Braver.Battle { Graphics.SetVertexBuffer(_vertexBuffer); foreach (var chunk in _backgroundChunks) { - chunk.Effect.View = _view.View; - chunk.Effect.Projection = _view.Projection; + chunk.Effect.View = _view.View.View; + chunk.Effect.Projection = _view.View.Projection; foreach (var pass in chunk.Effect.CurrentTechnique.Passes) { pass.Apply(); Graphics.DrawIndexedPrimitives( @@ -152,7 +159,7 @@ namespace Braver.Battle { foreach (var model in Models.Values) if (model.Visible) - model.Render(_view); + model.Render(_view.View); Sprites.Render(); diff --git a/Braver/Battle/BattleScreen.cs b/Braver/Battle/BattleScreen.cs index 15d54c5..fc6720a 100644 --- a/Braver/Battle/BattleScreen.cs +++ b/Braver/Battle/BattleScreen.cs @@ -102,7 +102,14 @@ namespace Braver.Battle { } } + private enum BattleState { + Intro, + Normal, + Victory, + } + private BattleScene _scene; + private BattleState _state; private UI.UIBatch _menuUI; private Menu _activeMenu; @@ -141,6 +148,7 @@ namespace Braver.Battle { public BattleRenderer Renderer { get; private set; } public SpriteManager Sprites { get; private set; } + public CameraController CameraController { get; private set; } public UI.UIBatch MenuUI => _menuUI; public override string Description { @@ -258,11 +266,12 @@ namespace Braver.Battle { var ui = new UI.Layout.LayoutScreen("battle", _uiHandler, isEmbedded: true); ui.Init(Game, Graphics); - Renderer = new BattleRenderer(g, graphics, ui); + CameraController = new CameraController(g, _scene.CamDatNumber, _scene.Cameras); + CameraController.ResetToIdleCamera(); + + Renderer = new BattleRenderer(g, graphics, ui, CameraController); Renderer.LoadBackground(_scene.LocationID); - var cam = _scene.Cameras[0]; - Renderer.SetCamera(cam); - + foreach (var enemy in _scene.Enemies) { AddModel( SceneDecoder.ModelIDToFileName(enemy.Enemy.ID), @@ -285,6 +294,14 @@ namespace Braver.Battle { g.Net.Send(new Net.ScreenReadyMessage()); g.Audio.PlayMusic("bat", true); //TODO! + + _state = BattleState.Intro; + CameraController.ExecuteIntro( + _scene.InitialCamera, + Renderer.Models[_engine.ActiveCombatants.First(c => !c.IsPlayer)], + _engine.ActiveCombatants.Where(c => c.IsPlayer).Select(c => Renderer.Models[c]), + () => _state = BattleState.Normal + ); } private void InitEngine() { @@ -392,7 +409,7 @@ namespace Braver.Battle { var middle = (model.MaxBounds + model.MinBounds) * 0.5f; if (offset != null) middle += offset.Value; - var screenPos = Renderer.View3D.ProjectTo2D(model.Translation + middle); + var screenPos = CameraController.View.ProjectTo2D(model.Translation + middle); return screenPos; } @@ -500,8 +517,10 @@ namespace Braver.Battle { if (_debug != null) { _debug.Step(); } else { - EngineTick(elapsed); + if (_state == BattleState.Normal) + EngineTick(elapsed); } + CameraController.Step(); Renderer.Step(elapsed); } @@ -535,18 +554,21 @@ namespace Braver.Battle { _debug?.ProcessInput(input); + if (_state != BattleState.Normal) + return; + if (input.IsJustDown(InputKey.Debug1)) _debugCamera = !_debugCamera; if (_debugCamera) { if (input.IsDown(InputKey.Up)) - Renderer.View3D.CameraPosition += new Vector3(0, 0, -100); + CameraController.View.CameraPosition += new Vector3(0, 0, -100); if (input.IsDown(InputKey.Down)) - Renderer.View3D.CameraPosition += new Vector3(0, 0, 100); + CameraController.View.CameraPosition += new Vector3(0, 0, 100); if (input.IsDown(InputKey.Left)) - Renderer.View3D.CameraPosition += new Vector3(100, 0, 0); + CameraController.View.CameraPosition += new Vector3(100, 0, 0); if (input.IsDown(InputKey.Right)) - Renderer.View3D.CameraPosition += new Vector3(-100, 0, 0); + CameraController.View.CameraPosition += new Vector3(-100, 0, 0); } else if (input.IsJustDown(InputKey.Menu)) { NextMenu(); } else if (Targets != null) { diff --git a/Braver/Battle/CameraController.cs b/Braver/Battle/CameraController.cs new file mode 100644 index 0000000..dca510f --- /dev/null +++ b/Braver/Battle/CameraController.cs @@ -0,0 +1,259 @@ +// This program and the accompanying materials are made available under the terms of the +// Eclipse Public License v2.0 which accompanies this distribution, and is available at +// https://www.eclipse.org/legal/epl-v20.html +// +// SPDX-License-Identifier: EPL-2.0 + +using Braver.Net; +using Ficedula.FF7.Battle; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Braver.Battle { + + public class CameraController : ICameraView { + private CameraData _data; + private EmbeddedCameraData _introData; + private FGame _game; + + private CameraFocusScript _focus; + private CameraPositionScript _position; + private Action _onComplete; + private int _focusWait, _positionWait; + private Model _source; + private List _targets; + private int _idleCamera; + private List _cameras; + private PerspView3D _view; + + private Func _getPosition, _getFocus; + + public PerspView3D View => _view; + + public CameraController(FGame g, int camdatNumber, IEnumerable cameras) { + _data = new CameraData(g.Open("battle", $"camdat{camdatNumber}.bin")); + _introData = new EmbeddedCameraData(g.Open("exe", "IntroCamera.bin"), 0x900000, 0x10D0, 0x1270); + _game = g; + _cameras = cameras.ToList(); + ResetToIdleCamera(); + } + + public void ResetToIdleCamera() { + var cam = _cameras[_idleCamera]; + _view = cam.ToView3D(); + _game.Net.Send(new SetBattleCameraMessage { Camera = cam }); + } + + public void ExecuteIntro(int script, Model source, IEnumerable targets, Action onComplete) { + _onComplete = onComplete; + Trace.WriteLine($"Executing intro camera script {script}"); + _focus = _introData.ReadFocus(script); + _position = _introData.ReadPosition(script); + _source = source; + _targets = targets.ToList(); + } + + public void Execute(int script, Model source, IEnumerable targets, Action onComplete) { + _onComplete = onComplete; + int variation = _game.NextRandom(3); + Trace.WriteLine($"Executing camera script {script} variation {variation}"); + _focus = _data.ReadFocus(script, variation, false); + _position = _data.ReadPosition(script, variation, false); + _source = source; + _targets = targets.ToList(); + } + + public void ExecuteVictory(Model source, IEnumerable targets) { + int variation = _game.NextRandom(3); + _focus = _data.ReadFocus(0, variation, false); + _position = _data.ReadPosition(0, variation, false); + _source = source; + _targets = targets.ToList(); + } + + private void TriggerCompletionIfNecessary() { + if ((_position == null) && (_focus == null)) { + _onComplete?.Invoke(); + _onComplete = null; + } + } + + private Func GetTransition(Vector3 start, Vector3 end, int frames) { + int frame = 0; + return () => { + float progress; + if (frame < frames) + progress = 1f * frame / frames; + else + progress = 1f; + frame++; + return Vector3.Lerp(start, end, progress); + }; + } + + private Vector3 AggregatePositions(IEnumerable positions) { + var positionsArray = positions.ToArray(); + return positionsArray + .Aggregate((v1, v2) => v1 + v2) / positionsArray.Length; + } + + //We want to run our camera transitions at 60fps + private const int FRAME_MULTIPLIER = 2; + + private bool Execute(DecodedCameraOp op) { + switch (op.Opcode) { + case CameraPositionOpcode.JumpToAttackerJoint: + int bone = op.Operands[0]; + Vector3 offset = new Vector3(op.Operands[1], op.Operands[2], op.Operands[3]); + _getPosition = () => _source.GetBonePosition(bone) + offset; + return true; + case CameraPositionOpcode.TransitionToAttackerJoint: + bone = op.Operands[0]; + offset = new Vector3(op.Operands[1], op.Operands[2], op.Operands[3]); + var start = _view.CameraPosition; + _getPosition = GetTransition(start, _source.GetBonePosition(bone) + offset, op.Operands[4] * FRAME_MULTIPLIER); + return true; + case CameraPositionOpcode.TransitionToTargetJoint: + bone = op.Operands[0]; + offset = new Vector3(op.Operands[1], op.Operands[2], op.Operands[3]); + start = _view.CameraPosition; + _getPosition = GetTransition( + start, + AggregatePositions(_targets.Select(t => t.GetBonePosition(bone) + offset)), + op.Operands[4] * FRAME_MULTIPLIER + ); + return true; + case CameraPositionOpcode.TransitionToIdle: + var idleCam = _cameras[_idleCamera]; + start = _getFocus?.Invoke() ?? Vector3.Zero; + _getPosition = GetTransition( + start, + new Vector3(idleCam.X, idleCam.Y, idleCam.Z), + op.Operands[0] + ); + return true; + case CameraPositionOpcode.LoadPoint: + Vector3 fixedPosition = new Vector3(op.Operands[0], op.Operands[1], op.Operands[2]); + _getPosition = () => fixedPosition; + return true; + case CameraPositionOpcode.Wait: + if (_positionWait > 0) { + _positionWait--; + _position.Rewind(); + return false; + } + return true; + case CameraPositionOpcode.SetWait: + _positionWait = op.Operands[0] * FRAME_MULTIPLIER; + return true; + case CameraPositionOpcode.ConditionalRestart: + _position.ConditionalRestart(_positionWait); + return true; + case CameraPositionOpcode.ScriptEnd: + _position = null; + TriggerCompletionIfNecessary(); + return false; + /* + case CameraPositionOpcode.SetActiveIdleCamera: + break; + case CameraPositionOpcode.LoadIdleCameraPos: + break; + */ + default: + Trace.WriteLine($"Skipping unimplemented CameraPositionOpcode {op.Opcode}"); + return true; + } + } + private bool Execute(DecodedCameraOp op) { + switch (op.Opcode) { + case CameraFocusOpcode.JumpToAttackerJoint: + int bone = op.Operands[0]; + var offset = new Vector3(op.Operands[1], op.Operands[2], op.Operands[3]); + var pos = _source.GetBonePosition(bone); + Trace.WriteLine($"Focus: Jump to {pos} offset {offset}"); + _getFocus = () => pos + offset; + return true; + case CameraFocusOpcode.TransitionToAttackerJoint: + bone = op.Operands[0]; + offset = new Vector3(op.Operands[1], op.Operands[2], op.Operands[3]); + var start = _getFocus?.Invoke() ?? Vector3.Zero; + _getFocus = GetTransition(start, _source.GetBonePosition(bone) + offset, op.Operands[4] * FRAME_MULTIPLIER); + return true; + case CameraFocusOpcode.TransitionToTargetJoint: + bone = op.Operands[0]; + offset = new Vector3(op.Operands[1], op.Operands[2], op.Operands[3]); + start = _getFocus?.Invoke() ?? Vector3.Zero; + _getFocus = GetTransition( + start, + AggregatePositions(_targets.Select(t => t.GetBonePosition(bone) + offset)), + op.Operands[4] * FRAME_MULTIPLIER + ); + return true; + + case CameraFocusOpcode.TransitionToIdle: + var idleCam = _cameras[_idleCamera]; + start = _getFocus?.Invoke() ?? Vector3.Zero; + _getFocus = GetTransition( + start, + new Vector3(idleCam.LookAtX, idleCam.LookAtY, idleCam.LookAtZ), + op.Operands[0] + ); + return true; + + case CameraFocusOpcode.Wait: + if (_focusWait > 0) { + _focusWait--; + _focus.Rewind(); + return false; + } + return true; + case CameraFocusOpcode.SetWait: + _focusWait = op.Operands[0] * FRAME_MULTIPLIER; + return true; + case CameraFocusOpcode.LoadPoint: + Vector3 fixedFocus = new Vector3(op.Operands[0], op.Operands[1], op.Operands[2]); + _getFocus = () => fixedFocus; + return true; + case CameraFocusOpcode.ConditionalRestart: + _focus.ConditionalRestart(_focusWait); + return true; + /* + case CameraFocusOpcode.SetActiveIdleCamera: + break; + case CameraFocusOpcode.LoadIdleCameraPos: + break; + */ + case CameraFocusOpcode.ScriptEnd: + _focus = null; + TriggerCompletionIfNecessary(); + return false; + default: + Trace.WriteLine($"Skipping unimplemented CameraFocusOpcode {op.Opcode}"); + return true; + } + } + + public void Step() { + if (_focus != null) + while (Execute(_focus.NextOp())) { } + + if (_position != null) + while (Execute(_position.NextOp())) { } + + var camera = _view.Clone(); + if (_getPosition != null) + camera.CameraPosition = _getPosition(); + if (_getFocus != null) { + var focus = _getFocus(); + camera.CameraForwards = focus - camera.CameraPosition; + } + _view = camera; + } + + } +} diff --git a/Braver/Battle/ClientBattleScreen.cs b/Braver/Battle/ClientBattleScreen.cs index 0ed4be4..b5afaa8 100644 --- a/Braver/Battle/ClientBattleScreen.cs +++ b/Braver/Battle/ClientBattleScreen.cs @@ -20,8 +20,18 @@ using Ficedula.FF7; namespace Braver.Battle { public class ClientBattleScreen : Screen, Net.IListen, - Net.IListen, Net.IListen, + Net.IListen, Net.IListen { + + private class ClientCameraController : ICameraView, Net.IListen { + public PerspView3D View { get; private set; } + + public void Received(SetBattleCameraMessage message) { + View = message.Camera.ToView3D(); + } + } + + public override Color ClearColor => Color.Black; public override string Description => ""; @@ -35,6 +45,7 @@ namespace Braver.Battle { private TargetOptionsMessage _targets; private TargetOption _currentTargets; private ICharacterAction _targetsFor; + private ClientCameraController _camera; public ClientBattleScreen(int formationID) { _formationID = formationID; @@ -49,10 +60,14 @@ namespace Braver.Battle { UpdateInBackground = true, }; ui.Init(g, graphics); - _renderer = new BattleRenderer(g, graphics, ui); + + _camera = new ClientCameraController(); + g.Net.Listen(_camera); + + + _renderer = new BattleRenderer(g, graphics, ui, _camera); _renderer.LoadBackground(scene.LocationID); g.Net.Listen(this); - g.Net.Listen(this); g.Net.Listen(this); g.Net.Listen(this); @@ -111,7 +126,7 @@ namespace Braver.Battle { private Vector2 GetModelScreenPos(int modelID) { var model = _renderer.Models[modelID]; var middle = (model.MaxBounds + model.MinBounds) * 0.5f; - var screenPos = _renderer.View3D.ProjectTo2D(model.Translation + middle); + var screenPos = _camera.View.ProjectTo2D(model.Translation + middle); return screenPos.XY(); } @@ -124,10 +139,6 @@ namespace Braver.Battle { _renderer.Models.Add(message.ID, model); } - public void Received(SetBattleCameraMessage message) { - _renderer.SetCamera(message.Camera); - } - public void Received(CharacterReadyMessage message) { if (_activeMenu == null) { _activeMenu = new Menu( diff --git a/Braver/Battle/Model.cs b/Braver/Battle/Model.cs index 9ab3427..4a3af1e 100644 --- a/Braver/Battle/Model.cs +++ b/Braver/Battle/Model.cs @@ -143,14 +143,15 @@ namespace Braver.Battle { Math.Max(maxBounds.Y, transformed.Max(v => v.Y)), Math.Max(maxBounds.Z, transformed.Max(v => v.Z)) ); - } + }, + null ); MinBounds = minBounds; MaxBounds = maxBounds; System.Diagnostics.Trace.WriteLine($"Model {skeleton} with min bounds {minBounds}, max {maxBounds}"); } - private void Descend(BBone bone, Matrix m, Action onChunk) { + private void Descend(BBone bone, Matrix m, Action onChunk, Action onBone) { var frame = _animations.Anims[AnimationState.Animation].Frames[AnimationState.Frame]; Matrix child = m; var rotation = frame.Rotations[bone.Index + 1]; @@ -181,16 +182,37 @@ namespace Braver.Battle { ; } - if (bone.PFileIndex != null) { + if ((bone.PFileIndex != null) && (onChunk != null)) { foreach (var chunk in _pfiles[bone.PFileIndex.Value].Chunks) { onChunk(chunk, child); } } + if (onBone != null) + onBone(bone, child); child = Matrix.CreateTranslation(0, 0, bone.Length) * child; foreach (var cb in bone.Children) - Descend(cb, child, onChunk); + Descend(cb, child, onChunk, onBone); + } + + private Matrix GetBaseTransform() { + return Matrix.CreateRotationX(0 * (float)Math.PI / 180) + * Matrix.CreateRotationZ((-Rotation.Z + Rotation2.Z) * (float)Math.PI / 180) + * Matrix.CreateRotationX((-Rotation.X + Rotation2.X) * (float)Math.PI / 180) + * Matrix.CreateRotationY((-Rotation.Y + Rotation2.Y) * (float)Math.PI / 180) + * Matrix.CreateScale(Scale, Scale, Scale) + * Matrix.CreateTranslation(Translation + Translation2); + } + + public Vector3 GetBonePosition(int boneIndex) { + Vector3 pos = Vector3.Zero; + Descend(_root, GetBaseTransform(), null, (bone, matrix) => { + if (bone.Index == boneIndex) + pos = Vector3.Transform(Vector3.Zero, matrix); + }); + //TODO - could optimise to not need to walk the whole skeleton + return pos; } public void Render(Viewer viewer) { @@ -211,13 +233,7 @@ namespace Braver.Battle { using (graphicsState) { Descend( _root, - Matrix.CreateRotationX(0 * (float)Math.PI / 180) - * Matrix.CreateRotationZ((-Rotation.Z + Rotation2.Z) * (float)Math.PI / 180) - * Matrix.CreateRotationX((-Rotation.X + Rotation2.X) * (float)Math.PI / 180) - * Matrix.CreateRotationY((-Rotation.Y + Rotation2.Y) * (float)Math.PI / 180) - * Matrix.CreateScale(Scale, Scale, Scale) - * Matrix.CreateTranslation(Translation + Translation2) - , + GetBaseTransform(), (chunk, m) => { var rn = _nodes[chunk]; BasicEffect effect; @@ -237,7 +253,8 @@ namespace Braver.Battle { PrimitiveType.TriangleList, rn.VertOffset, rn.IndexOffset, rn.TriCount ); } - } + }, + null ); } } diff --git a/Braver/FGame.cs b/Braver/FGame.cs index 18eb352..3ab59f5 100644 --- a/Braver/FGame.cs +++ b/Braver/FGame.cs @@ -100,6 +100,8 @@ namespace Braver { list.Add(new LGPDataSource(new Ficedula.FF7.LGPFile(path))); else if (kind == "FILE") list.Add(new FileDataSource(path)); + else if (kind == "EXE") + list.Add(new ExeData(path, Expand(parts[4]), this)); else if (kind == "PACK") { if (!packs.TryGetValue(path, out var pack)) packs[path] = pack = new Pack(new FileStream(path, FileMode.Open, FileAccess.Read)); diff --git a/Braver/UI/Splash.cs b/Braver/UI/Splash.cs index 6dfaf0e..c8a2ba4 100644 --- a/Braver/UI/Splash.cs +++ b/Braver/UI/Splash.cs @@ -129,7 +129,7 @@ namespace Braver.UI { //Game.PushScreen(new TestScreen()); //Game.ChangeScreen(this, new TestScreen()); //Game.ChangeScreen(this, new WorldMap.WMScreen(139348, 126329)); - Battle.BattleScreen.Launch(Game, 324, Battle.BattleFlags.BraverDebug); + Battle.BattleScreen.Launch(Game, 0x88, Battle.BattleFlags.BraverDebug); /* Game.ChangeScreen(this, new Field.FieldScreen( new Ficedula.FF7.Field.FieldDestination { diff --git a/Braver/data.txt b/Braver/data.txt index a425f5f..5fbc280 100644 --- a/Braver/data.txt +++ b/Braver/data.txt @@ -23,6 +23,7 @@ DATA FILE? kernel %ff7%\data\lang-en\kernel DATA FILE root %ff7% DATA FILE? battle %braver%\battle +DATA FILE? braver %braver%\braver DATA FILE? field %braver%\field DATA FILE? layout %braver%\layout DATA FILE? movies %braver%\movies @@ -30,6 +31,8 @@ DATA FILE? save %braver%\save DATA FILE? ui %braver%\ui DATA FILE? wm %braver%\wm +DATA PACK? battle %bdata% +DATA PACK? braver %bdata% DATA PACK? field %bdata% DATA PACK? layout %bdata% DATA PACK? movies %bdata% @@ -38,11 +41,13 @@ DATA PACK? ui %bdata% DATA PACK? wm %bdata% DATA FILE? vgmstream %music% + +DATA EXE exe %ff7exe% exe_en.txt + PATH SFX %ff7%\data\sound PATH FFMPEG %braver%\ffmpeg.exe PATH MOVIES %movies% PATH DEBUG %braver% PATH SAVE %save% -PATH FF7EXE %ff7exe% PATH PLUGINS %plugins% diff --git a/Ficedula.FF7/Battle/Camera.cs b/Ficedula.FF7/Battle/Camera.cs index da6d921..ca4c3b6 100644 --- a/Ficedula.FF7/Battle/Camera.cs +++ b/Ficedula.FF7/Battle/Camera.cs @@ -13,12 +13,82 @@ using System.Threading; using System.Threading.Tasks; namespace Ficedula.FF7.Battle { - public class CameraData : IDisposable { - private Stream _source; + public abstract class BaseCameraData : IDisposable { + protected Stream _source; + protected List _offsets; - private List _offsets; - public int CameraCount { get; private set; } + public int CameraCount { get; protected set; } + + protected byte[] Read(int group, int camera) { + int[] offsets = _offsets[group]; + int offset = offsets[camera], + next = offsets[camera + 1]; + byte[] data = new byte[next - offset]; + _source.Position = offset; + _source.Read(data, 0, data.Length); + return data; + } + + public void Dispose() { + _source.Dispose(); + } + } + + public class EmbeddedCameraData : BaseCameraData { + + public EmbeddedCameraData(Stream source, int baseAddress, int positionTableOffset, int focusTableOffset) { + _source = source; + + List GetOffsets(int start) { + var offsets = new List(); + source.Position = start; + while (true) { + int offset = source.ReadI32(); + if (offset < baseAddress) break; + offsets.Add(offset - baseAddress); + } + return offsets; + } + + var posOffset = GetOffsets(positionTableOffset); + var focusOffset = GetOffsets(focusTableOffset); + + CameraCount = posOffset.Count; + + if (posOffset.Last() > focusOffset.Last()) { + focusOffset.Add(posOffset.Last()); + posOffset.Add(Math.Min(positionTableOffset, focusTableOffset)); + } else { + posOffset.Add(focusOffset.Last()); + focusOffset.Add(Math.Min(positionTableOffset, focusTableOffset)); + } + + _offsets = new List { + posOffset.ToArray(), focusOffset.ToArray() + }; + } + + public CameraPositionScript ReadPosition(int camera) { + return new CameraPositionScript(Read(0, camera)); + } + public CameraFocusScript ReadFocus(int camera) { + return new CameraFocusScript(Read(1, camera)); + } + } + + public class CameraData : BaseCameraData { + + private byte[] Read(int camera, int index, bool focus, bool victoryCamera) { + return Read((focus ? 1 : 0) + (victoryCamera ? 2 : 0), camera * 3 + index); + } + + public CameraPositionScript ReadPosition(int camera, int index, bool isVictory) { + return new CameraPositionScript(Read(camera, index, false, isVictory)); + } + public CameraFocusScript ReadFocus(int camera, int index, bool isVictory) { + return new CameraFocusScript(Read(camera, index, true, isVictory)); + } public CameraData(Stream source) { _source = source; @@ -39,37 +109,15 @@ namespace Ficedula.FF7.Battle { }) .ToList(); } - - private byte[] Read(int camera, int index, bool focus, bool victoryCamera) { - int[] offsets = _offsets[(focus ? 1 : 0) + (victoryCamera ? 2 : 0)]; - int offset = offsets[camera * 3 + index], - next = offsets[camera * 3 + index + 1]; - byte[] data = new byte[next - offset]; - _source.Position = offset; - _source.Read(data, 0, data.Length); - return data; - } - - public CameraPositionScript ReadPosition(int camera, int index, bool isVictory) { - return new CameraPositionScript(Read(camera, index, false, isVictory)); - } - public CameraFocusScript ReadFocus(int camera, int index, bool isVictory) { - return new CameraFocusScript(Read(camera, index, true, isVictory)); - } - - - public void Dispose() { - _source.Dispose(); - } } public abstract class CameraScript where T : struct { protected static Dictionary _opParams; - protected static T _conditionalRestartOp; private byte[] _bytecode; private int _ip; + private int? _previousIP; protected CameraScript(byte[] bytecode) { _bytecode = bytecode; @@ -77,10 +125,26 @@ namespace Ficedula.FF7.Battle { protected abstract T FromByte(byte b); + public void Rewind() { + if (_previousIP != null) { + _ip = _previousIP.Value; + _previousIP = null; + } else + throw new NotSupportedException(); + } + + public void ConditionalRestart(int waitCounter) { + if ((waitCounter == 0) && _bytecode[_ip] == 0xc0) { + _ip = 0; + } + } + public DecodedCameraOp NextOp() { + _previousIP = _ip; var op = new DecodedCameraOp { Opcode = FromByte(_bytecode[_ip++]), }; + if (_opParams.TryGetValue(op.Opcode, out int[] parms)) { op.Operands = parms .Select(i => { @@ -111,7 +175,7 @@ namespace Ficedula.FF7.Battle { [CameraPositionOpcode.SetActiveIdleCamera] = new[] { 1 }, [CameraPositionOpcode.UnknownDE] = new[] { 1 }, [CameraPositionOpcode.UnknownE0] = new[] { 1, 1 }, - [CameraPositionOpcode.UnknownE2] = new[] { 1 }, + [CameraPositionOpcode.TransitionToIdle] = new[] { 1 }, [CameraPositionOpcode.UnknownE3] = new[] { 1, 1, 2, 2, 2, 1 }, [CameraPositionOpcode.TransitionToAttackerJoint] = new[] { 1, 2, 2, 2, 1 }, [CameraPositionOpcode.TransitionToTargetJoint] = new[] { 1, 2, 2, 2, 1 }, @@ -120,16 +184,14 @@ namespace Ficedula.FF7.Battle { [CameraPositionOpcode.UnknownE9] = new[] { 1, 2, 2, 2, 1 }, [CameraPositionOpcode.UnknownEB] = new[] { 1, 1, 2, 2, 2, 1 }, [CameraPositionOpcode.UnknownEF] = new[] { 1, 1, 2, 2, 2 }, - [CameraPositionOpcode.UnknownF0] = new[] { 1, 2, 2, 2 }, + [CameraPositionOpcode.JumpToAttackerJoint] = new[] { 1, 2, 2, 2 }, [CameraPositionOpcode.UnknownF2] = new[] { 1, 2, 2 }, [CameraPositionOpcode.UnknownF3] = new[] { 1, 2, 2 }, [CameraPositionOpcode.SetWait] = new[] { 1 }, [CameraPositionOpcode.UnknownF7] = new[] { 1, 2, 2, 2 }, [CameraPositionOpcode.UnknownF8] = new[] { 2, 2, 2, 2, 2, 2 }, - [CameraPositionOpcode.UnknownF9] = new[] { 2, 2, 2 }, - //ConditionalRestart is special + [CameraPositionOpcode.LoadPoint] = new[] { 2, 2, 2 }, }; - _conditionalRestartOp = CameraPositionOpcode.ConditionalRestart; } public CameraPositionScript(byte[] bytecode) : base(bytecode) { @@ -153,7 +215,7 @@ namespace Ficedula.FF7.Battle { [CameraFocusOpcode.TransitionToAttackerJoint] = new[] { 1, 1, 2, 2, 2, 1 }, [CameraFocusOpcode.TransitionToTargetJoint] = new[] { 1, 2, 2, 2, 1 }, [CameraFocusOpcode.UnknownE6] = new[] { 2, 2, 2, 1 }, - [CameraFocusOpcode.TransitionToAttackerView] = new[] { 1, 2, 2, 2, 1 }, + [CameraFocusOpcode.JumpToAttackerJoint] = new[] { 1, 2, 2, 2, 1 }, [CameraFocusOpcode.UnknownEA] = new[] { 1, 2, 2, 2, 1 }, [CameraFocusOpcode.UnknownEC] = new[] { 1, 1, 2, 2, 2, 1 }, [CameraFocusOpcode.UnknownF0] = new[] { 1, 1, 2, 2, 2 }, @@ -161,9 +223,7 @@ namespace Ficedula.FF7.Battle { [CameraFocusOpcode.UnknownF8] = new[] { 1, 2, 2, 2 }, [CameraFocusOpcode.UnknownF9] = new[] { 1, 2, 2, 2 }, [CameraFocusOpcode.LoadPoint] = new[] { 2, 2, 2 }, - //ConditionalRestart is special }; - _conditionalRestartOp = CameraFocusOpcode.ConditionalRestart; } public CameraFocusScript(byte[] bytecode) : base(bytecode) { @@ -193,7 +253,7 @@ namespace Ficedula.FF7.Battle { UnknownDF = 0xDF, UnknownE0 = 0xE0, LoadIdleCameraPos = 0xE1, - UnknownE2 = 0xE2, + TransitionToIdle = 0xE2, UnknownE3 = 0xE3, TransitionToAttackerJoint = 0xE4, TransitionToTargetJoint = 0xE5, @@ -202,7 +262,7 @@ namespace Ficedula.FF7.Battle { UnknownE9 = 0xE9, UnknownEB = 0xEB, UnknownEF = 0xEF, - UnknownF0 = 0xF0, + JumpToAttackerJoint = 0xF0, UnknownF1 = 0xF1, UnknownF2 = 0xF2, UnknownF3 = 0xF3, @@ -210,7 +270,7 @@ namespace Ficedula.FF7.Battle { SetWait = 0xF5, UnknownF7 = 0xF7, UnknownF8 = 0xF8, - UnknownF9 = 0xF9, + LoadPoint = 0xF9, ConditionalRestart = 0xFE, ScriptEnd = 0xFF, } @@ -230,7 +290,7 @@ namespace Ficedula.FF7.Battle { TransitionToAttackerJoint = 0xE4, TransitionToTargetJoint = 0xE5, UnknownE6 = 0xE6, - TransitionToAttackerView = 0xE8, + JumpToAttackerJoint = 0xE8, UnknownEA = 0xEA, UnknownEC = 0xEC, UnknownF0 = 0xF0, diff --git a/Ficedula.FF7/Battle/Scene.cs b/Ficedula.FF7/Battle/Scene.cs index 623ab46..7399129 100644 --- a/Ficedula.FF7/Battle/Scene.cs +++ b/Ficedula.FF7/Battle/Scene.cs @@ -29,6 +29,19 @@ namespace Ficedula.FF7.Battle { } public class BattleScene { + private static Dictionary _layoutToCamDatNumber = new() { + [BattleLayout.Normal] = 0, + [BattleLayout.Preemptive] = 0, + [BattleLayout.BackAttack] = 1, + [BattleLayout.SideAttack2] = 2, + [BattleLayout.PincerAttack] = 2, + [BattleLayout.PincerAttack2] = 2, + [BattleLayout.SideAttack2] = 2, + [BattleLayout.SideAttack3] = 2, + [BattleLayout.NormalLockFront] = 0, + }; + + //Computed public int FormationID { get; set; } @@ -38,6 +51,7 @@ namespace Ficedula.FF7.Battle { public List NextBattleArenaFormations { get; } = new(); public ushort EscapableFlag { get; set; } //TODO flags? public BattleLayout Layout { get; set; } + public int CamDatNumber => _layoutToCamDatNumber[Layout]; public byte InitialCamera { get; set; } public List Cameras { get; } = new(); diff --git a/data/braver/exe_en.txt b/data/braver/exe_en.txt new file mode 100644 index 0000000..bb8fcbe --- /dev/null +++ b/data/braver/exe_en.txt @@ -0,0 +1,2 @@ +#FF7.exe EN offsets +IntroCamera.bin 900000 2000 \ No newline at end of file