Improved mob spawn mechanics for PSPE w/ misc cleanup

This commit is contained in:
Sotr
2019-04-08 19:52:02 +08:00
parent f3a6ec65cc
commit 0deb4ca743
13 changed files with 356 additions and 11 deletions

View File

@@ -40,8 +40,8 @@ public class AkarinGlobalConfig {
config.options().header(HEADER);
config.options().copyDefaults(true);
version = getInt("config-version", 2);
set("config-version", 2);
version = getInt("config-version", 3);
set("config-version", 3);
readConfig(AkarinGlobalConfig.class, null);
}
@@ -178,7 +178,11 @@ public class AkarinGlobalConfig {
public static double blockbreakAnimationVisibleDistance = 1024;
private static void blockbreakAnimationVisibleDistance() {
blockbreakAnimationVisibleDistance = Math.sqrt(getDouble("alternative.block-break-animation-visible-distance", 32.00));
double def = 32.00;
if (version == 2)
def = getDouble("alternative.block-break-animation-visible-distance", def);
blockbreakAnimationVisibleDistance = Math.sqrt(getDouble("core.block-break-animation-visible-distance", def));
}
public static boolean enableAsyncLighting = true;
@@ -205,4 +209,9 @@ public class AkarinGlobalConfig {
private static void ignoreRayTraceForSeatableBlocks() {
ignoreRayTraceForSeatableBlocks = getBoolean("alternative.ignore-ray-trace-for-seatable-blocks", ignoreRayTraceForSeatableBlocks);
}
public static boolean improvedMobSpawnMechanics = false;
private static void improvedMobSpawnMechanics() {
improvedMobSpawnMechanics = getBoolean("core.improved-mob-spawn-mechanics.enable", improvedMobSpawnMechanics);
}
}

View File

@@ -15,7 +15,6 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.NetworkManager;
import net.minecraft.server.PacketPlayOutPlayerInfo;
import net.minecraft.server.PacketPlayOutUpdateTime;
import net.minecraft.server.World;
import net.minecraft.server.WorldServer;
public class AkarinAsyncScheduler extends Thread {

View File

@@ -0,0 +1,252 @@
package io.akarin.server.core;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
import com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent;
import com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent;
import com.destroystokyo.paper.exception.ServerInternalException;
import com.koloboke.collect.set.hash.HashObjSets;
import io.akarin.server.misc.ChunkCoordOrdinalInt3Tuple;
import net.minecraft.server.BiomeBase;
import net.minecraft.server.BlockPosition;
import net.minecraft.server.Chunk;
import net.minecraft.server.ChunkCoordIntPair;
import net.minecraft.server.EntityHuman;
import net.minecraft.server.EntityInsentient;
import net.minecraft.server.EntityPositionTypes;
import net.minecraft.server.EntityTypes;
import net.minecraft.server.EnumCreatureType;
import net.minecraft.server.GroupDataEntity;
import net.minecraft.server.MCUtil;
import net.minecraft.server.MathHelper;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.PlayerChunk;
import net.minecraft.server.SpawnerCreature;
import net.minecraft.server.WorldServer;
/*
* Reference on spawning mechanics by Colin Godsey <crgodsey@gmail.com>
* https://github.com/yesdog/Paper/blob/0de3dd84b7e6688feb42af4fe6b4f323ce7e3013/Spigot-Server-Patches/0433-alternate-mob-spawning-mechanic.patch
*/
public class AkarinCreatureSpanwner {
private static int getSpawnRange(WorldServer world, EntityHuman player) {
byte mobSpawnRange = world.spigotConfig.mobSpawnRange;
mobSpawnRange = (mobSpawnRange > world.spigotConfig.viewDistance) ? (byte) world.spigotConfig.viewDistance : mobSpawnRange;
mobSpawnRange = (mobSpawnRange > 8) ? 8 : mobSpawnRange;
if (PlayerNaturallySpawnCreaturesEvent.getHandlerList().getRegisteredListeners().length > 0) {
PlayerNaturallySpawnCreaturesEvent event = new PlayerNaturallySpawnCreaturesEvent((Player) player.getBukkitEntity(), mobSpawnRange);
new RuntimeException("Warning, one or more plugins is listening PlayerNaturallySpawnCreaturesEvent which is running asynchronously, this will may cause safe issue!").printStackTrace();
synchronized (PlayerNaturallySpawnCreaturesEvent.class) {
Bukkit.getPluginManager().callEvent(event);
}
return event.isCancelled() ? 0 : event.getSpawnRadius();
}
return mobSpawnRange;
}
private static int getCreatureLimit(WorldServer world, EnumCreatureType type) {
switch (type) {
case MONSTER:
return world.getWorld().getMonsterSpawnLimit();
case CREATURE:
return world.getWorld().getAnimalSpawnLimit();
case WATER_CREATURE:
return world.getWorld().getWaterAnimalSpawnLimit();
case AMBIENT:
return world.getWorld().getAmbientSpawnLimit();
}
return type.spawnLimit();
}
@Nullable
private static EntityInsentient createMob(WorldServer world, EnumCreatureType type, BlockPosition pos, BiomeBase.BiomeMeta biomeMeta) {
if (!world.isBiomeMetaValidAt(type, biomeMeta, pos)) return null;
EntityTypes<? extends EntityInsentient> entityType = biomeMeta.entityType();
org.bukkit.entity.EntityType bType = EntityTypes.clsToTypeMap.get(entityType.entityClass());
if (bType != null) {
if (PreCreatureSpawnEvent.getHandlerList().getRegisteredListeners().length > 0) {
PreCreatureSpawnEvent event = new PreCreatureSpawnEvent(
MCUtil.toLocation(world, pos),
bType, SpawnReason.NATURAL
);
new RuntimeException("Warning, one or more plugins is listening PlayerNaturallySpawnCreaturesEvent which is running asynchronously, this will may cause safe issue!").printStackTrace();
synchronized (PreCreatureSpawnEvent.class) {
Bukkit.getPluginManager().callEvent(event);
}
if (!event.isCancelled() || event.shouldAbortSpawn())
return null;
}
}
EntityInsentient entity = null;
try {
entity = entityType.create(world);
} catch (Exception exception) {
MinecraftServer.LOGGER.warn("Failed to create mob", exception);
ServerInternalException.reportInternalException(exception);
}
return entity;
}
private static void spawnMob0(WorldServer world, Set<ChunkCoordIntPair> chunks, EnumCreatureType type, int amount) {
if (chunks.isEmpty()) return;
final int maxPackIterations = 10; // X attempts per pack, 1 pack per chunk
Iterator<ChunkCoordIntPair> iterator = chunks.iterator();
BlockPosition worldSpawn = world.getSpawn();
int spawned = 0;
while (spawned < amount && iterator.hasNext()) {
ChunkCoordIntPair chunkCoord = iterator.next();
int packSize = world.random.nextInt(4) + 1;
BlockPosition packCenter = SpawnerCreature.getRandomPosition(world, chunkCoord.x, chunkCoord.z);
if (world.getType(packCenter).isOccluding()) continue;
int x = packCenter.getX();
int y = packCenter.getY();
int z = packCenter.getZ();
BlockPosition.MutableBlockPosition blockPointer = new BlockPosition.MutableBlockPosition();
BiomeBase.BiomeMeta biomeMeta = null;
GroupDataEntity group = null;
EntityPositionTypes.Surface surfaceType = null;
int iter = 0;
int packSpawned = 0;
while (packSpawned < packSize && iter < maxPackIterations) {
iter++;
// random walk
x += world.random.nextInt(12) - 6;
y += world.random.nextInt(2) - 1;
z += world.random.nextInt(12) - 6;
blockPointer.setValues(x, y, z);
if (worldSpawn.distanceSquared(x + 0.5, y, z + 0.5) < (24 * 24)) continue;
if (biomeMeta == null) {
biomeMeta = world.getBiomeMetaAt(type, blockPointer);
if (biomeMeta == null) break;
int packRange = 1 + biomeMeta.getMaxPackSize() - biomeMeta.getMinPackSize();
packSize = biomeMeta.getMinPackSize() + world.random.nextInt(packRange);
surfaceType = EntityPositionTypes.a(biomeMeta.entityType());
}
EntityInsentient entity = createMob(world, type, blockPointer, biomeMeta);
if (entity == null) continue;
entity.setPositionRotation(x + 0.5, y, z + 0.5, world.random.nextFloat() * 360.0F, 0.0F);
if (entity.canSpawnHere() && surfaceType != null
&& SpawnerCreature.isValidSpawnSurface(surfaceType, world, blockPointer, biomeMeta.entityType())
&& entity.isNotColliding(world) && !world.isPlayerNearby(x + 0.5, y, z + 0.5, 24)) {
group = entity.prepare(world.getDamageScaler(new BlockPosition(entity)), group, null);
if (entity.isNotColliding(world) && world.addEntity(entity, SpawnReason.NATURAL))
packSpawned++;
if (packSpawned >= entity.maxPackSize()) break;
if ((packSpawned + spawned) >= amount) break;
} else {
entity.die();
}
}
spawned += packSpawned;
}
}
public static void spawnMobs(WorldServer world, boolean spawnMonsters, boolean spawnPassives, boolean spawnRare) {
if(!spawnMonsters && !spawnPassives) return;
int hashOrdinal = world.random.nextInt();
Set<Chunk> rangeChunks = HashObjSets.newUpdatableSet();
Map<EnumCreatureType, Set<ChunkCoordIntPair>> creatureChunks = new EnumMap<>(EnumCreatureType.class);
int[] typeNumSpawn = new int[EnumCreatureType.values().length];
for (EnumCreatureType type : EnumCreatureType.values()) {
if (type.passive() && !spawnPassives) continue;
if (!type.passive() && !spawnMonsters) continue;
if (type.rare() && !spawnRare) continue;
if (getCreatureLimit(world, type) <= 0) continue;
creatureChunks.put(type, HashObjSets.newUpdatableSet());
}
if (creatureChunks.isEmpty()) return;
for (EntityHuman player : world.players) {
if (!player.affectsSpawning || player.isSpectator()) continue;
int spawnRange = getSpawnRange(world, player);
if (spawnRange <= 0) continue;
int playerChunkX = MathHelper.floor(player.locX / 16.0);
int playerChunkZ = MathHelper.floor(player.locZ / 16.0);
rangeChunks.clear();
for (int dX = -spawnRange; dX <= spawnRange; ++dX) {
for (int dZ = -spawnRange; dZ <= spawnRange; ++dZ) {
ChunkCoordIntPair chunkCoord = new ChunkCoordOrdinalInt3Tuple(dX + playerChunkX, dZ + playerChunkZ, hashOrdinal);
if (!world.getWorldBorder().isInBounds(chunkCoord)) continue;
PlayerChunk pChunk = world.getPlayerChunkMap().getChunk(chunkCoord.x, chunkCoord.z);
if (pChunk == null || !pChunk.isDone() || pChunk.chunk == null) continue;
rangeChunks.add(pChunk.chunk);
}
}
for (EnumCreatureType type : creatureChunks.keySet()) {
int limit = getCreatureLimit(world, type);
int creatureTotal = 0;
for (Chunk chunk : rangeChunks)
creatureTotal += chunk.creatureCounts[type.ordinal()];
// if our local count is above the limit, dont qualify our chunks
if (creatureTotal >= limit) continue;
Set<ChunkCoordIntPair> chunks = creatureChunks.get(type);
for (Chunk chunk : rangeChunks)
chunks.add(chunk.getPos());
// expect number is rather meaningless, just a ceil
int expect = limit - creatureTotal;
typeNumSpawn[type.ordinal()] = Math.max(typeNumSpawn[type.ordinal()], expect);
}
}
for (EnumCreatureType type : creatureChunks.keySet()) {
Set<ChunkCoordIntPair> chunks = creatureChunks.get(type);
if (!chunks.isEmpty())
spawnMob0(world, chunks, type, typeNumSpawn[type.ordinal()]);
}
}
}

View File

@@ -0,0 +1,47 @@
package io.akarin.server.misc;
import net.minecraft.server.ChunkCoordIntPair;
import com.google.common.hash.Hashing;
import com.google.common.hash.HashFunction;
/*
* Reference on spawning mechanics by Colin Godsey <crgodsey@gmail.com>
* https://github.com/yesdog/Paper/blob/0de3dd84b7e6688feb42af4fe6b4f323ce7e3013/Spigot-Server-Patches/0433-alternate-mob-spawning-mechanic.patch
*/
public class ChunkCoordOrdinalInt3Tuple extends ChunkCoordIntPair {
public static final HashFunction hashFunc = Hashing.murmur3_32("akarin".hashCode());
public final int ordinal;
public final int cachedHashCode;
public ChunkCoordOrdinalInt3Tuple(int x, int z, int ord) {
super(x, z);
this.ordinal = ord;
cachedHashCode = hashFunc.newHasher()
.putInt(ordinal)
.putInt(x)
.putInt(z)
.hash().asInt();
}
@Override
public int hashCode() {
return cachedHashCode;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
} else if (!(object instanceof ChunkCoordOrdinalInt3Tuple)) {
return false;
} else {
ChunkCoordOrdinalInt3Tuple pair = (ChunkCoordOrdinalInt3Tuple) object;
return this.x == pair.x && this.z == pair.z && this.ordinal == pair.ordinal;
}
}
}

View File

@@ -587,9 +587,9 @@ public abstract class BiomeBase {
public static class BiomeMeta extends WeightedRandom.WeightedRandomChoice {
public EntityTypes<? extends EntityInsentient> b;
public int c;
public int d;
public EntityTypes<? extends EntityInsentient> b; public EntityTypes<? extends EntityInsentient> entityType() { return b; } // Akarin
public int c; public int getMinPackSize() { return c; } // Akarin - OBFHELPER
public int d; public int getMaxPackSize() { return d; } // Akarin - OBFHELPER
public BiomeMeta(EntityTypes<? extends EntityInsentient> entitytypes, int i, int j, int k) {
super(i);

View File

@@ -75,6 +75,7 @@ public class Chunk implements IChunkAccess {
private int D;
private final AtomicInteger E;
private final ChunkCoordIntPair F;
public final int[] creatureCounts = new int[EnumCreatureType.values().length]; // Akarin
// CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking
private volatile int neighbors = 0x1 << 12; // Akarin - volatile
@@ -766,6 +767,12 @@ public class Chunk implements IChunkAccess {
itemCounts[k]++;
} else if (entity instanceof IInventory) {
inventoryEntityCounts[k]++;
// Akarin start
} else if (entity instanceof IAnimal) {
for (EnumCreatureType type : EnumCreatureType.values())
if (type.matches(entity))
creatureCounts[type.ordinal()]++;
// Akarin end
}
// Paper end
}
@@ -800,6 +807,14 @@ public class Chunk implements IChunkAccess {
itemCounts[i]--;
} else if (entity instanceof IInventory) {
inventoryEntityCounts[i]--;
// Akarin start
} else if (entity instanceof IAnimal) {
for (EnumCreatureType type : EnumCreatureType.values())
if (type.matches(entity)) {
int typeCount = creatureCounts[type.ordinal()];
creatureCounts[type.ordinal()] = typeCount > 1 ? --typeCount : 0;
}
// Akarin end
}
entityCounts.decrement(entity.getMinecraftKeyString());
// Paper end

View File

@@ -754,6 +754,7 @@ public abstract class EntityInsentient extends EntityLiving {
return f + f3;
}
public boolean canSpawnHere() { return a((GeneratorAccess) this.world, false); } // Akarin
public boolean a(GeneratorAccess generatoraccess, boolean flag) {
IBlockData iblockdata = generatoraccess.getType((new BlockPosition(this)).down());
@@ -764,10 +765,12 @@ public abstract class EntityInsentient extends EntityLiving {
return this.a((IWorldReader) this.world);
}
public boolean isNotColliding(IWorldReader iworldreader) { return a(iworldreader); } // Akarin
public boolean a(IWorldReader iworldreader) {
return !iworldreader.containsLiquid(this.getBoundingBox()) && iworldreader.getCubes(this, this.getBoundingBox()) && iworldreader.a_(this, this.getBoundingBox());
}
public int maxPackSize() { return dg(); } // Akarin
public int dg() {
return 4;
}

View File

@@ -1,5 +1,6 @@
package net.minecraft.server;
import com.koloboke.collect.map.hash.HashObjObjMaps;
import com.mojang.datafixers.DataFixUtils;
import com.mojang.datafixers.types.Type;
@@ -125,8 +126,8 @@ public class EntityTypes<T extends Entity> {
EntityTypes<T> entitytypes = entitytypes_a.a(s);
// Paper start
if (clsToKeyMap == null ) clsToKeyMap = new java.util.HashMap<>();
if (clsToTypeMap == null ) clsToTypeMap = new java.util.HashMap<>();
if (clsToKeyMap == null ) clsToKeyMap = HashObjObjMaps.newUpdatableMap(); // Akarin
if (clsToTypeMap == null ) clsToTypeMap = HashObjObjMaps.newUpdatableMap(); // Akarin
MinecraftKey key = new MinecraftKey(s);
Class<? extends T> entityClass = entitytypes_a.getEntityClass();
@@ -249,6 +250,7 @@ public class EntityTypes<T extends Entity> {
return this.aV;
}
public Class<? extends T> entityClass() { return this.c(); } // Akarin
public Class<? extends T> c() {
return this.aS;
}

View File

@@ -22,14 +22,17 @@ public enum EnumCreatureType {
return this.e;
}
public int spawnLimit() { return this.b(); } // Akarin
public int b() {
return this.f;
}
public boolean passive() { return c(); } // Akarin
public boolean c() {
return this.g;
}
public boolean rare() { return d(); } // Akarin
public boolean d() {
return this.h;
}

View File

@@ -302,6 +302,7 @@ public class PlayerChunk {
return false;
}
public boolean isDone() { return e(); } // Paper - OBFHELPER
public boolean e() {
return this.done;
}

View File

@@ -1,6 +1,10 @@
package net.minecraft.server;
import com.google.common.collect.Sets;
import io.akarin.server.core.AkarinCreatureSpanwner;
import io.akarin.server.core.AkarinGlobalConfig;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
@@ -25,6 +29,13 @@ public final class SpawnerCreature {
public SpawnerCreature() {}
public int a(WorldServer worldserver, boolean flag, boolean flag1, boolean flag2) {
// Akarin start
if (AkarinGlobalConfig.improvedMobSpawnMechanics) {
int before = worldserver.entityList.size();
AkarinCreatureSpanwner.spawnMobs(worldserver, flag, flag1, flag2);
return worldserver.entityList.size() - before;
}
// Akarin end
if (!flag && !flag1) {
return 0;
} else {
@@ -253,7 +264,7 @@ public final class SpawnerCreature {
}
}
private static BlockPosition getRandomPosition(World world, int i, int j) {
public static BlockPosition getRandomPosition(World world, int i, int j) { // Akarin - public
Chunk chunk = world.getChunkAt(i, j);
int k = i * 16 + world.random.nextInt(16);
int l = j * 16 + world.random.nextInt(16);
@@ -267,6 +278,7 @@ public final class SpawnerCreature {
return iblockdata.k() ? false : (iblockdata.isPowerSource() ? false : (!fluid.e() ? false : !iblockdata.a(TagsBlock.RAILS)));
}
public static boolean isValidSpawnSurface(EntityPositionTypes.Surface entitypositiontypes_surface, IWorldReader iworldreader, BlockPosition blockposition, @Nullable EntityTypes<? extends EntityInsentient> entitytypes) { return a(entitypositiontypes_surface, iworldreader, blockposition, entitytypes); } // Akarin
public static boolean a(EntityPositionTypes.Surface entitypositiontypes_surface, IWorldReader iworldreader, BlockPosition blockposition, @Nullable EntityTypes<? extends EntityInsentient> entitytypes) {
if (entitytypes != null && iworldreader.getWorldBorder().a(blockposition)) {
IBlockData iblockdata = iworldreader.getType(blockposition);

View File

@@ -341,6 +341,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
return this.P;
}
@Nullable public BiomeBase.BiomeMeta getBiomeMetaAt(EnumCreatureType enumcreaturetype, BlockPosition blockposition) { return a(enumcreaturetype, blockposition); } // Akarin
@Nullable
public BiomeBase.BiomeMeta a(EnumCreatureType enumcreaturetype, BlockPosition blockposition) {
List<BiomeBase.BiomeMeta> list = this.getChunkProvider().a(enumcreaturetype, blockposition);
@@ -348,6 +349,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
return list.isEmpty() ? null : (BiomeBase.BiomeMeta) WeightedRandom.a(this.random, list);
}
public boolean isBiomeMetaValidAt(EnumCreatureType enumcreaturetype, BiomeBase.BiomeMeta biomebase_biomemeta, BlockPosition blockposition) { return this.a(enumcreaturetype, biomebase_biomemeta, blockposition); } // Akarin
public boolean a(EnumCreatureType enumcreaturetype, BiomeBase.BiomeMeta biomebase_biomemeta, BlockPosition blockposition) {
List<BiomeBase.BiomeMeta> list = this.getChunkProvider().a(enumcreaturetype, blockposition);