#1266: Add support for virtual entities

By: Jishuna <joshl5324@gmail.com>
This commit is contained in:
CraftBukkit/Spigot
2023-11-19 19:03:35 +13:00
parent 9a3c24e787
commit 95e4221adf
13 changed files with 472 additions and 61 deletions

View File

@@ -64,6 +64,7 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.block.CraftBiome;
import org.bukkit.craftbukkit.block.CraftBlock;
import org.bukkit.craftbukkit.block.data.CraftBlockData;
import org.bukkit.craftbukkit.entity.CraftEntity;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.potion.CraftPotionUtil;
import org.bukkit.craftbukkit.util.BlockStateListPopulator;
@@ -528,6 +529,18 @@ public abstract class CraftRegionAccessor implements RegionAccessor {
public abstract Iterable<net.minecraft.world.entity.Entity> getNMSEntities();
@Override
@SuppressWarnings("unchecked")
public <T extends Entity> T createEntity(Location location, Class<T> clazz) throws IllegalArgumentException {
net.minecraft.world.entity.Entity entity = createEntity(location, clazz, true);
if (!isNormalWorld()) {
entity.generation = true;
}
return (T) entity.getBukkitEntity();
}
@Override
public <T extends Entity> T spawn(Location location, Class<T> clazz) throws IllegalArgumentException {
return spawn(location, clazz, null, CreatureSpawnEvent.SpawnReason.CUSTOM);
@@ -553,6 +566,19 @@ public abstract class CraftRegionAccessor implements RegionAccessor {
return addEntity(entity, reason, function, randomizeData);
}
@Override
@SuppressWarnings("unchecked")
public <T extends Entity> T addEntity(T entity) {
Preconditions.checkArgument(!entity.isInWorld(), "Entity has already been added to a world");
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
if (nmsEntity.level() != getHandle().getLevel()) {
nmsEntity = nmsEntity.changeDimension(getHandle().getLevel());
}
addEntityWithPassengers(nmsEntity, CreatureSpawnEvent.SpawnReason.CUSTOM);
return (T) nmsEntity.getBukkitEntity();
}
@SuppressWarnings("unchecked")
public <T extends Entity> T addEntity(net.minecraft.world.entity.Entity entity, CreatureSpawnEvent.SpawnReason reason) throws IllegalArgumentException {
return addEntity(entity, reason, null, true);
@@ -580,8 +606,10 @@ public abstract class CraftRegionAccessor implements RegionAccessor {
public abstract void addEntityToWorld(net.minecraft.world.entity.Entity entity, CreatureSpawnEvent.SpawnReason reason);
public abstract void addEntityWithPassengers(net.minecraft.world.entity.Entity entity, CreatureSpawnEvent.SpawnReason reason);
@SuppressWarnings("unchecked")
public net.minecraft.world.entity.Entity createEntity(Location location, Class<? extends Entity> clazz) throws IllegalArgumentException {
public net.minecraft.world.entity.Entity makeEntity(Location location, Class<? extends Entity> clazz) throws IllegalArgumentException {
return createEntity(location, clazz, true);
}

View File

@@ -822,6 +822,11 @@ public class CraftWorld extends CraftRegionAccessor implements World {
getHandle().addFreshEntity(entity, reason);
}
@Override
public void addEntityWithPassengers(net.minecraft.world.entity.Entity entity, SpawnReason reason) {
getHandle().tryAddFreshEntityWithPassengers(entity, reason);
}
@Override
public Collection<Entity> getNearbyEntities(Location location, double x, double y, double z) {
return this.getNearbyEntities(location, x, y, z, null);

View File

@@ -1,15 +1,25 @@
package org.bukkit.craftbukkit.block;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.InclusiveRange;
import net.minecraft.util.RandomSource;
import net.minecraft.util.random.SimpleWeightedRandomList;
import net.minecraft.util.random.WeightedEntry.b;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.world.level.MobSpawnerData;
import net.minecraft.world.level.block.entity.TileEntityMobSpawner;
import org.bukkit.World;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.block.spawner.SpawnRule;
import org.bukkit.block.spawner.SpawnerEntry;
import org.bukkit.craftbukkit.entity.CraftEntitySnapshot;
import org.bukkit.craftbukkit.entity.CraftEntityType;
import org.bukkit.entity.EntitySnapshot;
import org.bukkit.entity.EntityType;
public class CraftCreatureSpawner extends CraftBlockEntityState<TileEntityMobSpawner> implements CreatureSpawner {
@@ -46,6 +56,78 @@ public class CraftCreatureSpawner extends CraftBlockEntityState<TileEntityMobSpa
this.getSnapshot().setEntityId(CraftEntityType.bukkitToMinecraft(entityType), rand);
}
@Override
public EntitySnapshot getSpawnedEntity() {
MobSpawnerData spawnData = this.getSnapshot().getSpawner().nextSpawnData;
if (spawnData == null) {
return null;
}
return CraftEntitySnapshot.create(spawnData.getEntityToSpawn());
}
@Override
public void setSpawnedEntity(EntitySnapshot snapshot) {
NBTTagCompound compoundTag = ((CraftEntitySnapshot) snapshot).getData();
this.getSnapshot().getSpawner().spawnPotentials = SimpleWeightedRandomList.empty();
this.getSnapshot().getSpawner().nextSpawnData = new MobSpawnerData(compoundTag, Optional.empty());
}
@Override
public void addPotentialSpawn(EntitySnapshot snapshot, int weight, SpawnRule spawnRule) {
NBTTagCompound compoundTag = ((CraftEntitySnapshot) snapshot).getData();
SimpleWeightedRandomList.a<MobSpawnerData> builder = SimpleWeightedRandomList.builder(); // PAIL rename Builder
this.getSnapshot().getSpawner().spawnPotentials.unwrap().forEach(entry -> builder.add(entry.getData(), entry.getWeight().asInt()));
builder.add(new MobSpawnerData(compoundTag, Optional.ofNullable(toMinecraftRule(spawnRule))), weight);
this.getSnapshot().getSpawner().spawnPotentials = builder.build();
}
@Override
public void addPotentialSpawn(SpawnerEntry spawnerEntry) {
addPotentialSpawn(spawnerEntry.getSnapshot(), spawnerEntry.getSpawnWeight(), spawnerEntry.getSpawnRule());
}
@Override
public void setPotentialSpawns(Collection<SpawnerEntry> entries) {
SimpleWeightedRandomList.a<MobSpawnerData> builder = SimpleWeightedRandomList.builder();
for (SpawnerEntry spawnerEntry : entries) {
NBTTagCompound compoundTag = ((CraftEntitySnapshot) spawnerEntry.getSnapshot()).getData();
builder.add(new MobSpawnerData(compoundTag, Optional.ofNullable(toMinecraftRule(spawnerEntry.getSpawnRule()))), spawnerEntry.getSpawnWeight());
}
this.getSnapshot().getSpawner().spawnPotentials = builder.build();
}
@Override
public List<SpawnerEntry> getPotentialSpawns() {
List<SpawnerEntry> entries = new ArrayList<>();
for (b<MobSpawnerData> entry : this.getSnapshot().getSpawner().spawnPotentials.unwrap()) { // PAIL rename Wrapper
CraftEntitySnapshot snapshot = CraftEntitySnapshot.create(entry.getData().getEntityToSpawn());
if (snapshot != null) {
SpawnRule rule = entry.getData().customSpawnRules().map(this::fromMinecraftRule).orElse(null);
entries.add(new SpawnerEntry(snapshot, entry.getWeight().asInt(), rule));
}
}
return entries;
}
private MobSpawnerData.a toMinecraftRule(SpawnRule rule) { // PAIL rename CustomSpawnRules
if (rule == null) {
return null;
}
return new MobSpawnerData.a(new InclusiveRange<>(rule.getMinBlockLight(), rule.getMaxBlockLight()), new InclusiveRange<>(rule.getMinSkyLight(), rule.getMaxSkyLight()));
}
private SpawnRule fromMinecraftRule(MobSpawnerData.a rule) {
InclusiveRange<Integer> blockLight = rule.blockLightLimit();
InclusiveRange<Integer> skyLight = rule.skyLightLimit();
return new SpawnRule(blockLight.maxInclusive(), blockLight.maxInclusive(), skyLight.minInclusive(), skyLight.maxInclusive());
}
@Override
public String getCreatureTypeName() {
MobSpawnerData spawnData = this.getSnapshot().getSpawner().nextSpawnData;

View File

@@ -24,6 +24,7 @@ import net.minecraft.world.entity.EntityFlying;
import net.minecraft.world.entity.EntityLightning;
import net.minecraft.world.entity.EntityLiving;
import net.minecraft.world.entity.EntityTameableAnimal;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.world.entity.GlowSquid;
import net.minecraft.world.entity.Interaction;
import net.minecraft.world.entity.Marker;
@@ -164,6 +165,7 @@ import net.minecraft.world.entity.vehicle.EntityMinecartTNT;
import net.minecraft.world.phys.AxisAlignedBB;
import org.bukkit.EntityEffect;
import org.bukkit.Location;
import org.bukkit.Registry;
import org.bukkit.Server;
import org.bukkit.Sound;
import org.bukkit.World;
@@ -177,8 +179,10 @@ import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry;
import org.bukkit.craftbukkit.util.CraftChatMessage;
import org.bukkit.craftbukkit.util.CraftLocation;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.CraftSpawnCategory;
import org.bukkit.craftbukkit.util.CraftVector;
import org.bukkit.entity.EntitySnapshot;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
@@ -641,7 +645,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
@Override
public boolean isValid() {
return entity.isAlive() && entity.valid && entity.isChunkLoaded();
return entity.isAlive() && entity.valid && entity.isChunkLoaded() && isInWorld();
}
@Override
@@ -1114,6 +1118,42 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
return CraftSpawnCategory.toBukkit(getHandle().getType().getCategory());
}
@Override
public boolean isInWorld() {
return getHandle().inWorld;
}
@Override
public EntitySnapshot createSnapshot() {
return CraftEntitySnapshot.create(this);
}
@Override
public org.bukkit.entity.Entity copy() {
Entity copy = copy(getHandle().level());
Preconditions.checkArgument(copy != null, "Error creating new entity.");
return copy.getBukkitEntity();
}
@Override
public org.bukkit.entity.Entity copy(Location location) {
Preconditions.checkArgument(location.getWorld() != null, "Location has no world");
Entity copy = copy(((CraftWorld) location.getWorld()).getHandle());
Preconditions.checkArgument(copy != null, "Error creating new entity.");
copy.setPos(location.getX(), location.getY(), location.getZ());
return location.getWorld().addEntity(copy.getBukkitEntity());
}
private Entity copy(net.minecraft.world.level.World level) {
NBTTagCompound compoundTag = new NBTTagCompound();
getHandle().saveAsPassenger(compoundTag, false);
return EntityTypes.loadEntityRecursive(compoundTag, level, java.util.function.Function.identity());
}
public void storeBukkitValues(NBTTagCompound c) {
if (!this.persistentDataContainer.isEmpty()) {
c.put("BukkitValues", this.persistentDataContainer.toTagCompound());

View File

@@ -0,0 +1,83 @@
package org.bukkit.craftbukkit.entity;
import com.google.common.base.Preconditions;
import java.util.function.Function;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.entity.EntityTypes;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntitySnapshot;
import org.bukkit.entity.EntityType;
public class CraftEntitySnapshot implements EntitySnapshot {
private final NBTTagCompound data;
private final EntityType type;
private CraftEntitySnapshot(NBTTagCompound data, EntityType type) {
this.data = data;
this.type = type;
}
@Override
public EntityType getEntityType() {
return type;
}
@Override
public Entity createEntity(World world) {
net.minecraft.world.entity.Entity internal = createInternal(world);
return internal.getBukkitEntity();
}
@Override
public Entity createEntity(Location location) {
Preconditions.checkArgument(location.getWorld() != null, "Location has no world");
net.minecraft.world.entity.Entity internal = createInternal(location.getWorld());
internal.setPos(location.getX(), location.getY(), location.getZ());
return location.getWorld().addEntity(internal.getBukkitEntity());
}
private net.minecraft.world.entity.Entity createInternal(World world) {
net.minecraft.world.level.World nms = ((CraftWorld) world).getHandle();
net.minecraft.world.entity.Entity internal = EntityTypes.loadEntityRecursive(data, nms, Function.identity());
if (internal == null) { // Try creating by type
internal = CraftEntityType.bukkitToMinecraft(type).create(nms);
}
Preconditions.checkArgument(internal != null, "Error creating new entity."); // This should only fail if the stored NBTTagCompound is malformed.
internal.load(data);
return internal;
}
public NBTTagCompound getData() {
return data;
}
public static CraftEntitySnapshot create(CraftEntity entity) {
NBTTagCompound tag = new NBTTagCompound();
if (!entity.getHandle().saveAsPassenger(tag, false)) {
return null;
}
return new CraftEntitySnapshot(tag, entity.getType());
}
public static CraftEntitySnapshot create(NBTTagCompound tag, EntityType type) {
if (tag == null || tag.isEmpty() || type == null) {
return null;
}
return new CraftEntitySnapshot(tag, type);
}
public static CraftEntitySnapshot create(NBTTagCompound tag) {
EntityType type = EntityTypes.by(tag).map(CraftEntityType::minecraftToBukkit).orElse(null);
return create(tag, type);
}
}

View File

@@ -38,6 +38,7 @@ import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.memory.CraftMemoryMapper;
import org.bukkit.craftbukkit.event.CraftEventFactory;
import org.bukkit.craftbukkit.inventory.CraftContainer;
@@ -684,4 +685,14 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity {
boolean success = getHandle().level().addFreshEntity(fireworks, SpawnReason.CUSTOM);
return success ? (Firework) fireworks.getBukkitEntity() : null;
}
@Override
public org.bukkit.entity.Entity copy() {
throw new UnsupportedOperationException("Cannot copy human entities");
}
@Override
public org.bukkit.entity.Entity copy(Location location) {
throw new UnsupportedOperationException("Cannot copy human entities");
}
}

View File

@@ -116,12 +116,12 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe
if (entity.isAlive()) {
// check if entity is still in region or if it got teleported outside it
Preconditions.checkState(region.contains(entity.getX(), entity.getY(), entity.getZ()), "Entity %s is not in the region", entity);
access.addFreshEntity(entity);
access.addFreshEntityWithPassengers(entity);
}
}
for (net.minecraft.world.entity.Entity entity : outsideEntities) {
access.addFreshEntity(entity);
access.addFreshEntityWithPassengers(entity);
}
}
@@ -250,4 +250,9 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe
public void addEntityToWorld(net.minecraft.world.entity.Entity entity, CreatureSpawnEvent.SpawnReason reason) {
entities.add(entity);
}
@Override
public void addEntityWithPassengers(net.minecraft.world.entity.Entity entity, CreatureSpawnEvent.SpawnReason reason) {
entities.add(entity);
}
}

View File

@@ -1,5 +1,6 @@
package org.bukkit.craftbukkit.inventory;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.Sets;
import java.util.Map;
@@ -9,7 +10,9 @@ import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.resources.MinecraftKey;
import org.bukkit.Material;
import org.bukkit.configuration.serialization.DelegateDeserialization;
import org.bukkit.craftbukkit.entity.CraftEntitySnapshot;
import org.bukkit.craftbukkit.util.CraftLegacy;
import org.bukkit.entity.EntitySnapshot;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.meta.SpawnEggMeta;
import org.bukkit.material.MaterialData;
@@ -216,6 +219,17 @@ public class CraftMetaSpawnEgg extends CraftMetaItem implements SpawnEggMeta {
throw new UnsupportedOperationException("Must change item type to set spawned type");
}
@Override
public EntitySnapshot getSpawnedEntity() {
return CraftEntitySnapshot.create(this.entityTag);
}
@Override
public void setSpawnedEntity(EntitySnapshot snapshot) {
Preconditions.checkArgument(snapshot.getEntityType().isSpawnable(), "Entity is not spawnable");
this.entityTag = ((CraftEntitySnapshot) snapshot).getData();
}
@Override
boolean equalsCommon(CraftMetaItem meta) {
if (!super.equalsCommon(meta)) {