forked from GeyserMC/Geyser
		
	Add Ender Dragon effects and sounds (#1781)
* Add Ender Dragon effects and sounds * Add proper death effect and clean up * Add Ender Dragon respawn sound * Possibly fix dragon breath direction? * Update mappings * Fix death animation triggering at low health * Trigger death event when health is 0 and add explosions back * Add comment
This commit is contained in:
		
							parent
							
								
									ade4c14911
								
							
						
					
					
						commit
						9232c5565f
					
				
					 3 changed files with 183 additions and 41 deletions
				
			
		| 
						 | 
				
			
			@ -28,15 +28,26 @@ package org.geysermc.connector.entity.living.monster;
 | 
			
		|||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
 | 
			
		||||
import com.nukkitx.math.vector.Vector3f;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.data.AttributeData;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.*;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.geysermc.connector.entity.Tickable;
 | 
			
		||||
import org.geysermc.connector.entity.attribute.AttributeType;
 | 
			
		||||
import org.geysermc.connector.entity.living.InsentientEntity;
 | 
			
		||||
import org.geysermc.connector.entity.type.EntityType;
 | 
			
		||||
import org.geysermc.connector.network.session.GeyserSession;
 | 
			
		||||
import org.geysermc.connector.utils.AttributeUtils;
 | 
			
		||||
import org.geysermc.connector.utils.DimensionUtils;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
import java.util.concurrent.ThreadLocalRandom;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicLong;
 | 
			
		||||
 | 
			
		||||
public class EnderDragonEntity extends InsentientEntity implements Tickable {
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +70,19 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
 | 
			
		|||
    private final Segment[] segmentHistory = new Segment[19];
 | 
			
		||||
    private int latestSegment = -1;
 | 
			
		||||
 | 
			
		||||
    private boolean hovering;
 | 
			
		||||
    private int phase;
 | 
			
		||||
    /**
 | 
			
		||||
     * The number of ticks since the beginning of the phase
 | 
			
		||||
     */
 | 
			
		||||
    private int phaseTicks;
 | 
			
		||||
 | 
			
		||||
    private int ticksTillNextGrowl = 100;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Used to determine when the wing flap sound should be played
 | 
			
		||||
     */
 | 
			
		||||
    private float wingPosition;
 | 
			
		||||
    private float lastWingPosition;
 | 
			
		||||
 | 
			
		||||
    public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
 | 
			
		||||
        super(entityId, geyserId, entityType, position, motion, rotation);
 | 
			
		||||
| 
						 | 
				
			
			@ -69,49 +92,67 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
 | 
			
		||||
        // Phase
 | 
			
		||||
        if (entityMetadata.getId() == 15) {
 | 
			
		||||
            int value = (int) entityMetadata.getValue();
 | 
			
		||||
            if (value == 5) {
 | 
			
		||||
                // Performing breath attack
 | 
			
		||||
        if (entityMetadata.getId() == 15) { // Phase
 | 
			
		||||
            phase = (int) entityMetadata.getValue();
 | 
			
		||||
            phaseTicks = 0;
 | 
			
		||||
            metadata.getFlags().setFlag(EntityFlag.SITTING, isSitting());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        super.updateBedrockMetadata(entityMetadata, session);
 | 
			
		||||
 | 
			
		||||
        if (entityMetadata.getId() == 8) { // Health
 | 
			
		||||
            // Update the health attribute, so that the death animation gets played
 | 
			
		||||
            // Round health up, so that Bedrock doesn't consider the dragon to be dead when health is between 0 and 1
 | 
			
		||||
            float health = (float) Math.ceil(metadata.getFloat(EntityData.HEALTH));
 | 
			
		||||
            if (phase == 9 && health <= 0) { // Dying phase
 | 
			
		||||
                EntityEventPacket entityEventPacket = new EntityEventPacket();
 | 
			
		||||
                entityEventPacket.setType(EntityEventType.DRAGON_FLAMING);
 | 
			
		||||
                entityEventPacket.setType(EntityEventType.ENDER_DRAGON_DEATH);
 | 
			
		||||
                entityEventPacket.setRuntimeEntityId(geyserId);
 | 
			
		||||
                entityEventPacket.setData(0);
 | 
			
		||||
                session.sendUpstreamPacket(entityEventPacket);
 | 
			
		||||
            }
 | 
			
		||||
            metadata.getFlags().setFlag(EntityFlag.SITTING, value == 5 || value == 6 || value == 7);
 | 
			
		||||
            hovering = value == 10;
 | 
			
		||||
            attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(health, 200));
 | 
			
		||||
            updateBedrockAttributes(session);
 | 
			
		||||
        }
 | 
			
		||||
        super.updateBedrockMetadata(entityMetadata, session);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send an updated list of attributes to the Bedrock client.
 | 
			
		||||
     * This is overwritten to allow the health attribute to differ from
 | 
			
		||||
     * the health specified in the metadata.
 | 
			
		||||
     *
 | 
			
		||||
     * @param session GeyserSession
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateBedrockAttributes(GeyserSession session) {
 | 
			
		||||
        if (!valid) return;
 | 
			
		||||
 | 
			
		||||
        List<AttributeData> attributes = new ArrayList<>();
 | 
			
		||||
        for (Map.Entry<AttributeType, org.geysermc.connector.entity.attribute.Attribute> entry : this.attributes.entrySet()) {
 | 
			
		||||
            if (!entry.getValue().getType().isBedrockAttribute())
 | 
			
		||||
                continue;
 | 
			
		||||
            attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket();
 | 
			
		||||
        updateAttributesPacket.setRuntimeEntityId(geyserId);
 | 
			
		||||
        updateAttributesPacket.setAttributes(attributes);
 | 
			
		||||
        session.sendUpstreamPacket(updateAttributesPacket);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void spawnEntity(GeyserSession session) {
 | 
			
		||||
        AddEntityPacket addEntityPacket = new AddEntityPacket();
 | 
			
		||||
        addEntityPacket.setIdentifier("minecraft:" + entityType.name().toLowerCase());
 | 
			
		||||
        addEntityPacket.setRuntimeEntityId(geyserId);
 | 
			
		||||
        addEntityPacket.setUniqueEntityId(geyserId);
 | 
			
		||||
        addEntityPacket.setPosition(position);
 | 
			
		||||
        addEntityPacket.setMotion(motion);
 | 
			
		||||
        addEntityPacket.setRotation(getBedrockRotation());
 | 
			
		||||
        addEntityPacket.setEntityType(entityType.getType());
 | 
			
		||||
        addEntityPacket.getMetadata().putAll(metadata);
 | 
			
		||||
        super.spawnEntity(session);
 | 
			
		||||
 | 
			
		||||
        // Otherwise dragon is always 'dying'
 | 
			
		||||
        addEntityPacket.getAttributes().add(new AttributeData("minecraft:health", 0.0f, 200f, 200f, 200f));
 | 
			
		||||
 | 
			
		||||
        valid = true;
 | 
			
		||||
        session.sendUpstreamPacket(addEntityPacket);
 | 
			
		||||
 | 
			
		||||
        head = new EnderDragonPartEntity(entityId + 1, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 1, 1);
 | 
			
		||||
        neck = new EnderDragonPartEntity(entityId + 2, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 3, 3);
 | 
			
		||||
        body = new EnderDragonPartEntity(entityId + 3, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 5, 3);
 | 
			
		||||
        leftWing = new EnderDragonPartEntity(entityId + 4, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2);
 | 
			
		||||
        rightWing = new EnderDragonPartEntity(entityId + 5, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2);
 | 
			
		||||
        AtomicLong nextEntityId = session.getEntityCache().getNextEntityId();
 | 
			
		||||
        head = new EnderDragonPartEntity(entityId + 1, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 1, 1);
 | 
			
		||||
        neck = new EnderDragonPartEntity(entityId + 2, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 3, 3);
 | 
			
		||||
        body = new EnderDragonPartEntity(entityId + 3, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 5, 3);
 | 
			
		||||
        leftWing = new EnderDragonPartEntity(entityId + 4, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 4, 2);
 | 
			
		||||
        rightWing = new EnderDragonPartEntity(entityId + 5, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 4, 2);
 | 
			
		||||
        tail = new EnderDragonPartEntity[3];
 | 
			
		||||
        for (int i = 0; i < 3; i++) {
 | 
			
		||||
            tail[i] = new EnderDragonPartEntity(entityId + 6 + i, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 2, 2);
 | 
			
		||||
            tail[i] = new EnderDragonPartEntity(entityId + 6 + i, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 2, 2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]};
 | 
			
		||||
| 
						 | 
				
			
			@ -125,8 +166,6 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
 | 
			
		|||
            segmentHistory[i].yaw = rotation.getZ();
 | 
			
		||||
            segmentHistory[i].y = position.getY();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -139,8 +178,11 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void tick(GeyserSession session) {
 | 
			
		||||
        pushSegment();
 | 
			
		||||
        updateBoundingBoxes(session);
 | 
			
		||||
        effectTick(session);
 | 
			
		||||
        if (!metadata.getFlags().getFlag(EntityFlag.NO_AI) && isAlive()) {
 | 
			
		||||
            pushSegment();
 | 
			
		||||
            updateBoundingBoxes(session);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			@ -158,7 +200,7 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
 | 
			
		|||
 | 
			
		||||
        // Lowers the head when the dragon sits/hovers
 | 
			
		||||
        float headDuck;
 | 
			
		||||
        if (hovering || metadata.getFlags().getFlag(EntityFlag.SITTING)) {
 | 
			
		||||
        if (isHovering() || isSitting()) {
 | 
			
		||||
            headDuck = -1f;
 | 
			
		||||
        } else {
 | 
			
		||||
            headDuck = baseSegment.y - getSegment(0).y;
 | 
			
		||||
| 
						 | 
				
			
			@ -188,6 +230,105 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the particles and sounds of the Ender Dragon
 | 
			
		||||
     * @param session GeyserSession.
 | 
			
		||||
     */
 | 
			
		||||
    private void effectTick(GeyserSession session) {
 | 
			
		||||
        Random random = ThreadLocalRandom.current();
 | 
			
		||||
        if (!metadata.getFlags().getFlag(EntityFlag.SILENT)) {
 | 
			
		||||
            if (Math.cos(wingPosition * 2f * Math.PI) <= -0.3f && Math.cos(lastWingPosition * 2f * Math.PI) >= -0.3f) {
 | 
			
		||||
                PlaySoundPacket playSoundPacket = new PlaySoundPacket();
 | 
			
		||||
                playSoundPacket.setSound("mob.enderdragon.flap");
 | 
			
		||||
                playSoundPacket.setPosition(position);
 | 
			
		||||
                playSoundPacket.setVolume(5f);
 | 
			
		||||
                playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f);
 | 
			
		||||
                session.sendUpstreamPacket(playSoundPacket);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!isSitting() && !isHovering() && ticksTillNextGrowl-- == 0) {
 | 
			
		||||
                playGrowlSound(session);
 | 
			
		||||
                ticksTillNextGrowl = 200 + random.nextInt(200);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            lastWingPosition = wingPosition;
 | 
			
		||||
        }
 | 
			
		||||
        if (isAlive()) {
 | 
			
		||||
            if (metadata.getFlags().getFlag(EntityFlag.NO_AI)) {
 | 
			
		||||
                wingPosition = 0.5f;
 | 
			
		||||
            } else if (isHovering() || isSitting()) {
 | 
			
		||||
                wingPosition += 0.1f;
 | 
			
		||||
            } else {
 | 
			
		||||
                double speed = motion.length();
 | 
			
		||||
                wingPosition += 0.2f / (speed * 10f + 1) * Math.pow(2, motion.getY());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            phaseTicks++;
 | 
			
		||||
            if (phase == 3) { // Landing Phase
 | 
			
		||||
                float headHeight = head.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT);
 | 
			
		||||
                Vector3f headCenter = head.getPosition().up(headHeight * 0.5f);
 | 
			
		||||
 | 
			
		||||
                for (int i = 0; i < 8; i++) {
 | 
			
		||||
                    Vector3f particlePos = headCenter.add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f);
 | 
			
		||||
                    // This is missing velocity information
 | 
			
		||||
                    LevelEventPacket particlePacket = new LevelEventPacket();
 | 
			
		||||
                    particlePacket.setType(LevelEventType.PARTICLE_DRAGONS_BREATH);
 | 
			
		||||
                    particlePacket.setPosition(particlePos);
 | 
			
		||||
                    session.sendUpstreamPacket(particlePacket);
 | 
			
		||||
                }
 | 
			
		||||
            } else if (phase == 5) { // Sitting Flaming Phase
 | 
			
		||||
                if (phaseTicks % 2 == 0 && phaseTicks < 10) {
 | 
			
		||||
                    // Performing breath attack
 | 
			
		||||
                    // Entity event DRAGON_FLAMING seems to create particles from the origin of the dragon,
 | 
			
		||||
                    // so we need to manually spawn particles
 | 
			
		||||
                    for (int i = 0; i < 8; i++) {
 | 
			
		||||
                        SpawnParticleEffectPacket spawnParticleEffectPacket = new SpawnParticleEffectPacket();
 | 
			
		||||
                        spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
 | 
			
		||||
                        spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f));
 | 
			
		||||
                        spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire");
 | 
			
		||||
                        session.sendUpstreamPacket(spawnParticleEffectPacket);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else if (phase == 7) { // Sitting Attacking Phase
 | 
			
		||||
                playGrowlSound(session);
 | 
			
		||||
            } else if (phase == 9) { // Dying Phase
 | 
			
		||||
                // Send explosion particles as the dragon move towards the end portal
 | 
			
		||||
                if (phaseTicks % 10 == 0) {
 | 
			
		||||
                    float xOffset = 8f * (random.nextFloat() - 0.5f);
 | 
			
		||||
                    float yOffset = 4f * (random.nextFloat() - 0.5f) + 2f;
 | 
			
		||||
                    float zOffset = 8f * (random.nextFloat() - 0.5f);
 | 
			
		||||
                    Vector3f particlePos = position.add(xOffset, yOffset, zOffset);
 | 
			
		||||
                    LevelEventPacket particlePacket = new LevelEventPacket();
 | 
			
		||||
                    particlePacket.setType(LevelEventType.PARTICLE_EXPLOSION);
 | 
			
		||||
                    particlePacket.setPosition(particlePos);
 | 
			
		||||
                    session.sendUpstreamPacket(particlePacket);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void playGrowlSound(GeyserSession session) {
 | 
			
		||||
        Random random = ThreadLocalRandom.current();
 | 
			
		||||
        PlaySoundPacket playSoundPacket = new PlaySoundPacket();
 | 
			
		||||
        playSoundPacket.setSound("mob.enderdragon.growl");
 | 
			
		||||
        playSoundPacket.setPosition(position);
 | 
			
		||||
        playSoundPacket.setVolume(2.5f);
 | 
			
		||||
        playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f);
 | 
			
		||||
        session.sendUpstreamPacket(playSoundPacket);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isAlive() {
 | 
			
		||||
        return metadata.getFloat(EntityData.HEALTH) > 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isHovering() {
 | 
			
		||||
        return phase == 10;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isSitting() {
 | 
			
		||||
        return phase == 5 || phase == 6 || phase == 7;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Store the current yaw and y into the circular buffer
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,11 +32,12 @@ import org.geysermc.connector.entity.Entity;
 | 
			
		|||
import org.geysermc.connector.entity.type.EntityType;
 | 
			
		||||
 | 
			
		||||
public class EnderDragonPartEntity extends Entity {
 | 
			
		||||
    public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, float width, float height) {
 | 
			
		||||
        super(entityId, geyserId, entityType, position, motion, rotation);
 | 
			
		||||
    public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, float width, float height) {
 | 
			
		||||
        super(entityId, geyserId, entityType, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO);
 | 
			
		||||
 | 
			
		||||
        metadata.put(EntityData.BOUNDING_BOX_WIDTH, width);
 | 
			
		||||
        metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height);
 | 
			
		||||
        metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
 | 
			
		||||
        metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
Subproject commit 143285afb4bdf4d5ef40ef7a7959477dabf4d34c
 | 
			
		||||
Subproject commit dd0347bd51e00e42ea58faaf68b562526c4d2817
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue