From b36a5e15ea17142ac7fad729c460c81f3d125066 Mon Sep 17 00:00:00 2001 From: pixeltris <-> Date: Mon, 14 May 2018 13:19:27 +0100 Subject: [PATCH] Fix FileFormats/bin gitignore --- .gitignore | 2 + Lotd/FileFormats/bin/CardLimits.cs | 59 + Lotd/FileFormats/bin/CardManager.cs | 1775 +++++++++++++++++++++++ Lotd/FileFormats/bin/RelatedCardData.cs | 99 ++ 4 files changed, 1935 insertions(+) create mode 100644 Lotd/FileFormats/bin/CardLimits.cs create mode 100644 Lotd/FileFormats/bin/CardManager.cs create mode 100644 Lotd/FileFormats/bin/RelatedCardData.cs diff --git a/.gitignore b/.gitignore index f652b45..b0b9604 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ bld/ [Oo]bj/ [Ll]og/ +!Lotd/FileFormats/[Bb]in + # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot diff --git a/Lotd/FileFormats/bin/CardLimits.cs b/Lotd/FileFormats/bin/CardLimits.cs new file mode 100644 index 0000000..5d62818 --- /dev/null +++ b/Lotd/FileFormats/bin/CardLimits.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Lotd.FileFormats +{ + /// + /// Holds limited / banned card information (pd_limits.bin) + /// + public class CardLimits : FileData + { + public HashSet Forbidden { get; private set; } + public HashSet Limited { get; private set; } + public HashSet SemiLimited { get; private set; } + + public CardLimits() + { + Forbidden = new HashSet(); + Limited = new HashSet(); + SemiLimited = new HashSet(); + } + + public override void Load(BinaryReader reader, long length) + { + ReadCardIds(reader, Forbidden); + ReadCardIds(reader, Limited); + ReadCardIds(reader, SemiLimited); + } + + public override void Save(BinaryWriter writer) + { + WriteCardIds(writer, Forbidden); + WriteCardIds(writer, Limited); + WriteCardIds(writer, SemiLimited); + } + + private void ReadCardIds(BinaryReader reader, HashSet cardIds) + { + cardIds.Clear(); + + short count = reader.ReadInt16(); + for (int i = 0; i < count; i++) + { + cardIds.Add(reader.ReadInt16()); + } + } + + private void WriteCardIds(BinaryWriter writer, HashSet cardIds) + { + writer.Write((short)cardIds.Count); + foreach (ushort cardId in cardIds) + { + writer.Write(cardId); + } + } + } +} diff --git a/Lotd/FileFormats/bin/CardManager.cs b/Lotd/FileFormats/bin/CardManager.cs new file mode 100644 index 0000000..0b561ad --- /dev/null +++ b/Lotd/FileFormats/bin/CardManager.cs @@ -0,0 +1,1775 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace Lotd.FileFormats +{ + // TODO: Split some of this into data files + + public class CardManager + { + public Manager Manager { get; private set; } + public Dictionary Cards { get; private set; } + public List CardsByIndex { get; private set; } + + // Card name types / archetypes + public Dictionary> CardNameTypes { get; private set; } + + // Tags used for finding related cards which is used to display related cards on the left/right panels in deck edit + public List Tags { get; private set; } + + private Dictionary> cardsByName; + + public CardManager(Manager manager) + { + Manager = manager; + Cards = new Dictionary(); + CardsByIndex = new List(); + Tags = new List(); + cardsByName = new Dictionary>(); + CardNameTypes = new Dictionary>(); + } + + public CardInfo FindCardByName(Language language, string name) + { + CardInfo cardInfo; + cardsByName[language].TryGetValue(name, out cardInfo); + return cardInfo; + } + + public void Load() + { + Cards.Clear(); + CardsByIndex.Clear(); + cardsByName.Clear(); + Tags.Clear(); + + LotdArchive archive = Manager.Archive; + + Dictionary indx = archive.LoadLocalizedBuffer("CARD_Indx_", true); + Dictionary names = archive.LoadLocalizedBuffer("CARD_Name_", true); + Dictionary descriptions = archive.LoadLocalizedBuffer("CARD_Desc_", true); + Dictionary taginfos = archive.LoadLocalizedBuffer("taginfo_", true); + + List allCardImages = new List(); + allCardImages.AddRange(archive.Root.FindFile("cardcropHD400.jpg.zib").LoadData().Files.Values); + allCardImages.AddRange(archive.Root.FindFile("cardcropHD401.jpg.zib").LoadData().Files.Values); + + Dictionary cardImagesById = new Dictionary(); + foreach (ZibFile file in allCardImages) + { + short cardId = short.Parse(file.FileName.Substring(0, file.FileName.IndexOf('.'))); + cardImagesById.Add(cardId, file); + } + + List cards = new List(); + foreach (Language language in Enum.GetValues(typeof(Language))) + { + if (language != Language.Unknown) + { + LoadCardNamesAndDescriptions(language, cards, indx, names, descriptions); + + Dictionary languageCardsByName = new Dictionary(); + cardsByName.Add(language, languageCardsByName); + foreach (CardInfo card in cards) + { + // This will wipe over cards with conflicting names + languageCardsByName[card.Name.GetText(language)] = card; + } + } + } + CardsByIndex.AddRange(cards); + + // Load card props (card id, atk, def, level, attribute, etc) + LoadCardProps(cards, Cards, cardImagesById); + + ProcessLimitedCardList(Cards); + LoadCardGenre(cards); + LoadRelatedCards(cards, Cards, Tags, taginfos); + LoadCardNameTypes(Cards, CardNameTypes); + + //PrintLimitedCardList(); + } + + /// + /// Loads the card name types / archetypes (e.g. "Harpie") + /// + private void LoadCardNameTypes(Dictionary cards, Dictionary> cardNameTypes) + { + using (BinaryReader reader = new BinaryReader(new MemoryStream(Manager.Archive.Root.FindFile("bin/CARD_Named.bin").LoadBuffer()))) + { + ushort numArchetypes = reader.ReadUInt16(); + ushort numCards = reader.ReadUInt16(); + + long cardsStartOffset = 4 + (numArchetypes * 4); + long cardsEndOffset = cardsStartOffset + (numCards * 2); + Debug.Assert(reader.BaseStream.Length == cardsEndOffset); + + for (int i = 0; i < numArchetypes; i++) + { + int offset = reader.ReadInt16();// The offset of the card into the cards list + int count = reader.ReadInt16();// The number of cards for this name type + HashSet cardIds = new HashSet(); + cardNameTypes.Add((CardNameType)i, cardIds); + + long tempOffset = reader.BaseStream.Position; + reader.BaseStream.Position = cardsStartOffset + (offset * 2); + for (int j = 0; j < count; j++) + { + short cardId = reader.ReadInt16(); + Cards[cardId].NameTypes.Add((CardNameType)i); + cardIds.Add(cardId); + } + + reader.BaseStream.Position = tempOffset; + } + } + } + + private void LoadCardGenre(List cards) + { + using (BinaryReader reader = new BinaryReader(new MemoryStream(Manager.Archive.Root.FindFile("bin/CARD_Genre.bin").LoadBuffer()))) + { + for (int i = 0; i < cards.Count; i++) + { + CardInfo card = cards[i]; + card.Genre = (CardGenre)reader.ReadUInt64(); + } + } + } + + private void LoadCardProps(List cards, Dictionary cardsById, Dictionary cardImagesById) + { + using (BinaryReader reader = new BinaryReader(new MemoryStream(Manager.Archive.Root.FindFile("bin/CARD_Prop.bin").LoadBuffer()))) + { + for (int i = 0; i < cards.Count; i++) + { + CardInfo card = cards[i]; + LoadCardProp(card, cardsById, reader.ReadUInt32(), reader.ReadUInt32()); + if (card.CardId > 0) + { + card.ImageFile = cardImagesById[card.CardId]; + } + } + } + } + + private void LoadCardProp(CardInfo card, Dictionary cardsById, uint a1, uint a2) + { + uint first = (a1 << 18) | ((a1 & 0x7FC000 | a1 >> 18) >> 5); + + uint second = (((a2 & 1u) | (a2 << 21)) & 0x80000001 | ((a2 & 0x7800) | ((a2 & 0x780 | ((a2 & 0x7E) << 10)) << 8)) << 6 | + ((a2 & 0x38000 | ((a2 & 0x7C0000 | ((a2 & 0x7800000 | (a2 >> 8) & 0x780000) >> 9)) >> 8)) >> 1)); + + short cardId = (short)((first >> 18) & 0x3FFF); + uint atk = ((first >> 9) & 0x1FF); + uint def = (first & 0x1FF); + CardType cardType = (CardType)((second >> 25) & 0x3F); + CardAttribute attribute = (CardAttribute)((second >> 21) & 0xF); + uint level = (second >> 17) & 0xF; + SpellType spellType = (SpellType)((second >> 14) & 7); + MonsterType monsterType = (MonsterType)((second >> 9) & 0x1F); + uint pendulumScale1 = (second >> 1) & 0xF; + uint pendulumScale2 = (second >> 5) & 0xF; + + card.CardId = cardId; + card.Atk = (int)(atk * 10); + card.Def = (int)(def * 10); + card.Level = (byte)level; + card.Attribute = attribute; + card.CardType = cardType; + card.SpellType = spellType; + card.MonsterType = monsterType; + card.PendulumScale1 = (byte)pendulumScale1; + card.PendulumScale2 = (byte)pendulumScale2; + + cardsById.Add(cardId, card); + + // This is a hard coded value in native code. Might as well do the same check here. + Debug.Assert(cardId < Constants.MaxCardId + 1); + + if (!Enum.IsDefined(typeof(MonsterType), monsterType) || + !Enum.IsDefined(typeof(SpellType), spellType) || + !Enum.IsDefined(typeof(CardType), cardType) || + !Enum.IsDefined(typeof(CardAttribute), attribute)) + { + Debug.Assert(false); + } + } + + private void LoadCardNamesAndDescriptions(Language language, List cards, + Dictionary indxByLanguage, + Dictionary namesByLanguage, + Dictionary descriptionsByLanguage) + { + if (language == Language.Unknown) + { + return; + } + + byte[] indx = indxByLanguage[language]; + byte[] names = namesByLanguage[language]; + byte[] descriptions = descriptionsByLanguage[language]; + + using (BinaryReader indxReader = new BinaryReader(new MemoryStream(indx))) + using (BinaryReader namesReader = new BinaryReader(new MemoryStream(names))) + using (BinaryReader descriptionsReader = new BinaryReader(new MemoryStream(descriptions))) + { + Dictionary namesByOffset = ReadStrings(namesReader); + Dictionary descriptionsByOffset = ReadStrings(descriptionsReader); + + int index = 0; + while (true) + { + uint nameOffset = indxReader.ReadUInt32(); + uint descriptionOffset = indxReader.ReadUInt32(); + + if (indxReader.BaseStream.Position >= indxReader.BaseStream.Length) + { + // The last index points to an invalid offset + break; + } + + CardInfo card = null; + if (cards.Count > index) + { + card = cards[index]; + } + else + { + cards.Add(card = new CardInfo(index)); + } + + card.Name.SetText(language, namesByOffset[nameOffset]); + card.Description.SetText(language, descriptionsByOffset[descriptionOffset]); + + index++; + } + } + } + + private Dictionary ReadStrings(BinaryReader reader) + { + Dictionary result = new Dictionary(); + while (reader.BaseStream.Position < reader.BaseStream.Length) + { + uint offset = (uint)reader.BaseStream.Position; + string name = reader.ReadNullTerminatedString(Encoding.Unicode); + result.Add(offset, name); + } + return result; + } + + private void LoadRelatedCards(List cards, Dictionary cardsByCardId, List tags, + Dictionary taginfos) + { + foreach (Language language in Enum.GetValues(typeof(Language))) + { + if (language == Language.Unknown) + { + continue; + } + + using (BinaryReader reader = new BinaryReader(new MemoryStream(taginfos[language]))) + { + int count = reader.ReadInt32(); + for (int i = 0; i < count; i++) + { + CardTagInfo tagInfo = null; + if (i >= tags.Count) + { + tagInfo = new CardTagInfo(); + tags.Add(tagInfo); + } + else + { + tagInfo = tags[i]; + } + + tagInfo.Index = i; + tagInfo.MainType = (CardTagInfo.Type)reader.ReadInt16(); + tagInfo.MainValue = reader.ReadInt16(); + for (int j = 0; j < tagInfo.Elements.Length; j++) + { + tagInfo.Elements[j].Type = (CardTagInfo.ElementType)reader.ReadInt16(); + tagInfo.Elements[j].Value = reader.ReadInt16(); + } + long stringOffset1 = reader.ReadInt64(); + long stringOffset2 = reader.ReadInt64(); + + long tempOffset = reader.BaseStream.Position; + + reader.BaseStream.Position = stringOffset1; + tagInfo.Text.SetText(language, reader.ReadNullTerminatedString(Encoding.Unicode)); + + reader.BaseStream.Position = stringOffset2; + tagInfo.DisplayText.SetText(language, reader.ReadNullTerminatedString(Encoding.Unicode)); + + reader.BaseStream.Position = tempOffset; + } + } + } + + using (BinaryReader reader = new BinaryReader(new MemoryStream(Manager.Archive.Root.FindFile("bin/tagdata.bin").LoadBuffer()))) + { + long dataStart = reader.BaseStream.Position + (cards.Count * 8); + + for (int i = 0; i < cards.Count; i++) + { + uint shortoffset = reader.ReadUInt32(); + uint tagCount = reader.ReadUInt32(); + + long tempOffset = reader.BaseStream.Position; + + long start = dataStart + (shortoffset * 4); + reader.BaseStream.Position = start; + if (tagCount > 0 && i >= 0) + { + CardInfo card = cards[i]; + card.RelatedCards.Clear(); + for (int j = 0; j < tagCount; j++) + { + card.RelatedCards.Add(new RelatedCardInfo(cardsByCardId[reader.ReadInt16()], Tags[reader.ReadInt16()])); + } + } + + reader.BaseStream.Position = tempOffset; + } + } + + CardTagInfo.Type[] knownMainTagTypes = (CardTagInfo.Type[])Enum.GetValues(typeof(CardTagInfo.Type)); + CardTagInfo.ElementType[] knownElementTagTypes = (CardTagInfo.ElementType[])Enum.GetValues(typeof(CardTagInfo.ElementType)); + foreach (CardTagInfo tag in tags) + { + Debug.Assert(knownMainTagTypes.Contains(tag.MainType)); + Debug.Assert(tag.MainValue <= 1); + + foreach (CardTagInfo.Element element in tag.Elements) + { + if (element.Type == CardTagInfo.ElementType.None) + { + continue; + } + + Debug.Assert(knownElementTagTypes.Contains(element.Type)); + } + + if (tag.MainType == CardTagInfo.Type.Exact) + { + // Need english here + Language language = Language.English; + string displayText = tag.DisplayText.GetText(language); + string text = tag.Text.GetText(Language.English); + int splitIndex = displayText == null ? -1 : displayText.IndexOf(':'); + if (splitIndex >= 0) + { + string typeStr = displayText.Substring(0, splitIndex).Trim(); + string value = displayText.Substring(splitIndex + 1).Trim(); + switch (typeStr.ToLower()) + { + case "related to": + tag.Exact = CardTagInfo.ExactType.RelatedTo; + tag.ExactCard = FindCardByName(language, value); + break; + case "card effect": + tag.Exact = CardTagInfo.ExactType.CardEffect; + break; + case "ritual monster": + tag.Exact = CardTagInfo.ExactType.RitualMonster; + tag.ExactCard = FindCardByName(language, value); + break; + case "fusion monster": + tag.Exact = CardTagInfo.ExactType.FusionMonster; + tag.ExactCard = FindCardByName(language, value); + break; + case "spell and trap": + tag.Exact = CardTagInfo.ExactType.SpellTrap; + break; + case "works well with": + tag.Exact = CardTagInfo.ExactType.WorksWellWith; + tag.ExactCard = FindCardByName(language, value); + break; + default: + Debug.Assert(false); + break; + } + } + else + { + switch (text.ToLower()) + { + case "banishbeast": + tag.Exact = CardTagInfo.ExactType.BanishBeast; + break; + case "banishdark": + tag.Exact = CardTagInfo.ExactType.BanishDark; + break; + case "banishfish": + tag.Exact = CardTagInfo.ExactType.BanishFish; + break; + case "banishrock": + tag.Exact = CardTagInfo.ExactType.BanishRock; + break; + case "countertrapfairy": + tag.Exact = CardTagInfo.ExactType.CounterTrapFairy; + break; + case "spellcounter": + tag.Exact = CardTagInfo.ExactType.SpellCounter; + break; + default: + Debug.Assert(false); + break; + } + } + + switch (tag.Exact) + { + case CardTagInfo.ExactType.CardEffect: + CardTagInfo.CardEffectType cardEffect; + if (!Enum.TryParse(text, true, out cardEffect)) + { + Debug.Assert(false); + } + tag.CardEffect = cardEffect; + break; + + case CardTagInfo.ExactType.SpellTrap: + CardTagInfo.CardEffectType spellEffect; + if (!Enum.TryParse("Spell_" + text, true, out spellEffect)) + { + Debug.Assert(false); + } + tag.CardEffect = spellEffect; + break; + } + } + } + + foreach (CardInfo card in cards) + { + foreach (RelatedCardInfo relatedCardInfo in card.RelatedCards) + { + if (relatedCardInfo.TagInfo.CardEffect != CardTagInfo.CardEffectType.None) + { + card.CardEffectTags.Add(relatedCardInfo.TagInfo.CardEffect); + } + } + } + } + + private void ProcessLimitedCardList(Dictionary cardsById) + { + foreach (CardInfo card in cardsById.Values) + { + card.Limit = CardLimitation.NotLimited; + } + + foreach (short cardId in Manager.CardLimits.Forbidden) + { + cardsById[cardId].Limit = CardLimitation.Forbidden; + } + + foreach (short cardId in Manager.CardLimits.Limited) + { + cardsById[cardId].Limit = CardLimitation.Limited; + } + + foreach (short cardId in Manager.CardLimits.SemiLimited) + { + cardsById[cardId].Limit = CardLimitation.SemiLimited; + } + } + + private void PrintLimitedCardList() + { + Debug.WriteLine("========================== Forbidden =========================="); + foreach (short cardId in Manager.CardLimits.Forbidden) + { + Debug.WriteLine(Cards[cardId].Name); + } + + Debug.WriteLine("========================== Limited =========================="); + foreach (short cardId in Manager.CardLimits.Limited) + { + Debug.WriteLine(Cards[cardId].Name); + } + + Debug.WriteLine("========================== Semi-limited =========================="); + foreach (short cardId in Manager.CardLimits.SemiLimited) + { + Debug.WriteLine(Cards[cardId].Name); + } + } + } + + public class CardInfo + { + public int CardIndex { get; set; } + public short CardId { get; set; } + public ZibFile ImageFile { get; set; } + public LocalizedText Name { get; set; } + public LocalizedText Description { get; set; } + public List RelatedCards { get; private set; } + public HashSet CardEffectTags { get; private set; } + + /// + /// Name type / archetype e.g. "Harpie" + /// + public HashSet NameTypes { get; set; } + + /// + /// The set ids this card belongs to (this is loaded from external sources) + /// A set is a pack, deck or other form of card collection in the official game + /// + public List SetIds { get; private set; } + + public int Atk { get; set; } + public int Def { get; set; } + public byte Level { get; set; } + + public bool IsUnknownAtk + { + get { return Atk == 5110; } + } + + public bool IsUnknownDef + { + get { return Def == 5110; } + } + + /// + /// Light, Dark, Water, Fire (also Spell / Trap) + /// + public CardAttribute Attribute { get; set; } + + /// + /// Fusion, Effect, Tuner, Flip, Ritual (also Spell / Trap) + /// + public CardType CardType { get; set; } + + /// + /// Field, Equip, QuickPlay, Continuous + /// + public SpellType SpellType { get; set; } + + /// + /// Insect, Fiend, Beast, Aqua, Plant (also Spell / Trap) + /// + public MonsterType MonsterType { get; set; } + + public byte PendulumScale1 { get; set; } + public byte PendulumScale2 { get; set; } + + public byte PendulumScale + { + get { return Math.Max(PendulumScale1, PendulumScale2); } + } + + /// + /// Card limitation (NotLimited, Forbidden, Limited, SemiLimited) + /// + public CardLimitation Limit { get; set; } + + /// + /// Card genre (negate effect, direct attack, cannot be destroyed, etc) + /// + public CardGenre Genre { get; set; } + + public CardTypeFlags CardTypeFlags + { + get { return GetCardTypeFlags(CardType); } + } + + public bool IsMonsterToken + { + get { return IsMonster && CardTypeFlags.HasFlag(CardTypeFlags.Token); } + } + + public bool IsEffect + { + get { return CardTypeFlags.HasFlag(CardTypeFlags.Effect); } + } + + public bool IsMonster + { + get { return Attribute != CardAttribute.Spell && Attribute != CardAttribute.Trap; } + } + + public bool IsNormalMonster + { + get { return FrameType == CardFrameType.Normal || FrameType == CardFrameType.PendulumNormal; } + } + + public bool IsPendulum + { + get { return CardTypeFlags.HasFlag(CardTypeFlags.Pendulum); } + } + + public bool IsXyz + { + get { return CardTypeFlags.HasFlag(CardTypeFlags.Xyz); } + } + + public bool IsSynchro + { + get { return CardTypeFlags.HasFlag(CardTypeFlags.Synchro); } + } + + public bool IsFusion + { + get { return CardTypeFlags.HasFlag(CardTypeFlags.Fusion); } + } + + public bool IsMainDeckCard + { + get { return !IsExtraDeckCard; } + } + + public bool IsExtraDeckCard + { + get + { + return CardTypeFlags.HasFlag(CardTypeFlags.Xyz) || CardTypeFlags.HasFlag(CardTypeFlags.Fusion) || + CardTypeFlags.HasFlag(CardTypeFlags.Synchro); + } + } + + public bool IsSpell + { + get { return Attribute == CardAttribute.Spell; } + } + + public bool IsTrap + { + get { return Attribute == CardAttribute.Trap; } + } + + public string FrameName + { + get { return GetFrameName(FrameType); } + } + + public CardFrameType FrameType + { + get + { + if (IsSpell) + { + return CardFrameType.Spell; + } + + if (IsTrap) + { + return CardFrameType.Trap; + } + + CardTypeFlags cardFlags = CardTypeFlags; + + if (cardFlags.HasFlag(CardTypeFlags.Synchro)) + { + if (cardFlags.HasFlag(CardTypeFlags.Pendulum)) + { + return CardFrameType.PendulumSynchro; + } + return CardFrameType.Synchro; + } + + if (cardFlags.HasFlag(CardTypeFlags.Xyz)) + { + if (cardFlags.HasFlag(CardTypeFlags.Pendulum)) + { + return CardFrameType.PendulumXyz; + } + return CardFrameType.Xyz; + } + + if (cardFlags.HasFlag(CardTypeFlags.Pendulum)) + { + if (cardFlags.HasFlag(CardTypeFlags.Effect)) + { + return CardFrameType.PendulumEffect; + } + return CardFrameType.PendulumNormal; + } + + if (cardFlags.HasFlag(CardTypeFlags.Token)) + { + return CardFrameType.Token; + } + + if (cardFlags.HasFlag(CardTypeFlags.Fusion)) + { + return CardFrameType.Fusion; + } + + if (cardFlags.HasFlag(CardTypeFlags.Ritual)) + { + return CardFrameType.Ritual; + } + + if (cardFlags.HasFlag(CardTypeFlags.Effect) || + cardFlags.HasFlag(CardTypeFlags.SpecialSummon) || + cardFlags.HasFlag(CardTypeFlags.Union) || + cardFlags.HasFlag(CardTypeFlags.Toon) || + cardFlags.HasFlag(CardTypeFlags.Gemini)) + { + return CardFrameType.Effect; + } + + return CardFrameType.Normal; + } + } + + public CardInfo(int index) + { + CardIndex = index; + Name = new LocalizedText(); + Description = new LocalizedText(); + RelatedCards = new List(); + CardEffectTags = new HashSet(); + NameTypes = new HashSet(); + SetIds = new List(); + } + + public string GetDescription(Language language, bool pendulumDescription) + { + if (pendulumDescription && !IsPendulum) + { + return string.Empty; + } + + string text = Description.GetText(language); + if (IsPendulum) + { + string pendulumHeader = "[Pendulum Effect]"; + int index = text.IndexOf(pendulumHeader); + if (pendulumDescription) + { + return index == -1 ? string.Empty : text.Substring(index + pendulumHeader.Length); + } + else + { + return index == -1 ? text : text.Substring(0, index); + } + } + return text; + } + + public static CardTypeFlags GetCardTypeFlags(CardType cardType) + { + switch (cardType) + { + case CardType.Default: return CardTypeFlags.Default; + case CardType.Effect: return CardTypeFlags.Effect; + case CardType.Fusion: return CardTypeFlags.Fusion; + case CardType.FusionEffect: return CardTypeFlags.Fusion | CardTypeFlags.Effect; + case CardType.Ritual: return CardTypeFlags.Ritual; + case CardType.RitualEffect: return CardTypeFlags.Ritual | CardTypeFlags.Effect; + case CardType.ToonEffect: return CardTypeFlags.Toon | CardTypeFlags.Effect; + case CardType.SpiritEffect: return CardTypeFlags.Spirit | CardTypeFlags.Effect; + case CardType.UnionEffect: return CardTypeFlags.Union | CardTypeFlags.Effect; + case CardType.GeminiEffect: return CardTypeFlags.Gemini | CardTypeFlags.Effect; + case CardType.Token: return CardTypeFlags.Token; + case CardType.Spell: return CardTypeFlags.Spell; + case CardType.Trap: return CardTypeFlags.Trap; + case CardType.Tuner: return CardTypeFlags.Tuner; + case CardType.TunerEffect: return CardTypeFlags.Tuner | CardTypeFlags.Effect; + case CardType.Synchro: return CardTypeFlags.Synchro; + case CardType.SynchroEffect: return CardTypeFlags.Synchro | CardTypeFlags.Effect; + case CardType.SynchroTunerEffect: return CardTypeFlags.Synchro | CardTypeFlags.Tuner | CardTypeFlags.Effect; + case CardType.DarkTunerEffect: return CardTypeFlags.DarkTuner | CardTypeFlags.Effect; + case CardType.DarkSynchroEffect: return CardTypeFlags.DarkSynchro | CardTypeFlags.Effect; + case CardType.Xyz: return CardTypeFlags.Xyz; + case CardType.XyzEffect: return CardTypeFlags.Xyz | CardTypeFlags.Effect; + case CardType.FlipEffect: return CardTypeFlags.Flip | CardTypeFlags.Effect; + case CardType.Pendulum: return CardTypeFlags.Pendulum; + case CardType.PendulumEffect: return CardTypeFlags.Pendulum | CardTypeFlags.Effect; + case CardType.EffectSp: return CardTypeFlags.Effect | CardTypeFlags.SpecialSummon; + case CardType.ToonEffectSp: return CardTypeFlags.Toon | CardTypeFlags.Effect | CardTypeFlags.SpecialSummon; + case CardType.SpiritEffectSp: return CardTypeFlags.Spirit | CardTypeFlags.Effect | CardTypeFlags.SpecialSummon; + case CardType.TunerEffectSp: return CardTypeFlags.Tuner | CardTypeFlags.Effect | CardTypeFlags.SpecialSummon; + case CardType.DarkTunerEffectSp: return CardTypeFlags.DarkTuner | CardTypeFlags.Effect | CardTypeFlags.SpecialSummon; + case CardType.FlipTunerEffect: return CardTypeFlags.Flip | CardTypeFlags.Tuner | CardTypeFlags.Effect; + case CardType.PendulumTunerEffect: return CardTypeFlags.Pendulum | CardTypeFlags.Tuner | CardTypeFlags.Effect; + case CardType.XyzPendulumEffect: return CardTypeFlags.Xyz | CardTypeFlags.Pendulum | CardTypeFlags.Effect; + case CardType.PendulumFlipEffect: return CardTypeFlags.Pendulum | CardTypeFlags.Flip | CardTypeFlags.Effect; + //case CardType.SynchoPendulumEffect: return CardTypeFlags.Synchro | CardTypeFlags.Pendulum | CardTypeFlags.Effect; + //case CardType.UnionTunerEffect: return CardTypeFlags.Union | CardTypeFlags.Tuner | CardTypeFlags.Effect; + //case CardType.RitualSpiritEffect: return CardTypeFlags.Ritual | CardTypeFlags.Spirit | CardTypeFlags.Effect; + case CardType.AnyNormal: return CardTypeFlags.Any | CardTypeFlags.Normal; + case CardType.AnyFusion: return CardTypeFlags.Any | CardTypeFlags.Fusion; + case CardType.AnyFlip: return CardTypeFlags.Any | CardTypeFlags.Flip; + case CardType.AnyPendulum: return CardTypeFlags.Any | CardTypeFlags.Pendulum; + case CardType.AnyRitual: return CardTypeFlags.Any | CardTypeFlags.Ritual; + case CardType.AnySynchro: return CardTypeFlags.Any | CardTypeFlags.Synchro; + case CardType.AnyTuner: return CardTypeFlags.Any | CardTypeFlags.Tuner; + case CardType.AnyXyz: return CardTypeFlags.Any | CardTypeFlags.Xyz; + default: + throw new NotImplementedException("Unhandled CardType->CardTypeFlags conversion " + cardType); + } + } + + public static string GetFrameName(CardFrameType frameType) + { + switch (frameType) + { + default: + case CardFrameType.Normal: return "card_nomal"; + case CardFrameType.Effect: return "card_kouka"; + case CardFrameType.Token: return "card_token"; + case CardFrameType.Ritual: return "card_gisiki"; + case CardFrameType.Fusion: return "card_yugo"; + case CardFrameType.PendulumEffect: return "card_pendulum"; + case CardFrameType.PendulumNormal: return "card_pendulum_n"; + case CardFrameType.PendulumSynchro: return "card_sync_pendulum"; + case CardFrameType.PendulumXyz: return "card_xyz_pendulum"; + case CardFrameType.Synchro: return "card_sync"; + case CardFrameType.Xyz: return "card_xyz"; + case CardFrameType.Spell: return "card_mahou"; + case CardFrameType.Trap: return "card_wana"; + } + } + + public static string GetFullMonsterTypeName(MonsterType monsterType, CardTypeFlags cardType) + { + string result = null; + foreach (CardTypeFlags flag in Enum.GetValues(typeof(CardTypeFlags))) + { + if (cardType.HasFlag(flag)) + { + string flagName = GetCardTypeFlagName(flag); + if (!string.IsNullOrEmpty(flagName)) + { + // Reverse order + result = result == null ? flagName : flagName + "/" + result; + } + } + } + + return "[" + GetMonsterTypeName(monsterType) + (result == null ? string.Empty : "/" + result) + "]"; + } + + public static string GetMonsterTypeName(MonsterType monsterType) + { + switch (monsterType) + { + case MonsterType.Dragon: return "Dragon"; + case MonsterType.Zombie: return "Zombie"; + case MonsterType.Fiend: return "Fiend"; + case MonsterType.Pyro: return "Pyro"; + case MonsterType.SeaSerpent: return "Sea Serpent"; + case MonsterType.Rock: return "Rock"; + case MonsterType.Machine: return "Machine"; + case MonsterType.Fish: return "Fish"; + case MonsterType.Dinosaur: return "Dinosaur"; + case MonsterType.Insect: return "Insect"; + case MonsterType.Beast: return "Beast"; + case MonsterType.BeastWarrior: return "Beast-Warrior"; + case MonsterType.Plant: return "Plant"; + case MonsterType.Aqua: return "Aqua"; + case MonsterType.Warrior: return "Warrior"; + case MonsterType.WingedBeast: return "Winged Beast"; + case MonsterType.Fairy: return "Fairy"; + case MonsterType.Spellcaster: return "Spellcaster"; + case MonsterType.Thunder: return "Thunder"; + case MonsterType.Reptile: return "Reptile"; + case MonsterType.Psychic: return "Psychic"; + case MonsterType.Wyrm: return "Wyrm"; + case MonsterType.DivineBeast: return "Divine-Beast"; + case MonsterType.CreatorGod: return "Creator"; + case MonsterType.Spell: return "Spell"; + case MonsterType.Trap: return "Trap"; + case MonsterType.Unknown: + default: + return "?"; + } + } + + public static string GetCardTypeFlagName(CardTypeFlags flag) + { + switch (flag) + { + default: + case CardTypeFlags.Default: return null; + case CardTypeFlags.Effect: return "Effect"; + case CardTypeFlags.Fusion: return "Fusion"; + case CardTypeFlags.Ritual: return "Ritual"; + case CardTypeFlags.Toon: return "Toon"; + case CardTypeFlags.Spirit: return "Spirit"; + case CardTypeFlags.Union: return "Union"; + case CardTypeFlags.Gemini: return "Gemini"; + case CardTypeFlags.Token: return "Token"; + case CardTypeFlags.Spell: return "Spell"; + case CardTypeFlags.Trap: return "Trap"; + //case CardTypeFlags.Common: return ""; + case CardTypeFlags.Tuner: return "Tuner"; + case CardTypeFlags.DarkTuner: return "Dark Tuner"; + case CardTypeFlags.DarkSynchro: return "Dark Synchro"; + case CardTypeFlags.Synchro: return "Synchro"; + case CardTypeFlags.Xyz: return "Xyz"; + case CardTypeFlags.Flip: return "Flip"; + case CardTypeFlags.Pendulum: return "Pendulum"; + //case CardTypeFlags.SpecialSummon: return ""; + } + } + } + + public enum CardAttribute + { + Unknown = 0, + LightMonster = 1, + DarkMonster = 2, + WaterMonster = 3, + FireMonster = 4, + EarthMonster = 5, + WindMonster = 6, + DivineMonster = 7, + Spell = 8, + Trap = 9 + } + + public enum CardType + { + Default = 0, + Effect = 1, + Fusion = 2, + FusionEffect = 3,// Thousand-Eyes Restrict + Ritual = 4, + RitualEffect = 5,// Relinquished + ToonEffect = 6,// Toon Masked Scorcerer + SpiritEffect = 7,// Maharaghi + UnionEffect = 8,// Y-Dragon Head + GeminiEffect = 9,// Elemental HERO Neos Alius + Token = 10, + //11 = Effect? - duel links states this is "God" + //12 = Effect? - duel links states this is "Dummy" + Spell = 13, + Trap = 14, + Tuner = 15,// Flamvell Guard + TunerEffect = 16,// Cryomancer of the Ice Barrier + Synchro = 17, // Gaia Knight, the Force of Earth + SynchroEffect = 18,// Dark End Dragon + SynchroTunerEffect = 19,// Formula Synchron + DarkTunerEffect = 20,// unused + DarkSynchroEffect = 21,// unused + Xyz = 22,// Gem-Knight Pearl + XyzEffect = 23,// Number 39: Utopia + FlipEffect = 24, + Pendulum = 25,// Flash Knight + PendulumEffect = 26,// Stargazer Magician + EffectSp = 27,// Larvae Moth + ToonEffectSp = 28,// Manga Ryu-Ran (Sp = special summon "This monster cannot be Normal Summoned or Set") + SpiritEffectSp = 29,// Yamato-no-Kami + TunerEffectSp = 30,// Trap Eater + DarkTunerEffectSp = 31,// unused + FlipTunerEffect = 32,// Shaddoll Falco + PendulumTunerEffect = 33,// "Luster Pendulum, the Dracoslayer" + XyzPendulumEffect = 34,// Odd-Eyes Rebellion Dragon + PendulumFlipEffect = 35,// Performapal Momoncarpet + //SynchoPendulumEffect = 36,// unused + //UnionTunerEffect = 37,// unused + //RitualSpiritEffect = 38,// unused + //_______ = 39,// unused - underscores?? + + // These values are used for tagdata/taginfo + AnyNormal = 37,// NORMAL* + AnySynchro = 38,// SYNC* + AnyXyz = 39,// XYZ* + AnyTuner = 40,// TUNER* + AnyFusion = 41,// FUSION* + AnyRitual = 42,// RITUAL* + AnyPendulum = 43,// PEND* + AnyFlip = 44,// FLIP* + } + + /// + /// Flags version of CardType for easier checking of individual types + /// + [Flags] + public enum CardTypeFlags : uint + { + Default = 0, + Effect = 1 << 0, + Fusion = 1 << 1, + Ritual = 1 << 2, + Toon = 1 << 3, + Spirit = 1 << 4, + Union = 1 << 5, + Gemini = 1 << 6, + Token = 1 << 7, + Spell = 1 << 8, + Trap = 1 << 9, + Tuner = 1 << 10, + DarkTuner = 1 << 11, + DarkSynchro = 1 << 12, + Synchro = 1 << 13, + Xyz = 1 << 14, + Flip = 1 << 15, + Pendulum = 1 << 16, + SpecialSummon = 1 << 17,// "This monster cannot be Normal Summoned or Set" + + Link = 1 << 18,// Not in LOTD + + Normal = 1 << 19,// Special flag used for finding related cards + Any = 1 << 20,// Special flag used for finding related cards + } + + public enum MonsterType + { + Unknown = 0, + Dragon = 1, + Zombie = 2, + Fiend = 3, + Pyro = 4, + SeaSerpent = 5, + Rock = 6, + Machine = 7, + Fish = 8, + Dinosaur = 9, + Insect = 10, + Beast = 11, + BeastWarrior = 12, + Plant = 13, + Aqua = 14, + Warrior = 15, + WingedBeast = 16, + Fairy = 17, + Spellcaster = 18, + Thunder = 19, + Reptile = 20, + Psychic = 21, + Wyrm = 22, + DivineBeast = 23, + CreatorGod = 24,// This does't appear on any card in the game - its meant for "Holacite the Creator of Light" + Spell = 25, + Trap = 26 + } + + /// + /// Also known as "Property" - the type of spell / trap card + /// + public enum SpellType + { + Normal = 0, + + /// + /// Counter trap cards are a unique trap card type that are of spell speed 3. + /// + Counter = 1, + + Field = 2, + Equip = 3, + Continuous = 4, + QuickPlay = 5, + Ritual = 6 + } + + public enum CardFrameType + { + Normal, + Effect, + Token, + Ritual, + Fusion, + PendulumEffect, + PendulumNormal, + PendulumSynchro, + PendulumXyz, + Synchro, + Xyz, + Spell, + Trap, + } + + public enum CardLimitation + { + NotLimited, + Forbidden, + Limited, + SemiLimited + } + + [Flags] + public enum CardGenre : ulong + { + None = 0, + RecoverLP = 1UL << 0,//0x0000000000000001 ICON_ID_GENRE_LPUP + DamageLP = 1UL << 1,//0x0000000000000002 ICON_ID_GENRE_LPDOWN + HelpDraw = 1UL << 2,//0x0000000000000004 ICON_ID_GENRE_DRAW + SpecialSummon = 1UL << 3,//0x0000000000000008 ICON_ID_GENRE_SPSUMMON + NegateEffect = 1UL << 4,//0x0000000000000010 ICON_ID_GENRE_DISABLE + SearchDeck = 1UL << 5,//0x0000000000000020 ICON_ID_GENRE_DECKSEARCH + RecoverFromGraveyard = 1UL << 6,//0x0000000000000040 ICON_ID_GENRE_USEGRAVE + IncreaseDecreaseAtkDef = 1UL << 7,//0x0000000000000080 ICON_ID_GENRE_POWER + ChangeBattlePosition = 1UL << 8,//0x0000000000000100 ICON_ID_GENRE_POSITION + SetControls = 1UL << 9,//0x0000000000000200 ICON_ID_GENRE_CONTROL + DestroyMonster = 1UL << 10,//0x0000000000000400 ICON_ID_GENRE_BREAKMONST + DestroySpellCard = 1UL << 11,//0x0000000000000800 ICON_ID_GENRE_BREAKMAGIC + DestroyHand = 1UL << 12,//0x0000000000001000 ICON_ID_GENRE_HANDDES + DestroyDeck = 1UL << 13,//0x0000000000002000 ICON_ID_GENRE_DECKDES + RemoveCard = 1UL << 14,//0x0000000000004000 ICON_ID_GENRE_REMOVECARD + ReturnCard = 1UL << 15,//0x0000000000008000 ICON_ID_GENRE_CARDBACK + Piercing = 1UL << 16,//0x0000000000010000 ICON_ID_GENRE_SPEAR + DirectAttack = 1UL << 17,//0x0000000000020000 ICON_ID_GENRE_DIRECTATK + AttackMultipleTimes = 1UL << 18,//0x0000000000040000 ICON_ID_GENRE_MANYATK + CannotBeDestroyed = 1UL << 19,//0x0000000000080000 ICON_ID_GENRE_UNBREAK + LimitAttack = 1UL << 20,//0x0000000000100000 ICON_ID_GENRE_LIMITATK + CannotNormalSummon = 1UL << 21,//0x0000000000200000 ICON_ID_GENRE_CANTSUMMON + FlipEffectMonster = 1UL << 22,//0x0000000000400000 ICON_ID_GENRE_REVERSE + ToonMonster = 1UL << 23,//0x0000000000800000 ICON_ID_GENRE_TOON + SpiritMonster = 1UL << 24,//0x0000000001000000 ICON_ID_GENRE_SPIRIT + UnionMonster = 1UL << 25,//0x0000000002000000 ICON_ID_GENRE_UNION + GeminiMonster = 1UL << 26,//0x0000000004000000 ICON_ID_GENRE_DUAL + LvMonster = 1UL << 27,//0x0000000008000000 ICON_ID_GENRE_LEVELUP + Original = 1UL << 28,//0x0000000010000000 ICON_ID_GENRE_ORIGINAL + FusionMaterialMonster = 1UL << 29,//0x0000000020000000 ICON_ID_GENRE_FUSION + Ritual = 1UL << 30,//0x0000000040000000 ICON_ID_GENRE_RITUAL + Token = 1UL << 31,//0x0000000080000000 ICON_ID_GENRE_TOKEN + Counter = 1UL << 32,//0x0000000100000000 ICON_ID_GENRE_COUNTER + Gamble = 1UL << 33,//0x0000000200000000 ICON_ID_GENRE_GAMBLE + AttributeRelated = 1UL << 34,//0x0000000400000000 ICON_ID_GENRE_ATTR + TypeRelated = 1UL << 35,//0x0000000800000000 ICON_ID_GENRE_TYPE + Tuner = 1UL << 36,//0x0000001000000000 ICON_ID_GENRE_TUNER + SynchroMonster = 1UL << 37,//0x0000002000000000 ICON_ID_GENRE_SYNC + SendToGraveyard = 1UL << 38,//0x0000004000000000 ICON_ID_GENRE_DROPGRAVE + + // These values don't visibly appear in the game + NormalMonsterRelated = 1UL << 39,//0x0000008000000000 ICON_ID_GENRE_NORMAL + LightMonsterRelated = 1UL << 40,//0x0000010000000000 ICON_ID_GENRE_ATTR_LIGHT + DarkMonsterRelated = 1UL << 41,//0x0000020000000000 ICON_ID_GENRE_ATTR_DARK + EarthMonsterRelated = 1UL << 42,//0x0000040000000000 ICON_ID_GENRE_ATTR_EARTH + WaterMonsterRelated = 1UL << 43,//0x0000080000000000 ICON_ID_GENRE_ATTR_WATER + FireMonsterRelated = 1UL << 44,//0x0000100000000000 ICON_ID_GENRE_ATTR_FIRE + WindMonsterRelated = 1UL << 45,//0x0000200000000000 ICON_ID_GENRE_ATTR_WIND + + XyzMonster = 1UL << 46,//0x0000400000000000 ICON_ID_GENRE_XYZ + LevelModifier = 1UL << 47,//0x0000800000000000 ICON_ID_GENRE_LVUPDOWN + Pendulum = 1UL << 48,//0x0001000000000000 ICON_ID_GENRE_PENDULUM + + // These values aren't on any cards (but appear in game if you force them on a card) + DivineAttribute = 1UL << 49,//0x0002000000000000 ICON_ID_GENRE_ATTR_GOD + NewCard = 1UL << 50,//0x0004000000000000 ICON_ID_GENRE_NEW + GameOriginal = 1UL << 51,//0x0008000000000000 ICON_ID_GENRE_GAME_ORIGINAL + CardVaritation = 1UL << 52,//0x0010000000000000 ICON_ID_GENRE_PICTURE (assumed) + // + // The game uses broken icons for those values: + //DivineAttribute = ICON_ID_ORDER_ASCENDING + //NewCard = ICON_ID_ORDER_DESCENDING + //GameOriginal = ICON_ID_SEARCH + //CardVaritation = ICON_ID_DUEL_MENU_PHASE + + //Unused8 = 1UL << 53,//0x0020000000000000 + //Unused9 = 1UL << 54,//0x0040000000000000 + //Unused10 = 1UL << 55,//0x0080000000000000 + //Unused11 = 1UL << 56,//0x0100000000000000 + //Unused12 = 1UL << 57,//0x0200000000000000 + //Unused12 = 1UL << 58,//0x0400000000000000 + //Unused13 = 1UL << 59,//0x0800000000000000 + //Unused14 = 1UL << 60,//0x1000000000000000 + //Unused15 = 1UL << 61,//0x2000000000000000 + //Unused16 = 1UL << 62,//0x4000000000000000 + //Unused17 = 1UL << 63,//0x8000000000000000 + } + + public enum CardNameType + { + Null, + Toon, + Demon, + Keeper, + Guardian, + Scorpion, + Amazoness, + Ninja, + Level, + EHERO, + DHERO, + NeosMaterial, + NeosFusion, + Neos, + Ojama, + Battery, + DarkWorld, + BES, + Antique, + Sphinx, + Machiners, + Harpie, + Roid, + Vehicloid, + Neospacian, + Cocoon, + Alien, + Mythical, + HERO, + Allure, + Gadget, + Six, + Jewel, + Volcanic, + BlazeCanon, + Venom, + Cloudian, + Gladial, + Weapon, + Takemitsu, + EvHERO, + Drunk, + Arcana, + Fossil, + Gunner, + Forbidden, + Rainbow, + CyberFusion, + Icebarrier, + AOJ, + Saber, + Worm, + LightLord, + Frog, + Nitro, + Genex, + MistValley, + Flamebell, + NeosNHERO, + Deformer, + Chain, + Natul, + Clear, + RedEyes, + BlackFeather, + SlashBuster, + Roaring, + Jurac, + RealGenex, + EarthbindGod, + Koakimail, + Infernity, + X_Saber, + FortuneLady, + Dragnity, + FortuneWitch, + Synchron, + Saviour, + Reptiles, + Shien, + Junk, + Tomabo, + Sin, + Gem, + GemKnight, + Laval, + Vailon, + Scrap, + Eleki, + Fusion, + Infinity, + Wisel, + TG, + Karakuri, + Ritua, + Gusta, + Invelds, + Reactor, + Agent, + Polestar, + PolestarBeast, + PolestarGhost, + PolestarAngel, + PolestarItem, + PoleGod, + SoundWarrior, + Resonator, + MHERO, + VHERO, + Meklord_Emp, + Meklord_Sld, + Meklord, + Zenmai, + Penguin, + Evold, + Evolder, + TrapHole, + TimeGod, + Sacred, + Velds, + Numbers, + Gagaga, + Gogogo, + Photon, + Ninjutsu, + Inzector, + Invasion, + Bouncer, + Butterfly, + HolySeal, + Majin, + Heroic, + Ooparts, + Spellbook, + Madolce, + Geargear, + Xyz, + Poseidon, + Mermail, + Abyss, + Magical, + Nimble, + Duston, + Medallion, + NobleKnight, + FireKing, + Galaxy, + HolySword, + FireStar, + FireDance, + HazeBeast, + Haze, + ZexalWeapon, + Hope, + GimmickPuppet, + Dododo, + BK, + PhantomMek, + FireKingBeast, + ChaosNumbers, + ChaosXyz, + Geargearno, + SDRobo, + SDRobo2, + Umbral, + HolyLightning, + Bujin, + Kowakuma, + Hole, + CNo39, + H_Challenger, + Malicebolus, + Ghostrick, + Vampire, + Cat, + CyberDragon, + Cybernetic, + Shinra, + Necrovalley, + Zubaba, + Fishborg, + RUM, + Medallion2, + Artifact, + Evolkaiser, + GalaxyEyes, + Tachyon, + Over100, + Wizard, + OddEyes, + LegendDragon, + LegendKnight, + WingedKuriboh, + Stardust, + Sprout, + Artorius, + Lancelot, + Superheavy, + Genso, + Tellarknight, + Shadoll, + DragonStar, + EM, + Change, + Higan, + UA, + DD, + DDD, + Furnimal, + Deathtoy, + Qliphot, + Bunborg, + Goblin, + Cthulhu, + Contract, + Gottoms, + Yosen, + Necroth, + Spirit_All, + Spirit_Tamer, + Spirit_Beast, + RR, + Infernoid, + Jinzo, + Gaia, + Monarch, + Charmer, + Possessed, + Crystal, + Warrior, + PowerTool, + BMG, + EdgeImp, + Sephira, + GensoPrincess, + Spirit_Rider, + Stellarknight, + Void, + Em, + Dragonsword, + Igknight, + Aroma, + Empowered, + AetherWeapon, + FortunePrince, + Aquaactress, + Aquarium, + ChaosSoldier, + Majespecter, + Gradle, + SOz, + Kaiju, + SR, + PSYFrame, + RedDemon, + Burgestoma, + Dante, + BusterBlader, + BusterSword, + Dynamist, + Shiranui, + Dragondevil, + Exodia, + PhantomKnight, + Phantom, + Super, + Super_Quantum, + Super_Machine, + BlueEyes, + HopeX, + Moonlight, + Amorphage, + ElfSwordsman, + MagicianGirl, + BlackMagic, + Metalphose, + Tramid, + ABF, + Houkai, + Chaos, + CyberAngel, + Cypher, + Cardian, + SilentSword, + SilentMagic, + MagnetWarrior, + BlackMagic2, + Kuriboh, + Crystron, + Kagoju, + ApoQliphot, + Chichukai, + ChichukaiRyu, + Spyral, + SpyralGear, + MakaiGekidan, + MakaiDaihon, + FallenAngel, + WW, + Beast12, + PendDragon + } + + public class RelatedCardInfo + { + /// + /// The related card + /// + public CardInfo Card { get; set; } + + /// + /// The relationship tag info + /// + public CardTagInfo TagInfo { get; set; } + + public RelatedCardInfo(CardInfo card, CardTagInfo tagInfo) + { + Card = card; + TagInfo = tagInfo; + } + } + + /// + /// Describes how cards can be tagged / related to one another + /// + public class CardTagInfo + { + /// + /// The index of this tag info in the tags collection + /// + public int Index { get; set; } + + public ExactType Exact { get; set; } + public CardEffectType CardEffect { get; set; } + public CardInfo ExactCard { get; set; } + + /// + /// The main type of this tag - priority seems to be 0,1,2 + /// + public Type MainType { get; set; } + + /// + /// Unknown - some kind of sub priority? + /// + public short MainValue { get; set; } + + /// + /// The elements which make up this tag info + /// + public Element[] Elements { get; set; } + + /// + /// The string representation + /// + public LocalizedText Text { get; private set; } + + /// + /// The string respresentation which is displayed at the bottom of the relationship window + /// + public LocalizedText DisplayText { get; private set; } + + public CardTagInfo() + { + Elements = new Element[8]; + Text = new LocalizedText(); + DisplayText = new LocalizedText(); + } + + /// + /// The main type of the tag info + /// + public enum Type + { + /// + /// An exact relationship of some kind e.g. "Card effect: Negate Attack", "Related to: Exodia the Forbidden One" + /// + Exact = 0, + + /// + /// Some kind of impact on the card + /// + Ad = 1, + + /// + /// Finding other cards e.g. "Summon 1 Level 4 or lower Gemini monster from your hand." - "{FIND}KIND:DUAL LEVEL:<=4" + /// + Find = 2, + + /// + /// Same as Ad but for only XYZ + /// + AdXyz = 257, + + /// + /// Same as Find but only for XYZ + /// + FindXyz = 258 + } + + public enum ElementType + { + None = -1, + AtkLessThanOrEquals = 0, + DefLessThanOrEquals = 2, + LevelLessThanOrEquals = 4, + RankLessThanOrEquals = 8, + AtkLessThan = 256, + Attribute = 513,// This should map into the enum CardAttribute + AtkEquals = 512, + DefEquals = 514, + CardType = 515,// XYZ/Monster/Effect/etc - should map into CardType + LevelEquals = 516, + SpellType = 517,// This should map into enum SpellType + MonsterType = 518,// This should map into enum MonsterType + DeckType = 519, + RankEquals = 520,// For XYZ monsters + SpecialSummon = 521, + Tribute = 522, + Tribute2 = 523,// This monster counts as 2 tribute summons + AtkGreaterThanOrEquals = 768, + LevelGreaterThanOrEquals = 772, + RankGreaterThanOrEquals = 776, + } + + public struct Element + { + public ElementType Type; + public short Value; + } + + /// + /// Defines what an "Exact" tag info type does + /// + public enum ExactType + { + None, + + /// + /// Related to an archetype / exact card + /// + RelatedTo, + + /// + /// Related to a fusion monster + /// + FusionMonster, + + /// + /// Related to a ritual monster + /// + RitualMonster, + + /// + /// A spell / trap card effect + /// + SpellTrap, + + /// + /// Monster card effect + /// + CardEffect, + + WorksWellWith, + + /// + /// Monsters that use Spell Counters + /// + SpellCounter, + + + /// + /// Fairy-Type effects that trigger with Counter Traps + /// + CounterTrapFairy, + + /// + /// Banished Beast and Winged-Beast Type monsters + /// + BanishBeast, + /// + /// Banished Dark monsters + /// + BanishDark, + /// + /// Banished Fish, Sea Serpent, and Aqua-Type monsters + /// + BanishFish, + /// + /// Banished Rock-Type monsters + /// + BanishRock + } + + public enum CardEffectType + { + None, + AntiAttack, + AntiDefense, + AntiDiscard, + AntiDraw, + AntiEffectDamage, + AntiFaceDown, + AntiMonsterEffect, + AntiPendulum, + AntiSpell, + AntiTrap, + ATKReduction, + AttackDirectly, + AttributeDestruction, + AttributeEquipBoost, + BanishOpp, + BanishPlayer, + BoostNormal, + BurnDamageAtk, + BurnDamageCont, + BurnDamageDirect, + BurnDamageMons, + BurnDamageTrib, + CannotAttack, + CannotChangePosition, + CardDiscard, + CardDraw, + ChangeLevel, + ChooseAttackTarget, + CoinToss, + Combo, + ContinuousSpellTrib, + DarkCardDraw, + DEFGain, + DEFReduction, + DestroyType, + Dice, + DragonBoost, + EquipDragon, + EquipFairy, + EquipMachine, + EquipSpellcaster, + EquipWarrior, + FaceUp, + FieldPowerAttr, + FieldPowerType, + FlipFaceDown, + Fusion, + GiveControl, + GraveToHandMonster, + GraveToHandSpellTrap, + IceCount, + LargeATKGainEquip, + LifeGain, + LookAtDeck, + LookAtHand, + Mill, + NegateAttack, + RecycleToDeck, + RestrictMonster, + Ritual, + SameAttributeBoost, + Simochi, + SpellTrapProtect, + SSGraveyard, + SSZombie, + StopFlipNormalSummon, + StopSpecialSummon, + SwitchATKDEF, + SynchroFusion, + SynchroMaterial, + TakeControl, + ToGraveyard, + Token, + TokenOpponent, + TrapMonster, + Ultimaya, + ZoneDeny, + + // Spell/trap values + Spell_DoubleSummon, + Spell_MonsterDestruction, + Spell_MonsterProtect, + Spell_Piercing, + Spell_PreventBattleDamage, + Spell_QuickATKboost, + Spell_SSHandDeck, + Spell_StopFusion, + Spell_StopRitual, + Spell_StopSynchro, + Spell_StopTribute, + Spell_StopXyz + } + } + + public enum DeckType + { + Main = 0, + Extra = 1, + Side = 2 + } +} diff --git a/Lotd/FileFormats/bin/RelatedCardData.cs b/Lotd/FileFormats/bin/RelatedCardData.cs new file mode 100644 index 0000000..df6a1a3 --- /dev/null +++ b/Lotd/FileFormats/bin/RelatedCardData.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Lotd.FileFormats +{ + /// + /// Holds a list of related cards for each card. This information is displayed in the panel on the right side of the screen + /// in the deck editor. This is tagdata.bin + /// + public class RelatedCardData : FileData + { + /// + /// List of related cards. + /// The first list represents a card index (not the card id). + /// The second list represents the related cards for that index. + /// + public List> Items { get; private set; } + + public RelatedCardData() + { + Items = new List>(); + } + + public override void Load(BinaryReader reader, long length) + { + Clear(); + + // There doesn't seem to be any identifier which says how many items to read. Assuming it reads until known max cards. + int numCards = Constants.NumCards; + + long dataStart = reader.BaseStream.Position + (numCards * 8); + + for (int i = 0; i < numCards; i++) + { + uint shortoffset = reader.ReadUInt32(); + uint tagCount = reader.ReadUInt32(); + + long tempOffset = reader.BaseStream.Position; + + long start = dataStart + (shortoffset * 4); + reader.BaseStream.Position = start; + + List items = new List(); + for (int j = 0; j < tagCount; j++) + { + items.Add(new Item(reader.ReadUInt16(), reader.ReadUInt16())); + } + Items.Add(items); + + reader.BaseStream.Position = tempOffset; + } + } + + public override void Save(BinaryWriter writer) + { + uint shortOffset = 0; + for (int i = 0; i < Items.Count; i++) + { + writer.Write(shortOffset); + writer.Write(Items[i].Count); + shortOffset += (uint)Items[i].Count + 1; + } + + for (int i = 0; i < Items.Count; i++) + { + foreach (Item item in Items[i]) + { + writer.Write(item.CardId); + writer.Write(item.TagIndex); + } + writer.Write(0); + } + } + + public override void Clear() + { + Items.Clear(); + } + + public class Item + { + public ushort CardId { get; set; } + + /// + /// An index into taginfo_X.bin + /// + public ushort TagIndex { get; set; } + + public Item(ushort cardId, ushort tagIndex) + { + CardId = cardId; + TagIndex = tagIndex; + } + } + } +}