mirror of
https://github.com/ficed/Braver.git
synced 2024-11-26 22:50:38 +00:00
Implement exe data source, exe embedded camera data
Camera interpreter for battle screen implementing most common opcodes working
This commit is contained in:
parent
b5a2055da9
commit
11f70afe4c
@ -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();
|
||||
|
@ -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
47
Braver.Core/ExeData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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" });
|
||||
|
@ -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>...
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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) {
|
||||
|
259
Braver/Battle/CameraController.cs
Normal file
259
Braver/Battle/CameraController.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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>(
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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 {
|
||||
|
@ -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%
|
||||
|
@ -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,
|
||||
|
@ -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
2
data/braver/exe_en.txt
Normal file
@ -0,0 +1,2 @@
|
||||
#FF7.exe EN offsets
|
||||
IntroCamera.bin 900000 2000
|
Loading…
Reference in New Issue
Block a user