diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index 141c18002..308aa834e 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -20,7 +20,7 @@ import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import ru.dbotthepony.mc.otm.block.entity.BlockEntityBlackHole; +import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleExplosionQueue; import ru.dbotthepony.mc.otm.capability.MatteryCapability; import ru.dbotthepony.mc.otm.capability.android.AndroidCapability; import ru.dbotthepony.mc.otm.capability.android.AndroidCapabilityPlayer; @@ -131,7 +131,7 @@ public class OverdriveThatMatters { MinecraftForge.EVENT_BUS.register(AndroidCapabilityPlayer.Companion); MinecraftForge.EVENT_BUS.register(AndroidCapability.Companion); MinecraftForge.EVENT_BUS.register(MatterRegistry.class); - MinecraftForge.EVENT_BUS.register(BlockEntityBlackHole.BlackHoleExplosionQueue.class); + MinecraftForge.EVENT_BUS.register(BlackHoleExplosionQueue.Companion); FMLJavaModLoadingContext.get().getModEventBus().register(MatteryCapability.class); diff --git a/src/main/java/ru/dbotthepony/mc/otm/Registry.java b/src/main/java/ru/dbotthepony/mc/otm/Registry.java index 1a5160cec..8dfd84e12 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/Registry.java +++ b/src/main/java/ru/dbotthepony/mc/otm/Registry.java @@ -35,6 +35,7 @@ import ru.dbotthepony.mc.otm.android.feature.AndroidNanobotsRegeneration; import ru.dbotthepony.mc.otm.block.*; import ru.dbotthepony.mc.otm.block.entity.*; import ru.dbotthepony.mc.otm.android.AndroidFeatureType; +import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntityBlackHole; import ru.dbotthepony.mc.otm.client.render.BlackHoleRenderer; import ru.dbotthepony.mc.otm.client.render.SkinElement; import ru.dbotthepony.mc.otm.core.Fraction; diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityBlackHole.java b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityBlackHole.java deleted file mode 100644 index 0360901b0..000000000 --- a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityBlackHole.java +++ /dev/null @@ -1,474 +0,0 @@ -package ru.dbotthepony.mc.otm.block.entity; - -import net.minecraft.MethodsReturnNonnullByDefault; -import net.minecraft.client.Minecraft; -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.StringTag; -import net.minecraft.nbt.Tag; -import net.minecraft.network.Connection; -import net.minecraft.network.protocol.PacketFlow; -import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Explosion; -import net.minecraft.world.level.ExplosionDamageCalculator; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.levelgen.structure.BoundingBox; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.level.saveddata.SavedData; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import ru.dbotthepony.mc.otm.OverdriveThatMatters; -import ru.dbotthepony.mc.otm.Registry; -import ru.dbotthepony.mc.otm.capability.MatteryCapability; -import ru.dbotthepony.mc.otm.core.Fraction; -import ru.dbotthepony.mc.otm.matter.MatterRegistry; - -import javax.annotation.Nullable; -import javax.annotation.ParametersAreNonnullByDefault; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Optional; - -@MethodsReturnNonnullByDefault -@ParametersAreNonnullByDefault -public class BlockEntityBlackHole extends BlockEntity { - public BlockEntityBlackHole(BlockPos p_155229_, BlockState p_155230_) { - super(Registry.BlockEntities.BLACK_HOLE, p_155229_, p_155230_); - setMass(mass); - } - - public static final Fraction NORMAL_MASS = new Fraction(1_000); - private Fraction mass = NORMAL_MASS; - private double gravitation_strength = 1; - private boolean suppress_updates = true; - public boolean spin_direction = false; - - public double getGravitationStrength() { - return gravitation_strength; - } - - private static class BlackHoleExplosionDamageCalculator extends ExplosionDamageCalculator { - @Override - public Optional getBlockExplosionResistance(Explosion explosion, BlockGetter getter, BlockPos pos, BlockState state, FluidState fstate) { - return state.isAir() && fstate.isEmpty() ? - Optional.empty() : - Optional.of( - (float) Math.sqrt( - Math.max(0, - Math.max( - state.getExplosionResistance(getter, pos, explosion), - fstate.getExplosionResistance(getter, pos, explosion) - ) - ) - ) - ); - } - - private static final BlackHoleExplosionDamageCalculator INSTANCE = new BlackHoleExplosionDamageCalculator(); - } - - public static class BlackHoleExplosionQueue extends SavedData { - private record QueuedExplosion(double x, double y, double z, float radius) { - public CompoundTag serializeNBT() { - var tag = new CompoundTag(); - - tag.putDouble("x", x); - tag.putDouble("y", y); - tag.putDouble("z", z); - tag.putFloat("radius", radius); - - return tag; - } - - public static QueuedExplosion deserializeNBT(CompoundTag tag) { - return new QueuedExplosion(tag.getDouble("x"), tag.getDouble("y"), tag.getDouble("z"), tag.getFloat("radius")); - } - - public void explode(Level level) { - level.explode( - null, - Registry.DAMAGE_HAWKING_RADIATION, - BlackHoleExplosionDamageCalculator.INSTANCE, - x, - y, - z, - radius, - false, - Explosion.BlockInteraction.DESTROY); - } - } - - private record RingExplosion(double x, double y, double z, double radius, float strength) { - public CompoundTag serializeNBT() { - var tag = new CompoundTag(); - - tag.putDouble("x", x); - tag.putDouble("y", y); - tag.putDouble("z", z); - tag.putDouble("radius", radius); - - return tag; - } - - public static RingExplosion deserializeNBT(CompoundTag tag) { - return new RingExplosion(tag.getDouble("x"), tag.getDouble("y"), tag.getDouble("z"), tag.getDouble("radius"), tag.getFloat("strength")); - } - - public void explode(BlackHoleExplosionQueue queue) { - final int fragments = (int) radius * 8; - - final double stack_step = Math.PI / (double) fragments; - final double sector_step = Math.PI / (double) fragments * 2; - - for (int stack = 0; stack < fragments; stack++) { - final double stack_angle = Math.PI / 2 - stack * stack_step; - final double xy = radius * 15 * Math.cos(stack_angle); - final double z = radius * 15 * Math.sin(stack_angle); - - for (int sector = 0; sector < fragments; sector++) { - final double sector_angle = sector * sector_step; - - queue.explode( - this.x + xy * Math.cos(sector_angle), - this.y + xy * Math.sin(sector_angle), - this.z + z, - strength - ); - } - } - } - } - - private int index = 0; - private int index_ring = 0; - private final ArrayList explosions = new ArrayList<>(); - private final ArrayList rings = new ArrayList<>(); - private final ServerLevel level; - - public BlackHoleExplosionQueue(ServerLevel level) { - this.level = level; - } - - @Override - public CompoundTag save(CompoundTag tag) { - var list = new ListTag(); - var list_rings = new ListTag(); - - for (int i = index; i < explosions.size(); i++) - list.add(explosions.get(i).serializeNBT()); - - for (int i = index_ring; i < rings.size(); i++) - list_rings.add(rings.get(i).serializeNBT()); - - tag.put("explosions", list); - tag.put("rings", list_rings); - - return tag; - } - - public void load(CompoundTag tag) { - explosions.clear(); - rings.clear(); - index = 0; - index_ring = 0; - - for (var explosion : tag.getList("explosions", Tag.TAG_COMPOUND)) - explosions.add(QueuedExplosion.deserializeNBT((CompoundTag) explosion)); - - for (var ring : tag.getList("rings", Tag.TAG_COMPOUND)) - rings.add(RingExplosion.deserializeNBT((CompoundTag) ring)); - } - - public static BlackHoleExplosionQueue factory(ServerLevel level) { - return level.getDataStorage().computeIfAbsent( - (tag) -> { - var factory = new BlackHoleExplosionQueue(level); - factory.load(tag); - return factory; - }, - - () -> new BlackHoleExplosionQueue(level), - - "otm_blackhole_explosion_queue" - ); - } - - public void explode(double x, double y, double z, float radius) { - explosions.add(new QueuedExplosion(x, y, z, radius)); - setDirty(); - } - - public void explodeRing(double x, double y, double z, double radius, float strength) { - rings.add(new RingExplosion(x, y, z, radius, strength)); - setDirty(); - } - - public void tick() { - if (explosions.size() != 0) { - setDirty(); - - int iterations = 0; - - for (int i = index; i < explosions.size(); i++) { - explosions.get(i).explode(level); - index++; - - if (iterations++ == 4) { - break; - } - } - - if (index >= explosions.size()) { - index = 0; - explosions.clear(); - } - } else if (rings.size() != 0) { - if (index_ring >= rings.size()) { - index_ring = 0; - rings.clear(); - } else { - rings.get(index_ring++).explode(this); - } - } - } - - @SubscribeEvent - public static void onWorldTick(TickEvent.WorldTickEvent event) { - if (event.phase == TickEvent.Phase.START && event.world instanceof ServerLevel level) { - factory(level).tick(); - } - } - } - - public void collapse() { - level.setBlock(getBlockPos(), Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL); - - if (gravitation_strength > 0.25) { - final double x0 = getBlockPos().getX() + 0.5; - final double y0 = getBlockPos().getY() + 0.5; - final double z0 = getBlockPos().getZ() + 0.5; - - var queue = BlackHoleExplosionQueue.factory((ServerLevel) level); - - for (int radius = 0; radius < Math.ceil(gravitation_strength * 4); radius++) { - queue.explodeRing( - x0, - y0, - z0, - radius, - (float) Math.min(20, Math.max(1, (gravitation_strength * 4 - radius) * 20))); - } - } else { - level.explode( - null, - Registry.DAMAGE_HAWKING_RADIATION, - null, - getBlockPos().getX() + 0.5D, - getBlockPos().getY() + 0.5D, - getBlockPos().getZ() + 0.5D, - (float) gravitation_strength * 60, - false, - Explosion.BlockInteraction.DESTROY); - } - } - - public void addMass(Fraction mass) { - setMass(this.mass.plus(mass)); - } - - public void setMass(Fraction mass) { - if (mass.compareTo(Fraction.ZERO) <= 0) { - collapse(); - return; - } - - this.mass = mass; - setChanged(); - gravitation_strength = mass.div(NORMAL_MASS).toDouble(); - - if (gravitation_strength > 1) { - gravitation_strength = Math.min(20, Math.sqrt(gravitation_strength)); - } else { - gravitation_strength = Math.max(0.08, Math.pow(gravitation_strength, 2)); - } - - if (level != null && !level.isClientSide && !suppress_updates) - level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), Block.UPDATE_CLIENTS); - - affected_bounds = new BoundingBox( - (int) (-30 * gravitation_strength), - (int) (-30 * gravitation_strength), - (int) (-30 * gravitation_strength), - (int) (30 * gravitation_strength), - (int) (30 * gravitation_strength), - (int) (30 * gravitation_strength) - ).move(getBlockPos()); - - affected_bounds_aabb = AABB.of(affected_bounds); - } - - // shared functions - public CompoundTag writeBlackHoleData(CompoundTag tag) { - tag.putString("mass", mass.toString()); - tag.putBoolean("spin_direction", spin_direction); - return tag; - } - - public void readBlackHoleData(CompoundTag tag) { - if (tag.contains("mass")) - setMass(Fraction.deserializeNBT(tag.get("mass"))); - - spin_direction = tag.getBoolean("spin_direction"); - } - - // disk io - @Override - public void saveAdditional(CompoundTag nbt) { - super.saveAdditional(nbt); - writeBlackHoleData(nbt); - } - - @Override - public void load(CompoundTag tag) { - super.load(tag); - readBlackHoleData(tag); - } - - // received either by game engine (from getUpdateTag on ClientLevelPacket) - // or by onDataPacket - @Override - public void handleUpdateTag(CompoundTag tag) { - super.handleUpdateTag(tag); - readBlackHoleData(tag); - } - - // called by game engine for ClientLevelPacket - @Override - public CompoundTag getUpdateTag() { - return writeBlackHoleData(super.getUpdateTag()); - } - - // called by game engine on block updates - @Nullable - @Override - public ClientboundBlockEntityDataPacket getUpdatePacket() { - return ClientboundBlockEntityDataPacket.create(this); - } - - // called by game engine by forge patches when ClientboundBlockEntityDataPacket is received - @Override - public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt) { - super.onDataPacket(net, pkt); - - if (net.getReceiving() == PacketFlow.CLIENTBOUND) { - handleUpdateTag(pkt.getTag()); - } - } - - private BoundingBox affected_bounds; - private AABB affected_bounds_aabb; - - public BoundingBox affectedBounds() { - return affected_bounds; - } - - public AABB affectedBoundsAABB() { - return affected_bounds_aabb; - } - - private void setDeltaMovement(Entity living, Vec3 center, double distance, boolean weaker) { - //final double mult = Math.min(2, (30 * this.gravitation_strength) / Math.max(1, Math.pow(distance, 2))); - // Сила притяжения - final double mult = Math.pow(1 - distance / (30 * this.gravitation_strength), 2) * this.gravitation_strength / 8; - - // Притяжение к ядру - final var delta = living.position().vectorTo(center).normalize(); - - if (distance < this.gravitation_strength) { - living.setDeltaMovement(living.getDeltaMovement().add(delta.multiply(mult, mult, mult))); - } else { - // Закручивание - final var rotate = spin_direction ? delta.yRot((float) (Math.PI / 2)) : delta.yRot((float) (-Math.PI / 2)); - - if (weaker) - living.setDeltaMovement(living.getDeltaMovement().add(rotate.multiply(mult * 0.4f, mult * 0.4f, mult * 0.4f)).add(delta.multiply(mult * 0.33f, mult * 0.33f, mult * 0.33f))); - else - living.setDeltaMovement(living.getDeltaMovement().add(rotate.multiply(mult * 0.4f, mult * 0.4f, mult * 0.4f)).add(delta.multiply(mult, mult, mult))); - } - } - - public static void clientTicker(Level level, BlockPos blockPos, BlockState blockState, T t) { - if (t instanceof BlockEntityBlackHole tile) { - var ply = Minecraft.getInstance().player; - final var center = Vec3.atCenterOf(tile.getBlockPos()); - - if (!ply.getAbilities().mayfly) { - final double distance = ply.position().distanceTo(center); - tile.setDeltaMovement(ply, center, distance, true); - } - - for (var item : tile.level.getEntitiesOfClass(ItemEntity.class, tile.affected_bounds_aabb)) { - final double distance = item.position().distanceTo(center); - tile.setDeltaMovement(item, center, distance, false); - } - } - } - - public static final Fraction HAWKING_MASS_LOSE_STEP = new Fraction("-0.1"); - - public static void ticker(Level level, BlockPos blockPos, BlockState blockState, T t) { - if (t instanceof BlockEntityBlackHole tile) { - tile.suppress_updates = false; - - final var center = Vec3.atCenterOf(tile.getBlockPos()); - - for (var living : level.getEntitiesOfClass(LivingEntity.class, tile.affected_bounds_aabb)) { - final double distance = living.position().distanceTo(center); - - if (!(living instanceof Player ply) || !ply.getAbilities().mayfly) { - tile.setDeltaMovement(living, center, distance, false); - } - - if (distance < tile.gravitation_strength + 1) { - living.hurt(Registry.DAMAGE_EVENT_HORIZON, (float) (tile.gravitation_strength / distance)); - } - } - - for (var item : level.getEntitiesOfClass(ItemEntity.class, tile.affected_bounds_aabb)) { - final double distance = item.position().distanceTo(center); - tile.setDeltaMovement(item, center, distance, false); - - if (distance < tile.gravitation_strength + 1) { - if (item.hurt(Registry.DAMAGE_EVENT_HORIZON, (float) (tile.gravitation_strength / distance)) && item.isRemoved()) { - if (item.getItem().getItem() == Registry.Items.GRAVITATIONAL_DISRUPTOR) { - tile.collapse(); - } else { - var mass = MatterRegistry.getMatterValue(item.getItem()); - - if (mass.compareTo(Fraction.ZERO) > 0) - tile.addMass(mass); - } - } - } - } - - // шанс 1% что черная дыра потеряет 0.1 MtU каждую секунду * силу гравитации дыры ^ -1 - if (level.random.nextDouble() < 0.01 * 0.05 * (1 / tile.gravitation_strength)) { - tile.addMass(HAWKING_MASS_LOSE_STEP); - } - } - } -} diff --git a/src/main/java/ru/dbotthepony/mc/otm/client/render/BlackHoleRenderer.java b/src/main/java/ru/dbotthepony/mc/otm/client/render/BlackHoleRenderer.java index 31345b1f4..a7cfc993f 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/client/render/BlackHoleRenderer.java +++ b/src/main/java/ru/dbotthepony/mc/otm/client/render/BlackHoleRenderer.java @@ -7,7 +7,7 @@ import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; import net.minecraft.world.phys.Vec3; -import ru.dbotthepony.mc.otm.block.entity.BlockEntityBlackHole; +import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntityBlackHole; import static org.lwjgl.opengl.GL33.*; diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/Ext.kt index e01811bec..f345ae6e3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/Ext.kt @@ -27,6 +27,8 @@ operator fun CompoundTag.set(s: String, value: Int) = putInt(s, value) operator fun CompoundTag.set(s: String, value: Byte) = putByte(s, value) operator fun CompoundTag.set(s: String, value: Short) = putShort(s, value) operator fun CompoundTag.set(s: String, value: Long) = putLong(s, value) +operator fun CompoundTag.set(s: String, value: Float) = putFloat(s, value) +operator fun CompoundTag.set(s: String, value: Double) = putDouble(s, value) operator fun CompoundTag.set(s: String, value: String) = putString(s, value) operator fun CompoundTag.set(s: String, value: Boolean) = putBoolean(s, value) operator fun CompoundTag.set(s: String, value: ByteArray) = putByteArray(s, value) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/BlockBlackHole.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/BlockBlackHole.kt index 71171603d..4534d12ed 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/BlockBlackHole.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/BlockBlackHole.kt @@ -14,7 +14,7 @@ import net.minecraft.world.level.material.MaterialColor import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.Registry -import ru.dbotthepony.mc.otm.block.entity.BlockEntityBlackHole +import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntityBlackHole import ru.dbotthepony.mc.otm.shapes.BlockShapes class BlockBlackHole : @@ -29,7 +29,10 @@ class BlockBlackHole : } override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { - return BlockEntityBlackHole(blockPos, blockState) + return BlockEntityBlackHole( + blockPos, + blockState + ) } override fun getTicker( @@ -39,21 +42,11 @@ class BlockBlackHole : ): BlockEntityTicker? { if (p_153214_ !== Registry.BlockEntities.BLACK_HOLE) return null - return if (p_153212_.isClientSide) BlockEntityTicker { level: Level, blockPos: BlockPos, blockState: BlockState, t: T -> - BlockEntityBlackHole.clientTicker( - level, - blockPos, - blockState, - t - ) - } else BlockEntityTicker { level: Level, blockPos: BlockPos, blockState: BlockState, t: T -> - BlockEntityBlackHole.ticker( - level, - blockPos, - blockState, - t - ) + if (p_153212_.isClientSide) { + return BlockEntityTicker { _, _, _, t -> if (t is BlockEntityBlackHole) t.clientTick() } } + + return BlockEntityTicker { _, _, _, t -> if (t is BlockEntityBlackHole) t.tick() } } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlockEntityBlackHole.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlockEntityBlackHole.kt new file mode 100644 index 000000000..618ed0f92 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlockEntityBlackHole.kt @@ -0,0 +1,469 @@ +package ru.dbotthepony.mc.otm.block.entity.blackhole + +import net.minecraft.client.Minecraft +import net.minecraft.core.BlockPos +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag +import net.minecraft.nbt.Tag +import net.minecraft.network.Connection +import net.minecraft.network.protocol.PacketFlow +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.item.ItemEntity +import net.minecraft.world.entity.player.Player +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.Explosion +import net.minecraft.world.level.ExplosionDamageCalculator +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.Blocks +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.levelgen.structure.BoundingBox +import net.minecraft.world.level.material.FluidState +import net.minecraft.world.level.saveddata.SavedData +import net.minecraft.world.phys.AABB +import net.minecraft.world.phys.Vec3 +import net.minecraftforge.event.TickEvent +import net.minecraftforge.eventbus.api.SubscribeEvent +import ru.dbotthepony.mc.otm.Registry +import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleExplosionQueue.Companion.factory +import ru.dbotthepony.mc.otm.core.Fraction +import ru.dbotthepony.mc.otm.matter.MatterRegistry +import ru.dbotthepony.mc.otm.set +import java.util.* +import kotlin.collections.ArrayList +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.sin +import kotlin.math.sqrt + +private object BlackHoleExplosionDamageCalculator : ExplosionDamageCalculator() { + override fun getBlockExplosionResistance( + explosion: Explosion, + getter: BlockGetter, + pos: BlockPos, + state: BlockState, + fstate: FluidState + ): Optional { + return if (state.isAir && fstate.isEmpty) Optional.empty() else Optional.of( + Math.sqrt( + Math.max( + 0f, + Math.max( + state.getExplosionResistance(getter, pos, explosion), + fstate.getExplosionResistance(getter, pos, explosion) + ) + ).toDouble() + ).toFloat() + ) + } +} + +private data class RingExplosion(val x: Double, val y: Double, val z: Double, val radius: Double, val strength: Float) { + fun serializeNBT(): CompoundTag { + return CompoundTag().also { + it["x"] = x + it["y"] = y + it["z"] = z + it["radius"] = radius + it["strength"] = strength + } + } + + fun explode(queue: BlackHoleExplosionQueue) { + val fragments = radius.toInt() * 8 + val stackStep = Math.PI / fragments.toDouble() + val sectorStep = Math.PI / fragments.toDouble() * 2 + + for (stack in 0 until fragments) { + val stackAngle = Math.PI / 2 - stack * stackStep + val xy = radius * 15 * cos(stackAngle) + val z = radius * 15 * sin(stackAngle) + + for (sector in 0 until fragments) { + val sectorAngle = sector * sectorStep + + queue.explode( + this.x + xy * cos(sectorAngle), + this.y + xy * sin(sectorAngle), + this.z + z, + strength + ) + } + } + } + + companion object { + @JvmStatic + fun deserializeNBT(tag: CompoundTag): RingExplosion { + return RingExplosion( + tag.getDouble("x"), + tag.getDouble("y"), + tag.getDouble("z"), + tag.getDouble("radius"), + tag.getFloat("strength") + ) + } + } +} + +private data class QueuedExplosion(val x: Double, val y: Double, val z: Double, val radius: Float) { + fun serializeNBT(): CompoundTag { + return CompoundTag().also { + it["x"] = x + it["y"] = y + it["z"] = z + it["radius"] = radius + } + } + + fun explode(level: Level) { + level.explode( + null, + Registry.DAMAGE_HAWKING_RADIATION, + BlackHoleExplosionDamageCalculator, + x, + y, + z, + radius, + false, + Explosion.BlockInteraction.DESTROY + ) + } + + companion object { + fun deserializeNBT(tag: CompoundTag): QueuedExplosion { + return QueuedExplosion(tag.getDouble("x"), tag.getDouble("y"), tag.getDouble("z"), tag.getFloat("radius")) + } + } +} + +class BlackHoleExplosionQueue(private val level: ServerLevel) : SavedData() { + private var indexExplosion = 0 + private var indexRing = 0 + private val explosions = ArrayList() + private val rings = ArrayList() + + override fun save(tag: CompoundTag): CompoundTag { + val listExplosions = ListTag() + val listRings = ListTag() + + for (i in indexExplosion until explosions.size) + listExplosions.add(explosions[i].serializeNBT()) + + for (i in indexRing until rings.size) + listRings.add(rings[i].serializeNBT()) + + tag["explosions"] = listExplosions + tag["rings"] = listRings + + return tag + } + + fun load(tag: CompoundTag) { + explosions.clear() + rings.clear() + + indexExplosion = 0 + indexRing = 0 + + for (explosion in tag.getList("explosions", Tag.TAG_COMPOUND.toInt())) + explosions.add(QueuedExplosion.deserializeNBT(explosion as CompoundTag)) + + for (ring in tag.getList("rings", Tag.TAG_COMPOUND.toInt())) + rings.add(RingExplosion.deserializeNBT(ring as CompoundTag)) + } + + fun explode(x: Double, y: Double, z: Double, radius: Float) { + explosions.add(QueuedExplosion(x, y, z, radius)) + isDirty = true + } + + fun explodeRing(x: Double, y: Double, z: Double, radius: Double, strength: Float) { + rings.add(RingExplosion(x, y, z, radius, strength)) + isDirty = true + } + + fun tick() { + if (explosions.size != 0) { + setDirty() + var iterations = 0 + + for (i in indexExplosion until explosions.size) { + explosions[i].explode(level) + indexExplosion++ + + if (iterations++ == 4) { + break + } + } + + if (indexExplosion >= explosions.size) { + indexExplosion = 0 + explosions.clear() + } + } else if (rings.size != 0) { + if (indexRing >= rings.size) { + indexRing = 0 + rings.clear() + } else { + rings[indexRing++].explode(this) + } + } + } + + companion object { + @JvmStatic + fun factory(level: ServerLevel): BlackHoleExplosionQueue { + return level.dataStorage.computeIfAbsent( + { + val factory = BlackHoleExplosionQueue(level) + factory.load(it) + factory + }, + { BlackHoleExplosionQueue(level) }, + "otm_blackhole_explosion_queue" + ) + } + + @SubscribeEvent + fun onWorldTick(event: TickEvent.WorldTickEvent) { + if (event.phase == TickEvent.Phase.START && event.world is ServerLevel) { + factory(event.world as ServerLevel).tick() + } + } + } +} + +class BlockEntityBlackHole(p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(Registry.BlockEntities.BLACK_HOLE, p_155229_, p_155230_) { + var mass = BASELINE_MASS + set(mass) { + if (mass <= Fraction.ZERO) { + collapse() + return + } + + field = mass + + setChanged() + gravitationStrength = mass.div(BASELINE_MASS).toDouble() + + if (gravitationStrength > 1) { + gravitationStrength = 20.0.coerceAtMost(sqrt(gravitationStrength)) + } else { + gravitationStrength = 0.08.coerceAtLeast(gravitationStrength.pow(2.0)) + } + + val level = level + + if (level != null && !level.isClientSide && !suppress_updates) + level.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_CLIENTS) + + affectedBounds = BoundingBox( + (-30 * gravitationStrength).toInt(), + (-30 * gravitationStrength).toInt(), + (-30 * gravitationStrength).toInt(), + (30 * gravitationStrength).toInt(), + (30 * gravitationStrength).toInt(), + (30 * gravitationStrength).toInt() + ).move( + blockPos + ) + + affectedBoundsAABB = AABB.of(affectedBounds) + } + + var gravitationStrength = 1.0 + private set + + private var suppress_updates = true + var spin_direction = false + + fun collapse() { + val level = level as? ServerLevel ?: return + + level.setBlock(blockPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL) + + if (gravitationStrength > 0.25) { + val x0 = blockPos.x + 0.5 + val y0 = blockPos.y + 0.5 + val z0 = blockPos.z + 0.5 + val queue = factory(level) + var radius = 0 + + while (radius < Math.ceil(gravitationStrength * 4)) { + queue.explodeRing( + x0, + y0, + z0, + radius.toDouble(), Math.min(20.0, Math.max(1.0, (gravitationStrength * 4 - radius) * 20)).toFloat() + ) + + radius++ + } + } else { + level.explode( + null, + Registry.DAMAGE_HAWKING_RADIATION, + null, + blockPos.x + 0.5, + blockPos.y + 0.5, + blockPos.z + 0.5, + gravitationStrength.toFloat() * 60, + false, + Explosion.BlockInteraction.DESTROY + ) + } + } + + // shared functions + fun writeBlackHoleData(tag: CompoundTag): CompoundTag { + tag["mass"] = mass.serializeNBT() + tag["spin_direction"] = spin_direction + return tag + } + + fun readBlackHoleData(tag: CompoundTag) { + tag["mass"]?.let { + mass = Fraction.deserializeNBT(it) + } + + spin_direction = tag.getBoolean("spin_direction") + } + + // disk io + public override fun saveAdditional(nbt: CompoundTag) { + super.saveAdditional(nbt) + writeBlackHoleData(nbt) + } + + override fun load(tag: CompoundTag) { + super.load(tag) + readBlackHoleData(tag) + } + + // received either by game engine (from getUpdateTag on ClientLevelPacket) + // or by onDataPacket + override fun handleUpdateTag(tag: CompoundTag) { + super.handleUpdateTag(tag) + readBlackHoleData(tag) + } + + // called by game engine for ClientLevelPacket + override fun getUpdateTag(): CompoundTag { + return writeBlackHoleData(super.getUpdateTag()) + } + + // called by game engine on block updates + override fun getUpdatePacket(): ClientboundBlockEntityDataPacket? { + return ClientboundBlockEntityDataPacket.create(this) + } + + // called by game engine by forge patches when ClientboundBlockEntityDataPacket is received + override fun onDataPacket(net: Connection, pkt: ClientboundBlockEntityDataPacket) { + super.onDataPacket(net, pkt) + + if (net.receiving == PacketFlow.CLIENTBOUND) { + handleUpdateTag(pkt.tag!!) + } + } + + var affectedBounds = BoundingBox(0, 0, 0, 1, 1, 1) + private set + var affectedBoundsAABB = AABB.of(affectedBounds) + private set + + private fun setDeltaMovement(living: Entity, center: Vec3, distance: Double, weaker: Boolean) { + //final double mult = Math.min(2, (30 * this.gravitation_strength) / Math.max(1, Math.pow(distance, 2))); + // Сила притяжения + val mult = Math.pow(1 - distance / (30 * gravitationStrength), 2.0) * gravitationStrength / 8 + + // Притяжение к ядру + val delta = living.position().vectorTo(center).normalize() + + if (distance < gravitationStrength) { + living.deltaMovement = living.deltaMovement.add(delta.multiply(mult, mult, mult)) + } else { + // Закручивание + val rotate = if (spin_direction) delta.yRot((Math.PI / 2).toFloat()) else delta.yRot((-Math.PI / 2).toFloat()) + + if (weaker) + living.deltaMovement = living.deltaMovement + .add(rotate.multiply(mult * 0.4f, mult * 0.4f, mult * 0.4f)) + .add(delta.multiply(mult * 0.33f, mult * 0.33f, mult * 0.33f)) + else + living.deltaMovement = living.deltaMovement + .add(rotate.multiply(mult * 0.4f, mult * 0.4f, mult * 0.4f)) + .add(delta.multiply(mult, mult, mult)) + } + } + + fun clientTick() { + val ply = Minecraft.getInstance().player!! + val center = Vec3.atCenterOf(blockPos) + + if (!ply.abilities.mayfly) { + val distance = ply.position().distanceTo(center) + setDeltaMovement(ply, center, distance, true) + } + + for (item in level!!.getEntitiesOfClass(ItemEntity::class.java, affectedBoundsAABB)) { + val distance = item.position().distanceTo(center) + setDeltaMovement(item, center, distance, false) + } + } + + fun tick() { + val level = level as ServerLevel + suppress_updates = false + + val center = Vec3.atCenterOf(blockPos) + + for (living in level.getEntitiesOfClass(LivingEntity::class.java, affectedBoundsAABB)) { + val distance = living.position().distanceTo(center) + + if (living !is Player || !living.abilities.mayfly) { + setDeltaMovement(living, center, distance, false) + } + + if (distance < gravitationStrength + 1) { + living.hurt(Registry.DAMAGE_EVENT_HORIZON, (gravitationStrength / distance).toFloat()) + } + } + + for (item in level.getEntitiesOfClass(ItemEntity::class.java, affectedBoundsAABB)) { + val distance = item.position().distanceTo(center) + setDeltaMovement(item, center, distance, false) + + if (distance < gravitationStrength + 1) { + if (item.hurt(Registry.DAMAGE_EVENT_HORIZON, (gravitationStrength / distance).toFloat()) && item.isRemoved) { + if (item.item.item === Registry.Items.GRAVITATIONAL_DISRUPTOR) { + collapse() + } else { + val mass = MatterRegistry.getMatterValue(item.item) + + if (mass > Fraction.ZERO) + this.mass += mass + } + } + } + } + + // шанс 1% что черная дыра потеряет 0.1 MtU каждую секунду * силу гравитации дыры ^ -1 + if (level.random.nextDouble() < 0.01 * 0.05 * (1 / gravitationStrength)) { + this.mass += HAWKING_MASS_LOSE_STEP + } + } + + init { + mass = mass + } + + companion object { + val BASELINE_MASS = Fraction(1_000) + val HAWKING_MASS_LOSE_STEP = Fraction("-0.1") + } +} \ No newline at end of file