CitizensDev / Citizens2

Citizens - the premier plugin and API for creating server-side NPCs in Minecraft.
https://citizensnpcs.co
Open Software License 3.0
567 stars 296 forks source link

NPC name doesn't update correctly in different screnarios #1752

Closed Sago92 closed 4 years ago

Sago92 commented 5 years ago

The output of command /version on my server is: Screenshot3 The output of command /version citizens on my server is: Screenshot4 Citizens configuration & latest server log file: click

Dear Development-Team,

I am using the engine/plugin/api of Citizens to control and create different NPC's on my in progress MMORPG Server.

I've created some own traits. For example to archive a friendly/aggressive or neutral behavior or for exmaple a wander trait where an NPC walks randomly in a defined area.

Now I have the issue, that my custom NPC names won't show up in the following scenarios. Scenario 1: When the NPC is navigating directly after it was spawned Scenario 2: When a NPC gets into a fight after spawned Scenario 3: When you moving from far away into the area where NPCs are fighting each other

As you can see in the following screenshots the wolf and the sheep are in a fight and the custom name doesn't show up.

Screenshot1 Screenshot2

I am using different color codes in the name and figured out that setting the name of the NPC is slow because of the internal respawn (~1-3 seconds). I am using this piece of code to change the name of the NPC

// Updates the entity display name
private void updateEntityDisplayName() {
    // Gets the color based on the attitude
    ChatColor nameColor = ColorHelper.attitudeColor.get(definition.getAttitude());

    // Splits the entity name by spaces, because client side names need the color code before every part of the name
    String[] splittedName = definition.getName().split(" ");

    // Combines back the name with the needed color code
    String entityName = "";
    for (String namePart : splittedName)
        entityName += nameColor + namePart + " ";

    npc.setName(definition.getRolePrefix() + entityName);
}

When a entitys health reached zero (custom health management) the kill method will get invoked

// Kills the entity
@Override
public void kill() {
    if (npc.isSpawned() && !isDead) {
        isDead = true;
    LivingEntity livingEntity = (LivingEntity) npc.getEntity();
    livingEntity.setHealth(0);
    health = 0;
    npc.despawn(DespawnReason.DEATH);
    aggro.clear();

    // Cancels the old in fight reset task
    if (inFightResetTaskID != 0)
        Bukkit.getScheduler().cancelTask(inFightResetTaskID);

    // Triggers the leave fight event
    isInFight = false;
    ConquestEntityLeaveFightEvent eventData = new ConquestEntityLeaveFightEvent(this);
    listeners.forEach(x -> x.conquestEntityLeaveFight(eventData));
    }
}

After a specified defined of time the respawn logic calls the spawn method

// Spawns the entity
@Override
public void spawn() {
    if (!npc.isSpawned() && isDead) {
        isDead = false;
    health = definition.getMaxHealth();
    npc.spawn(detail.getSpawnLocation());
    aggro.clear();
    spawnTime = System.currentTimeMillis();
    }
}

Finally my setup method after I created an NPC

