diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/ConfigExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/ConfigExt.kt index cda780e73..d58d45981 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/ConfigExt.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/ConfigExt.kt @@ -40,3 +40,11 @@ fun ForgeConfigSpec.Builder.appendComment(vararg comments: String): ForgeConfigS builderContextField.set(context, reconstruct) return this } + +fun ForgeConfigSpec.Builder.defineInRange(path: String, value: Int, minValue: Int): ForgeConfigSpec.IntValue { + return defineInRange(path, value, minValue, Int.MAX_VALUE) +} + +fun ForgeConfigSpec.Builder.defineInRange(path: String, value: Double, minValue: Double): ForgeConfigSpec.DoubleValue { + return defineInRange(path, value, minValue, Double.MAX_VALUE) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt index ea0b64296..84d35bce6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt @@ -141,7 +141,28 @@ object ServerConfig { val NIGHT_VISION_POWER_DRAW by specBuilder.defineImpreciseFraction("nightVisionPowerDraw", ImpreciseFraction(8), ImpreciseFraction.ZERO) + object Shockwave { + init { + specBuilder.comment("Shockwave ability").push("shockwave") + } + + val TERMINAL_VELOCITY: Double by specBuilder.comment("In meters per second vertically").defineInRange("terminalVelocity", 5.6, 0.0) + val ACCELERATION: Double by specBuilder.comment("In meters per second vertically").defineInRange("acceleration", 4.0, 0.0) + val COOLDOWN: Int by specBuilder.comment("In ticks").defineInRange("cooldown", 30, 1) + val RADIUS_HORIZONTAL: Double by specBuilder.comment("In meters").defineInRange("radiusHorizontal", 4.0, 0.0) + val RADIUS_VERTICAL: Double by specBuilder.comment("In meters").defineInRange("radiusVertical", 1.0, 0.0) + val BREAK_BLOCKS: Boolean by specBuilder.comment("Break blocks without any blast resistance").define("breakBlocks", true) + val DAMAGE: Double by specBuilder.comment("Max potential damage done by shockwave").defineInRange("damage", 12.0, 0.0, Float.MAX_VALUE.toDouble()) + + init { + specBuilder.pop() + } + } + init { + // access shockwave class so spec is built + Shockwave + specBuilder.pop() specBuilder.comment("Tweaking of exosuits").push("exosuitPlayer") diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt index 4439f9bc0..f78e9bb32 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt @@ -11,7 +11,7 @@ import java.io.DataInputStream import java.io.InputStream abstract class AndroidFeature(val type: AndroidFeatureType<*>, val android: MatteryPlayerCapability) : INBTSerializable { - val entity get() = android.ply + val ply get() = android.ply val synchronizer = FieldSynchronizer() open var level by synchronizer.int(setter = setter@{ value, field, setByRemote -> diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt index 9b8568996..566125386 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt @@ -22,6 +22,7 @@ abstract class AndroidSwitchableFeature(type: AndroidFeatureType<*>, android: Ma open val allowToSwitchByPlayer: Boolean get() = true + // TODO: PoseStack is stripped from server dist abstract fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) override fun serializeNBT(): CompoundTag { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoost.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoost.kt index 4d2710c9b..d46078857 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoost.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoost.kt @@ -9,7 +9,7 @@ import java.util.* class AttackBoost(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.LIMB_OVERCLOCKING, android) { override fun applyModifiers() { - val modifier = entity.getAttribute(Attributes.ATTACK_DAMAGE) + val modifier = ply.getAttribute(Attributes.ATTACK_DAMAGE) if (modifier != null) { modifier.removePermanentModifier(MODIFIER_ID) @@ -18,7 +18,7 @@ class AttackBoost(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeat } override fun removeModifiers() { - entity.getAttribute(Attributes.ATTACK_DAMAGE)?.removePermanentModifier(MODIFIER_ID) + ply.getAttribute(Attributes.ATTACK_DAMAGE)?.removePermanentModifier(MODIFIER_ID) } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ExtendedReach.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ExtendedReach.kt index 254e026ae..755e23dde 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ExtendedReach.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ExtendedReach.kt @@ -12,7 +12,7 @@ class ExtendedReach(android: MatteryPlayerCapability) : AndroidFeature(AndroidFe if (!ForgeMod.REACH_DISTANCE.isPresent) return - val reach = entity.getAttribute(ForgeMod.REACH_DISTANCE.get()) ?: return + val reach = ply.getAttribute(ForgeMod.REACH_DISTANCE.get()) ?: return reach.removePermanentModifier(MODIFIER_ID) reach.addPermanentModifier(AttributeModifier(MODIFIER_ID, type.displayName.toString(), level + 1.0, AttributeModifier.Operation.ADDITION)) @@ -22,7 +22,7 @@ class ExtendedReach(android: MatteryPlayerCapability) : AndroidFeature(AndroidFe if (!ForgeMod.REACH_DISTANCE.isPresent) return - entity.getAttribute(ForgeMod.REACH_DISTANCE.get())?.removePermanentModifier(MODIFIER_ID) + ply.getAttribute(ForgeMod.REACH_DISTANCE.get())?.removePermanentModifier(MODIFIER_ID) } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclocking.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclocking.kt index c611d3d8c..d0e0bc903 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclocking.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclocking.kt @@ -9,14 +9,14 @@ import java.util.* class LimbOverclocking(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.LIMB_OVERCLOCKING, android) { override fun applyModifiers() { - val speed = entity.getAttribute(Attributes.MOVEMENT_SPEED) + val speed = ply.getAttribute(Attributes.MOVEMENT_SPEED) if (speed != null) { speed.removePermanentModifier(MODIFIER_ID) speed.addPermanentModifier(AttributeModifier(MODIFIER_ID, type.displayName.toString(), (level + 1) * 0.08, AttributeModifier.Operation.MULTIPLY_TOTAL)) } - val attackSpeed = entity.getAttribute(Attributes.ATTACK_SPEED) + val attackSpeed = ply.getAttribute(Attributes.ATTACK_SPEED) if (attackSpeed != null) { attackSpeed.removePermanentModifier(MODIFIER_ID) @@ -25,8 +25,8 @@ class LimbOverclocking(android: MatteryPlayerCapability) : AndroidFeature(Androi } override fun removeModifiers() { - entity.getAttribute(Attributes.MOVEMENT_SPEED)?.removePermanentModifier(MODIFIER_ID) - entity.getAttribute(Attributes.ATTACK_SPEED)?.removePermanentModifier(MODIFIER_ID) + ply.getAttribute(Attributes.MOVEMENT_SPEED)?.removePermanentModifier(MODIFIER_ID) + ply.getAttribute(Attributes.ATTACK_SPEED)?.removePermanentModifier(MODIFIER_ID) } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmor.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmor.kt index d47cad690..c3236c7c0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmor.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmor.kt @@ -9,7 +9,6 @@ import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact import ru.dbotthepony.mc.otm.core.ImpreciseFraction import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.StatNames -import ru.dbotthepony.mc.otm.container.set import ru.dbotthepony.mc.otm.core.set import kotlin.math.roundToInt @@ -47,7 +46,7 @@ class NanobotsArmor(android: MatteryPlayerCapability) : AndroidFeature(AndroidFe val powerExtracted = android.androidEnergy.extractEnergyInner(powerRequired, false) val realAbsorbed = (powerExtracted / ENERGY_PER_HITPOINT).toFloat() event.amount = event.amount - realAbsorbed - (entity as ServerPlayer?)?.awardStat(StatNames.DAMAGE_ABSORBED, (realAbsorbed * 10f).roundToInt()) + (ply as ServerPlayer?)?.awardStat(StatNames.DAMAGE_ABSORBED, (realAbsorbed * 10f).roundToInt()) layers-- } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegeneration.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegeneration.kt index dfebaefb8..9c7231470 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegeneration.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegeneration.kt @@ -8,7 +8,6 @@ import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.core.ImpreciseFraction import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.StatNames -import ru.dbotthepony.mc.otm.container.set import ru.dbotthepony.mc.otm.core.set import kotlin.math.roundToInt @@ -17,21 +16,21 @@ class NanobotsRegeneration(android: MatteryPlayerCapability) : AndroidFeature(An private var healTicks = 0 override fun tickServer() { - if (entity.health > 0f && entity.health < entity.maxHealth) { + if (ply.health > 0f && ply.health < ply.maxHealth) { ticksPassed++ val waitTime = TICKS_BETWEEN_HEAL.getOrElse(healTicks) { TICKS_BETWEEN_HEAL.last() } if (ticksPassed > waitTime) { - val missingHealth = entity.maxHealth - entity.health + val missingHealth = ply.maxHealth - ply.health val power = ENERGY_PER_HITPOINT * missingHealth val extracted = android.androidEnergy.extractEnergyInner(power, false) if (extracted.isPositive) { healTicks = (healTicks + 1).coerceAtMost(level) val healed = (extracted / ENERGY_PER_HITPOINT).toFloat() - entity.heal(healed) - (entity as ServerPlayer?)?.awardStat(StatNames.HEALTH_REGENERATED, (healed * 10f).roundToInt()) + ply.heal(healed) + (ply as ServerPlayer?)?.awardStat(StatNames.HEALTH_REGENERATED, (healed * 10f).roundToInt()) ticksPassed = 0 } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt index 58ecaf3a7..b5c265587 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt @@ -3,6 +3,8 @@ package ru.dbotthepony.mc.otm.android.feature import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.world.effect.MobEffectInstance import net.minecraft.world.effect.MobEffects +import net.minecraftforge.api.distmarker.Dist +import net.minecraftforge.api.distmarker.OnlyIn import ru.dbotthepony.mc.otm.ServerConfig import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt new file mode 100644 index 000000000..1dcb185fc --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt @@ -0,0 +1,153 @@ +package ru.dbotthepony.mc.otm.android.feature + +import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.level.block.Block +import net.minecraft.world.phys.AABB +import ru.dbotthepony.mc.otm.ServerConfig +import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.core.Vector +import ru.dbotthepony.mc.otm.core.getEllipsoidBlockPositions +import ru.dbotthepony.mc.otm.core.getExplosionResistance +import ru.dbotthepony.mc.otm.core.minus +import ru.dbotthepony.mc.otm.core.plus +import ru.dbotthepony.mc.otm.core.position +import ru.dbotthepony.mc.otm.core.roundToIntVector +import ru.dbotthepony.mc.otm.core.times +import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel +import ru.dbotthepony.mc.otm.network.TriggerShockwavePacket +import ru.dbotthepony.mc.otm.registry.AndroidFeatures +import ru.dbotthepony.mc.otm.registry.AndroidResearch +import ru.dbotthepony.mc.otm.registry.ShockwaveDamageSource +import kotlin.math.pow +import kotlin.math.roundToInt + +class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.SHOCKWAVE, capability) { + var cooldown by synchronizer.int() + + private var wasMidair = false + private var highestSpeed = 0.0 + var airTicks = 0 + private set + + override fun tickClient() { + if (isActive && ply.isSteppingCarefully && cooldown <= 0) { + ply.deltaMovement += Vector(0.0, -ServerConfig.Shockwave.ACCELERATION / 20.0, 0.0) + } + + ticker(true) + } + + fun shockwave() { + // TODO: raycasting + val entities = ply.level.getEntities(ply, AABB( + ply.position.x - ServerConfig.Shockwave.RADIUS_HORIZONTAL, + ply.position.y - ServerConfig.Shockwave.RADIUS_VERTICAL, + ply.position.z - ServerConfig.Shockwave.RADIUS_HORIZONTAL, + + ply.position.x + ServerConfig.Shockwave.RADIUS_HORIZONTAL, + ply.position.y + ServerConfig.Shockwave.RADIUS_VERTICAL, + ply.position.z + ServerConfig.Shockwave.RADIUS_HORIZONTAL, + )) { (it !is LivingEntity || !it.isSpectator && it.isAlive) && ((it.position - ply.position).let { vec -> + vec.x.pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) + + vec.y.pow(2.0) / ServerConfig.Shockwave.RADIUS_VERTICAL.pow(2.0) + + vec.z.pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) <= 1.0 + }) || it.boundingBox.center.let { vec -> + (vec.x - ply.position.x).pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) + + (vec.y - ply.position.y).pow(2.0) / ServerConfig.Shockwave.RADIUS_VERTICAL.pow(2.0) + + (vec.z - ply.position.z).pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) <= 1.0 + } } + + for (entity in entities) { + val diff = entity.position - ply.position + + val distanceMultiplier = diff.let { + it.x.pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) + + it.y.pow(2.0) / ServerConfig.Shockwave.RADIUS_VERTICAL.pow(2.0) + + it.z.pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) + }.coerceAtMost(entity.boundingBox.center.let { vec -> + (vec.x - ply.position.x).pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) + + (vec.y - ply.position.y).pow(2.0) / ServerConfig.Shockwave.RADIUS_VERTICAL.pow(2.0) + + (vec.z - ply.position.z).pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) + }) + + val multiplier = (1.0 - distanceMultiplier).pow(1.25) + + // don't hurt items, arrows, etc etc + if (entity is LivingEntity) { + entity.hurt(ShockwaveDamageSource(ply), multiplier.toFloat() * ServerConfig.Shockwave.DAMAGE.toFloat()) + entity.deltaMovement += diff.normalize() * (multiplier * 3.0) + } else { + entity.deltaMovement += diff.normalize() * (multiplier * 6.0) + } + } + + if (ServerConfig.Shockwave.BREAK_BLOCKS) { + val rounded = ply.position.roundToIntVector() + + for (blockPos in getEllipsoidBlockPositions(ServerConfig.Shockwave.RADIUS_HORIZONTAL.roundToInt(), ServerConfig.Shockwave.RADIUS_VERTICAL.roundToInt(), ServerConfig.Shockwave.RADIUS_HORIZONTAL.roundToInt())) { + val newBlockPos = blockPos + rounded + + val blockState = ply.level.getBlockState(newBlockPos) + + if (!blockState.isAir && blockState.getExplosionResistance(ply.level, newBlockPos) <= 0f && ply.level.getBlockEntity(newBlockPos) == null) { + // Block.dropResources(blockState, ply.level, newBlockPos) + // blockState.block.destroy(ply.level, newBlockPos, blockState) + // ply.level.setBlock(newBlockPos, blockState.fluidState.createLegacyBlock(), Block.UPDATE_ALL) + + ply.level.destroyBlock(newBlockPos, true) + } + } + } + } + + private fun ticker(isClient: Boolean) { + if (!ply.isOnGround) { + airTicks = (airTicks + 1).coerceAtMost(3) + } else { + airTicks = (airTicks - 1).coerceAtLeast(0) + } + + if (isActive && cooldown <= 0) { + val old = wasMidair + wasMidair = !ply.isOnGround + + if (wasMidair) { + highestSpeed = (-ply.deltaMovement.y).coerceAtLeast(highestSpeed) + } + + if (old != wasMidair && !wasMidair && ServerConfig.Shockwave.TERMINAL_VELOCITY <= (highestSpeed * 20.0) && ply.isSteppingCarefully) { + cooldown = ServerConfig.Shockwave.COOLDOWN + + if (isClient) { + // I HATE SELF-UPDATING PLAYERS + // I HATE SELF-UPDATING PLAYERS + // fix "bug" where shockwave doesn't trigger even when player is falling faster than orbiting satellite + MatteryPlayerNetworkChannel.sendToServer(TriggerShockwavePacket) + } else { + shockwave() + } + } + + if (!wasMidair) { + highestSpeed = 0.0 + } + } else if (!isActive) { + highestSpeed = 0.0 + wasMidair = false + } + } + + override fun tickServer() { + if (cooldown > 0) { + cooldown-- + } + + ticker(false) + } + + override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { + AndroidResearch.ICON_SHOCKWAVE.render(stack, x, y, width, height) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt index b44380147..4c0f837b2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt @@ -12,7 +12,7 @@ class StepAssistFeature(android: MatteryPlayerCapability) : AndroidFeature(Andro if (!ForgeMod.STEP_HEIGHT_ADDITION.isPresent) return - val reach = entity.getAttribute(ForgeMod.STEP_HEIGHT_ADDITION.get()) ?: return + val reach = ply.getAttribute(ForgeMod.STEP_HEIGHT_ADDITION.get()) ?: return reach.removePermanentModifier(MODIFIER_ID) reach.addPermanentModifier(AttributeModifier(MODIFIER_ID, type.displayName.toString(), level + 1.0, AttributeModifier.Operation.ADDITION)) @@ -22,7 +22,7 @@ class StepAssistFeature(android: MatteryPlayerCapability) : AndroidFeature(Andro if (!ForgeMod.STEP_HEIGHT_ADDITION.isPresent) return - entity.getAttribute(ForgeMod.STEP_HEIGHT_ADDITION.get())?.removePermanentModifier(MODIFIER_ID) + ply.getAttribute(ForgeMod.STEP_HEIGHT_ADDITION.get())?.removePermanentModifier(MODIFIER_ID) } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt index eb3411d8c..c027d455f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt @@ -31,7 +31,7 @@ import ru.dbotthepony.mc.otm.matter.getMatterValue import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRegistry -import ru.dbotthepony.mc.otm.container.set +import ru.dbotthepony.mc.otm.core.getSphericalBlockPositions import ru.dbotthepony.mc.otm.core.set import kotlin.math.pow import kotlin.math.roundToInt @@ -302,7 +302,7 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : BlockEn } if (gravitationStrength > 0.4) { - val sphere = getSphericalShape((gravitationStrength * 6.0).roundToInt()) + val sphere = getSphericalBlockPositions((gravitationStrength * 6.0).roundToInt()) if (sphere.size != lastSphereSizeOuter) { lastSphereSizeOuter = sphere.size @@ -327,12 +327,14 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : BlockEn val eResist = try { getBlock.getExplosionResistance(level, pos, null) - } catch (err: Throwable) { - getBlock.block.getExplosionResistance() + } catch (err: NullPointerException) { + getBlock.block.explosionResistance // Потому что возможно какой-либо мод не ожидает что Explosion == null // особенно учитывая что интерфейс IForgeBlock не имеет @ParamsAreNonnullByDefault // и аргумент не помечен как @Nullable // тем самым имеет тип Explosion! который указывается как Explosion? .. Explosion!! + } catch (err: IllegalArgumentException) { + getBlock.block.explosionResistance } var strengthLinear = sqrt(blockPos.distSqr(pos)) @@ -362,49 +364,5 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : BlockEn const val ITERATIONS = 30_000 val BASELINE_MASS = ImpreciseFraction(1_000) val HAWKING_MASS_LOSE_STEP = ImpreciseFraction("-0.1") - - private val blockShapeCache = HashMap>() - - private fun getSphericalShape(size: Int): Array { - return blockShapeCache.computeIfAbsent(size) { - val result = ArrayList((it * it * it * Math.PI * (4.0 / 3.0)).toInt() + 16) - - for (x in -it .. it) { - for (y in -it .. it) { - for (z in -it .. it) { - val dist = sqrt(x * x.toDouble() + y * y.toDouble() + z * z.toDouble()) - - if (dist <= size && dist != 0.0) { - result.add(BlockPos(x, y, z)) - } - } - } - } - - val arr = result.toTypedArray() - - arr.sortWith { a, b -> - val xa = a.x - val ya = a.y - val za = a.z - val distA = sqrt(xa * xa.toDouble() + ya * ya.toDouble() + za * za.toDouble()) - - val xb = b.x - val yb = b.y - val zb = b.z - val distB = sqrt(xb * xb.toDouble() + yb * yb.toDouble() + zb * zb.toDouble()) - - if (distA == distB) { - return@sortWith 0 - } else if (distA > distB) { - return@sortWith 1 - } else { - return@sortWith -1 - } - } - - return@computeIfAbsent arr - } - } } } \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/EuclidMath.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/EuclidMath.kt index d089dced4..6ba61c65d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/EuclidMath.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/EuclidMath.kt @@ -2,10 +2,14 @@ package ru.dbotthepony.mc.otm.core import com.mojang.math.Quaternion import com.mojang.math.Vector3f +import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap +import it.unimi.dsi.fastutil.ints.Int2ObjectFunction +import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap import net.minecraft.core.BlockPos import net.minecraft.core.Direction import net.minecraft.core.Vec3i import net.minecraft.world.phys.Vec3 +import java.lang.ref.SoftReference import kotlin.math.* typealias Vector = Vec3 @@ -376,6 +380,9 @@ operator fun Vec3i.times(int: Int): Vec3i = this.multiply(int) operator fun Direction.times(int: Int): Vec3i = this.normal.multiply(int) operator fun Vec3i.times(double: Double): Vector = Vector(x * double, y * double, z * double) +fun Vec3.toIntVector() = Vec3i(x.toInt(), y.toInt(), z.toInt()) +fun Vec3.roundToIntVector() = Vec3i(x.roundToInt(), y.roundToInt(), z.roundToInt()) + fun BlockPos.asVector(): Vector { return Vector(x + 0.5, y + 0.5, z + 0.5) } @@ -383,3 +390,61 @@ fun BlockPos.asVector(): Vector { operator fun Direction.unaryMinus(): Direction = this.opposite operator fun Vec3i.unaryMinus(): Vec3i = Vec3i(-x, -y, -z) operator fun BlockPos.unaryMinus(): BlockPos = BlockPos(-x, -y, -z) + +private data class EllipsoidShapeCacheKey(val x: Int, val y: Int, val z: Int) : Comparable { + override fun compareTo(other: EllipsoidShapeCacheKey): Int { + return x.compareTo(other.x).let { if (it != 0) it else y.compareTo(other.y).let { if (it != 0) it else z.compareTo(other.z) } } + } +} + +private val blockShapeCache = Object2ObjectAVLTreeMap>>(EllipsoidShapeCacheKey::compareTo) + +fun getSphericalBlockPositions(size: Int): List { + return getEllipsoidBlockPositions(size, size, size) +} + +fun getEllipsoidBlockPositions(x: Int, y: Int, z: Int): List { + val getResult = blockShapeCache[EllipsoidShapeCacheKey(x, y, z)]?.get() + + if (getResult != null) { + return getResult + } + + val result = ArrayList((x * y * z * Math.PI * (4.0 / 3.0)).toInt() + 16) + + val xPow = x.toDouble().pow(2.0) + val yPow = y.toDouble().pow(2.0) + val zPow = z.toDouble().pow(2.0) + + for (ellipsoidX in -x..x) { + for (ellipsoidY in -y..y) { + for (ellipsoidZ in -z..z) { + if ( + ellipsoidX.toDouble().pow(2.0) / xPow + + ellipsoidY.toDouble().pow(2.0) / yPow + + ellipsoidZ.toDouble().pow(2.0) / zPow <= 1.0 + ) { + result.add(BlockPos(ellipsoidX, ellipsoidY, ellipsoidZ)) + } + } + } + } + + result.sortWith { a, b -> + val xa = a.x + val ya = a.y + val za = a.z + val distA = sqrt(xa * xa.toDouble() + ya * ya.toDouble() + za * za.toDouble()) + + val xb = b.x + val yb = b.y + val zb = b.z + val distB = sqrt(xb * xb.toDouble() + yb * yb.toDouble() + zb * zb.toDouble()) + + return@sortWith distA.compareTo(distB) + } + + val immutableList = com.google.common.collect.ImmutableList.copyOf(result) + blockShapeCache[EllipsoidShapeCacheKey(x, y, z)] = SoftReference(immutableList) + return immutableList +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt index 7f2a35d03..07414d233 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -6,6 +6,7 @@ package ru.dbotthepony.mc.otm.core import com.google.common.collect.ImmutableList import com.google.gson.JsonElement import com.google.gson.JsonObject +import net.minecraft.core.BlockPos import net.minecraft.nbt.ByteArrayTag import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.Tag @@ -14,6 +15,8 @@ import net.minecraft.resources.ResourceLocation import net.minecraft.world.entity.Entity import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.StateHolder import net.minecraft.world.level.block.state.properties.Property import net.minecraft.world.phys.Vec3 @@ -344,3 +347,17 @@ fun List.toImmutableList(): ImmutableList { operator fun (() -> R).getValue(thisRef: Any, property: KProperty<*>): R { return invoke() } + +fun BlockState.getExplosionResistance(level: BlockGetter, pos: BlockPos): Float { + return try { + getExplosionResistance(level, pos, null) + } catch (err: NullPointerException) { + block.explosionResistance + // Потому что возможно какой-либо мод не ожидает что Explosion == null + // особенно учитывая что интерфейс IForgeBlock не имеет @ParamsAreNonnullByDefault + // и аргумент не помечен как @Nullable + // тем самым имеет тип Explosion! который указывается как Explosion? .. Explosion!! + } catch (err: IllegalArgumentException) { + block.explosionResistance + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt index c0ee07b09..f4e67d2c2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt @@ -13,6 +13,7 @@ import ru.dbotthepony.mc.otm.android.AndroidFeatureType import ru.dbotthepony.mc.otm.android.AndroidResearch import ru.dbotthepony.mc.otm.android.AndroidResearchType import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature +import ru.dbotthepony.mc.otm.android.feature.ShockwaveFeature import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.MatteryGUI import ru.dbotthepony.mc.otm.client.minecraft @@ -353,6 +354,23 @@ class SwitchAndroidFeaturePacket(val type: AndroidFeatureType<*>, val newState: } } +object TriggerShockwavePacket : MatteryPacket { + override fun write(buff: FriendlyByteBuf) { + // no op + } + + override fun play(context: Supplier) { + context.packetHandled = true + context.enqueueWork { + val shockwave = context.sender?.matteryPlayer?.getFeature(AndroidFeatures.SHOCKWAVE) as ShockwaveFeature? ?: return@enqueueWork + + if (shockwave.cooldown <= 0 && shockwave.isActive && shockwave.airTicks > 0) { + shockwave.shockwave() + } + } + } +} + object MatteryPlayerNetworkChannel : MatteryNetworkChannel( version = "1", name = "player" @@ -375,5 +393,6 @@ object MatteryPlayerNetworkChannel : MatteryNetworkChannel( add(ExoSuitMenuClose::class, { ExoSuitMenuClose }, PLAY_TO_SERVER) add(SwitchAndroidFeaturePacket::class, SwitchAndroidFeaturePacket.Companion::read, PLAY_TO_SERVER) + add(TriggerShockwavePacket::class, { TriggerShockwavePacket }, PLAY_TO_SERVER) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt index 2fd3366f3..f828d31ee 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt @@ -19,6 +19,7 @@ object AndroidFeatures { val NANOBOTS_ARMOR: AndroidFeatureType<*> by registry.register(MNames.NANOBOTS_ARMOR) { AndroidFeatureType(::NanobotsArmor) } val EXTENDED_REACH: AndroidFeatureType<*> by registry.register(MNames.EXTENDED_REACH) { AndroidFeatureType(::ExtendedReach) } val NIGHT_VISION: AndroidFeatureType<*> by registry.register(MNames.NIGHT_VISION) { AndroidFeatureType(::NightVisionFeature) } + val SHOCKWAVE: AndroidFeatureType<*> by registry.register(MNames.SHOCKWAVE) { AndroidFeatureType(::ShockwaveFeature) } internal fun register(bus: IEventBus) { registry.register(bus) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt index 33dcd99dd..7cba65a61 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt @@ -260,7 +260,7 @@ object AndroidResearch { armorSpeedList.add(registry.register(MNames.NANOBOTS_ARMOR_SPEED_LIST[i]) { AndroidResearchBuilder() - .withExperience(20 + i * 8) + .withExperience(18 + i * 6) .withIconText(TextComponent((i + 1).toString())) .withIcon(ICON_ARMOR) .addPrerequisite(OverdriveThatMatters.loc(if (i > 0) MNames.NANOBOTS_ARMOR_SPEED_LIST[i - 1] else MNames.NANOBOTS_ARMOR)) @@ -290,4 +290,14 @@ object AndroidResearch { NANOBOTS_ARMOR_STRENGTH = RegistryObjectList(armorStrengthList) ATTACK_BOOST = RegistryObjectList(attackBoostList) } + + val SHOCKWAVE: AndroidResearchType<*> by registry.register(MNames.SHOCKWAVE) { + AndroidResearchBuilder() + .withExperience(40) + .withDescription() + .withIcon(ICON_SHOCKWAVE) + .addFeatureResult(AndroidFeatures.SHOCKWAVE) + .addPrerequisite(ATTACK_BOOST[2]) + .build() + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/DamageSources.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/DamageSources.kt index ed4425028..5d6580085 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/DamageSources.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/DamageSources.kt @@ -169,6 +169,16 @@ class EMPDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : Ma } } +class ShockwaveDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : MatteryDamageSource(MRegistry.DAMAGE_SHOCKWAVE_NAME, entity, inflictor) { + init { + bypassArmor() + } + + override fun scalesWithDifficulty(): Boolean { + return false + } +} + class PlasmaDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : MatteryDamageSource(MRegistry.DAMAGE_PLASMA_NAME, entity, inflictor) { override fun scalesWithDifficulty(): Boolean { return false diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt index e359b674d..3180a46ad 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt @@ -208,6 +208,7 @@ object MNames { const val EXTENDED_REACH = "extended_reach" const val NIGHT_VISION = "night_vision" + const val SHOCKWAVE = "shockwave" const val IMPROVED_LIMBS = "improved_limbs" // stats diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt index d706bfa2a..5587f6dd1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt @@ -211,6 +211,7 @@ object MRegistry { val DAMAGE_EVENT_HORIZON = ImmutableDamageSource(DamageSource(DAMAGE_EVENT_HORIZON_ID).bypassMagic().bypassArmor()) val DAMAGE_HAWKING_RADIATION = ImmutableDamageSource(DamageSource(DAMAGE_HAWKING_RADIATION_ID)) const val DAMAGE_EMP_NAME = "otm_emp" + const val DAMAGE_SHOCKWAVE_NAME = "otm_shockwave" const val DAMAGE_PLASMA_NAME = "otm_plasma" val DAMAGE_EMP: DamageSource = ImmutableDamageSource(EMPDamageSource())