Captain Redstone Keyes

This commit is contained in:
DBotThePony 2025-01-07 18:17:03 +07:00
parent 6f67912041
commit 9621356682
Signed by: DBot
GPG Key ID: DCC23B5715498507
13 changed files with 218 additions and 30 deletions

View File

@ -633,6 +633,11 @@ private fun blocks(provider: MatteryLanguageProvider) {
private fun items(provider: MatteryLanguageProvider) { private fun items(provider: MatteryLanguageProvider) {
with(provider.english) { with(provider.english) {
add(MItems.REDSTONE_INTERACTOR, "Redstone Key")
add(MItems.REDSTONE_INTERACTOR, "desc", "Allows to 'send' redstone signal out of any block")
add(MItems.REDSTONE_INTERACTOR, "desc1", "In other words, allows to temporarily activate redstone mechanisms, such as opening Iron Doors")
add(MItems.REDSTONE_INTERACTOR, "desc2", "Use while sneaking to set redstone timer")
add(MItems.PROCEDURAL_BATTERY, "Mythical Battery") add(MItems.PROCEDURAL_BATTERY, "Mythical Battery")
add(MItems.PROCEDURAL_BATTERY, "desc", "These batteries are found in dungeons with randomized stats") add(MItems.PROCEDURAL_BATTERY, "desc", "These batteries are found in dungeons with randomized stats")

View File

@ -634,6 +634,11 @@ private fun blocks(provider: MatteryLanguageProvider) {
private fun items(provider: MatteryLanguageProvider) { private fun items(provider: MatteryLanguageProvider) {
with(provider.russian) { with(provider.russian) {
add(MItems.REDSTONE_INTERACTOR, "Ключ красного камня")
add(MItems.REDSTONE_INTERACTOR, "desc", "'Посылает' сигнал красного камня из блока, с которым взаимодействует")
add(MItems.REDSTONE_INTERACTOR, "desc1", "Другими словами, позволяет активировать механизмы красного камня, такие как железные двери")
add(MItems.REDSTONE_INTERACTOR, "desc2", "Для настройки таймера используйте будучи крадясь")
add(MItems.PROCEDURAL_BATTERY, "Загадочный аккумулятор") add(MItems.PROCEDURAL_BATTERY, "Загадочный аккумулятор")
add(MItems.PROCEDURAL_BATTERY, "desc", "Данные аккумуляторы можно найти в подземельях, со случайными характеристиками") add(MItems.PROCEDURAL_BATTERY, "desc", "Данные аккумуляторы можно найти в подземельях, со случайными характеристиками")

View File

@ -522,4 +522,9 @@ fun addCraftingTableRecipes(consumer: RecipeOutput) {
.rowB(MItemTags.TRITANIUM_PLATES) .rowB(MItemTags.TRITANIUM_PLATES)
.build(consumer, "grill_alt_c/${color?.name?.lowercase() ?: "default"}") .build(consumer, "grill_alt_c/${color?.name?.lowercase() ?: "default"}")
} }
MatteryRecipe(MItems.REDSTONE_INTERACTOR, category = RecipeCategory.TOOLS)
.rowAB(Items.LEVER, Tags.Items.NUGGETS_IRON)
.rowB(Tags.Items.DUSTS_REDSTONE)
.build(consumer)
} }

View File

@ -0,0 +1,36 @@
package ru.dbotthepony.mc.otm.mixin;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem;
@Mixin(BlockBehaviour.BlockStateBase.class)
public abstract class BlockStateBaseMixin {
@Shadow
protected abstract BlockState asState();
@Inject(
method = "getSignal(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;)I",
at = @At("HEAD"),
cancellable = true,
remap = false
)
public void getSignal(BlockGetter p_60747_, BlockPos p_60748_, Direction p_60749_, CallbackInfoReturnable<Integer> info) {
if (p_60747_ instanceof Level level) {
int hookResult = RedstoneInteractorItem.Companion.getSignalHook(level, this.asState(), p_60748_, p_60749_);
if (hookResult != -1) {
info.setReturnValue(hookResult);
}
}
}
}

View File

@ -9,9 +9,6 @@ import net.minecraft.core.HolderLookup
import net.minecraft.server.MinecraftServer import net.minecraft.server.MinecraftServer
import net.minecraft.world.level.Level import net.minecraft.world.level.Level
import net.neoforged.api.distmarker.Dist import net.neoforged.api.distmarker.Dist
import net.neoforged.bus.api.EventPriority
import net.neoforged.bus.api.SubscribeEvent
import net.neoforged.fml.common.EventBusSubscriber
import net.neoforged.fml.loading.FMLLoader import net.neoforged.fml.loading.FMLLoader
import net.neoforged.neoforge.event.server.ServerAboutToStartEvent import net.neoforged.neoforge.event.server.ServerAboutToStartEvent
import net.neoforged.neoforge.event.server.ServerStoppedEvent import net.neoforged.neoforge.event.server.ServerStoppedEvent
@ -19,7 +16,6 @@ import net.neoforged.neoforge.event.server.ServerStoppingEvent
import net.neoforged.neoforge.event.tick.LevelTickEvent import net.neoforged.neoforge.event.tick.LevelTickEvent
import net.neoforged.neoforge.event.tick.ServerTickEvent import net.neoforged.neoforge.event.tick.ServerTickEvent
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.collect.WeakHashSet import ru.dbotthepony.mc.otm.core.collect.WeakHashSet
@ -226,6 +222,17 @@ fun tickServer(ticker: IConditionalTickable) {
postServerTick.add(ticker, SERVER_IS_LIVE, "Server is stopping") postServerTick.add(ticker, SERVER_IS_LIVE, "Server is stopping")
} }
fun Level.timer(time: Int, ticker: Runnable): TickList.Timer? {
if (this.isClientSide) return null
if (!SERVER_IS_LIVE) {
LOGGER.error("Refusing to add ticker $ticker while server is dying", IllegalStateException("Server is stopping"))
return null
}
return postWorldTick.computeIfAbsent(this) { TickList() }.timer(time, ticker)
}
fun Level.once(ticker: ITickable) { fun Level.once(ticker: ITickable) {
if (this.isClientSide) return if (this.isClientSide) return

View File

@ -56,6 +56,7 @@ import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem
import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem
import ru.dbotthepony.mc.otm.item.QuantumBatteryItem import ru.dbotthepony.mc.otm.item.QuantumBatteryItem
import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem
import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem
import ru.dbotthepony.mc.otm.matter.AbstractRegistryAction import ru.dbotthepony.mc.otm.matter.AbstractRegistryAction
import ru.dbotthepony.mc.otm.matter.IMatterFunction import ru.dbotthepony.mc.otm.matter.IMatterFunction
import ru.dbotthepony.mc.otm.matter.MatterManager import ru.dbotthepony.mc.otm.matter.MatterManager
@ -200,6 +201,8 @@ object OverdriveThatMatters {
FORGE_BUS.addListener(EventPriority.NORMAL, MCommands::register) FORGE_BUS.addListener(EventPriority.NORMAL, MCommands::register)
FORGE_BUS.addListener(EventPriority.NORMAL, RedstoneInteractorItem::onUse)
if (isCuriosLoaded) { if (isCuriosLoaded) {
FORGE_BUS.addListener(EventPriority.NORMAL, ::onCuriosSlotModifiersUpdated) FORGE_BUS.addListener(EventPriority.NORMAL, ::onCuriosSlotModifiersUpdated)
} }

View File

@ -32,6 +32,7 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.core.get
import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.set import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem
import ru.dbotthepony.mc.otm.shapes.BlockShapes import ru.dbotthepony.mc.otm.shapes.BlockShapes
class ComputerTerminalBlock(val color: DyeColor?) : RotatableMatteryBlock( class ComputerTerminalBlock(val color: DyeColor?) : RotatableMatteryBlock(
@ -44,7 +45,7 @@ class ComputerTerminalBlock(val color: DyeColor?) : RotatableMatteryBlock(
private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.COMPUTER_TERMINAL.rotateFromNorth(it[rotationProperty]).computeShape() } private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.COMPUTER_TERMINAL.rotateFromNorth(it[rotationProperty]).computeShape() }
init { init {
registerDefaultState(defaultBlockState().set(BlockStateProperties.POWERED, false).set(TICKS, TickTimer.TICK_20)) registerDefaultState(defaultBlockState().set(BlockStateProperties.POWERED, false).set(TICKS, RedstoneInteractorItem.TickTimer.TICK_20))
} }
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) { override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
@ -86,9 +87,9 @@ class ComputerTerminalBlock(val color: DyeColor?) : RotatableMatteryBlock(
): InteractionResult { ): InteractionResult {
if (ply.isShiftKeyDown) { if (ply.isShiftKeyDown) {
val current = blockState[TICKS] val current = blockState[TICKS]
val next = TickTimer.entries.getOrElse(current.ordinal + 1) { TickTimer.TICK_2 } val next = RedstoneInteractorItem.TickTimer.entries.getOrElse(current.ordinal + 1) { RedstoneInteractorItem.TickTimer.TICK_2 }
level.playSound(ply, blockPos, SoundEvents.DISPENSER_FAIL, SoundSource.BLOCKS, 1f, 1f) level.playSound(ply, blockPos, SoundEvents.DISPENSER_FAIL, SoundSource.BLOCKS, 0.3f, 1.1f)
level.gameEvent(ply, GameEvent.BLOCK_ACTIVATE, blockPos) level.gameEvent(ply, GameEvent.BLOCK_ACTIVATE, blockPos)
if (!level.isClientSide) { if (!level.isClientSide) {
@ -141,21 +142,7 @@ class ComputerTerminalBlock(val color: DyeColor?) : RotatableMatteryBlock(
return shapes[state]!! return shapes[state]!!
} }
enum class TickTimer(val ticks: Int) : StringRepresentable {
TICK_2(2),
TICK_10(10),
TICK_20(20),
TICK_30(30),
TICK_40(40);
private val str = ticks.toString()
override fun getSerializedName(): String {
return str
}
}
companion object { companion object {
val TICKS: EnumProperty<TickTimer> = EnumProperty.create("ticks", TickTimer::class.java) val TICKS: EnumProperty<RedstoneInteractorItem.TickTimer> = EnumProperty.create("ticks", RedstoneInteractorItem.TickTimer::class.java)
} }
} }

View File

@ -19,12 +19,9 @@ class TickList : ITickable {
private var nextSometime = 0 private var nextSometime = 0
inner class Timer(val timerTicks: Int, val runnable: Runnable) : Comparable<Timer> { inner class Timer(timerTicks: Int, private var runnable: Runnable?) : Comparable<Timer> {
val ringAt = ticks + timerTicks val ringAt = ticks + timerTicks
var finished = false
private set
init { init {
timers.add(this) timers.add(this)
} }
@ -33,10 +30,14 @@ class TickList : ITickable {
return ringAt.compareTo(other.ringAt) return ringAt.compareTo(other.ringAt)
} }
fun cancel() {
runnable = null
timers.remove(this)
}
fun execute() { fun execute() {
if (finished) return runnable?.run()
runnable.run() runnable = null
finished = true
} }
} }

View File

@ -0,0 +1,132 @@
package ru.dbotthepony.mc.otm.item.tool
import com.mojang.serialization.Codec
import net.minecraft.ChatFormatting
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
import net.neoforged.neoforge.event.entity.player.UseItemOnBlockEvent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
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.util.TickList
import ru.dbotthepony.mc.otm.item.MatteryItem
import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
import ru.dbotthepony.mc.otm.timer
import java.util.*
class RedstoneInteractorItem : MatteryItem(Properties().stacksTo(1)) {
init {
tooltips.add { TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.GRAY) }
tooltips.add { TranslatableComponent("$descriptionId.desc1").withStyle(ChatFormatting.GRAY) }
tooltips.add { TranslatableComponent("$descriptionId.desc2").withStyle(ChatFormatting.GRAY) }
}
private fun updateTickTimer(level: Level, player: Player, itemStack: ItemStack) {
val current = itemStack[MDataComponentTypes.TICK_TIMER] ?: TickTimer.TICK_30
val next = TickTimer.entries.getOrElse(current.ordinal + 1) { RedstoneInteractorItem.TickTimer.TICK_2 }
if (level.isClientSide) {
player.sendSystemMessage(TranslatableComponent("otm.gui.tick_timer_set", next.ticks.toString()))
// play sound in front of player so it sounds properly with HRTF enabled
val (x, y, z) = player.getEyePosition(1f) + player.getViewVector(1f)
level.playSound(player, x, y, z, SoundEvents.DISPENSER_FAIL, SoundSource.PLAYERS, 0.1f, 1.1f)
}
itemStack[MDataComponentTypes.TICK_TIMER] = next
}
override fun use(level: Level, player: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
if (player.isShiftKeyDown) {
val itemStack = player.getItemInHand(hand)
updateTickTimer(level, player, itemStack)
return InteractionResultHolder.success(itemStack)
}
return super.use(level, player, hand)
}
override fun useOn(context: UseOnContext): InteractionResult {
if (context.player?.isShiftKeyDown == true) {
updateTickTimer(context.level, context.player!!, context.itemInHand)
return InteractionResult.sidedSuccess(context.level.isClientSide())
}
context.level.playSound(context.player, context.clickedPos, SoundEvents.STONE_BUTTON_CLICK_ON, SoundSource.BLOCKS, 1f, 1f)
context.level.gameEvent(context.player, GameEvent.BLOCK_ACTIVATE, context.clickedPos)
if (context.level.isClientSide)
return InteractionResult.SUCCESS
val map = signals.computeIfAbsent(context.level) { HashMap() }
val positions = Direction.entries.map { context.clickedPos + it.normal }.toMutableList()
positions.add(context.clickedPos)
for (pos in positions) {
val shouldSendUpdate = pos !in map
val timer = context.level.timer(context.itemInHand.getOrDefault(MDataComponentTypes.TICK_TIMER, TickTimer.TICK_30).ticks) {
map.remove(pos)
context.level.updateNeighborsAt(pos, context.level.getBlockState(pos).block)
} ?: throw RuntimeException("Timer unexpectedly ended up being null")
map.put(pos, timer)?.cancel()
if (shouldSendUpdate) {
context.level.updateNeighborsAt(pos, context.level.getBlockState(pos).block)
}
}
return InteractionResult.CONSUME
}
enum class TickTimer(val ticks: Int) : StringRepresentable {
TICK_2(2),
TICK_10(10),
TICK_20(20),
TICK_30(30),
TICK_40(40);
private val str = ticks.toString()
override fun getSerializedName(): String {
return str
}
companion object {
val CODEC: Codec<TickTimer> = StringRepresentable.fromEnum(TickTimer::values)
}
}
companion object {
private val signals = WeakHashMap<Level, HashMap<BlockPos, TickList.Timer>>()
fun getSignalHook(level: Level, blockState: BlockState, blockPos: BlockPos, direction: Direction): Int {
if (signals[level]?.containsKey(blockPos) == true)
return 15
return -1
}
fun onUse(event: UseItemOnBlockEvent) {
if (event.itemStack.item is RedstoneInteractorItem) {
if ((event.itemStack.item as RedstoneInteractorItem).useOn(event.useOnContext).consumesAction())
event.cancelWithResult(ItemInteractionResult.sidedSuccess(event.level.isClientSide()))
}
}
}
}

View File

@ -167,6 +167,7 @@ private fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) {
energized(MItems.ENERGY_SWORD) energized(MItems.ENERGY_SWORD)
accept(MItems.REDSTONE_INTERACTOR)
accept(MItems.EXPLOSIVE_HAMMER) accept(MItems.EXPLOSIVE_HAMMER)
accept(ItemStack(MItems.EXPLOSIVE_HAMMER).also { MItems.EXPLOSIVE_HAMMER.prime(it) }) accept(ItemStack(MItems.EXPLOSIVE_HAMMER).also { MItems.EXPLOSIVE_HAMMER.prime(it) })

View File

@ -14,6 +14,7 @@ import ru.dbotthepony.mc.otm.capability.matter.PatternState
import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.data.DecimalCodec import ru.dbotthepony.mc.otm.data.DecimalCodec
import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem
import ru.dbotthepony.mc.otm.network.StreamCodecs import ru.dbotthepony.mc.otm.network.StreamCodecs
import java.util.UUID import java.util.UUID
@ -60,6 +61,7 @@ object MDataComponentTypes {
} }
val ITEM_FILTER: DataComponentType<ItemFilter> by registry.register("item_filter") { DataComponentType.builder<ItemFilter>().persistent(ItemFilter.CODEC).build() } val ITEM_FILTER: DataComponentType<ItemFilter> by registry.register("item_filter") { DataComponentType.builder<ItemFilter>().persistent(ItemFilter.CODEC).build() }
val TICK_TIMER: DataComponentType<RedstoneInteractorItem.TickTimer> by registry.register("tick_timer") { DataComponentType.builder<RedstoneInteractorItem.TickTimer>().persistent(RedstoneInteractorItem.TickTimer.CODEC).build() }
val EXPERIENCE: DataComponentType<Long> by registry.register("experience") { DataComponentType.builder<Long>().persistent(Codec.LONG).networkSynchronized(StreamCodecs.LONG).build() } val EXPERIENCE: DataComponentType<Long> by registry.register("experience") { DataComponentType.builder<Long>().persistent(Codec.LONG).networkSynchronized(StreamCodecs.LONG).build() }

View File

@ -68,6 +68,7 @@ import ru.dbotthepony.mc.otm.item.matter.MatterDustItem
import ru.dbotthepony.mc.otm.item.matter.PatternStorageItem import ru.dbotthepony.mc.otm.item.matter.PatternStorageItem
import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem
import ru.dbotthepony.mc.otm.item.tool.MatteryAxeItem import ru.dbotthepony.mc.otm.item.tool.MatteryAxeItem
import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem
import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem
import java.util.function.Supplier import java.util.function.Supplier
@ -320,6 +321,8 @@ object MItems {
val TRITANIUM_INGOT_BLOCK: BlockItem by registry.register(MNames.TRITANIUM_INGOT_BLOCK) { BlockItem(MBlocks.TRITANIUM_INGOT_BLOCK, DEFAULT_PROPERTIES) } val TRITANIUM_INGOT_BLOCK: BlockItem by registry.register(MNames.TRITANIUM_INGOT_BLOCK) { BlockItem(MBlocks.TRITANIUM_INGOT_BLOCK, DEFAULT_PROPERTIES) }
val TRITANIUM_BARS: BlockItem by registry.register(MNames.TRITANIUM_BARS) { BlockItem(MBlocks.TRITANIUM_BARS, DEFAULT_PROPERTIES) } val TRITANIUM_BARS: BlockItem by registry.register(MNames.TRITANIUM_BARS) { BlockItem(MBlocks.TRITANIUM_BARS, DEFAULT_PROPERTIES) }
val REDSTONE_INTERACTOR: RedstoneInteractorItem by registry.register("redstone_interactor") { RedstoneInteractorItem() }
val ESSENCE_SERVO: EssenceServoItem by registry.register("essence_servo") { EssenceServoItem() } val ESSENCE_SERVO: EssenceServoItem by registry.register("essence_servo") { EssenceServoItem() }
val ESSENCE_CAPSULE: EssenceCapsuleItem by registry.register("essence_capsule") { EssenceCapsuleItem(false) } val ESSENCE_CAPSULE: EssenceCapsuleItem by registry.register("essence_capsule") { EssenceCapsuleItem(false) }
val ESSENCE_DRIVE: EssenceCapsuleItem by registry.register("essence_drive") { EssenceCapsuleItem(true) } val ESSENCE_DRIVE: EssenceCapsuleItem by registry.register("essence_drive") { EssenceCapsuleItem(true) }

View File

@ -17,7 +17,8 @@
"MixinPlayer", "MixinPlayer",
"HopperBlockEntityMixin", "HopperBlockEntityMixin",
"DispenserBlockEntityMixin", "DispenserBlockEntityMixin",
"GuiGraphicsMixin" "GuiGraphicsMixin",
"BlockStateBaseMixin"
], ],
"client": [ "client": [
"MixinGameRenderer", "MixinGameRenderer",