PaperMC / Paper

The most widely used, high performance Minecraft server that aims to fix gameplay and mechanics inconsistencies
https://papermc.io/
Other
9.97k stars 2.31k forks source link

Mob#setAggressive() Should Modify Anger/Goals #11360

Open MegaTophat opened 2 months ago

MegaTophat commented 2 months ago

Expected behavior

When running the following code on a Player p:

private static void stopSwarming(final Player player) {
    for (final World world : player.getServer().getWorlds()) {
        for (final Mob mob : world.getEntitiesByClass(Mob.class)) {
            if (!player.equals(mob.getTarget())) {
                continue;
            }

            mob.setTarget(null);
            mob.setAggressive(false);
        }
    }
}

I expect the anger/pendingTarget Goals of that Mob entity to be changed internally to also remove the target and their aggression from their memories. Below is an example of what I would expect the "anger" Goal of an Enderman to look like after this method is invoked: image Notice that the "pendingTarget" field is null.

This stops the Mob from immediately becoming aggressive again for any anger based mob(Enderman, Pigman, Wolf, etc)

Observed/Actual behavior

The Enderman I was testing on all had their Enderman#getTarget() set to null, but did not have their targetSelector.pendingTarget set to null, meaning the next tick they would immediately re-aggro onto the same player.

Steps/models to reproduce

Go to the end/nether. Run this code on any given Player

private static void aggroOn(final Player player) {
    final Collection<Mob> mobs = player.getLocation().getNearbyEntitiesByType(Mob.class, 100.0, 20.0);

    mobs.forEach(mob -> {
        mob.setAggressive(true);
        mob.setTarget(player);
    }
}

Then give it a couple of ticks for the anger to fully settle in. Then run this method on the same Player

for (final Mob mob : world.getEntitiesByClass(Mob.class)) {
    if (!player.equals(mob.getTarget())) {
        continue;
    }

    mob.setTarget(null);
    mob.setAggressive(false);
}

The mobs will stop attacking for one tick then most will immediately resume attacking on the next tick

Plugin and Datapack List

pl [12:33:13 INFO]: Server Plugins (1): [12:33:13 INFO]: Paper Plugins: [12:33:13 INFO]: - SandboxPlugin

SandboxPlugin is the plugin I'm working on where this code is present

datapack list [12:34:11 INFO]: There are 3 data pack(s) enabled: [vanilla (built-in)], [file/bukkit (world)], [paper (built-in)] [12:34:11 INFO]: There are no more data packs available

Paper version

version [12:31:50 INFO]: Checking version, please wait... [12:31:51 INFO]: This server is running Paper version 1.21.1-57-master@b483da4 (2024-09-01T18:09:05Z) (Implementing API version 1.21.1-R0.1-SNAPSHOT) You are running the latest version Previous version: 1.21.1-56-227c94a (MC: 1.21.1)

Other

There appears to be infrastructure that could fix this somewhat simply, but would require work for every individual Mob. This is currently what the NMS setAggressive() method does

public void setAggressive(boolean attacking) {
        byte b0 = (Byte) this.entityData.get(Mob.DATA_MOB_FLAGS_ID);

        this.entityData.set(Mob.DATA_MOB_FLAGS_ID, attacking ? (byte) (b0 | 4) : (byte) (b0 & -5));
    }

Maybe each extension of the Mob abstract class could override this method and invoke super.setAggressive(), then null out any pending aggression/universal anger goals?

electronicboy commented 2 months ago

from what I recall there was a similar issue for this in the past which was a "won't fix", as the only real solution was a state shotgun

Owen1212055 commented 2 months ago

Yeah, I agree this should be a won’t fix. Use goal api if you wanna control this.