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.

Addon API

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.


Background

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.


How it works

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.

Registering predicates

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(...);
    }
}

registerValidTargetPredicate

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.

Example — protecting minions of allied players

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: attacker is 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.


registerAllyPredicate

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

Example — treating faction members as allies

AllyDesignationSystem.registerAllyPredicate((allyOf, possibleAlly) ->
    MyFactionMod.sameFaction(allyOf, possibleAlly)
);

Example — making your summoned mobs eligible for Wizardry healing

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;
});

Registering both together

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);
}

Performance guidance

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 — getCapability is 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 isValidTarget or isAllied from inside a predicate — this re-enters the method and will cause a stack overflow. isPlayerAlly is safe because it is a direct WizardData lookup 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.


Soft dependency

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);
    }
}
⚠️ **GitHub.com Fallback** ⚠️