More improvement to materia menu (mostly working)

Continue working on battle UI
This commit is contained in:
ficedula 2023-01-09 19:55:06 +00:00
parent db1b18d70c
commit a18df93d26
12 changed files with 257 additions and 39 deletions

View File

@ -69,7 +69,7 @@ namespace Braver {
Memory.Load(fs);
using (var fs = File.OpenRead(path + ".sav"))
SaveData = Serialisation.Deserialise<SaveData>(fs);
SaveData.Loaded();
SaveData.CleanUp();
}
public T Singleton<T>() where T : Cacheable, new() {
@ -96,7 +96,7 @@ namespace Braver {
SaveData = Serialisation.Deserialise<SaveData>(s);
Memory.ResetAll();
Braver.NewGame.Init(this);
SaveData.Loaded();
SaveData.CleanUp();
}
public IEnumerable<string> ScanData(string category) {

View File

@ -88,6 +88,7 @@ namespace Braver.Battle {
private CombatStats _stats;
public string Name => _char.Name;
public Character Character => _char;
public List<CharacterAction> Actions { get; } = new();

View File

@ -18,6 +18,7 @@ namespace Braver.Battle {
private List<Event> _events = new();
public bool IsFull => _value >= _max;
public float Fill => 1f * _value / _max;
public int Ticks => _ticks;
public Timer(int increment, int max, int value, bool autoReset = true) {

View File

@ -145,9 +145,21 @@ namespace Braver {
[XmlIgnore]
public bool IsBackRow => Flags.HasFlag(CharFlags.BackRow);
[XmlIgnore]
public int PartyIndex {
get {
switch(Flags & CharFlags.ANY_PARTY_SLOT) {
case CharFlags.Party1: return 0;
case CharFlags.Party2: return 1;
case CharFlags.Party3: return 2;
default: return -1;
}
}
}
public IEnumerable<(Materia Materia, int AP, int Level)> EquippedMateria(BGame game) {
var materias = game.Singleton<Materias>();
foreach(var mat in WeaponMateria.Concat(ArmourMateria)) {
foreach(var mat in WeaponMateria.Concat(ArmourMateria).Where(m => m != null)) {
var materia = materias[mat.MateriaID];
int level = Enumerable.Range(0, materia.APLevels.Count)
.Where(lvl => materia.APLevels[lvl] <= mat.AP)
@ -211,16 +223,20 @@ namespace Braver {
public int GameTimeSeconds { get; set; }
[XmlIgnore]
public Character[] Party => CharactersInParty().ToArray(); //Array so we can interop with Lua
public Character[] Party => CharactersInParty().ToArray(); //Array so we can interop
public void Loaded() {
Characters.Sort((c1, c2) => c1.CharIndex.CompareTo(c2.CharIndex));
public void CleanUp() {
Characters.Sort((c1, c2) => (c1?.CharIndex ?? 0).CompareTo(c2?.CharIndex ?? 0));
foreach (int i in Enumerable.Range(0, 8)) {
while (Characters.Count <= i)
Characters.Add(null);
while ((Characters[i] != null) && (Characters[i].CharIndex > i))
Characters.Insert(i, null);
}
int lastValid = MateriaStock.FindLastIndex(m => m != null);
if (lastValid < (MateriaStock.Count - 1))
MateriaStock.RemoveRange(lastValid + 1, MateriaStock.Count - lastValid - 1);
}
public bool GiveInventoryItem(InventoryItemKind kind, int id, int quantity = 1) {

View File

@ -100,5 +100,6 @@ namespace Braver {
SaveReady = 1,
Invalid = 2,
Cancel = 3,
DeEquip = 446,
}
}

View File

@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using Ficedula.FF7.Battle;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
@ -9,10 +10,10 @@ using System.Threading.Tasks;
namespace Braver.Battle {
public class BattleSceneCache {
public Dictionary<int, Ficedula.FF7.Battle.BattleScene> Scenes { get; }
public Dictionary<int, BattleScene> Scenes { get; }
public BattleSceneCache(FGame g) {
Scenes = Ficedula.FF7.Battle.SceneDecoder.Decode(g.Open("battle", "scene.bin"))
Scenes = SceneDecoder.Decode(g.Open("battle", "scene.bin"))
.Where(s => s.Enemies.Any())
.ToDictionary(s => s.FormationID, s => s);
}
@ -29,8 +30,62 @@ namespace Braver.Battle {
public class BattleScreen : Screen {
private class UIHandler : UI.Layout.LayoutModel {
private class Callbacks : AICallbacks {
public Callbacks(VMM vmm) {
_vmm = vmm;
}
public override void DisplayText(byte[] text) {
//TODO encoding?!?!
Console.WriteLine(Encoding.ASCII.GetString(text));
}
}
private class UIHandler : UI.Layout.LayoutModel {
public UI.Layout.Label
lHP0, lHP1, lHP2,
lMaxHP0, lMaxHP1, lMaxHP2,
lMP0, lMP1, lMP2,
lMaxMP0, lMaxMP1, lMaxMP2;
public UI.Layout.Gauge
gHP0, gHP1, gHP2,
gMP0, gMP1, gMP2,
gLimit0, gLimit1, gLimit2,
gTime0, gTime1, gTime2;
public UI.Layout.Box
bMenu0, bMenu1, bMenu2;
private List<CharacterCombatant> _combatants;
public IReadOnlyList<CharacterCombatant> Combatants => _combatants.AsReadOnly();
public override bool IsRazorModel => true;
public UIHandler(IEnumerable<CharacterCombatant> combatants) {
_combatants = combatants.ToList();
}
private void DoUpdate(CharacterCombatant combatant,
UI.Layout.Label lHP, UI.Layout.Label lMaxHP, UI.Layout.Label lMP, UI.Layout.Label lMaxMP,
UI.Layout.Gauge gHP, UI.Layout.Gauge gMP, UI.Layout.Gauge gLimit, UI.Layout.Gauge gTime,
UI.Layout.Box bMenu) {
if (combatant == null) return;
lHP.Text = combatant.Character.CurrentHP.ToString();
lMP.Text = combatant.Character.CurrentMP.ToString();
gHP.Current = combatant.Character.CurrentHP;
gMP.Current = combatant.Character.CurrentMP;
gLimit.Current = combatant.Character.LimitBar;
gTime.Current = 255 * combatant.TTimer.Fill;
}
public void Update() {
DoUpdate(_combatants.ElementAtOrDefault(0), lHP0, lMaxHP0, lMP0, lMaxMP0, gHP0, gMP0, gLimit0, gTime0, bMenu0);
DoUpdate(_combatants.ElementAtOrDefault(1), lHP1, lMaxHP1, lMP1, lMaxMP1, gHP1, gMP1, gLimit1, gTime1, bMenu1);
DoUpdate(_combatants.ElementAtOrDefault(2), lHP2, lMaxHP2, lMP2, lMaxMP2, gHP2, gMP2, gLimit2, gTime2, bMenu2);
}
}
@ -53,6 +108,10 @@ namespace Braver.Battle {
private PerspView3D _view;
private UI.Layout.LayoutScreen _ui;
private UIHandler _uiHandler;
private Engine _engine;
private bool _debugCamera = false;
@ -170,19 +229,47 @@ namespace Braver.Battle {
AddModel(player.First.BattleModel, player.Second);
}
_ui = new UI.Layout.LayoutScreen("battle", new UIHandler());
InitEngine();
_uiHandler = new UIHandler(_engine.Combatants.OfType<CharacterCombatant>());
_ui = new UI.Layout.LayoutScreen("battle", _uiHandler);
_ui.Init(Game, Graphics);
g.Audio.PlayMusic("bat"); //TODO!
}
private void InitEngine() {
ICombatant[] combatants = new ICombatant[16];
int index = 0;
foreach (var chr in Game.SaveData.Party)
combatants[index++] = new CharacterCombatant(Game, chr);
index = 4;
foreach (var group in _scene.Enemies.GroupBy(ei => ei.Enemy.ID)) {
int c = 0;
foreach (var enemy in group) {
combatants[index++] = new EnemyCombatant(enemy, group.Count() == 1 ? null : c++);
}
}
var callbacks = new Callbacks(Game.Memory);
_engine = new Engine(128, combatants, Game, callbacks);
_engine.ReadyForAction += c => { };
_engine.ActionQueued += a => { };
}
public override Color ClearColor => Color.Black;
protected override void DoStep(GameTime elapsed) {
foreach (var model in _models) {
model.FrameStep();
}
_engine.Tick();
_uiHandler.Update();
_ui.Step(elapsed);
}

View File

@ -448,6 +448,7 @@ namespace Braver.UI.Layout {
builder.AddAssemblyReference(typeof(RazorLayoutCache));
builder.AddAssemblyReference(typeof(SaveData));
builder.AddAssemblyReference(typeof(Ficedula.FF7.Item));
builder.AddAssemblyReference(typeof(Enumerable));
});
}
return razor;

View File

@ -1,4 +1,5 @@
using Ficedula.FF7;
using RazorEngineCore;
using System;
using System.Collections.Generic;
using System.Linq;
@ -11,7 +12,7 @@ namespace Braver.UI.Layout {
public int AP { get; set; }
public Materia Materia { get; set; }
public int Level => Materia.APLevels.TakeWhile(ap => ap <= AP).Count();
public int Level => 1 + Materia.APLevels.TakeWhile(ap => ap < AP).Count();
public int? ToNextLevel {
get {
var next = Materia.APLevels.FirstOrDefault(ap => ap > AP);
@ -83,6 +84,7 @@ namespace Braver.UI.Layout {
if (FocusGroup == gMain) {
_game.Audio.PlaySfx(Sfx.Cancel, 1f, 0f);
InputEnabled = false;
_game.SaveData.CleanUp();
_screen.FadeOut(() => _game.PopScreen(_screen));
} else
base.CancelPressed();
@ -99,6 +101,53 @@ namespace Braver.UI.Layout {
}
}
private void ReturnMateria(OwnedMateria m) {
_game.Audio.PlaySfx(Sfx.DeEquip, 1f, 0f);
foreach (int i in Enumerable.Range(0, _game.SaveData.MateriaStock.Count)) {
if (_game.SaveData.MateriaStock[i] == null) {
_game.SaveData.MateriaStock[i] = m;
return;
}
}
_game.SaveData.MateriaStock.Add(m);
}
public override bool ProcessInput(InputState input) {
if (FocusGroup == gMain) {
if (input.IsJustDown(InputKey.Menu)) {
if (Focus.ID.StartsWith("W")) {
int slot = int.Parse(Focus.ID.Substring(1));
ReturnMateria(Character.WeaponMateria[slot]);
Character.WeaponMateria[slot] = null;
_screen.Reload();
return true;
} else if (Focus.ID.StartsWith("A")) {
int slot = int.Parse(Focus.ID.Substring(1));
ReturnMateria(Character.ArmourMateria[slot]);
Character.ArmourMateria[slot] = null;
_screen.Reload();
return true;
}
}
if (input.IsJustDown(InputKey.PanRight)) {
int c = (int)_screen.Param;
c = (c + _game.SaveData.Party.Length - 1) % _game.SaveData.Party.Length;
_game.PopScreen(_screen);
_game.PushScreen(new LayoutScreen("MateriaMenu", parm: c));
return true;
} else if (input.IsJustDown(InputKey.PanLeft)) {
int c = (int)_screen.Param;
c = (c + 1) % _game.SaveData.Party.Length;
_game.PopScreen(_screen);
_game.PushScreen(new LayoutScreen("MateriaMenu", parm: c));
return true;
}
}
return base.ProcessInput(input);
}
public void Arrange_Click(Label L) {
}
@ -106,27 +155,46 @@ namespace Braver.UI.Layout {
}
public void EClick(Image i) {
public void MateriaClick(Group g) {
int index = int.Parse(g.ID.Substring(1));
OwnedMateria removing;
if (_focusIsWeapon) {
removing = Character.WeaponMateria[_focusSlot];
Character.WeaponMateria[_focusSlot] = _game.SaveData.MateriaStock[index];
} else {
throw new NotImplementedException();
}
_game.SaveData.MateriaStock[index] = removing;
PopFocus();
_screen.Reload();
}
public void EFocussed(Image i) {
bool isWeapon = i.ID.StartsWith("W");
int slot = int.Parse(i.ID.Substring(1));
if (isWeapon)
CurrentMateria = WeaponMateria[slot];
else
CurrentMateria = ArmourMateria[slot];
public void MateriaFocussed(Group g) {
int index = int.Parse(g.ID.Substring(1));
CurrentMateria = AvailableMateria.ElementAtOrDefault(index);
_screen.Reload();
}
public void MateriaSelected(Label L) {
private bool _focusIsWeapon;
private int _focusSlot;
public void EClick(Image i) {
if (AvailableMateria.Any()) {
PushFocus(lbMateria, lbMateria.Children[0]);
} else {
_game.Audio.PlaySfx(Sfx.Invalid, 1f, 0f);
}
}
public void MateriaFocussed() {
public void EFocussed(Image i) {
_focusIsWeapon = i.ID.StartsWith("W");
_focusSlot = int.Parse(i.ID.Substring(1));
if (_focusIsWeapon)
CurrentMateria = WeaponMateria[_focusSlot];
else
CurrentMateria = ArmourMateria[_focusSlot];
_screen.Reload();
}
public IEnumerable<string> MateriaAbilities(Materia m) {
switch(m) {
case MagicMateria magic:

View File

@ -4,10 +4,18 @@
<Component xsi:type="Box" X="0" Y="0" W="630" H="150"></Component>
<Component xsi:type="Box" X="630" Y="0" W="650" H="150"></Component>
@foreach(int i in Enumerable.Range(0, @Model.SaveData.Party.Length)) {
<Component xsi:type="Group" X="0" Y="@(10 + 40 * i)" W="1280" H="40" ID='@("Char" + i)'>
@Include("battlechar", Model.SaveData.Party[i])
</Component>
<Component xsi:type="Image" X="30" Y="15">bm_name</Component>
<Component xsi:type="Image" X="400" Y="15">bm_barrier</Component>
<Component xsi:type="Image" X="650" Y="15">bm_hp</Component>
<Component xsi:type="Image" X="875" Y="15">bm_mp</Component>
<Component xsi:type="Image" X="1050" Y="15">bm_limit</Component>
<Component xsi:type="Image" X="1150" Y="15">bm_time</Component>
@foreach(int i in Enumerable.Range(0, @Model.Combatants.Count)) {
<Component xsi:type="Group" X="0" Y="@(30 + 40 * i)" W="1280" H="40" ID='@("Char" + i)'>
@Include("battlechar", Model.Combatants[i])
</Component>
}
</Root>
</Layout>

View File

@ -1,11 +1,23 @@
<Component xsi:type="Label" Y="0" X="10">@Model.Name</Component>
<Component xsi:type="Label" Y="5" X="20">@Model.Character.Name</Component>
<Component xsi:type="Gauge" X="650" Y="35" W="250" H="4" Style="HP" Current="@Model.CurrentHP" Max="@Model.MaxHP" />
<Component xsi:type="Label" X="720" Y="5" Font="bats" Alignment="Right" ID='@("lHP" + Model.Character.PartyIndex)'>@Model.Character.CurrentHP</Component>
<Component xsi:type="Label" X="750" Y="5" Font="bats" Alignment="Right" Color="Gray">/</Component>
<Component xsi:type="Label" X="830" Y="5" Font="bats" Alignment="Right" ID='@("lMaxHP" + Model.Character.PartyIndex)'>@Model.Character.MaxHP</Component>
<Component xsi:type="Gauge" X="925" Y="35" W="100" H="4" Style="MP" Current="@Model.CurrentMP" Max="@Model.MaxMP" />
<Component xsi:type="Label" X="935" Y="5" Font="bats" Alignment="Right" ID='@("lMP" + Model.Character.PartyIndex)'>@Model.Character.CurrentMP</Component>
<Component xsi:type="Label" X="965" Y="5" Font="bats" Alignment="Right" Color="Gray">/</Component>
<Component xsi:type="Label" X="1015" Y="5" Font="bats" Alignment="Right" ID='@("lMaxMP" + Model.Character.PartyIndex)'>@Model.Character.MaxMP</Component>
<Component xsi:type="Box" X="200" Y="-200" H="250" W="100" ID='@("Menu" + Model.ID)' Visible="false">
<Component xsi:type="Gauge" X="650" Y="25" W="200" H="4" ID='@("gHP" + Model.Character.PartyIndex)' Style="HP" Current="@Model.Character.CurrentHP" Max="@Model.Character.MaxHP" />
<Component xsi:type="Gauge" X="875" Y="25" W="150" H="4" ID='@("gMP" + Model.Character.PartyIndex)' Style="MP" Current="@Model.Character.CurrentMP" Max="@Model.Character.MaxMP" />
<Component xsi:type="Gauge" X="1050" Y="5" W="100" H="24" ID='@("gLimit" + Model.Character.PartyIndex)' Style="Limit" Current="@Model.Character.LimitBar" Max="255" />
<Component xsi:type="Gauge" X="1150" Y="5" W="100" H="24" ID='@("gTime" + Model.Character.PartyIndex)' Style="Limit" Current="0" Max="255" />
<Component xsi:type="Box" X="200" Y="-200" H="250" W="100" ID='@("bMenu" + Model.Character.PartyIndex)' Visible="false">
<Component xsi:type="Label" X="10" Y="10">Attack</Component>
<Component xsi:type="Label" X="10" Y="30">Magic</Component>

View File

@ -31,7 +31,16 @@
<Component xsi:type="Label" Y="130" X="620" ID="lArrange" Click="Arrange_Click">Arrange</Component>
<Component xsi:type="Group" BackgroundAlpha="0.2" X="700" Y="130" W="300" H="30" InputPassthrough="true">
@foreach(int slot in Enumerable.Range(0, Model.Armour?.MateriaSlots?.Count ?? 0)) {
<Component xsi:type="Image" X="@(5 + 30 * slot)" Y="3" ID="A@(slot)" Click="EClick" Focussed="EFocussed">materia_slot</Component>
@if (((slot % 2) == 0) && (Model.Weapon.MateriaSlots[slot] == MateriaSlotKind.Linked)) {
<Component xsi:type="Image" X="@(25 + 30 * slot)" Y="5">materia_slot_link</Component>
}
@if (Model.ArmourMateria[slot] != null) {
<Component xsi:type="Image" X="@(8 + 30 * slot)" Y="5">materia_@Model.MateriaColor(Model.ArmourMateria[slot].Materia)</Component>
}
}
</Component>
</Component>
</Component>
@ -59,8 +68,11 @@
}
<Component xsi:type="List" X="50" Y="260" W="240" H="240">
@foreach(var ability in Model.MateriaAbilities(Model.CurrentMateria.Materia)) {
<Component xsi:type="Label">@ability</Component>
@{
int abilityCount = 0;
}
@foreach(string ability in Model.MateriaAbilities(Model.CurrentMateria.Materia)) {
<Component xsi:type="Label" Color='@(abilityCount++ >= Model.CurrentMateria.Level ? "Gray" : "White")'>@ability</Component>
}
</Component>
@ -86,11 +98,14 @@
<Component xsi:type="Box" X="800" Y="240" W="480" H="480">
<Component xsi:type="List" X="0" Y="20" W="730" H="450" ID="lbMateria">
@{
int mcount = 0;
}
@foreach(var m in @Model.AvailableMateria) {
<Component xsi:type="Group">
<Component xsi:type="Group" ID='@("M" + mcount++)' Click="MateriaClick" Focussed="MateriaFocussed">
@if (m != null) {
<Component xsi:type="Image" X="20" Y="0">materia_@Model.MateriaColor(m.Materia)</Component>
<Component xsi:type="Label" X="50" Y="0" ID='@("Materia" + m.Materia.ID)' Click="MateriaSelected" Focussed="MateriaFocussed">@m.Materia.Name</Component>
<Component xsi:type="Label" X="50" Y="0" ID='@("Materia" + m.Materia.ID)'>@m.Materia.Name</Component>
}
</Component>
}

View File

@ -14,6 +14,12 @@
<Y>256</Y>
<Chunk X="224" Y="64" W="24" H="24">materia_slot_nogrowth</Chunk>
<Chunk X="227" Y="98" W="14" H="18">materia_slot_link</Chunk>
<Chunk X="112" Y="0" W="48" H="16">bm_limit</Chunk>
<Chunk X="160" Y="0" W="32" H="16">bm_hp</Chunk>
<Chunk X="192" Y="0" W="32" H="16">bm_mp</Chunk>
<Chunk X="224" Y="0" W="48" H="16">bm_time</Chunk>
<Chunk X="112" Y="16" W="80" H="16">bm_barrier</Chunk>
<Chunk X="192" Y="16" W="48" H="16">bm_wait</Chunk>
</SourceImage>
<SourceImage>
<Texture>menu\btl_win_b_h.tex,0</Texture>
@ -30,6 +36,8 @@
<Chunk X="3" Y="67" W="18" H="18">materia</Chunk>
<Chunk X="32" Y="64" W="26" H="26">materia_star</Chunk>
<Chunk X="32" Y="96" W="26" H="26">materia_star_empty</Chunk>
<Chunk X="0" Y="32" W="64" H="16">bm_name</Chunk>
<Chunk X="0" Y="48" W="64" H="16">bm_status</Chunk>
</SourceImage>
<SourceImage>
<Texture>menu\btl_win_c_h.tex,0</Texture>