Pulse.Team
Pulse.Team gives you a resolver pattern for friend/enemy classification. Instead of writing the same team-check conditions in every component, you define the rules once and share them.
The problem
Without a resolver, every feature that cares about who's an enemy has its own version of:
if player.Team ~= LocalPlayer.Team
and player ~= LocalPlayer
and player.Character ~= nil
and not isAllySpecialCase(player)
then
-- it's an enemy
end
Duplicated 6 times = 6 places to update when your logic changes. A resolver centralises this.
Creating a resolver
local resolver = Pulse.Team.resolver({
isSelf = function(p) return p == _LocalPlayer end,
isValid = function(p) return p.Character ~= nil end,
isHostile = function(p)
-- true = this entity should be targeted
return func.IsOnWarriorsTeam() ~= func.IsWarrior(p)
end,
exclude = {
-- array of functions; if any returns true, skip this entity
func.IsAllySpecialCase,
},
})
All fields are optional. The resolver applies them in order:
- Skip if
isSelfreturns true - Skip if
isValidreturns false - Skip if any
excludefunction returns true - If
isHostilereturns true → enemy
Using a resolver
resolver:isEnemy(player) -- → bool: is this specific entity an enemy?
resolver:filter(playerList) -- → array of enemies only
resolver:partition(playerList) -- → { enemies = {}, friendlies = {} }
The recommended pattern: define in globals.lua, share via func
-- src/misc/helpers/globals.lua
local _playerResolver = Pulse.Team.resolver({
isSelf = function(p) return p == _LocalPlayer end,
isValid = function(p) return p.Character ~= nil end,
isHostile = function(p) return func.IsOnWarriorsTeam() ~= func.IsWarrior(p) end,
exclude = { func.IsParamountShifterFriendly },
})
func.IsEnemy = function(p) return _playerResolver:isEnemy(p) end
func.PlayerResolver = _playerResolver
Then in any component:
for _, p in ipairs(func.PlayerResolver:filter(func.GetCachedPlayers())) do
-- only enemies
end
The exclude array
exclude is checked after isHostile. If ANY exclude function returns true, the entity is treated as friendly — even if isHostile is also true. This is how you handle "always ally" special cases:
exclude = {
func.IsOurOwnShifter, -- never target yourself when shifted
func.ShouldIgnoreAttackTitan, -- special ally that uses the enemy team
}
Limitations
- Resolvers are pure functions — they don't track state. The result can change between calls if the underlying team state changes (e.g., a player switches teams mid-game).
partitioniterates the full list. For very large lists (hundreds of entities), considerfilter+ your own logic if you only need one partition.