Explosive Hammer

This commit is contained in:
DBotThePony 2023-04-13 21:07:29 +07:00
parent f0076aca99
commit 8760fc3ec0
Signed by: DBot
GPG Key ID: DCC23B5715498507
20 changed files with 587 additions and 81 deletions

View File

@ -8,7 +8,6 @@ import net.minecraft.advancements.critereon.InventoryChangeTrigger
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.data.ExistingFileHelper
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider
import ru.dbotthepony.mc.otm.datagen.modLocation
@ -16,6 +15,7 @@ import ru.dbotthepony.mc.otm.registry.MItemTags
import ru.dbotthepony.mc.otm.registry.MItems
import ru.dbotthepony.mc.otm.registry.MRegistry
import ru.dbotthepony.mc.otm.triggers.BlackHoleTrigger
import ru.dbotthepony.mc.otm.triggers.NailedEntityTrigger
import java.util.function.Consumer
fun addAdvancements(serializer: Consumer<Advancement>, existingFileHelper: ExistingFileHelper, lang: MatteryLanguageProvider) {
@ -553,4 +553,18 @@ fun addAdvancements(serializer: Consumer<Advancement>, existingFileHelper: Exist
.addCriterion("essence1", criterion(MItems.ESSENCE_CAPSULE))
.addCriterion("essence2", criterion(MItems.ESSENCE_DRIVE))
.save(serializer, modLocation("regular/essence_capsule"), existingFileHelper)
AdvancementBuilder()
.parent(root)
.display(
itemStack = ItemStack(MItems.EXPLOSIVE_HAMMER).also { MItems.EXPLOSIVE_HAMMER.prime(it) },
title = translation.add("explosive_hammer", "I Did It Like This") {
russian("Я сделал это вот так")
},
description = translation.add("explosive_hammer.desc", "Nail down something (or someone)") {
russian("Пригвоздите что-либо (или кого-либо)")
}
)
.addCriterion("damage", NailedEntityTrigger.Instance())
.save(serializer, modLocation("regular/explosive_hammer"), existingFileHelper)
}

View File

@ -348,6 +348,11 @@ private fun death(provider: MatteryLanguageProvider) {
death("otm_emp.player", "%2\$s blew fuzes of %1\$s")
death("otm_emp.player.item", "%2\$s blew fuzes of %1\$s using %3\$s")
death(MRegistry.DAMAGE_EXPLOSIVE_HAMMER_NAME, "%1\$s's fun time with hammer is over")
death(MRegistry.DAMAGE_HAMMER_NAIL_NAME, "%1\$s got nailed")
death(MRegistry.DAMAGE_HAMMER_NAIL_NAME + ".player", "%1\$s got nailed by %\$2")
death(MRegistry.DAMAGE_HAMMER_NAIL_NAME + ".player.item", "%1\$s got nailed by %2\$s using %3\$s")
death(MRegistry.DAMAGE_EXOPACK_PROBE_ID, "%1\$s couldn't handle spinal surgery")
death("${MRegistry.DAMAGE_EXOPACK_PROBE_ID}.player", "%1\$s couldn't handle spinal surgery whilst fighting %2\$s")
}
@ -447,6 +452,12 @@ private fun blocks(provider: MatteryLanguageProvider) {
private fun items(provider: MatteryLanguageProvider) {
with(provider.english) {
add(MItems.EXPLOSIVE_HAMMER, "Explosive Hammer")
add(MItems.EXPLOSIVE_HAMMER, "desc", "For those who feel bored")
add(MItems.EXPLOSIVE_HAMMER, "primed", "Primed!")
add(MItems.EXPLOSIVE_HAMMER, "not_primed0", "Not primed")
add(MItems.EXPLOSIVE_HAMMER, "not_primed1", "Prime at crafting table with little of gunpowder and nugget payload")
add(MItems.EXOPACK_PROBE, "Exopack Probe")
add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_CREATIVE, "Creative Exopack Inventory Upgrade")
add(MItems.ExopackUpgrades.CRAFTING_UPGRADE, "Exopack Crafting Upgrade")

View File

@ -355,6 +355,11 @@ private fun death(provider: MatteryLanguageProvider) {
death("otm_emp.player", "%2\$s выбил все предохранители %1\$s")
death("otm_emp.player.item", "%2\$s выбил все предохранители %1\$s используя %3\$s")
death(MRegistry.DAMAGE_EXPLOSIVE_HAMMER_NAME, "Время развлечений у %1\$s с молотком подошло к концу")
death(MRegistry.DAMAGE_HAMMER_NAIL_NAME, "%1\$s был пригвождён")
death(MRegistry.DAMAGE_HAMMER_NAIL_NAME + ".player", "%1\$s был пригвождён %2\$s")
death(MRegistry.DAMAGE_HAMMER_NAIL_NAME + ".player.item", "%1\$s был пригвождён %2\$s используя %3\$s")
death(MRegistry.DAMAGE_EXOPACK_PROBE_ID, "%1\$s не выдержал спинную хирургию")
death("${MRegistry.DAMAGE_EXOPACK_PROBE_ID}.player", "%1\$s не выдержал спинную хирургию пока сражался с %2\$s")
}
@ -454,6 +459,12 @@ private fun blocks(provider: MatteryLanguageProvider) {
private fun items(provider: MatteryLanguageProvider) {
with(provider.russian) {
add(MItems.EXPLOSIVE_HAMMER, "Молоток-убийца")
add(MItems.EXPLOSIVE_HAMMER, "desc", "Для тех, кому стало скучно")
add(MItems.EXPLOSIVE_HAMMER, "primed", "Заряжен!")
add(MItems.EXPLOSIVE_HAMMER, "not_primed0", "Не заряжен")
add(MItems.EXPLOSIVE_HAMMER, "not_primed1", "Для зарядки добавьте немного пороха и наконечник-самородок")
add(MItems.EXOPACK_PROBE, "Маяк экзопака")
add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_CREATIVE, "Творческое обновление инвентаря экзопака")
add(MItems.ExopackUpgrades.CRAFTING_UPGRADE, "Обновление сетки крафта экзопака")

View File

@ -15,6 +15,7 @@ import ru.dbotthepony.mc.otm.registry.MItems
import ru.dbotthepony.mc.otm.registry.MRegistry
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.datagen.modLocation
import ru.dbotthepony.mc.otm.recipe.ExplosiveHammerPrimingRecipe
import ru.dbotthepony.mc.otm.recipe.UpgradeRecipe
import java.util.function.Consumer
@ -331,4 +332,13 @@ fun addCraftingTableRecipes(consumer: Consumer<FinishedRecipe>) {
.unlockedBy(MItemTags.HARDENED_GLASS_PANES)
.unlockedBy(MItemTags.TRITANIUM_INGOTS)
.build(consumer)
consumer.accept(ExplosiveHammerPrimingRecipe(modLocation("hammer_priming"), Ingredient.of(Tags.Items.NUGGETS_IRON)).finishedRecipe)
MatteryRecipe(MItems.EXPLOSIVE_HAMMER, category = RecipeCategory.COMBAT)
.rowB(Tags.Items.INGOTS_IRON)
.rowAB(Tags.Items.INGOTS_IRON, Tags.Items.RODS_WOODEN)
.rowB(Tags.Items.RODS_WOODEN)
.unlockedBy(Items.FLINT_AND_STEEL)
.build(consumer)
}

View File

@ -46,6 +46,7 @@ import ru.dbotthepony.mc.otm.config.ServerCompatConfig;
import ru.dbotthepony.mc.otm.config.ServerConfig;
import ru.dbotthepony.mc.otm.config.ToolsConfig;
import ru.dbotthepony.mc.otm.core.math.Decimal;
import ru.dbotthepony.mc.otm.item.ExplosiveHammerItem;
import ru.dbotthepony.mc.otm.item.ItemTritaniumArmor;
import ru.dbotthepony.mc.otm.item.QuantumBatteryItem;
import ru.dbotthepony.mc.otm.item.weapon.AbstractWeaponItem;
@ -191,6 +192,8 @@ public final class OverdriveThatMatters {
EVENT_BUS.addListener(EventPriority.NORMAL, EnderTeleporterFeature.Companion::onEntityDeath);
EVENT_BUS.addListener(EventPriority.HIGH, ItemTritaniumArmor.Companion::onHurt);
EVENT_BUS.addListener(EventPriority.NORMAL, ExplosiveHammerItem.Companion::onLeftClickBlock);
MatteryPlayerNetworkChannel.INSTANCE.register();
MenuNetworkChannel.INSTANCE.register();
WeaponNetworkChannel.INSTANCE.register();

View File

@ -9,6 +9,7 @@ import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import ru.dbotthepony.mc.otm.OverdriveThatMatters;
import ru.dbotthepony.mc.otm.recipe.EnergyContainerRecipe;
import ru.dbotthepony.mc.otm.recipe.ExplosiveHammerPrimingRecipe;
import ru.dbotthepony.mc.otm.recipe.PlatePressRecipe;
import ru.dbotthepony.mc.otm.recipe.PlatePressRecipeFactory;
import ru.dbotthepony.mc.otm.recipe.UpgradeRecipe;
@ -30,6 +31,7 @@ public class MRecipes {
public static final MatteryRecipeType<PlatePressRecipe> PLATE_PRESS = new MatteryRecipeType<>(OverdriveThatMatters.loc(MNames.PLATE_PRESS));
public static final MatteryRecipeType<PlatePressRecipe> ENERGY_CONTAINER = new MatteryRecipeType<>(OverdriveThatMatters.loc("energy_container"));
public static final MatteryRecipeType<PlatePressRecipe> UPGRADE = new MatteryRecipeType<>(OverdriveThatMatters.loc("upgrade"));
public static final MatteryRecipeType<PlatePressRecipe> HAMMER_PRIMING = new MatteryRecipeType<>(OverdriveThatMatters.loc("hammer_priming"));
private static final DeferredRegister<RecipeSerializer<?>> serializerRegistry = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, OverdriveThatMatters.MOD_ID);
private static final DeferredRegister<RecipeType<?>> typeRegistry = DeferredRegister.create(ForgeRegistries.RECIPE_TYPES, OverdriveThatMatters.MOD_ID);
@ -38,10 +40,12 @@ public class MRecipes {
serializerRegistry.register(MNames.PLATE_PRESS, () -> PlatePressRecipeFactory.INSTANCE);
serializerRegistry.register(ENERGY_CONTAINER.name.getPath(), () -> EnergyContainerRecipe.Companion);
serializerRegistry.register(UPGRADE.name.getPath(), () -> UpgradeRecipe.Companion);
serializerRegistry.register(HAMMER_PRIMING.name.getPath(), () -> ExplosiveHammerPrimingRecipe.Companion);
typeRegistry.register(MNames.PLATE_PRESS, () -> PLATE_PRESS);
typeRegistry.register(ENERGY_CONTAINER.name.getPath(), () -> ENERGY_CONTAINER);
typeRegistry.register(UPGRADE.name.getPath(), () -> UPGRADE);
typeRegistry.register(HAMMER_PRIMING.name.getPath(), () -> HAMMER_PRIMING);
}
public static void register(IEventBus bus) {

View File

@ -140,7 +140,7 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF
entity.deltaMovement += (entity.position - ply.position).normalize() * (multiplier * 3.0)
if (ply is ServerPlayer) {
ShockwaveDamageMobTrigger.trigger(ply as ServerPlayer, entity, source, damage)
ShockwaveDamageMobTrigger.trigger(ply as ServerPlayer, entity, damage, source)
}
}
@ -156,7 +156,7 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF
entity.deltaMovement += (entity.position - ply.position).normalize() * (multiplier * 3.0)
if (ply is ServerPlayer) {
ShockwaveDamageMobTrigger.trigger(ply as ServerPlayer, entity, source, damage)
ShockwaveDamageMobTrigger.trigger(ply as ServerPlayer, entity, damage, source)
}
} else {
entity.deltaMovement += (entity.position - ply.position).normalize() * (multiplier * 6.0)

View File

@ -23,6 +23,8 @@ import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.chunk.LevelChunk
import net.minecraft.world.phys.Vec3
import net.minecraftforge.common.capabilities.Capability
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.util.INBTSerializable
@ -59,6 +61,7 @@ import ru.dbotthepony.mc.otm.onceServer
import java.lang.ref.WeakReference
import java.util.*
import java.util.function.Supplier
import java.util.stream.Stream
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
@ -730,6 +733,38 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
private val vec2Dir = Int2ObjectOpenHashMap<Direction>()
/**
* Returns stream of players watching (tracking) specified [chunkPos] in [level]
*/
fun watchingPlayers(chunkPos: Long, level: Level): Stream<ServerPlayer> {
if (level !is ServerLevel)
return Stream.empty()
val subs = playerMap[level] ?: return Stream.empty()
val chunk = subs[chunkPos] ?: return Stream.empty()
return chunk.players.stream()
}
/**
* Returns stream of players watching (tracking) specified [chunkPos] in [level]
*/
fun watchingPlayers(chunkPos: ChunkPos, level: Level) = watchingPlayers(chunkPos.toLong(), level)
/**
* Returns stream of players watching (tracking) specified [blockPos] in [level]
*/
fun watchingPlayers(blockPos: BlockPos, level: Level) = watchingPlayers(ChunkPos(blockPos), level)
/**
* Returns stream of players watching (tracking) specified [blockPos] in [level]
*/
fun watchingPlayers(blockPos: Vec3, level: Level) = watchingPlayers(ChunkPos.asLong(SectionPos.blockToSectionCoord(blockPos.x), SectionPos.blockToSectionCoord(blockPos.z)), level)
/**
* Returns stream of players watching (tracking) specified [chunk]
*/
fun watchingPlayers(chunk: LevelChunk) = watchingPlayers(chunk.pos, chunk.level)
private fun vecKey(value: Vec3i): Int {
if (value.x !in -1 .. 1) return -1
if (value.y !in -1 .. 1) return -1

View File

@ -21,6 +21,8 @@ import ru.dbotthepony.mc.otm.block.BlackHoleBlock
import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue.Companion.queueForLevel
import ru.dbotthepony.mc.otm.core.getExplosionResistance
import ru.dbotthepony.mc.otm.core.gracefulBlockBreak
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.registry.MBlockEntities
@ -101,7 +103,7 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
if (setByRemote) {
updateMass()
}
})
}).property
fun stabilizerAttached(stabilizer: GravitationStabilizerBlockEntity) {
if (stabilizer in stabilizers)
@ -162,10 +164,10 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
nbt["spin_direction"] = spinDirection
}
override fun load(tag: CompoundTag) {
super.load(tag)
mass = tag.map("mass", Decimal::deserializeNBT) ?: BASELINE_MASS
spinDirection = tag.getBoolean("spin_direction")
override fun load(nbt: CompoundTag) {
super.load(nbt)
mass = nbt.map("mass", Decimal::deserializeNBT) ?: BASELINE_MASS
spinDirection = nbt.getBoolean("spin_direction")
}
var affectedBounds = BoundingBox(0, 0, 0, 1, 1, 1)
@ -176,7 +178,7 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
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)).coerceAtLeast(0.0), 2.0) * gravitationStrength / 8
val mult = (1 - distance / (30 * gravitationStrength)).coerceAtLeast(0.0).pow(2.0) * gravitationStrength / 8
// Притяжение к ядру
val delta = living.position().vectorTo(center).normalize()
@ -291,18 +293,7 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
if (!getBlock.isAir && getBlock.block !is BlackHoleBlock) {
val speed = getBlock.getDestroySpeed(level, pos)
val eResist = try {
getBlock.getExplosionResistance(level, pos, null)
} catch (err: NullPointerException) {
getBlock.block.explosionResistance
// Потому что возможно какой-либо мод не ожидает что Explosion == null
// особенно учитывая что интерфейс IForgeBlock не имеет @ParamsAreNonnullByDefault
// и аргумент не помечен как @Nullable
// тем самым имеет тип Explosion! который указывается как Explosion? .. Explosion!!
} catch (err: IllegalArgumentException) {
getBlock.block.explosionResistance
}
val eResist = getBlock.getExplosionResistance(level, pos)
var strengthLinear = sqrt(blockPos.distSqr(pos))
if (strengthLinear < gravitationStrength) {
@ -310,9 +301,7 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
}
if (speed >= 0f && (speed * 2f).coerceAtLeast(eResist / 3f) < gravitationStrength * (12.0 / strengthLinear)) {
Block.dropResources(getBlock, level, pos, level.getBlockEntity(pos))
getBlock.block.destroy(level, pos, getBlock)
level.setBlock(pos, getBlock.fluidState.createLegacyBlock(), Block.UPDATE_ALL)
level.gracefulBlockBreak(pos, getBlock)
}
}
}

