Braver/Ficedula.FF7/Materia.cs
ficedula bcc8ec53fb Remove inventory item separation - run off single ID as per FF7 data
Implement independent and support materia
Remove Braver newgame.xml, read new game data from FF7 kernel
Allow not setting player destination on field load, use when starting a new game
Init MenuLocked properly - apparently the game script doesn't?
Upgrade Monogame, NVorbis; correct usings for new Monogame
2023-03-07 20:49:10 +00:00

404 lines
14 KiB
C#

// 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.Diagnostics;
using System.Linq;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Ficedula.FF7 {
public class MateriaEquipEffect {
public int Strength { get; set; }
public int Vitality { get; set; }
public int Magic { get; set; }
public int Spirit { get; set; }
public int Dexterity { get; set; }
public int Luck { get; set; }
public int MaxHP { get; set; }
public int MaxMP { get; set; }
private MateriaEquipEffect() { }
public static MateriaEquipEffect ByIndex(int index) => _byIndex[index];
private static MateriaEquipEffect[] _byIndex = new[] {
new MateriaEquipEffect { },
new MateriaEquipEffect { Strength = -2, Vitality = -1, Magic = +2, Spirit = +1, MaxHP = -5, MaxMP = +5 },
new MateriaEquipEffect { Strength = -4, Vitality = -2, Magic = +4, Spirit = +2, MaxHP = -10, MaxMP = +10 },
new MateriaEquipEffect { Dexterity = +2, Luck = -2 },
new MateriaEquipEffect { Strength = -1, Vitality = -1, Magic = +1, Spirit = +1 },
new MateriaEquipEffect { Strength = +1, Vitality = +1, Magic = -1, Spirit = -1 },
new MateriaEquipEffect { Vitality = +1 },
new MateriaEquipEffect { Luck = +1 },
new MateriaEquipEffect { Luck = -1 },
new MateriaEquipEffect { Dexterity = -2 },
new MateriaEquipEffect { Dexterity = +2 },
new MateriaEquipEffect { Strength = -1, Magic = +1, MaxHP = -2, MaxMP = +2 },
new MateriaEquipEffect { Magic = +1, MaxHP = -2, MaxMP = +2 },
new MateriaEquipEffect { Magic = +1, Spirit = +1, MaxHP = -5, MaxMP = +5 },
new MateriaEquipEffect { Magic = +2, Spirit = +2, MaxHP = -10, MaxMP = +10 },
new MateriaEquipEffect { Magic = +4, Spirit = +4, MaxHP = -10, MaxMP = +15 },
new MateriaEquipEffect { Magic = +8, Spirit = +8, MaxHP = -10, MaxMP = +20 },
new MateriaEquipEffect { },
new MateriaEquipEffect { },
new MateriaEquipEffect { },
new MateriaEquipEffect { },
new MateriaEquipEffect { Strength = +1, Vitality = +2, Magic = +4, Spirit = +8, Dexterity = +16, Luck = +32, MaxHP = +64, MaxMP = +128 },
};
}
public abstract class Materia {
private List<int> _apLevels;
public IReadOnlyList<int> APLevels => _apLevels.AsReadOnly();
public string Name { get; private set; }
public int ID { get; private set; }
public string Description { get; private set; }
public MateriaEquipEffect EquipEffect { get; private set; }
protected abstract void DoInit(byte subType, IEnumerable<byte> attrs);
public void Init(string name, string description, int id, MateriaEquipEffect equipEffect,
IEnumerable<int> apLevels,
byte subType, IEnumerable<byte> attrs) {
Name = name;
ID = id;
Description = description;
EquipEffect = equipEffect;
_apLevels = apLevels.ToList();
DoInit(subType, attrs);
}
}
public enum SupportMateriaKind {
All = 0x51,
CommandCounter = 0x54,
MagicCounter = 0x55,
SneakAttack = 0x56,
FinalAttack = 0x57,
MPTurbo = 0x58,
MPAbsorb = 0x59,
HPAbsorb = 0x5A,
AddedCut = 0x5C,
StealAsWell = 0x5D,
Elemental = 0x5E,
AddedEffect = 0x5F,
QuadraMagic = 0x63,
}
public class SupportMateria : Materia {
public SupportMateriaKind Kind { get; private set; }
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
Kind = (SupportMateriaKind)attrs.First();
}
public override string ToString() {
return $"Apply effect {Kind} to linked materia";
}
}
public enum IndependentMateriaKind {
Underwater,
HP_MP_Swap,
LongRange,
XPPlus,
CounterAttack,
Cover,
StrengthPlus,
VitalityPlus,
MagicPlus,
SpiritPlus,
SpeedPlus,
LuckPlus,
AttackPlus,
DefensePlus,
MaxHPPlus,
MaxMPPlus,
GilPlus,
EnemyAway,
EnemyLure,
ChocoboLure,
PreEmptive,
MegaAll,
}
public abstract class IndependentMateria : Materia {
public IndependentMateriaKind Kind { get; protected set; }
public override string ToString() {
return $"Independent effect {Kind}";
}
}
public class IndependentMateria1 : IndependentMateria {
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
switch (attrs.First()) {
case 0:
Kind = IndependentMateriaKind.GilPlus;
break;
case 1:
Kind = IndependentMateriaKind.EnemyLure;//TODO or away!
break;
case 2:
Kind = IndependentMateriaKind.ChocoboLure;
break;
case 3:
Kind = IndependentMateriaKind.PreEmptive;
break;
default:
throw new NotImplementedException();
}
}
}
public class IndependentMateria4 : IndependentMateria {
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
Kind = IndependentMateriaKind.MegaAll;
}
}
public class IndependentMateria0 : IndependentMateria {
public List<int> Amounts { get; private set; }
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
switch (subType) {
case 0x0:
switch (attrs.First()) {
case 0xC:
Kind = IndependentMateriaKind.Underwater;
break;
case 0x62:
Kind = IndependentMateriaKind.HP_MP_Swap;
break;
default:
throw new NotSupportedException();
}
break;
case 0x2:
switch (attrs.First()) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
Kind = IndependentMateriaKind.StrengthPlus + attrs.First();
break;
case 11:
Kind = IndependentMateriaKind.Cover;
break;
case 0x53:
Kind = IndependentMateriaKind.CounterAttack;
break;
default:
throw new NotImplementedException();
}
Amounts = attrs.Skip(1).Select(b => (int)b).ToList();
break;
case 0x3:
Kind = IndependentMateriaKind.LongRange;
break;
case 0x4:
Kind = IndependentMateriaKind.XPPlus;
break;
}
}
}
public abstract class CommandMateria : Materia {
public int? RemoveCommand { get; protected set; }
public List<int> Commands { get; protected set; }
public override string ToString() => $"{Name} - grants commands {string.Join("/", Commands)} removes {RemoveCommand}";
}
public class MasterCommandMateria : CommandMateria {
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
Commands = new List<int> {
5, 6, 7, 9, 10, 11, 12,
};
}
}
public class ESkillCommandMateria : CommandMateria {
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
Commands = new List<int> { 13 };
}
}
public class AttackCommandMateria : CommandMateria {
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
RemoveCommand = 1;
Commands = attrs
.TakeWhile(b => b != 0xff)
.Select(b => (int)b)
.ToList();
}
}
public class WCommandMateria : CommandMateria {
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
switch (attrs.First()) {
case 0x15:
RemoveCommand = 2;
Commands = new List<int> { 21 };
break;
case 0x16:
RemoveCommand = 3;
Commands = new List<int> { 22 };
break;
case 0x17:
RemoveCommand = 4;
Commands = new List<int> { 23 };
break;
default:
throw new NotImplementedException();
}
}
}
public class StandardCommandMateria : CommandMateria {
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
Commands = attrs
.TakeWhile(b => b != 0xff)
.Select(b => (int)b)
.ToList();
}
}
public class MasterSummonMateria : SummonMateria {
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
_summons = Enumerable.Range(56, 16).ToList();
}
}
public class SummonMateria : Materia {
protected List<int> _summons;
public IReadOnlyList<int> Summons => _summons.AsReadOnly();
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
_summons = new List<int> { attrs.First() };
}
public override string ToString() => $"{Name} - grants summons {string.Join("/", Summons)}";
}
public class MasterMagicMateria : MagicMateria {
public override IEnumerable<int> GrantedAtLevel(int level) => _magics.Select(m => m.Value);
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
_magics = Enumerable.Range(0, 53).Cast<int?>().ToList();
}
}
public class MagicMateria : Materia {
protected List<int?> _magics;
public IReadOnlyList<int?> Magics => _magics.AsReadOnly();
public virtual IEnumerable<int> GrantedAtLevel(int level) => Magics
.Take(level)
.Where(m => m.HasValue)
.Select(m => m.Value);
protected override void DoInit(byte subType, IEnumerable<byte> attrs) {
_magics = attrs
.Reverse()
.SkipWhile(b => b == 0xff)
.Reverse()
.Select(b => b == 0xff ? (int?)null : (int)b)
.ToList();
}
public override string ToString() => $"{Name} - grants magic {string.Join("/", Magics)}";
}
public class MateriaCollection {
private Dictionary<int, Materia> _materia = new();
public IReadOnlyDictionary<int, Materia> Item => _materia;
public MateriaCollection(Kernel kernel) {
var descriptions = new KernelText(kernel.Sections.ElementAt(15));
var names = new KernelText(kernel.Sections.ElementAt(23));
var data = new MemoryStream(kernel.Sections.ElementAt(8));
int index = 0;
while(data.Position < data.Length) {
ushort[] apLimits = Enumerable.Range(0, 4)
.Select(_ => data.ReadU16())
.ToArray();
byte equipEffect = data.ReadU8();
uint statusElement = data.ReadU32();
byte materiaType = data.ReadU8();
byte[] attrs = Enumerable.Range(0, 6)
.Select(_ => data.ReadU8())
.ToArray();
if (_byType.TryGetValue(materiaType & 0xf, out var create)) {
var materia = create();
materia.Init(
names.Get(index), descriptions.Get(index), index,
MateriaEquipEffect.ByIndex(equipEffect),
apLimits.TakeWhile(u16 => u16 != 0xffff).Select(u16 => (int)u16),
(byte)(materiaType >> 4), attrs
);
_materia[index] = materia;
}
index++;
}
}
private static Dictionary<int, Func<Materia>> _byType = new();
private static void Register<T>(byte type) where T : Materia, new() {
_byType[type] = () => new T();
}
static MateriaCollection() {
Register<IndependentMateria0>(0x0);
Register<IndependentMateria1>(0x1);
Register<AttackCommandMateria>(0x2);
Register<WCommandMateria>(0x3);
Register<IndependentMateria4>(0x4);
Register<SupportMateria>(0x5);
Register<StandardCommandMateria>(0x6);
Register<ESkillCommandMateria>(0x7);
Register<MasterCommandMateria>(0x8);
Register<MagicMateria>(0x9);
Register<MasterMagicMateria>(0xA);
Register<SummonMateria>(0xB);
Register<MasterSummonMateria>(0xC);
}
}
}