using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; namespace OpenVIII { [Flags] [SuppressMessage("ReSharper", "UnusedMember.Global")] public enum ItemTarget : byte { None = 0x0, Useable = 0x1,//usable Character = 0x2, GF = 0x4, All = 0x8, All2 = 0x10,// all two? KO = 0x20, BlueMagic = 0x40,//blue magic? Compatibility = 0x80,//compatibility } public enum ItemType : byte { Heal = 0x00, Revive = 0x01, HealGF = 0x03, ReviveGF = 0x04, SavePointHeal = 0x06, Battle = 0x07, Ammo = 0x08, Magazine = 0x09, /// /// Refine or None /// None = 0x0A, /// /// Rename /// GF = 0x0B, /// /// Rename /// Angelo = 0x0C, /// /// Rename /// Chocobo = 0x0D, /// /// start battle /// Lamp = 0x0E, /// /// get Doomtrain /// SolomonRing = 0x0F, GFCompatibility = 0x10, GFLearn = 0x11, GFForget = 0x12, /// /// blue magic only shows quistis in target list, should detect if she knows the spell to /// grey it out item. /// BlueMagic = 0x13, // learn Stat = 0x14, CureAbnormalStatus = 0x15, } /// /// Effects of Items inside the in game menu. /// public struct ItemInMenu { #region Fields private const int BulletOffset = 101; private byte _b2; private byte _b3; private ItemTarget _itemTarget; private ItemType _itemType; private Dictionary> _useActions; #endregion Fields #region Properties private bool All => (ItemTarget & (ItemTarget.All | ItemTarget.All2)) != 0; private Kernel.AttackType AttackType => Battle?.AttackType ?? Kernel.AttackType.None; public Kernel.BattleItemData Battle { get { if (Memory.KernelBin.BattleItemsData != null) return (Memory.KernelBin.BattleItemsData.Count) > ID ? Memory.KernelBin.BattleItemsData[ID] : null; return null; } } /// /// Which persistant statuses are removed. /// public Kernel.PersistentStatuses CleansedStatuses { get { var ret = Kernel.PersistentStatuses.None; if (ItemType == ItemType.HealGF || ItemType == ItemType.Heal || ItemType == ItemType.Revive || ItemType == ItemType.ReviveGF || ItemType == ItemType.CureAbnormalStatus || ItemType == ItemType.SavePointHeal) ret = (Kernel.PersistentStatuses)_b3; return ret; } } public FF8String Description => Battle?.Description ?? NonBattle?.Description; public byte FadedPalette => 7; /// /// How much healing is done /// public ushort Heals { get { if (ItemType != ItemType.Heal && ItemType != ItemType.HealGF && ItemType != ItemType.SavePointHeal) return 0; if (AttackType == Kernel.AttackType.GivePercentageHP) return (byte)((_b2 * 100) / byte.MaxValue); return (ushort)(_b2 * 0x32); //else if (Type == _Type.Revive || Type == _Type.ReviveGF) // return 0; // 12.5% } } public Icons.ID Icon { get; private set; } /// /// Item ID /// public byte ID { get; private set; } /// /// Who is targeted and 0x01 seems to be a useable item in menu item. Magazine values don't /// seem to corrispond. /// public ItemTarget ItemTarget => ItemType == ItemType.Magazine ? ItemTarget.None : _itemTarget; /// /// Type of item. /// public ItemType ItemType { get { if (ID == 0) _itemType = ItemType.None; return _itemType; } private set => _itemType = value; } /// /// Abilities a GF can learn /// public Kernel.Abilities Learn => ItemType == ItemType.GFLearn ? (Kernel.Abilities)_b2 : Kernel.Abilities.None; public Kernel.BlueMagic LearnedBlueMagic => ItemType == ItemType.BlueMagic ? (Kernel.BlueMagic)_b2 : Kernel.BlueMagic.None; public FF8String Name => Battle?.Name ?? NonBattle?.Name; public Kernel.NonBattleItemsData NonBattle => Battle == null ? Memory.KernelBin.NonBattleItemsData[ID - (Memory.KernelBin.BattleItemsData?.Count ?? 0)] : null; public byte Palette => 9; public Kernel.ShotIrvineLimitBreak Shot { get { if (Memory.KernelBin.ShotIrvineLimitBreak == null) return null; var id = ID - BulletOffset; return (Memory.KernelBin.ShotIrvineLimitBreak.Count) < id || id < 0 ? null : Memory.KernelBin.ShotIrvineLimitBreak[id]; } } public Kernel.Stat Stat => ItemType == ItemType.Stat ? (Kernel.Stat)_b3 : Kernel.Stat.None; public byte StatIncrease => (byte)(ItemType == ItemType.Stat ? _b2 : 0); /* /// /// Target in byte form /// private byte TargetByte => (byte)_target; */ private IReadOnlyDictionary> UseActions { get { if (_useActions == null) { _useActions = new Dictionary> { {ItemType.Heal, HealAction }, {ItemType.Revive, ReviveAction }, {ItemType.HealGF, HealGFAction }, {ItemType.ReviveGF, ReviveGFAction }, {ItemType.SavePointHeal, SavePointHealAction }, {ItemType.Battle, BattleAction }, {ItemType.Ammo, AmmoAction }, {ItemType.Magazine, MagazineAction }, {ItemType.None, NoneAction }, {ItemType.GF, GFAction }, {ItemType.Angelo, AngeloAction }, {ItemType.Chocobo, ChocoboAction }, {ItemType.Lamp, LampAction }, {ItemType.SolomonRing, SolomonRingAction }, {ItemType.GFCompatibility, GFCompatibilityAction }, {ItemType.GFLearn, GFLearnAction }, {ItemType.GFForget, GFForgetAction }, {ItemType.BlueMagic, Blue_MagicAction }, {ItemType.Stat, StatAction }, {ItemType.CureAbnormalStatus, Cure_Abnormal_StatusAction }, }; } return _useActions; } } #endregion Properties #region Methods public static ItemInMenu Read(BinaryReader br, byte i) { Memory.Log.WriteLine($"{nameof(ItemInMenu)} :: {nameof(Read)} :: {i}"); var tmp = new ItemInMenu { ItemType = (ItemType)br.ReadByte(), _itemTarget = (ItemTarget)br.ReadByte(), _b2 = br.ReadByte(), _b3 = br.ReadByte(), ID = i }; switch (tmp.ItemType) { case ItemType.Heal: case ItemType.Revive: case ItemType.CureAbnormalStatus: tmp.Icon = Icons.ID.Item_Recovery; break; case ItemType.HealGF: case ItemType.ReviveGF: case ItemType.GF: case ItemType.GFForget: case ItemType.GFLearn: tmp.Icon = Icons.ID.Item_GF; break; case ItemType.Battle: case ItemType.Angelo: case ItemType.Chocobo: // i'm not sure about this one i have no chocobo items. case ItemType.SolomonRing: case ItemType.Lamp: tmp.Icon = Icons.ID.Item_Battle; break; case ItemType.SavePointHeal: tmp.Icon = Icons.ID.Item_Tent; break; case ItemType.Ammo: tmp.Icon = Icons.ID.Item_Ammo; break; case ItemType.Magazine: tmp.Icon = Icons.ID.Item_Magazine; break; case ItemType.None: case ItemType.BlueMagic: case ItemType.GFCompatibility: case ItemType.Stat: tmp.Icon = Icons.ID.Item_Misc; break; default: tmp.Icon = Icons.ID.None; break; } return tmp; } /// /// How much Compatability is added to gf. neg is how much is removed from other gfs. /// /// Which GF is effected /// How much Comptability is lost by all other GFs /// Total compatability gained by selected character for this GF. /// public byte Compatibility(out GFs gf, out sbyte neg) { gf = 0; neg = 0; byte ret = 0; if (ItemType == ItemType.GFCompatibility) { gf = (GFs)_b2; switch (_b3) { case 0x08://Weak C Item ret = 0x01; neg = (sbyte)(gf == GFs.All ? 0x00 : -0x01); break; case 0x10://Strong C Item ret = 0x03; neg = (sbyte)(gf == GFs.All ? 0x00 : -0x02); break; case 0x65: //LuvLuvG ret = 0x14; neg = 0x00; break; } } return ret; } /// /// If True Quistis can learn the ability. Else Quistis already knows the ability. /// /// public bool TestBlueMagic() { if (LearnedBlueMagic != Kernel.BlueMagic.None) return !Memory.State.LimitBreakQuistisUnlockedBlueMagic[(int)LearnedBlueMagic]; return false; } public bool TestCharacter(ref Faces.ID id, out Characters character) { character = id.ToCharacters(); if (ItemType == ItemType.BlueMagic) { return character == Characters.Quistis_Trepe && TestBlueMagic(); } var teamLaguna = character == Characters.Laguna_Loire || character == Characters.Kiros_Seagill || character == Characters.Ward_Zabac; if(Memory.State.TeamLaguna && !teamLaguna) return false; if (!Memory.State.TeamLaguna && teamLaguna) return false; return ItemTarget.HasFlag(ItemTarget.Character)&& (Memory.State[character]?.Available ?? false); } public bool TestGF(ref Faces.ID id, out GFs gf) { gf = id.ToGFs(); if (ItemType == ItemType.Angelo && id == Faces.ID.Angelo) { return true; } if (ItemType == ItemType.Chocobo && id == Faces.ID.Boko) { return true; } if (gf == GFs.Blank || gf == GFs.All || (ItemTarget & ItemTarget.GF) == 0) return false; if (Memory.State?.GFs != null && Memory.State.GFs.ContainsKey(gf))// && Memory.State.GFs[gf].VisibleInMenu) { if (ItemType == ItemType.GFLearn && (!Memory.State.GFs[gf].TestGFCanLearn(Learn) || Memory.State.GFs[gf].MaxGFAbilities)) return false; return true; } return false; } public override string ToString() => Name ?? base.ToString(); public bool Use(Faces.ID obj, bool battle = false) { if (UseActions.ContainsKey(ItemType)) { var ret = GFAction(UseActions[ItemType], obj, battle); ret = CharAction(UseActions[ItemType], obj, battle) || ret; if (ret) { for (var i = 0; i < Memory.State.Items.Count; i++) { if (Memory.State.Items[i].ID == ID) Memory.State.Items[i].UsedOne(); } //Memory.State.Items.Where(m => m.ID == ID).ForEach(x => x.UsedOne()); return true; } } return false; } public bool ValidTarget(bool b = false) { if (ItemType == ItemType.Magazine) return true; //TODO pressing okay display Magazine. else if (ItemType == ItemType.SolomonRing) return !Memory.State[GFs.Doomtrain]?.Exists ?? true; //TODO detect doomtrain and return false if unlocked else if (ItemType == ItemType.Lamp) return !Memory.State[GFs.Diablos]?.Exists ?? true; //TODO detect diablo and return false if unlocked Faces.ID _face; foreach (var face in (Faces.ID[])Enum.GetValues(typeof(Faces.ID))) { _face = face; if (TestCharacter(ref _face, out _) || TestGF(ref _face, out _)) { return true; } } if (Battle != null && b) return true; return false; } private static bool ChocoboAction(Faces.ID obj, bool battle = false) => false; private static bool GFAction(Faces.ID obj, bool battle = false) => false; private static bool GFCompatibilityAction(Faces.ID obj, bool battle = false) => false; /// /// Spawn a menu of GF's unlocked abilities and you select one to remove. /// private static bool GFForgetAction(Faces.ID obj, bool battle = false) => false; private bool AmmoAction(Faces.ID obj, bool battle = false) => false; private bool AngeloAction(Faces.ID obj, bool battle = false) => false; private bool BattleAction(Faces.ID obj, bool battle = false) => false; private bool Blue_MagicAction(Faces.ID obj, bool battle = false) => false; private bool CharAction(Func func, Faces.ID obj, bool battle = false) { var ret = false; if (All) { foreach (var c in Memory.State.PartyData) { obj = c.ToFacesID(); if (TestCharacter(ref obj, out _)) ret = func(obj, battle) || ret; } return ret; } if (TestCharacter(ref obj, out _)) ret = func(obj, battle); return ret; } private bool Cure_Abnormal_StatusAction(Faces.ID obj, bool battle = false) => Cure_Abnormal_StatusAction(Memory.State[obj]); private bool Cure_Abnormal_StatusAction(IStatusEffects c) { var ret = false; if (c != null && !c.StatusImmune) ret = c.DealStatus(CleansedStatuses, Battle?.Statuses1, Battle?.AttackType ?? Kernel.AttackType.CurativeItem, Battle?.AttackFlags); return ret; } private bool GFAction(Func func, Faces.ID obj, bool battle = false) { var ret = false; if (All) { foreach (var c in Memory.State.GFs.Where(x => x.Value.Exists)) { obj = c.Key.ToFacesID(); if (TestGF(ref obj, out _)) ret = func(obj, battle) || ret; } return ret; } if (TestGF(ref obj, out _)) ret = func(obj, battle); return ret; } /// /// GF learns new skill if below max abilities /// private bool GFLearnAction(Faces.ID obj, bool battle = false) => GFLearnAction(Memory.State[obj.ToGFs()]); private bool GFLearnAction(Saves.GFData gf) => gf.Learn(Learn); private bool HealAction(Faces.ID obj, bool battle = false) => //Memory.State[obj].ChangeHP(-Heals); //return true; //bool ret = false; //if (!Memory.State[obj.ToCharacters()]?.StatusImmune ?? true && Cleansed_Statuses != Kernel_bin.Persistant_Statuses.None) // ret = Memory.State[obj.ToCharacters()]?.DealStatus(Cleansed_Statuses, Battle?.Statuses1, Battle?.Attack_Type ?? Kernel_bin.Attack_Type.Curative_Item, Battle?.Attack_Flags) ?? false; //ret = Memory.State[obj.ToCharacters()]?.DealDamage(Heals, Battle?.Attack_Type ?? Kernel_bin.Attack_Type.Curative_Item, Battle?.Attack_Flags) ?? false || ret; //return ret; HealAction(Memory.State[obj], battle); private bool HealAction(Damageable c, bool battle) { //c.ChangeHP(-Heals); var ret = false; if (c != null && !ZombieCheck(c.Statuses0, battle)) { if (!c.StatusImmune && CleansedStatuses != Kernel.PersistentStatuses.None) ret = c.DealStatus(CleansedStatuses, Battle?.Statuses1, Battle?.AttackType ?? Kernel.AttackType.CurativeItem, Battle?.AttackFlags); ret = c.DealDamage(Heals, Battle?.AttackType ?? Kernel.AttackType.CurativeItem, Battle?.AttackFlags) || ret; } return ret; } private bool HealGFAction(Faces.ID obj, bool battle = false) => HealAction(Memory.State[obj.ToGFs()], battle); private bool LampAction(Faces.ID obj, bool battle = false) => false; private bool MagazineAction(Faces.ID obj, bool battle = false) => //TODO display magazine false; private bool NoneAction(Faces.ID obj, bool battle = false) => false; private bool ReviveAction(Faces.ID obj, bool battle = false) => ReviveAction(Memory.State[obj], battle); private bool ReviveAction(Damageable c, bool battle) { var ret = false; if (c != null && !ZombieCheck(c.Statuses0, battle)) { if (!c.StatusImmune && CleansedStatuses != Kernel.PersistentStatuses.None) ret = c.DealStatus(CleansedStatuses, Battle?.Statuses1, Battle?.AttackType ?? Kernel.AttackType.CurativeItem, Battle?.AttackFlags); ret = c.DealDamage(0, Battle?.AttackType ?? Kernel.AttackType.Revive, Battle?.AttackFlags) || ret; } return ret; } private bool ReviveGFAction(Faces.ID obj, bool battle = false) => ReviveAction(Memory.State[obj], battle); private bool SavePointHealAction(Faces.ID obj, bool battle = false) => false; private bool SolomonRingAction(Faces.ID obj, bool battle = false) => false; private bool StatAction(Faces.ID obj, bool battle = false) => false; private bool ZombieCheck(Kernel.PersistentStatuses statuses0, bool battle = false) => (statuses0 & Kernel.PersistentStatuses.Zombie) != 0 && (CleansedStatuses & Kernel.PersistentStatuses.Zombie) == 0 && !battle; #endregion Methods } /// /// Info on Items in menus. Items have a slightly limited effect in menues. See /// Kernel_bin.Battle_Items for effects in battle. /// /// public class ItemsInMenu : IReadOnlyList { #region Fields private readonly List _items; #endregion Fields #region Constructors private ItemsInMenu() { Memory.Log.WriteLine($"{nameof(ItemsInMenu)} :: new "); _items = new List(); } #endregion Constructors #region Properties public int Count => _items.Count; public IReadOnlyList Items => _items; #endregion Properties #region Indexers public ItemInMenu this[byte item] => Items[item]; public ItemInMenu this[Saves.Item item] => Items[item.ID]; public ItemInMenu this[int index] => _items[index]; #endregion Indexers #region Methods public static ItemsInMenu Read() { var aw = ArchiveWorker.Load(Memory.Archives.A_MENU); var buffer = aw.GetBinaryFile("mitem.bin"); if (buffer != null) using (var br = new BinaryReader(new MemoryStream(buffer))) { return Read(br); } return default; } public IEnumerator GetEnumerator() => _items.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_items).GetEnumerator(); private static ItemsInMenu Read(BinaryReader br) { Memory.Log.WriteLine($"{nameof(ItemsInMenu)} :: {nameof(Read)} "); var ret = new ItemsInMenu(); for (byte i = 0; br.BaseStream.Position + 4 <= br.BaseStream.Length; i++) { var tmp = ItemInMenu.Read(br, i); ret._items.Add(tmp); } return ret; } #endregion Methods } }