View File

@ -98,4 +98,76 @@ object ItemsConfig : AbstractConfig("items") {
val FLUID_CAPSULE_CAPACITY: Int by builder.defineInRange("FLUID_CAPSULE_CAPACITY", 1000, 1, Int.MAX_VALUE)
val FLUID_TANK_CAPACITY: Int by builder.defineInRange("FLUID_TANK_CAPACITY", 32_000, 1, Int.MAX_VALUE)
object ExplosiveHammer {
val MAX_TRAVEL: Double by builder
.comment("Max distance, in blocks, the nail can travel", "This also dictates potential max damage nail can deal (controlled by TRAVEL_DAMAGE_MULT)")
.comment("The nail penetrate through blocks and entities, losing power in process")
.comment("The nail can not travel into unloaded chunks and will stop immediately if it does so")
.comment("The nail travels its entire path instantaneously")
.defineInRange("MAX_TRAVEL", 48.0, 0.0, Double.MAX_VALUE)
val TRAVEL_EXPLOSION_RESIST_MULT: Double by builder
.comment("Multiplier of explosion resistance of blocks on nail path")
.comment("If explosion resistance multiplied by this is bigger than nail current force (base of MAX_TRAVEL)")
.comment("then nail movement halts, otherwise nail force is reduced by explosion resistance multiplied by this")
.comment("and block is broken (if DAMAGE_BLOCKS is true, otherwise it only acts as stopping power)")
.comment("---")
.comment("Values over 1 will make blocks more resistant to be penetrated through (and broken)")
.comment("Values below 1 will make blocks less resistant to be penetrated though (and broken)")
.comment("Value of 0 means no matter how strong block resistance is, it will be broken by nail", "(this includes bedrock!!!)")
.comment("---")
.defineInRange("TRAVEL_EXPLOSION_RESIST_MULT", 1.75, 0.0, Double.MAX_VALUE)
val TRAVEL_DAMAGE_MULT: Double by builder
.comment("Multiplier of current nail force (base of MAX_TRAVEL) for damage values")
.comment("That is, any being on path of nail will get damaged by nail power multiplied by this")
.defineInRange("TRAVEL_DAMAGE_MULT", 0.6, 0.0, Double.MAX_VALUE)
val TRAVEL_MAX_HEALTH_MULT: Double by builder
.comment("Multiplier of being's max health in nail path subtracted from nail force when penetrating through")
.defineInRange("TRAVEL_MAX_HEALTH_MULT", 0.4, 0.0, Double.MAX_VALUE)
val DAMAGE_BLOCKS: Boolean by builder
.comment("Should hammer (**not** explosion) damage blocks", "Blocks damaged this way will always be dropped")
.define("DAMAGE_BLOCKS", true)
val EXPLOSION_DAMAGE_BLOCKS: Boolean by builder
.comment("Should hammer **explosion** damage blocks")
.define("EXPLOSION_DAMAGE_BLOCKS", false)
val FLY_OFF_CHANCE: Double by builder
.comment("Chance that hammer will fly off hands of attacker upon impact")
.defineInRange("FLY_OFF_CHANCE", 0.6, 0.0, 1.0)
val SELF_HARM_CHANCE: Double by builder
.comment("When hammer DOES NOT fly off hands, what is chance of it damaging the user?", "(this does not account for damage because of hitting things close)", "(idiot)")
.defineInRange("SELF_HARM_CHANCE", 0.4, 0.0, 1.0)
val SELF_HARM_MIN_DAMAGE: Double by builder
.comment("When hammer DOES NOT fly off hands, what is minimal damage it deals to the user (in half of hearts)?")
.defineInRange("SELF_HARM_MIN_DAMAGE", 2.0, 0.0, Double.MAX_VALUE)
val SELF_HARM_MAX_DAMAGE: Double by builder
.comment("When hammer DOES NOT fly off hands, what is maximal damage it deals to the user (in half of hearts)?")
.defineInRange("SELF_HARM_MAX_DAMAGE", 10.0, 0.0, Double.MAX_VALUE)
val FLY_OFF_DAMAGE_CHANCE: Double by builder
.comment("When hammer flies off hands, what is chance of it damaging the user?")
.defineInRange("FLY_OFF_DAMAGE_CHANCE", 0.8, 0.0, 1.0)
val FLY_OFF_MIN_DAMAGE: Double by builder
.comment("When hammer flies off hands, what is minimal damage it deals to the user (in half of hearts)?")
.defineInRange("FLY_OFF_MIN_DAMAGE", 4.0, 0.0, Double.MAX_VALUE)
val FLY_OFF_MAX_DAMAGE: Double by builder
.comment("When hammer flies off hands, what is maximal damage it deals to the user (in half of hearts)?")
.defineInRange("FLY_OFF_MAX_DAMAGE", 25.0, 0.0, Double.MAX_VALUE)
}
init {
builder.push("ExplosiveHammer")
ExplosiveHammer
builder.pop()
}
}

