port crystal damage and placement helpers from telaviv
CrystalUtil computes explosion exposure/damage with armor, enchants and resistance reductions; BlockUtil covers placement sides, strict directions and sight traces; TargetUtil picks the nearest non-friend player in range.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
package cc.fros.util.world;
|
||||
|
||||
import cc.fros.util.traits.Util;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.entity.Pose;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Block placement / raytrace helpers, ported from telaviv's {@code BlockUtil}.
|
||||
*/
|
||||
public final class BlockUtil implements Util {
|
||||
private BlockUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static List<BlockPos> sphere(double range, BlockPos pos, boolean sphere, boolean hollow) {
|
||||
List<BlockPos> blocks = new ArrayList<>();
|
||||
int cx = pos.getX();
|
||||
int cy = pos.getY();
|
||||
int cz = pos.getZ();
|
||||
|
||||
for (int x = cx - (int) range; x <= cx + (int) range; x++) {
|
||||
for (int z = cz - (int) range; z <= cz + (int) range; z++) {
|
||||
for (int y = (sphere ? cy - (int) range : cy); y < (cy + range); y++) {
|
||||
double dist = (cx - x) * (cx - x) + (cz - z) * (cz - z) + (sphere ? (cy - y) * (cy - y) : 0);
|
||||
if (dist < range * range && !(hollow && dist < (range - 1) * (range - 1))) {
|
||||
blocks.add(new BlockPos(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public static Direction getPlaceableSideCrystal(BlockPos pos, boolean strict) {
|
||||
return getPlaceableSideCrystal(pos, strict, null);
|
||||
}
|
||||
|
||||
public static Direction getPlaceableSideCrystal(BlockPos pos, boolean strict, BlockPos ignore) {
|
||||
if (mc.player.blockPosition().above().equals(pos) && mc.player.getPose() != Pose.SWIMMING) {
|
||||
return Direction.UP;
|
||||
}
|
||||
|
||||
double bestDist = 99999;
|
||||
Direction bestSide = null;
|
||||
for (Direction side : Direction.values()) {
|
||||
BlockPos neighbour = pos.relative(side);
|
||||
BlockState blockState = mc.level.getBlockState(neighbour);
|
||||
|
||||
if (strict) {
|
||||
if (!canSeeFace(pos, side)) continue;
|
||||
if (!blockState.canBeReplaced() && !neighbour.equals(ignore)) continue;
|
||||
}
|
||||
|
||||
if (side == Direction.UP) return side;
|
||||
|
||||
double dist = distanceTo(pos, side);
|
||||
if (bestSide == null || dist < bestDist) {
|
||||
bestDist = dist;
|
||||
bestSide = side;
|
||||
}
|
||||
}
|
||||
return bestSide;
|
||||
}
|
||||
|
||||
public static boolean placeTrace(BlockPos pos) {
|
||||
for (Direction facing : Direction.values()) {
|
||||
Vec3 vec = new Vec3(
|
||||
pos.getX() + 0.5 + facing.getStepX() / 2.0,
|
||||
pos.getY() + 0.5 + facing.getStepY() / 2.0,
|
||||
pos.getZ() + 0.5 + facing.getStepZ() / 2.0);
|
||||
|
||||
BlockHitResult result = mc.level.clip(new ClipContext(
|
||||
mc.player.getEyePosition(), vec,
|
||||
ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, mc.player));
|
||||
|
||||
if (result.getType() != HitResult.Type.MISS && result.getBlockPos().equals(pos)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean canSeeFace(BlockPos target, Direction face) {
|
||||
Vec3 eyes = mc.player.getEyePosition();
|
||||
double cx = target.getX() + 0.5;
|
||||
double cy = target.getY() + 0.5;
|
||||
double cz = target.getZ() + 0.5;
|
||||
double px = cx + face.getStepX() * 0.5;
|
||||
double py = cy + face.getStepY() * 0.5;
|
||||
double pz = cz + face.getStepZ() * 0.5;
|
||||
|
||||
return switch (face) {
|
||||
case NORTH -> eyes.z < pz;
|
||||
case EAST -> eyes.x > px;
|
||||
case SOUTH -> eyes.z > pz;
|
||||
case WEST -> eyes.x < px;
|
||||
case UP -> eyes.y + 0.5 > py;
|
||||
case DOWN -> eyes.y < py;
|
||||
};
|
||||
}
|
||||
|
||||
public static double distanceTo(BlockPos pos, Direction direction) {
|
||||
return mc.player.getEyePosition().distanceTo(
|
||||
new Vec3(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5)
|
||||
.add(direction.getStepX(), direction.getStepY(), direction.getStepZ()));
|
||||
}
|
||||
|
||||
public static List<Direction> getStrictDirections(Vec3 eyePos, Vec3 blockPos) {
|
||||
List<Direction> directions = new ArrayList<>();
|
||||
|
||||
double differenceX = eyePos.x - blockPos.x;
|
||||
double differenceY = eyePos.y - blockPos.y;
|
||||
double differenceZ = eyePos.z - blockPos.z;
|
||||
|
||||
if (differenceY > 0.5) directions.add(Direction.UP);
|
||||
else if (differenceY < -0.5) directions.add(Direction.DOWN);
|
||||
else {
|
||||
directions.add(Direction.UP);
|
||||
directions.add(Direction.DOWN);
|
||||
}
|
||||
|
||||
if (differenceX > 0.5) directions.add(Direction.EAST);
|
||||
else if (differenceX < -0.5) directions.add(Direction.WEST);
|
||||
else {
|
||||
directions.add(Direction.EAST);
|
||||
directions.add(Direction.WEST);
|
||||
}
|
||||
|
||||
if (differenceZ > 0.5) directions.add(Direction.SOUTH);
|
||||
else if (differenceZ < -0.5) directions.add(Direction.NORTH);
|
||||
else {
|
||||
directions.add(Direction.SOUTH);
|
||||
directions.add(Direction.NORTH);
|
||||
}
|
||||
|
||||
return directions;
|
||||
}
|
||||
|
||||
public static Direction getDirection(BlockPos position, boolean strictDirection) {
|
||||
return getDirection(position, null, strictDirection);
|
||||
}
|
||||
|
||||
public static Direction getDirection(BlockPos position, List<BlockPos> exceptions, boolean strictDirection) {
|
||||
List<Direction> strictDirections = strictDirection
|
||||
? getStrictDirections(mc.player.getEyePosition(), Vec3.atCenterOf(position))
|
||||
: List.of();
|
||||
|
||||
for (Direction direction : Direction.values()) {
|
||||
BlockPos offset = position.relative(direction);
|
||||
if ((!strictDirection || strictDirections.contains(direction.getOpposite()))
|
||||
&& (!mc.level.getBlockState(offset).canBeReplaced()
|
||||
|| (exceptions != null && exceptions.contains(offset)))) {
|
||||
return direction;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package cc.fros.util.world;
|
||||
|
||||
import cc.fros.util.EnchantmentUtil;
|
||||
import cc.fros.util.traits.Util;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.damagesource.CombatRules;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.effect.MobEffectInstance;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Crystal damage / exposure calculation, ported from telaviv's {@code CrystalUtil}.
|
||||
*/
|
||||
public final class CrystalUtil implements Util {
|
||||
private CrystalUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static boolean canPlaceCrystal(BlockPos pos, boolean protocol) {
|
||||
BlockState state = mc.level.getBlockState(pos);
|
||||
|
||||
if (state.getBlock() != Blocks.OBSIDIAN && state.getBlock() != Blocks.BEDROCK) return false;
|
||||
|
||||
BlockState crystal = mc.level.getBlockState(pos.above());
|
||||
if (!crystal.isAir()) return false;
|
||||
|
||||
if (!protocol) return true;
|
||||
|
||||
return mc.level.getBlockState(pos.above(2)).isAir();
|
||||
}
|
||||
|
||||
public static boolean canPlaceCrystalServer(BlockPos block) {
|
||||
BlockState blockState = mc.level.getBlockState(block);
|
||||
if (blockState.getBlock() != Blocks.OBSIDIAN && blockState.getBlock() != Blocks.BEDROCK) return false;
|
||||
|
||||
BlockPos up = block.above();
|
||||
if (!mc.level.getBlockState(up).isAir()) return false;
|
||||
|
||||
AABB box = new AABB(up.getX(), up.getY(), up.getZ(), up.getX() + 1.0, up.getY() + 2.0, up.getZ() + 1.0);
|
||||
return mc.level.getEntities(null, box).isEmpty();
|
||||
}
|
||||
|
||||
public static boolean canPlaceCrystalAir(BlockPos block) {
|
||||
BlockPos up = block.above();
|
||||
if (!mc.level.getBlockState(up).isAir()) return false;
|
||||
|
||||
AABB box = new AABB(up.getX(), up.getY(), up.getZ(), up.getX() + 1.0, up.getY() + 2.0, up.getZ() + 1.0);
|
||||
return mc.level.getEntities(null, box).isEmpty();
|
||||
}
|
||||
|
||||
public static float calculateDamage(LivingEntity entity, Vec3 crystal, boolean ignoreTerrain) {
|
||||
return calculateDamage(entity, crystal, ignoreTerrain, null);
|
||||
}
|
||||
|
||||
public static float calculateDamage(LivingEntity entity, Vec3 crystal, boolean ignoreTerrain, BlockPos ignore) {
|
||||
double exposure = getExposure(crystal, entity.getBoundingBox(), ignoreTerrain, ignore);
|
||||
double distanceFactor = Math.sqrt(entity.distanceToSqr(crystal)) / 12.0;
|
||||
double factor = (1.0 - distanceFactor) * exposure;
|
||||
float damage = (float) ((int) ((factor * factor + factor) / 2.0 * 7.0 * 12.0 + 1.0));
|
||||
damage = calculateReductions(damage, entity, mc.level.damageSources().explosion(null, null));
|
||||
return (float) Math.max(0.0, damage);
|
||||
}
|
||||
|
||||
public static float calculateReductions(float damage, LivingEntity entity, DamageSource source) {
|
||||
if (source.scalesWithDifficulty()) {
|
||||
Difficulty difficulty = mc.level.getDifficulty();
|
||||
if (difficulty == Difficulty.EASY) damage = Math.min(damage / 2.0f + 1.0f, damage);
|
||||
else if (difficulty == Difficulty.HARD) damage *= 1.5f;
|
||||
}
|
||||
|
||||
damage = CombatRules.getDamageAfterAbsorb(
|
||||
entity, damage, source,
|
||||
entity.getArmorValue(),
|
||||
(float) entity.getAttributeValue(Attributes.ARMOR_TOUGHNESS)
|
||||
);
|
||||
|
||||
damage = resistanceReduction(entity, damage);
|
||||
|
||||
float protAmount = getProtectionAmount(entity);
|
||||
if (protAmount > 0) damage = CombatRules.getDamageAfterMagicAbsorb(damage, protAmount);
|
||||
|
||||
return Math.max(damage, 0.0f);
|
||||
}
|
||||
|
||||
public static int getProtectionAmount(LivingEntity entity) {
|
||||
int total = 0;
|
||||
for (EquipmentSlot slot : new EquipmentSlot[]{EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}) {
|
||||
total += getProtectionAmount(entity.getItemBySlot(slot));
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public static int getProtectionAmount(ItemStack stack) {
|
||||
if (stack.isEmpty()) return 0;
|
||||
int blast = EnchantmentUtil.getLevel(Enchantments.BLAST_PROTECTION, stack);
|
||||
int prot = EnchantmentUtil.getLevel(Enchantments.PROTECTION, stack);
|
||||
return blast * 2 + prot;
|
||||
}
|
||||
|
||||
private static float resistanceReduction(LivingEntity entity, float damage) {
|
||||
MobEffectInstance resistance = entity.getEffect(MobEffects.RESISTANCE);
|
||||
if (resistance != null) {
|
||||
int level = resistance.getAmplifier() + 1;
|
||||
damage *= (1.0f - level * 0.2f);
|
||||
}
|
||||
return Math.max(damage, 0.0f);
|
||||
}
|
||||
|
||||
private static float getExposure(Vec3 source, AABB box, boolean ignoreTerrain, BlockPos ignorePos) {
|
||||
RaycastFactory factory = getRaycastFactory(ignoreTerrain, ignorePos);
|
||||
|
||||
double xDiff = box.maxX - box.minX;
|
||||
double yDiff = box.maxY - box.minY;
|
||||
double zDiff = box.maxZ - box.minZ;
|
||||
|
||||
double xStep = 1.0 / (xDiff * 2.0 + 1.0);
|
||||
double yStep = 1.0 / (yDiff * 2.0 + 1.0);
|
||||
double zStep = 1.0 / (zDiff * 2.0 + 1.0);
|
||||
|
||||
if (xStep > 0 && yStep > 0 && zStep > 0) {
|
||||
int misses = 0;
|
||||
int hits = 0;
|
||||
|
||||
double xOffset = (1.0 - Math.floor(1.0 / xStep) * xStep) * 0.5;
|
||||
double zOffset = (1.0 - Math.floor(1.0 / zStep) * zStep) * 0.5;
|
||||
|
||||
xStep *= xDiff;
|
||||
yStep *= yDiff;
|
||||
zStep *= zDiff;
|
||||
|
||||
double startX = box.minX + xOffset;
|
||||
double startY = box.minY;
|
||||
double startZ = box.minZ + zOffset;
|
||||
double endX = box.maxX + xOffset;
|
||||
double endY = box.maxY;
|
||||
double endZ = box.maxZ + zOffset;
|
||||
|
||||
for (double x = startX; x <= endX; x += xStep) {
|
||||
for (double y = startY; y <= endY; y += yStep) {
|
||||
for (double z = startZ; z <= endZ; z += zStep) {
|
||||
Vec3 position = new Vec3(x, y, z);
|
||||
if (raycast(new ExposureRaycastContext(position, source), factory) == null) misses++;
|
||||
hits++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (float) misses / hits;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private static RaycastFactory getRaycastFactory(boolean ignoreTerrain, BlockPos ignorePos) {
|
||||
return (context, blockPos) -> {
|
||||
BlockState blockState = mc.level.getBlockState(blockPos);
|
||||
|
||||
if (ignorePos != null && blockPos.equals(ignorePos)) return null;
|
||||
|
||||
if (ignoreTerrain && blockState.getBlock().getExplosionResistance() < 600.0f) return null;
|
||||
|
||||
VoxelShape shape = blockState.getCollisionShape(mc.level, blockPos);
|
||||
return shape.clip(context.start(), context.end(), blockPos);
|
||||
};
|
||||
}
|
||||
|
||||
private static BlockHitResult raycast(ExposureRaycastContext context, RaycastFactory factory) {
|
||||
return BlockGetter.traverseBlocks(context.start(), context.end(), context, factory, ctx -> null);
|
||||
}
|
||||
|
||||
public static boolean isEndCrystal(Entity entity) {
|
||||
return entity instanceof EndCrystal;
|
||||
}
|
||||
|
||||
public record ExposureRaycastContext(Vec3 start, Vec3 end) {
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RaycastFactory extends BiFunction<ExposureRaycastContext, BlockPos, BlockHitResult> {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package cc.fros.util.world;
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.util.traits.Util;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Simple target selection helpers, ported from telaviv's {@code TargetUtil}.
|
||||
*/
|
||||
public final class TargetUtil implements Util {
|
||||
private TargetUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static LivingEntity getTarget(double range) {
|
||||
LivingEntity best = null;
|
||||
double bestDistance = Double.MAX_VALUE;
|
||||
for (LivingEntity entity : getTargets(range)) {
|
||||
double distance = mc.player.position().distanceTo(entity.position());
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
best = entity;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
public static List<LivingEntity> getTargets(double range) {
|
||||
List<LivingEntity> targets = new ArrayList<>();
|
||||
for (Player entity : mc.level.players()) {
|
||||
if (!isValidTarget(entity)) continue;
|
||||
if (mc.player.position().distanceTo(entity.position()) > range) continue;
|
||||
targets.add(entity);
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
public static boolean isValidTarget(Player entity) {
|
||||
if (entity == mc.player) return false;
|
||||
if (!entity.isAlive() || entity.isRemoved()) return false;
|
||||
if (entity.isSpectator()) return false;
|
||||
return !Fros.friendManager.isFriend(entity);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user