Implement exe data source, exe embedded camera data

Camera interpreter for battle screen implementing most common opcodes working
This commit is contained in:
ficedula 2023-10-10 23:32:01 +01:00
parent b5a2055da9
commit 11f70afe4c
17 changed files with 556 additions and 91 deletions

View File

@ -121,9 +121,11 @@ namespace Braver {
protected Dictionary<string, List<DataSource>> _data = new Dictionary<string, List<DataSource>>(StringComparer.InvariantCultureIgnoreCase);
private Dictionary<Type, object> _singletons = new();
protected Dictionary<string, string> _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<T>() where T : Cacheable, new() {
return Singleton<T>(() => {
T t = new T();

View File

@ -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();
}

47
Braver.Core/ExeData.cs Normal file
View File

@ -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<string, (int address, int size)> _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<string> 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;
}
}
}

View File

@ -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);

View File

@ -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" });

View File

@ -333,7 +333,7 @@ namespace Braver.Battle {
*
* SPRITE <alias> <combatantID> <xyzOffset>
* SFX <numberOrName> <positionAtCombatantID>
* CAMERA ...
* CAMERA <id>|auto
* ANIM <combatantID> <animNumber>
* RESUME <combatantID>
* WAIT <id> <id> <id>...

View File

@ -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<T> {
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<T, Model> 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();

View File

@ -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<CharacterCombatant> _activeMenu;
@ -141,6 +148,7 @@ namespace Braver.Battle {
public BattleRenderer<ICombatant> 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<ICombatant>(g, graphics, ui);
CameraController = new CameraController(g, _scene.CamDatNumber, _scene.Cameras);
CameraController.ResetToIdleCamera();
Renderer = new BattleRenderer<ICombatant>(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) {

View File

@ -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<Model> _targets;
private int _idleCamera;
private List<BattleCamera> _cameras;
private PerspView3D _view;
private Func<Vector3> _getPosition, _getFocus;
public PerspView3D View => _view;
public CameraController(FGame g, int camdatNumber, IEnumerable<BattleCamera> 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<Model> 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<Model> 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<Model> 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<Vector3> 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<Vector3> 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<CameraPositionOpcode> 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<CameraFocusOpcode> 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;
}
}
}

View File

@ -20,8 +20,18 @@ using Ficedula.FF7;
namespace Braver.Battle {
public class ClientBattleScreen : Screen, Net.IListen<AddBattleModelMessage>,
Net.IListen<SetBattleCameraMessage>, Net.IListen<CharacterReadyMessage>,
Net.IListen<CharacterReadyMessage>,
Net.IListen<TargetOptionsMessage> {
private class ClientCameraController : ICameraView, Net.IListen<SetBattleCameraMessage> {
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<int>(g, graphics, ui);
_camera = new ClientCameraController();
g.Net.Listen<Net.SetBattleCameraMessage>(_camera);
_renderer = new BattleRenderer<int>(g, graphics, ui, _camera);
_renderer.LoadBackground(scene.LocationID);
g.Net.Listen<Net.AddBattleModelMessage>(this);
g.Net.Listen<Net.SetBattleCameraMessage>(this);
g.Net.Listen<Net.CharacterReadyMessage>(this);
g.Net.Listen<Net.TargetOptionsMessage>(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<CharacterReadyMessage>(

View File

@ -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<PFileChunk, Matrix> onChunk) {
private void Descend(BBone bone, Matrix m, Action<PFileChunk, Matrix> onChunk, Action<BBone, Matrix> 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
);
}
}

View File

@ -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));

View File

@ -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 {

View File

@ -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%

View File

@ -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<int[]> _offsets;
private List<int[]> _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<int> GetOffsets(int start) {
var offsets = new List<int>();
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<int[]> {
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<T> where T : struct {
protected static Dictionary<T, int[]> _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<T> NextOp() {
_previousIP = _ip;
var op = new DecodedCameraOp<T> {
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,

View File

@ -29,6 +29,19 @@ namespace Ficedula.FF7.Battle {
}
public class BattleScene {
private static Dictionary<BattleLayout, int> _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<ushort> 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<BattleCamera> Cameras { get; } = new();

2
data/braver/exe_en.txt Normal file
View File

@ -0,0 +1,2 @@
#FF7.exe EN offsets
IntroCamera.bin 900000 2000