package erebus.world;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent.Phase;
import net.minecraftforge.fml.common.gameevent.TickEvent.ServerTickEvent;
import erebus.core.handler.configs.ConfigHandler;
import erebus.world.biomes.BiomeBaseErebus;
import erebus.world.loot.IWeightProvider;
import gnu.trove.map.hash.TObjectIntHashMap;
public final class SpawnerErebus {
public static final SpawnerErebus INSTANCE = new SpawnerErebus();
public static final int MAX_MOBS_PER_WORLD = 300;
public static void onChunkPopulate(World world, Random rand, BiomeBaseErebus biome, int x, int z) {
if (!world.isRemote && world.getGameRules().getBoolean("doMobSpawning"))
INSTANCE.runPopulationSpawning((WorldServer) world, rand, biome, x, z);
}
@SubscribeEvent
public void onServerTick(ServerTickEvent e) {
if (e.phase != Phase.START)
return;
WorldServer erebusWorld = DimensionManager.getWorld(ConfigHandler.INSTANCE.erebusDimensionID);
if (erebusWorld != null && erebusWorld.getGameRules().getBoolean("doMobSpawning"))
runGradualSpawning(erebusWorld);
}
private boolean canSpawnHostiles;
private boolean canSpawnAnimals;
private Map<ChunkPos, Boolean> spawnChunks = new HashMap<ChunkPos, Boolean>(64);
private void prepare(WorldServer world) {
WorldProviderErebus provider = (WorldProviderErebus) world.provider;
canSpawnHostiles = provider.getCanSpawnHostiles();
canSpawnAnimals = provider.getCanSpawnAnimals();
}
@SuppressWarnings("unchecked")
private void runGradualSpawning(WorldServer world) {
prepare(world);
if (!canSpawnHostiles && !canSpawnAnimals)
return;
spawnChunks.clear();
List<EntityPlayer> players = world.playerEntities;
int chunkX, chunkZ, px, pz;
byte dist = 8;
for (EntityPlayer player : players) {
chunkX = player.chunkCoordX;
chunkZ = player.chunkCoordZ;
for (px = -dist; px <= dist; px++)
for (pz = -dist; pz <= dist; pz++) {
ChunkPos coords = new ChunkPos(chunkX + px, chunkZ + pz);
if (px == -dist || px == dist || pz == -dist || pz == dist || Math.abs(px) <= 1 || Math.abs(pz) <= 1) {
if (!spawnChunks.containsKey(coords))
spawnChunks.put(coords, Boolean.valueOf(false));
} else
spawnChunks.put(coords, Boolean.valueOf(true));
}
}
if (spawnChunks.isEmpty())
return;
TObjectIntHashMap<Class<? extends Entity>> entityCount = new TObjectIntHashMap<Class<? extends Entity>>();
for (Object entity : world.loadedEntityList)
if (entity instanceof EntityLiving)
entityCount.adjustOrPutValue(((Entity) entity).getClass(), 1, 1);
int totalAmount = 0;
for (int amt : entityCount.values())
totalAmount += amt;
if (totalAmount >= Math.min(spawnChunks.size() >> 1, MAX_MOBS_PER_WORLD) / (world.getDifficulty() == EnumDifficulty.PEACEFUL ? 2 : 1))
return;
List<ChunkPos> chunksToTest = new ArrayList<ChunkPos>();
for (Entry<ChunkPos, Boolean> entry : spawnChunks.entrySet())
if (entry.getValue())
chunksToTest.add(entry.getKey());
Random rand = world.rand;
int x, y, z, spawned, spawnGroup, attempts, posAttempts, maxPosAttempts, testedChunks = 3 + rand.nextInt(1 + 2 * world.getDifficulty().getDifficultyId());
float fx, fy, fz, yaw = 0F;
boolean continueSpawning, coordsFinal;
Collections.shuffle(chunksToTest, rand);
for (ChunkPos coords : chunksToTest) {
spawned = attempts = 0;
while (attempts < 4 && spawned < 2) {
x = coords.chunkXPos * 16 + rand.nextInt(16);
z = coords.chunkZPos * 16 + rand.nextInt(16);
y = 10 + rand.nextInt(100);
BlockPos blockCoord = new BlockPos(x, y, z);
Biome biome = world.getBiomeGenForCoords(blockCoord);
if (!(biome instanceof BiomeBaseErebus))
break;
SpawnEntry entry = ((BiomeBaseErebus) biome).getRandomSpawnGradual(rand);
if (entry == null)
break;
++attempts;
if (entry.isHostile && !canSpawnHostiles || !entry.isHostile && !canSpawnAnimals)
continue;
if (rand.nextFloat() > biome.getSpawningChance() || entry.worldLimit != -1 && entityCount.get(entry.mobClass) >= entry.worldLimit)
continue;
EntityLiving entity = null;
spawnGroup = entry.minGroupSize + rand.nextInt(entry.maxGroupSize - entry.minGroupSize + 1);
continueSpawning = true;
coordsFinal = false;
maxPosAttempts = 20 + spawnGroup * 2;
while (continueSpawning) {
for (posAttempts = 0; posAttempts < maxPosAttempts; posAttempts++) {
fx = x + rand.nextInt(12) - 6 + 0.5F;
fy = y + rand.nextInt(2) - 1;
fz = z + rand.nextInt(12) - 6 + 0.5F;
if ((entry.blockBelow == null && world.getBlockState(new BlockPos ((int) fx, (int) fy - 1, (int) fz)).isNormalCube() || world.getBlockState(new BlockPos ((int) fx, (int) fy - 1, (int) fz)) == entry.blockBelow) && world.getClosestPlayer(fx, fy, fz, 24D, false) == null) {
if (!coordsFinal) {
coordsFinal = true;
posAttempts = 10;
}
if (entity == null) {
entity = entry.createEntity(world);
yaw = rand.nextFloat() * 360F;
}
if (entity == null) {
continueSpawning = false;
--spawned;
break;
}
entity.setLocationAndAngles(fx, fy, fz, yaw, 0F);
if (entity.getCanSpawnHere()) {
world.spawnEntityInWorld(entity);
entity = null;
if (--spawnGroup <= 0)
continueSpawning = false;
break;
}
}
if (!coordsFinal) {
x = coords.chunkXPos * 16 + rand.nextInt(16);
z = coords.chunkZPos * 16 + rand.nextInt(16);
y = 20 + rand.nextInt(80);
}
if (posAttempts == maxPosAttempts - 1)
continueSpawning = false;
}
++spawned;
}
}
if (--testedChunks <= 0)
break;
}
}
private void runPopulationSpawning(WorldServer world, Random rand, BiomeBaseErebus biome, int x, int z) {
prepare(world);
// TODO maybe I'll finish this one sometime...
}
private SpawnerErebus() {
}
public static final class SpawnEntry implements IWeightProvider {
private final Constructor<? extends EntityLiving> mobConstructor;
protected final Class<? extends EntityLiving> mobClass;
protected final short weight;
protected final boolean isHostile;
protected byte minGroupSize = 1, maxGroupSize = 1;
protected int worldLimit = 10;
protected Block blockBelow = null;
public SpawnEntry(Class<? extends EntityLiving> mobClass, int weight) {
this.mobClass = mobClass;
this.weight = (short) weight;
isHostile = IMob.class.isAssignableFrom(mobClass);
try {
mobConstructor = mobClass.getConstructor(World.class);
} catch (Exception e) {
throw new RuntimeException("Could not find constructor (World) of mob " + mobClass.getSimpleName(), e);
}
}
/**
* The spawner will attempt to spawn the mob in a group (it is not guaranteed to spawn minGroupSize mobs, but it will go for a number between that and maxGroupSize)
*/
public SpawnEntry setGroupSize(int minGroupSize, int maxGroupSize) {
this.minGroupSize = (byte) minGroupSize;
this.maxGroupSize = (byte) maxGroupSize;
return this;
}
/**
* Maximum amount of mobs of this type per world
*/
public SpawnEntry setWorldLimit(int mobAmountLimit) {
worldLimit = mobAmountLimit;
return this;
}
/**
* Defaults to null = any solid block
*/
public SpawnEntry setBlockBelow(Block block) {
blockBelow = block;
return this;
}
@Override
public short getWeight() {
return weight;
}
public EntityLiving createEntity(World world) {
try {
return mobConstructor.newInstance(world);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
}