Skip to content

[IDEA] Custom/Dynamic Team and Faction System #376

@xaze1

Description

@xaze1

Goal Description

Right now Teams and Factions are enums, which makes them pretty limiting to work with - especially for plugins.

It would be really nice if these were class-based instead, so they can be extended dynamically. That way plugin devs could create their own teams/factions and have them properly handled by the base game instead of hacking around the existing ones.

At the moment, if you want custom roles or groups, you’re basically forced to reuse existing Teams/Factions or do a bunch of workaround logic.

Switching to a class-based system would:

  • Allow fully custom teams/factions
  • Make relationships between teams more flexible (not just “same faction = friendly”)
  • Reduce the need for plugins to override or patch core logic

You could still keep the existing enums internally and map them (BaseGameTeam / BaseGameFaction) for compatibility
Default behavior would stay the same unless overridden
This would make things way cleaner for plugins that add custom roles/teams

Notes

Rough Code Example

// Custom Faction
public interface ICustomFaction
{
    public string DisplayName { get; }
    public string FactionId { get; }
    public Faction BaseGameFaction { get; }
}

// Custom Team
public interface ICustomTeam
{
    public string DisplayName { get; }
    public string TeamId { get; }
    public Team BaseGameTeam { get; }
    public ICustomFaction Faction { get; }
    
    public bool IsHostileTo(ICustomTeam other);
}

// Custom Team Base class
public abstract class CustomTeamBase : ICustomTeam
{
    public abstract string DisplayName { get; }
    public abstract string TeamId { get; }
    public abstract Team BaseGameTeam { get; }
    public abstract ICustomFaction Faction { get; }
    
    public virtual bool IsHostileTo(ICustomTeam other)
    {
        return Faction.FactionId != other.Faction.FactionId;
    }
}

Enemy checking

public static ICustomTeam GetTeam(ReferenceHub player)
{
    if (player == null || AudioManager.ActiveFakes.Contains(player))
    {
        return new DeadTeam();
    }

    if (ActiveManagers.TryGetValue(player.netId, out var manager) && manager._anySet)
    {
        return manager.CurrentRole?.Team?? new DeadTeam();
    }

    return player.GetTeam().ToCustomTeam();
}

public static bool IsEnemy(ReferenceHub attacker, ReferenceHub target)
{
    return IsEnemy(GetTeam(attacker), GetTeam(target));
}

public static bool IsEnemy(ICustomTeam attacker, ICustomTeam target)
{
    if (attacker is DeadTeam || target is DeadTeam)
    {
        return false;
    }
    
    return attacker.IsHostileTo(target) || target.IsHostileTo(attacker);
}

Base Game Team and Faction Example

public class UnclassifiedFaction : ICustomFaction
{
    public string DisplayName => "Unclassified";
    public string FactionId => nameof(Faction.Unclassified);
    public Faction BaseGameFaction => Faction.Unclassified;
}

public class DeadTeam : CustomTeamBase
{
    public override string DisplayName => "Dead";
    public override string TeamId => nameof(Team.Dead);
    public override Team BaseGameTeam => Team.Dead;
    public override ICustomFaction Faction => new UnclassifiedFaction();
    
    public override bool IsHostileTo(ICustomTeam other)
    {
        return false;
    }
}

The Roles would access the Team and Faction like this:

public virtual ICustomTeam Team => new OtherAliveTeam();
public virtual ICustomFaction Faction => Team.Faction;

You could technically remove the Faction variable, since the Team would already have it

Metadata

Metadata

Assignees

No one assigned

    Labels

    ideaThis is an idea to do

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions