2020-04-15 01:52:25 +00:00
|
|
|
/*
|
2021-01-01 15:10:36 +00:00
|
|
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
2020-04-15 01:52:25 +00:00
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
* THE SOFTWARE.
|
|
|
|
*
|
|
|
|
* @author GeyserMC
|
|
|
|
* @link https://github.com/GeyserMC/Geyser
|
|
|
|
*/
|
|
|
|
|
2020-04-11 17:06:50 +00:00
|
|
|
package org.geysermc.connector.entity.living.monster;
|
|
|
|
|
2020-04-11 17:10:35 +00:00
|
|
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
2021-11-18 03:02:38 +00:00
|
|
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
2020-04-11 17:06:50 +00:00
|
|
|
import com.nukkitx.math.vector.Vector3f;
|
2021-01-10 01:43:03 +00:00
|
|
|
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
|
|
|
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
2020-06-23 00:11:09 +00:00
|
|
|
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
|
|
|
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
2021-01-10 01:43:03 +00:00
|
|
|
import com.nukkitx.protocol.bedrock.packet.*;
|
2020-10-29 22:35:46 +00:00
|
|
|
import lombok.Data;
|
2021-11-18 03:02:38 +00:00
|
|
|
import org.geysermc.connector.entity.EntityDefinition;
|
2021-01-05 23:41:20 +00:00
|
|
|
import org.geysermc.connector.entity.Tickable;
|
2021-11-18 03:02:38 +00:00
|
|
|
import org.geysermc.connector.entity.living.MobEntity;
|
2020-04-11 17:10:35 +00:00
|
|
|
import org.geysermc.connector.network.session.GeyserSession;
|
2021-01-10 01:43:03 +00:00
|
|
|
import org.geysermc.connector.utils.DimensionUtils;
|
|
|
|
|
|
|
|
import java.util.Random;
|
2021-11-18 03:02:38 +00:00
|
|
|
import java.util.UUID;
|
2021-01-10 01:43:03 +00:00
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
|
import java.util.concurrent.atomic.AtomicLong;
|
2020-04-11 17:06:50 +00:00
|
|
|
|
2021-11-18 03:02:38 +00:00
|
|
|
public class EnderDragonEntity extends MobEntity implements Tickable {
|
2020-10-29 22:35:46 +00:00
|
|
|
/**
|
|
|
|
* The Ender Dragon has multiple hit boxes, which
|
|
|
|
* are each its own invisible entity
|
|
|
|
*/
|
|
|
|
private EnderDragonPartEntity head;
|
|
|
|
private EnderDragonPartEntity neck;
|
|
|
|
private EnderDragonPartEntity body;
|
|
|
|
private EnderDragonPartEntity leftWing;
|
|
|
|
private EnderDragonPartEntity rightWing;
|
|
|
|
private EnderDragonPartEntity[] tail;
|
|
|
|
|
|
|
|
private EnderDragonPartEntity[] allParts;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A circular buffer that stores a history of
|
|
|
|
* y and yaw values.
|
|
|
|
*/
|
|
|
|
private final Segment[] segmentHistory = new Segment[19];
|
|
|
|
private int latestSegment = -1;
|
|
|
|
|
2021-01-10 01:43:03 +00:00
|
|
|
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;
|
2020-10-29 22:35:46 +00:00
|
|
|
|
2021-11-18 03:02:38 +00:00
|
|
|
public EnderDragonEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
|
|
|
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
|
|
|
}
|
2020-10-29 22:35:46 +00:00
|
|
|
|
2021-11-18 03:02:38 +00:00
|
|
|
@Override
|
|
|
|
protected void initializeMetadata() {
|
|
|
|
super.initializeMetadata();
|
|
|
|
setFlag(EntityFlag.FIRE_IMMUNE, true);
|
2020-04-11 17:06:50 +00:00
|
|
|
}
|
2020-04-11 17:10:35 +00:00
|
|
|
|
|
|
|
@Override
|
2021-11-18 03:02:38 +00:00
|
|
|
public void setHealth(EntityMetadata<Float> entityMetadata) {
|
|
|
|
super.setHealth(entityMetadata);
|
|
|
|
if (phase == 9 && this.health <= 0) { // Dying phase
|
|
|
|
EntityEventPacket entityEventPacket = new EntityEventPacket();
|
|
|
|
entityEventPacket.setType(EntityEventType.ENDER_DRAGON_DEATH);
|
|
|
|
entityEventPacket.setRuntimeEntityId(geyserId);
|
|
|
|
entityEventPacket.setData(0);
|
|
|
|
session.sendUpstreamPacket(entityEventPacket);
|
2021-01-10 01:43:03 +00:00
|
|
|
}
|
2021-11-18 03:02:38 +00:00
|
|
|
}
|
2021-01-10 01:43:03 +00:00
|
|
|
|
2021-11-18 03:02:38 +00:00
|
|
|
public void setPhase(EntityMetadata<Integer> entityMetadata) {
|
|
|
|
phase = ((IntEntityMetadata) entityMetadata).getPrimitiveValue();
|
|
|
|
phaseTicks = 0;
|
|
|
|
setFlag(EntityFlag.SITTING, isSitting());
|
2021-01-10 01:43:03 +00:00
|
|
|
}
|
|
|
|
|
2020-04-14 20:58:41 +00:00
|
|
|
@Override
|
2021-11-18 03:02:38 +00:00
|
|
|
public void spawnEntity() {
|
|
|
|
super.spawnEntity();
|
2021-01-10 01:43:03 +00:00
|
|
|
|
|
|
|
AtomicLong nextEntityId = session.getEntityCache().getNextEntityId();
|
2021-11-18 03:02:38 +00:00
|
|
|
head = new EnderDragonPartEntity(session, entityId + 1, nextEntityId.incrementAndGet(), 1, 1);
|
|
|
|
neck = new EnderDragonPartEntity(session, entityId + 2, nextEntityId.incrementAndGet(), 3, 3);
|
|
|
|
body = new EnderDragonPartEntity(session, entityId + 3, nextEntityId.incrementAndGet(), 5, 3);
|
|
|
|
leftWing = new EnderDragonPartEntity(session, entityId + 4, nextEntityId.incrementAndGet(), 4, 2);
|
|
|
|
rightWing = new EnderDragonPartEntity(session, entityId + 5, nextEntityId.incrementAndGet(), 4, 2);
|
2020-10-29 22:35:46 +00:00
|
|
|
tail = new EnderDragonPartEntity[3];
|
|
|
|
for (int i = 0; i < 3; i++) {
|
2021-11-18 03:02:38 +00:00
|
|
|
tail[i] = new EnderDragonPartEntity(session, entityId + 6 + i, nextEntityId.incrementAndGet(), 2, 2);
|
2020-10-29 22:35:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]};
|
|
|
|
|
|
|
|
for (EnderDragonPartEntity part : allParts) {
|
|
|
|
session.getEntityCache().spawnEntity(part);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < segmentHistory.length; i++) {
|
|
|
|
segmentHistory[i] = new Segment();
|
2021-11-18 03:02:38 +00:00
|
|
|
segmentHistory[i].yaw = headYaw;
|
2020-10-29 22:35:46 +00:00
|
|
|
segmentHistory[i].y = position.getY();
|
|
|
|
}
|
2020-04-14 20:58:41 +00:00
|
|
|
}
|
2020-10-29 22:35:46 +00:00
|
|
|
|
2021-07-08 02:44:53 +00:00
|
|
|
@Override
|
|
|
|
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
|
|
|
// Bedrock is EXTREMELY sensitive to the Ender Dragon's health - if it is dead once, it is dead for the rest of its life
|
|
|
|
// Ensure that the first spawn packet sent has health data so this cannot happen until it actually should
|
|
|
|
addEntityPacket.getAttributes().add(createHealthAttribute());
|
|
|
|
}
|
|
|
|
|
2020-10-29 22:35:46 +00:00
|
|
|
@Override
|
2021-11-18 03:02:38 +00:00
|
|
|
public boolean despawnEntity() {
|
2020-10-29 22:35:46 +00:00
|
|
|
for (EnderDragonPartEntity part : allParts) {
|
2021-11-18 03:02:38 +00:00
|
|
|
part.despawnEntity();
|
2020-10-29 22:35:46 +00:00
|
|
|
}
|
2021-11-18 03:02:38 +00:00
|
|
|
return super.despawnEntity();
|
2020-10-29 22:35:46 +00:00
|
|
|
}
|
|
|
|
|
2021-01-05 23:41:20 +00:00
|
|
|
@Override
|
2021-11-18 03:02:38 +00:00
|
|
|
public void tick() {
|
|
|
|
effectTick();
|
|
|
|
if (!getFlag(EntityFlag.NO_AI) && isAlive()) {
|
2021-01-10 01:43:03 +00:00
|
|
|
pushSegment();
|
2021-11-18 03:02:38 +00:00
|
|
|
updateBoundingBoxes();
|
2021-01-10 01:43:03 +00:00
|
|
|
}
|
2021-01-05 23:41:20 +00:00
|
|
|
}
|
|
|
|
|
2020-10-29 22:35:46 +00:00
|
|
|
/**
|
|
|
|
* Updates the positions of the Ender Dragon's multiple bounding boxes
|
|
|
|
*/
|
2021-11-18 03:02:38 +00:00
|
|
|
private void updateBoundingBoxes() {
|
|
|
|
Vector3f facingDir = Vector3f.createDirectionDeg(0, headYaw);
|
2020-10-29 22:35:46 +00:00
|
|
|
Segment baseSegment = getSegment(5);
|
|
|
|
// Used to angle the head, neck, and tail when the dragon flies up/down
|
|
|
|
float pitch = (float) Math.toRadians(10 * (baseSegment.getY() - getSegment(10).getY()));
|
|
|
|
float pitchXZ = (float) Math.cos(pitch);
|
|
|
|
float pitchY = (float) Math.sin(pitch);
|
|
|
|
|
|
|
|
// Lowers the head when the dragon sits/hovers
|
|
|
|
float headDuck;
|
2021-01-10 01:43:03 +00:00
|
|
|
if (isHovering() || isSitting()) {
|
2020-10-29 22:35:46 +00:00
|
|
|
headDuck = -1f;
|
|
|
|
} else {
|
|
|
|
headDuck = baseSegment.y - getSegment(0).y;
|
|
|
|
}
|
|
|
|
|
|
|
|
head.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(6.5f).up(headDuck));
|
|
|
|
neck.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(5.5f).up(headDuck));
|
|
|
|
body.setPosition(facingDir.mul(0.5f, 0f, -0.5f));
|
|
|
|
|
2021-11-18 03:02:38 +00:00
|
|
|
Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - headYaw).mul(4.5f).up(2f);
|
2020-10-29 22:35:46 +00:00
|
|
|
rightWing.setPosition(wingPos);
|
|
|
|
leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally
|
|
|
|
|
|
|
|
Vector3f tailBase = facingDir.mul(1.5f);
|
|
|
|
for (int i = 0; i < tail.length; i++) {
|
|
|
|
float distance = (i + 1) * 2f;
|
|
|
|
// Curls the tail when the dragon turns
|
|
|
|
Segment targetSegment = getSegment(12 + 2 * i);
|
2021-11-18 03:02:38 +00:00
|
|
|
float angle = headYaw + targetSegment.yaw - baseSegment.yaw;
|
2020-10-29 22:35:46 +00:00
|
|
|
|
|
|
|
float tailYOffset = targetSegment.y - baseSegment.y - (distance + 1.5f) * pitchY + 1.5f;
|
|
|
|
tail[i].setPosition(Vector3f.createDirectionDeg(0, angle).mul(distance).add(tailBase).mul(-pitchXZ, 1, pitchXZ).up(tailYOffset));
|
|
|
|
}
|
|
|
|
// Send updated positions
|
|
|
|
for (EnderDragonPartEntity part : allParts) {
|
2021-11-18 03:02:38 +00:00
|
|
|
part.moveAbsolute(part.getPosition().add(position), 0, 0, 0, false, false);
|
2020-10-29 22:35:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-10 01:43:03 +00:00
|
|
|
/**
|
|
|
|
* Handles the particles and sounds of the Ender Dragon
|
|
|
|
*/
|
2021-11-18 03:02:38 +00:00
|
|
|
private void effectTick() {
|
2021-01-10 01:43:03 +00:00
|
|
|
Random random = ThreadLocalRandom.current();
|
2021-11-18 03:02:38 +00:00
|
|
|
if (!getFlag(EntityFlag.SILENT)) {
|
2021-01-10 01:43:03 +00:00
|
|
|
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) {
|
2021-11-18 03:02:38 +00:00
|
|
|
playGrowlSound();
|
2021-01-10 01:43:03 +00:00
|
|
|
ticksTillNextGrowl = 200 + random.nextInt(200);
|
|
|
|
}
|
|
|
|
|
|
|
|
lastWingPosition = wingPosition;
|
|
|
|
}
|
|
|
|
if (isAlive()) {
|
2021-11-18 03:02:38 +00:00
|
|
|
if (getFlag(EntityFlag.NO_AI)) {
|
2021-01-10 01:43:03 +00:00
|
|
|
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
|
2021-11-18 03:02:38 +00:00
|
|
|
float headHeight = head.getDirtyMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT); //TODO
|
2021-01-10 01:43:03 +00:00
|
|
|
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
|
2021-11-18 03:02:38 +00:00
|
|
|
playGrowlSound();
|
2021-01-10 01:43:03 +00:00
|
|
|
} 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-18 03:02:38 +00:00
|
|
|
private void playGrowlSound() {
|
2021-01-10 01:43:03 +00:00
|
|
|
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() {
|
2021-07-08 02:44:53 +00:00
|
|
|
return health > 0;
|
2021-01-10 01:43:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private boolean isHovering() {
|
|
|
|
return phase == 10;
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean isSitting() {
|
|
|
|
return phase == 5 || phase == 6 || phase == 7;
|
|
|
|
}
|
|
|
|
|
2020-10-29 22:35:46 +00:00
|
|
|
/**
|
|
|
|
* Store the current yaw and y into the circular buffer
|
|
|
|
*/
|
|
|
|
private void pushSegment() {
|
|
|
|
latestSegment = (latestSegment + 1) % segmentHistory.length;
|
2021-11-18 03:02:38 +00:00
|
|
|
segmentHistory[latestSegment].yaw = headYaw;
|
2020-10-29 22:35:46 +00:00
|
|
|
segmentHistory[latestSegment].y = position.getY();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the previous yaw and y
|
|
|
|
* Used to curl the tail and pitch the head and tail up/down
|
|
|
|
*
|
|
|
|
* @param index Number of ticks in the past
|
|
|
|
* @return Segment with the yaw and y
|
|
|
|
*/
|
|
|
|
private Segment getSegment(int index) {
|
|
|
|
index = (latestSegment - index) % segmentHistory.length;
|
|
|
|
if (index < 0) {
|
|
|
|
index += segmentHistory.length;
|
|
|
|
}
|
|
|
|
return segmentHistory[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
@Data
|
|
|
|
private static class Segment {
|
|
|
|
private float yaw;
|
|
|
|
private float y;
|
|
|
|
}
|
2020-04-11 17:06:50 +00:00
|
|
|
}
|