1932 lines
No EOL
99 KiB
C#
1932 lines
No EOL
99 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Decal.Adapter;
|
|
using Decal.Adapter.Wrappers;
|
|
using Decal.Filters;
|
|
using Mag.Shared.Constants;
|
|
|
|
namespace MosswartMassacre
|
|
{
|
|
/// <summary>
|
|
/// Data management class for Flag Tracker
|
|
/// Ported from UBS Lua flagtracker data structures
|
|
/// </summary>
|
|
public class FlagTrackerData : IDisposable
|
|
{
|
|
public FlagTrackerData()
|
|
{
|
|
InitializeDataStructures();
|
|
}
|
|
#region Augmentation Data Structures
|
|
public class AugmentationInfo
|
|
{
|
|
public string Name { get; set; }
|
|
public int? IntId { get; set; }
|
|
public int Repeatable { get; set; }
|
|
public string Trainer { get; set; }
|
|
public string Location { get; set; }
|
|
public int CurrentValue { get; set; }
|
|
public bool IsMaxed => CurrentValue >= Repeatable;
|
|
}
|
|
|
|
public Dictionary<string, List<AugmentationInfo>> AugmentationCategories { get; private set; }
|
|
|
|
#endregion
|
|
|
|
#region Luminance Aura Data Structures
|
|
public class LuminanceAuraInfo
|
|
{
|
|
public string Name { get; set; }
|
|
public int IntId { get; set; }
|
|
public int Cap { get; set; }
|
|
public string QuestFlag { get; set; } // For Seer auras only
|
|
public int CurrentValue { get; set; }
|
|
}
|
|
|
|
public Dictionary<string, List<LuminanceAuraInfo>> LuminanceAuraCategories { get; private set; }
|
|
#endregion
|
|
|
|
#region Recall Spell Data Structures
|
|
public class RecallSpellInfo
|
|
{
|
|
public string Name { get; set; }
|
|
public int SpellId { get; set; }
|
|
public bool IsKnown { get; set; }
|
|
public int IconId { get; set; }
|
|
public string Category { get; set; }
|
|
}
|
|
|
|
public List<RecallSpellInfo> RecallSpells { get; private set; }
|
|
#endregion
|
|
|
|
#region Society Quest Data Structures
|
|
public class SocietyQuestInfo
|
|
{
|
|
public string Name { get; set; }
|
|
public string StartTag { get; set; }
|
|
public string EndTag { get; set; }
|
|
public QuestType Type { get; set; }
|
|
public object[] ExtraData { get; set; } // For type-specific data
|
|
}
|
|
|
|
public enum QuestType
|
|
{
|
|
Other = 0,
|
|
KillTask = 1,
|
|
CollectItem = 2,
|
|
QuestTag = 3,
|
|
MultiQuestTag = 4
|
|
}
|
|
|
|
public Dictionary<string, List<SocietyQuestInfo>> SocietyQuests { get; private set; }
|
|
#endregion
|
|
|
|
#region Character Flag Data Structures
|
|
public class CharacterFlagInfo
|
|
{
|
|
public string Name { get; set; }
|
|
public string QuestFlag { get; set; }
|
|
public QuestInfoType InfoType { get; set; }
|
|
}
|
|
|
|
public enum QuestInfoType
|
|
{
|
|
SolveCount = 1,
|
|
ReadyCheck = 2,
|
|
StampCheck = 3
|
|
}
|
|
|
|
public Dictionary<string, List<CharacterFlagInfo>> CharacterFlags { get; private set; }
|
|
#endregion
|
|
|
|
#region Cantrip Data Structures
|
|
public class CantripInfo
|
|
{
|
|
public string Name { get; set; }
|
|
public string Value { get; set; } // "Minor", "Major", "Epic", etc.
|
|
public System.Drawing.Color Color { get; set; }
|
|
public int? IconId { get; set; } // Skill icon from character skills
|
|
public int? SpellIconId { get; set; } // Spell icon (for attributes)
|
|
public int? BackgroundIconId { get; set; } // Background icon (for attributes)
|
|
public int ComputedIconId { get; set; } // Final resolved icon for display
|
|
}
|
|
|
|
public Dictionary<string, Dictionary<string, CantripInfo>> Cantrips { get; private set; }
|
|
|
|
// Skill name mappings for cantrips that have different names than their skills
|
|
private readonly Dictionary<int, string> SkillCantripReplacements = new Dictionary<int, string>
|
|
{
|
|
[15] = "MagicResistance", // MagicDefense
|
|
[6] = "Invulnerability", // MeleeDefense
|
|
[7] = "Impgrenability", // MissileDefense
|
|
[47] = "MissileWeapon", // MissileWeapons
|
|
[44] = "HeavyWeapon", // HeavyWeapons
|
|
[45] = "LightWeapon", // LightWeapons
|
|
[46] = "FinesseWeapon" // FinesseWeapons
|
|
};
|
|
#endregion
|
|
|
|
#region Weapon Data Structures (Legacy - to be removed)
|
|
// Old weapon structure - keeping for compatibility but unused
|
|
#endregion
|
|
|
|
#region Cached Data
|
|
private DateTime lastUpdateTime = DateTime.MinValue;
|
|
private Dictionary<string, object> cachedValues = new Dictionary<string, object>();
|
|
#endregion
|
|
|
|
#region Initialization
|
|
private void InitializeDataStructures()
|
|
{
|
|
InitializeAugmentationData();
|
|
InitializeLuminanceAuraData();
|
|
InitializeRecallSpellData();
|
|
InitializeSocietyQuestData();
|
|
InitializeCharacterFlagData();
|
|
InitializeCantripData();
|
|
InitializeNewWeaponData();
|
|
}
|
|
|
|
private void InitializeAugmentationData()
|
|
{
|
|
AugmentationCategories = new Dictionary<string, List<AugmentationInfo>>
|
|
{
|
|
["Death Augs"] = new List<AugmentationInfo>
|
|
{
|
|
new AugmentationInfo { Name = "Keep Items", IntId = 231, Repeatable = 3, Trainer = "Rohula bint Ludun", Location = "Ayan Baqur" }, // AugmentationLessDeathItemLoss
|
|
new AugmentationInfo { Name = "Keep Spells", IntId = 232, Repeatable = 1, Trainer = "Erik Festus", Location = "Ayan Baqur" } // AugmentationSpellsRemainPastDeath
|
|
},
|
|
["Skill Augs"] = new List<AugmentationInfo>
|
|
{
|
|
new AugmentationInfo { Name = "+5 All Skills", IntId = 326, Repeatable = 1, Trainer = "Arianna the Adept", Location = "Bandit Castle" }, // AugmentationJackOfAllTrades
|
|
new AugmentationInfo { Name = "+10 Melee Skills", IntId = 300, Repeatable = 1, Trainer = "Carlito Gallo", Location = "Silyun" }, // AugmentationSkilledMelee
|
|
new AugmentationInfo { Name = "+10 Magic Skills", IntId = 302, Repeatable = 1, Trainer = "Rahina bint Zalanis", Location = "Zaikhal" }, // AugmentationSkilledMagic
|
|
new AugmentationInfo { Name = "+10 Missile Skills", IntId = 301, Repeatable = 1, Trainer = "Kilaf", Location = "Zaikhal" } // AugmentationSkilledMissile
|
|
},
|
|
["Rating Augs"] = new List<AugmentationInfo>
|
|
{
|
|
new AugmentationInfo { Name = "25% Crit Protection", IntId = 233, Repeatable = 1, Trainer = "Piersanti Linante", Location = "Sanamar" }, // AugmentationCriticalDefense
|
|
new AugmentationInfo { Name = "1% Critical Chance", IntId = 298, Repeatable = 1, Trainer = "Anfram Mellow", Location = "Ayan Baqur" }, // AugmentationCriticalExpertise
|
|
new AugmentationInfo { Name = "3% Critical Damage", IntId = 299, Repeatable = 1, Trainer = "Alishia bint Aldan", Location = "Ayan Baqur" }, // AugmentationCriticalPower
|
|
new AugmentationInfo { Name = "3% Damage Rating", IntId = 309, Repeatable = 1, Trainer = "Neela Nashua", Location = "Bandit Castle" }, // AugmentationDamageBonus
|
|
new AugmentationInfo { Name = "3% Damage Reduction", IntId = 310, Repeatable = 1, Trainer = "Emily Yarow", Location = "Cragstone" } // AugmentationDamageReduction
|
|
},
|
|
["Burden / Pack Augs"] = new List<AugmentationInfo>
|
|
{
|
|
new AugmentationInfo { Name = "Extra Carrying Capacity", IntId = 230, Repeatable = 5, Trainer = "Husoon", Location = "Zaikhal" }, // AugmentationIncreasedCarryingCapacity
|
|
new AugmentationInfo { Name = "Extra Pack Slot", IntId = 229, Repeatable = 1, Trainer = "Dumida bint Ruminre", Location = "Zaikhal" }, // AugmentationExtraPackSlot
|
|
new AugmentationInfo { Name = "Infused War Magic", IntId = 297, Repeatable = 1, Trainer = "Raphel Detante", Location = "Silyun" }, // AugmentationInfusedWarMagic
|
|
new AugmentationInfo { Name = "Infused Void Magic", IntId = 328, Repeatable = 1, Trainer = "Morathe", Location = "Candeth Keep" }, // AugmentationInfusedVoidMagic
|
|
new AugmentationInfo { Name = "Infused Creature Magic", IntId = 294, Repeatable = 1, Trainer = "Gustuv Lansdown", Location = "Cragstone" }, // AugmentationInfusedCreatureMagic
|
|
new AugmentationInfo { Name = "Infused Life Magic", IntId = 296, Repeatable = 1, Trainer = "Akemi Fei", Location = "Hebian-To" }, // AugmentationInfusedLifeMagic
|
|
new AugmentationInfo { Name = "Infused Item Magic", IntId = 295, Repeatable = 1, Trainer = "Gan Fo", Location = "Hebian-To" } // AugmentationInfusedItemMagic
|
|
},
|
|
["Misc Augs"] = new List<AugmentationInfo>
|
|
{
|
|
new AugmentationInfo { Name = "10% Health Increase", IntId = null, Repeatable = 1, Trainer = "Donatello Linante", Location = "Silyun" }, // No specific property
|
|
new AugmentationInfo { Name = "Increased Spell Duration", IntId = 238, Repeatable = 5, Trainer = "Nawamara Ujio", Location = "Mayoi" }, // AugmentationIncreasedSpellDuration
|
|
new AugmentationInfo { Name = "Faster HP Regen", IntId = 237, Repeatable = 2, Trainer = "Alison Dulane", Location = "Bandit Castle" }, // AugmentationFasterRegen
|
|
new AugmentationInfo { Name = "5% Experience Increase", IntId = 234, Repeatable = 1, Trainer = "Rickard Dumalia", Location = "Silyun" } // AugmentationBonusXp
|
|
},
|
|
["Salvage Augs"] = new List<AugmentationInfo>
|
|
{
|
|
new AugmentationInfo { Name = "Specialized Weapon Tinkering", IntId = 228, Repeatable = 1, Trainer = "Lenor Turk", Location = "Cragstone" }, // AugmentationSpecializeWeaponTinkering
|
|
new AugmentationInfo { Name = "Specialized Armor Tinkering", IntId = 226, Repeatable = 1, Trainer = "Joshun Felden", Location = "Cragstone" }, // AugmentationSpecializeArmorTinkering
|
|
new AugmentationInfo { Name = "Specialized Item Tinkering", IntId = 225, Repeatable = 1, Trainer = "Brienne Carlus", Location = "Cragstone" }, // AugmentationSpecializeItemTinkering
|
|
new AugmentationInfo { Name = "Specialized Magic Item Tinkering", IntId = 227, Repeatable = 1, Trainer = "Burrell Sammrun", Location = "Cragstone" }, // AugmentationSpecializeMagicItemTinkering
|
|
new AugmentationInfo { Name = "Specialized Salvaging", IntId = 224, Repeatable = 1, Trainer = "Robert Crow", Location = "Cragstone" }, // AugmentationSpecializeSalvaging
|
|
new AugmentationInfo { Name = "25% More Salvage", IntId = 235, Repeatable = 4, Trainer = "Kris Cennis", Location = "Cragstone" }, // AugmentationBonusSalvage
|
|
new AugmentationInfo { Name = "5% Imbue Chance", IntId = 236, Repeatable = 1, Trainer = "Lug", Location = "Oolutanga's Refuge" } // AugmentationBonusImbueChance
|
|
},
|
|
["Stat Augs"] = new List<AugmentationInfo>
|
|
{
|
|
new AugmentationInfo { Name = "All Stats", IntId = 217, Repeatable = 10, Trainer = "", Location = "" }, // AugmentationInnateFamily
|
|
new AugmentationInfo { Name = "Strength", IntId = 218, Repeatable = 10, Trainer = "Fiun Luunere", Location = "Fiun Outpost" }, // AugmentationInnateStrength
|
|
new AugmentationInfo { Name = "Endurance", IntId = 219, Repeatable = 10, Trainer = "Fiun Ruun", Location = "Fiun Outpost" }, // AugmentationInnateEndurance
|
|
new AugmentationInfo { Name = "Coordination", IntId = 220, Repeatable = 10, Trainer = "Fiun Bayaas", Location = "Fiun Outpost" }, // AugmentationInnateCoordination
|
|
new AugmentationInfo { Name = "Quickness", IntId = 221, Repeatable = 10, Trainer = "Fiun Riish", Location = "Fiun Outpost" }, // AugmentationInnateQuickness
|
|
new AugmentationInfo { Name = "Focus", IntId = 222, Repeatable = 10, Trainer = "Fiun Vasherr", Location = "Fiun Outpost" }, // AugmentationInnateFocus
|
|
new AugmentationInfo { Name = "Self", IntId = 223, Repeatable = 10, Trainer = "Fiun Noress", Location = "Fiun Outpost" } // AugmentationInnateSelf
|
|
},
|
|
["Resistance Augs"] = new List<AugmentationInfo>
|
|
{
|
|
new AugmentationInfo { Name = "All Resistances", IntId = 239, Repeatable = 2, Trainer = "", Location = "" }, // AugmentationResistanceFamily
|
|
new AugmentationInfo { Name = "Blunt", IntId = 242, Repeatable = 2, Trainer = "Nawamara Dia", Location = "Hebian-To" }, // AugmentationResistanceBlunt
|
|
new AugmentationInfo { Name = "Pierce", IntId = 241, Repeatable = 2, Trainer = "Kyujo Rujen", Location = "Hebian-To" }, // AugmentationResistancePierce
|
|
new AugmentationInfo { Name = "Slashing", IntId = 240, Repeatable = 2, Trainer = "Ilin Wis", Location = "Hebian-To" }, // AugmentationResistanceSlash
|
|
new AugmentationInfo { Name = "Fire", IntId = 244, Repeatable = 2, Trainer = "Rikshen Ri", Location = "Hebian-To" }, // AugmentationResistanceFire
|
|
new AugmentationInfo { Name = "Frost", IntId = 245, Repeatable = 2, Trainer = "Lu Bao", Location = "Hebian-To" }, // AugmentationResistanceFrost
|
|
new AugmentationInfo { Name = "Acid", IntId = 243, Repeatable = 2, Trainer = "Shujio Milao", Location = "Hebian-To" }, // AugmentationResistanceAcid
|
|
new AugmentationInfo { Name = "Lightning", IntId = 246, Repeatable = 2, Trainer = "Enli Yuo", Location = "Hebian-To" } // AugmentationResistanceLightning
|
|
}
|
|
};
|
|
}
|
|
|
|
private void InitializeLuminanceAuraData()
|
|
{
|
|
LuminanceAuraCategories = new Dictionary<string, List<LuminanceAuraInfo>>
|
|
{
|
|
["Nalicana Auras"] = new List<LuminanceAuraInfo>
|
|
{
|
|
new LuminanceAuraInfo { Name = "+1 Aetheria Proc Rating", IntId = 338, Cap = 5 }, // LumAugSurgeChanceRating
|
|
new LuminanceAuraInfo { Name = "+1 Damage Reduction Rating", IntId = 334, Cap = 5 }, // LumAugDamageReductionRating
|
|
new LuminanceAuraInfo { Name = "+1 Crit Reduction Rating", IntId = 336, Cap = 5 }, // LumAugCritReductionRating
|
|
new LuminanceAuraInfo { Name = "+1 Damage Rating", IntId = 333, Cap = 5 }, // LumAugDamageRating
|
|
new LuminanceAuraInfo { Name = "+1 Crit Damage Rating", IntId = 335, Cap = 5 }, // LumAugCritDamageRating
|
|
new LuminanceAuraInfo { Name = "+1 Heal Rating", IntId = 342, Cap = 5 }, // LumAugHealingRating
|
|
new LuminanceAuraInfo { Name = "+1 Equipment Mana Rating", IntId = 339, Cap = 5 }, // LumAugItemManaUsage
|
|
new LuminanceAuraInfo { Name = "+1 Mana Stone Rating", IntId = 340, Cap = 5 }, // LumAugItemManaGain
|
|
new LuminanceAuraInfo { Name = "+1 Crafting Skills", IntId = 343, Cap = 5 }, // LumAugSkilledCraft
|
|
new LuminanceAuraInfo { Name = "+1 All Skills", IntId = 365, Cap = 10 } // LumAugAllSkills
|
|
},
|
|
["Seer Auras"] = new List<LuminanceAuraInfo>
|
|
{
|
|
new LuminanceAuraInfo { Name = "(Ka'hiri) +2 Specialized Skills", IntId = 344, Cap = 5, QuestFlag = "LoyalToKahiri" }, // LumAugSkilledSpec
|
|
new LuminanceAuraInfo { Name = "(Ka'hiri) +1 Damage Rating", IntId = 333, Cap = 5, QuestFlag = "LoyalToKahiri" }, // LumAugDamageRating
|
|
new LuminanceAuraInfo { Name = "(Shade of Lady Adja) +2 Specialized Skills", IntId = 344, Cap = 5, QuestFlag = "LoyalToShadeOfLadyAdja" }, // LumAugSkilledSpec
|
|
new LuminanceAuraInfo { Name = "(Shade of Lady Adja) +1 Damage Reduction Rating", IntId = 334, Cap = 5, QuestFlag = "LoyalToShadeOfLadyAdja" }, // LumAugDamageReductionRating
|
|
new LuminanceAuraInfo { Name = "(Liam of Gelid) +1 Damage Rating", IntId = 333, Cap = 5, QuestFlag = "LoyalToLiamOfGelid" }, // LumAugDamageRating
|
|
new LuminanceAuraInfo { Name = "(Liam of Gelid) +1 Crit Damage Rating", IntId = 335, Cap = 5, QuestFlag = "LoyalToLiamOfGelid" }, // LumAugCritDamageRating
|
|
new LuminanceAuraInfo { Name = "(Lord Tyragar) +1 Crit Reduction Rating", IntId = 336, Cap = 5, QuestFlag = "LoyalToLordTyragar" }, // LumAugCritReductionRating
|
|
new LuminanceAuraInfo { Name = "(Lord Tyragar) +1 Damage Reduction Rating", IntId = 334, Cap = 5, QuestFlag = "LoyalToLordTyragar" } // LumAugDamageReductionRating
|
|
}
|
|
};
|
|
}
|
|
|
|
private void InitializeRecallSpellData()
|
|
{
|
|
RecallSpells = new List<RecallSpellInfo>
|
|
{
|
|
new RecallSpellInfo { Name = "Recall the Sanctuary", SpellId = 2023, IconId = 0, Category = "Basic Recalls" },
|
|
new RecallSpellInfo { Name = "Aerlinthe Recall", SpellId = 2041, IconId = 0, Category = "Island Recalls" },
|
|
new RecallSpellInfo { Name = "Mount Lethe Recall", SpellId = 2813, IconId = 0, Category = "Island Recalls" },
|
|
new RecallSpellInfo { Name = "Recall Aphus Lassel", SpellId = 2931, IconId = 0, Category = "Island Recalls" },
|
|
new RecallSpellInfo { Name = "Ulgrim's Recall", SpellId = 2941, IconId = 0, Category = "Special Recalls" },
|
|
new RecallSpellInfo { Name = "Recall to the Singularity Caul", SpellId = 2943, IconId = 0, Category = "Island Recalls" },
|
|
new RecallSpellInfo { Name = "Glenden Wood Recall", SpellId = 3865, IconId = 0, Category = "Town Recalls" },
|
|
new RecallSpellInfo { Name = "Bur Recall", SpellId = 4084, IconId = 0, Category = "Town Recalls" },
|
|
new RecallSpellInfo { Name = "Call of the Mhoire Forge", SpellId = 4128, IconId = 0, Category = "Special Recalls" },
|
|
new RecallSpellInfo { Name = "Paradox-touched Olthoi Infested Area Recall", SpellId = 4198, IconId = 0, Category = "Special Recalls" },
|
|
new RecallSpellInfo { Name = "Colosseum Recall", SpellId = 4213, IconId = 0, Category = "Special Recalls" },
|
|
new RecallSpellInfo { Name = "Return to the Keep", SpellId = 4214, IconId = 0, Category = "Special Recalls" },
|
|
new RecallSpellInfo { Name = "Gear Knight Invasion Area Camp Recall", SpellId = 5330, IconId = 0, Category = "Special Recalls" },
|
|
new RecallSpellInfo { Name = "Lost City of Neftet Recall", SpellId = 5541, IconId = 0, Category = "Special Recalls" },
|
|
new RecallSpellInfo { Name = "Rynthid Recall", SpellId = 6150, IconId = 0, Category = "Special Recalls" },
|
|
new RecallSpellInfo { Name = "Viridian Rise Recall", SpellId = 6321, IconId = 0, Category = "Special Recalls" },
|
|
new RecallSpellInfo { Name = "Viridian Rise Great Tree Recall", SpellId = 6322, IconId = 0, Category = "Special Recalls" }
|
|
};
|
|
}
|
|
|
|
private void InitializeSocietyQuestData()
|
|
{
|
|
SocietyQuests = new Dictionary<string, List<SocietyQuestInfo>>();
|
|
// TODO: Initialize society quest data from Lua
|
|
}
|
|
|
|
private void InitializeCharacterFlagData()
|
|
{
|
|
CharacterFlags = new Dictionary<string, List<CharacterFlagInfo>>
|
|
{
|
|
["Additional Skill Credits"] = new List<CharacterFlagInfo>
|
|
{
|
|
new CharacterFlagInfo { Name = "+1 Skill Lum Aura", QuestFlag = "lumaugskillquest", InfoType = QuestInfoType.SolveCount },
|
|
new CharacterFlagInfo { Name = "+1 Skill Aun Ralirea", QuestFlag = "arantahkill1", InfoType = QuestInfoType.SolveCount },
|
|
new CharacterFlagInfo { Name = "+1 Skill Chasing Oswald", QuestFlag = "oswaldmanualcompleted", InfoType = QuestInfoType.SolveCount }
|
|
},
|
|
["Aetheria"] = new List<CharacterFlagInfo>
|
|
{
|
|
new CharacterFlagInfo { Name = "Blue Aetheria (75)", QuestFlag = "efulcentermanafieldused", InfoType = QuestInfoType.StampCheck },
|
|
new CharacterFlagInfo { Name = "Yellow Aetheria (150)", QuestFlag = "efmlcentermanafieldused", InfoType = QuestInfoType.StampCheck },
|
|
new CharacterFlagInfo { Name = "Red Aetheria (225)", QuestFlag = "efllcentermanafieldused", InfoType = QuestInfoType.StampCheck }
|
|
}
|
|
// TODO: Add remaining character flag categories
|
|
};
|
|
}
|
|
|
|
private void InitializeCantripData()
|
|
{
|
|
Cantrips = new Dictionary<string, Dictionary<string, CantripInfo>>
|
|
{
|
|
["Specialized Skills"] = new Dictionary<string, CantripInfo>(), // Dynamically populated
|
|
["Trained Skills"] = new Dictionary<string, CantripInfo>(), // Dynamically populated
|
|
["Attributes"] = new Dictionary<string, CantripInfo>(), // Dynamically populated when cantrips are detected
|
|
["Protection Auras"] = new Dictionary<string, CantripInfo>
|
|
{
|
|
// Pre-populate all protection auras so they show as red when missing
|
|
["Armor"] = new CantripInfo { Name = "Armor", Value = "N/A", Color = System.Drawing.Color.White },
|
|
["Bludgeoning Ward"] = new CantripInfo { Name = "Bludgeoning Ward", Value = "N/A", Color = System.Drawing.Color.White },
|
|
["Piercing Ward"] = new CantripInfo { Name = "Piercing Ward", Value = "N/A", Color = System.Drawing.Color.White },
|
|
["Slashing Ward"] = new CantripInfo { Name = "Slashing Ward", Value = "N/A", Color = System.Drawing.Color.White },
|
|
["Flame Ward"] = new CantripInfo { Name = "Flame Ward", Value = "N/A", Color = System.Drawing.Color.White },
|
|
["Frost Ward"] = new CantripInfo { Name = "Frost Ward", Value = "N/A", Color = System.Drawing.Color.White },
|
|
["Acid Ward"] = new CantripInfo { Name = "Acid Ward", Value = "N/A", Color = System.Drawing.Color.White },
|
|
["Storm Ward"] = new CantripInfo { Name = "Storm Ward", Value = "N/A", Color = System.Drawing.Color.White }
|
|
}
|
|
};
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Refresh Methods
|
|
public void RefreshAll()
|
|
{
|
|
RefreshCachedData();
|
|
RefreshAugmentations();
|
|
RefreshLuminanceAuras();
|
|
RefreshRecallSpells();
|
|
RefreshSocietyQuests();
|
|
RefreshFacilityHubQuests();
|
|
RefreshCharacterFlags();
|
|
RefreshCantrips();
|
|
}
|
|
|
|
public void RefreshCachedData()
|
|
{
|
|
try
|
|
{
|
|
if (CoreManager.Current?.CharacterFilter?.Name != null)
|
|
{
|
|
// Update cached values
|
|
var character = CoreManager.Current.CharacterFilter;
|
|
lastUpdateTime = DateTime.Now;
|
|
|
|
// TODO: Implement cached data refresh
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error refreshing cached data: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public void RefreshAugmentations()
|
|
{
|
|
try
|
|
{
|
|
if (CoreManager.Current?.CharacterFilter?.Name == null) return;
|
|
|
|
var character = CoreManager.Current.CharacterFilter;
|
|
|
|
// Update augmentation values
|
|
foreach (var category in AugmentationCategories)
|
|
{
|
|
foreach (var aug in category.Value)
|
|
{
|
|
if (aug.IntId.HasValue)
|
|
{
|
|
// Get augmentation value from character data using DECAL API
|
|
try
|
|
{
|
|
if (CoreManager.Current?.CharacterFilter != null)
|
|
{
|
|
var characterFilter = CoreManager.Current.CharacterFilter;
|
|
var playerObject = CoreManager.Current.WorldFilter[characterFilter.Id];
|
|
|
|
if (playerObject != null)
|
|
{
|
|
// Use CharacterFilter.GetCharProperty for character properties
|
|
try
|
|
{
|
|
// DECAL API uses CharacterFilter.GetCharProperty for character properties
|
|
aug.CurrentValue = characterFilter.GetCharProperty(aug.IntId.Value);
|
|
}
|
|
catch
|
|
{
|
|
// Try alternative access using reflection
|
|
try
|
|
{
|
|
var valuesMethod = playerObject.GetType().GetMethod("Values", new Type[] { typeof(int) });
|
|
if (valuesMethod != null)
|
|
{
|
|
aug.CurrentValue = (int)valuesMethod.Invoke(playerObject, new object[] { aug.IntId.Value });
|
|
}
|
|
else
|
|
{
|
|
aug.CurrentValue = 0;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
aug.CurrentValue = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aug.CurrentValue = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aug.CurrentValue = 0;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
aug.CurrentValue = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Handle special case for Asheron's Lesser Benediction (inventory count)
|
|
if (aug.Name == "Asheron's Lesser Benediction")
|
|
{
|
|
// Count Asheron's Lesser Benediction items in inventory
|
|
aug.CurrentValue = CountAsheronsLesserBenediction();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error refreshing augmentations: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private int CountAsheronsLesserBenediction()
|
|
{
|
|
try
|
|
{
|
|
int count = 0;
|
|
|
|
// Search inventory for Asheron's Lesser Benediction
|
|
foreach (WorldObject item in CoreManager.Current.WorldFilter.GetInventory())
|
|
{
|
|
if (item.Name.Contains("Asheron's Lesser Benediction"))
|
|
{
|
|
// Use stack size for item count (default to 1 for non-stackable items)
|
|
try
|
|
{
|
|
// Access stack size using reflection to avoid type issues
|
|
var stackMethod = item.GetType().GetMethod("Values", new Type[] { typeof(int) });
|
|
if (stackMethod != null)
|
|
{
|
|
int stackSize = (int)stackMethod.Invoke(item, new object[] { 12 }); // 12 = StackSize
|
|
count += Math.Max(1, stackSize);
|
|
}
|
|
else
|
|
{
|
|
count += 1; // Default to 1
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
count += 1; // Default fallback
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error counting Asheron's Lesser Benediction: {ex.Message}");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public void RefreshLuminanceAuras()
|
|
{
|
|
try
|
|
{
|
|
if (CoreManager.Current?.CharacterFilter?.Name == null) return;
|
|
|
|
var characterFilter = CoreManager.Current.CharacterFilter;
|
|
|
|
// Update luminance aura values
|
|
foreach (var category in LuminanceAuraCategories)
|
|
{
|
|
foreach (var aura in category.Value)
|
|
{
|
|
try
|
|
{
|
|
// Use CharacterFilter.GetCharProperty for luminance auras
|
|
aura.CurrentValue = characterFilter.GetCharProperty(aura.IntId);
|
|
}
|
|
catch
|
|
{
|
|
aura.CurrentValue = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error refreshing luminance auras: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public void RefreshRecallSpells()
|
|
{
|
|
try
|
|
{
|
|
if (CoreManager.Current?.CharacterFilter?.Name == null) return;
|
|
|
|
var characterFilter = CoreManager.Current.CharacterFilter;
|
|
int knownCount = 0;
|
|
|
|
// Check each recall spell to see if the character knows it
|
|
foreach (var recall in RecallSpells)
|
|
{
|
|
try
|
|
{
|
|
// Use DECAL API to check if the character knows this spell
|
|
recall.IsKnown = characterFilter.IsSpellKnown(recall.SpellId);
|
|
if (recall.IsKnown) knownCount++;
|
|
|
|
// Get spell icon from FileService if not already set
|
|
if (recall.IconId == 0)
|
|
{
|
|
recall.IconId = GetSpellIcon(recall.SpellId);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
recall.IsKnown = false;
|
|
}
|
|
}
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error refreshing recall spells: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private int GetSpellIcon(int spellId)
|
|
{
|
|
try
|
|
{
|
|
// Try to get real spell icon first (matches original Lua approach)
|
|
int realSpellIcon = GetRealSpellIcon(spellId);
|
|
if (realSpellIcon != 0)
|
|
{
|
|
return realSpellIcon;
|
|
}
|
|
|
|
// Fallback to known recall spell icons
|
|
var recallSpellIcons = new Dictionary<int, int>
|
|
{
|
|
// Recall spell icons from AC spell data
|
|
[2023] = 2943, // Recall the Sanctuary
|
|
[2041] = 2814, // Aerlinthe Recall
|
|
[2813] = 2813, // Mount Lethe Recall
|
|
[2931] = 2931, // Recall Aphus Lassel
|
|
[2943] = 2943, // Recall to the Singularity Caul
|
|
[3865] = 3864, // Glenden Wood Recall
|
|
[4084] = 4084, // Bur Recall
|
|
[2941] = 2814, // Ulgrim's Recall (uses portal icon)
|
|
[4128] = 4128, // Call of the Mhoire Forge
|
|
[4198] = 4197, // Paradox-touched Olthoi Infested Area Recall
|
|
[4213] = 4213, // Colosseum Recall
|
|
[4214] = 4199, // Return to the Keep
|
|
[5330] = 5175, // Gear Knight Invasion Area Camp Recall
|
|
[5541] = 5541, // Lost City of Neftet Recall
|
|
[6150] = 6150, // Rynthid Recall
|
|
[6321] = 6321, // Viridian Rise Recall
|
|
[6322] = 6322 // Viridian Rise Great Tree Recall
|
|
};
|
|
|
|
if (recallSpellIcons.ContainsKey(spellId))
|
|
{
|
|
// Add offset for spell icons
|
|
return recallSpellIcons[spellId] + 0x6000000;
|
|
}
|
|
|
|
// Final fallback - default portal/recall icon
|
|
return 0x6002D14;
|
|
}
|
|
catch
|
|
{
|
|
return 0x6002D14; // Default icon on error
|
|
}
|
|
}
|
|
|
|
private int GetRealSpellIcon(int spellId)
|
|
{
|
|
try
|
|
{
|
|
// Method 1: Use DECAL FileService SpellTable directly (proper API approach)
|
|
try
|
|
{
|
|
var fileService = CoreManager.Current.Filter<Decal.Filters.FileService>();
|
|
if (fileService?.SpellTable != null)
|
|
{
|
|
var spell = fileService.SpellTable.GetById(spellId);
|
|
if (spell != null)
|
|
{
|
|
// Use reflection to access the internal Spell_Class object
|
|
// DECAL's Spell wrapper has an internal m_pSpell field that contains the actual data
|
|
var spellType = spell.GetType();
|
|
|
|
// Try to get the internal spell object first
|
|
var internalSpellField = spellType.GetField("m_pSpell", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|
if (internalSpellField != null)
|
|
{
|
|
var internalSpell = internalSpellField.GetValue(spell);
|
|
if (internalSpell != null)
|
|
{
|
|
// Now get the icon from the internal spell object
|
|
var internalType = internalSpell.GetType();
|
|
|
|
// Try icon properties on the internal object
|
|
string[] iconPropertyNames = { "Icon", "icon", "IconId", "iconId", "IconID", "iconID" };
|
|
foreach (var propName in iconPropertyNames)
|
|
{
|
|
try
|
|
{
|
|
// Try as property
|
|
var iconProperty = internalType.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (iconProperty != null)
|
|
{
|
|
var iconValue = iconProperty.GetValue(internalSpell, null);
|
|
if (iconValue is int iconInt && iconInt > 0)
|
|
{
|
|
// Spell icons use raw values (no offset)
|
|
return iconInt;
|
|
}
|
|
else if (iconValue is uint iconUint && iconUint > 0)
|
|
{
|
|
return (int)iconUint;
|
|
}
|
|
}
|
|
|
|
// Try as field
|
|
var iconField = internalType.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (iconField != null)
|
|
{
|
|
var iconValue = iconField.GetValue(internalSpell);
|
|
if (iconValue is int iconInt && iconInt > 0)
|
|
{
|
|
return iconInt;
|
|
}
|
|
else if (iconValue is uint iconUint && iconUint > 0)
|
|
{
|
|
return (int)iconUint;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Continue trying other property names
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback: Try direct properties on the wrapper
|
|
string[] wrapperPropertyNames = { "Icon", "IconId", "IconID" };
|
|
foreach (var propName in wrapperPropertyNames)
|
|
{
|
|
var iconProperty = spellType.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance);
|
|
if (iconProperty != null)
|
|
{
|
|
var iconValue = iconProperty.GetValue(spell, null);
|
|
if (iconValue is int iconId && iconId > 0)
|
|
{
|
|
return iconId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
// Method 2: Use known spell icon mappings for cantrips
|
|
// These are based on AC spell icon IDs from spell data
|
|
var cantripSpellIcons = new Dictionary<int, int>
|
|
{
|
|
// Strength cantrips - use Strength Self VIII icon
|
|
[2091] = 1332, // Major Strength
|
|
[4325] = 1332, // Epic Strength
|
|
[6107] = 1332, // Legendary Strength
|
|
|
|
// Endurance cantrips - use Endurance Self VIII icon
|
|
[2061] = 1354, // Major Endurance
|
|
[4226] = 1354, // Epic Endurance
|
|
[6104] = 1354, // Legendary Endurance
|
|
|
|
// Coordination cantrips - use Coordination Self VIII icon
|
|
[2059] = 1378, // Major Coordination
|
|
[4296] = 1378, // Epic Coordination
|
|
[6102] = 1378, // Legendary Coordination
|
|
|
|
// Quickness cantrips - use Quickness Self VIII icon
|
|
[2081] = 1409, // Major Quickness
|
|
[4319] = 1409, // Epic Quickness
|
|
[6106] = 1409, // Legendary Quickness
|
|
|
|
// Focus cantrips - use Focus Self VIII icon
|
|
[2067] = 1426, // Major Focus
|
|
[4304] = 1426, // Epic Focus
|
|
[6105] = 1426, // Legendary Focus
|
|
|
|
// Willpower/Self cantrips - use Willpower Self VIII icon
|
|
[2091] = 1450, // Major Willpower
|
|
[4329] = 1450, // Epic Willpower
|
|
[6101] = 1450, // Legendary Willpower
|
|
|
|
// Protection Auras - Armor - use Armor Self VIII icon
|
|
[2113] = 1316, // Major Armor
|
|
[4291] = 1316, // Epic Armor
|
|
[6095] = 1316, // Legendary Armor
|
|
|
|
// Protection Auras - Physical
|
|
[2245] = 1023, // Major Piercing Ward - use Blade Protection Self VIII
|
|
[4306] = 1023, // Epic Piercing Ward
|
|
[6096] = 1023, // Legendary Piercing Ward
|
|
|
|
[2244] = 1114, // Major Slashing Ward - use Piercing Protection Self VIII
|
|
[4321] = 1114, // Epic Slashing Ward
|
|
[6097] = 1114, // Legendary Slashing Ward
|
|
|
|
[2243] = 1138, // Major Bludgeoning Ward - use Bludgeoning Protection Self VIII
|
|
[4293] = 1138, // Epic Bludgeoning Ward
|
|
[6098] = 1138, // Legendary Bludgeoning Ward
|
|
|
|
// Protection Auras - Elemental
|
|
[2157] = 1096, // Major Frost Ward - use Cold Protection Self VIII
|
|
[4309] = 1096, // Epic Frost Ward
|
|
[6100] = 1096, // Legendary Frost Ward
|
|
|
|
[2158] = 1035, // Major Flame Ward - use Fire Protection Self VIII
|
|
[4294] = 1035, // Epic Flame Ward
|
|
[6099] = 1035, // Legendary Flame Ward
|
|
|
|
[2149] = 1078, // Major Acid Ward - use Acid Protection Self VIII
|
|
[4290] = 1078, // Epic Acid Ward
|
|
[6094] = 1078, // Legendary Acid Ward
|
|
|
|
[2159] = 1161, // Major Storm Ward - use Lightning Protection Self VIII
|
|
[4322] = 1161, // Epic Storm Ward
|
|
[6079] = 1161, // Legendary Storm Ward
|
|
|
|
// Magic Defense cantrips - use Magic Resistance Self VIII icon
|
|
[2249] = 610, // Major Magic Resistance
|
|
[4314] = 610, // Epic Magic Resistance
|
|
[6067] = 610, // Legendary Magic Resistance
|
|
|
|
// Melee Defense cantrips - use Invulnerability Self VIII icon
|
|
[2248] = 562, // Major Invulnerability
|
|
[4312] = 562, // Epic Invulnerability
|
|
[6051] = 562, // Legendary Invulnerability
|
|
|
|
// Missile Defense cantrips - use Impregnability Self VIII icon
|
|
[2247] = 1562, // Major Impregnability
|
|
[4311] = 1562, // Epic Impregnability
|
|
[6055] = 1562, // Legendary Impregnability
|
|
|
|
// Life Magic cantrips - use Life Magic Mastery Self VIII icon
|
|
[2156] = 610, // Major Life Magic Aptitude
|
|
[4700] = 610, // Epic Life Magic Aptitude
|
|
[6044] = 610, // Legendary Life Magic Aptitude
|
|
|
|
// War Magic cantrips - use War Magic Mastery Self VIII icon
|
|
[2183] = 634, // Major War Magic Aptitude
|
|
[4715] = 634, // Epic War Magic Aptitude
|
|
[6075] = 634, // Legendary War Magic Aptitude
|
|
|
|
// Creature Enchantment cantrips - use Creature Enchantment Mastery Self VIII icon
|
|
[2215] = 586, // Major Creature Enchantment Aptitude
|
|
[4689] = 586, // Epic Creature Enchantment Aptitude
|
|
[6042] = 586, // Legendary Creature Enchantment Aptitude
|
|
|
|
// Item Enchantment cantrips - use Item Enchantment Mastery Self VIII icon
|
|
[2249] = 658, // Major Item Enchantment Aptitude
|
|
[4697] = 658, // Epic Item Enchantment Aptitude
|
|
[6043] = 658, // Legendary Item Enchantment Aptitude
|
|
|
|
// Void Magic cantrips - use Void Magic Mastery Self VI icon (no VIII version)
|
|
[5427] = 5418, // Major Void Magic Aptitude
|
|
[5428] = 5418, // Epic Void Magic Aptitude
|
|
[5429] = 5418, // Legendary Void Magic Aptitude
|
|
|
|
// Weapon cantrips
|
|
[2223] = 522, // Major Heavy Weapon Aptitude - use Heavy Weapon Mastery Self VIII
|
|
[4624] = 522, // Epic Heavy Weapon Aptitude
|
|
[6073] = 522, // Legendary Heavy Weapon Aptitude
|
|
|
|
[2226] = 327, // Major Light Weapon Aptitude - use Light Weapon Mastery Self VI
|
|
[4639] = 327, // Epic Light Weapon Aptitude
|
|
[6074] = 327, // Legendary Light Weapon Aptitude
|
|
|
|
[2227] = 350, // Major Finesse Weapon Aptitude - use Finesse Weapon Mastery Self VI
|
|
[4638] = 350, // Epic Finesse Weapon Aptitude
|
|
[6072] = 350, // Legendary Finesse Weapon Aptitude
|
|
|
|
[2230] = 473, // Major Missile Weapon Aptitude - use Missile Weapon Mastery Self VI
|
|
[4713] = 473, // Epic Missile Weapon Aptitude
|
|
[6071] = 473, // Legendary Missile Weapon Aptitude
|
|
|
|
// Mana Conversion cantrips - use Mana Conversion Mastery Self VIII icon
|
|
[2152] = 658, // Major Mana Conversion Prowess
|
|
[4705] = 658, // Epic Mana Conversion Prowess
|
|
[6048] = 658, // Legendary Mana Conversion Prowess
|
|
};
|
|
|
|
if (cantripSpellIcons.ContainsKey(spellId))
|
|
{
|
|
int iconId = cantripSpellIcons[spellId];
|
|
// Add offset for spell icons to display correctly in VVS
|
|
// Based on MagTools pattern, spell icons need the offset for display
|
|
int finalIconId = iconId + 0x6000000;
|
|
return finalIconId;
|
|
}
|
|
|
|
return 0; // No real icon found
|
|
}
|
|
catch
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public void RefreshSocietyQuests()
|
|
{
|
|
try
|
|
{
|
|
// TODO: Implement society quest refresh
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error refreshing society quests: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public void RefreshFacilityHubQuests()
|
|
{
|
|
try
|
|
{
|
|
// TODO: Implement facility hub quest refresh
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error refreshing facility hub quests: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public void RefreshCharacterFlags()
|
|
{
|
|
try
|
|
{
|
|
// TODO: Implement character flag refresh
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error refreshing character flags: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public void RefreshCantrips()
|
|
{
|
|
try
|
|
{
|
|
|
|
if (CoreManager.Current?.CharacterFilter?.Name == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var characterFilter = CoreManager.Current.CharacterFilter;
|
|
var playerObject = CoreManager.Current.WorldFilter[characterFilter.Id];
|
|
|
|
if (playerObject == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
// Clear dynamic skill lists
|
|
Cantrips["Specialized Skills"].Clear();
|
|
Cantrips["Trained Skills"].Clear();
|
|
|
|
// Populate skills dynamically based on character's actual training
|
|
PopulateCharacterSkills(characterFilter);
|
|
|
|
// Reset all cantrips to "N/A"
|
|
foreach (var category in Cantrips)
|
|
{
|
|
foreach (var cantrip in category.Value.Values)
|
|
{
|
|
cantrip.Value = "N/A";
|
|
cantrip.Color = System.Drawing.Color.White;
|
|
}
|
|
}
|
|
|
|
// Scan active spells for cantrips using CharacterFilter.Enchantments
|
|
var enchantments = characterFilter.Enchantments;
|
|
if (enchantments != null)
|
|
{
|
|
for (int i = 0; i < enchantments.Count; i++)
|
|
{
|
|
var ench = enchantments[i];
|
|
var spell = SpellManager.GetSpell(ench.SpellId);
|
|
if (spell != null && spell.CantripLevel != Mag.Shared.Spells.Spell.CantripLevels.None)
|
|
{
|
|
DetectCantrip(ench.SpellId);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
}
|
|
|
|
// Compute final icon IDs for all cantrips after refresh
|
|
foreach (var category in Cantrips)
|
|
{
|
|
foreach (var cantrip in category.Value.Values)
|
|
{
|
|
cantrip.ComputedIconId = ComputeCantripIcon(cantrip);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error refreshing cantrips: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void PopulateCharacterSkills(CharacterFilter characterFilter)
|
|
{
|
|
try
|
|
{
|
|
// Map of skill IDs to skill names - based on DECAL CharFilterSkillType enumeration
|
|
// Reference: DECAL API documentation and AC development sources
|
|
var skillIdToName = new Dictionary<int, string>
|
|
{
|
|
[1] = "Axe", // Retired weapon skill
|
|
[2] = "Bow", // Retired weapon skill
|
|
[3] = "Crossbow", // Retired weapon skill
|
|
[4] = "Dagger", // Retired weapon skill
|
|
[5] = "Mace", // Retired weapon skill
|
|
[6] = "Melee Defense", // Active defense skill
|
|
[7] = "Missile Defense", // Active defense skill
|
|
[8] = "Sling", // Retired weapon skill
|
|
[9] = "Spear", // Retired weapon skill
|
|
[10] = "Staff", // Retired weapon skill
|
|
[11] = "Sword", // Retired weapon skill
|
|
[12] = "Thrown Weapons", // Retired weapon skill
|
|
[13] = "Unarmed Combat", // Retired weapon skill
|
|
[14] = "Arcane Lore", // Active magic skill
|
|
[15] = "Magic Defense", // Active defense skill
|
|
[16] = "Mana Conversion", // Active magic skill
|
|
[17] = "Spellcraft", // Unused/Reserved
|
|
[18] = "Item Tinkering", // Active tinker skill
|
|
[19] = "Assess Person", // Active misc skill
|
|
[20] = "Deception", // Active misc skill
|
|
[21] = "Healing", // Active misc skill
|
|
[22] = "Jump", // Active misc skill
|
|
[23] = "Lockpick", // Active misc skill
|
|
[24] = "Run", // Active misc skill
|
|
[25] = "Awareness", // Unused/Reserved
|
|
[26] = "Arms and Armor Repair", // Unused/Reserved
|
|
[27] = "Assess Creature", // Active misc skill
|
|
[28] = "Weapon Tinkering", // Active tinker skill
|
|
[29] = "Armor Tinkering", // Active tinker skill
|
|
[30] = "Magic Item Tinkering", // Active tinker skill
|
|
[31] = "Creature Enchantment", // Active magic skill
|
|
[32] = "Item Enchantment", // Active magic skill
|
|
[33] = "Life Magic", // Active magic skill
|
|
[34] = "War Magic", // Active magic skill
|
|
[35] = "Leadership", // Active misc skill
|
|
[36] = "Loyalty", // Active misc skill
|
|
[37] = "Fletching", // Active tinker skill
|
|
[38] = "Alchemy", // Active tinker skill
|
|
[39] = "Cooking", // Active tinker skill
|
|
[40] = "Salvaging", // Active tinker skill
|
|
[41] = "Two Handed Combat", // Active weapon skill
|
|
[42] = "Gearcraft", // Retired tinker skill
|
|
[43] = "Void Magic", // Active magic skill
|
|
[44] = "Heavy Weapons", // Active weapon skill
|
|
[45] = "Light Weapons", // Active weapon skill
|
|
[46] = "Finesse Weapons", // Active weapon skill
|
|
[47] = "Missile Weapons", // Active weapon skill
|
|
[48] = "Shield", // Active weapon skill
|
|
[49] = "Dual Wield", // Active weapon skill
|
|
[50] = "Recklessness", // Active weapon skill
|
|
[51] = "Sneak Attack", // Active weapon skill
|
|
[52] = "Dirty Fighting", // Active weapon skill
|
|
[53] = "Threat Assessment", // Unused/Reserved
|
|
[54] = "Summoning" // Active magic skill
|
|
};
|
|
|
|
// Check each skill's training status
|
|
foreach (var kvp in skillIdToName)
|
|
{
|
|
int skillId = kvp.Key;
|
|
string skillName = kvp.Value;
|
|
|
|
try
|
|
{
|
|
// Get skill training status using CharacterFilter.Skills
|
|
var skillInfo = characterFilter.Skills[(Decal.Adapter.Wrappers.CharFilterSkillType)skillId];
|
|
if (skillInfo == null) continue;
|
|
|
|
// Apply skill name replacements for cantrips
|
|
if (SkillCantripReplacements.ContainsKey(skillId))
|
|
{
|
|
skillName = SkillCantripReplacements[skillId];
|
|
}
|
|
|
|
if (skillInfo.Training == Decal.Adapter.Wrappers.TrainingType.Specialized)
|
|
{
|
|
Cantrips["Specialized Skills"][skillName] = new CantripInfo
|
|
{
|
|
Name = skillName,
|
|
Value = "N/A",
|
|
Color = System.Drawing.Color.White
|
|
// IconId removed - will be set by spell icon when cantrips are detected
|
|
};
|
|
}
|
|
else if (skillInfo.Training == Decal.Adapter.Wrappers.TrainingType.Trained)
|
|
{
|
|
Cantrips["Trained Skills"][skillName] = new CantripInfo
|
|
{
|
|
Name = skillName,
|
|
Value = "N/A",
|
|
Color = System.Drawing.Color.White
|
|
// IconId removed - will be set by spell icon when cantrips are detected
|
|
};
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Skill not available on this character, skip it
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error populating character skills: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private int? GetSkillIconId(int skillId)
|
|
{
|
|
try
|
|
{
|
|
var characterFilter = CoreManager.Current.CharacterFilter;
|
|
if (characterFilter == null)
|
|
{
|
|
return GetFallbackSkillIcon(skillId);
|
|
}
|
|
|
|
// Validate skillId range for DECAL API
|
|
if (skillId < 1 || skillId > 54)
|
|
{
|
|
return GetFallbackSkillIcon(skillId);
|
|
}
|
|
|
|
try
|
|
{
|
|
var skillInfo = characterFilter.Skills[(Decal.Adapter.Wrappers.CharFilterSkillType)skillId];
|
|
if (skillInfo == null)
|
|
{
|
|
return GetFallbackSkillIcon(skillId);
|
|
}
|
|
|
|
|
|
// Try to access skill icon via reflection (DECAL's SkillInfoWrapper.Dat property)
|
|
var skillType = skillInfo.GetType();
|
|
|
|
// Method 1: Try FileService SkillTable approach (most reliable)
|
|
int realIconId = GetRealSkillIconFromDat(skillId);
|
|
if (realIconId > 0)
|
|
{
|
|
return realIconId + 0x6000000;
|
|
}
|
|
|
|
// Method 2: Reflection on SkillInfoWrapper.Dat
|
|
var datProperty = skillType.GetProperty("Dat", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (datProperty != null)
|
|
{
|
|
var datObject = datProperty.GetValue(skillInfo, null);
|
|
if (datObject != null)
|
|
{
|
|
var datType = datObject.GetType();
|
|
|
|
// Try the exact property names from AC system
|
|
string[] iconPropertyNames = { "IconID", "Icon", "IconId", "uiGraphic", "GraphicID" };
|
|
|
|
foreach (var propName in iconPropertyNames)
|
|
{
|
|
var iconProperty = datType.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (iconProperty != null)
|
|
{
|
|
var iconValue = iconProperty.GetValue(datObject, null);
|
|
if (iconValue != null)
|
|
{
|
|
if (iconValue is int iconId && iconId > 0)
|
|
{
|
|
return iconId + 0x6000000;
|
|
}
|
|
else if (iconValue is uint uiconId && uiconId > 0)
|
|
{
|
|
return (int)uiconId + 0x6000000;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Try as field
|
|
var iconField = datType.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (iconField != null)
|
|
{
|
|
var iconValue = iconField.GetValue(datObject);
|
|
if (iconValue != null)
|
|
{
|
|
if (iconValue is int iconId && iconId > 0)
|
|
{
|
|
return iconId + 0x6000000;
|
|
}
|
|
else if (iconValue is uint uiconId && uiconId > 0)
|
|
{
|
|
return (int)uiconId + 0x6000000;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var prop in datType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
|
|
{
|
|
try
|
|
{
|
|
var val = prop.GetValue(datObject, null);
|
|
PluginCore.WriteToChat($" {prop.Name}: {val} ({prop.PropertyType.Name})");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($" {prop.Name}: <error: {ex.Message}> ({prop.PropertyType.Name})");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
}
|
|
}
|
|
else
|
|
{
|
|
}
|
|
|
|
// Method 3: Try direct properties on SkillInfoWrapper
|
|
string[] directPropertyNames = { "IconID", "Icon", "IconId", "GraphicID" };
|
|
foreach (var propName in directPropertyNames)
|
|
{
|
|
var iconProperty = skillType.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (iconProperty != null)
|
|
{
|
|
var iconValue = iconProperty.GetValue(skillInfo, null);
|
|
if (iconValue is int iconId && iconId > 0)
|
|
{
|
|
return iconId + 0x6000000;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
// Fallback to predefined mapping
|
|
return GetFallbackSkillIcon(skillId);
|
|
}
|
|
catch
|
|
{
|
|
return GetFallbackSkillIcon(skillId);
|
|
}
|
|
}
|
|
|
|
private string GetSkillName(int skillId)
|
|
{
|
|
var skillNames = new Dictionary<int, string>
|
|
{
|
|
[1] = "Axe", [2] = "Bow", [3] = "Crossbow", [4] = "Dagger", [5] = "Mace",
|
|
[6] = "Melee Defense", [7] = "Missile Defense", [8] = "Sling", [9] = "Spear", [10] = "Staff",
|
|
[11] = "Sword", [12] = "Thrown Weapons", [13] = "Unarmed Combat", [14] = "Arcane Lore", [15] = "Magic Defense",
|
|
[16] = "Mana Conversion", [17] = "Spellcraft", [18] = "Item Tinkering", [19] = "Assess Person", [20] = "Deception",
|
|
[21] = "Healing", [22] = "Jump", [23] = "Lockpick", [24] = "Run", [25] = "Awareness",
|
|
[26] = "Arms and Armor Repair", [27] = "Assess Creature", [28] = "Weapon Tinkering", [29] = "Armor Tinkering", [30] = "Magic Item Tinkering",
|
|
[31] = "Creature Enchantment", [32] = "Item Enchantment", [33] = "Life Magic", [34] = "War Magic", [35] = "Leadership",
|
|
[36] = "Loyalty", [37] = "Fletching", [38] = "Alchemy", [39] = "Cooking", [40] = "Salvaging",
|
|
[41] = "Two Handed Combat", [42] = "Gearcraft", [43] = "Void Magic", [44] = "Heavy Weapons", [45] = "Light Weapons",
|
|
[46] = "Finesse Weapons", [47] = "Missile Weapons", [48] = "Shield", [49] = "Dual Wield", [50] = "Recklessness",
|
|
[51] = "Sneak Attack", [52] = "Dirty Fighting", [53] = "Threat Assessment", [54] = "Summoning"
|
|
};
|
|
|
|
return skillNames.ContainsKey(skillId) ? skillNames[skillId] : $"Unknown({skillId})";
|
|
}
|
|
|
|
private int GetRealSkillIconFromDat(int skillId)
|
|
{
|
|
try
|
|
{
|
|
// Try using FileService SkillTable directly (similar to CharacterCreation.cs pattern)
|
|
var fileService = CoreManager.Current.Filter<Decal.Filters.FileService>();
|
|
if (fileService?.SkillTable != null)
|
|
{
|
|
// Try to get skill data from the skill table
|
|
try
|
|
{
|
|
// Access SkillTable via reflection to get skill data
|
|
var skillTableType = fileService.SkillTable.GetType();
|
|
|
|
// Look for methods that can get skill by ID
|
|
var methods = skillTableType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
foreach (var method in methods)
|
|
{
|
|
if (method.Name.Contains("Get") || method.Name.Contains("get") || method.Name == "Item")
|
|
{
|
|
var parameters = method.GetParameters();
|
|
if (parameters.Length == 1 && (parameters[0].ParameterType == typeof(int) || parameters[0].ParameterType == typeof(uint)))
|
|
{
|
|
try
|
|
{
|
|
var skillData = method.Invoke(fileService.SkillTable, new object[] { skillId });
|
|
if (skillData != null)
|
|
{
|
|
|
|
// Look for icon properties on the skill data
|
|
var skillDataType = skillData.GetType();
|
|
string[] iconProps = { "IconID", "Icon", "IconId", "GraphicID", "uiGraphic" };
|
|
|
|
foreach (var propName in iconProps)
|
|
{
|
|
var iconProp = skillDataType.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (iconProp != null)
|
|
{
|
|
var iconValue = iconProp.GetValue(skillData, null);
|
|
if (iconValue is int iconInt && iconInt > 0)
|
|
{
|
|
return iconInt;
|
|
}
|
|
else if (iconValue is uint iconUint && iconUint > 0)
|
|
{
|
|
return (int)iconUint;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Method call failed, try next one
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
else
|
|
{
|
|
}
|
|
|
|
return 0; // No icon found
|
|
}
|
|
catch
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private int? GetFallbackSkillIcon(int skillId)
|
|
{
|
|
// Use proven working icon IDs from the recall spells system
|
|
// These icons are confirmed to display correctly in VVS
|
|
var skillIconMap = new Dictionary<int, int>
|
|
{
|
|
// Magic Skills - Use magical/mystical icons (from recalls system)
|
|
[14] = 0x6002D14, // Arcane Lore - Portal icon (default recall icon)
|
|
[16] = 0x60011F9, // Mana Conversion - Green circle (confirmed working)
|
|
[31] = 0x6002D14, // Creature Enchantment - Portal icon
|
|
[32] = 0x6002D14, // Item Enchantment - Portal icon
|
|
[33] = 0x60011F9, // Life Magic - Green circle (life/healing)
|
|
[34] = 0x60011F8, // War Magic - Red circle (destruction)
|
|
[43] = 0x600287A, // Void Magic - Gray dot (void)
|
|
[54] = 0x6002D14, // Summoning - Portal icon
|
|
|
|
// Combat Skills - Use distinct working icons
|
|
[41] = 0x60011F8, // Two Handed Combat - Red circle
|
|
[44] = 0x60011F8, // Heavy Weapons - Red circle
|
|
[45] = 0x60028FC, // Light Weapons - Up arrow (from recalls)
|
|
[46] = 0x60028FD, // Finesse Weapons - Down arrow (from recalls)
|
|
[47] = 0x60020B5, // Missile Weapons - Question mark (from recalls)
|
|
[48] = 0x600287A, // Shield - Gray dot
|
|
[49] = 0x60011F8, // Dual Wield - Red circle
|
|
[50] = 0x60011F8, // Recklessness - Red circle
|
|
[51] = 0x600287A, // Sneak Attack - Gray dot
|
|
[52] = 0x60011F8, // Dirty Fighting - Red circle
|
|
|
|
// Defense Skills - Use defensive icons
|
|
[6] = 0x600287A, // Melee Defense - Gray dot
|
|
[7] = 0x600287A, // Missile Defense - Gray dot
|
|
[15] = 0x600287A, // Magic Defense - Gray dot
|
|
|
|
// Misc Skills - Use varied working icons
|
|
[19] = 0x60020B5, // Assess Person - Question mark (from recalls)
|
|
[20] = 0x60020B5, // Deception - Question mark
|
|
[21] = 0x60011F9, // Healing - Green circle
|
|
[22] = 0x60028FC, // Jump - Up arrow (motion)
|
|
[23] = 0x60020B5, // Lockpick - Question mark
|
|
[24] = 0x60028FC, // Run - Up arrow (motion)
|
|
[27] = 0x60020B5, // Assess Creature - Question mark
|
|
[35] = 0x60020B5, // Leadership - Question mark
|
|
[36] = 0x60020B5, // Loyalty - Question mark
|
|
|
|
// Craft Skills - Use working craft icons
|
|
[18] = 0x600287A, // Item Tinkering - Gray dot
|
|
[28] = 0x600287A, // Weapon Tinkering - Gray dot
|
|
[29] = 0x600287A, // Armor Tinkering - Gray dot
|
|
[30] = 0x600287A, // Magic Item Tinkering - Gray dot
|
|
[37] = 0x600287A, // Fletching - Gray dot
|
|
[38] = 0x600287A, // Alchemy - Gray dot
|
|
[39] = 0x600287A, // Cooking - Gray dot
|
|
[40] = 0x600287A, // Salvaging - Gray dot
|
|
|
|
// Retired weapon skills - Use weapon-style icons
|
|
[1] = 0x60011F8, // Axe - Red circle
|
|
[2] = 0x60028FC, // Bow - Up arrow (projectile)
|
|
[3] = 0x60028FC, // Crossbow - Up arrow (projectile)
|
|
[4] = 0x60011F8, // Dagger - Red circle
|
|
[5] = 0x60011F8, // Mace - Red circle
|
|
[8] = 0x60028FC, // Sling - Up arrow (projectile)
|
|
[9] = 0x60011F8, // Spear - Red circle
|
|
[10] = 0x60011F8, // Staff - Red circle
|
|
[11] = 0x60011F8, // Sword - Red circle
|
|
[12] = 0x60028FC, // Thrown Weapons - Up arrow (projectile)
|
|
[13] = 0x60011F8 // Unarmed Combat - Red circle
|
|
};
|
|
|
|
if (skillIconMap.ContainsKey(skillId))
|
|
{
|
|
return skillIconMap[skillId];
|
|
}
|
|
|
|
// Final fallback to proven working icon from recalls system
|
|
return 0x6002D14; // Portal icon - confirmed working in recalls
|
|
}
|
|
|
|
private int ComputeCantripIcon(CantripInfo cantrip)
|
|
{
|
|
try
|
|
{
|
|
// Green circle for active cantrips, red circle for missing cantrips
|
|
if (cantrip.Value != "N/A")
|
|
{
|
|
return 0x60011F9; // Green circle - has cantrip
|
|
}
|
|
else
|
|
{
|
|
return 0x60011F8; // Red circle - missing cantrip
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
return 0x60011F8; // Red circle on error
|
|
}
|
|
}
|
|
|
|
private void DetectCantrip(int spellId)
|
|
{
|
|
try
|
|
{
|
|
// Get spell name from SpellManager
|
|
string spellName = GetSpellName(spellId);
|
|
if (string.IsNullOrEmpty(spellName))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Debug output to see what spells we're processing
|
|
|
|
// Define cantrip levels and their patterns
|
|
var cantripPatterns = new Dictionary<string, (string level, System.Drawing.Color color)>
|
|
{
|
|
["Minor"] = ("Minor", System.Drawing.Color.White),
|
|
["Moderate"] = ("Moderate", System.Drawing.Color.Green),
|
|
["Major"] = ("Major", System.Drawing.Color.Blue),
|
|
["Epic"] = ("Epic", System.Drawing.Color.Purple),
|
|
["Legendary"] = ("Legendary", System.Drawing.Color.Orange)
|
|
};
|
|
|
|
// Check each cantrip level
|
|
foreach (var cantripPattern in cantripPatterns)
|
|
{
|
|
string pattern = cantripPattern.Key;
|
|
var (level, color) = cantripPattern.Value;
|
|
|
|
if (!spellName.StartsWith(pattern + " ")) continue;
|
|
|
|
// Remove the level prefix to get the skill/attribute name
|
|
string skillPart = spellName.Substring(pattern.Length + 1);
|
|
|
|
// Get the spell icon for this cantrip spell
|
|
int spellIconId = GetRealSpellIcon(spellId);
|
|
if (spellIconId == 0)
|
|
{
|
|
spellIconId = 0x6002D14; // Default fallback icon
|
|
}
|
|
else
|
|
{
|
|
}
|
|
|
|
// Try to match Protection Auras first (exact format: "Minor Armor", "Epic Bludgeoning Ward")
|
|
if (MatchProtectionAura(skillPart, level, color, spellIconId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Try to match Attributes (exact format: "Minor Strength", "Epic Focus")
|
|
if (MatchAttribute(skillPart, level, color, spellIconId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Try to match Skills using the replacement mappings
|
|
if (MatchSkill(skillPart, level, color, spellIconId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error detecting cantrip for spell {spellId}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private bool MatchProtectionAura(string skillPart, string level, System.Drawing.Color color, int spellIconId)
|
|
{
|
|
// Map AC cantrip spell names to protection aura names
|
|
var protectionMappings = new Dictionary<string, string>
|
|
{
|
|
["Armor"] = "Armor",
|
|
["Bludgeoning Ward"] = "Bludgeoning Ward",
|
|
["Piercing Ward"] = "Piercing Ward",
|
|
["Slashing Ward"] = "Slashing Ward",
|
|
["Flame Ward"] = "Flame Ward",
|
|
["Frost Ward"] = "Frost Ward",
|
|
["Cold Ward"] = "Frost Ward", // AC also uses "Cold Ward"
|
|
["Acid Ward"] = "Acid Ward",
|
|
["Storm Ward"] = "Storm Ward", // AC spell is "Storm Ward"
|
|
["Lightning Ward"] = "Storm Ward", // AC also uses "Lightning Ward"
|
|
|
|
// Add more variations that might appear in AC spell names
|
|
["Bludgeoning Protection"] = "Bludgeoning Ward",
|
|
["Piercing Protection"] = "Piercing Ward",
|
|
["Slashing Protection"] = "Slashing Ward",
|
|
["Fire Protection"] = "Flame Ward",
|
|
["Cold Protection"] = "Frost Ward",
|
|
["Acid Protection"] = "Acid Ward",
|
|
["Lightning Protection"] = "Storm Ward",
|
|
|
|
// Single word variations
|
|
["Bludgeoning"] = "Bludgeoning Ward",
|
|
["Piercing"] = "Piercing Ward",
|
|
["Slashing"] = "Slashing Ward",
|
|
["Fire"] = "Flame Ward",
|
|
["Flame"] = "Flame Ward",
|
|
["Cold"] = "Frost Ward",
|
|
["Frost"] = "Frost Ward",
|
|
["Acid"] = "Acid Ward",
|
|
["Lightning"] = "Storm Ward",
|
|
["Storm"] = "Storm Ward"
|
|
};
|
|
|
|
foreach (var mapping in protectionMappings)
|
|
{
|
|
if (skillPart.Equals(mapping.Key, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// Create the cantrip entry if it doesn't exist
|
|
if (!Cantrips["Protection Auras"].ContainsKey(mapping.Value))
|
|
{
|
|
Cantrips["Protection Auras"][mapping.Value] = new CantripInfo
|
|
{
|
|
Name = mapping.Value,
|
|
Value = "N/A",
|
|
Color = System.Drawing.Color.White
|
|
};
|
|
}
|
|
|
|
var cantrip = Cantrips["Protection Auras"][mapping.Value];
|
|
if (cantrip.Value == "N/A" || IsHigherCantripLevel(level, cantrip.Value))
|
|
{
|
|
cantrip.Value = level;
|
|
cantrip.Color = color;
|
|
cantrip.SpellIconId = spellIconId; // Use the actual spell icon from the cantrip
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool MatchAttribute(string skillPart, string level, System.Drawing.Color color, int spellIconId)
|
|
{
|
|
// Clean the skill part - remove extra spaces and normalize
|
|
string cleanedSkillPart = skillPart.Trim();
|
|
|
|
var attributeMappings = new Dictionary<string, string>
|
|
{
|
|
["Strength"] = "Strength",
|
|
["Endurance"] = "Endurance",
|
|
["Coordination"] = "Coordination",
|
|
["Quickness"] = "Quickness",
|
|
["Focus"] = "Focus",
|
|
["Self"] = "Willpower", // "Minor Self" -> Willpower
|
|
["Willpower"] = "Willpower" // "Epic Willpower" -> Willpower
|
|
};
|
|
|
|
|
|
foreach (var mapping in attributeMappings)
|
|
{
|
|
if (cleanedSkillPart.Equals(mapping.Key, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
|
|
// Create the cantrip entry if it doesn't exist
|
|
if (!Cantrips["Attributes"].ContainsKey(mapping.Value))
|
|
{
|
|
Cantrips["Attributes"][mapping.Value] = new CantripInfo
|
|
{
|
|
Name = mapping.Value,
|
|
Value = "N/A",
|
|
Color = System.Drawing.Color.White
|
|
};
|
|
}
|
|
|
|
var cantrip = Cantrips["Attributes"][mapping.Value];
|
|
if (cantrip.Value == "N/A" || IsHigherCantripLevel(level, cantrip.Value))
|
|
{
|
|
cantrip.Value = level;
|
|
cantrip.Color = color;
|
|
cantrip.SpellIconId = spellIconId; // Use the actual spell icon from the cantrip
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Try more flexible matching - check if the cleaned skill part contains any of our attributes
|
|
foreach (var mapping in attributeMappings)
|
|
{
|
|
if (cleanedSkillPart.IndexOf(mapping.Key, StringComparison.OrdinalIgnoreCase) >= 0)
|
|
{
|
|
|
|
// Create the cantrip entry if it doesn't exist
|
|
if (!Cantrips["Attributes"].ContainsKey(mapping.Value))
|
|
{
|
|
Cantrips["Attributes"][mapping.Value] = new CantripInfo
|
|
{
|
|
Name = mapping.Value,
|
|
Value = "N/A",
|
|
Color = System.Drawing.Color.White
|
|
};
|
|
}
|
|
|
|
var cantrip = Cantrips["Attributes"][mapping.Value];
|
|
if (cantrip.Value == "N/A" || IsHigherCantripLevel(level, cantrip.Value))
|
|
{
|
|
cantrip.Value = level;
|
|
cantrip.Color = color;
|
|
cantrip.SpellIconId = spellIconId; // Use the actual spell icon from the cantrip
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool MatchSkill(string skillPart, string level, System.Drawing.Color color, int spellIconId)
|
|
{
|
|
// Map actual cantrip spell names to our skill names
|
|
var skillMappings = new Dictionary<string, string>
|
|
{
|
|
// Defense skills (with AC spell name mappings)
|
|
["Magic Resistance"] = "MagicResistance",
|
|
["Invulnerability"] = "Invulnerability",
|
|
["Impregnability"] = "Impgrenability", // Note: AC uses "Impregnability" not "Impgrenability"
|
|
|
|
// Weapon skills (with AC spell name patterns)
|
|
["Heavy Weapon Aptitude"] = "HeavyWeapon",
|
|
["Light Weapon Aptitude"] = "LightWeapon",
|
|
["Finesse Weapon Aptitude"] = "FinesseWeapon",
|
|
["Missile Weapon Aptitude"] = "MissileWeapon",
|
|
|
|
// Craft skills (with AC spell name patterns)
|
|
["Alchemical Prowess"] = "Alchemy",
|
|
["Arcane Prowess"] = "Arcane Lore",
|
|
["Armor Tinkering Expertise"] = "Armor Tinkering",
|
|
["Assess Creature"] = "Assess Creature",
|
|
["Assess Person"] = "Assess Person",
|
|
["Cooking Prowess"] = "Cooking",
|
|
["Deception Prowess"] = "Deception",
|
|
["Fletching Prowess"] = "Fletching",
|
|
["Healing Prowess"] = "Healing",
|
|
["Item Tinkering Expertise"] = "Item Tinkering",
|
|
["Leadership"] = "Leadership",
|
|
["Lockpick Prowess"] = "Lockpick",
|
|
["Fealty"] = "Loyalty", // AC uses "Fealty" for Loyalty
|
|
["Magic Item Tinkering Expertise"] = "Magic Item Tinkering",
|
|
["Mana Conversion Prowess"] = "Mana Conversion",
|
|
["Jumping Prowess"] = "Jump", // AC has Jump cantrips
|
|
["Salvaging Aptitude"] = "Salvaging",
|
|
["Weapon Tinkering Expertise"] = "Weapon Tinkering",
|
|
|
|
// Magic schools (with AC spell name patterns)
|
|
["War Magic Aptitude"] = "War Magic",
|
|
["Life Magic Aptitude"] = "Life Magic",
|
|
["Creature Enchantment Aptitude"] = "Creature Enchantment",
|
|
["Item Enchantment Aptitude"] = "Item Enchantment",
|
|
["Void Magic Aptitude"] = "Void Magic",
|
|
["Summoning Prowess"] = "Summoning",
|
|
|
|
// Combat skills
|
|
["Two Handed Combat Aptitude"] = "Two Handed Combat",
|
|
["Dual Wield Aptitude"] = "Dual Wield",
|
|
["Shield Aptitude"] = "Shield",
|
|
["Sneak Attack Prowess"] = "Sneak Attack",
|
|
["Dirty Fighting Prowess"] = "Dirty Fighting",
|
|
["Recklessness"] = "Recklessness"
|
|
};
|
|
|
|
foreach (var mapping in skillMappings)
|
|
{
|
|
if (skillPart.Equals(mapping.Key, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// Check both specialized and trained skills
|
|
foreach (var category in new[] { "Specialized Skills", "Trained Skills" })
|
|
{
|
|
if (Cantrips[category].ContainsKey(mapping.Value))
|
|
{
|
|
var cantrip = Cantrips[category][mapping.Value];
|
|
if (cantrip.Value == "N/A" || IsHigherCantripLevel(level, cantrip.Value))
|
|
{
|
|
cantrip.Value = level;
|
|
cantrip.Color = color;
|
|
cantrip.SpellIconId = spellIconId; // Use the actual spell icon instead of skill icon
|
|
cantrip.IconId = null; // Clear any skill icon reference
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private string GetSpellName(int spellId)
|
|
{
|
|
try
|
|
{
|
|
// Use our existing SpellManager that was already working
|
|
var spell = SpellManager.GetSpell(spellId);
|
|
if (spell != null)
|
|
{
|
|
return spell.Name;
|
|
}
|
|
return "";
|
|
}
|
|
catch
|
|
{
|
|
return "";
|
|
}
|
|
}
|
|
|
|
private bool IsHigherCantripLevel(string newLevel, string currentLevel)
|
|
{
|
|
var levels = new Dictionary<string, int>
|
|
{
|
|
["Minor"] = 1,
|
|
["Moderate"] = 2,
|
|
["Major"] = 3,
|
|
["Epic"] = 4,
|
|
["Legendary"] = 5
|
|
};
|
|
|
|
if (!levels.ContainsKey(newLevel) || !levels.ContainsKey(currentLevel))
|
|
return false;
|
|
|
|
return levels[newLevel] > levels[currentLevel];
|
|
}
|
|
|
|
private void TestCantripDetection()
|
|
{
|
|
try
|
|
{
|
|
// Test real AC cantrip spell names to verify the detection logic
|
|
var testSpells = new[]
|
|
{
|
|
"Major Strength", // Attribute cantrip
|
|
"Epic Coordination", // Attribute cantrip
|
|
"Legendary Focus", // Attribute cantrip
|
|
"Major Willpower", // Willpower attribute
|
|
"Major Invulnerability", // Melee Defense skill
|
|
"Major Impregnability", // Missile Defense skill
|
|
"Major Alchemical Prowess", // Alchemy skill
|
|
"Major Arcane Prowess", // Arcane Lore skill
|
|
"Epic Life Magic Aptitude", // Life Magic skill
|
|
"Major Fealty", // Loyalty skill
|
|
"Major Armor", // Protection aura
|
|
"Epic Flame Ward", // Protection aura
|
|
"Legendary Storm Ward" // Protection aura
|
|
};
|
|
|
|
foreach (var spellName in testSpells)
|
|
{
|
|
TestDetectCantripByName(spellName);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error in test cantrip detection: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void TestDetectCantripByName(string spellName)
|
|
{
|
|
try
|
|
{
|
|
// Simulate the detection logic with a fake spell name
|
|
|
|
// Define cantrip levels and their patterns
|
|
var cantripPatterns = new Dictionary<string, (string level, System.Drawing.Color color)>
|
|
{
|
|
["Minor"] = ("Minor", System.Drawing.Color.White),
|
|
["Moderate"] = ("Moderate", System.Drawing.Color.Green),
|
|
["Major"] = ("Major", System.Drawing.Color.Blue),
|
|
["Epic"] = ("Epic", System.Drawing.Color.Purple),
|
|
["Legendary"] = ("Legendary", System.Drawing.Color.Orange)
|
|
};
|
|
|
|
// Check each cantrip level
|
|
foreach (var cantripPattern in cantripPatterns)
|
|
{
|
|
string pattern = cantripPattern.Key;
|
|
var (level, color) = cantripPattern.Value;
|
|
|
|
if (!spellName.StartsWith(pattern + " ")) continue;
|
|
|
|
// Remove the level prefix to get the skill/attribute name
|
|
string skillPart = spellName.Substring(pattern.Length + 1);
|
|
|
|
|
|
// Get a test spell icon (use default for testing)
|
|
int testSpellIconId = 0x6002D14;
|
|
|
|
// Try to match Protection Auras first
|
|
if (MatchProtectionAura(skillPart, level, color, testSpellIconId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Try to match Attributes
|
|
if (MatchAttribute(skillPart, level, color, testSpellIconId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Try to match Skills
|
|
if (MatchSkill(skillPart, level, color, testSpellIconId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error testing cantrip detection: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
#region Weapon Data Structures
|
|
public class TrackedWeaponInfo
|
|
{
|
|
public string Name { get; set; }
|
|
public string Category { get; set; }
|
|
public string WeaponType { get; set; }
|
|
public string Status { get; set; }
|
|
public bool IsAcquired { get; set; }
|
|
public int ItemId { get; set; }
|
|
public int IconId { get; set; }
|
|
}
|
|
|
|
public Dictionary<string, List<TrackedWeaponInfo>> WeaponCategories { get; private set; }
|
|
#endregion
|
|
|
|
public void RefreshWeapons()
|
|
{
|
|
try
|
|
{
|
|
if (CoreManager.Current?.CharacterFilter?.Name == null) return;
|
|
|
|
InitializeNewWeaponData();
|
|
CheckAcquiredWeapons();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error refreshing weapons: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void InitializeNewWeaponData()
|
|
{
|
|
WeaponCategories = new Dictionary<string, List<TrackedWeaponInfo>>
|
|
{
|
|
["Legendary Weapons"] = new List<TrackedWeaponInfo>
|
|
{
|
|
new TrackedWeaponInfo { Name = "Bow of the Quiddity", Category = "Legendary Weapons", WeaponType = "Bow", ItemId = 23044, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Sword of the Quiddity", Category = "Legendary Weapons", WeaponType = "Sword", ItemId = 23039, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Mace of the Quiddity", Category = "Legendary Weapons", WeaponType = "Mace", ItemId = 23040, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Atlatl of the Quiddity", Category = "Legendary Weapons", WeaponType = "Atlatl", ItemId = 23043, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Staff of the Quiddity", Category = "Legendary Weapons", WeaponType = "Staff", ItemId = 23041, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Dagger of the Quiddity", Category = "Legendary Weapons", WeaponType = "Dagger", ItemId = 23042, Status = "Unknown" }
|
|
},
|
|
["Slayer Weapons"] = new List<TrackedWeaponInfo>
|
|
{
|
|
new TrackedWeaponInfo { Name = "Shadowfire Isparian Bow", Category = "Slayer Weapons", WeaponType = "Bow", ItemId = 20636, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Shadowfire Isparian Sword", Category = "Slayer Weapons", WeaponType = "Sword", ItemId = 20631, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Shadowfire Isparian Staff", Category = "Slayer Weapons", WeaponType = "Staff", ItemId = 20633, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Shadowfire Isparian Dagger", Category = "Slayer Weapons", WeaponType = "Dagger", ItemId = 20634, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Shadowfire Isparian Axe", Category = "Slayer Weapons", WeaponType = "Axe", ItemId = 20635, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Shadowfire Isparian Mace", Category = "Slayer Weapons", WeaponType = "Mace", ItemId = 20632, Status = "Unknown" }
|
|
},
|
|
["Society Weapons"] = new List<TrackedWeaponInfo>
|
|
{
|
|
new TrackedWeaponInfo { Name = "Radiant Blood Sword", Category = "Society Weapons", WeaponType = "Sword", ItemId = 32834, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Celestial Hand Sword", Category = "Society Weapons", WeaponType = "Sword", ItemId = 32835, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Eldritch Web Sword", Category = "Society Weapons", WeaponType = "Sword", ItemId = 32836, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Radiant Blood Bow", Category = "Society Weapons", WeaponType = "Bow", ItemId = 32837, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Celestial Hand Bow", Category = "Society Weapons", WeaponType = "Bow", ItemId = 32838, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Eldritch Web Bow", Category = "Society Weapons", WeaponType = "Bow", ItemId = 32839, Status = "Unknown" }
|
|
},
|
|
["Atlan Weapons"] = new List<TrackedWeaponInfo>
|
|
{
|
|
new TrackedWeaponInfo { Name = "Atlan Sword", Category = "Atlan Weapons", WeaponType = "Sword", ItemId = 11648, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Atlan Axe", Category = "Atlan Weapons", WeaponType = "Axe", ItemId = 11649, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Atlan Mace", Category = "Atlan Weapons", WeaponType = "Mace", ItemId = 11650, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Atlan Spear", Category = "Atlan Weapons", WeaponType = "Spear", ItemId = 11651, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Atlan Staff", Category = "Atlan Weapons", WeaponType = "Staff", ItemId = 11652, Status = "Unknown" },
|
|
new TrackedWeaponInfo { Name = "Atlan Dagger", Category = "Atlan Weapons", WeaponType = "Dagger", ItemId = 11653, Status = "Unknown" }
|
|
}
|
|
};
|
|
}
|
|
|
|
private void CheckAcquiredWeapons()
|
|
{
|
|
try
|
|
{
|
|
// Get inventory items
|
|
var worldFilter = CoreManager.Current.WorldFilter;
|
|
var inventoryItems = worldFilter.GetInventory().Cast<WorldObject>().ToList();
|
|
|
|
// Check each weapon category
|
|
foreach (var category in WeaponCategories)
|
|
{
|
|
foreach (var weapon in category.Value)
|
|
{
|
|
// Check if weapon is acquired by name matching
|
|
var foundItem = inventoryItems.FirstOrDefault(item =>
|
|
item.Name.Contains(weapon.Name) ||
|
|
weapon.Name.Contains(item.Name));
|
|
|
|
if (foundItem != null)
|
|
{
|
|
weapon.IsAcquired = true;
|
|
weapon.Status = "Acquired";
|
|
weapon.IconId = foundItem.Icon + 0x6000000;
|
|
}
|
|
else
|
|
{
|
|
weapon.IsAcquired = false;
|
|
weapon.Status = "Not Acquired";
|
|
weapon.IconId = 0x6002D14; // Default icon
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error checking acquired weapons: {ex.Message}");
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Cleanup
|
|
public void Dispose()
|
|
{
|
|
// Cleanup if needed
|
|
}
|
|
#endregion
|
|
}
|
|
} |