// Setups the default NPC data
private void setup() {
    // Updates the display name of the entity
    updateEntityDisplayName();

    // Adds the friendly behavior trait to the NPC if its attitude type is friendly and not already attachedd
    if (definition.getAttitude() == EntityAttitudeType.Friendly && !npc.hasTrait(FriendlyBehaviorTrait.class)) {
        FriendlyBehaviorTrait friendlyBehaviorTrait = new FriendlyBehaviorTrait(this);
    npc.addTrait(friendlyBehaviorTrait);
    } else if (npc.hasTrait(FriendlyBehaviorTrait.class))
        // Removes the friendly behavior trait because the attitude type of the entity changed to another
    npc.removeTrait(FriendlyBehaviorTrait.class);

    // Adds the neutral behavior trait to the NPC if its attitude type is neutral and not already attachedd
    if (definition.getAttitude() == EntityAttitudeType.Neutral && !npc.hasTrait(NeutralBehaviorTrait.class)) {
        NeutralBehaviorTrait neutralBehaviorTrait = new NeutralBehaviorTrait(this);
    npc.addTrait(neutralBehaviorTrait);
    } else if (npc.hasTrait(NeutralBehaviorTrait.class))
        // Removes the neutral behavior trait because the attitude type of the entity changed to another
    npc.removeTrait(NeutralBehaviorTrait.class);

    // Adds the aggressive behavior trait to the NPC if its attitude type is aggressive and not already attachedd
    if (definition.getAttitude() == EntityAttitudeType.Aggressive && !npc.hasTrait(AggressiveBehaviorTrait.class)) {
        AggressiveBehaviorTrait aggressiveBehaviorTrait = new AggressiveBehaviorTrait(this);
    npc.addTrait(aggressiveBehaviorTrait);
    } else if (npc.hasTrait(AggressiveBehaviorTrait.class))
        // Removes the aggressive behavior trait because the attitude type of the entity changed to another
    npc.removeTrait(AggressiveBehaviorTrait.class);

    // Sets the equipment of the entity
    Equipment equipment = npc.getTrait(Equipment.class);
    ItemStack weaponItem = ItemManager.getItem(definition.getWeaponItem(), RarityType.Common);
    if (weaponItem != null)
        equipment.set(EquipmentSlot.HAND, weaponItem);
    ItemStack helmetItem = ItemManager.getItem(definition.getHelmetItem(), RarityType.Common);
    if (helmetItem != null)
        equipment.set(EquipmentSlot.HELMET, helmetItem);
    ItemStack chestplatItem = ItemManager.getItem(definition.getChestplateItem(), RarityType.Common);
    if (chestplatItem != null)
        equipment.set(EquipmentSlot.CHESTPLATE, chestplatItem);
    ItemStack leggingsItem = ItemManager.getItem(definition.getLeggingsItem(), RarityType.Common);
    if (leggingsItem != null)
        equipment.set(EquipmentSlot.LEGGINGS, leggingsItem);
     ItemStack bootsItem = ItemManager.getItem(definition.getBootsItem(), RarityType.Common);
    if (bootsItem != null)
        equipment.set(EquipmentSlot.BOOTS, bootsItem);

    // Adds the watching move trait and look close trait to the NPC if its move type is watching and not already attachedd
     if (definition.getMoveType() == EntityMoveType.Watching && !npc.hasTrait(WatchingTrait.class)) {
        WatchingTrait watchingTrait = new WatchingTrait(this);
     npc.addTrait(watchingTrait);
     LookClose lookClose = npc.getTrait(LookClose.class);
     lookClose.lookClose(true);
     lookClose.setRange(5);
    } else if (npc.hasTrait(WatchingTrait.class)) {
        // Removes the watching and look close trait because the move type of the entity changed to another
    npc.removeTrait(WatchingTrait.class);
    npc.removeTrait(LookClose.class);
    }

    // Adds the walking move trait to the NPC if its move type is walking and not already attached
    if (definition.getMoveType() == EntityMoveType.Walking && !npc.hasTrait(WalkingTrait.class)) 
    {
        WalkingTrait walkingTrait = new WalkingTrait(this);
    npc.addTrait(walkingTrait);
    } else if (npc.hasTrait(WalkingTrait.class)) {
        // Removes the walking trait and removes the latest goals because the move type of the entity changed to another
    npc.removeTrait(WalkingTrait.class);
    npc.getDefaultGoalController().cancelCurrentExecution();
    npc.getDefaultGoalController().addGoal(new MoveToGoal(npc, detail.getSpawnLocation()), 1);
    }

    // Adds the patrol move trait to the NPC if its move type is patrol and not already attached
    if (definition.getMoveType() == EntityMoveType.Patrol && !npc.hasTrait(PatrolTrait.class)) {
        PatrolTrait patrolTrait = new PatrolTrait(this);
    npc.addTrait(patrolTrait);
    } else if (npc.hasTrait(PatrolTrait.class)) {
        // Removes the patrol trait and removes the latest goals because the move type of the entity changed to another
    npc.removeTrait(PatrolTrait.class);
    npc.getDefaultGoalController().cancelCurrentExecution();
    npc.getDefaultGoalController().addGoal(new MoveToGoal(npc, detail.getSpawnLocation()), 1);
    }

    // Teleports the entity back to its start position
    if (npc.isSpawned())
        npc.getEntity().teleport(detail.getSpawnLocation());

    // Sets the protected property to true so the entity can take damage and dies
    npc.setProtected(false);
}

