Well, this has been a pain in my ass for the last few days, but I finally got it all worked out.
We're using a database to store vendors, mailboxes, and the items they have. We store the faction ID for each vendor (it *was* to make things easier, but it turned out to be the opposite, then turned out to be the best way to go in the end.)
Basically we all know every unit has a FACTIONTEMPLATE descriptor, which points to the FactionTemplate.dbc record. The issue for me, was figuring out how to properly use it from C#. Long story short; I got it figured out, and the code is a bit ugly. It's an almost direct copy->paste from WoW's code, so it should work for all.
Includes the ability to 'traverse' the faction tree, going up to each parent faction, etc.
It also includes the faction comparison (best used at runtime) to decide whether another unit is friendly/hostile/neutral/etc.
Firstly; don't give me the 'use UnitRelation!' crap. If all you have are the faction IDs, and not the actual unit objects, you're up shits creek without a paddle, unless you know how Blizz calculates the faction relations.
Anyhow, here's the code, please let me know of anything I missed. (I intentionally left out friendly/hostile faction stuff, since the comparison funcs are there.)
Code:
using System.Runtime.InteropServices;
namespace Onyx.WoW
{
public class WoWFaction
{
private readonly FactionTemplateDbcRecord _template;
private FactionDbcRecord _record;
public WoWFaction(int id) : this(id, true)
{
}
internal WoWFaction(int id, bool isTemplate)
{
Id = id;
if (IsValid)
{
if (isTemplate)
{
_template = OnyxWoW.Db[ClientDb.FactionTemplate].GetRow(id).GetStruct<FactionTemplateDbcRecord>();
_record = OnyxWoW.Db[ClientDb.Faction].GetRow(_template.FactionId).GetStruct<FactionDbcRecord>();
}
else
{
_record = OnyxWoW.Db[ClientDb.Faction].GetRow(id).GetStruct<FactionDbcRecord>();
}
}
}
public int Id { get; private set; }
public string Name { get { return _record.Name; } }
public string Description { get { return _record.Description; } }
public WoWFaction ParentFaction { get { return new WoWFaction(_record.ParentFaction, false); } }
public bool IsValid { get { return Id != 0; } }
public WoWUnitRelation RelationTo(WoWFaction other)
{
return CompareFactions(this, other);
}
public static WoWUnitRelation CompareFactions(WoWFaction factionA, WoWFaction factionB)
{
FactionTemplateDbcRecord atmpl = factionA._template;
FactionTemplateDbcRecord btmpl = factionB._template;
if ((btmpl.FightSupport & atmpl.HostileMask) != 0)
{
return (WoWUnitRelation) 1;
}
for (int i = 0; i < 4; i++)
{
if (atmpl.EnemyFactions[i] == btmpl.Id)
{
return (WoWUnitRelation) 1;
}
if (atmpl.EnemyFactions[i] == 0)
{
break;
}
}
if ((btmpl.FightSupport & atmpl.FriendlyMask) != 0)
{
return (WoWUnitRelation) 4;
}
for (int i = 0; i < 4; i++)
{
if (atmpl.FriendlyFactions[i] == btmpl.Id)
{
return (WoWUnitRelation) 4;
}
if (atmpl.FriendlyFactions[i] == 0)
{
break;
}
}
if ((atmpl.FightSupport & btmpl.FriendlyMask) != 0)
{
return (WoWUnitRelation) 4;
}
for (int i = 0; i < 4; i++)
{
if (btmpl.FriendlyFactions[i] == atmpl.Id)
{
return (WoWUnitRelation) 4;
}
if (btmpl.FriendlyFactions[i] == 0)
{
break;
}
}
return (WoWUnitRelation) (~(byte) ((uint) atmpl.FactionFlags >> 12) & 2 | 1);
}
public override string ToString()
{
if (!IsValid)
{
return "N/A";
}
return Name + ", Parent: " + ParentFaction;
}
#region Nested type: FactionDbcRecord
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private struct FactionDbcRecord
{
public int Id;
private int _unk0;
public int Allied;
public int AtWar;
private int _unk1;
private int _unk2;
private int _unk3;
private int _unk4;
private int _unk5;
private int _unk6;
public int Reputation;
public int Mod1;
public int Mod2;
public int Mod3;
private int _unk7;
private int _unk8;
private int _unk9;
private int _unk10;
public int ParentFaction;
// 4 unknowns added recently. Cba to figure out what they're for
// since I have no use for them!
private int _unk11;
private int _unk12;
private int _unk13;
private int _unk14;
[MarshalAs(UnmanagedType.LPStr)]
public string Name;
[MarshalAs(UnmanagedType.LPStr)]
public string Description;
}
#endregion
#region Nested type: FactionTemplateDbcRecord
[StructLayout(LayoutKind.Sequential)]
private struct FactionTemplateDbcRecord
{
public int Id;
public int FactionId;
public int FactionFlags;
public int FightSupport;
public int FriendlyMask;
public int HostileMask;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] EnemyFactions;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] FriendlyFactions;
}
#endregion
}
}