View File

@ -22,6 +22,8 @@ import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
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
@ -220,6 +222,12 @@ fun BlockState.getExplosionResistance(level: BlockGetter, pos: BlockPos): Float
}
}
fun Level.gracefulBlockBreak(blockPos: BlockPos, block: BlockState = getBlockState(blockPos)) {
Block.dropResources(block, this, blockPos, getBlockEntity(blockPos))
block.block.destroy(this, blockPos, block)
setBlock(blockPos, block.fluidState.createLegacyBlock(), Block.UPDATE_ALL)
}
fun <E> MutableCollection<E>.addAll(elements: Iterator<E>) {
for (item in elements) {
add(item)

View File

@ -0,0 +1,210 @@
package ru.dbotthepony.mc.otm.item
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import net.minecraft.ChatFormatting
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.game.ClientboundExplodePacket
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.level.Explosion
import net.minecraft.world.level.Level
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.entity.player.PlayerInteractEvent
import net.minecraftforge.event.level.BlockEvent
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.config.ItemsConfig
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.getExplosionResistance
import ru.dbotthepony.mc.otm.core.gracefulBlockBreak
import ru.dbotthepony.mc.otm.core.math.component1
import ru.dbotthepony.mc.otm.core.math.component2
import ru.dbotthepony.mc.otm.core.math.component3
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.times
import ru.dbotthepony.mc.otm.core.math.toDoubleVector
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.position
import ru.dbotthepony.mc.otm.core.tagNotNull
import ru.dbotthepony.mc.otm.registry.HammerNailDamageSource
import ru.dbotthepony.mc.otm.registry.MRegistry
import ru.dbotthepony.mc.otm.triggers.NailedEntityTrigger
class ExplosiveHammerItem(durability: Int = 64) : Item(Properties().stacksTo(1).fireResistant().durability(durability)) {
override fun canBeHurtBy(pDamageSource: DamageSource): Boolean {
return super.canBeHurtBy(pDamageSource) && !pDamageSource.isExplosion
}
fun isPrimed(itemStack: ItemStack): Boolean {
return itemStack.tag?.getBoolean("primed") ?: false
}
fun prime(itemStack: ItemStack) {
itemStack.tagNotNull["primed"] = true
}
fun unprime(itemStack: ItemStack) {
if (isPrimed(itemStack))
itemStack.tagNotNull["primed"] = false
}
private val aabb = AABB(-0.1, -0.1, -0.1, 0.1, 0.1, 0.1)
fun attackAt(itemStack: ItemStack, attacker: LivingEntity, pos: Vec3, aim: Vec3, hand: InteractionHand) {
if (!isPrimed(itemStack) || attacker.level.isClientSide)
return
val (ex, ey, ez) = pos
// взрыв в месте удара молотком
val exp = Explosion(attacker.level, attacker, ex, ey, ez, 1f, false, if (ItemsConfig.ExplosiveHammer.EXPLOSION_DAMAGE_BLOCKS) Explosion.BlockInteraction.DESTROY_WITH_DECAY else Explosion.BlockInteraction.KEEP)
exp.explode()
exp.finalizeExplosion(true)
if (!ItemsConfig.ExplosiveHammer.EXPLOSION_DAMAGE_BLOCKS)
exp.clearToBlow()
val level = attacker.level as ServerLevel
val hitEntities = ObjectArraySet<LivingEntity>()
hitEntities.add(attacker)
var canTravel = ItemsConfig.ExplosiveHammer.MAX_TRAVEL
var rayPos = pos
var lastBlockPos: BlockPos? = null
// "полёт" гвоздя
// так как у меня алгоритм "своеобразный", я изобрету велосипед ибо я такой нехороший и непослушный
// Трассировка луча используя наивный метод
while (canTravel > 0.0) {
val blockPos = BlockPos(rayPos)
if (blockPos != lastBlockPos) {
if (!level.hasChunkAt(blockPos)) break
val block = level.getBlockState(blockPos)
if (!block.isAir) {
val resist = block.getExplosionResistance(level, blockPos) * ItemsConfig.ExplosiveHammer.TRAVEL_EXPLOSION_RESIST_MULT
if (resist >= 0.0) {
if (resist <= canTravel) {
canTravel -= resist
val cond = ItemsConfig.ExplosiveHammer.DAMAGE_BLOCKS &&
(attacker !is Player || level.mayInteract(attacker, blockPos) && !MinecraftForge.EVENT_BUS.post(BlockEvent.BreakEvent(level, blockPos, block, attacker)))
if (cond) {
level.gracefulBlockBreak(blockPos, block)
}
} else {
break
}
}
}
lastBlockPos = blockPos
}
val rayBox = aabb.move(rayPos)
val entities = level.getEntities(null, rayBox) { it is LivingEntity && it.isAlive && !it.isSpectator && hitEntities.add(it) } as List<LivingEntity>
val damageSource = HammerNailDamageSource(attacker, itemStack)
for (it in entities) {
val damage = canTravel * ItemsConfig.ExplosiveHammer.TRAVEL_DAMAGE_MULT
canTravel -= it.maxHealth * ItemsConfig.ExplosiveHammer.TRAVEL_MAX_HEALTH_MULT
it.hurt(damageSource, damage.toFloat())
if (attacker is ServerPlayer) {
NailedEntityTrigger.trigger(attacker, it, damage.toFloat(), damageSource)
}
}
val delta = canTravel.coerceAtMost(0.25)
canTravel -= delta
rayPos += aim * delta
}
MatteryBlockEntity.watchingPlayers(pos, attacker.level)
.filter { it.position.distanceTo(pos) <= 64.0 }
.forEach {
it.connection.send(ClientboundExplodePacket(ex, ey, ez, 1f, exp.toBlow, exp.hitPlayers[it]))
}
if (attacker !is Player || !attacker.isCreative) {
unprime(itemStack)
itemStack.hurtAndBreak(1, attacker) {
it.broadcastBreakEvent(hand)
}
if (!itemStack.isEmpty && attacker.random.nextDouble() <= ItemsConfig.ExplosiveHammer.FLY_OFF_CHANCE) {
attacker.setItemInHand(hand, ItemStack.EMPTY)
val (x, y, z) = attacker.position
val entity = ItemEntity(attacker.level, x, y, z, itemStack)
val (xv, yv, zv) = attacker.getViewVector(0f)
entity.deltaMovement = Vec3(
attacker.random.nextDouble() * xv * -0.5,
attacker.random.nextDouble() * yv * -0.5,
(attacker.random.nextDouble() + 0.65) * zv * -2.0,
)
attacker.level.addFreshEntity(entity)
if (attacker.random.nextDouble() <= ItemsConfig.ExplosiveHammer.FLY_OFF_DAMAGE_CHANCE) {
attacker.invulnerableTime = 0
val dmg = ItemsConfig.ExplosiveHammer.FLY_OFF_MIN_DAMAGE + attacker.random.nextDouble() * (ItemsConfig.ExplosiveHammer.FLY_OFF_MAX_DAMAGE - ItemsConfig.ExplosiveHammer.FLY_OFF_MIN_DAMAGE)
attacker.hurt(MRegistry.DAMAGE_EXPLOSIVE_HAMMER, dmg.toFloat())
}
} else if (attacker.random.nextDouble() <= ItemsConfig.ExplosiveHammer.SELF_HARM_CHANCE) {
attacker.invulnerableTime = 0
val dmg = ItemsConfig.ExplosiveHammer.SELF_HARM_MIN_DAMAGE + attacker.random.nextDouble() * (ItemsConfig.ExplosiveHammer.SELF_HARM_MAX_DAMAGE - ItemsConfig.ExplosiveHammer.SELF_HARM_MIN_DAMAGE)
attacker.hurt(MRegistry.DAMAGE_EXPLOSIVE_HAMMER, dmg.toFloat())
}
}
}
override fun hurtEnemy(pStack: ItemStack, pTarget: LivingEntity, pAttacker: LivingEntity): Boolean {
val aim = pAttacker.getViewVector(0f)
val pos = pAttacker.eyePosition + aim * pTarget.position.distanceTo(pAttacker.position)
attackAt(pStack, pAttacker, pos, aim, InteractionHand.MAIN_HAND)
return true
}
override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltipComponents: MutableList<Component>, pIsAdvanced: TooltipFlag) {
super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced)
pTooltipComponents.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.DARK_GRAY))
if (isPrimed(pStack)) {
pTooltipComponents.add(TranslatableComponent("$descriptionId.primed").withStyle(ChatFormatting.GRAY))
} else {
pTooltipComponents.add(TranslatableComponent("$descriptionId.not_primed0").withStyle(ChatFormatting.GRAY))
pTooltipComponents.add(TranslatableComponent("$descriptionId.not_primed1").withStyle(ChatFormatting.GRAY))
}
}
companion object {
fun onLeftClickBlock(event: PlayerInteractEvent.LeftClickBlock) {
val item = event.itemStack.item
if (item is ExplosiveHammerItem) {
val (x, y, z) = event.pos
item.attackAt(event.itemStack, event.entity, Vec3(x + 0.5, y + 0.5, z + 0.5), event.face?.opposite?.normal?.toDoubleVector() ?: event.entity.getViewVector(0f), event.hand)
event.isCanceled = true
}
}
}
}

