package jas.spawner.refactor;
import jas.common.JASLog;
import jas.common.JustAnotherSpawner;
import jas.common.global.BiomeBlacklist;
import jas.common.helper.MVELHelper;
import jas.spawner.modern.EntityProperties;
import jas.spawner.modern.spawner.CountInfo;
import jas.spawner.modern.spawner.CountInfo.ChunkStat;
import jas.spawner.modern.spawner.EntityCounter;
import jas.spawner.modern.spawner.Tags;
import jas.spawner.modern.spawner.biome.group.BiomeHelper;
import jas.spawner.refactor.LivingTypeBuilder.LivingType;
import jas.spawner.refactor.SpawnSettings.BiomeSettings;
import jas.spawner.refactor.SpawnSettings.LivingSettings;
import jas.spawner.refactor.SpawnerHelper.Counter.SpawnCounter;
import jas.spawner.refactor.biome.list.SpawnListEntryBuilder.SpawnListEntry;
import jas.spawner.refactor.despawn.DespawnRuleBuilder.DespawnRule;
import jas.spawner.refactor.entities.LivingHandlerBuilder.LivingHandler;
import jas.spawner.refactor.mvel.MVELExpression;
import jas.spawner.refactor.structure.StructureHandlerBuilder.StructureHandler;
import jas.spawner.refactor.structure.StructureHandlers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.IEntityLivingData;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ChunkCoordinates;
import net.minecraft.util.MathHelper;
import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.world.ChunkPosition;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.event.ForgeEventFactory;
import org.apache.logging.log4j.Level;
import com.google.common.base.Optional;
import cpw.mods.fml.common.eventhandler.Event.Result;
/**
* Common Spawner Processes
*/
public class SpawnerHelper {
// TODO create interface for different spawners
public interface SpawnerLogic {
public Counter counter(World world);
public void spawnCycle(World worldServer, CountInfo countInfo, LivingType creatureType);
}
public static SpawnCounter counter = new SpawnCounter();
// public static Despawner despawn;
// public static Spawner spawner;
public static interface Counter {
public CountInfo countEntities(World world);
public CountInfo countEntities(World world, int spawnChunkDistance, int countChunkDistance);
public Collection<Entity> countLoadedEntities(World world, int chunkDistance);
public static final class SpawnCounter implements Counter {
@Override
public CountInfo countEntities(World world) {
return countEntities(world, JustAnotherSpawner.globalSettings().chunkSpawnDistance,
JustAnotherSpawner.globalSettings().chunkCountDistance);
}
@Override
public CountInfo countEntities(World world, int spawnChunkDistance, int countChunkDistance) {
// JustAnotherSpawner.globalSettings().chunkSpawnDistance
HashMap<ChunkCoordIntPair, ChunkStat> eligibleChunksForSpawning = determineChunksForSpawning(world,
spawnChunkDistance);
return countInfo(world, eligibleChunksForSpawning, countChunkDistance);
}
private HashMap<ChunkCoordIntPair, ChunkStat> determineChunksForSpawning(World world, int chunkDistance) {
HashMap<ChunkCoordIntPair, ChunkStat> eligibleChunksForSpawning = new HashMap<ChunkCoordIntPair, ChunkStat>();
for (int i = 0; i < world.playerEntities.size(); ++i) {
EntityPlayer entityplayer = (EntityPlayer) world.playerEntities.get(i);
int posX = MathHelper.floor_double(entityplayer.posX / 16.0D);
int posZ = MathHelper.floor_double(entityplayer.posZ / 16.0D);
for (int xOffset = -chunkDistance; xOffset <= chunkDistance; ++xOffset) {
for (int zOffset = -chunkDistance; zOffset <= chunkDistance; ++zOffset) {
boolean flag3 = xOffset == -chunkDistance || xOffset == chunkDistance
|| zOffset == -chunkDistance || zOffset == chunkDistance;
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(xOffset + posX, zOffset + posZ);
eligibleChunksForSpawning.put(chunkcoordintpair, new ChunkStat(flag3));
}
}
}
return eligibleChunksForSpawning;
}
private CountInfo countInfo(World world, HashMap<ChunkCoordIntPair, ChunkStat> eligibleChunksForSpawning,
int countChunkDistance) {
EntityCounter creatureTypeCount = new EntityCounter();
EntityCounter creatureCount = new EntityCounter();
for (Object object : world.loadedEntityList) {
Entity entity = (Entity) object;
EntityPlayer player = world.getClosestPlayerToEntity(entity, countChunkDistance * 16);
if (isPlayerClose(world, entity, countChunkDistance * 16)) {
ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(
MathHelper.floor_double(entity.posX / 16.0D),
MathHelper.floor_double(entity.posZ / 16.0D));
SpawnSettings spawnSettings = ExperimentalProfile.worldSettings().getSpawnSettings(world);
Collection<String> livingTypes = ExperimentalProfile.worldSettings().getSpawnSettings(world)
.biomeGroupRegistry()
.livingTypesForEntity(world, entity, spawnSettings.livingSettings());
creatureCount.incrementOrPutIfAbsent(entity.getClass().getSimpleName(), 1);
for (String creatureTypeID : livingTypes) {
creatureTypeCount.incrementOrPutIfAbsent(creatureTypeID, 1);
}
ChunkStat chunkStat = eligibleChunksForSpawning.get(chunkPos);
if (chunkStat != null) {
chunkStat.entityClassCount.incrementOrPutIfAbsent(entity.getClass().getSimpleName(), 1);
for (String creatureTypeID : livingTypes) {
chunkStat.entityTypeCount.incrementOrPutIfAbsent(creatureTypeID, 1);
}
}
}
}
return new CountInfo(eligibleChunksForSpawning, creatureTypeCount, creatureCount);
}
public Collection<Entity> countLoadedEntities(World world) {
return countLoadedEntities(world, JustAnotherSpawner.globalSettings().chunkCountDistance);
}
@Override
public Collection<Entity> countLoadedEntities(World world, int chunkDistance) {
List<Entity> entities = new ArrayList<Entity>();
if (chunkDistance <= 0) {
return world.loadedEntityList;
}
for (Object object : world.loadedEntityList) {
Entity entity = (Entity) object;
if (isPlayerClose(world, entity, chunkDistance * 16)) {
entities.add(entity);
}
}
return entities;
}
private boolean isPlayerClose(World world, Entity entity, double cutoffDist) {
for (int i = 0; i < world.playerEntities.size(); ++i) {
EntityPlayer player = (EntityPlayer) world.playerEntities.get(i);
double xDist = player.posX - entity.posX;
double zDist = player.posZ - entity.posZ;
double curDist = (xDist * xDist + zDist * zDist);
if ((cutoffDist < 0.0D || curDist < cutoffDist * cutoffDist)) {
return true;
}
}
return false;
}
}
}
// public static CountInfo countEntities(World world) {
// return SpawnerLogic.counter.countEntities(world);
// }
public static void despawnEntity(EntityLiving entity, Tags tags, DespawnRule despawnRule) {
EntityPlayer entityplayer = entity.worldObj.getClosestPlayerToEntity(entity, -1.0D);
int xCoord = MathHelper.floor_double(entity.posX);
int yCoord = MathHelper.floor_double(entity.boundingBox.minY);
int zCoord = MathHelper.floor_double(entity.posZ);
if (entityplayer != null) {
double d0 = entityplayer.posX - entity.posX;
double d1 = entityplayer.posY - entity.posY;
double d2 = entityplayer.posZ - entity.posZ;
double playerDistance = d0 * d0 + d1 * d1 + d2 * d2;
EntityProperties entityProps = (EntityProperties) entity
.getExtendedProperties(EntityProperties.JAS_PROPERTIES);
entityProps.incrementAge(60);
boolean canDespawn;
if (despawnRule.canDspwn.isPresent()) {
canDespawn = !MVELHelper.executeExpression(despawnRule.canDspwn.get().compiled.get(), tags,
"Error processing canSpawn compiled expression for " + despawnRule.content() + ": "
+ despawnRule.canDspwn.get().expression);
} else {
canDespawn = LivingHelper.canDespawn(entity);
}
if (canDespawn == false) {
entityProps.resetAge();
return;
}
boolean canInstantDespawn;
if (despawnRule.shouldInstantDspwn.isPresent()) {
canInstantDespawn = !MVELHelper.executeExpression(despawnRule.shouldInstantDspwn.get().compiled.get(),
tags, "Error processing canSpawn compiled expression for " + despawnRule.content() + ": "
+ despawnRule.shouldInstantDspwn.get().expression);
} else {
Integer maxRange = ExperimentalProfile.worldSettings().worldProperties().getGlobal().maxDespawnDist;
canInstantDespawn = playerDistance > maxRange * maxRange;
}
if (canInstantDespawn) {
entity.setDead();
} else {
boolean dieOfAge;
final int rate = 40; // Value from Vanilla
if (despawnRule.dieOfAge.isPresent()) {
dieOfAge = !MVELHelper.executeExpression(despawnRule.dieOfAge.get().compiled.get(), tags,
"Error processing canSpawn compiled expression for " + despawnRule.content() + ": "
+ despawnRule.dieOfAge.get().expression);
} else {
Integer minRange = ExperimentalProfile.worldSettings().worldProperties().getGlobal().despawnDist;
Integer minAge = ExperimentalProfile.worldSettings().worldProperties().getGlobal().minDespawnTime;
boolean isOfAge = entityProps.getAge() > minAge;
boolean validDistance = playerDistance > minRange * minRange;
dieOfAge = isOfAge && entity.worldObj.rand.nextInt(1 + rate / 3) == 0 && validDistance;
}
boolean resetAge;
if (despawnRule.resetAge.isPresent()) {
resetAge = !MVELHelper.executeExpression(despawnRule.resetAge.get().compiled.get(), tags,
"Error processing canSpawn compiled expression for " + despawnRule.content() + ": "
+ despawnRule.resetAge.get().expression);
} else {
Integer minRange = ExperimentalProfile.worldSettings().worldProperties().getGlobal().despawnDist;
Integer minAge = ExperimentalProfile.worldSettings().worldProperties().getGlobal().minDespawnTime;
boolean validDistance = playerDistance > minRange * minRange;
resetAge = !(playerDistance > minRange * minRange);
}
if (dieOfAge) {
JASLog.log().debug(Level.INFO, "Entity %s is DEAD At Age %s rate %s",
entity.getCommandSenderName(), entityProps.getAge(), rate);
entity.setDead();
} else if (resetAge) {
entityProps.resetAge();
}
}
}
}
public static boolean willEntityDespawn(EntityLiving entity, Tags tags, DespawnRule despawnRule) {
EntityPlayer entityplayer = entity.worldObj.getClosestPlayerToEntity(entity, -1.0D);
int xCoord = MathHelper.floor_double(entity.posX);
int yCoord = MathHelper.floor_double(entity.boundingBox.minY);
int zCoord = MathHelper.floor_double(entity.posZ);
if (entityplayer != null) {
double d0 = entityplayer.posX - entity.posX;
double d1 = entityplayer.posY - entity.posY;
double d2 = entityplayer.posZ - entity.posZ;
double playerDistance = d0 * d0 + d1 * d1 + d2 * d2;
EntityProperties entityProps = (EntityProperties) entity
.getExtendedProperties(EntityProperties.JAS_PROPERTIES);
entityProps.incrementAge(60);
boolean canDespawn;
if (despawnRule.canDspwn.isPresent()) {
canDespawn = !MVELHelper.executeExpression(despawnRule.canDspwn.get().compiled.get(), tags,
"Error processing canSpawn compiled expression for " + despawnRule.content() + ": "
+ despawnRule.canDspwn.get().expression);
} else {
canDespawn = LivingHelper.canDespawn(entity);
}
if (canDespawn == false) {
return false;
}
// Other Despawn logic is ignored; entity is assumed to eventually be able to age and despawn
}
return true;
}
public static void spawnCycle(World world, CountInfo countInfo, LivingType livingType, BiomeBlacklist blacklist,
BiomeSettings biomeSettings, LivingSettings livingSettings, BiomeSpawnLists biomesSpawns,
StructureHandlers structureSpawns) {
ChunkCoordinates serverOriginPoint = world.getSpawnPoint();
List<ChunkCoordIntPair> eligibleChunksForSpawning = new ArrayList<ChunkCoordIntPair>(
countInfo.eligibleChunkLocations());
Collections.shuffle(eligibleChunksForSpawning);
labelChunkStart: for (ChunkCoordIntPair chunkCoord : eligibleChunksForSpawning) {
ChunkStat chunkStat = countInfo.getChunkStat(chunkCoord);
if (chunkStat.isEdge) {
continue;
}
countInfo.resetEntitiesSpawnedThisLoop();
for (int numLocAttempts = 0; numLocAttempts < livingType.iterationsPerChunk; ++numLocAttempts) {
IEntityLivingData entitylivingdata = null;
ChunkPosition startSpawningPoint = getRandomSpawningPointInChunk(world, chunkCoord.chunkXPos,
chunkCoord.chunkZPos);
SpawnListEntry spawnlistentry = null;
Class<? extends EntityLiving> livingToSpawn = null;
LivingHandler handler = null;
countInfo.resetEntitiesPackCount();
if (livingType.quickCheck.isPresent()) {
Tags tags = new Tags(world, countInfo, startSpawningPoint.chunkPosX, startSpawningPoint.chunkPosY,
startSpawningPoint.chunkPosZ);
Optional<Boolean> quickCheckCanSpawn = MVELExpression.execute(livingType.quickCheck, tags,
"Error processing spawnExpression compiled expression for " + livingType.livingTypeID
+ ": " + livingType.quickCheck.expression);
if (quickCheckCanSpawn.isPresent() && !quickCheckCanSpawn.get()) {
continue;
}
}
for (int numEntAttempts = 0; numEntAttempts < livingType.iterationsPerPack; ++numEntAttempts) {
// Randomized on Each Attempt, but horizontally to allow a 'Pack' to spawn near each other
final int horVar = 10;
final int verVar = 3;
ChunkPosition spawningPoint = new ChunkPosition(startSpawningPoint.chunkPosX
+ world.rand.nextInt(horVar) - world.rand.nextInt(horVar), startSpawningPoint.chunkPosY
+ world.rand.nextInt(verVar) - world.rand.nextInt(verVar), startSpawningPoint.chunkPosZ
+ world.rand.nextInt(horVar) - world.rand.nextInt(horVar));
// Biome BlackList
if (blacklist.isBlacklisted(world.getBiomeGenForCoords(spawningPoint.chunkPosX,
spawningPoint.chunkPosY))) {
break;
}
if (isNearPlayerOrOrigin(world, serverOriginPoint, spawningPoint.chunkPosX,
spawningPoint.chunkPosY, spawningPoint.chunkPosZ)) {
continue;
}
// Set SpawnList Specific attributes, set only for outer loop (when SpawnListEntry == null), is done
// in inner loop after creatureType.canSpawnHere for performance reasons
// (regsitry.getSpawnListEntryToSpawn is not cheap)
if (spawnlistentry == null) {
spawnlistentry = getRandomSpawnListEntryToSpawn(world, structureSpawns, biomesSpawns,
biomeSettings, livingType, spawningPoint.chunkPosX, spawningPoint.chunkPosY,
spawningPoint.chunkPosZ);
if (spawnlistentry == null) {
break;
}
int randomElement = spawnlistentry.entityMappings.size();
String livingMappingToSpawn_JASName = spawnlistentry.entityMappings.get(randomElement);
String livingMappingToSpawn_FMLName = livingSettings.livingMappings().mappingToKey()
.get(livingMappingToSpawn_JASName);
livingToSpawn = (Class<? extends EntityLiving>) EntityList.stringToClassMapping
.get(livingMappingToSpawn_FMLName);
}
// LivingCap > TODO Ensure this can be accomplished via the CanSpawnExpression
// {
// int globalEntityClassCount = countInfo.getGlobalEntityClassCount(livingToSpawn);
// int livingCap = handler.getLivingCap();
//
// if (livingCap > 0 && globalEntityClassCount >= livingCap) {
// spawnlistentry = null;
// break;
// }
// }
/* Spawn is Centered Version of blockSpawn such that entity is not placed in Corner */
float spawnX = spawningPoint.chunkPosX + 0.5F;
float spawnY = spawningPoint.chunkPosY;
float spawnZ = spawningPoint.chunkPosZ + 0.5F;
EntityLiving entityliving;
try {
entityliving = livingToSpawn.getConstructor(new Class[] { World.class }).newInstance(
new Object[] { world });
} catch (Exception exception) {
exception.printStackTrace();
return;
}
entityliving.setLocationAndAngles(spawnX, spawnY, spawnZ, world.rand.nextFloat() * 360.0F, 0.0F);
if (canSpawn(entityliving, spawnlistentry, countInfo, livingType, spawningPoint.chunkPosX,
spawningPoint.chunkPosY, spawningPoint.chunkPosZ)) {
world.spawnEntityInWorld(entityliving);
if (!ForgeEventFactory.doSpecialSpawn(entityliving, world, spawnX, spawnY, spawnZ)) {
entitylivingdata = entityliving.onSpawnWithEgg(entitylivingdata);
}
JASLog.log().logSpawn(
false,
(String) EntityList.classToStringMapping.get(entityliving.getClass()),
livingType.livingTypeID,
(int) entityliving.posX,
(int) entityliving.posY,
(int) entityliving.posZ,
BiomeHelper.getPackageName(entityliving.worldObj.getBiomeGenForCoords(
(int) entityliving.posX, (int) entityliving.posZ)));
Tags tags = new Tags(entityliving.worldObj, countInfo, spawningPoint.chunkPosX,
spawningPoint.chunkPosY, spawningPoint.chunkPosZ, entityliving);
if (spawnlistentry.postSpawn.isPresent()) {
MVELExpression.execute(spawnlistentry.postSpawn.get(), tags,
"Error processing compiled handler postSpawn expression for "
+ livingType.livingTypeID + ": "
+ spawnlistentry.postSpawn.get().expression);
}
countInfo.countSpawn(entityliving, livingType.livingTypeID);
// Living PackSize
Optional<Integer> packSize = MVELExpression.execute(spawnlistentry.passivePackSize, tags,
"Error processing compiled handler postSpawn expression for " + livingType.livingTypeID
+ ": " + spawnlistentry.postSpawn.get().expression);
if (!packSize.isPresent() || countInfo.getEntitiesSpawnedThisLoop() >= packSize.get()) {
continue labelChunkStart;
}
}
}
}
}
}
private static ChunkPosition getRandomSpawningPointInChunk(World world, int chunkX, int chunkZ) {
Chunk chunk = world.getChunkFromChunkCoords(chunkX, chunkZ);
int xCoord = chunkX * 16 + world.rand.nextInt(16);
int zCoord = chunkZ * 16 + world.rand.nextInt(16);
int yCoord = world.rand.nextInt(chunk == null ? world.getActualHeight() : chunk.getTopFilledSegment() + 16 - 1);
return new ChunkPosition(xCoord, yCoord, zCoord);
}
private static boolean isNearPlayerOrOrigin(World world, ChunkCoordinates serverSpawnPoint, int originX,
int originY, int originZ) {
if (world.getClosestPlayer(originX, originY, originZ, 24.0D) == null) {
float xOffset = originX - serverSpawnPoint.posX;
float yOffset = originY - serverSpawnPoint.posY;
float zOffset = originZ - serverSpawnPoint.posZ;
float sqOffset = xOffset * xOffset + yOffset * yOffset + zOffset * zOffset;
if (sqOffset < 576.0F) {
return true;
}
return false;
}
return true;
}
private static SpawnListEntry getRandomSpawnListEntryToSpawn(World world, StructureHandlers structures,
BiomeSpawnLists spawnLists, BiomeSettings biomeSettings, LivingType livingType, int xCoord, int yCoord,
int zCoord) {
Collection<SpawnListEntry> spawnList = getSpawnList(world, structures, spawnLists, biomeSettings, livingType,
xCoord, yCoord, zCoord);
int totalWeight = 0;
for (SpawnListEntry entry : spawnList) {
totalWeight += entry.weight;
}
if (totalWeight <= 0) {
return null;
} else {
int selectedWeight = world.rand.nextInt(totalWeight) + 1;
SpawnListEntry resultEntry = null;
for (SpawnListEntry spawnListEntry : spawnList) {
resultEntry = spawnListEntry;
selectedWeight -= spawnListEntry.weight;
if (selectedWeight <= 0) {
return resultEntry;
}
}
return resultEntry;
}
}
private static Collection<SpawnListEntry> getSpawnList(World world, StructureHandlers structures,
BiomeSpawnLists spawnLists, BiomeSettings biomeSettings, LivingType livingType, int xCoord, int yCoord,
int zCoord) {
for (StructureHandler handler : structures.handlers()) {
Collection<SpawnListEntry> spawnList = handler.getStructureSpawnList(world, xCoord, yCoord, zCoord);
if (!spawnList.isEmpty()) {
return spawnList;
}
}
return spawnLists.getSpawnList(world, biomeSettings, world.getBiomeGenForCoords(xCoord, zCoord), livingType);
}
private static boolean canSpawn(EntityLiving entity, SpawnListEntry sle, CountInfo countInfo,
LivingType livingType, int xCoord, int yCoord, int zCoord) {
Tags tags = new Tags(entity.worldObj, countInfo, xCoord, yCoord, zCoord, entity);
Optional<Boolean> canSpawnType = MVELExpression.execute(livingType.canSpawn, tags,
"Error processing spawnExpression compiled expression for " + livingType.livingTypeID + ": "
+ livingType.quickCheck.expression);
if (canSpawnType.isPresent() && canSpawnType.get()) {
Result canSpawn = ForgeEventFactory.canEntitySpawn(entity, entity.worldObj, (int) entity.posX,
(int) entity.posY, (int) entity.posZ);
Optional<Boolean> canSpawnSLE = Optional.absent();
if (sle.canSpawn.isPresent()) {
canSpawnSLE = MVELExpression.execute(sle.canSpawn.get(), tags,
"Error processing spawnExpression compiled expression for " + livingType.livingTypeID + ": "
+ livingType.quickCheck.expression);
}
if (!canSpawnSLE.isPresent()) {
return canSpawn != Result.DENY;
} else {
return canSpawnSLE.get();
}
} else {
return false;
}
}
}