Load item data

Rework cacheable singletons
Very basic character combatant in sim
This commit is contained in:
ficedula 2022-11-28 14:29:21 +00:00
parent f14a63bae2
commit db9a5c25ad
15 changed files with 366 additions and 127 deletions

View File

@ -13,13 +13,43 @@ namespace Braver {
}
public abstract class BGame {
protected class LGPDataSource : DataSource {
private Ficedula.FF7.LGPFile _lgp;
public LGPDataSource(Ficedula.FF7.LGPFile lgp) {
_lgp = lgp;
}
public override IEnumerable<string> Scan() => _lgp.Filenames;
public override Stream TryOpen(string file) => _lgp.TryOpen(file);
}
protected class FileDataSource : DataSource {
private string _root;
public FileDataSource(string root) {
_root = root;
}
public override IEnumerable<string> Scan() {
//TODO subdirectories
return Directory.GetFiles(_root).Select(s => Path.GetFileName(s));
}
public override Stream TryOpen(string file) {
string fn = Path.Combine(_root, file);
if (File.Exists(fn))
return new FileStream(fn, FileMode.Open, FileAccess.Read);
return null;
}
}
public VMM Memory { get; } = new();
public SaveMap SaveMap { get; }
public SaveData SaveData { get; private set; }
public SaveData SaveData { get; protected set; }
protected Dictionary<string, List<DataSource>> _data = new Dictionary<string, List<DataSource>>(StringComparer.InvariantCultureIgnoreCase);
private Dictionary<Type, object> _singletons = new();
private Dictionary<Type, Dictionary<int, CacheItem>> _cacheItems = new();
public BGame() {
SaveMap = new SaveMap(Memory);
@ -42,6 +72,14 @@ namespace Braver {
SaveData.Loaded();
}
public T Singleton<T>() where T : Cacheable, new() {
return Singleton<T>(() => {
T t = new T();
t.Init(this);
return t;
});
}
public T Singleton<T>(Func<T> create) {
if (_singletons.TryGetValue(typeof(T), out object obj))
return (T)obj;
@ -52,17 +90,6 @@ namespace Braver {
}
}
public T CacheItem<T>(int id) where T : CacheItem, new() {
if (!_cacheItems.TryGetValue(typeof(T), out var dict))
dict = _cacheItems[typeof(T)] = new Dictionary<int, CacheItem>();
if (!dict.TryGetValue(id, out var item)) {
T t = new T();
t.Init(this, id);
item = dict[id] = t;
}
return (T)item;
}
public void NewGame() {
using (var s = Open("save", "newgame.xml"))
@ -104,22 +131,8 @@ namespace Braver {
}
public abstract class CacheItem {
public abstract void Init(BGame g, int index);
public abstract class Cacheable {
public abstract void Init(BGame g);
}
public class GameText {
private List<Ficedula.FF7.KernelText> _texts = new();
public GameText(BGame g) {
var kernel = new Ficedula.FF7.Kernel(g.Open("kernel", "kernel.bin"));
_texts.AddRange(Enumerable.Repeat<Ficedula.FF7.KernelText>(null, 9));
_texts.AddRange(
kernel.Sections
.Skip(9)
.Select(section => new Ficedula.FF7.KernelText(section))
);
}
public string Get(int section, int item) => _texts[section].Get(item);
}
}

View File

@ -60,6 +60,18 @@ namespace Braver.Battle {
public Statuses Statuses { get; set; }
}
public class CharacterActionItem {
public int ID { get; set; }
public Ability Ability { get; set; }
public string Name { get; set; }
}
public class CharacterAction {
public Ability? Ability { get; set; }
public string Name { get; set; }
public List<CharacterActionItem> SubMenu { get; set; }
}
public class CharacterCombatant : ICombatant {
private Character _char;
@ -67,23 +79,62 @@ namespace Braver.Battle {
public string Name => _char.Name;
public CharacterCombatant(Character chr) {
public List<CharacterAction> Actions { get; } = new();
public CharacterCombatant(BGame g, Character chr) {
_char = chr;
var weapon = chr.GetWeapon(g);
var armour = chr.GetArmour(g);
var accessory = chr.GetAccessory(g);
_stats = new CombatStats {
Dex = chr.Dexterity,
Lck = chr.Luck,
Level = chr.Level,
CriticalChance = 0, //TODO weapon crit%
Att = 90, //TODO Str + Weapon Attack Bonus
Def = chr.Vitality, //TODO Vit + Armour Defense Bonus
DfPC = chr.Dexterity / 4, //TODO [Dex / 4] + Armour Defense% Bonus
CriticalChance = weapon.CriticalChance,
Att = chr.Strength + (weapon?.AttackStrength ?? 0),
Def = chr.Vitality + (armour?.Defense ?? 0),
DfPC = chr.Dexterity / 4 + (armour?.DefensePercent ?? 0),
MAt = chr.Spirit,
MDf = chr.Spirit, //TODO Spr + Armour MDefense Bonus
MDPC = 0, //TODO Armour MDefense% Bonus
MDf = chr.Spirit + (armour?.MDefense ?? 0),
MDPC = armour?.MDefensePercent ?? 0,
};
//Attack action: //AtPC = 90, //TODO weapon at%,
Actions.Add(new CharacterAction {
Name = "Attack",
Ability = new Ability {
PAtPercent = (byte)weapon.HitChance,
Power = (byte)(chr.Strength + weapon.AttackStrength),
IsPhysical = true,
Elements = new HashSet<Element>(weapon.Elements.Split()),
LongRange = !weapon.TargettingFlags.HasFlag(TargettingFlags.ShortRange),
InflictStatus = weapon.Statuses,
Formula = AttackFormula.Physical, //TODO
}
});
Actions.Add(new CharacterAction {
Name = "Item",
SubMenu = g.SaveData
.Inventory
.Where(inv => inv.Kind == InventoryItemKind.Item)
.Select(inv => g.Singleton<Items>()[inv.ItemID])
.Where(item => item.Restrictions.HasFlag(EquipRestrictions.CanUseInBattle))
.Select(item => {
return new CharacterActionItem {
ID = item.ID,
Ability = new Ability {
Power = item.Power,
Elements = new HashSet<Element>(item.Elements.Split()),
StatusChance = item.StatusChance,
InflictStatus = item.StatusType == AttackStatusType.Inflict ? item.Statuses : Statuses.None,
RemoveStatus = item.StatusType == AttackStatusType.Cure ? item.Statuses : Statuses.None,
ToggleStatus = item.StatusType == AttackStatusType.Toggle ? item.Statuses : Statuses.None,
}
};
})
.ToList()
});
}
public CombatStats BaseStats => _stats;

View File

@ -1,15 +1,9 @@
using Ficedula.FF7;
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;
namespace Braver.Battle {
public class Engine {

View File

@ -43,6 +43,10 @@ namespace Braver.Battle {
});
}
public void Reset() {
_value = 0;
}
public void Tick() {
if (_value < _max) {
_value += _increment;

View File

@ -1,31 +1,92 @@
using System;
using Ficedula.FF7;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Braver {
public class Item : CacheItem {
public class KeyItem {
public string Name { get; init; }
public string Description { get; init; }
}
public string Name { get; private set; }
public string Description { get; private set; }
public class KernelCache : Cacheable {
public Kernel Kernel { get; private set; }
public override void Init(BGame g, int index) {
var text = g.Singleton<GameText>(() => new GameText(g));
Name = text.Get(19, index);
Description = text.Get(11, index);
public override void Init(BGame g) {
Kernel = new Kernel(g.Open("kernel", "kernel.bin"));
}
}
public class KeyItem : CacheItem {
public class KeyItems : Cacheable {
public string Name { get; private set; }
public string Description { get; private set; }
private List<KeyItem> _keyitems = new();
public override void Init(BGame g, int index) {
var text = g.Singleton<GameText>(() => new GameText(g));
Name = text.Get(24, index);
Description = text.Get(16, index);
public IReadOnlyList<KeyItem> Items => _keyitems.AsReadOnly();
public override void Init(BGame g) {
var kernel = g.Singleton<KernelCache>();
var names = new KernelText(kernel.Kernel.Sections[24]);
var descriptions = new KernelText(kernel.Kernel.Sections[16]);
_keyitems = Enumerable.Range(0, names.Count)
.Select(i => new KeyItem {
Name = names.Get(i),
Description = descriptions.Get(i),
})
.ToList();
}
}
public class Items : Cacheable {
private ItemCollection _items;
public Item this[int index] => _items.Items[index];
public int Count => _items.Items.Count;
public override void Init(BGame g) {
var kernel = g.Singleton<KernelCache>();
_items = new ItemCollection(kernel.Kernel);
}
}
public class Weapons : Cacheable {
private WeaponCollection _weapons;
public Weapon this[int index] => _weapons.Weapons[index];
public int Count => _weapons.Weapons.Count;
public override void Init(BGame g) {
var kernel = g.Singleton<KernelCache>();
_weapons = new WeaponCollection(kernel.Kernel);
}
}
public class Armours : Cacheable {
private ArmourCollection _armours;
public Armour this[int index] => _armours.Armour[index];
public int Count => _armours.Armour.Count;
public override void Init(BGame g) {
var kernel = g.Singleton<KernelCache>();
_armours = new ArmourCollection(kernel.Kernel);
}
}
public class Accessories : Cacheable {
private AccessoryCollection _accessories;
public Accessory this[int index] => _accessories.Accessories[index];
public int Count => _accessories.Accessories.Count;
public override void Init(BGame g) {
var kernel = g.Singleton<KernelCache>();
_accessories = new AccessoryCollection(kernel.Kernel);
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using Ficedula.FF7;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -143,6 +144,16 @@ namespace Braver {
[XmlIgnore]
public bool IsBackRow => Flags.HasFlag(CharFlags.BackRow);
public Weapon GetWeapon(BGame game) {
return EquipWeapon < 0 ? null : game.Singleton<Weapons>()[EquipWeapon];
}
public Armour GetArmour(BGame game) {
return EquipArmour < 0 ? null : game.Singleton<Armours>()[EquipArmour];
}
public Accessory GetAccessory(BGame game) {
return EquipAccessory < 0 ? null : game.Singleton<Accessories>()[EquipAccessory];
}
}
public class SaveData {

View File

@ -1,17 +1,20 @@
// Usage: BraverBattleSim [DataFolder] [EncounterNumber] [SaveDataFile]
using Braver;
using Braver.Battle;
using Ficedula.FF7.Battle;
using System;
Console.WriteLine("Braver Battle Sim");
var scene = Ficedula.FF7.Battle.SceneDecoder.Decode(File.OpenRead(Path.Combine(args[0], "battle", "scene.bin")))
var game = new SimGame(args[0]);
game.Start(args[2]);
var scene = Ficedula.FF7.Battle.SceneDecoder.Decode(game.Open("battle", "scene.bin"))
.ElementAt(int.Parse(args[1]));
var saveData = Serialisation.Deserialise<SaveData>(File.OpenRead(args[2]));
var chars = saveData
var chars = game.SaveData
.Party
.Select(c => new CharacterCombatant(c));
.Select(c => new CharacterCombatant(game, c));
var enemies = scene
.Enemies
@ -25,7 +28,31 @@ while (true) {
var ready = engine.Combatants.Where(c => c.TTimer.IsFull);
if (ready.Any()) {
Console.WriteLine($"At {engine.GTimer.Ticks} gticks, {string.Join(", ", ready.Select(c => c.Name))} ready to act");
foreach(var enemy in ready.OfType<EnemyCombatant>()) {
foreach(var chr in ready.OfType<CharacterCombatant>()) {
Console.WriteLine(chr.Name);
var ability = MenuChoose(chr);
Console.WriteLine("Targets:");
Console.WriteLine(string.Join(" ", engine.Combatants.Select((comb, index) => $"{(char)('A' + index)}:{comb.Name}")));
var targets = Console.ReadLine()
.Trim()
.ToUpper()
.Split(' ')
.Select(s => s[0])
.Select(c => engine.Combatants.ElementAt(c - 'A'));
var results = engine.ApplyAbility(
chr,
ability,
targets
);
foreach (var result in results) {
Console.WriteLine($"--Target {result.Target}, hit {result.Hit}, inflict {result.InflictStatus} remove {result.RemoveStatus} recovery {result.Recovery}, damage HP {result.HPDamage} MP {result.MPDamage}");
}
chr.TTimer.Reset();
Console.ReadLine();
}
foreach (var enemy in ready.OfType<EnemyCombatant>()) {
//TODO AI
var action = enemy.Enemy.Enemy.Actions[0];
var target = engine.Combatants
@ -42,7 +69,44 @@ while (true) {
foreach(var result in results) {
Console.WriteLine($"--Target {result.Target}, hit {result.Hit}, inflict {result.InflictStatus} remove {result.RemoveStatus} recovery {result.Recovery}, damage HP {result.HPDamage} MP {result.MPDamage}");
}
enemy.TTimer.Reset();
Console.ReadLine();
}
}
}
}
Ability MenuChoose(CharacterCombatant chr) {
char c = 'A';
foreach (var action in chr.Actions) {
Console.WriteLine($" {c++}: {action.Name}");
}
char choice = Console.ReadLine().Trim().ToUpper().First();
var chosen = chr.Actions[choice - 'A'];
if (chosen.Ability != null)
return chosen.Ability.Value;
c = 'A';
foreach(var sub in chosen.SubMenu) {
Console.WriteLine($" {c++}: {sub.Name}");
}
choice = Console.ReadLine().Trim().ToUpper().First();
var subchosen = chosen.SubMenu[choice - 'A'];
return subchosen.Ability;
}
public class SimGame : BGame {
public SimGame(string data) {
_data["battle"] = new List<DataSource> {
new LGPDataSource(new Ficedula.FF7.LGPFile(Path.Combine(data, "battle", "battle.lgp"))),
new FileDataSource(Path.Combine(data, "battle"))
};
_data["kernel"] = new List<DataSource> {
new FileDataSource(Path.Combine(data, "kernel"))
};
}
public void Start(string savegame) {
SaveData = Serialisation.Deserialise<SaveData>(File.OpenRead(savegame));
}
}

View File

@ -23,37 +23,6 @@ namespace Braver {
public class FGame : BGame {
private class LGPDataSource : DataSource {
private Ficedula.FF7.LGPFile _lgp;
public LGPDataSource(Ficedula.FF7.LGPFile lgp) {
_lgp = lgp;
}
public override IEnumerable<string> Scan() => _lgp.Filenames;
public override Stream TryOpen(string file) => _lgp.TryOpen(file);
}
private class FileDataSource : DataSource {
private string _root;
public FileDataSource(string root) {
_root = root;
}
public override IEnumerable<string> Scan() {
//TODO subdirectories
return Directory.GetFiles(_root).Select(s => Path.GetFileName(s));
}
public override Stream TryOpen(string file) {
string fn = Path.Combine(_root, file);
if (File.Exists(fn))
return new FileStream(fn, FileMode.Open, FileAccess.Read);
return null;
}
}
private Stack<Screen> _screens = new();
private Microsoft.Xna.Framework.Graphics.GraphicsDevice _graphics;

View File

@ -20,14 +20,10 @@ namespace Braver.UI.Layout {
public override bool IsRazorModel => true;
private WeaponCollection _weapons;
private ArmourCollection _armours;
private AccessoryCollection _accessories;
public Character Character => _game.SaveData.Party[(int)_screen.Param];
public Weapon Weapon => _weapons.Weapons.ElementAtOrDefault(Character.EquipWeapon);
public Armour Armour => _armours.Armour.ElementAtOrDefault(Character.EquipArmour);
public Accessory Accessory => _accessories.Accessories.ElementAtOrDefault(Character.EquipAccessory);
public Weapon Weapon => Character.GetWeapon(_game);
public Armour Armour => Character.GetArmour(_game);
public Accessory Accessory => Character.GetAccessory(_game);
public List<Weapon> AvailableWeapons { get; } = new();
public List<Armour> AvailableArmour { get; } = new();
@ -35,10 +31,11 @@ namespace Braver.UI.Layout {
public override void Created(FGame g, LayoutScreen screen) {
base.Created(g, screen);
var kernel = _game.Singleton(() => new Kernel(_game.Open("kernel", "kernel.bin")));
_weapons = _game.Singleton(() => new WeaponCollection(kernel));
_armours = _game.Singleton(() => new ArmourCollection(kernel));
_accessories = _game.Singleton(() => new AccessoryCollection(kernel));
var weapons = _game.Singleton(() => new WeaponCollection(kernel));
var armours = _game.Singleton(() => new ArmourCollection(kernel));
var accessories = _game.Singleton(() => new AccessoryCollection(kernel));
AvailableWeapons.Clear();
AvailableWeapons.AddRange(
@ -49,7 +46,7 @@ namespace Braver.UI.Layout {
.Where(id => id >= 0)
.Distinct()
.OrderBy(i => i)
.Select(i => _weapons.Weapons[i])
.Select(i => weapons.Weapons[i])
);
AvailableArmour.Clear();
@ -61,7 +58,7 @@ namespace Braver.UI.Layout {
.Where(id => id >= 0)
.Distinct()
.OrderBy(i => i)
.Select(i => _armours.Armour[i])
.Select(i => armours.Armour[i])
);
AvailableAccessories.Clear();
@ -73,7 +70,7 @@ namespace Braver.UI.Layout {
.Where(id => id >= 0)
.Distinct()
.OrderBy(i => i)
.Select(i => _accessories.Accessories[i])
.Select(i => accessories.Accessories[i])
);
}

View File

@ -64,24 +64,18 @@ namespace Braver.UI.Layout {
public static (string Item, string Description) GetInventory(FGame game, int index) {
var inv = game.SaveData.Inventory[index];
switch (inv.Kind) {
switch (inv.Kind) {
case InventoryItemKind.Item:
var item = game.CacheItem<Item>(inv.ItemID);
var item = game.Singleton<Items>()[inv.ItemID];
return (item.Name, item.Description);
case InventoryItemKind.Weapon:
var kernel = game.Singleton(() => new Kernel(game.Open("kernel", "kernel.bin")));
var weapons = game.Singleton(() => new WeaponCollection(kernel));
var weapon = weapons.Weapons[index];
var weapon = game.Singleton<Weapons>()[inv.ItemID];
return (weapon.Name, weapon.Description);
case InventoryItemKind.Armour:
kernel = game.Singleton(() => new Kernel(game.Open("kernel", "kernel.bin")));
var armours = game.Singleton(() => new ArmourCollection(kernel));
var armour = armours.Armour[index];
return (armour.Name, armour.Description);
case InventoryItemKind.Accessory:
kernel = game.Singleton(() => new Kernel(game.Open("kernel", "kernel.bin")));
var accessories = game.Singleton(() => new AccessoryCollection(kernel));
var accessory = accessories.Accessories[index];
var armour = game.Singleton<Armours>()[inv.ItemID];
return (armour.Name, armour.Description);
case InventoryItemKind.Accessory:
var accessory = game.Singleton<Accessories>()[inv.ItemID];
return (accessory.Name, accessory.Description);
default:
throw new NotImplementedException();
@ -94,7 +88,7 @@ namespace Braver.UI.Layout {
}
public void KeyItemFocussed() {
var keyItem = _game.CacheItem<KeyItem>(_game.SaveData.KeyItems[lbKeyItems.GetSelectedIndex(this)]);
var keyItem = _game.Singleton<KeyItems>().Items[_game.SaveData.KeyItems[lbKeyItems.GetSelectedIndex(this)]];
lDescription.Text = keyItem.Description;
}

View File

@ -53,6 +53,8 @@ if (args[0].Equals("Kernel", StringComparison.OrdinalIgnoreCase)) {
var accessories = new Ficedula.FF7.AccessoryCollection(kernel);
var items = new Ficedula.FF7.ItemCollection(kernel);
File.WriteAllBytes(@"C:\temp\s9.bin", kernel.Sections.ElementAt(9));
File.WriteAllBytes(@"C:\temp\s16.bin", kernel.Sections.ElementAt(16));

View File

@ -78,6 +78,7 @@ namespace Ficedula.FF7.Battle {
public short ActionID { get; set; }
public string Name { get; set; }
public Attack() { }
public Attack(Stream s) {
AttackPC = (byte)s.ReadByte();
ImpactEffect = (byte)s.ReadByte();

78
Ficedula.FF7/Item.cs Normal file
View File

@ -0,0 +1,78 @@
using Ficedula.FF7.Battle;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ficedula.FF7 {
public class Item {
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ushort CameraMovementID { get; set; }
public EquipRestrictions Restrictions { get; set; }
public TargettingFlags TargettingFlags { get; set; }
public byte AttackEffectID { get; set; }
public byte DamageFormula { get; set; }
public byte Power { get; set; }
public AttackCondition AttackCondition { get; set; }
public AttackStatusType StatusType { get; set; }
public byte StatusChance { get; set; }
public Statuses Statuses { get; set; }
public byte AdditionalEffects { get; set; }
public byte AdditionalEffectsModifier { get; set; }
public Elements Elements { get; set; }
public ushort AttackFlags { get; set; }
}
public class ItemCollection {
private List<Item> _items = new();
public IReadOnlyList<Item> Items => _items.AsReadOnly();
public ItemCollection(Kernel kernel) {
var descriptions = new KernelText(kernel.Sections.ElementAt(11));
var names = new KernelText(kernel.Sections.ElementAt(19));
var data = new MemoryStream(kernel.Sections.ElementAt(4));
int index = 0;
while (data.Position < data.Length) {
Item item = new Item {
Name = names.Get(index),
Description = descriptions.Get(index),
ID = index,
};
index++;
data.ReadI32();
data.ReadI32();
item.CameraMovementID = data.ReadU16();
item.Restrictions = (EquipRestrictions)(~data.ReadU16() & 0x7);
item.TargettingFlags = (TargettingFlags)data.ReadU8();
item.AttackEffectID = data.ReadU8();
item.DamageFormula = data.ReadU8();
item.Power = data.ReadU8();
item.AttackCondition = (AttackCondition)data.ReadU8();
byte chance = data.ReadU8();
item.StatusChance = (byte)(chance & 0x3f);
if ((chance & 0x80) != 0)
item.StatusType = AttackStatusType.Toggle;
else if ((chance & 0x40) != 0)
item.StatusType = AttackStatusType.Cure;
else
item.StatusType = AttackStatusType.Inflict;
item.AdditionalEffects = data.ReadU8();
item.AdditionalEffectsModifier = data.ReadU8();
item.Statuses = (Statuses)data.ReadI32();
if (chance == 0xff)
item.Statuses = Statuses.None;
item.Elements = (Elements)data.ReadU16();
item.AttackFlags = data.ReadU16();
_items.Add(item);
}
}
}
}

View File

@ -11,7 +11,7 @@ namespace Ficedula.FF7 {
private List<byte[]> _sections = new();
public IEnumerable<byte[]> Sections => _sections.AsReadOnly();
public IReadOnlyList<byte[]> Sections => _sections.AsReadOnly();
public Kernel(Stream source) {
while (source.Position < source.Length) {

View File

@ -32,7 +32,7 @@
<Component xsi:type="List" X="0" Y="20" W="730" H="560" ID="lbKeyItems" Visible="false">
@foreach(int i in Enumerable.Range(0, @Model.SaveData.KeyItems.Count)) {
<Component xsi:type="Label" Y="0" X="20" ID='@("KeyItem" + i)' Focussed="KeyItemFocussed">@(Model.CacheItem<Braver.KeyItem>(Model.SaveData.KeyItems[i]).Name)</Component>
<Component xsi:type="Label" Y="0" X="20" ID='@("KeyItem" + i)' Focussed="KeyItemFocussed">@(Model.Singleton<Braver.KeyItems>().Items[Model.SaveData.KeyItems[i]].Name)</Component>
}
</Component>
</Component>