View File

@ -0,0 +1,109 @@
package ru.dbotthepony.mc.otm.recipe
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import net.minecraft.core.NonNullList
import net.minecraft.data.recipes.FinishedRecipe
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.CraftingBookCategory
import net.minecraft.world.item.crafting.CraftingRecipe
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.item.crafting.RecipeSerializer
import net.minecraft.world.level.Level
import net.minecraftforge.common.Tags
import ru.dbotthepony.mc.otm.container.stream
import ru.dbotthepony.mc.otm.core.isActuallyEmpty
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.item.ExplosiveHammerItem
import ru.dbotthepony.mc.otm.registry.MItems
class ExplosiveHammerPrimingRecipe(private val _id: ResourceLocation, val payload: Ingredient) : CraftingRecipe {
override fun isIncomplete(): Boolean {
return payload.isActuallyEmpty
}
override fun getId(): ResourceLocation {
return _id
}
override fun isSpecial(): Boolean {
return true
}
override fun matches(pContainer: CraftingContainer, pLevel: Level): Boolean {
val result = pContainer.stream().filter { it.isNotEmpty }.toList()
if (result.size != 3) return false
return result.any { it.item is ExplosiveHammerItem } &&
result.any { it.`is`(Tags.Items.GUNPOWDER) } &&
result.any { payload.test(it) }
}
override fun assemble(pContainer: CraftingContainer): ItemStack {
val hammer = pContainer.stream().filter { it.isNotEmpty && it.item is ExplosiveHammerItem }.findAny()
if (hammer.isEmpty) return ItemStack.EMPTY
return hammer.orElseThrow().copyWithCount(1).also {
(it.item as ExplosiveHammerItem).prime(it)
}
}
override fun canCraftInDimensions(pWidth: Int, pHeight: Int): Boolean {
return pWidth * pHeight >= 3
}
override fun getResultItem(): ItemStack {
return ItemStack.EMPTY
}
override fun getSerializer(): RecipeSerializer<*> {
return Companion
}
override fun category(): CraftingBookCategory {
return CraftingBookCategory.EQUIPMENT
}
override fun getIngredients(): NonNullList<Ingredient> {
return NonNullList.of(Ingredient.of(), Ingredient.of(MItems.EXPLOSIVE_HAMMER), Ingredient.of(Tags.Items.GUNPOWDER), payload)
}
val finishedRecipe = object : FinishedRecipe {
override fun serializeRecipeData(pJson: JsonObject) {
pJson["payload"] = payload.toJson()
}
override fun getId(): ResourceLocation {
return _id
}
override fun getType(): RecipeSerializer<*> {
return Companion
}
override fun serializeAdvancement(): JsonObject? {
return null
}
override fun getAdvancementId(): ResourceLocation? {
return null
}
}
companion object : RecipeSerializer<ExplosiveHammerPrimingRecipe> {
override fun fromJson(pRecipeId: ResourceLocation, pSerializedRecipe: JsonObject): ExplosiveHammerPrimingRecipe {
return ExplosiveHammerPrimingRecipe(pRecipeId, Ingredient.fromJson(pSerializedRecipe["payload"] ?: throw JsonSyntaxException("Missing `payload` from hammer priming recipe in $pRecipeId")))
}
override fun fromNetwork(pRecipeId: ResourceLocation, pBuffer: FriendlyByteBuf): ExplosiveHammerPrimingRecipe {
return ExplosiveHammerPrimingRecipe(pRecipeId, Ingredient.fromNetwork(pBuffer))
}
override fun toNetwork(pBuffer: FriendlyByteBuf, pRecipe: ExplosiveHammerPrimingRecipe) {
pRecipe.payload.toNetwork(pBuffer)
}
}
}

