Ally Designation System - Electroblob77/Wizardry GitHub Wiki
Since: Wizardry 1.1
The ally designation system (ADS) provides a way of controlling the targets of your spells, allowing you to fight alongside your friends more effectively.
To designate a player as your ally, sneak-right-click on them whilst holding any wand. A chat message should appear confirming that the player is now your ally. Repeat this process to remove a player from your allies. Note that you are not automatically made an ally of the player in question; one player can be in another player's allies without that player being in theirs, and vice versa.
Designating a player as an ally has the following effects:
- Allies are not targeted by any minions you summon, unless you use the minion targeting mechanic to target them manually
- Allies are not targeted by any creatures you mind control
- Lightning from your spells does not chain to allies
- Spells which seek targets do not seek your allies
- Allies are affected by group spells such as invigorating presence
- Allies will not trigger magical traps such as snare
- Allies are not damaged by spells that have an area of effect
- However, spells aimed directly at allies will still affect them as normal
If enabled, the friendly fire config option makes allies immune to all damage and negative effects from spells you cast. (Personally, I think this is unbalanced in regular survival, but it's there if you want it - it could also be useful for some sort of minigame...)
You can also interact with the ADS using the /ally and /allies commands; see Commands for details.
The ADS supports the scoreboard team system in vanilla minecraft - players on the same team are automatically allies of each other. This feature overrides the normal behaviour.
This describes how external mods can integrate with Wizardry's ally designation system to make their own summoned or otherwise-owned entities recognised as allies by Wizardry spells, constructs, and minions.
Wizardry's ally system determines two related but distinct things:
| Method | Question answered | Used by |
|---|---|---|
AllyDesignationSystem.isValidTarget(attacker, target) |
Should this attack be blocked? | AI targeting for minions/constructs, seeking projectiles, AoE spells, lightning chains |
AllyDesignationSystem.isAllied(allyOf, possibleAlly) |
Should this buff be applied? | Healing AoE spells, Radiant Totem, friendly-fire event handler |
By default, Wizardry only recognises entities that implement IEntityOwnable (vanilla tamed animals, and Wizardry's own summoned creatures) as being "owned" by a player. If your mod has its own summoned mobs that do not implement IEntityOwnable, Wizardry's minions will attack them even when their summoner is a Wizardry ally — and Wizardry's healing spells will ignore them.
The predicate registry lets you fix this without modifying Wizardry.
You register a lambda (predicate) once during mod initialisation. Wizardry stores it and calls it on every ally check, passing the live entity references. Your lambda can inspect any state — NBT, capabilities, an in-memory map — and return true to declare that the two entities should be treated as allies.
Key properties:
- Zero cost when not registered. If no predicates are registered, the check is guarded by an empty-list test and adds no measurable overhead.
- Additive only. Predicates run after all built-in Wizardry logic. They can add new ally relationships but cannot remove existing ones (direct ownership, scoreboard teams, the player ally list, etc.).
-
Called once per logical query. For
isValidTarget, the recursion that handles owned attackers (e.g. your zombie delegates to its player owner) is resolved before predicates are consulted, so your predicate receives the root attacker, not every link in the ownership chain.
Call the registration methods from your @Mod-annotated class during any FML lifecycle event up to and including FMLPostInitializationEvent. Do not register during or after FMLServerStartingEvent — predicates are expected to be stable for the lifetime of the game session.
import electroblob.wizardry.util.AllyDesignationSystem;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
@Mod(modid = "mymod", ...)
public class MyMod {
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
AllyDesignationSystem.registerValidTargetPredicate(...);
AllyDesignationSystem.registerAllyPredicate(...);
}
}public static void registerValidTargetPredicate(BiPredicate<Entity, Entity> predicate)Predicate signature: (Entity attacker, Entity target) -> boolean
Return true to declare that target is an ally of attacker and must not be attacked. Return false to defer to other predicates or the default result (allow the attack).
This affects AI targeting for all Wizardry minions and constructs, seeking projectiles, AoE spells, and lightning chains.
The recommended approach is to store the owner UUID in a capability attached to your entity. Capability reads are O(1) and allocation-free once the capability object is cached on the entity.
AllyDesignationSystem.registerValidTargetPredicate((attacker, target) -> {
// Read ownership from our capability — returns null if the entity isn't one of ours
IOwnerCapability cap = target.getCapability(MyCapabilities.OWNER, null);
if (cap == null) return false; // not our mob, don't interfere
UUID targetOwnerUUID = cap.getOwnerUUID();
if (targetOwnerUUID == null) return false; // our mob type, but unowned
// If the attacker is the direct owner, never attack
if (attacker instanceof EntityPlayer && ((EntityPlayer) attacker).getUniqueID().equals(targetOwnerUUID))
return true;
// If the attacker is also one of our mobs, compare their owners
IOwnerCapability attackerCap = attacker.getCapability(MyCapabilities.OWNER, null);
if (attackerCap != null && targetOwnerUUID.equals(attackerCap.getOwnerUUID())) return true;
// Cross-player check: is the target's owner a Wizardry ally of the attacker?
// isPlayerAlly is a direct WizardData lookup — not recursive into isValidTarget.
if (attacker instanceof EntityPlayer) {
EntityPlayer ownerOfTarget = EntityUtils.getPlayerByUUID(attacker.world, targetOwnerUUID);
if (ownerOfTarget == null) return false; // owner offline, can't verify
return AllyDesignationSystem.isPlayerAlly((EntityPlayer) attacker, ownerOfTarget);
}
return false;
});Note:
attackeris always the root caster (a player, wizard NPC, or casterless entity) by the time your predicate is invoked. You do not need to unwrap ownership chains yourself.
public static void registerAllyPredicate(BiPredicate<EntityLivingBase, EntityLivingBase> predicate)Predicate signature: (EntityLivingBase allyOf, EntityLivingBase possibleAlly) -> boolean
Return true to declare that possibleAlly is allied with allyOf. Return false to defer.
This affects all systems that call isAllied, including:
- Healing AoE spells
- Radiant Totem (
EntityRadiantTotem) — determines which nearby entities receive heals - The friendly-fire event handler — determines which entities are shielded from accidental magic damage
AllyDesignationSystem.registerAllyPredicate((allyOf, possibleAlly) ->
MyFactionMod.sameFaction(allyOf, possibleAlly)
);AllyDesignationSystem.registerAllyPredicate((allyOf, possibleAlly) -> {
// Read ownership from our capability
IOwnerCapability cap = possibleAlly.getCapability(MyCapabilities.OWNER, null);
if (cap == null) return false; // not our mob
UUID ownerUUID = cap.getOwnerUUID();
if (ownerUUID == null) return false;
// Heal if allyOf is the direct owner
if (allyOf instanceof EntityPlayer && ((EntityPlayer) allyOf).getUniqueID().equals(ownerUUID)) return true;
// Heal if allyOf is a Wizardry ally of the owner
// isPlayerAlly is a direct WizardData lookup — not recursive into isAllied.
if (allyOf instanceof EntityPlayer) {
EntityPlayer owner = EntityUtils.getPlayerByUUID(allyOf.world, ownerUUID);
return owner != null && AllyDesignationSystem.isPlayerAlly((EntityPlayer) allyOf, owner);
}
return false;
});In most cases you will want to register both predicates, since the two checks are independent. A mob being protected from attack (isValidTarget) does not automatically make it eligible for healing (isAllied), and vice versa.
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
AllyDesignationSystem.registerValidTargetPredicate(MyMod::isAllyForTargeting);
AllyDesignationSystem.registerAllyPredicate(MyMod::isAllyForBuffing);
}Predicates are called thousands of times per second in a busy game (once per entity pair per AI targeting tick). Keep them fast:
-
Do store the owner UUID in a capability on your entity —
getCapabilityis O(1) and allocation-free once the capability object exists on the entity. -
Do not call
entity.readFromNBT(...), deserialise NBT tags, perform world queries, or allocate objects inside the lambda. -
Do not call
isValidTargetorisAlliedfrom inside a predicate — this re-enters the method and will cause a stack overflow.isPlayerAllyis safe because it is a directWizardDatalookup that does not re-enter the predicate loop.
A minimal capability interface for ownership looks like:
public interface IOwnerCapability {
UUID getOwnerUUID();
void setOwnerUUID(UUID uuid);
}Set the UUID when the mob spawns (e.g. in your Spell.cast() method) and Forge will handle serialisation to NBT automatically via your capability's IStorage implementation.
Simpler alternative: If your mod does not use capabilities, a
WeakHashMap<Entity, UUID>populated at spawn time is also O(1) and requires no explicit cleanup — dead entities are garbage-collected automatically. It is less robust across save/load cycles than a capability, however.
If your mod lists Wizardry as a soft dependency (after = "ebwizardry" in @Mod), wrap the registration in a guard so it only runs when Wizardry is present:
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
if (Loader.isModLoaded("ebwizardry")) {
AllyDesignationSystem.registerValidTargetPredicate(MyMod::isAllyForTargeting);
AllyDesignationSystem.registerAllyPredicate(MyMod::isAllyForBuffing);
}
}