/*
* This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT).
*
* Copyright (c) 2015 contributors
*
* 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.
*/
package cubicchunks.world;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.EnumCreatureType;
import net.minecraft.entity.IEntityLivingData;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraft.world.WorldEntitySpawner;
import net.minecraft.world.WorldServer;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.fml.common.eventhandler.Event;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import cubicchunks.server.CubeWatcher;
import cubicchunks.util.CubePos;
public class CubeWorldEntitySpawner extends WorldEntitySpawner {
private static final int CUBES_PER_CHUNK = 16;
private static final int MOB_COUNT_DIV = (int) Math.pow(17.0D, 2.0D)*CUBES_PER_CHUNK;
private static final int SPAWN_RADIUS = 8;
private Set<CubePos> cubesForSpawn = new HashSet<>();
@Override
public int findChunksForSpawning(WorldServer worldOrig, boolean hostileEnable, boolean peacefulEnable, boolean spawnOnSetTickRate) {
if (!hostileEnable && !peacefulEnable) {
return 0;
}
ICubicWorldServer world = (ICubicWorldServer) worldOrig;
this.cubesForSpawn.clear();
int chunkCount = addEligibleChunks(world, this.cubesForSpawn);
int totalSpawnCount = 0;
for (EnumCreatureType mobType : EnumCreatureType.values()) {
if (!shouldSpawnType(mobType, hostileEnable, peacefulEnable, spawnOnSetTickRate)) {
continue;
}
int worldEntityCount = world.countEntities(mobType, true);
int maxEntityCount = mobType.getMaxNumberOfCreature()*chunkCount/MOB_COUNT_DIV;
if (worldEntityCount > maxEntityCount) {
continue;
}
ArrayList<CubePos> shuffled = getShuffledCopy(this.cubesForSpawn);
totalSpawnCount += spawnCreatureTypeInAllChunks(mobType, world, shuffled);
}
return totalSpawnCount;
}
private int addEligibleChunks(ICubicWorldServer world, Set<CubePos> possibleChunks) {
int chunkCount = 0;
for (EntityPlayer player : world.getPlayerEntities()) {
if (player.isSpectator()) {
continue;
}
CubePos center = CubePos.fromEntity(player);
for (int cubeXRel = -SPAWN_RADIUS; cubeXRel <= SPAWN_RADIUS; ++cubeXRel) {
for (int cubeYRel = -SPAWN_RADIUS; cubeYRel <= SPAWN_RADIUS; ++cubeYRel) {
for (int cubeZRel = -SPAWN_RADIUS; cubeZRel <= SPAWN_RADIUS; ++cubeZRel) {
boolean isEdge = cubeXRel == -SPAWN_RADIUS || cubeXRel == SPAWN_RADIUS ||
cubeYRel == -SPAWN_RADIUS || cubeYRel == SPAWN_RADIUS ||
cubeZRel == -SPAWN_RADIUS || cubeZRel == SPAWN_RADIUS;
CubePos chunkPos = center.add(cubeXRel, cubeYRel, cubeZRel);
if (possibleChunks.contains(chunkPos)) {
continue;
}
++chunkCount;
if (isEdge || !world.getWorldBorder().contains(chunkPos.chunkPos())) {
continue;
}
CubeWatcher chunkInfo = world.getPlayerCubeMap().getCubeWatcher(chunkPos);
if (chunkInfo != null && chunkInfo.isSentToPlayers()) {
possibleChunks.add(chunkPos);
}
}
}
}
}
return chunkCount;
}
private int spawnCreatureTypeInAllChunks(EnumCreatureType mobType, ICubicWorldServer world, ArrayList<CubePos> chunkList) {
BlockPos spawnPoint = world.getSpawnPoint();
BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos();
int totalSpawned = 0;
nextChunk:
for (CubePos currentChunkPos : chunkList) {
BlockPos blockpos = getRandomChunkPosition(world, currentChunkPos);
if (blockpos == null) {
continue nextChunk;
}
IBlockState block = world.getBlockState(blockpos);
if (block.isNormalCube()) {
continue;
}
int blockX = blockpos.getX();
int blockY = blockpos.getY();
int blockZ = blockpos.getZ();
int currentPackSize = 0;
for (int k2 = 0; k2 < 3; ++k2) {
int entityBlockX = blockX;
int entityY = blockY;
int entityBlockZ = blockZ;
int searchRadius = 6;
Biome.SpawnListEntry biomeMobs = null;
IEntityLivingData entityData = null;
int numSpawnAttempts = MathHelper.ceil(Math.random()*4.0D);
Random rand = world.getRand();
for (int spawnAttempt = 0; spawnAttempt < numSpawnAttempts; ++spawnAttempt) {
entityBlockX += rand.nextInt(searchRadius) - rand.nextInt(searchRadius);
entityY += rand.nextInt(1) - rand.nextInt(1);
entityBlockZ += rand.nextInt(searchRadius) - rand.nextInt(searchRadius);
blockPos.setPos(entityBlockX, entityY, entityBlockZ);
float entityX = (float) entityBlockX + 0.5F;
float entityZ = (float) entityBlockZ + 0.5F;
if (world.isAnyPlayerWithinRangeAt(entityX, entityY, entityZ, 24.0D) ||
spawnPoint.distanceSq(entityX, entityY, entityZ) < 576.0D) {
continue;
}
if (biomeMobs == null) {
biomeMobs = world.getSpawnListEntryForTypeAt(mobType, blockPos);
if (biomeMobs == null) {
break;
}
}
if (!world.canCreatureTypeSpawnHere(mobType, biomeMobs, blockPos) ||
!canCreatureTypeSpawnAtLocation(EntitySpawnPlacementRegistry
.getPlacementForEntity(biomeMobs.entityClass), (World) world, blockPos)) {
continue;
}
EntityLiving toSpawn;
try {
toSpawn = biomeMobs.entityClass.getConstructor(new Class[]{
World.class
}).newInstance(world);
} catch (Exception exception) {
exception.printStackTrace();
//TODO: throw when entity creation fails
return totalSpawned;
}
toSpawn.setLocationAndAngles(entityX, entityY, entityZ, rand.nextFloat()*360.0F, 0.0F);
Event.Result canSpawn = ForgeEventFactory.canEntitySpawn(toSpawn, (World) world, entityX, entityY, entityZ);
if (canSpawn == Event.Result.ALLOW ||
(canSpawn == Event.Result.DEFAULT && toSpawn.getCanSpawnHere() &&
toSpawn.isNotColliding())) {
if (!ForgeEventFactory.doSpecialSpawn(toSpawn, (World) world, entityX, entityY, entityZ))
entityData = toSpawn.onInitialSpawn(world.getDifficultyForLocation(new BlockPos(toSpawn)), entityData);
if (toSpawn.isNotColliding()) {
++currentPackSize;
world.spawnEntityInWorld(toSpawn);
} else {
toSpawn.setDead();
}
if (blockZ >= ForgeEventFactory.getMaxSpawnPackSize(toSpawn)) {
continue nextChunk;
}
}
totalSpawned += currentPackSize;
}
}
}
return totalSpawned;
}
private static <T> ArrayList<T> getShuffledCopy(Collection<T> collection) {
ArrayList<T> list = new ArrayList<>(collection);
Collections.shuffle(list);
return list;
}
private static boolean shouldSpawnType(EnumCreatureType type, boolean hostile, boolean peaceful, boolean spawnOnSetTickRate) {
return !((type.getPeacefulCreature() && !peaceful) ||
(!type.getPeacefulCreature() && !hostile) ||
(type.getAnimal() && !spawnOnSetTickRate));
}
protected static BlockPos getRandomChunkPosition(ICubicWorldServer world, CubePos pos) {
int blockX = pos.getMinBlockX() + world.getRand().nextInt(16);
int blockZ = pos.getMinBlockZ() + world.getRand().nextInt(16);
int height = world.getEffectiveHeight(blockX, blockZ);
if (pos.getMinBlockY() > height) {
return null;
}
int blockY = pos.getMinBlockY() + world.getRand().nextInt(16);
return new BlockPos(blockX, blockY, blockZ);
}
}