The game mechanics doesn't allow to use damage skills on allies and heal or protection skills on foes. This makes it necessary that the game can identify allies and foes. A simple if statement seems to be sufficient for that, but it's not. There can be many different combinations of relations between the groups in a game, see the image.
So there are different groups which have different relations to each other. So how to solve this problem? One could have the idea to maintain a list of friend and foe groups, but well, not really, because that's probably the most inefficient way to do this. Instead I chose to use a Bit mask which encodes the friends as well as the foes. The Actor class was extended as shown bellow:
class Actor
{
private:
/// Lower 16 bit friends, upper 16 bit foes
/// This gives us 3 types of relation: (1) friend, (2) foe and (3) neutral
/// and (4) in theory, both but that would be silly.
unsigned groupMask_{ 0 };
/// Get lower 16 bits of the group mask
unsigned GetFriendMask() const { return groupMask_ & 0xffff; }
/// Get upper 16 bits of the group mask
unsigned GetFoeMask() const { return groupMask_ >> 16; }
public:
void AddFriendFoe(unsigned frnd, unsigned foe)
{
groupMask_ |= (frnd | (foe << 16));
}
void RemoveFriendFoe(uint32_t frnd, uint32_t foe)
{
groupMask_ &= ~(frnd | (foe << 16));
}
bool IsAlly(const Actor* other) const
{
// Return true if they have matching bits in the friend mask
return ((GetFriendMask() & other->GetFriendMask()) != 0);
}
bool IsEnemy(const Actor* other) const
{
// Return true if we have a matching bit of our foe mask in their friend mask
return ((GetFoeMask() & other->GetFriendMask()) != 0);
}
};
This makes it possible to have 16 different groups on one map, which have different relations to each other. To define who is friend with whom and who is an enemy of whom, only the AddFriendFoe() method has to be called when the Actor is created:
void CreateActorsForGame()
{
static const unsigned GROUPMASK_NONE = 0;
static const unsigned GROUPMASK_1 = 1;
static const unsigned GROUPMASK_2 = 1 << 1;
static const unsigned GROUPMASK_3 = 1 << 2;
static const unsigned GROUPMASK_4 = 1 << 3;
static const unsigned GROUPMASK_5 = 1 << 4;
static const unsigned GROUPMASK_6 = 1 << 5;
static const unsigned GROUPMASK_7 = 1 << 7;
static const unsigned GROUPMASK_8 = 1 << 8;
// Can have up to 8 more
static const unsigned GROUPMASK_ALL = 65536;
Actor trojan1;
trojan1.AddFriendFoe(GROUPMASK_1, GROUPMASK_2 | GROUPMASK_3);
Actor trojan2;
trojan2.AddFriendFoe(GROUPMASK_1, GROUPMASK_2 | GROUPMASK_3);
Actor pacifist;
pacifist.AddFriendFoe(0, 0);
Actor roman;
roman.AddFriendFoe(GROUPMASK_2, GROUPMASK_1 | GROUPMASK_3);
Actor player;
player.AddFriendFoe(GROUPMASK_3, GROUPMASK_1 | GROUPMASK_2);
}
In terms of? There are tradeoffs to different methods, that might be worth mentioning/exploring. The list method may allow for a generic implementation of "relationships" between groups. Have you actually benchmarked/measured any of it? Even if yours is faster, don't under-estimate the value of using collections, in any language that has expressive methods/functions for them. Coding efficiency must also be balanced and taken into consideration.
Also, as you listed, your system has the potentially invalid/nonsensical state where two groups could be both friend and foe. In general, the possibility for invalid state should be avoided.