View File

@ -146,6 +146,9 @@ internal fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) {
energized(MItems.ENERGY_SWORD)
energized(MItems.PLASMA_RIFLE)
accept(MItems.EXPLOSIVE_HAMMER)
accept(ItemStack(MItems.EXPLOSIVE_HAMMER).also { MItems.EXPLOSIVE_HAMMER.prime(it) })
accept(MItems.BLACK_HOLE_SCANNER)
accept(MItems.GRAVITATION_FIELD_LIMITER)
accept(MItems.GRAVITATION_FIELD_SENSOR)

View File

@ -184,3 +184,9 @@ class PlasmaDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) :
return false
}
}
class HammerNailDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : MatteryDamageSource(MRegistry.DAMAGE_HAMMER_NAIL_NAME, entity, inflictor) {
override fun scalesWithDifficulty(): Boolean {
return false
}
}

View File

@ -215,6 +215,8 @@ object MItems {
::TRITANIUM_BOOTS
)
val EXPLOSIVE_HAMMER: ExplosiveHammerItem by registry.register("explosive_hammer") { ExplosiveHammerItem() }
val ENERGY_SWORD: Item by registry.register(MNames.ENERGY_SWORD) { EnergySwordItem() }
val PLASMA_RIFLE: Item by registry.register(MNames.PLASMA_RIFLE) { PlasmaRifleItem() }

