Move black holes to kotlin

This commit is contained in:
DBotThePony 2022-01-09 17:17:31 +07:00
parent 3039bff89b
commit ec6c903b6f
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 484 additions and 493 deletions

View File

@ -20,7 +20,7 @@ import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 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.MatteryCapability;
import ru.dbotthepony.mc.otm.capability.android.AndroidCapability; import ru.dbotthepony.mc.otm.capability.android.AndroidCapability;
import ru.dbotthepony.mc.otm.capability.android.AndroidCapabilityPlayer; 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(AndroidCapabilityPlayer.Companion);
MinecraftForge.EVENT_BUS.register(AndroidCapability.Companion); MinecraftForge.EVENT_BUS.register(AndroidCapability.Companion);
MinecraftForge.EVENT_BUS.register(MatterRegistry.class); 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); FMLJavaModLoadingContext.get().getModEventBus().register(MatteryCapability.class);

View File

@ -35,6 +35,7 @@ import ru.dbotthepony.mc.otm.android.feature.AndroidNanobotsRegeneration;
import ru.dbotthepony.mc.otm.block.*; import ru.dbotthepony.mc.otm.block.*;
import ru.dbotthepony.mc.otm.block.entity.*; import ru.dbotthepony.mc.otm.block.entity.*;
import ru.dbotthepony.mc.otm.android.AndroidFeatureType; 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.BlackHoleRenderer;
import ru.dbotthepony.mc.otm.client.render.SkinElement; import ru.dbotthepony.mc.otm.client.render.SkinElement;
import ru.dbotthepony.mc.otm.core.Fraction; import ru.dbotthepony.mc.otm.core.Fraction;

View File

@ -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<Float> 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<QueuedExplosion> explosions = new ArrayList<>();
private final ArrayList<RingExplosion> 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 <T extends BlockEntity> 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 <T extends BlockEntity> 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);
}
}
}
}

View File

@ -7,7 +7,7 @@ import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.world.phys.Vec3; 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.*; import static org.lwjgl.opengl.GL33.*;

View File

@ -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: Byte) = putByte(s, value)
operator fun CompoundTag.set(s: String, value: Short) = putShort(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: 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: String) = putString(s, value)
operator fun CompoundTag.set(s: String, value: Boolean) = putBoolean(s, value) operator fun CompoundTag.set(s: String, value: Boolean) = putBoolean(s, value)
operator fun CompoundTag.set(s: String, value: ByteArray) = putByteArray(s, value) operator fun CompoundTag.set(s: String, value: ByteArray) = putByteArray(s, value)

View File

@ -14,7 +14,7 @@ import net.minecraft.world.level.material.MaterialColor
import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape import net.minecraft.world.phys.shapes.VoxelShape
import ru.dbotthepony.mc.otm.Registry 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 import ru.dbotthepony.mc.otm.shapes.BlockShapes
class BlockBlackHole : class BlockBlackHole :
@ -29,7 +29,10 @@ class BlockBlackHole :
} }
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity {
return BlockEntityBlackHole(blockPos, blockState) return BlockEntityBlackHole(
blockPos,
blockState
)
} }
override fun <T : BlockEntity?> getTicker( override fun <T : BlockEntity?> getTicker(
@ -39,21 +42,11 @@ class BlockBlackHole :
): BlockEntityTicker<T>? { ): BlockEntityTicker<T>? {
if (p_153214_ !== Registry.BlockEntities.BLACK_HOLE) return null if (p_153214_ !== Registry.BlockEntities.BLACK_HOLE) return null
return if (p_153212_.isClientSide) BlockEntityTicker { level: Level, blockPos: BlockPos, blockState: BlockState, t: T -> if (p_153212_.isClientSide) {
BlockEntityBlackHole.clientTicker( return BlockEntityTicker { _, _, _, t -> if (t is BlockEntityBlackHole) t.clientTick() }
level,
blockPos,
blockState,
t
)
} else BlockEntityTicker { level: Level, blockPos: BlockPos, blockState: BlockState, t: T ->
BlockEntityBlackHole.ticker(
level,
blockPos,
blockState,
t
)
} }
return BlockEntityTicker { _, _, _, t -> if (t is BlockEntityBlackHole) t.tick() }
} }
companion object { companion object {

View File

@ -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<Float> {
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<QueuedExplosion>()
private val rings = ArrayList<RingExplosion>()
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")
}
}