Do you have any ideas/suggestions or code improvements to solve the above scenarios?

Regards Sascha

mcmonkey4eva commented 5 years ago

Generally you want to spawn the NPC with the correct name from the start, not spawn it and rename it after.

Sago92 commented 5 years ago

Now I changed my logic so that the name will only be set when I create a whole new NPC and I never update or set the name in the rest of my code (spawn, despawn or setup logic).

// Gets the color based on the attitude
ChatColor nameColor = ColorHelper.attitudeColor.get(definition.getAttitude());

// Splits the entity name by spaces, because client side names need the color code before every part of the name
String[] splittedName = definition.getName().split(" ");

// Combines back the name with the needed color code
String entityName = "";
for (String namePart : splittedName)
    entityName += nameColor + namePart + " ";

// Creates the new entity/npc instance
NPC npc = CitizensAPI.getNPCRegistry().createNPC(definition.getType(), definition.getRolePrefix() + entityName);
npc.spawn(spawnLocation);

Nevertheless the NPCs name wont show up after the NPC gets killed, respawns and directly starts a fight or is directly walking after the respawn.

Video1

And the last described scenario when you come from far away and the NPCs are already in a fight the names won't update, too.

Video2

When the NPC stands still for around 1~2 seconds the name will correctly be updated after respawn.

Video3

Do you need any additional information or soruce code snippets?

Regards Sascha

mcmonkey4eva commented 5 years ago

Can you test with a fully up-to-date Spigot 1.13.2 build (make a new one via buildtools), and the latest stable 1.13.2 build - which is this one: https://ci.citizensnpcs.co/job/Citizens2/1636/

Sago92 commented 5 years ago

Issue is still there with the latest version of Spigot and Citizens

Screenshot1 Screenshot2

Sample: Directly walking after spawn:

Video1

fullwall commented 5 years ago

Try latest build.

Sago92 commented 5 years ago

With the latest build all 3 described behaviors are still there. It look like the update of the name won't trigger/work if the NPC is moving (see directly walking after spawn or fight while player ist comming from far away)

Screenshot

fullwall commented 5 years ago

And what's your packet update delay? The point of 1657 was to change that if you look at the commit.

fullwall commented 5 years ago

https://github.com/CitizensDev/Citizens2/commit/cc7d3ae85f09d3b68038546b88d0a7bd506ed9f9

Sago92 commented 5 years ago

My packet update delay is 30. I tried to change it to 5, but it doesn't change anything.

Sago92 commented 5 years ago

I did some additional testing. 1.) The scencario when I come from far away to the NPCs that are fighting, the names are updated correctly now. - Had this issue again 2.) Adding a "spawn protection" from 2 seconds where the NPC don't walk or gets into a fight including the reducing of the packet update delay to 5 seconds updates the name correctly, too

Nevertheless without the delay the name doesn't update when an NPC is directly walking or starting a fight

Sago92 commented 5 years ago

I figured something new out. If the entity type of the spawned entity is a Player the name is directly correct. Only if the entity type is something different like a sheep or a wolf the update appears a bit later

Sago92 commented 4 years ago

Switchted from Spigot to Sponge, because of better entity control. Close this issue because it seems nobody is facing it and nobody is doing the "research to do"...