View File

@ -44,6 +44,7 @@ import ru.dbotthepony.mc.otm.triggers.BecomeHumaneTrigger
import ru.dbotthepony.mc.otm.triggers.BlackHoleTrigger
import ru.dbotthepony.mc.otm.triggers.EnderTeleporterFallDeathTrigger
import ru.dbotthepony.mc.otm.triggers.FallDampenersSaveTrigger
import ru.dbotthepony.mc.otm.triggers.NailedEntityTrigger
import ru.dbotthepony.mc.otm.triggers.NanobotsArmorTrigger
import ru.dbotthepony.mc.otm.triggers.ShockwaveDamageMobTrigger
import ru.dbotthepony.mc.otm.triggers.ShockwaveTrigger
@ -195,6 +196,8 @@ object MRegistry {
const val DAMAGE_SHOCKWAVE_NAME = "otm_shockwave"
const val DAMAGE_PLASMA_NAME = "otm_plasma"
const val DAMAGE_COSMIC_RAYS_NAME = "otm_cosmic_rays"
const val DAMAGE_EXPLOSIVE_HAMMER_NAME = "otm_explosive_hammer"
const val DAMAGE_HAMMER_NAIL_NAME = "otm_hammer_nail"
val DAMAGE_EXOPACK_PROBE = ImmutableDamageSource(DamageSource(DAMAGE_EXOPACK_PROBE_ID).bypassArmor().bypassMagic())
@ -203,6 +206,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))
val DAMAGE_COSMIC_RAYS = ImmutableDamageSource(DamageSource(DAMAGE_COSMIC_RAYS_NAME).bypassArmor().bypassMagic())
val DAMAGE_EXPLOSIVE_HAMMER = ImmutableDamageSource(DamageSource(DAMAGE_EXPLOSIVE_HAMMER_NAME))
val DAMAGE_EMP: DamageSource = ImmutableDamageSource(EMPDamageSource())
@ -270,6 +274,7 @@ object MRegistry {
CriteriaTriggers.register(EnderTeleporterFallDeathTrigger)
CriteriaTriggers.register(KillAsAndroidTrigger)
CriteriaTriggers.register(AndroidTravelUnderwater)
CriteriaTriggers.register(NailedEntityTrigger)
}
}

