package com.hypixel.hytale.server.core.entity;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.math.block.BlockSphereUtil;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockGathering;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.asset.type.item.config.ItemTool;
import com.hypixel.hytale.server.core.entity.knockback.KnockbackComponent;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.damage.Damage;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems;
import com.hypixel.hytale.server.core.modules.interaction.BlockHarvestUtils;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.Selector;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.Knockback;
import com.hypixel.hytale.server.core.modules.physics.component.Velocity;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.util.TargetUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class ExplosionUtils {
private static final boolean DEBUG_SHAPES = false;
private static final Vector3f DEBUG_POTENTIAL_TARGET_COLOR = new Vector3f(1.0F, 1.0F, 0.0F);
private static final int DEBUG_POTENTIAL_TARGET_TIME = 5;
private static final float DEBUG_BLOCK_HIT_SCALE = 1.1F;
private static final float DEBUG_BLOCK_HIT_TIME = 2.0F;
private static final float DEBUG_BLOCK_HIT_ALPHA = 0.25F;
private static final Vector3f DEBUG_BLOCK_RADIUS_COLOR = new Vector3f(1.0F, 0.5F, 0.5F);
private static final Vector3f DEBUG_ENTITY_RADIUS_COLOR = new Vector3f(0.5F, 1.0F, 0.5F);
private static final int DEBUG_BLOCK_RADIUS_TIME = 5;
private static final int DEBUG_ENTITY_RADIUS_TIME = 5;
public ExplosionUtils() {
}
public static void performExplosion(@Nonnull Damage.Source damageSource, @Nonnull Vector3d position, @Nonnull ExplosionConfig config, @Nullable Ref<EntityStore> ignoreRef, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull ComponentAccessor<ChunkStore> chunkStore) {
if (config.damageBlocks || config.damageEntities) {
Set<Ref<EntityStore>> targetRefs = new ObjectOpenHashSet<Ref<EntityStore>>();
Vector3d blockPosition = new Vector3d(Math.floor(position.x) + 0.5, Math.floor(position.y) + 0.5, Math.floor(position.z) + 0.5);
processTargetBlocks(blockPosition, config, ignoreRef, targetRefs, commandBuffer, chunkStore);
if (config.damageEntities) {
processTargetEntities(config, position, damageSource, ignoreRef, targetRefs, commandBuffer);
}
}
}
private static void processTargetBlocks(@Nonnull Vector3d position, @Nonnull ExplosionConfig config, @Nullable Ref<EntityStore> ignoreRef, @Nonnull Set<Ref<EntityStore>> targetRefs, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull ComponentAccessor<ChunkStore> chunkStore) {
ThreadLocalRandom random = ThreadLocalRandom.current();
World world = ((EntityStore)commandBuffer.getExternalData()).getWorld();
int explosionBlockRadius = config.blockDamageRadius;
if (config.damageEntities && config.entityDamageRadius > (float)config.blockDamageRadius) {
explosionBlockRadius = (int)config.entityDamageRadius;
}
List<Ref<EntityStore>> potentialTargets = new ObjectArrayList<Ref<EntityStore>>();
if (config.damageEntities) {
double var10002 = (double)config.entityDamageRadius;
Objects.requireNonNull(potentialTargets);
Selector.selectNearbyEntities(commandBuffer, position, var10002, potentialTargets::add, (e) -> ignoreRef == null || !e.equals(ignoreRef));
}
if (config.damageBlocks || !potentialTargets.isEmpty()) {
ItemTool itemTool = config.itemTool;
Set<Vector3i> targetBlocks = new ObjectOpenHashSet<Vector3i>();
int posX = MathUtil.floor(position.x);
int posY = MathUtil.floor(position.y);
int posZ = MathUtil.floor(position.z);
BlockSphereUtil.forEachBlock(posX, posY, posZ, explosionBlockRadius, (Object)null, (x, y, z, aVoid) -> {
targetBlocks.add(new Vector3i(x, y, z));
return true;
});
Set<Vector3i> avoidBlocks = new ObjectOpenHashSet<Vector3i>();
for(Vector3i targetBlock : targetBlocks) {
Vector3d targetBlockPosition = targetBlock.toVector3d().add(0.5, 0.5, 0.5);
int setBlockSettings = 1028;
if (random.nextFloat() > config.blockDropChance) {
setBlockSettings |= 2048;
}
double distance = position.distanceTo(targetBlockPosition);
if (!(distance <= 0.0) && !Double.isNaN(distance)) {
Vector3d direction = targetBlockPosition.clone().subtract(position);
Vector3i targetBlockPos = TargetUtil.getTargetBlock(world, (id, fluidId) -> isValidTargetBlock(id, config.damageBlocks), position.x, position.y, position.z, direction.x, direction.y, direction.z, distance);
if (targetBlockPos == null) {
if (config.damageEntities) {
Vector3d entityHitPos = position.clone().add(direction);
collectPotentialTargets(targetRefs, potentialTargets, entityHitPos, position, commandBuffer);
}
} else if (!avoidBlocks.contains(targetBlockPos)) {
Vector3d targetBlockPosD = targetBlockPos.toVector3d().add(0.5, 0.5, 0.5);
if (config.damageEntities) {
collectPotentialTargets(targetRefs, potentialTargets, targetBlockPosD, position, commandBuffer);
}
float damageDistance = (float)position.distanceTo(targetBlockPosD);
float damageScale = calculateBlockDamageScale(damageDistance, (float)explosionBlockRadius, config.blockDamageFalloff);
long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlockPos.x, targetBlockPos.z);
Ref<ChunkStore> chunkReference = ((ChunkStore)chunkStore.getExternalData()).getChunkReference(chunkIndex);
if (chunkReference != null) {
boolean canDamageBlock = distance <= (double)config.blockDamageRadius;
if (!config.damageBlocks || canDamageBlock && !BlockHarvestUtils.performBlockDamage(targetBlockPos, (ItemStack)null, itemTool, damageScale, setBlockSettings, chunkReference, commandBuffer, chunkStore)) {
avoidBlocks.add(targetBlockPos);
}
}
}
}
}
}
}
private static boolean isValidTargetBlock(int blockTypeId, boolean damageBlocks) {
if (blockTypeId != 0 && blockTypeId != 1) {
if (!damageBlocks) {
BlockType blockType = (BlockType)BlockType.getAssetMap().getAsset(blockTypeId);
if (blockType == null) {
return false;
} else {
BlockGathering gathering = blockType.getGathering();
return gathering == null || !gathering.isSoft();
}
} else {
return true;
}
} else {
return false;
}
}
private static void collectPotentialTargets(@Nonnull Set<Ref<EntityStore>> targetRefs, @Nonnull List<Ref<EntityStore>> potentialTargetRefs, @Nonnull Vector3d startPosition, @Nonnull Vector3d endPosition, @Nonnull CommandBuffer<EntityStore> commandBuffer) {
World world = ((EntityStore)commandBuffer.getExternalData()).getWorld();
for(Ref<EntityStore> potentialTarget : potentialTargetRefs) {
if (processPotentialEntity(potentialTarget, startPosition, endPosition, commandBuffer) && targetRefs.add(potentialTarget)) {
}
}
}
private static boolean processPotentialEntity(@Nonnull Ref<EntityStore> ref, @Nonnull Vector3d startPosition, @Nonnull Vector3d endPosition, @Nonnull CommandBuffer<EntityStore> commandBuffer) {
BoundingBox boundingBoxComponent = (BoundingBox)commandBuffer.getComponent(ref, BoundingBox.getComponentType());
if (boundingBoxComponent == null) {
return false;
} else {
TransformComponent transformComponent = (TransformComponent)commandBuffer.getComponent(ref, TransformComponent.getComponentType());
if (transformComponent == null) {
return false;
} else {
Vector3d entityPosition = transformComponent.getPosition();
Box boundingBox = boundingBoxComponent.getBoundingBox().clone().offset(entityPosition);
return boundingBox.intersectsLine(startPosition, endPosition);
}
}
}
private static float calculateBlockDamageScale(float distance, float radius, float fallOff) {
if (distance >= radius) {
return 0.0F;
} else {
float normalizedDistance = distance / radius;
return 1.0F - (float)Math.pow((double)normalizedDistance, (double)fallOff);
}
}
private static void processTargetEntities(@Nonnull ExplosionConfig config, @Nonnull Vector3d position, @Nonnull Damage.Source damageSource, @Nullable Ref<EntityStore> ignoreRef, @Nonnull Set<Ref<EntityStore>> targetRefs, @Nonnull CommandBuffer<EntityStore> commandBuffer) {
for(Ref<EntityStore> targetRef : targetRefs) {
processTargetEntity(config, targetRef, position, damageSource, commandBuffer);
}
}
private static void processTargetEntity(@Nonnull ExplosionConfig config, @Nonnull Ref<EntityStore> targetRef, @Nonnull Vector3d position, @Nonnull Damage.Source damageSource, @Nonnull CommandBuffer<EntityStore> commandBuffer) {
float entityDamageRadius = config.entityDamageRadius;
float explosionDamage = config.entityDamage;
float explosionFalloff = config.entityDamageFalloff;
TransformComponent targetTransformComponent = (TransformComponent)commandBuffer.getComponent(targetRef, TransformComponent.getComponentType());
assert targetTransformComponent != null;
Velocity targetVelocityComponent = (Velocity)commandBuffer.getComponent(targetRef, Velocity.getComponentType());
assert targetVelocityComponent != null;
Vector3d targetPosition = targetTransformComponent.getPosition();
Vector3d diff = targetPosition.clone().subtract(position);
double distance = diff.length();
float damage = (float)((double)explosionDamage * Math.pow(1.0 - distance / (double)entityDamageRadius, (double)explosionFalloff));
if (damage > 0.0F) {
DamageSystems.executeDamage(targetRef, commandBuffer, new Damage(damageSource, DamageCause.ENVIRONMENT, damage));
}
Knockback knockbackConfig = config.knockback;
if (knockbackConfig != null) {
ComponentType<EntityStore, KnockbackComponent> knockbackComponentType = KnockbackComponent.getComponentType();
KnockbackComponent knockbackComponent = (KnockbackComponent)commandBuffer.getComponent(targetRef, knockbackComponentType);
if (knockbackComponent == null) {
knockbackComponent = new KnockbackComponent();
commandBuffer.putComponent(targetRef, knockbackComponentType, knockbackComponent);
}
Vector3d direction = diff.clone().normalize();
knockbackComponent.setVelocity(knockbackConfig.calculateVector(position, (float)direction.y, targetPosition));
knockbackComponent.setVelocityType(knockbackConfig.getVelocityType());
knockbackComponent.setVelocityConfig(knockbackConfig.getVelocityConfig());
knockbackComponent.setDuration(knockbackConfig.getDuration());
}
}
}