View File

@ -0,0 +1,61 @@
package ru.dbotthepony.mc.otm.triggers
import com.google.gson.JsonObject
import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance
import net.minecraft.advancements.critereon.DamagePredicate
import net.minecraft.advancements.critereon.DamageSourcePredicate
import net.minecraft.advancements.critereon.DeserializationContext
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.advancements.critereon.MinMaxBounds
import net.minecraft.advancements.critereon.SerializationContext
import net.minecraft.advancements.critereon.SimpleCriterionTrigger
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.LivingEntity
import ru.dbotthepony.mc.otm.core.set
abstract class HurtTrigger : SimpleCriterionTrigger<HurtTrigger.Instance>() {
abstract val ID: ResourceLocation
override fun getId(): ResourceLocation {
return ID
}
override fun createInstance(
p_66248_: JsonObject,
p_66249_: EntityPredicate.Composite,
p_66250_: DeserializationContext
): Instance {
return Instance(
EntityPredicate.Composite.fromJson(p_66248_, "entity_predicate", p_66250_),
(p_66248_["damage"] as? JsonObject)?.let(DamagePredicate::fromJson) ?: DamagePredicate.ANY
)
}
fun trigger(player: ServerPlayer, entity: LivingEntity, damage: Float, damageSource: DamageSource) {
val context = EntityPredicate.createContext(player, entity)
trigger(player) {
it.predicate.matches(context) && it.damagePredicate.matches(player, damageSource, damage, damage, false)
}
}
inner class Instance(
val predicate: EntityPredicate.Composite = EntityPredicate.Composite.ANY,
val damagePredicate: DamagePredicate = DamagePredicate(
MinMaxBounds.Doubles.atLeast(1.0),
MinMaxBounds.Doubles.atLeast(1.0),
EntityPredicate.ANY,
null,
DamageSourcePredicate.ANY
)
) : AbstractCriterionTriggerInstance(ID, EntityPredicate.Composite.ANY) {
override fun serializeToJson(pConditions: SerializationContext): JsonObject {
return super.serializeToJson(pConditions).also {
it["entity_predicate"] = predicate.toJson(pConditions)
it["damage"] = damagePredicate.serializeToJson()
}
}
}
}

View File

@ -0,0 +1,8 @@
package ru.dbotthepony.mc.otm.triggers
import net.minecraft.resources.ResourceLocation
import ru.dbotthepony.mc.otm.OverdriveThatMatters
object NailedEntityTrigger : HurtTrigger() {
override val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "hammer_nail_damage")
}

View File

@ -1,63 +1,8 @@
package ru.dbotthepony.mc.otm.triggers
import com.google.gson.JsonObject
import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance
import net.minecraft.advancements.critereon.DamagePredicate
import net.minecraft.advancements.critereon.DamageSourcePredicate
import net.minecraft.advancements.critereon.DeserializationContext
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.advancements.critereon.MinMaxBounds
import net.minecraft.advancements.critereon.SerializationContext
import net.minecraft.advancements.critereon.SimpleCriterionTrigger
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.Entity
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.set
object ShockwaveDamageMobTrigger: SimpleCriterionTrigger<ShockwaveDamageMobTrigger.Instance>() {
val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "shockwave_damage_mob")
override fun getId(): ResourceLocation {
return ID
}
override fun createInstance(
p_66248_: JsonObject,
p_66249_: EntityPredicate.Composite,
p_66250_: DeserializationContext
): Instance {
return Instance(
EntityPredicate.Composite.fromJson(p_66248_, "entity_predicate", p_66250_),
(p_66248_["damage"] as? JsonObject)?.let(DamagePredicate::fromJson) ?: DamagePredicate.ANY
)
}
fun trigger(player: ServerPlayer, entity: Entity, damageSource: DamageSource, damage: Float) {
val context = EntityPredicate.createContext(player, entity)
trigger(player) {
it.predicate.matches(context) && it.damagePredicate.matches(player, damageSource, damage, damage, false)
}
}
class Instance(
val predicate: EntityPredicate.Composite = EntityPredicate.Composite.ANY,
val damagePredicate: DamagePredicate = DamagePredicate(
MinMaxBounds.Doubles.atLeast(1.0),
MinMaxBounds.Doubles.atLeast(1.0),
EntityPredicate.ANY,
null,
DamageSourcePredicate.ANY
)
) : AbstractCriterionTriggerInstance(ID, EntityPredicate.Composite.ANY) {
override fun serializeToJson(p_16979_: SerializationContext): JsonObject {
return super.serializeToJson(p_16979_).also {
it["entity_predicate"] = predicate.toJson(p_16979_)
it["damage"] = damagePredicate.serializeToJson()
}
}
}
object ShockwaveDamageMobTrigger : HurtTrigger() {
override val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "shockwave_damage_mob")
}